您正在查看: Ethereum 分类下的文章

BTTC跨链分享

一、原理

BTTC跨链

  • 参考了Polygon,是基于侧链的公共区块链扩展解决方案;
  • 主要是通过双向锚定的跨链桥来实现与主网的链接、以及互相操作
  • 基本原理:
    • 充值:通过锁定主链上的资产,并在侧链发行相关资产;
    • 提币:如果想要回到主链,只需要销毁侧链上的资产,并在主链上解锁相关资产。
  • 实现:
    • 采用POS(Proof of Stake)机制
    • 部署多节点验证
    • 通过侧链进行智能合约的扩展
    • 兼容Ethereum链架构的智能合约及其他功能

跨链桥

跨链桥提供了一条在 侧链 和 主链 之间的可信双向交易通道。

当代币通过跨链桥传递时,它的总流通量不会被影响

  • 离开Ethereum的代币会被锁定,同时在Polygon网络上铸造与其等量的映射代币。
  • 将代币从Polygon转回Ethereum时,Polygon上的代币将被销毁,同时将解锁Ethereum上的等量原始代币。

架构

BTTC是三层架构:

  • 根链智能合约层:
    • 由部署在各公链(Ethereum / TRON / BSC)上的,一系列去中心化的智能合约组成;
    • 负责收集跨入/跨出 BTTC 的交易,validator的质押管理、委托管理,以及验证侧链状态的检查点/快照等功能;
  • Validator层:验证BitTorrent-Chain区块,定期发送Checkpoint至支持的TRON及其他区块链网络

Bridge:负责监听各链路事件,发送事件消息等。

Core:共识模块,包括Checkpoint(BitTorrent-Chain链的状态快照)的验证,Statesync事件&Staking事件的共识。

REST-Server:提供相关API服务。

实现:Delivery

  • BTTC层:区块生产者层,是一条与以太坊 (EVM) 完全兼容的,且由一组去中心化的验证人 (validators) 共同治理的 PoS 链。

Root Contracts

目前支持与BTTC跨链的根链有 TRON, Ethereum, BSC。BTTC设计的框架支持后续增加其它的公共区块链,仅需要在该公链上部署以下3类合约即可:

  • Staking管理:主要处理 BTTC 验证者节点的质押、slashing等
    • StakeManagerExtension, StakingInfo, StakeManagerProxy, ValidatorShare, StakeManager, ValidatorShareFactory 等
  • 代币映射:包括和子代币相映射的根代币所遵循的合约标准,mint权限委托的Predicate合约等
    • DummyERC20, ERC20Predicate, ERC20PredicateProxy, MintableERC20Predicate, MintableERC20PredicateProxy 及 ERC721相关的合约等
  • 资产转移及状态同步管理:deposit、withdraw tx 及状态同步所要调用的合约方法包含在里面
    • RootChain, RootChainManager, EventsHub, StateSender 等

部署的合约列表查看

Child Contracts

BTTC子链上为支持跨链所部署的合约有:

  • ChildChainManager:处理代币映射、销毁等逻辑;
  • ChildToken 模板:支持跨链的代币类型是 20、721,并支持子代币Mintable;
  • 状态同步相关:StateReceiver, ChildTunnel等。

代币映射

使用 BTTC 跨链桥需要先将 Root Token、Child Token 进行映射。

操作步骤:

  1. 实现子代币合约
    1. 标准子代币(20,721):继承项目代码中 的 ChildERC20,确保有deposit以及withdrawTo方法
    2. 自定义子代币:实现自己的子代币合约
  2. 将子代币合约部署到 BTTC 网上
    1. 参考 deploy_child_chain_contracts.js进行部署;
    2. 部署时需要 BTTC 上的 ChildChainManager 合约地址;
  3. 提交映射请求
    1. 这里提交映射请求,其中 The Token Contract Address on Ethereum/BSC/TRON 是根链上的根代币合约地址,The Token Contract Address on BTTC 是部署在BTTC上的子代币合约地址。
    2. 审核时间是3~5个工作日

根链权限

deposit 需要根链锁币,withdraw 需要解锁相关资产。

代币映射时,会指定代币类型,每种类型有对应的 Predicate 合约。

