共识算法选择
eth/ethconfig/config.go CreateConsensusEngine 209
func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine {
// If proof-of-authority is requested, set it up
if chainConfig.Clique != nil {
return clique.New(chainConfig.Clique, db)
}
...
}
type CliqueConfig struct {
Period uint64 `json:"period"` // Number of seconds between blocks to enforce 出块间隔, --dev.period
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint 选举时间间隔
}
投票列表信息
consensus/clique/clique.go Prepare 506
if number%c.config.Epoch == 0 {
for _, signer := range snap.signers() {
header.Extra = append(header.Extra, signer[:]...)
}
}
dev 模式下面,创世块有一个投票人
投票人信息
保留在header extraData数据中。可以使用密码学得方式解码出地址。
出块机会
consensus/clique/clique.go Seal 599
防止有人作恶。
for seen, recent := range snap.Recents {
if recent == signer {
// Signer is among recents, only wait if the current block doesn't shift it out
if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit {
log.Info("Signed recently, must wait for others")
return nil
}
}
}
number < limit:人很多,你出了就暂时不要出块了。seen > number-limit:你最近不是已经出过快了吗?让别人出。
经过上诉步骤还是有多个人出块? 如果不是inturn,随机延迟一段时间出块。允许并发出块。给 noturn 加延迟时间的方式来支持 inturn 首先出块,避免 noturn 的结点无谓生成区块。
delay := time.Unix(int64(header.Time), 0).Sub(time.Now()) // nolint: gosimple
if header.Difficulty.Cmp(diffNoTurn) == 0 {
// It's not our turn explicitly to sign, delay it a bit
wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime
delay += time.Duration(rand.Int63n(int64(wiggle)))
log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
}
确定出块人
此数值结果反映在区块难度里面。
diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures 轮到我出块了
diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures 不是我出块
func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
signers, offset := s.signers(), 0
for offset < len(signers) && signers[offset] != signer {
offset++
}
return (number % uint64(len(signers))) == uint64(offset)
}
动态调整出块人
启动得时候增加clique --http.api eth,...,clique console,输入clique即可看到如下方法
{
proposals: {},
discard: function(),
getProposals: function(callback),
getSigners: function(),
getSignersAtHash: function(),
getSnapshot: function(),
getSnapshotAtHash: function(),
propose: function(),
status: function()
}
clique.propose("0x5e17555c1ed3bb5010aa9386d56ca823a0f33d19", true/false);
对应RPC的go代码 consensus/clique/api.go
对应的go代码 consensus/clique/snapshot.go apply 185
大概逻辑就是:票数过半,如果是赞成加入,那就加入出块人列表。如果是反对,那就踢出出块列表。
if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
if tally.Authorize {
snap.Signers[header.Coinbase] = struct{}{}
} else {
}
}
每隔 blockNumber % (checkpointInterval = 1024) == 0 个区块会将snap写入数据库保存。
数据签名
accounts/keystore/wallet.go 97
func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
return w.signHash(account, crypto.Keccak256(data))
}
搭建一个基于PoA的四节点
创建挖矿账号
使用命令geth --datadir node1 account new
依次创建需要挖矿的账号,想要多少个节点就创建多少个节点。
创建genesis.json文件
使用交互式命令puppeth
,根据提示创建一个genesis.json文件。创建出来的genesis.json文件内容类似如下:
{
"config": {
"chainId": 1205,
"homesteadBlock": 0,
"eip150Block": 0,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"clique": {
"period": 15,
"epoch": 30000
}
},
"nonce": "0x0",
"timestamp": "0x60de826f",
"extraData": "0x000000000000000000000000000000000000000000000000000000000000000023893ab711b8a31f166e5977191a9acb737d49089ae5c0f24355ca814c0c99d7ee9637ec005fae18b3b1a10f515740ecf805c31c5c612935d60ba5eb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x47b7600000",
"difficulty": "0x1",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"23893ab711b8a31f166e5977191a9acb737d4908": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"9ae5c0f24355ca814c0c99d7ee9637ec005fae18": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"b3b1a10f515740ecf805c31c5c612935d60ba5eb": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"baseFeePerGas": null
}
其中extraData里面有初始的出块人列表。alloc里面是默认账号的初始金额。
创建创世块
使用命令geth --datadir node1 init genesis.json
依次创建创世块
启动节点
使用命令./geth --datadir ./node1 --ipcdisable --port 30001 --nodiscover --http --http.corsdomain "*" --http.addr 0.0.0.0 --http.port 41231 --http.api eth,net,web3,personal,admin,miner,txpool,clique --log.debug --verbosity 5 --allow-insecure-unlock console
启动链。参数根据自己需求进行增删。
解锁账号并开启挖矿
节点互连
由于启动的节点使用了 --nodiscover
参数。所以启动之后还没有链接起来,可以使用 RPC 接口 admin_addPeer
将节点互联起来。每次启动这么弄都很麻烦。可以在节点的数据库里面放一个 static-nodes.json
文件,跟目录 geth
同级即可。 static-nodes.json
文件内容大概如下:
[
"enode://4d87f6cd87680adb4b51a80ed6bc7125640a29a953b7fd9a3e4ab21009a99436faa16bbb359994bcc9b89e52869abd3b0dc3908e2a368f49eb553609276836b2@127.0.0.1:30001",
"enode://a3c958413831b2561273b6d2cc21c818e2675443872ba2077a66264d253d7525eb5b895647c37d3249439608506eb0fb99987fe0fbac1c174acb2bf996e658dc@127.0.0.1:30002",
"enode://d581f48b7c4902b91f9709b7fa8149d10a5f11a1078e196f0d4bdaf89e4b1be43b7c5115e61976384ea96a2dd6f7ca96e500e571221302094c0b18b31caddea6@127.0.0.1:30003",
"enode://430b1342533e9f0db719bd0eedd46cf5f4b163786e7a0238afccce771869a16604eb67106d288a152b5c814b7fb4cd184ac5cf330761a8f6e624fe2e554eb89c@127.0.0.1:30004"
]
数据量对智能合约的影响