以太坊区块中的额外数据extraData

基本含义

块的额外数据,通过 --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不是零地址就可以了。

相关链接

暂无评论

发送评论 编辑评论


				
上一篇
下一篇