锁币:交易发起者 Approve 对应的 Predicate 合约,批准合约Predicate消费代币,将 token 从发起者账户转给 Predicate 地址;

解锁:Predicate 地址给接收者账户转 token。

Token Type Predicate
ERC20 ERC20Predicate
ERC721 ERC721Predicate
MintableERC20 MintableERC20Predicate
MintableERC721 MintableERC721Predicate

二、跨链流程

前提

进行充提币操作之前,需要先下载插件钱包并连接钱包中的账户地址。

目前BTTC网页支持2种钱包插件,分别为 TronLinkMetaMask

  • TronLink
    • 不支持签名BTTC网络交易,所以仅支持使用TronLink将资产存入BTTC
  • MetaMask
    • 支持Ethereum、BSC网络资产映射到BTTC
    • 添加BTTC网络(即自定义RPC)后,可以发送或取出BTTC网络的资产

2.1 根链 <-> BTTC

deposit

状态转移数据格式:

/* StateSynced event内数据格式如下
{
  id: counter++,
  receiver: childChainManagerAddress,
  calldata: abi.encode(
    DEPOSIT,
    abi.encode(user, rootToken, CHAIN_ID, depositeData)
  )
}
*/

type MsgEventRecord struct {
   From            // 提交tx的Delivery节点地址
   TxHash          // deposit tx 的 tx hash
   LogIndex        // 日志的index
   BlockNumber     // 日志所在的block number
   ContractAddress // StateSynced 事件的 receiver,即 ChildChainManagerAddress
   Data            // StateSynced 事件的 data
   ID              // StateSynced 事件的 id, StateSender 合约维护一个counter,每次发出 StateSynced事件则加1
   ChainID         // 子链 ID
   RootChainType   // 根链类型,TRON? ETH? BSC?
}

checkpoint

Delivery层将BTTC层生产的区块聚合成一棵Merkle树,并定期将Merkle根发布到根链,这种定期发布称为检查点

Checkpoint很重要:

  • 在根链上提供侧链的最终确定性。
  • 提供侧链提取资产到主链的燃烧证明。

checkpoint 同步流程

  1. Delivery bridge 同步到最新的BTTC new header,验证header后发现该提交checkpoint,则 当前的 proposer 为每条根链创建对应的 checkpoint tx,并发送给 BTTC 节点。该 tx.Msg 数据格式如下:
type MsgCheckpoint struct {
  Proposer        // checkpoint 的提议者
  StartBlock      // checkpoint 开始的区块number
  EndBlock        // checkpoint 结束的区块number
  RootHash        // Merkle root
  AccountRootHash 
  BorChainID      // 子链 ID
  Epoch           
  RootChainType   // 根链类型,TRON? ETH? BSC?
}

StartBlock:访问根链RootChain合约的CurrentHeaderBlock及GetHeaderInfo方法可以获取根链记录的最新checkpoint,该checkpoint.end+1即为下一个checkpoint的start;

假设正常的checkpoint [StartBlock, EndBlock] 如下:[1,128], [129, 256],...若第一个checkpoint([1, 128]),由于各种原因导致根链没有接收并更新合约内存储的内容,则要发送给该根链的 checkpoint 是 [1, 256]

  1. 这些checkpoint tx被打包进block,执行后将生成 EventTypeCheckpoint 事件的日志;validator会对执行结果进行 Precommit 投票,收集到超过2/3的 Precommit 投票才有效。
// Emit event for checkpoint
ctx.EventManager().EmitEvents(sdk.Events{
   sdk.NewEvent(
      types.EventTypeCheckpoint,
      sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
      sdk.NewAttribute(types.AttributeKeyProposer, msg.Proposer.String()),
      sdk.NewAttribute(types.AttributeKeyStartBlock, strconv.FormatUint(msg.StartBlock, 10)),
      sdk.NewAttribute(types.AttributeKeyEndBlock, strconv.FormatUint(msg.EndBlock, 10)),
      sdk.NewAttribute(types.AttributeKeyRootHash, msg.RootHash.String()),
      sdk.NewAttribute(types.AttributeKeyAccountHash, msg.AccountRootHash.String()),
   ),
})
  1. bridge 监听到该事件,proposer 将创建和广播 checkpoint tx 给对应的根链。
  2. 给 ETH、BSC 发送 checkpoint tx:获取之前提交的、针对对应根链的 checkpoint tx,将其转换成 stdTx,再封装成 tx 的 SideTxMsg,提交一个新的tx,该tx调用 RootChain 合约的 submitCheckpoint 方法,其中 SideTxMsg 以及 checkpoint tx 的投票作为参数传递。
    1. 调用 StakeManager.sol 的 checkSignatures 方法对投票进行验签
    2. 根链的 RootChainStorage 合约保存 checkpoint;
