作者: songtianyi
shadow service 的主要目的是获取并验证区块,然后生成区块的 MMR(Merkle Mountain Range),因为有些链并不是 MMR 结构(先忽略 shadow 在整个跨链中的作用)。 具体的方式是:
代码结构大致如下:
├── api(step4)
│ ├── Cargo.toml
├── ffi(step1 and step2)
│ ├── Cargo.toml
├── mmr (step3 ckb-merkle-mountain-range)
│ ├── Cargo.toml
├── src(step4)
│ ├── bin
│ │ └── shadow.rs
│ ├── cmd
│ │ ├── count.rs
│ ├── mmr
│ │ ├── mod.rs
│ │ └── runner.rs
shadow service 新增一个链的支持需要考虑以下几点:
etherum 主网使用的是 PoW 共识,区块结构如下
// Block represents an entire block in the Ethereum blockchain.
type Block struct {
header *Header
uncles []*Header
transactions Transactions
// caches
hash atomic.Value
size atomic.Value
// Td is used by package core to store the total difficulty
// of the chain up to and including the block.
td *big.Int
// These fields are used by package eth to track
// inter-peer block relay.
ReceivedAt time.Time
ReceivedFrom interface{}
}
// Header represents a block header in the Ethereum blockchain.
type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
Coinbase common.Address `json:"miner" gencodec:"required"`
Root common.Hash `json:"stateRoot" gencodec:"required"`
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"`
Difficulty *big.Int `json:"difficulty" gencodec:"required"`
Number *big.Int `json:"number" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Time uint64 `json:"timestamp" gencodec:"required"`
Extra []byte `json:"extraData" gencodec:"required"`
MixDigest common.Hash `json:"mixHash"`
Nonce BlockNonce `json:"nonce"`
}
PoW 的特点是计算复杂,验证相对简单, 且每个人都能出块。
type Header struct {
Nonce [8]byte
...
}
通过不断修改 Nonce 的值并计算区块的 hash 值来得一个合法的(比如小于某一阈值)区块的过程就是我们通常所说的挖矿。 ethereum 的 PoW 实现命名为 ethash, 和标准 PoW 的实现大致一样,区别是:
Dagger-Hashimoto
算法生成 hash 值。该算法分为 Dagger 和 Hashimoto 两部分,Dagger 用来生成数据集(DAG),增大内存消耗,防止算力攻击,Hashimoto 使用区块 Header 的哈希和 Nonce 字段以及 DAG 数据集生成一个最终的 hash(digest). DAG 的生成和 epoch 相关, 它的大小每过一个 epoch 就会增加。heco 链使用的是 PoA(Proof of Authority) + PoS(Proof of Stake) 的共识机制,而 heco 的代码是从 ethereum copy 过来的,所以 heco 链的共识是在 ethereum clique 的基础上改的。PoS 的逻辑是通过合约实现的,然后以 ABI 的形式调用。
heco mainnet 的 epoch length 为 200, 出块周期为 3s, 参考 Genesis
虽然共识有差异,但是块结构是没有变化的,获取块数据的接口也没有变化。
# get recent block number
curl -s -H 'content-type:application/json' -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":83}' https://http-mainnet-node.huobichain.com
# get block by number
curl -s -H 'content-type:application/json' -X POST --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["0x39b857", false],"id":83}' https://http-mainnet-node.huobichain.com
在 PoA 共识中,是由合格的验证人来出块,链在启动的时候会内置一些合格的验证人节点,这些验证人会轮流出块,出块时会附带投票信息,投票信息会决定下一个 epoch 的合格验证人列表。当 blockNumber % epochLength == 0
时,会产生一个不包含投票信息但包含当前签名者列表的块,这个 block 称之为 checkpoint
type Vote struct {
Signer common.Address // 此次投票是由谁投的
Block uint64 // 此次投票是在哪个高度的block上投的
Address common.Address // 此次投票是投给谁的
Authorize bool // 这是一个加入票(申请被投人成为签名者)还是踢出票(申请将被投人踢出签名者列表)
}
type Tally struct {
Authorize bool // 这是加入票的统计还是踢出票的统计
Votes int // 目前为止累计的票数
}
虽然 clique 相比 ethash, 区块结构并没有变化,但是字段的含义发生了变化:
common.Hash{}
, 校验的也是这个值而 heco chain 的共识(congress 目录)相对于 clique 字段的意义也有些变化:
signer 在 heco 里面改成了 validator
common.Address{}
Fields | Ethash | Clique | HPoS(Congress) |
---|---|---|---|
Extra | -- | extraVanity + signers? + extraSeal | extraVanity + validators? + extraSeal |
Nonce | random hash | vote action | -- |
Coinbase | miner address | vote target address | validator address |
MixDigest | for verifying | -- | -- |
块校验:
块结构和字段含义弄清楚以后就可以校验块了。 整个过程可以仿照共识代码当中的 verifySeal
来进行,snapshot 也可以通过接口拿到。 共识的 PoS 部分对块的校验没有影响,PoS 只负责选 validator 不负责出块逻辑。PoS 是每个 epoch 更新一次 validator 集合。
// do epoch thing at the end, because it will update active validators
if header.Number.Uint64()%c.config.Epoch == 0 {
newValidators, err := c.doSomethingAtEpoch(chain, header, state)
if err != nil {
return err
}
validatorsBytes := make([]byte, len(newValidators)*common.AddressLength)
for i, validator := range newValidators {
copy(validatorsBytes[i*common.AddressLength:], validator.Bytes())
}
extraSuffix := len(header.Extra) - extraSeal
if !bytes.Equal(header.Extra[extraVanity:extraSuffix], validatorsBytes) {
return errInvalidExtraValidators
}
}
snapshot 核心逻辑:
// If we're at the genesis, snapshot the initial state. Alternatively if we're
// at a checkpoint block without a parent (light client CHT), or we have piled
// up more headers than allowed to be reorged (chain reinit from a freezer),
// consider the checkpoint trusted and snapshot it.
if number == 0 || (number%c.config.Epoch == 0 && (len(headers) > params.FullImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) {
checkpoint := chain.GetHeaderByNumber(number)
if checkpoint != nil {
hash := checkpoint.Hash()
validators := make([]common.Address, (len(checkpoint.Extra)-extraVanity-extraSeal)/common.AddressLength)
for i := 0; i < len(validators); i++ {
copy(validators[i][:], checkpoint.Extra[extraVanity+i*common.AddressLength:])
}
snap = newSnapshot(c.config, c.signatures, number, hash, validators)
if err := snap.store(c.db); err != nil {
return nil, err
}
log.Info("Stored checkpoint snapshot to disk", "number", number, "hash", hash)
break
}
}
snapshot 校验:
if _, ok := snap.Validators[signer]; !ok {
return errUnauthorizedValidator
}
for seen, recent := range snap.Recents {
if recent == signer {
// Validator is among recents, only fail if the current block doesn't shift it out
if limit := uint64(len(snap.Validators)/2 + 1); seen > number-limit {
return errRecentlySigned
}
}
}
snap.Recents[number] = validator
bsc 和 heco chain 是完全一个套路,copy ethereum 然后修改共识,bsc 用的共识也是 PoA + DPoS(Deputy Proof of Stake), 取名为 PoSA(Proof of Staked Authority), 在代码里命名为 parlia. 所以这两个链在官网上没有技术白皮书,只有简单的介绍。因此,重点还是看 ethereum 的文档,然后看共识部分的代码。
bsc mainnet 的 epoch length 为 200, 出块周期为 3s, 参考 Genesis
和 heco chain 的区别:
Concepts | go-ethereum Clique | Heco Congress | BSC Parlia |
---|---|---|---|
Namespace | clique | congress | parlia |
Snapshot | 根据 Votes 更新 signer 列表 | 从 checkpoint extra data 获取最新的 signer | RecentForkHashes, delay N/2 block |
API | 通过 proposal 修改 authority set | 去除了 vote 逻辑 | 去除了 vote 逻辑和 Status 接口 |
ABI | -- | Solidity contracts | Solidity Contracts |
Core | - | contracts, Verify ForkHash | wiggleTime(1 second), Gas verify, contracts, SealHash 增加了 ChainID, delay 的计算更复杂, Verify ForkHash |
substrate 需要验证 bsc 的 header 和交易信息,因此需要维护一个当前合法的 authority set