基本含义
块的额外数据,通过 --miner.extradata
指定。在以太坊中的黄皮书中这样规定extraData: An arbitrary byte array containing data relevant to this block. This must be 32 bytes or fewer; formally Hx.
就是说允许矿工在一个区块中自定一个不超过 32byte 的数据。在基于 PoW 共识算法中,这个字段就是这么简单。
PoA 共识算法重定义。
在基于 PoA 实现的共识算法中,对这个字段进行了一定的重定义。如果查看查看区块信息,你会看到矿工字段返回的是一个零地址 0x0000000000000000000000000000000000000000
。由于在 PoA 共识算法中,需要验证签名者,所以放开了extradata的32bytes限制。
第一种情况,当是一个epoch也就是换届的时候,会将下一批出块者写入到extradata中。具体规则是 32bytes自定义数据 + 出块者地址列表 + 65bytes签名数据。其中32bytes自定义数据还是来自--miner.extradata
,跟 PoW 共识算法不同的是,PoW不超过32bytes即可。但是这里,如果不足32bytes,它会补足。后面的 65bytes 后面有说明。根据这个规则,创世块假设要内置两个地址0x00000be6819f41400225702d32d3dd23663dd690,0x1111102dd32160b064f2a512cdef74bfdb6a9f96
作为第一个 epoch 出块者,那么创世块extraData
配置为:
0x0000000000000000000000000000000000000000000000000000000000000000-00000be6819f41400225702d32d3dd23663dd690-1111102dd32160b064f2a512cdef74bfdb6a9f96-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
注意中间的-
实际是没有的,为了表述清楚特意加的。
第二种情况,就是每个epoch出块。32bytes自定义数据 + 65bytes签名数据。 前面32bytes由出块者指定,不足后面的字节补零。后面的65bytes则为出块者使用私钥对 Keeack256(RLP(区块头数据)) 数据的签名。所以,如果想要恢复出块者的地址。那么需要自己计算 Keeack256(RLP(区块头数据)),然后截取后面的 65bytes签名数据,即可恢复出出块者地址。我的一个实现是这样:
import { ethers } from "ethers";
const cliqueMiner = (block) => {
try {
const keys = ["parentHash", "sha3Uncles", "miner", "stateRoot", "transactionsRoot", "receiptsRoot", "logsBloom", "difficulty", "number", "gasLimit", "gasUsed", "timestamp", "extraData", "mixHash", "nonce", "baseFeePerGas"];
const datas = [];
for (const key of keys) {
let data = block[key];
if (key == "baseFeePerGas" && !data) {
continue;
}
if (key == "extraData") {
data = data.substr(0, 66);
} else if (key == "difficulty") {
data = parseInt(data);
}
if (typeof data == "number") {
data = ethers.BigNumber.from(data).toHexString();
}
if (data.length % 2 == 1) {
data = "0x0" + data.substr(2); // RLP object must be BytesLike
}
datas.push(data);
}
console.log(datas);
const digest = ethers.utils.keccak256(ethers.utils.RLP.encode(datas));
const signature = "0x" + block["extraData"].substr(66);
const miner = ethers.utils.recoverAddress(digest, signature);
return miner;
} catch (error) {
console.log("error", error);
return block.miner;
}
};
const block = {
difficulty: "0x2",
extraData: "0xd883010a0f846765746888676f312e31372e35856c696e757800000000000000cc6274ad7527edc1fc104ad149c3c3340a559e4197bfb5cdc69add1cd5e3a1fc3b3511ef2f474527731fb6502b72e3d36766f775a3f4b98bba1b29ae4f0aa76200",
gasLimit: "0x47a57228001",
gasUsed: "0x5208",
hash: "0xdf4d0f6ec0403cb12063cf0c865ff32d2e006e44673ed3933cf1d3aae98e31bb",
logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
miner: "0x0000000000000000000000000000000000000000",
mixHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
nonce: "0x0000000000000000",
number: "0x1",
parentHash: "0xda1977f3cfeddfde314652d125c3a8c7a8331e664dace0e4b76319abcc720eb1",
receiptsRoot: "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2",
sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
size: "0x2d5",
stateRoot: "0x7d5885026291a063b55f5213697fd54c1b64efbfca8f6ff12ff31a1619a3a4b5",
timestamp: "0x6230b2ea",
totalDifficulty: "0x3",
transactions: ["0xa750ddae2ec3bb96125794d7446e941abef3c508ba8ca62b5569d34876fa18ea"],
transactionsRoot: "0x8b077b57dc9364facbee380526f640ad8161c5104cbd190ebae212020fd415d4",
uncles: [],
};
console.log(cliqueMiner(block)); // 0x1111102Dd32160B064F2A512CDEf74bFdB6a9F96
相关代码
设置 extradata:如果没有指定,默认一些信息。而且不允许超过32bytes。
func makeExtraData(extra []byte) []byte {
if len(extra) == 0 {
// create default extradata
extra, _ = rlp.EncodeToBytes([]interface{}{
uint(params.VersionMajor<<16 | params.VersionMinor<<8 | params.VersionPatch),
"geth",
runtime.Version(),
runtime.GOOS,
})
}
if uint64(len(extra)) > params.MaximumExtraDataSize {
log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize)
extra = nil
}
return extra
}
PoA共识算法中,计算签名数据。
// 计算区块头的RLP
func encodeSigHeader(w io.Writer, header *types.Header) {
enc := []interface{}{
header.ParentHash,
header.UncleHash,
header.Coinbase,
header.Root,
header.TxHash,
header.ReceiptHash,
header.Bloom,
header.Difficulty,
header.Number,
header.GasLimit,
header.GasUsed,
header.Time,
header.Extra[:len(header.Extra)-crypto.SignatureLength], // Yes, this will panic if extra is too short
header.MixDigest,
header.Nonce,
}
if header.BaseFee != nil {
enc = append(enc, header.BaseFee)
}
if err := rlp.Encode(w, enc); err != nil {
panic("can't encode: " + err.Error())
}
}
// 数据进行签名
sighash, err := signFn(accounts.Account{Address: signer}, accounts.MimetypeClique, CliqueRLP(header))
// 对 keeack256(RLP(block header)) 的数据签名,得到65bytes的签名数据(即 V、R、S)
func (w *keystoreWallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
return w.signHash(account, crypto.Keccak256(data))
}
区块头中 extradata 数据的设置
if number%c.config.Epoch != 0 {
// Gather all the proposals that make sense voting on
addresses := make([]common.Address, 0, len(c.proposals))
for address, authorize := range c.proposals {
if snap.validVote(address, authorize) {
addresses = append(addresses, address)
}
}
// If there's pending proposals, cast a vote on them
if len(addresses) > 0 {
header.Coinbase = addresses[rand.Intn(len(addresses))]
if c.proposals[header.Coinbase] {
copy(header.Nonce[:], nonceAuthVote)
} else {
copy(header.Nonce[:], nonceDropVote)
}
}
}
// Ensure the extra data has all its components
if len(header.Extra) < extraVanity {
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, extraVanity-len(header.Extra))...)
}
header.Extra = header.Extra[:extraVanity]
if number%c.config.Epoch == 0 {
for _, signer := range snap.signers() {
header.Extra = append(header.Extra, signer[:]...)
}
}
header.Extra = append(header.Extra, make([]byte, extraSeal)...)
疑问
- 为啥miner不直接填出块者地址呢,然后还是走签名那套流程。只是在RLP区块头数据中的miner不是零地址就可以了。