// RootChainStorage.sol
mapping(uint256 => HeaderBlock) public headerBlocks

// RootChain.sol submitCheckpoint() 
function _buildHeaderBlock(
        address proposer,
        uint256 start,
        uint256 end,
        bytes32 rootHash
    ) private returns (bool) {
  ...
  HeaderBlock memory headerBlock = HeaderBlock({
     root: rootHash,
     start: nextChildBlock,
     end: end,
     createdAt: now,
     proposer: proposer
  });

  headerBlocks[_nextHeaderBlock] = headerBlock;
  ...
}

// RootChain.sol
function getLastChildBlock() external view returns (uint256) {
   return headerBlocks[currentHeaderBlock()].end;
}
  1. ETH、BSC 执行该 tx 后,会 emit NewHeaderBlock 事件
  2. TRON 处理流程相同
  3. bridge 监听到来自根链的 NewHeaderBlock 事件后,proposer 给 BTTC 发布 checkpoint ack tx。

exit withdraw

调用RootChainManager合约的exit方法来解锁并从ERC20Predicate合约接收代币。这个方法接收一个参数:代币的销毁证明。

调用这个方法之前必须要等待包含销毁交易的checkpoint提交成功。销毁证明由RLP编码生成如下字段:

  • headerNumber:包含销毁交易的checkpoint起始块
  • blockProof:确保区块头是提交的默克尔根所在树中叶子的证明
  • blockNumber:包含销毁交易的区块号
  • blockTime:包含销毁交易的区块时间
  • txRoot:区块的交易根
  • receiptRoot:区块的receipt root
  • receipt:销毁交易的receipt
  • receiptProof:销毁交易receipt的默克尔根
  • branchMask:表示receipt在Merkle Patricia Tree中位置的一个32位参数
  • receiptLogIndex:用于从receipt中读取的日志索引

销毁证明可以自己生成,也可以调用 BTTC SDK 生成

2.2 根链 <-> 根链

TRON、Ethereum、BSC网络之间的跨链

  • 资产先存入 BTTC 网络,再取出至目标链网络;
  • 目前支持此跨链方式的代币有 BTT、TRX、JST、NFT、SUN、USDD、WIN;(均为原始代币是部署在 TRON 上的代币)

以 WIN 为例:原始代币 WIN 部署在 TRON 链上,其对应的子代币合约为 WIN_t ,Ethereum 链代币对应的子代币合约为 WIN_e,BSC链代币对应的子代币合约为 WIN_b。

子代币合约

WIN_e、WIN_b 两个合约与 WIN_t 所映射的子代币合约略有不同:

  • 增加了 originToken 成员变量(部署时设置 WIN_t 为其 originToken);
  • 增加了两个方法:
    • swapIn:用 originToken 换取子代币
      • 将 originToken 从交易发起者账户里转给子代币合约地址;
      • 为交易发起者账户 mint 子代币;(与deposit不同的是,交易发起者即可进行 mint,不需要 DEPOSITOR_ROLE 权限)
    • swapOut:用子代币换取 originToken
      • 交易发起者账户 burn 子代币;
      • 将 originToken 从子代币合约地址转给交易发起者账户;

根链代币合约

原始代币:部署在 TRON 上

其它根链代币:非标准代币,继承自 IMintableERC20,部署时需要指定主链上的 MintableAssetPredicate(例如:MintableERC20Predicate。Asset表示资产类型,下同) 合约为铸币者

MintableERC20Predicate 与 ERC20Predicate 的 exit() 方法实现不同:

  • ERC20Predicate:直接从 Predicate 地址转账给 withdrawer;
  • MintableERC20Predicate:若 Predicate 地址没有足够的余额,可先给 Predicate mint 不足的余额,再转给 withdrawer;

