https://www.toptal.com/ethereum/one-click-login-flows-a-metamask-tutorial
https://amaurym.com/login-with-metamask-demo/
Cosmos之Tendermint架构分析
一、介绍
有人把Tendermint当成一个共识,有人把它当成一个通信组件。这都是可以理解的。Tendermint融合了共识和网络通信部分。它类似于一个软件包,通过使用Tendermint可以很容易的开发一个和Cosmos相兼容的区块链(当然,如果使用Cosmos-sdk会更简单,但是会屏蔽更多的细节)。可以把它理解成Cosmos的一个底层架构,提供类似于基础服务的一个平台。Tendermint可以提供一个Cosmos标准的跨链的基础应用。
通过上面这个图可以看出Tendermint在整个Cosmos生态中的位置。Tendermint Core是所有Cosmos生态中区块链的核心(上图中的淡绿色部分),提供了DPOS+BFT的共识机制。Cosmos Hub提供了不同区块链的之间的交互和价值转移。各个区块链应用之间通过IBC接口进行通信。
二、整体架构
1、BFT
Tendermint使用的共识算法是拜占庭容错共识协议,它是来源于DLS共识算法。使用这种算法的目的是可以简单方便的解决区块链的分叉机制。这种BFT的机制要求有固定的一组验证人,然后他们会尝试在某个区块上达成共识。每个区块的共识轮流进行,每一轮都有一个提议者来发起区块,之后由验证人来决定是否接受区块或者进入下一轮投票。
Tendermint采用由绝对多数的选票(三分之二)待定的最优拜占庭算法。因此它可以确定的做到:
如果想做恶,必须要有三分之一以上的选票出现问题,并且提交了两个值。
如果任何验证组引起安全问题,就会被发现并对冲突进行投票,同时广播有问题的那些选票。
因为使用了BFT,所以其共识的速度在所有的共识中最相当快速的,很容易达到并维持每秒千笔交易的速度。
2、P2P
Tendermint中的网络底层通信,使用的是一种普通的反应器,它通过参数来查找需要连接的P2P节点,在Tendermint的节点连接中,维护着两组映射来管理连接自己和自己连接的对象,分别称做inbound,outbound.
outbound中,有两种连接,一种是连接时指定的seed,一种是在初始化时检测出来的节点。一般情况下,outbound的数量少于10个。而inbound控制在50个左右的连接。
既然是基于反应器的,那么编程的复杂性就大大降低了。只需要服务监听就可以了。这里不再细节赘述网络通信部分。
网络在启动时,会启动一个协程,定时轮询outbound的数量,来控制连接的稳定性。
3、架构
Tendermint的设计目的是为了创建一个统一的区块链开发的基础组件。通过将区块链中主要的P2P和共识抽象出来,实现区块链开发过程中的组件式管理。这样做的优势有以下几点:
一个是代码重用。对通用的网络通信和共识就不必再重复的造轮子。
二是解放了区块链编程的语言。比如以太坊用go,c++,但是通过Tendermint的抽象后,可以使用任何语言(觉得和当初JAVA才提出时一次编译的想法有些相似啊)。特别是对于智能合约,这个优点就更显得明显了。
4、Tendermint的共识过程
Tendermint共识机制中通过作验证人(Validators)来对区块达成共识,这个在前面已经介绍过,一组验证人负责对每一轮的新区块进行提议和投票。整个共识达成的过程如下图所示。
每一轮的开始(New Round),节点对新一轮的区块进行提议。之后,合格的提议区块首先经过一轮预投票(Prevote)。在提议区块获得2/3以上的投票后,进入下一轮的预认可(Precommit),同样是待获得2/3以上的验证人预认可后,被提议区块就正式获得了认可(Commit)。而得到认可的这个区块就被添加的到区块链中。
下面为详细的过程:
在Tendermint算法中,如果遇到对同一特定区块的同意及否决信息同时超过2/3的情况,需要启用外部的维护机制去核查是否存在超过1/3的验证节点伪造签名或者投出双重选票。
5、Tendermint的交易流程
当一个Tx进来时, Tmcore的mempool(MP)会通过mempool connection(一个socket连接,由abci-server提供)调用Application Logic(AL:也就是abci-app,我们自己用任何语言编写的APP逻辑)里的checkTx方法,AL向MP返回验证结果。MP根据验证结果通过或者拒绝该Tx。
Tendermint(TM)把tx暂存在内存池(mempool)里,并把这条Tx通过P2P网络复制给其它TM节点。TM发起了对这条Tx的拜占庭共识投票,所有Tendermint节点都参与了。投票过程分三轮,第一轮预投票(PreVote),超过2/3认可后进入第二轮预提交(PreCommit),超过2/3认可后进入最后一轮正式提交(Commit)
TM提交Tx时依次通过Consensus Connection(一个socket连接,由abci-server提供)向ABCI-APP发送指令BeginBlock-->多次DeliverTx-->EndBlock-->Commit,提交成功后会将StateRoot(application Merkle root hash)返回给TM,TM New出一个区块。
如下图所示的交易流程图:
三、总结
通过上面的分析可以看到,其实Tendermint的重点在于共识和P2P,将二者抽象出来的有利之处在于,可以让开发者忽略对网络通信和共识的复杂性。直接进行业务层面的开发,而SDK的封装,进一步减少了业务上对非相关的逻辑的考虑,大大减少了开发者生产一条区块链的复杂度,而这也恰恰是Tendermint和cosmos-sdk所想达到的目的。
Delivery层初始化配置
Delivery层初始化配置
首先需要和bttc层一样准备一个genesis配置,如果是同步现有测试网或者主网,可以官方仓库拿到
如果是启动私链也可以自己创建,deliveryd程序也提供了初始化测试链的功能,比如需要创建一个私链,3个验证者2个同步节点
./deliveryd create-testnet --v 3 --n 2 --output-dir ./output --chain-id delivery-9528
执行完成后,会自动创建5个节点对应的配置文件,每份主要的配置如下
genesis.json
初始化链配置,主要关心以下字段
-
chain_id 链id
-
app_state->accounts 初始化账户资金等信息
-
bor->spans->validator_set->validators 初始化验证者信息
-
bor->spans->validator_set->proposer 初始化提案者信息
-
bor->spans->selected_producers 初始化当前生产者信息
-
bor->spans->bor_chain_id bttc层链id
-
chainmanager->chain_params 链合约相关地址
-
checkpoint 配置相关参数
-
gov 治理相关参数
-
slashing 惩罚相关参数
-
staking 初始化相关地址抵押量
对于私链,在初始化数据基础上,主要关注chain_id和chain_params,其余地址相应数据使用默认即可
config.toml
链基础配置,主要关心参数如下
-
fast_sync:是否开始快速同步
-
db_dir: 数据的存放位置
-
genesis_file:genesis.json存放位置
-
priv_validator_key_file:验证者私钥文件位置
-
priv_validator_state_file:验证者状态文件位置
-
node_key_file:节点密钥存放位置
-
persistent_peers:持久链接的peer地址,类同于eth的staticnode
-
private_peer_ids: 私有的节点id, 用于隐私接入,比如哨兵节点
- 登录验证人节点.
- 运行
deliveryd tendermint show-node-id
. 示例:private_peer_ids = "e2c6a611e449b61f2266f0054a315fad6ce607ba"
delivery-config.toml
RPC和 REST配置,主要关心参数如下
-
eth_rpc_url:eth链RPC地址
-
bsc_rpc_url:bsc链RPC地址
-
bttc_rpc_url:bttc层RPC地址
-
tron_rpc_url:tron链RPC地址
-
测试网:47.252.19.181:50051
-
tron_grid_url:tron链grid地址
-
amqp_url:AMQP地址,在bridge过程中会用于任务的消息队列
node_key.json
节点的密钥信息,可以使用工具自动生成
priv_validator_key.json
节点验证者的密钥信息,可以使用工具自动生成
对于单节点测试,也可以直接执行初始化
./deliveryd init --chain-id delivery-9528 --home ./
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": {
},
eth节点压力过大时,RPC节点 txpool中pending交易堆积,不能正常广播到其他节点
问题已在以下issue中被讨论
https://github.com/ethereum/go-ethereum/issues/22308
目前先尝试bsc中的方案,验证后再做补充
https://github.com/bnb-chain/bsc/pull/570