From 441c7f2b0fb264981483850e9d155f7b0e908439 Mon Sep 17 00:00:00 2001
From: lightclient <14004106+lightclient@users.noreply.github.com>
Date: Mon, 22 Nov 2021 01:25:35 -0700
Subject: [PATCH] cmd/evm: add b11r tool (#23843)
evm block-builder (a.k.a b11r) is a utility to help assemble blocks, for use during the test-creation process.
---
cmd/evm/internal/t8ntool/block.go | 380 ++++++++++++++++++++++++
cmd/evm/internal/t8ntool/flags.go | 41 ++-
cmd/evm/internal/t8ntool/gen_header.go | 135 +++++++++
cmd/evm/internal/t8ntool/transaction.go | 2 +-
cmd/evm/internal/t8ntool/transition.go | 50 +---
cmd/evm/internal/t8ntool/utils.go | 54 ++++
cmd/evm/main.go | 20 ++
cmd/evm/t8n_test.go | 124 +++++++-
cmd/evm/testdata/20/exp.json | 4 +
cmd/evm/testdata/20/header.json | 14 +
cmd/evm/testdata/20/ommers.json | 1 +
cmd/evm/testdata/20/readme.md | 11 +
cmd/evm/testdata/20/txs.rlp | 1 +
cmd/evm/testdata/21/clique.json | 6 +
cmd/evm/testdata/21/exp-clique.json | 4 +
cmd/evm/testdata/21/exp.json | 4 +
cmd/evm/testdata/21/header.json | 11 +
cmd/evm/testdata/21/ommers.json | 1 +
cmd/evm/testdata/21/readme.md | 23 ++
cmd/evm/testdata/21/txs.rlp | 1 +
cmd/evm/testdata/22/exp-clique.json | 4 +
cmd/evm/testdata/22/exp.json | 4 +
cmd/evm/testdata/22/header.json | 11 +
cmd/evm/testdata/22/ommers.json | 1 +
cmd/evm/testdata/22/readme.md | 11 +
cmd/evm/testdata/22/txs.rlp | 1 +
26 files changed, 881 insertions(+), 38 deletions(-)
create mode 100644 cmd/evm/internal/t8ntool/block.go
create mode 100644 cmd/evm/internal/t8ntool/gen_header.go
create mode 100644 cmd/evm/internal/t8ntool/utils.go
create mode 100644 cmd/evm/testdata/20/exp.json
create mode 100644 cmd/evm/testdata/20/header.json
create mode 100644 cmd/evm/testdata/20/ommers.json
create mode 100644 cmd/evm/testdata/20/readme.md
create mode 100644 cmd/evm/testdata/20/txs.rlp
create mode 100644 cmd/evm/testdata/21/clique.json
create mode 100644 cmd/evm/testdata/21/exp-clique.json
create mode 100644 cmd/evm/testdata/21/exp.json
create mode 100644 cmd/evm/testdata/21/header.json
create mode 100644 cmd/evm/testdata/21/ommers.json
create mode 100644 cmd/evm/testdata/21/readme.md
create mode 100644 cmd/evm/testdata/21/txs.rlp
create mode 100644 cmd/evm/testdata/22/exp-clique.json
create mode 100644 cmd/evm/testdata/22/exp.json
create mode 100644 cmd/evm/testdata/22/header.json
create mode 100644 cmd/evm/testdata/22/ommers.json
create mode 100644 cmd/evm/testdata/22/readme.md
create mode 100644 cmd/evm/testdata/22/txs.rlp
diff --git a/cmd/evm/internal/t8ntool/block.go b/cmd/evm/internal/t8ntool/block.go
new file mode 100644
index 000000000..d4edd33bd
--- /dev/null
+++ b/cmd/evm/internal/t8ntool/block.go
@@ -0,0 +1,380 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
+
+package t8ntool
+
+import (
+ "crypto/ecdsa"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math/big"
+ "os"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/consensus/clique"
+ "github.com/ethereum/go-ethereum/consensus/ethash"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/rlp"
+ "gopkg.in/urfave/cli.v1"
+)
+
+//go:generate gencodec -type header -field-override headerMarshaling -out gen_header.go
+type header struct {
+ ParentHash common.Hash `json:"parentHash"`
+ OmmerHash *common.Hash `json:"sha3Uncles"`
+ Coinbase *common.Address `json:"miner"`
+ Root common.Hash `json:"stateRoot" gencodec:"required"`
+ TxHash *common.Hash `json:"transactionsRoot"`
+ ReceiptHash *common.Hash `json:"receiptsRoot"`
+ Bloom types.Bloom `json:"logsBloom"`
+ Difficulty *big.Int `json:"difficulty"`
+ Number *big.Int `json:"number" gencodec:"required"`
+ GasLimit uint64 `json:"gasLimit" gencodec:"required"`
+ GasUsed uint64 `json:"gasUsed"`
+ Time uint64 `json:"timestamp" gencodec:"required"`
+ Extra []byte `json:"extraData"`
+ MixDigest common.Hash `json:"mixHash"`
+ Nonce *types.BlockNonce `json:"nonce"`
+ BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
+}
+
+type headerMarshaling struct {
+ Difficulty *math.HexOrDecimal256
+ Number *math.HexOrDecimal256
+ GasLimit math.HexOrDecimal64
+ GasUsed math.HexOrDecimal64
+ Time math.HexOrDecimal64
+ Extra hexutil.Bytes
+ BaseFee *math.HexOrDecimal256
+}
+
+type bbInput struct {
+ Header *header `json:"header,omitempty"`
+ OmmersRlp []string `json:"ommers,omitempty"`
+ TxRlp string `json:"txs,omitempty"`
+ Clique *cliqueInput `json:"clique,omitempty"`
+
+ Ethash bool `json:"-"`
+ EthashDir string `json:"-"`
+ PowMode ethash.Mode `json:"-"`
+ Txs []*types.Transaction `json:"-"`
+ Ommers []*types.Header `json:"-"`
+}
+
+type cliqueInput struct {
+ Key *ecdsa.PrivateKey
+ Voted *common.Address
+ Authorize *bool
+ Vanity common.Hash
+}
+
+// UnmarshalJSON implements json.Unmarshaler interface.
+func (c *cliqueInput) UnmarshalJSON(input []byte) error {
+ var x struct {
+ Key *common.Hash `json:"secretKey"`
+ Voted *common.Address `json:"voted"`
+ Authorize *bool `json:"authorize"`
+ Vanity common.Hash `json:"vanity"`
+ }
+ if err := json.Unmarshal(input, &x); err != nil {
+ return err
+ }
+ if x.Key == nil {
+ return errors.New("missing required field 'secretKey' for cliqueInput")
+ }
+ if ecdsaKey, err := crypto.ToECDSA(x.Key[:]); err != nil {
+ return err
+ } else {
+ c.Key = ecdsaKey
+ }
+ c.Voted = x.Voted
+ c.Authorize = x.Authorize
+ c.Vanity = x.Vanity
+ return nil
+}
+
+// ToBlock converts i into a *types.Block
+func (i *bbInput) ToBlock() *types.Block {
+ header := &types.Header{
+ ParentHash: i.Header.ParentHash,
+ UncleHash: types.EmptyUncleHash,
+ Coinbase: common.Address{},
+ Root: i.Header.Root,
+ TxHash: types.EmptyRootHash,
+ ReceiptHash: types.EmptyRootHash,
+ Bloom: i.Header.Bloom,
+ Difficulty: common.Big0,
+ Number: i.Header.Number,
+ GasLimit: i.Header.GasLimit,
+ GasUsed: i.Header.GasUsed,
+ Time: i.Header.Time,
+ Extra: i.Header.Extra,
+ MixDigest: i.Header.MixDigest,
+ BaseFee: i.Header.BaseFee,
+ }
+
+ // Fill optional values.
+ if i.Header.OmmerHash != nil {
+ header.UncleHash = *i.Header.OmmerHash
+ } else if len(i.Ommers) != 0 {
+ // Calculate the ommer hash if none is provided and there are ommers to hash
+ header.UncleHash = types.CalcUncleHash(i.Ommers)
+ }
+ if i.Header.Coinbase != nil {
+ header.Coinbase = *i.Header.Coinbase
+ }
+ if i.Header.TxHash != nil {
+ header.TxHash = *i.Header.TxHash
+ }
+ if i.Header.ReceiptHash != nil {
+ header.ReceiptHash = *i.Header.ReceiptHash
+ }
+ if i.Header.Nonce != nil {
+ header.Nonce = *i.Header.Nonce
+ }
+ if header.Difficulty != nil {
+ header.Difficulty = i.Header.Difficulty
+ }
+ return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers)
+}
+
+// SealBlock seals the given block using the configured engine.
+func (i *bbInput) SealBlock(block *types.Block) (*types.Block, error) {
+ switch {
+ case i.Ethash:
+ return i.sealEthash(block)
+ case i.Clique != nil:
+ return i.sealClique(block)
+ default:
+ return block, nil
+ }
+}
+
+// sealEthash seals the given block using ethash.
+func (i *bbInput) sealEthash(block *types.Block) (*types.Block, error) {
+ if i.Header.Nonce != nil {
+ return nil, NewError(ErrorConfig, fmt.Errorf("sealing with ethash will overwrite provided nonce"))
+ }
+ ethashConfig := ethash.Config{
+ PowMode: i.PowMode,
+ DatasetDir: i.EthashDir,
+ CacheDir: i.EthashDir,
+ DatasetsInMem: 1,
+ DatasetsOnDisk: 2,
+ CachesInMem: 2,
+ CachesOnDisk: 3,
+ }
+ engine := ethash.New(ethashConfig, nil, true)
+ defer engine.Close()
+ // Use a buffered chan for results.
+ // If the testmode is used, the sealer will return quickly, and complain
+ // "Sealing result is not read by miner" if it cannot write the result.
+ results := make(chan *types.Block, 1)
+ if err := engine.Seal(nil, block, results, nil); err != nil {
+ panic(fmt.Sprintf("failed to seal block: %v", err))
+ }
+ found := <-results
+ return block.WithSeal(found.Header()), nil
+}
+
+// sealClique seals the given block using clique.
+func (i *bbInput) sealClique(block *types.Block) (*types.Block, error) {
+ // If any clique value overwrites an explicit header value, fail
+ // to avoid silently building a block with unexpected values.
+ if i.Header.Extra != nil {
+ return nil, NewError(ErrorConfig, fmt.Errorf("sealing with clique will overwrite provided extra data"))
+ }
+ header := block.Header()
+ if i.Clique.Voted != nil {
+ if i.Header.Coinbase != nil {
+ return nil, NewError(ErrorConfig, fmt.Errorf("sealing with clique and voting will overwrite provided coinbase"))
+ }
+ header.Coinbase = *i.Clique.Voted
+ }
+ if i.Clique.Authorize != nil {
+ if i.Header.Nonce != nil {
+ return nil, NewError(ErrorConfig, fmt.Errorf("sealing with clique and voting will overwrite provided nonce"))
+ }
+ if *i.Clique.Authorize {
+ header.Nonce = [8]byte{}
+ } else {
+ header.Nonce = [8]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
+ }
+ }
+ // Extra is fixed 32 byte vanity and 65 byte signature
+ header.Extra = make([]byte, 32+65)
+ copy(header.Extra[0:32], i.Clique.Vanity.Bytes()[:])
+
+ // Sign the seal hash and fill in the rest of the extra data
+ h := clique.SealHash(header)
+ sighash, err := crypto.Sign(h[:], i.Clique.Key)
+ if err != nil {
+ return nil, err
+ }
+ copy(header.Extra[32:], sighash)
+ block = block.WithSeal(header)
+ return block, nil
+}
+
+// BuildBlock constructs a block from the given inputs.
+func BuildBlock(ctx *cli.Context) error {
+ // Configure the go-ethereum logger
+ glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
+ glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name)))
+ log.Root().SetHandler(glogger)
+
+ baseDir, err := createBasedir(ctx)
+ if err != nil {
+ return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err))
+ }
+ inputData, err := readInput(ctx)
+ if err != nil {
+ return err
+ }
+ block := inputData.ToBlock()
+ block, err = inputData.SealBlock(block)
+ if err != nil {
+ return err
+ }
+ return dispatchBlock(ctx, baseDir, block)
+}
+
+func readInput(ctx *cli.Context) (*bbInput, error) {
+ var (
+ headerStr = ctx.String(InputHeaderFlag.Name)
+ ommersStr = ctx.String(InputOmmersFlag.Name)
+ txsStr = ctx.String(InputTxsRlpFlag.Name)
+ cliqueStr = ctx.String(SealCliqueFlag.Name)
+ ethashOn = ctx.Bool(SealEthashFlag.Name)
+ ethashDir = ctx.String(SealEthashDirFlag.Name)
+ ethashMode = ctx.String(SealEthashModeFlag.Name)
+ inputData = &bbInput{}
+ )
+ if ethashOn && cliqueStr != "" {
+ return nil, NewError(ErrorConfig, fmt.Errorf("both ethash and clique sealing specified, only one may be chosen"))
+ }
+ if ethashOn {
+ inputData.Ethash = ethashOn
+ inputData.EthashDir = ethashDir
+ switch ethashMode {
+ case "normal":
+ inputData.PowMode = ethash.ModeNormal
+ case "test":
+ inputData.PowMode = ethash.ModeTest
+ case "fake":
+ inputData.PowMode = ethash.ModeFake
+ default:
+ return nil, NewError(ErrorConfig, fmt.Errorf("unknown pow mode: %s, supported modes: test, fake, normal", ethashMode))
+ }
+ }
+ if headerStr == stdinSelector || ommersStr == stdinSelector || txsStr == stdinSelector || cliqueStr == stdinSelector {
+ decoder := json.NewDecoder(os.Stdin)
+ if err := decoder.Decode(inputData); err != nil {
+ return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err))
+ }
+ }
+ if cliqueStr != stdinSelector && cliqueStr != "" {
+ var clique cliqueInput
+ if err := readFile(cliqueStr, "clique", &clique); err != nil {
+ return nil, err
+ }
+ inputData.Clique = &clique
+ }
+ if headerStr != stdinSelector {
+ var env header
+ if err := readFile(headerStr, "header", &env); err != nil {
+ return nil, err
+ }
+ inputData.Header = &env
+ }
+ if ommersStr != stdinSelector && ommersStr != "" {
+ var ommers []string
+ if err := readFile(ommersStr, "ommers", &ommers); err != nil {
+ return nil, err
+ }
+ inputData.OmmersRlp = ommers
+ }
+ if txsStr != stdinSelector {
+ var txs string
+ if err := readFile(txsStr, "txs", &txs); err != nil {
+ return nil, err
+ }
+ inputData.TxRlp = txs
+ }
+ // Deserialize rlp txs and ommers
+ var (
+ ommers = []*types.Header{}
+ txs = []*types.Transaction{}
+ )
+ if inputData.TxRlp != "" {
+ if err := rlp.DecodeBytes(common.FromHex(inputData.TxRlp), &txs); err != nil {
+ return nil, NewError(ErrorRlp, fmt.Errorf("unable to decode transaction from rlp data: %v", err))
+ }
+ inputData.Txs = txs
+ }
+ for _, str := range inputData.OmmersRlp {
+ type extblock struct {
+ Header *types.Header
+ Txs []*types.Transaction
+ Ommers []*types.Header
+ }
+ var ommer *extblock
+ if err := rlp.DecodeBytes(common.FromHex(str), &ommer); err != nil {
+ return nil, NewError(ErrorRlp, fmt.Errorf("unable to decode ommer from rlp data: %v", err))
+ }
+ ommers = append(ommers, ommer.Header)
+ }
+ inputData.Ommers = ommers
+
+ return inputData, nil
+}
+
+// dispatchOutput writes the output data to either stderr or stdout, or to the specified
+// files
+func dispatchBlock(ctx *cli.Context, baseDir string, block *types.Block) error {
+ raw, _ := rlp.EncodeToBytes(block)
+
+ type blockInfo struct {
+ Rlp hexutil.Bytes `json:"rlp"`
+ Hash common.Hash `json:"hash"`
+ }
+ var enc blockInfo
+ enc.Rlp = raw
+ enc.Hash = block.Hash()
+
+ b, err := json.MarshalIndent(enc, "", " ")
+ if err != nil {
+ return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err))
+ }
+ switch dest := ctx.String(OutputBlockFlag.Name); dest {
+ case "stdout":
+ os.Stdout.Write(b)
+ os.Stdout.WriteString("\n")
+ case "stderr":
+ os.Stderr.Write(b)
+ os.Stderr.WriteString("\n")
+ default:
+ if err := saveFile(baseDir, dest, enc); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go
index 05b6ed164..7db39479c 100644
--- a/cmd/evm/internal/t8ntool/flags.go
+++ b/cmd/evm/internal/t8ntool/flags.go
@@ -68,6 +68,14 @@ var (
"\t - into the file ",
Value: "result.json",
}
+ OutputBlockFlag = cli.StringFlag{
+ Name: "output.block",
+ Usage: "Determines where to put the `block` after building.\n" +
+ "\t`stdout` - into the stdout output\n" +
+ "\t`stderr` - into the stderr output\n" +
+ "\t - into the file ",
+ Value: "block.json",
+ }
InputAllocFlag = cli.StringFlag{
Name: "input.alloc",
Usage: "`stdin` or file name of where to find the prestate alloc to use.",
@@ -81,10 +89,41 @@ var (
InputTxsFlag = cli.StringFlag{
Name: "input.txs",
Usage: "`stdin` or file name of where to find the transactions to apply. " +
- "If the file prefix is '.rlp', then the data is interpreted as an RLP list of signed transactions." +
+ "If the file extension is '.rlp', then the data is interpreted as an RLP list of signed transactions." +
"The '.rlp' format is identical to the output.body format.",
Value: "txs.json",
}
+ InputHeaderFlag = cli.StringFlag{
+ Name: "input.header",
+ Usage: "`stdin` or file name of where to find the block header to use.",
+ Value: "header.json",
+ }
+ InputOmmersFlag = cli.StringFlag{
+ Name: "input.ommers",
+ Usage: "`stdin` or file name of where to find the list of ommer header RLPs to use.",
+ }
+ InputTxsRlpFlag = cli.StringFlag{
+ Name: "input.txs",
+ Usage: "`stdin` or file name of where to find the transactions list in RLP form.",
+ Value: "txs.rlp",
+ }
+ SealCliqueFlag = cli.StringFlag{
+ Name: "seal.clique",
+ Usage: "Seal block with Clique. `stdin` or file name of where to find the Clique sealing data.",
+ }
+ SealEthashFlag = cli.BoolFlag{
+ Name: "seal.ethash",
+ Usage: "Seal block with ethash.",
+ }
+ SealEthashDirFlag = cli.StringFlag{
+ Name: "seal.ethash.dir",
+ Usage: "Path to ethash DAG. If none exists, a new DAG will be generated.",
+ }
+ SealEthashModeFlag = cli.StringFlag{
+ Name: "seal.ethash.mode",
+ Usage: "Defines the type and amount of PoW verification an ethash engine makes.",
+ Value: "normal",
+ }
RewardFlag = cli.Int64Flag{
Name: "state.reward",
Usage: "Mining reward. Set to -1 to disable",
diff --git a/cmd/evm/internal/t8ntool/gen_header.go b/cmd/evm/internal/t8ntool/gen_header.go
new file mode 100644
index 000000000..196e49dd7
--- /dev/null
+++ b/cmd/evm/internal/t8ntool/gen_header.go
@@ -0,0 +1,135 @@
+// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
+
+package t8ntool
+
+import (
+ "encoding/json"
+ "errors"
+ "math/big"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/common/math"
+ "github.com/ethereum/go-ethereum/core/types"
+)
+
+var _ = (*headerMarshaling)(nil)
+
+// MarshalJSON marshals as JSON.
+func (h header) MarshalJSON() ([]byte, error) {
+ type header struct {
+ ParentHash common.Hash `json:"parentHash"`
+ OmmerHash *common.Hash `json:"sha3Uncles"`
+ Coinbase *common.Address `json:"miner"`
+ Root common.Hash `json:"stateRoot" gencodec:"required"`
+ TxHash *common.Hash `json:"transactionsRoot"`
+ ReceiptHash *common.Hash `json:"receiptsRoot"`
+ Bloom types.Bloom `json:"logsBloom"`
+ Difficulty *math.HexOrDecimal256 `json:"difficulty"`
+ Number *math.HexOrDecimal256 `json:"number" gencodec:"required"`
+ GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
+ GasUsed math.HexOrDecimal64 `json:"gasUsed"`
+ Time math.HexOrDecimal64 `json:"timestamp" gencodec:"required"`
+ Extra hexutil.Bytes `json:"extraData"`
+ MixDigest common.Hash `json:"mixHash"`
+ Nonce *types.BlockNonce `json:"nonce"`
+ BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"`
+ }
+ var enc header
+ enc.ParentHash = h.ParentHash
+ enc.OmmerHash = h.OmmerHash
+ enc.Coinbase = h.Coinbase
+ enc.Root = h.Root
+ enc.TxHash = h.TxHash
+ enc.ReceiptHash = h.ReceiptHash
+ enc.Bloom = h.Bloom
+ enc.Difficulty = (*math.HexOrDecimal256)(h.Difficulty)
+ enc.Number = (*math.HexOrDecimal256)(h.Number)
+ enc.GasLimit = math.HexOrDecimal64(h.GasLimit)
+ enc.GasUsed = math.HexOrDecimal64(h.GasUsed)
+ enc.Time = math.HexOrDecimal64(h.Time)
+ enc.Extra = h.Extra
+ enc.MixDigest = h.MixDigest
+ enc.Nonce = h.Nonce
+ enc.BaseFee = (*math.HexOrDecimal256)(h.BaseFee)
+ return json.Marshal(&enc)
+}
+
+// UnmarshalJSON unmarshals from JSON.
+func (h *header) UnmarshalJSON(input []byte) error {
+ type header struct {
+ ParentHash *common.Hash `json:"parentHash"`
+ OmmerHash *common.Hash `json:"sha3Uncles"`
+ Coinbase *common.Address `json:"miner"`
+ Root *common.Hash `json:"stateRoot" gencodec:"required"`
+ TxHash *common.Hash `json:"transactionsRoot"`
+ ReceiptHash *common.Hash `json:"receiptsRoot"`
+ Bloom *types.Bloom `json:"logsBloom"`
+ Difficulty *math.HexOrDecimal256 `json:"difficulty"`
+ Number *math.HexOrDecimal256 `json:"number" gencodec:"required"`
+ GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"`
+ GasUsed *math.HexOrDecimal64 `json:"gasUsed"`
+ Time *math.HexOrDecimal64 `json:"timestamp" gencodec:"required"`
+ Extra *hexutil.Bytes `json:"extraData"`
+ MixDigest *common.Hash `json:"mixHash"`
+ Nonce *types.BlockNonce `json:"nonce"`
+ BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"`
+ }
+ var dec header
+ if err := json.Unmarshal(input, &dec); err != nil {
+ return err
+ }
+ if dec.ParentHash != nil {
+ h.ParentHash = *dec.ParentHash
+ }
+ if dec.OmmerHash != nil {
+ h.OmmerHash = dec.OmmerHash
+ }
+ if dec.Coinbase != nil {
+ h.Coinbase = dec.Coinbase
+ }
+ if dec.Root == nil {
+ return errors.New("missing required field 'stateRoot' for header")
+ }
+ h.Root = *dec.Root
+ if dec.TxHash != nil {
+ h.TxHash = dec.TxHash
+ }
+ if dec.ReceiptHash != nil {
+ h.ReceiptHash = dec.ReceiptHash
+ }
+ if dec.Bloom != nil {
+ h.Bloom = *dec.Bloom
+ }
+ if dec.Difficulty != nil {
+ h.Difficulty = (*big.Int)(dec.Difficulty)
+ }
+ if dec.Number == nil {
+ return errors.New("missing required field 'number' for header")
+ }
+ h.Number = (*big.Int)(dec.Number)
+ if dec.GasLimit == nil {
+ return errors.New("missing required field 'gasLimit' for header")
+ }
+ h.GasLimit = uint64(*dec.GasLimit)
+ if dec.GasUsed != nil {
+ h.GasUsed = uint64(*dec.GasUsed)
+ }
+ if dec.Time == nil {
+ return errors.New("missing required field 'timestamp' for header")
+ }
+ h.Time = uint64(*dec.Time)
+ if dec.Extra != nil {
+ h.Extra = *dec.Extra
+ }
+ if dec.MixDigest != nil {
+ h.MixDigest = *dec.MixDigest
+ }
+ if dec.Nonce != nil {
+ h.Nonce = dec.Nonce
+ }
+ if dec.BaseFee != nil {
+ h.BaseFee = (*big.Int)(dec.BaseFee)
+ }
+ return nil
+}
diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go
index e4fe24108..6f1c964ad 100644
--- a/cmd/evm/internal/t8ntool/transaction.go
+++ b/cmd/evm/internal/t8ntool/transaction.go
@@ -82,7 +82,7 @@ func Transaction(ctx *cli.Context) error {
)
// Construct the chainconfig
if cConf, _, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil {
- return NewError(ErrorVMConfig, fmt.Errorf("failed constructing chain configuration: %v", err))
+ return NewError(ErrorConfig, fmt.Errorf("failed constructing chain configuration: %v", err))
} else {
chainConfig = cConf
}
diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go
index 0aff715eb..8f203b0ed 100644
--- a/cmd/evm/internal/t8ntool/transition.go
+++ b/cmd/evm/internal/t8ntool/transition.go
@@ -43,11 +43,12 @@ import (
const (
ErrorEVM = 2
- ErrorVMConfig = 3
+ ErrorConfig = 3
ErrorMissingBlockhash = 4
ErrorJson = 10
ErrorIO = 11
+ ErrorRlp = 12
stdinSelector = "stdin"
)
@@ -88,21 +89,14 @@ func Transition(ctx *cli.Context) error {
log.Root().SetHandler(glogger)
var (
- err error
- tracer vm.EVMLogger
- baseDir = ""
+ err error
+ tracer vm.EVMLogger
)
var getTracer func(txIndex int, txHash common.Hash) (vm.EVMLogger, error)
- // If user specified a basedir, make sure it exists
- if ctx.IsSet(OutputBasedir.Name) {
- if base := ctx.String(OutputBasedir.Name); len(base) > 0 {
- err := os.MkdirAll(base, 0755) // //rw-r--r--
- if err != nil {
- return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err))
- }
- baseDir = base
- }
+ baseDir, err := createBasedir(ctx)
+ if err != nil {
+ return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err))
}
if ctx.Bool(TraceFlag.Name) {
// Configure the EVM logger
@@ -155,29 +149,17 @@ func Transition(ctx *cli.Context) error {
}
}
if allocStr != stdinSelector {
- inFile, err := os.Open(allocStr)
- if err != nil {
- return NewError(ErrorIO, fmt.Errorf("failed reading alloc file: %v", err))
- }
- defer inFile.Close()
- decoder := json.NewDecoder(inFile)
- if err := decoder.Decode(&inputData.Alloc); err != nil {
- return NewError(ErrorJson, fmt.Errorf("failed unmarshaling alloc-file: %v", err))
+ if err := readFile(allocStr, "alloc", &inputData.Alloc); err != nil {
+ return err
}
}
prestate.Pre = inputData.Alloc
// Set the block environment
if envStr != stdinSelector {
- inFile, err := os.Open(envStr)
- if err != nil {
- return NewError(ErrorIO, fmt.Errorf("failed reading env file: %v", err))
- }
- defer inFile.Close()
- decoder := json.NewDecoder(inFile)
var env stEnv
- if err := decoder.Decode(&env); err != nil {
- return NewError(ErrorJson, fmt.Errorf("failed unmarshaling env-file: %v", err))
+ if err := readFile(envStr, "env", &env); err != nil {
+ return err
}
inputData.Env = &env
}
@@ -190,7 +172,7 @@ func Transition(ctx *cli.Context) error {
// Construct the chainconfig
var chainConfig *params.ChainConfig
if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil {
- return NewError(ErrorVMConfig, fmt.Errorf("failed constructing chain configuration: %v", err))
+ return NewError(ErrorConfig, fmt.Errorf("failed constructing chain configuration: %v", err))
} else {
chainConfig = cConf
vmConfig.ExtraEips = extraEips
@@ -254,18 +236,18 @@ func Transition(ctx *cli.Context) error {
// Sanity check, to not `panic` in state_transition
if chainConfig.IsLondon(big.NewInt(int64(prestate.Env.Number))) {
if prestate.Env.BaseFee == nil {
- return NewError(ErrorVMConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section"))
+ return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section"))
}
}
if env := prestate.Env; env.Difficulty == nil {
// If difficulty was not provided by caller, we need to calculate it.
switch {
case env.ParentDifficulty == nil:
- return NewError(ErrorVMConfig, errors.New("currentDifficulty was not provided, and cannot be calculated due to missing parentDifficulty"))
+ return NewError(ErrorConfig, errors.New("currentDifficulty was not provided, and cannot be calculated due to missing parentDifficulty"))
case env.Number == 0:
- return NewError(ErrorVMConfig, errors.New("currentDifficulty needs to be provided for block number 0"))
+ return NewError(ErrorConfig, errors.New("currentDifficulty needs to be provided for block number 0"))
case env.Timestamp <= env.ParentTimestamp:
- return NewError(ErrorVMConfig, fmt.Errorf("currentDifficulty cannot be calculated -- currentTime (%d) needs to be after parent time (%d)",
+ return NewError(ErrorConfig, fmt.Errorf("currentDifficulty cannot be calculated -- currentTime (%d) needs to be after parent time (%d)",
env.Timestamp, env.ParentTimestamp))
}
prestate.Env.Difficulty = calcDifficulty(chainConfig, env.Number, env.Timestamp,
diff --git a/cmd/evm/internal/t8ntool/utils.go b/cmd/evm/internal/t8ntool/utils.go
new file mode 100644
index 000000000..1c54f09bf
--- /dev/null
+++ b/cmd/evm/internal/t8ntool/utils.go
@@ -0,0 +1,54 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
+
+package t8ntool
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "gopkg.in/urfave/cli.v1"
+)
+
+// readFile reads the json-data in the provided path and marshals into dest.
+func readFile(path, desc string, dest interface{}) error {
+ inFile, err := os.Open(path)
+ if err != nil {
+ return NewError(ErrorIO, fmt.Errorf("failed reading %s file: %v", desc, err))
+ }
+ defer inFile.Close()
+ decoder := json.NewDecoder(inFile)
+ if err := decoder.Decode(dest); err != nil {
+ return NewError(ErrorJson, fmt.Errorf("failed unmarshaling %s file: %v", desc, err))
+ }
+ return nil
+}
+
+// createBasedir makes sure the basedir exists, if user specified one.
+func createBasedir(ctx *cli.Context) (string, error) {
+ baseDir := ""
+ if ctx.IsSet(OutputBasedir.Name) {
+ if base := ctx.String(OutputBasedir.Name); len(base) > 0 {
+ err := os.MkdirAll(base, 0755) // //rw-r--r--
+ if err != nil {
+ return "", err
+ }
+ baseDir = base
+ }
+ }
+ return baseDir, nil
+}
diff --git a/cmd/evm/main.go b/cmd/evm/main.go
index 26064efc3..66d221a70 100644
--- a/cmd/evm/main.go
+++ b/cmd/evm/main.go
@@ -167,6 +167,25 @@ var transactionCommand = cli.Command{
},
}
+var blockBuilderCommand = cli.Command{
+ Name: "block-builder",
+ Aliases: []string{"b11r"},
+ Usage: "builds a block",
+ Action: t8ntool.BuildBlock,
+ Flags: []cli.Flag{
+ t8ntool.OutputBasedir,
+ t8ntool.OutputBlockFlag,
+ t8ntool.InputHeaderFlag,
+ t8ntool.InputOmmersFlag,
+ t8ntool.InputTxsRlpFlag,
+ t8ntool.SealCliqueFlag,
+ t8ntool.SealEthashFlag,
+ t8ntool.SealEthashDirFlag,
+ t8ntool.SealEthashModeFlag,
+ t8ntool.VerbosityFlag,
+ },
+}
+
func init() {
app.Flags = []cli.Flag{
BenchFlag,
@@ -200,6 +219,7 @@ func init() {
stateTestCommand,
stateTransitionCommand,
transactionCommand,
+ blockBuilderCommand,
}
cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate
}
diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go
index b4b816f57..3c759570f 100644
--- a/cmd/evm/t8n_test.go
+++ b/cmd/evm/t8n_test.go
@@ -131,7 +131,7 @@ func TestT8n(t *testing.T) {
output: t8nOutput{alloc: true, result: true},
expExitCode: 4,
},
- { // Ommer test
+ { // Uncle test
base: "./testdata/5",
input: t8nInput{
"alloc.json", "txs.json", "env.json", "Byzantium", "0x80",
@@ -171,7 +171,7 @@ func TestT8n(t *testing.T) {
output: t8nOutput{result: true},
expOut: "exp2.json",
},
- { // Difficulty calculation - with uncles + Berlin
+ { // Difficulty calculation - with ommers + Berlin
base: "./testdata/14",
input: t8nInput{
"alloc.json", "txs.json", "env.uncles.json", "Berlin", "",
@@ -336,6 +336,126 @@ func TestT9n(t *testing.T) {
}
}
+type b11rInput struct {
+ inEnv string
+ inOmmersRlp string
+ inTxsRlp string
+ inClique string
+ ethash bool
+ ethashMode string
+ ethashDir string
+}
+
+func (args *b11rInput) get(base string) []string {
+ var out []string
+ if opt := args.inEnv; opt != "" {
+ out = append(out, "--input.header")
+ out = append(out, fmt.Sprintf("%v/%v", base, opt))
+ }
+ if opt := args.inOmmersRlp; opt != "" {
+ out = append(out, "--input.ommers")
+ out = append(out, fmt.Sprintf("%v/%v", base, opt))
+ }
+ if opt := args.inTxsRlp; opt != "" {
+ out = append(out, "--input.txs")
+ out = append(out, fmt.Sprintf("%v/%v", base, opt))
+ }
+ if opt := args.inClique; opt != "" {
+ out = append(out, "--seal.clique")
+ out = append(out, fmt.Sprintf("%v/%v", base, opt))
+ }
+ if args.ethash {
+ out = append(out, "--seal.ethash")
+ }
+ if opt := args.ethashMode; opt != "" {
+ out = append(out, "--seal.ethash.mode")
+ out = append(out, fmt.Sprintf("%v/%v", base, opt))
+ }
+ if opt := args.ethashDir; opt != "" {
+ out = append(out, "--seal.ethash.dir")
+ out = append(out, fmt.Sprintf("%v/%v", base, opt))
+ }
+ out = append(out, "--output.block")
+ out = append(out, "stdout")
+ return out
+}
+
+func TestB11r(t *testing.T) {
+ tt := new(testT8n)
+ tt.TestCmd = cmdtest.NewTestCmd(t, tt)
+ for i, tc := range []struct {
+ base string
+ input b11rInput
+ expExitCode int
+ expOut string
+ }{
+ { // unsealed block
+ base: "./testdata/20",
+ input: b11rInput{
+ inEnv: "header.json",
+ inOmmersRlp: "ommers.json",
+ inTxsRlp: "txs.rlp",
+ },
+ expOut: "exp.json",
+ },
+ { // ethash test seal
+ base: "./testdata/21",
+ input: b11rInput{
+ inEnv: "header.json",
+ inOmmersRlp: "ommers.json",
+ inTxsRlp: "txs.rlp",
+ },
+ expOut: "exp.json",
+ },
+ { // clique test seal
+ base: "./testdata/21",
+ input: b11rInput{
+ inEnv: "header.json",
+ inOmmersRlp: "ommers.json",
+ inTxsRlp: "txs.rlp",
+ inClique: "clique.json",
+ },
+ expOut: "exp-clique.json",
+ },
+ { // block with ommers
+ base: "./testdata/22",
+ input: b11rInput{
+ inEnv: "header.json",
+ inOmmersRlp: "ommers.json",
+ inTxsRlp: "txs.rlp",
+ },
+ expOut: "exp.json",
+ },
+ } {
+
+ args := []string{"b11r"}
+ args = append(args, tc.input.get(tc.base)...)
+
+ tt.Run("evm-test", args...)
+ tt.Logf("args:\n go run . %v\n", strings.Join(args, " "))
+ // Compare the expected output, if provided
+ if tc.expOut != "" {
+ want, err := os.ReadFile(fmt.Sprintf("%v/%v", tc.base, tc.expOut))
+ if err != nil {
+ t.Fatalf("test %d: could not read expected output: %v", i, err)
+ }
+ have := tt.Output()
+ ok, err := cmpJson(have, want)
+ switch {
+ case err != nil:
+ t.Logf(string(have))
+ t.Fatalf("test %d, json parsing failed: %v", i, err)
+ case !ok:
+ t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want))
+ }
+ }
+ tt.WaitExit()
+ if have, want := tt.ExitStatus(), tc.expExitCode; have != want {
+ t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want)
+ }
+ }
+}
+
// cmpJson compares the JSON in two byte slices.
func cmpJson(a, b []byte) (bool, error) {
var j, j2 interface{}
diff --git a/cmd/evm/testdata/20/exp.json b/cmd/evm/testdata/20/exp.json
new file mode 100644
index 000000000..7bec6cefd
--- /dev/null
+++ b/cmd/evm/testdata/20/exp.json
@@ -0,0 +1,4 @@
+{
+ "rlp": "0xf902d9f90211a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794e997a23b159e2e2a5ce72333262972374b15425ca0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e99476574682f76312e302e312f6c696e75782f676f312e342e32a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf8897435673d874f7c8f8c2f85f8002825208948a8eafb1cf62bfbeb1741769dae1a9dd4799619201801ba09500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdba07235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600f85f8002825208948a8eafb1cf62bfbeb1741769dae1a9dd4799619201801ba09500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdba07235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600c0",
+ "hash": "0xaba9a3b6a4e96e9ecffcadaa5a2ae0589359455617535cd86589fe1dd26fe899"
+}
diff --git a/cmd/evm/testdata/20/header.json b/cmd/evm/testdata/20/header.json
new file mode 100644
index 000000000..fb9b7fc56
--- /dev/null
+++ b/cmd/evm/testdata/20/header.json
@@ -0,0 +1,14 @@
+{
+ "parentHash": "0xd6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34e",
+ "miner": "0xe997a23b159e2e2a5ce72333262972374b15425c",
+ "stateRoot": "0x325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2e",
+ "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "difficulty": "0x1000",
+ "number": "0xc3be",
+ "gasLimit": "0x50785",
+ "gasUsed": "0x0",
+ "timestamp": "0x55c5277e",
+ "extraData": "0x476574682f76312e302e312f6c696e75782f676f312e342e32",
+ "mixHash": "0x5865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf",
+ "nonce": "0x97435673d874f7c8"
+}
diff --git a/cmd/evm/testdata/20/ommers.json b/cmd/evm/testdata/20/ommers.json
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/cmd/evm/testdata/20/ommers.json
@@ -0,0 +1 @@
+[]
diff --git a/cmd/evm/testdata/20/readme.md b/cmd/evm/testdata/20/readme.md
new file mode 100644
index 000000000..2c448a96e
--- /dev/null
+++ b/cmd/evm/testdata/20/readme.md
@@ -0,0 +1,11 @@
+# Block building
+
+This test shows how `b11r` can be used to assemble an unsealed block.
+
+```console
+$ go run . b11r --input.header=testdata/20/header.json --input.txs=testdata/20/txs.rlp --input.ommers=testdata/20/ommers.json --output.block=stdout
+{
+ "rlp": "0xf90216f90211a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794e997a23b159e2e2a5ce72333262972374b15425ca0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e99476574682f76312e302e312f6c696e75782f676f312e342e32a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf8897435673d874f7c8c0c0",
+ "hash": "0xaba9a3b6a4e96e9ecffcadaa5a2ae0589359455617535cd86589fe1dd26fe899"
+}
+```
diff --git a/cmd/evm/testdata/20/txs.rlp b/cmd/evm/testdata/20/txs.rlp
new file mode 100644
index 000000000..3599ff065
--- /dev/null
+++ b/cmd/evm/testdata/20/txs.rlp
@@ -0,0 +1 @@
+"0xf8c2f85f8002825208948a8eafb1cf62bfbeb1741769dae1a9dd4799619201801ba09500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdba07235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600f85f8002825208948a8eafb1cf62bfbeb1741769dae1a9dd4799619201801ba09500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdba07235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600"
\ No newline at end of file
diff --git a/cmd/evm/testdata/21/clique.json b/cmd/evm/testdata/21/clique.json
new file mode 100644
index 000000000..84fa259a0
--- /dev/null
+++ b/cmd/evm/testdata/21/clique.json
@@ -0,0 +1,6 @@
+{
+ "secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8",
+ "voted": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
+ "authorize": false,
+ "vanity": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+}
diff --git a/cmd/evm/testdata/21/exp-clique.json b/cmd/evm/testdata/21/exp-clique.json
new file mode 100644
index 000000000..c990ba8aa
--- /dev/null
+++ b/cmd/evm/testdata/21/exp-clique.json
@@ -0,0 +1,4 @@
+{
+ "rlp": "0xf9025ff9025aa0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277eb861aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac540a67aaee364005841da84f488f6b6d0116dfb5103d091402c81a163d5f66666595e37f56f196d8c5c98da714dbfae68d6b7e1790cc734a20ec6ce52213ad800a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf88ffffffffffffffffc0c0",
+ "hash": "0x71c59102cc805dbe8741e1210ebe229a321eff144ac7276006fefe39e8357dc7"
+}
diff --git a/cmd/evm/testdata/21/exp.json b/cmd/evm/testdata/21/exp.json
new file mode 100644
index 000000000..b3e5e7a83
--- /dev/null
+++ b/cmd/evm/testdata/21/exp.json
@@ -0,0 +1,4 @@
+{
+ "rlp": "0xf901fdf901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0c0",
+ "hash": "0x801411e9f6609a659825690d13e4f75a3cfe9143952fa2d9573f3b0a5eb9ebbb"
+}
diff --git a/cmd/evm/testdata/21/header.json b/cmd/evm/testdata/21/header.json
new file mode 100644
index 000000000..62abe3cc2
--- /dev/null
+++ b/cmd/evm/testdata/21/header.json
@@ -0,0 +1,11 @@
+{
+ "parentHash": "0xd6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34e",
+ "stateRoot": "0x325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2e",
+ "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "difficulty": "0x1000",
+ "number": "0xc3be",
+ "gasLimit": "0x50785",
+ "gasUsed": "0x0",
+ "timestamp": "0x55c5277e",
+ "mixHash": "0x5865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf"
+}
diff --git a/cmd/evm/testdata/21/ommers.json b/cmd/evm/testdata/21/ommers.json
new file mode 100644
index 000000000..fe51488c7
--- /dev/null
+++ b/cmd/evm/testdata/21/ommers.json
@@ -0,0 +1 @@
+[]
diff --git a/cmd/evm/testdata/21/readme.md b/cmd/evm/testdata/21/readme.md
new file mode 100644
index 000000000..b70f106ff
--- /dev/null
+++ b/cmd/evm/testdata/21/readme.md
@@ -0,0 +1,23 @@
+# Sealed block building
+
+This test shows how `b11r` can be used to assemble a sealed block.
+
+## Ethash
+
+```console
+$ go run . b11r --input.header=testdata/21/header.json --input.txs=testdata/21/txs.rlp --input.ommers=testdata/21/ommers.json --seal.ethash --seal.ethash.mode=test --output.block=stdout
+{
+ "rlp": "0xf901fdf901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0c0",
+ "hash": "0x801411e9f6609a659825690d13e4f75a3cfe9143952fa2d9573f3b0a5eb9ebbb"
+}
+```
+
+## Clique
+
+```console
+$ go run . b11r --input.header=testdata/21/header.json --input.txs=testdata/21/txs.rlp --input.ommers=testdata/21/ommers.json --seal.clique=testdata/21/clique.json --output.block=stdout
+{
+ "rlp": "0xf9025ff9025aa0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277eb861aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac540a67aaee364005841da84f488f6b6d0116dfb5103d091402c81a163d5f66666595e37f56f196d8c5c98da714dbfae68d6b7e1790cc734a20ec6ce52213ad800a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf88ffffffffffffffffc0c0",
+ "hash": "0x71c59102cc805dbe8741e1210ebe229a321eff144ac7276006fefe39e8357dc7"
+}
+```
diff --git a/cmd/evm/testdata/21/txs.rlp b/cmd/evm/testdata/21/txs.rlp
new file mode 100644
index 000000000..e815397b3
--- /dev/null
+++ b/cmd/evm/testdata/21/txs.rlp
@@ -0,0 +1 @@
+"c0"
diff --git a/cmd/evm/testdata/22/exp-clique.json b/cmd/evm/testdata/22/exp-clique.json
new file mode 100644
index 000000000..c990ba8aa
--- /dev/null
+++ b/cmd/evm/testdata/22/exp-clique.json
@@ -0,0 +1,4 @@
+{
+ "rlp": "0xf9025ff9025aa0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277eb861aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac540a67aaee364005841da84f488f6b6d0116dfb5103d091402c81a163d5f66666595e37f56f196d8c5c98da714dbfae68d6b7e1790cc734a20ec6ce52213ad800a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf88ffffffffffffffffc0c0",
+ "hash": "0x71c59102cc805dbe8741e1210ebe229a321eff144ac7276006fefe39e8357dc7"
+}
diff --git a/cmd/evm/testdata/22/exp.json b/cmd/evm/testdata/22/exp.json
new file mode 100644
index 000000000..14fd81997
--- /dev/null
+++ b/cmd/evm/testdata/22/exp.json
@@ -0,0 +1,4 @@
+{
+ "rlp": "0xf905f5f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea06eb9f0c3cd68c9e97134e6725d12b1f1d8f0644458da6870a37ff84c908fb1e7940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0f903f6f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000",
+ "hash": "0xd9a81c8fcd57a7f2a0d2c375eff6ad192c30c3729a271303f0a9a7e1b357e755"
+}
diff --git a/cmd/evm/testdata/22/header.json b/cmd/evm/testdata/22/header.json
new file mode 100644
index 000000000..62abe3cc2
--- /dev/null
+++ b/cmd/evm/testdata/22/header.json
@@ -0,0 +1,11 @@
+{
+ "parentHash": "0xd6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34e",
+ "stateRoot": "0x325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2e",
+ "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "difficulty": "0x1000",
+ "number": "0xc3be",
+ "gasLimit": "0x50785",
+ "gasUsed": "0x0",
+ "timestamp": "0x55c5277e",
+ "mixHash": "0x5865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf"
+}
diff --git a/cmd/evm/testdata/22/ommers.json b/cmd/evm/testdata/22/ommers.json
new file mode 100644
index 000000000..997015b3c
--- /dev/null
+++ b/cmd/evm/testdata/22/ommers.json
@@ -0,0 +1 @@
+["0xf901fdf901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0c0","0xf901fdf901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0c0"]
diff --git a/cmd/evm/testdata/22/readme.md b/cmd/evm/testdata/22/readme.md
new file mode 100644
index 000000000..2cac8a243
--- /dev/null
+++ b/cmd/evm/testdata/22/readme.md
@@ -0,0 +1,11 @@
+# Building blocks with ommers
+
+This test shows how `b11r` can chain together ommer assembles into a canonical block.
+
+```console
+$ echo "{ \"ommers\": [`go run . b11r --input.header=testdata/22/header.json --input.txs=testdata/22/txs.rlp --output.block=stdout | jq '.[\"rlp\"]'`,`go run . b11r --input.header=testdata/22/header.json --input.txs=testdata/22/txs.rlp --output.block=stdout | jq '.[\"rlp\"]'`]}" | go run . b11r --input.header=testdata/22/header.json --input.txs=testdata/22/txs.rlp --input.ommers=stdin --output.block=stdout
+{
+ "rlp": "0xf905f5f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea06eb9f0c3cd68c9e97134e6725d12b1f1d8f0644458da6870a37ff84c908fb1e7940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0f903f6f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000",
+ "hash": "0xd9a81c8fcd57a7f2a0d2c375eff6ad192c30c3729a271303f0a9a7e1b357e755"
+}
+```
diff --git a/cmd/evm/testdata/22/txs.rlp b/cmd/evm/testdata/22/txs.rlp
new file mode 100644
index 000000000..e815397b3
--- /dev/null
+++ b/cmd/evm/testdata/22/txs.rlp
@@ -0,0 +1 @@
+"c0"