TRON给其它根链转账

操作:TRON 的账户 Addr_t 签名发起交易,转 100 WIN 给 BSC 的账户 Addr_b;

步骤 TRON Addr_t BTTC Addr_b BTTC Addr(WIN_b) BSC Addr_b
充值 -100 WIN +100 WIN_t (mint)
提币1: swapIn -100 WIN_t, +100 WIN_b (mint) +100 WIN_t
提币2: withdrawTo -100 WIN_b (burn)
收币 +100 WIN

其它根链给TRON转账

操作:从 BSC 的账户 Addr_b 转100 WIN给 TRON 的账户 Addr_t;

步骤 BSC Addr_b BTTC Addr_b BTTC Addr(WIN_b) TRON Addr_t
充值 -100 WIN +100 WIN_b (mint)
提币1: swapOut -100 WIN_b (burn), +100 WIN_t -100 WIN_t
提币2: withdrawTo -100 WIN_t (burn)
收币 +100 WIN

BSC给Ethereum转账

操作:从 BSC 的账户 Addr_b 转100 WIN给 Ethereum 的账户 Addr_e;

步骤 BSC Addr_b BTTC Addr_b BTTC Addr(WIN_b) BTTC Addr(WIN_e) Ethereum Addr_e
充值 -100 WIN +100 WIN_b (mint)
提币1: swapOut -100 WIN_b (burn), +100 WIN_t -100 WIN_t
提币2: swapIn -100 WIN_t (burn), +100 WIN_e +100 WIN_t
提币3: withdrawTo -100 WIN_e (burn)
收币 +100 WIN

Ethereum给BSC转账

操作:从 Ethereum 的账户 Addr_e 转100 WIN给 BSC 的账户 Addr_b;

步骤 Ethereum Addr_e BTTC Addr_e BTTC Addr(WIN_b) BTTC Addr(WIN_e) BSC Addr_b
充值 -100 WIN +100 WIN_e (mint)
提币1: swapOut -100 WIN_e (burn), +100 WIN_t +100 WIN_t
提币2: swapIn -100 WIN_t (burn), +100 WIN_b -100 WIN_t
提币3: withdrawTo -100 WIN_b (burn)
收币 +100 WIN

2.3 关于 BTT

BTT在各条链上的代币合约:

TRON:

  • 继承自 TRC20 的代币,合约创建者初始有 9900 1e8 1e18 * 1e3;
  • BTTC上映射的子代币:原始代币,由 GENESIS 创建,对应的合约地址是 0x0000000000000000000000000000000000001010;

genesis-contracts/bttc-contracts/contracts/child/MRC20.sol

  • 同其它子代币不一样的是,deposit 与 withdraw 方法并不调用 mint 或 burn,而是直接 transfer

Ethereum & BSC:

  • 继承自 IMintableERC20 的代币;
  • BTTC上映射的子代币:继承自 ChildERC20 的代币,有 swapIn、swapOut;
    • 其它子代币:
      • 需要与 originToken 进行兑换;
      • 同时要调用 mint、burn 方法,发送事件 Transfer(address(0), account, amount);
    • BTT子代币:
      • 没有 originToken,不需要兑换;
      • 直接操作 msg.sender 的 balance 及 _totalSupply,达到 mint、burn 的效果,发送事件 Transfer(address(0x1), msg.sender, msg.value);

三、总结

3.1 安全

使用侧链进行跨链的风险:

  • 侧链自身的安全性
    • 一旦侧链出现故障,转移到侧链的资产便极有可能丢失;
    • BTTC部署了多个节点进行验证和状态同步(StateSync, Checkpoint)
  • 跨链过程的安全性
    • 公证人和侧链的运行节点一旦作恶,便可以从主链上转移用户的资产;
    • BTTC侧链采用PoS共识机制,实现了一定程度上的去中心化;

每个侧链交易都会收集Precommit投票,只有超过2/3的节点进行了投票,交易结果才有效;

根链同步BTTC的staking情况,能验证针对侧链状态的Precommit投票。

