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 <http://www.gnu.org/licenses/>.
+
+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<file> - into the file <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<file> - into the file <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 <http://www.gnu.org/licenses/>.
+
+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"