MPCium 实测
部署
-
编译部署
git clone https://github.com/fystack/mpcium.git
cd mpcium
source /etc/profile
go env -w GOPRIVATE=github.com/bcskill/*
go mod tidy
make
可用命令
mpcium:启动 MPCium 节点mpcium-cli:用于对等方、身份和发起方配置的 CLI 实用程序
go build ./cmd/mpcium
go build ./cmd/mpcium-cli
-
启动NATS 和 Consul
cd mpcium
docker compose up -d
NATS:高性能、轻量级的 消息系统(消息队列 / 消息总线),主要用于服务之间的异步通信
Service A ───► NATS ───► Service B
▲
└─► Service C(也可同时收到消息)
Consul:HashiCorp 开源的一个用于服务网络的工具,主要用于 服务发现、配置管理、健康检查,也支持 分布式 Key-Value 存储 和 服务网格功能。
[Service A] <---> [Consul Agent] <---> [Consul Server] <---> [Service B info]
-
部署节点
3.1 创建节点配置
cd ../
mkdir test-node
cd test-node
mpcium-cli generate-peers -n 3
cp ../mpcium/config.yaml.template ./config.yaml
编辑badger_password 为16,24,32 位密码
3.2 注册链节点
mpcium-cli register-peers
3.3 生成启动器
mpcium-cli generate-initiator
从生成的event_initiator.identity.json 中复制public_key, 更新到 config.yaml中的event_initiator_pubkey
3.4 创建目录
mkdir node{0..2}
for dir in node{0..2}; do cp config.yaml peers.json "$dir/"; done
3.5 创建身份
cd node0
mpcium-cli generate-identity --node node0
cd ../node1
mpcium-cli generate-identity --node node1
cd ../node2
mpcium-cli generate-identity --node node2
cd ../
cp node0/identity/node0_identity.json node1/identity/node0_identity.json
cp node0/identity/node0_identity.json node2/identity/node0_identity.json
cp node1/identity/node1_identity.json node0/identity/node1_identity.json
cp node1/identity/node1_identity.json node2/identity/node1_identity.json
cp node2/identity/node2_identity.json node0/identity/node2_identity.json
cp node2/identity/node2_identity.json node1/identity/node2_identity.json
3.6 启动节点
cd node0
mpcium start -n node0
cd node1
mpcium start -n node1
cd node2
mpcium start -n node2
实际测试
https://github.com/fystack/mpcium-client-ts
event_initiator.key
f81851f905abc51de2618ddb7f75dde2a422dd0f6f3abb0d51c850287ecac900
创建钱包
import { connect } from "nats";
import { KeygenSuccessEvent, MpciumClient } from "../src";
import { computeAddress, hexlify } from "ethers";
import base58 from "bs58";
import * as fs from "fs";
import * as path from "path";
import { v4 } from "uuid";
async function main() {
const args = process.argv.slice(2);
const nIndex = args.indexOf("-n");
const walletCount = nIndex !== -1 ? parseInt(args[nIndex + 1]) || 1 : 1;
const nc = await connect({ servers: "nats://127.0.0.1:4222" }).catch(
(err) => {
console.error(`Failed to connect to NATS: ${err.message}`);
process.exit(1);
}
);
console.log(`Connected to NATS at ${nc.getServer()}`);
const mpcClient = await MpciumClient.create({
nc: nc,
keyPath: "./event_initiator.key",
// password: "your-password-here",
});
const walletsPath = path.resolve("./wallets.json");
let wallets: Record<string, KeygenSuccessEvent> = {};
if (fs.existsSync(walletsPath)) {
try {
wallets = JSON.parse(fs.readFileSync(walletsPath, "utf8"));
} catch (error) {
console.warn(`Could not read wallets file: ${error.message}`);
}
}
let remaining = walletCount;
mpcClient.onWalletCreationResult((event: KeygenSuccessEvent) => {
const timestamp = new Date().toISOString();
console.log(`${timestamp} Received wallet creation result:`, event);
if (event.eddsa_pub_key) {
const pubKeyBytes = Buffer.from(event.eddsa_pub_key, "base64");
const solanaAddress = base58.encode(pubKeyBytes);
console.log(`Solana wallet address: ${solanaAddress}`);
}
if (event.ecdsa_pub_key) {
const pubKeyBytes = Buffer.from(event.ecdsa_pub_key, "base64");
const uncompressedKey =
pubKeyBytes.length === 65
? pubKeyBytes
: Buffer.concat([Buffer.from([0x04]), pubKeyBytes]);
const ethAddress = computeAddress(hexlify(uncompressedKey));
console.log(`Ethereum wallet address: ${ethAddress}`);
}
wallets[event.wallet_id] = event;
fs.writeFileSync(walletsPath, JSON.stringify(wallets, null, 2));
console.log(`Wallet saved to wallets.json with ID: ${event.wallet_id}`);
remaining -= 1;
if (remaining === 0) {
console.log("All wallets generated.");
}
});
try {
for (let i = 0; i < walletCount; i++) {
const timestamp = new Date().toISOString();
const walletID = await mpcClient.createWallet(`${v4()}:${i}`);
console.log(
`${timestamp} CreateWallet sent #${
i + 1
}, awaiting result... walletID: ${walletID}`
);
}
const shutdown = async () => {
console.log("Cleaning up...");
await mpcClient.cleanup();
await nc.drain();
process.exit(0);
};
process.on("SIGINT", shutdown);
} catch (error) {
console.error("Error:", error);
await mpcClient.cleanup();
await nc.drain();
process.exit(1);
}
}
main().catch(console.error);
/mpcium-client-ts# ts-node ./examples/generate.ts
Connected to NATS at 127.0.0.1:4222
Subscribed to wallet creation results (consume mode)
result_type: 'success',
error_reason: '',
error_code: ''
}
Solana wallet address: Cmroym1zhiv6Q3DaHSRQv3CJBFnBqm4dtLkYnsAqbQ2c
Ethereum wallet address: 0xef049bd27aCD860D7A157e492378bf54eE9671B9
Wallet saved to wallets.json with ID: b7b3cb5e-fbea-4785-86c7-f67c3562ddfc:0
签名发送交易
测试地址,忽略手续费不足问题
import { connect } from "nats";
import { MpciumClient, KeyType, SigningResultEvent } from "../src";
import { ethers } from "ethers";
import { SigningResultType } from "../src/types";
import * as fs from "fs";
import * as path from "path";
// The wallet ID should be provided via command line argument
const walletId = "b7b3cb5e-fbea-4785-86c7-f67c3562ddfc:0";
// Destination wallet to send ETH to
const DESTINATION_WALLET = "0x742d35Cc6634C0532925a3b844Bc454e4438f44e";
// Amount to send in ETH
const AMOUNT_TO_SEND = "0.0001"; // 0.001 ETH
// Function to load wallet from wallets.json
function loadWallet(walletId: string) {
const walletsPath = path.resolve("./wallets.json");
try {
if (fs.existsSync(walletsPath)) {
const wallets = JSON.parse(fs.readFileSync(walletsPath, "utf8"));
if (wallets[walletId]) {
return wallets[walletId];
}
throw new Error(`Wallet with ID ${walletId} not found in wallets.json`);
} else {
throw new Error("wallets.json file not found");
}
} catch (error) {
console.error(`Failed to load wallet: ${error.message}`);
process.exit(1);
}
}
async function main() {
console.log(`Using wallet ID: ${walletId}`);
// First, establish NATS connection separately
const nc = await connect({ servers: "nats://127.0.0.1:4222" }).catch(
(err) => {
console.error(`Failed to connect to NATS: ${err.message}`);
process.exit(1);
}
);
console.log(`Connected to NATS at ${nc.getServer()}`);
// Create client with key path
const mpcClient = await MpciumClient.create({
nc: nc,
keyPath: "./event_initiator.key",
// password: "your-password-here", // Required for .age encrypted keys
});
try {
// Connect to Ethereum testnet (Sepolia)
const provider = new ethers.JsonRpcProvider(
"https://eth-sepolia.public.blastapi.io"
);
console.log("Connected to Ethereum Sepolia testnet");
// Get the wallet's public key/address
const ethAddress = await getEthAddressForWallet(walletId);
const fromAddress = ethAddress;
console.log(`Sender account: ${fromAddress}`);
console.log(`Destination account: ${DESTINATION_WALLET}`);
console.log(`Amount to send: ${AMOUNT_TO_SEND} ETH`);
// Get the current nonce for the sender address
const nonce = await provider.getTransactionCount(fromAddress);
// Get the current gas price
const feeData = await provider.getFeeData();
console.log("feeData:", feeData);
// Create an Ethereum transaction
const transaction = {
to: DESTINATION_WALLET,
value: ethers.parseEther(AMOUNT_TO_SEND),
gasLimit: 21000, // Standard gas limit for ETH transfers
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
nonce: nonce,
type: 2, // EIP-1559 transaction
chainId: 11155111, // Sepolia chain ID
};
// Calculate the transaction hash (this is what needs to be signed)
const unsignedTx = ethers.Transaction.from(transaction);
const txHash = unsignedTx.unsignedHash;
const txHashHex = txHash.substring(2); // Remove '0x' prefix
console.log(`Transaction hash: ${txHash}`);
// Subscribe to signing results
let signatureReceived = false;
mpcClient.onSignResult((event: SigningResultEvent) => {
console.log("Received signing result:", event);
signatureReceived = true;
if (event.result_type === SigningResultType.Success) {
processSuccessfulSignature(event);
} else {
console.error(`Signing failed: ${event.error_message}`);
}
});
// Process a successful signature
function processSuccessfulSignature(event: SigningResultEvent) {
try {
// For ECDSA Ethereum signatures, we need the r, s, and v (recovery) values
if (!event.r || !event.s || event.signature_recovery === null) {
console.error("Missing signature components in result:", event);
return;
}
// Convert from base64 to hex strings
const r = "0x" + Buffer.from(event.r, "base64").toString("hex");
const s = "0x" + Buffer.from(event.s, "base64").toString("hex");
// Decode signature_recovery from base64 to a number
const recoveryBuffer = Buffer.from(event.signature_recovery, "base64");
const v = recoveryBuffer[0]; // Get the first byte as the recovery value
console.log(`Signature components - r: ${r}, s: ${s}, v: ${v}`);
// Create a signed transaction
const signedTx = ethers.Transaction.from({
...transaction,
signature: { r, s, v },
});
// Verify signature
const recoveredAddress = signedTx.from;
console.log(`Recovered signer: ${recoveredAddress}`);
if (!recoveredAddress) {
console.error("Signature verification failed!");
return;
}
if (recoveredAddress.toLowerCase() !== fromAddress.toLowerCase()) {
console.error(
"Signature verification failed! Addresses don't match."
);
return;
}
console.log("Signature verification successful!");
broadcastTransaction(signedTx.serialized);
} catch (error) {
console.error("Error processing signature:", error);
if (error instanceof Error) {
console.error(error.stack);
}
}
}
// Broadcast the transaction to the network
function broadcastTransaction(signedTxHex: string) {
provider
.broadcastTransaction(signedTxHex)
.then((tx) => {
console.log(`Transaction sent! Transaction hash: ${tx.hash}`);
console.log(
`View transaction: https://sepolia.etherscan.io/tx/${tx.hash}`
);
})
.catch((err) => {
console.error("Error broadcasting transaction:", err);
});
}
// Send the transaction hash for signing, not the serialized transaction
const txId = await mpcClient.signTransaction({
walletId: walletId,
keyType: KeyType.Secp256k1,
networkInternalCode: "ethereum:sepolia",
tx: Buffer.from(txHashHex, "hex").toString("base64"), // Convert hex to base64
});
console.log(`Signing request sent with txID: ${txId}`);
// Wait for the result
await new Promise((resolve) => {
const checkInterval = setInterval(() => {
if (signatureReceived) {
clearInterval(checkInterval);
resolve(null);
}
}, 1000);
});
// Keep the process running to allow time for transaction confirmation
await new Promise((resolve) => setTimeout(resolve, 5000));
console.log("Cleaning up...");
await mpcClient.cleanup();
await nc.drain();
} catch (error) {
console.error("Error:", error);
await mpcClient.cleanup();
await nc.drain();
process.exit(1);
}
}
// Helper function to get a wallet's Ethereum address
async function getEthAddressForWallet(walletId: string): Promise<string> {
// Load wallet from wallets.json
const wallet = loadWallet(walletId);
if (wallet && wallet.ecdsa_pub_key) {
// Convert base64 public key to Ethereum address
const pubKeyBuffer = Buffer.from(wallet.ecdsa_pub_key, "base64");
// Convert the buffer to hex string with "0x" prefix
const pubKeyHex = "0x" + pubKeyBuffer.toString("hex");
// Ethereum addresses are derived from the keccak256 hash of the uncompressed public key
const address = ethers.computeAddress(pubKeyHex);
return address;
}
throw new Error(`Wallet with ID ${walletId} has no ECDSA public key`);
}
// Run the example
main().catch(console.error);
/mpcium-client-ts# ts-node ./examples/sign-eth.ts
Using wallet ID: b7b3cb5e-fbea-4785-86c7-f67c3562ddfc:0
Connected to NATS at 127.0.0.1:4222
Connected to Ethereum Sepolia testnet
Sender account: 0xef049bd27aCD860D7A157e492378bf54eE9671B9
Destination account: 0x742d35Cc6634C0532925a3b844Bc454e4438f44e
Amount to send: 0.0001 ETH
feeData: FeeData {
gasPrice: 14533302n,
maxFeePerGas: 28066604n,
maxPriorityFeePerGas: 1000000n
}
Transaction hash: 0x9c7835d42ef9643c110d5ddf038fba76a580c93a650242a1d66f5d74333bf3c7
Subscribed to signing results (consume mode)
SignTransaction request sent via JetStream for txID: c8354d11-aba5-46e8-88c2-c846b1fdac51
Signing request sent with txID: c8354d11-aba5-46e8-88c2-c846b1fdac51
Received signing result: {
result_type: 'success',
error_code: '',
error_reason: '',
is_timeout: false,
network_internal_code: 'ethereum:sepolia',
wallet_id: 'b7b3cb5e-fbea-4785-86c7-f67c3562ddfc:0',
tx_id: 'c8354d11-aba5-46e8-88c2-c846b1fdac51',
r: 'pylQvdMr+Z7N+9ADAdruVN7MTqysS0LNvVERgEx4Ktw=',
s: 'ZA4LMVBf3Di9E35C3vph3ZWUvH4GHNnRTBb7aKYHFuc=',
signature_recovery: 'AA==',
signature: null
}
Signature components - r: 0xa72950bdd32bf99ecdfbd00301daee54decc4eacac4b42cdbd5111804c782adc, s: 0x640e0b31505fdc38bd137e42defa61dd9594bc7e061cd9d14c16fb68a60716e7, v: 0
Recovered signer: 0xef049bd27aCD860D7A157e492378bf54eE9671B9
Signature verification successful!
Error broadcasting transaction: Error: insufficient funds for intrinsic transaction cost (transaction="0x02f87283aa36a780830f42408401ac432c82520894742d35cc6634c0532925a3b844bc454e4438f44e865af3107a400080c080a0a72950bdd32bf99ecdfbd00
301daee54decc4eacac4b42cdbd5111804c782adca0640e0b31505fdc38bd137e42defa61dd9594bc7e061cd9d14c16fb68a60716e7", info={ "error": { "code": -32003, "message": "insufficient funds for gas * price + value: have 0 want 100589398684000" } }, code=INSUFFICIENT_FUNDS, version=6.13.7)
at makeError (/mnt/d/github/bcskill/code/MPC/mpcium-client-ts/node_modules/ethers/src.ts/utils/errors.ts:694:21)
at JsonRpcProvider.getRpcError (/mnt/d/github/bcskill/code/MPC/mpcium-client-ts/node_modules/ethers/src.ts/providers/provider-jsonrpc.ts:1025:33)
at /mnt/d/github/bcskill/code/MPC/mpcium-client-ts/node_modules/ethers/src.ts/providers/provider-jsonrpc.ts:563:45
at processTicksAndRejections (node:internal/process/task_queues:95:5) {
code: 'INSUFFICIENT_FUNDS',
transaction: '0x02f87283aa36a780830f42408401ac432c82520894742d35cc6634c0532925a3b844bc454e4438f44e865af3107a400080c080a0a72950bdd32bf99ecdfbd00301daee54decc4eacac4b42cdbd5111804c782adca0640e0b31505fdc38bd137e42defa61dd9594bc7e061cd9d14c16fb68a60716e7',
info: {
error: {
code: -32003,
message: 'insufficient funds for gas * price + value: have 0 want 100589398684000'
}
},
shortMessage: 'insufficient funds for intrinsic transaction cost'
}
Cleaning up...
Cleaned up all subscriptions
多钱包测试
Subscribed to wallet creation results (consume mode)
CreateWallet request sent via JetStream for wallet: bbcd5019-2e7d-46aa-a1e8-5bc14371bc57:0
2025-07-14T12:57:13.424Z CreateWallet sent #1, awaiting result... walletID: bbcd5019-2e7d-46aa-a1e8-5bc14371bc57:0
2025-07-14T12:57:17.181Z Received wallet creation result: {
wallet_id: 'bbcd5019-2e7d-46aa-a1e8-5bc14371bc57:0',
ecdsa_pub_key: '2+ZZj1XCFR/OGWk9g1EMYZFyXfDNaNHCf+egn1oTo6DvR6kQ6tOBHK0oZkCL6QNsjmfi14tQN71W5n7OOXLSBg==',
eddsa_pub_key: 's8rOVjh6dMwmpXqRDRYSyOBenY/uaHBOUvumU2QFuBE=',
result_type: 'success',
error_reason: '',
error_code: ''
}
Solana wallet address: D6qLkdfZLXjHhSLAJ38UHjkYmBHU4Ac8S1oQKUDJZ2Gx
Ethereum wallet address: 0xCfcec2Beed19a40A663494396fB73042f2049dB1
Wallet saved to wallets.json with ID: bbcd5019-2e7d-46aa-a1e8-5bc14371bc57:0
测试通过
异常测试
使用相同walletID
WRN Keygen session error error="Key already exists: 81418adb-fb32-4e17-9b85-8ae207aa9cb8:0" context="Failed to create ECDSA key generation session" errorCode=ERROR_KEY_ALREADY_EXISTS walletID=81418adb-fb32-4e17-9b85-8ae207aa9cb8:0
https://github.com/fystack/mpcium/issues/56
常见问题
创建地址无返回
问题排查
nats consumer info
> Select a Stream mpc
> Select a Consumer mpc_keygen_result
查看 Waiting Pulls: 3 of maximum 512 当前有几个客户端连接
移除所有监听列表
nats consumer rm mpc mpc_keygen_resultBNB / BSC 节点中的 BLS 密钥:原理、作用与架构升级解析
随着 BNB Chain(包含 BNB Beacon Chain 与 BNB Smart Chain,简称 BSC)持续演进,其共识层经历了重要升级:从早期基于 ECDSA 的签名机制,过渡到采用 BLS(Boneh–Lynn–Shacham)聚合签名的高性能 PoSA 共识架构。
在新的节点体系中,每个验证者除了传统的 ECDSA 私钥之外,还必须生成并配置一套 BLS 密钥。本文将系统性介绍:
- BLS 是什么
- 为什么 BNB/BSC 要使用 BLS
- BLS 密钥在共识中的具体作用
- 与 ECDSA 的对比
- 架构升级带来的性能与安全变化
一、为什么 BNB/BSC 需要 BLS?
BSC 早期使用的是 PoSA(Proof of Staked Authority)+ ECDSA 签名。
这种方式在功能上可用,但存在两个核心问题:
- 签名无法聚合,每个区块需要存储和验证大量签名
- 验证者数量和区块大小难以进一步扩展
随着 BNB Chain 融合 Beacon Chain 架构、提升 TPS 的需求,BNB 引入了基于 BLS 的聚合签名技术,使共识开销更低、性能更高。
二、BLS 的核心能力
BLS(Boneh–Lynn–Shacham)最重要的特点包括以下三点。
1. 支持签名聚合(Aggregate Signature)
多个验证者的签名可以数学地合并为一个签名:
- 100 个签名
最终只需要 1 个聚合签名
区块头体积极大缩小,使出块更快、网络传输更轻。
2. 原生支持阈值签名(Threshold Signature)
可以实现多数验证者共同生成一个有效签名:
- 超过 2/3 的验证者签名
自动生成一个可验证的单一签名
无需额外的多重签名协议。
3. 验证速度快、可扩展性强
在验证区块时:
- 节点只需验证一个聚合签名
- 而不需要验证每个验证者的单独签名
这使得共识的可扩展性显著提升。
三、BLS 私钥在 BNB/BSC 中的作用
在新的 BNB Chain 共识架构中,BLS 密钥承担的是“共识身份”和“投票签名”的职责,而不是交易签名。
它主要有以下三个作用。
1. 作为验证者的共识层身份密钥
在共识过程(prevote、precommit、aggregate)中,验证者使用 BLS 私钥对投票进行签名。
链上保存的是验证者的 BLS 公钥,用于:
- 验证投票来源
- 验证者轮换
- 共识身份验证
可以理解为验证者的“共识身份证”。
2. 生成区块的 BLS 聚合签名
每个区块高度都需要验证者投票确认。
使用 BLS 后:
- 所有验证者的投票可以聚合为一个签名
- 区块头中只写入一个 BLS 聚合签名
- 节点验证速度更快,区块体积更小
这是 BNB Chain 性能提升的关键机制。
3. 支持安全的阈值共识机制
PoSA 要求超过三分之二的验证者同意区块才能最终确认。
BLS 天生支持阈值验证,并提供:
- 更简单的投票验证逻辑
- 更强的防伪造能力
- 更安全的验证者切换
让整个共识层更安全可靠。
四、为什么不是使用 ECDSA?(对比)
| 能力 | ECDSA | BLS |
|---|---|---|
| 签名聚合 | 不支持 | 支持 |
| 阈值签名 | 不支持,需要外部协议 | 原生支持 |
| 多验证者共识性能 | 一般 | 高效 |
| 区块头大小 | 大 | 小 |
| 扩展性 | 显著受限 | 强 |
| 用途 | 交易签名 | 共识投票签名 |
结论:
交易层仍然使用 ECDSA(钱包、合约交互等)。
共识层使用 BLS 才能获得高性能和更佳的扩展性。
五、BNB/BSC 架构升级后的变化
随着 BLS 的引入,BNB Chain 获得了以下提升。
1. 共识性能显著提升
减少共识消息体积、减少签名验证次数,使:
- 区块时间更稳定
- TPS 上限提升
- 网络带宽消耗更低
2. 验证者数量可扩展
BSC 早期固定 21 个验证者,使用 BLS 后可以支持更多验证者参与,而不用担心过高的签名成本。
3. 更安全的验证者管理
BLS 将验证者的共识身份抽象为统一的公钥,使身份管理更彻底、更可控。
六、BNB/BSC 节点中的两套密钥
BNB/BSC 节点实际上使用两套密钥体系,用途完全不同。
1. ECDSA Key(交易密钥)
- 用于链上交易签名
- secp256k1 曲线
- 与以太坊、MetaMask 完全一致
2. BLS Key(共识密钥)
- 仅验证者节点使用
- 只用于共识投票
- 不用于交易
- 不出现在任何钱包中
这两套密钥互不影响,各司其职。
七、总结
BLS 密钥在 BNB/BSC 中主要用于验证者共识层的投票与聚合签名,通过支持多签名聚合、阈值验证和快速校验,显著增强了区块确认速度、网络扩展性与整体安全性。它不用于交易,只用于共识层,是 BNB Chain 迈向高性能架构的重要基础组件。
BNB BSC 合约关系与方法梳理
1. 合约架构概述
核心合约
- System.sol / SystemV2.sol:基础合约,定义系统常量、地址和修饰符
- BSCValidatorSet.sol:验证者集合管理
- StakeHub.sol:验证者质押管理
- GovHub.sol:治理参数更新
- SlashIndicator.sol:验证者行为监控与惩罚
- SystemReward.sol:系统奖励管理
合约关系图

2. 各合约主要功能与方法
2.1 System.sol / SystemV2.sol
- 功能:定义系统常量、地址和修饰符
- 核心常量:各系统合约地址(VALIDATOR_CONTRACT_ADDR、SLASH_CONTRACT_ADDR等)
- 修饰符:onlyCoinbase、onlyZeroGasPrice、onlyGov等
2.2 BSCValidatorSet.sol
- 功能:管理验证者集合,处理惩罚和维护
- 对外方法:
initializeFromGenesis:从创世块初始化验证者集合updateValidatorSetV2:更新验证者集合deposit:收集交易费用并分配distributeFinalityReward:分配最终性奖励getLivingValidators:获取活跃验证者getMiningValidators:获取挖矿验证者enterMaintenance:进入维护模式exitMaintenance:退出维护模式
- 被链代码调用的方法:
updateValidatorSetV2:由共识引擎调用deposit:由区块生产者调用distributeFinalityReward:由区块生产者调用
2.3 StakeHub.sol
- 功能:验证者质押管理,包括创建验证者、委托等
- 对外方法:
createValidator:创建验证者editConsensusAddress:编辑共识地址editCommissionRate:编辑佣金率editDescription:编辑描述editVoteAddress:编辑投票地址unjail:解锁验证者delegate:委托undelegate:取消委托redelegate:重新委托claim:领取取消委托的BNB
- 被链代码调用的方法:
distributeReward:由验证者合约调用downtimeSlash:由惩罚合约调用maliciousVoteSlash:由惩罚合约调用doubleSignSlash:由惩罚合约调用
2.4 GovHub.sol
- 功能:治理参数更新
- 对外方法:
updateParam:更新参数,只能由治理时间锁合约调用
2.5 SlashIndicator.sol
- 功能:监控验证者行为,处理惩罚
- 对外方法:
slash:惩罚未出块的验证者clean:清理惩罚记录submitFinalityViolationEvidence:提交最终性违规证据submitDoubleSignEvidence:提交双重签名证据
- 被链代码调用的方法:
slash:由区块生产者调用clean:由验证者合约调用
2.6 SystemReward.sol
- 功能:系统奖励管理
- 对外方法:
claimRewards:领取奖励,只能由操作员调用
- 被链代码调用的方法:
claimRewards:由轻客户端、激励合约等调用
3. 合约调用关系
3.1 链代码调用合约
- 共识引擎 → BSCValidatorSet.updateValidatorSetV2
- 区块生产者 → BSCValidatorSet.deposit
- 区块生产者 → BSCValidatorSet.distributeFinalityReward
- 区块生产者 → SlashIndicator.slash
3.2 合约间调用
- BSCValidatorSet → StakeHub.distributeReward
- BSCValidatorSet → SlashIndicator.clean
- SlashIndicator → BSCValidatorSet.felony
- SlashIndicator → BSCValidatorSet.misdemeanor
- SlashIndicator → StakeHub.downtimeSlash
- SlashIndicator → StakeHub.maliciousVoteSlash
- SlashIndicator → StakeHub.doubleSignSlash
- GovHub → BSCValidatorSet.updateParam
- GovHub → StakeHub.updateParam
- GovHub → SlashIndicator.updateParam
- GovHub → SystemReward.updateParam
4. 接口定义
4.1 0.6.x 接口
- IBSCValidatorSet:验证者集合接口
- ISlashIndicator:惩罚接口
- IStakeHub:质押管理接口
- IParamSubscriber:参数订阅接口
- ISystemReward:系统奖励接口
4.2 0.8.x 接口
- IBSCValidatorSet:验证者集合接口
- IGovToken:治理代币接口
- IStakeCredit:质押信用接口
- IStakeHub:质押管理接口
5. 系统流程
5.1 验证者创建流程
- 用户调用 StakeHub.createValidator 创建验证者
- StakeHub 部署 StakeCredit 代理合约
- StakeHub 记录验证者信息
- 用户可以调用 delegate 进行委托
5.2 验证者惩罚流程
- 区块生产者调用 SlashIndicator.slash 惩罚未出块的验证者
- SlashIndicator 检查惩罚阈值
- 如果达到 misdemeanor 阈值,调用 BSCValidatorSet.misdemeanor
- 如果达到 felony 阈值,调用 BSCValidatorSet.felony 和 StakeHub.downtimeSlash
5.3 参数更新流程
- 治理提案通过后,GovernorTimelock 调用 GovHub.updateParam
- GovHub 调用目标合约的 updateParam 方法
- 目标合约更新参数并触发事件
6. 总结
该合约系统是一个完整的区块链验证者管理和治理系统,主要包括:
- 验证者管理:BSCValidatorSet 负责验证者集合的更新和维护
- 质押管理:StakeHub 负责验证者的质押、委托等操作
- 惩罚机制:SlashIndicator 负责监控验证者行为并处理惩罚
- 治理系统:GovHub 负责参数更新
- 奖励管理:SystemReward 负责系统奖励的分配
合约之间通过明确的接口进行交互,形成了一个完整的生态系统,确保区块链网络的安全和稳定运行。
对比 BNB Chain(BSC)旧版与新版 genesis-template.json:从 Parlia 到多硬分叉时代的全面升级
BNB Chain(BSC)近两年进行了大幅结构升级,特别是在 genesis-template.json 中,引入了多版本硬分叉、时间激活机制、Blob(EIP-4844)、EIP-1559、Deposit 接口等框架性变更,使其从传统 “Parlia 简易模式” 升级为与以太坊主网高度兼容的现代化架构。
本文将对比旧版与新版 BSC genesis-template.json 的结构变化,并解释每个字段设计背后的技术原因。
1. 旧版 BSC Genesis —— 只有 Parlia 配置的简易模式
早期版本的 BSC(包括 Mainnet 与 Testnet 很长时间)仅在 genesis 中包含 Parlia 共识参数:
"parlia": {
"period": 3,
"epoch": 200
}
1.1 字段含义
| 字段 | 作用 |
|---|---|
| period | 出块间隔(秒),默认 3 秒 |
| epoch | validator 切换周期(多少区块重新选举),默认 200 |
1.2 特点
- 结构简单
- 没有 EIP-1559
- 没有 EIP-4844(Blob)
- 没有时间控制的 Hardfork
- 与现代以太坊协议差距越来越大
旧版 BSC genesis 的核心只有 Parlia 配置,因此功能扩展能力有限。
2. 新版 BSC Genesis —— 多硬分叉 + 时间激活 + Blob 时代
BNB Chain 在 2024–2025 年陆续引入了大量升级(Feynman、Haber、Pascal、Prague、Lorentz…),genesis 文件结构变得高度现代化:
"feynmanFixTime": 0,
"cancunTime": 0,
"haberTime": 0,
"haberFixTime": 0,
"bohrTime": 0,
"pascalTime": 0,
"pragueTime": 0,
"lorentzTime": 0,
"depositContractAddress": "0x0000000000000000000000000000000000000000",
"parlia": {},
"blobSchedule": {
"cancun": {
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
},
"prague": {
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
}
}
下面逐项解析这些字段。
3. 新增字段解析(硬分叉、升级机制、Blob)
3.1 时间激活(Timestamp-based)硬分叉
新版 BSC 使用 Timestamp-based Hardfork 与以太坊主网保持一致。
| 字段 | 说明 |
|---|---|
feynmanFixTime |
Feynman 修复分叉激活时间 |
cancunTime |
Cancun 升级激活时间(支持 EIP-4844 Blob) |
haberTime |
Haber 升级 |
haberFixTime |
Haber 修复版 |
bohrTime |
Bohr 升级 |
pascalTime |
Pascal 升级 |
pragueTime |
Prague 升级 |
lorentzTime |
Lorentz 升级 |
统一规则:
设置为 0 表示链启动后立即激活所有功能
→ 适合私链 / DevNet / 测试链
→ 公链通常使用具体 Unix 时间戳
从此 BSC 不再使用区块高度(blockNumber)触发升级,而全面迁移到 ETH 的时间触发机制,升级可控性更强。
3.2 parlia 变成空对象
旧版的:
"parlia": {
"period": 3,
"epoch": 200
}
新版变成:
"parlia": {}
这是因为:
✔ 新版 BSC 内置默认值
period = 3epoch = 200
如果需要自定义,可以继续写入:
"parlia": {
"period": 1,
"epoch": 200
}
3.3 EIP-4844 Blob 交易:blobSchedule
BSC 新版开始兼容以太坊 Cancun 升级(EIP-4844),新增 Blob Gas 调整参数:
"blobSchedule": {
"cancun": {
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
},
"prague": {
"target": 3,
"max": 6,
"baseFeeUpdateFraction": 3338477
}
}
| 字段 | 说明 |
|---|---|
| target | 每个块的目标 Blob 数量 |
| max | 每块最大 blob 数量 |
| baseFeeUpdateFraction | Blob baseFee 调整参数 |
这是与 ETH 主网完全一致的 Blob Gas 定价模型,用于 Rollup / 数据可用性(DA)场景。
3.4 depositContractAddress
"depositContractAddress": "0x0000000000000000000000000000000000000000"
目前 BSC 仍是 PoSA(并非 ETH PoS),但这一字段用于兼容未来可能的信标链接口,是预留字段。
4. 对比总结:从「简单模型」到「现代以太坊兼容链」
| 项目 | 旧版 BSC | 新版 BSC |
|---|---|---|
| 共识 | Parlia | Parlia(默认配置) |
| 升级机制 | blockNumber 激活 | timestamp 激活(与 ETH 主网一致) |
| EIP-1559 | ❌ 不支持 | ✔ 支持 |
| EIP-4844 Blob | ❌ 不支持 | ✔ 支持 |
| 硬分叉版本 | 基本无 | Feynman / Haber / Pascal / Prague / Lorentz |
| genesis 结构 | 极简 | 高度模块化 |
| 可扩展性 | 较弱 | 强,接近 ETH 主网协议栈 |
新版 BSC 通过一套统一的时间激活 hardfork 机制,使其升级模型、blob gas 模型、EIP 兼容性全面向以太坊主网对齐。
5. 为什么 BSC 必须升级?
| 需求 | 原因 |
|---|---|
| 兼容 ETH 主网升级(Cancun/Prague) | 保持生态一致性 |
| 引入 Blob(EIP-4844) | 提升 DA 能力、支持 L2 |
| 统一升级机制 | 简化多版本支持 |
| 为未来 PoS 预留功能 | 兼容 depositContractAddress |
| 拓展链能力 | 支持更现代的 Gas 模型与交易类型 |
因此新版 genesis 是一个从 ETH 主网演化而来的高度可扩展结构。