3.2 vs Polygon

  • 跨链桥
    • Polygon 有 Plasma 桥、PoS 桥
      • Plasma 比 PoS 更安全,但资金退出流程复杂,有7天的挑战期;
    • BTTC 只有 PoS 桥
  • 支持跨链的公共区块链
    • Polygon 目前仅支持 Ethereum;
    • BTTC 支持 TRON/Ethereum/BSC;

3.3 侧链 vs 中继

侧链 中继
从属关系 从属于主链 没有
作用 区块链的可扩展性 跨链数据的传输
实现 将主链上的资产转移到侧链上来处理 从各主链抽象分离出来的一个跨链操作层
代表 Polygon,BTTC Cosmos,Polkadot

BTTC层初始化配置

BTTC层初始化配置

创世块包含配置网络的所有基本信息。它基本上是 Bor 链的配置文件。要启动 Bor 链,用户需要将文件的位置作为参数传递。

Borgenesis.json用作创世块和参数。这是 Bor 创世纪的一个例子config

"config": {
    "chainId": 15001,
    "homesteadBlock": 1,
    "eip150Block": 0,
    "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "eip155Block": 0,
    "eip158Block": 0,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0,
    "bor": {
      "period": 1,
      "producerDelay": 4,
      "sprint": 64,
      "backupMultiplier": 2,
      "validatorContract": "0x0000000000000000000000000000000000001000",
      "stateReceiverContract": "0x0000000000000000000000000000000000001001"
    }
  }
bor配置参数解释

period:出块时间

producerDelay:两次sprint之间的时间

sprint:每次sprint的块数

backupMultiplier:确定摆动时间的备用乘数 「后加」

validatorContract:Bor验证者集创始合约地址

stateReceiverContract:状态接收者创世合约地址

bor初始验证者地址设置

bttc层bor共识第一个sprint周期内的验证者集合,是从初识化合约中硬编码的数据获取的,

对于genesis中alloc->0000000000000000000000000000000000001000->code

默认的验证者地址是在BorValidatorSet.sol合约中的getInitialValidators设置的(查看代码

/// Get current validator set (last enacted or initial if no changes ever made) with current stake.
  function getInitialValidators() public view returns (address[] memory, uint256[] memory) {
    address[] memory addrs = new address[](3);
    addrs[0] = 0xfA841eAAcf03598bAadF0266eF6097C654DE5465;
    addrs[1] = 0x80CFb197Be875eE45294AC31406E6483e3eAb02E;
    addrs[2] = 0x0aB3ab4542ED5FA2A67B2B8DAbC82C42162853A6;
    uint256[] memory powers = new uint256[](3);
    powers[0] = 1;
    powers[1] = 1;
    powers[2] = 1;
    return (addrs, powers);
  }

对于私链的自定义可以通过提供的模版方式进行修改

首先clone https://github.com/bttcprotocol/genesis-contracts.git

然后修改初始化验证者列表,为自己需要的地址集合

https://github.com/bttcprotocol/genesis-contracts/blob/master/validators.json

然后就可以生成我们自己的初始合约

node generate-borvalidatorset.js --bttc-chain-id 9527 --delivery-chain-id delivery-9527
--bttc-chain-id:是对应bttc层的链id
--delivery-chain-id: 是设置delivery层的链id

重新创建后getInitialValidators中获取的就是我们自定义的初始话验证者地址集合了

使用genesis初始化bttc层启动后,第一个sprint周期内,会使用自定义的验证者集合进行出块,

查询此初始期间block的miner为0x0000000000000000000000000000000000000000

默认的话,下一周期开始与delivery层进行数据获取 // TODO

如果是为了单独测试bttc层,避免delivery层的干扰,可以禁用delivery层,也就是第一个sprint周期后,也不会向

delivery层获取验证者集合,一直消费合约初始化的验证者集合。

关闭delivery层链接的方式是,启动参数添加

--bor.withoutheimdall

需要注意,当前最新代码,该withoutheimdall参数目前只支持启动参数传入,config方式参数未支持。

如果添加参数后,将会执行WithoutHeimdall判断逻辑,相应的代码逻辑为

miner->commitNewWork->commit->FinalizeAndAssemble->(headerNumber%c.config.Sprint == 0)->checkAndCommitSpan->needToCommitSpan->fetchAndCommitSpan->WithoutHeimdall
getNextHeimdallSpanForTest / HeimdallClient.FetchWithRetry

周期验证者地址确认后,将和其他pos链一样,各个当前周期验证者进行依次出块,以及区块同步,这里就不多做解释了

bttc官方提供的脚本环境

主网:https://github.com/bttcprotocol/launch/tree/master/mainnet-v1/without-sentry/bttc

测试网:https://github.com/bttcprotocol/launch/tree/master/testnet-1029/without-sentry/bttc

"bor": {
      "period": 2,
      "producerDelay": 6,
      "sprint": 64,
      "backupMultiplier": 2,
      "validatorContract": "0x0000000000000000000000000000000000001000",
      "stateReceiverContract": "0x0000000000000000000000000000000000001001",
      "overrideStateSyncRecords": {
      },

Bittorrent-Chain私链搭建 - BTTC层(一)

部署仅在技术方案调研角度,客观测试,仅作记录
先草稿记录,后续再做整理

现有主网或者测试网

对于现有主网或者测试网节点的加入可参考
https://github.com/bttcprotocol/launch
环境进行运行,注意下脚本默认运行目录在home目录,可以通过执行--home 参数进行相应变更。

私链搭建

一方面为了技术调研,目前需要做的是搭建一个完整的BTTC私链
主要分为三部分进行测试部署

1. BTTC生产层

github: https://github.com/bttcprotocol/bttc.git
对于bttc,是从go-ethereum修改而来,简单来说这一层负责区块的生产
https://doc.bt.io/zh-Hans/docs/basics/bttc-basics/bttc-pos-architecture#bttc-%E5%8C%BA%E5%9D%97%E7%94%9F%E4%BA%A7%E8%80%85%E5%B1%82

2. Delivery层

github: https://github.com/bttcprotocol/delivery
负责区块验证、Bttc层区块生产者的选择、代表Bttc层状态检查点的验证和提交,以及其他各种责任。
https://doc.bt.io/zh-Hans/docs/basics/bttc-basics/bttc-pos-architecture#delivery-pos%E5%B1%82

3. 部署在TRON网络上的质押管理合约

Staking合约

从出块共识来说

简单来说,相对其他go-ethereum修改的pos链,Bittorrent-Chain的选举是在TRON网络上的质押管理合约中的,然后通过Delivery层进行数据的验证者的排名,然后bttc更新验证者排名时从Delivery层获取,作为下次出块周期的验证者列表。

测试私链

根据bttc的三层实现,计划私链的启动顺序如下

  1. 仅bttc层,完成私链搭建
    来验证bttc层genesis参数,初始化周期验证者的选择逻辑,以及一些启动参数的测试
  2. TRON测试网络部署相关合约
    来验证合约的编译和部署过程,为下一步与Delivery层结合做准备
  3. bttc + Delivery层 + TRON网络上的质押管理合约
    整体测试

bttc层私链搭建

先使用官方的测试例子,测一下
https://github.com/bttcprotocol/contracts/tree/stake/test-blockchain
从中找到genesis模板
https://github.com/bttcprotocol/contracts/blob/stake/test-blockchain/genesis.json.template
https://github.com/bttcprotocol/bttc/blob/master/tests/bor/testdata/genesis.json

从确认初始化验证者加入方式,重点关注validatorContract合约,对应地址0x0000000000000000000000000000000000001000
常规的初始化验证者的加入,时添加到extraData或者其他数组数据设置,从demo没看到对用的数据,然后调试整体代码

调试整体代码

miner->commitNewWork->commit->FinalizeAndAssemble->(headerNumber%c.config.Sprint == 0)->checkAndCommitSpan->needToCommitSpan->fetchAndCommitSpan->WithoutHeimdall
getNextHeimdallSpanForTest / HeimdallClient.FetchWithRetry

发现bttc层溜了一个测试开关,

--bor.withoutheimdall

启动参数添加后,进入Test模式,也就是bttc下一周期(genesis配置的Sprint,默认64)的验证者列表会执行getNextHeimdallSpanForTest继续从合约中初始数据中获取

GetCurrentValidators->getBorValidators

修改初始化验证者地址

默认的验证者地址是在getInitialValidators设置的
https://github.com/bttcprotocol/genesis-contracts/blob/63b72f1ca9a20064d4e700b026ca631335717b4b/contracts/BorValidatorSet.sol#L216

  /// Get current validator set (last enacted or initial if no changes ever made) with current stake.
  function getInitialValidators() public view returns (address[] memory, uint256[] memory) {
    address[] memory addrs = new address[](3);
    addrs[0] = 0xfA841eAAcf03598bAadF0266eF6097C654DE5465;
    addrs[1] = 0x80CFb197Be875eE45294AC31406E6483e3eAb02E;
    addrs[2] = 0x0aB3ab4542ED5FA2A67B2B8DAbC82C42162853A6;
    uint256[] memory powers = new uint256[](3);
    powers[0] = 1;
    powers[1] = 1;
    powers[2] = 1;
    return (addrs, powers);
  }

需要修改成我们自己的私链初始化验证者地址

编译系统合约

  1. Clone code

    git clone https://github.com/bttcprotocol/genesis-contracts.git
  2. 修改我们私链的初始化验证者地址
    https://github.com/bttcprotocol/genesis-contracts/blob/master/validators.json

    [
    {
     "address": "0xfA841eAAcf03598bAadF0266eF6097C654DE5465",
     "stake": 1,
     "balance": 300000000
    },
    {
     "address": "0x80CFb197Be875eE45294AC31406E6483e3eAb02E",
     "stake": 1,
     "balance": 300000000
    },
    {
     "address": "0x0aB3ab4542ED5FA2A67B2B8DAbC82C42162853A6",
     "stake": 1,
     "balance": 300000000
    }
    ]
  3. 生成我们私链的合约

    node generate-borvalidatorset.js --bttc-chain-id 9527 --delivery-chain-id delivery-9527
  4. Install dependencies and submodules

    $ npm install
    $ git submodule init
    $ git submodule update
  5. Compile Bttc contracts
    切记!!node需要v11版本!

    nvm install v11
    nvm use v11

    下面truffle:compile依赖docker
    https://desktop.docker.com/mac/main/amd64/Docker.dmg?utm_source=docker&utm_medium=webreferral&utm_campaign=docs-driven-download-mac-amd64

    $ cd bttc-contracts
    $ npm install
    $ node scripts/process-templates.js --bttc-chain-id <bttc-chain-id> // 9527
    $ npm run truffle:compile
    $ cd ..

附加

如果不加开关将会

--bor.heimdall "http://localhost:1317" 

第一轮(sprint 64)验证者 Signer 是从系统合约getBorValidators,获取的固定的信息,
查询此期间block的miner为
0x0000000000000000000000000000000000000000
下一周期开始与delivery层进行数据获取

参考:https://doc.bt.io/zh-Hans/docs/basics/bttc-basics/bttc-pos-architecture

参考

项目官网:https://bt.io/
github: https://github.com/bttcprotocol
官方文档:https://doc.bt.io/zh-Hans/

以太坊一个区块可以包含多少交易

今天在做以太坊开发时,突然想到一个问题,以太坊一个区块包含的交易个数由什么决定?

如果交易池中有足够的交易,一个区块最多可容纳多少交易?

带着这个问题,我阅读了一下go-ethereum中关于挖矿的源代码,找到了答案。

先抛出结论:

影响一个区块交易个数的因素有两个:

  1. 交易池中的交易个数。这个很好理解,交易池的交易个数直接决定了一个矿工可以打包多少交易到区块中。
  2. 区块允许的GasLimit。GasLimit又由父块GasLimit、GasFloor和GasCeil共同决定(1.8版本以前只受父块GasLimit影响),当交易的gas总计大于GasLimit时,交易将不在打包的区块中。
    其中gasFloor和gasCeil是在geth的中mine的两个配置项。详见文档:https://geth.ethereum.org/docs/interface/command-line-options

结论主要从"go-ethereum/miner/worker.go"源文件中得出。worker相当于一个挖矿工人,负责具体的挖矿工作流程。在worker对象中有一个“commitNewWork”方法,是创建一个新的区块,其中计算了该区块的GasLimit和需要处理的交易池中的交易。相关代码如下:

func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) {
    // 创建区块头
    header := &types.Header{
        ParentHash: parent.Hash(),
        Number:     num.Add(num, common.Big1),
        // 计算GasLimit
        GasLimit:   core.CalcGasLimit(parent, w.config.GasFloor, w.config.GasCeil),
        Extra:      w.extra,
        Time:       uint64(timestamp),
    }

    // 从交易池中读取交易
    pending, err := w.eth.TxPool().Pending()
    if err != nil {
        log.Error("Failed to fetch pending transactions", "err", err)
        return
    }

    // Short circuit if there is no available pending transactions
    if len(pending) == 0 {
        w.updateSnapshot()
        return
    }

    // Split the pending transactions into locals and remotes
    localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending
    for _, account := range w.eth.TxPool().Locals() {
        if txs := remoteTxs[account]; len(txs) > 0 {
            delete(remoteTxs, account)
            localTxs[account] = txs
        }
    }

    if len(localTxs) > 0 {
        txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs)
        if w.commitTransactions(txs, w.coinbase, interrupt) {
            return
        }
    }

    if len(remoteTxs) > 0 {
        txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs)
        if w.commitTransactions(txs, w.coinbase, interrupt) {
            return
        }
    }
}

“commitNewWork”方法中获取到交易池中的交易后将交易提交给了“commitTransactions”方法对交易进行验证。

func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool {
    // 将区块头中的GasLimit赋值给gasPool
    if w.current.gasPool == nil {
        w.current.gasPool = new(core.GasPool).AddGas(w.current.header.GasLimit)
    }

    // 循环判断所有交易
    for {
        // gasPool中的gas小于21000是跳出循环
        if w.current.gasPool.Gas() < params.TxGas {
            log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas)
            break
        }

        // 按照交易gas从大到小的顺序获取下一个交易
        tx := txs.Peek()

        // 没有交易后跳出循环
        if tx == nil {
            break
        }
        // 提交交易
        logs, err := w.commitTransaction(tx, coinbase)
    }
}

将交易提交给“commitTransaction”方法后,回调用

receipt, _, err := core.ApplyTransaction(w.config, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, vm.Config{})

在ApplyTransaction方法中会将gasPool中的gas值减去该笔交易的gas,当gasPool的gas值小于21000时,剩下的交易将不在打包的该区块中。

回头在来看一下区块中的GasLimit是怎么计算的。

core.CalcGasLimit(parent, w.config.GasFloor, w.config.GasCeil)方法在“go-ethereum/core/block_validator.go”,代码如下:

// CalcGasLimit computes the gas limit of the next block after parent. It aims
// to keep the baseline gas above the provided floor, and increase it towards the
// ceil if the blocks are full. If the ceil is exceeded, it will always decrease
// the gas allowance.
func CalcGasLimit(parent *types.Block, gasFloor, gasCeil uint64) uint64 {
    // contrib = (parentGasUsed * 3 / 2) / 1024
    contrib := (parent.GasUsed() + parent.GasUsed()/2) / params.GasLimitBoundDivisor

    // decay = parentGasLimit / 1024 -1
    decay := parent.GasLimit()/params.GasLimitBoundDivisor - 1

    /*
        strategy: gasLimit of block-to-mine is set based on parent's
        gasUsed value.  if parentGasUsed > parentGasLimit * (2/3) then we
        increase it, otherwise lower it (or leave it unchanged if it's right
        at that usage) the amount increased/decreased depends on how far away
        from parentGasLimit * (2/3) parentGasUsed is.
    */
    limit := parent.GasLimit() - decay + contrib
    if limit < params.MinGasLimit {
        limit = params.MinGasLimit
    }
    // If we're outside our allowed gas range, we try to hone towards them
    if limit < gasFloor {
        limit = parent.GasLimit() + decay
        if limit > gasFloor {
            limit = gasFloor
        }
    } else if limit > gasCeil {
        limit = parent.GasLimit() - decay
        if limit < gasCeil {
            limit = gasCeil
        }
    }
    return limit
}

计算GasLimit的策略很有意思,受父块的GasLimit、挖矿的gas上限gasCeil和gas下限gasFloor三者共同决定。

当父块的交易里的gas总和大于父块GasLimit的2/3时,则增加当前块的GasLimit,反之减少当前块的GasLimit,同时,保证GasLimit的范围在gasFloor和gasCeil之间。

转载:https://www.jianshu.com/p/3588fd52ec0a