Skip to content

Commit 441c7f2

Browse files
authored
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.
1 parent 5d4bcbc commit 441c7f2

26 files changed

+881
-38
lines changed

cmd/evm/internal/t8ntool/block.go

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
1+
// Copyright 2021 The go-ethereum Authors
2+
// This file is part of go-ethereum.
3+
//
4+
// go-ethereum is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// go-ethereum is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU General Public License
15+
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package t8ntool
18+
19+
import (
20+
"crypto/ecdsa"
21+
"encoding/json"
22+
"errors"
23+
"fmt"
24+
"math/big"
25+
"os"
26+
27+
"github.com/ethereum/go-ethereum/common"
28+
"github.com/ethereum/go-ethereum/common/hexutil"
29+
"github.com/ethereum/go-ethereum/common/math"
30+
"github.com/ethereum/go-ethereum/consensus/clique"
31+
"github.com/ethereum/go-ethereum/consensus/ethash"
32+
"github.com/ethereum/go-ethereum/core/types"
33+
"github.com/ethereum/go-ethereum/crypto"
34+
"github.com/ethereum/go-ethereum/log"
35+
"github.com/ethereum/go-ethereum/rlp"
36+
"gopkg.in/urfave/cli.v1"
37+
)
38+
39+
//go:generate gencodec -type header -field-override headerMarshaling -out gen_header.go
40+
type header struct {
41+
ParentHash common.Hash `json:"parentHash"`
42+
OmmerHash *common.Hash `json:"sha3Uncles"`
43+
Coinbase *common.Address `json:"miner"`
44+
Root common.Hash `json:"stateRoot" gencodec:"required"`
45+
TxHash *common.Hash `json:"transactionsRoot"`
46+
ReceiptHash *common.Hash `json:"receiptsRoot"`
47+
Bloom types.Bloom `json:"logsBloom"`
48+
Difficulty *big.Int `json:"difficulty"`
49+
Number *big.Int `json:"number" gencodec:"required"`
50+
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
51+
GasUsed uint64 `json:"gasUsed"`
52+
Time uint64 `json:"timestamp" gencodec:"required"`
53+
Extra []byte `json:"extraData"`
54+
MixDigest common.Hash `json:"mixHash"`
55+
Nonce *types.BlockNonce `json:"nonce"`
56+
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
57+
}
58+
59+
type headerMarshaling struct {
60+
Difficulty *math.HexOrDecimal256
61+
Number *math.HexOrDecimal256
62+
GasLimit math.HexOrDecimal64
63+
GasUsed math.HexOrDecimal64
64+
Time math.HexOrDecimal64
65+
Extra hexutil.Bytes
66+
BaseFee *math.HexOrDecimal256
67+
}
68+
69+
type bbInput struct {
70+
Header *header `json:"header,omitempty"`
71+
OmmersRlp []string `json:"ommers,omitempty"`
72+
TxRlp string `json:"txs,omitempty"`
73+
Clique *cliqueInput `json:"clique,omitempty"`
74+
75+
Ethash bool `json:"-"`
76+
EthashDir string `json:"-"`
77+
PowMode ethash.Mode `json:"-"`
78+
Txs []*types.Transaction `json:"-"`
79+
Ommers []*types.Header `json:"-"`
80+
}
81+
82+
type cliqueInput struct {
83+
Key *ecdsa.PrivateKey
84+
Voted *common.Address
85+
Authorize *bool
86+
Vanity common.Hash
87+
}
88+
89+
// UnmarshalJSON implements json.Unmarshaler interface.
90+
func (c *cliqueInput) UnmarshalJSON(input []byte) error {
91+
var x struct {
92+
Key *common.Hash `json:"secretKey"`
93+
Voted *common.Address `json:"voted"`
94+
Authorize *bool `json:"authorize"`
95+
Vanity common.Hash `json:"vanity"`
96+
}
97+
if err := json.Unmarshal(input, &x); err != nil {
98+
return err
99+
}
100+
if x.Key == nil {
101+
return errors.New("missing required field 'secretKey' for cliqueInput")
102+
}
103+
if ecdsaKey, err := crypto.ToECDSA(x.Key[:]); err != nil {
104+
return err
105+
} else {
106+
c.Key = ecdsaKey
107+
}
108+
c.Voted = x.Voted
109+
c.Authorize = x.Authorize
110+
c.Vanity = x.Vanity
111+
return nil
112+
}
113+
114+
// ToBlock converts i into a *types.Block
115+
func (i *bbInput) ToBlock() *types.Block {
116+
header := &types.Header{
117+
ParentHash: i.Header.ParentHash,
118+
UncleHash: types.EmptyUncleHash,
119+
Coinbase: common.Address{},
120+
Root: i.Header.Root,
121+
TxHash: types.EmptyRootHash,
122+
ReceiptHash: types.EmptyRootHash,
123+
Bloom: i.Header.Bloom,
124+
Difficulty: common.Big0,
125+
Number: i.Header.Number,
126+
GasLimit: i.Header.GasLimit,
127+
GasUsed: i.Header.GasUsed,
128+
Time: i.Header.Time,
129+
Extra: i.Header.Extra,
130+
MixDigest: i.Header.MixDigest,
131+
BaseFee: i.Header.BaseFee,
132+
}
133+
134+
// Fill optional values.
135+
if i.Header.OmmerHash != nil {
136+
header.UncleHash = *i.Header.OmmerHash
137+
} else if len(i.Ommers) != 0 {
138+
// Calculate the ommer hash if none is provided and there are ommers to hash
139+
header.UncleHash = types.CalcUncleHash(i.Ommers)
140+
}
141+
if i.Header.Coinbase != nil {
142+
header.Coinbase = *i.Header.Coinbase
143+
}
144+
if i.Header.TxHash != nil {
145+
header.TxHash = *i.Header.TxHash
146+
}
147+
if i.Header.ReceiptHash != nil {
148+
header.ReceiptHash = *i.Header.ReceiptHash
149+
}
150+
if i.Header.Nonce != nil {
151+
header.Nonce = *i.Header.Nonce
152+
}
153+
if header.Difficulty != nil {
154+
header.Difficulty = i.Header.Difficulty
155+
}
156+
return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers)
157+
}
158+
159+
// SealBlock seals the given block using the configured engine.
160+
func (i *bbInput) SealBlock(block *types.Block) (*types.Block, error) {
161+
switch {
162+
case i.Ethash:
163+
return i.sealEthash(block)
164+
case i.Clique != nil:
165+
return i.sealClique(block)
166+
default:
167+
return block, nil
168+
}
169+
}
170+
171+
// sealEthash seals the given block using ethash.
172+
func (i *bbInput) sealEthash(block *types.Block) (*types.Block, error) {
173+
if i.Header.Nonce != nil {
174+
return nil, NewError(ErrorConfig, fmt.Errorf("sealing with ethash will overwrite provided nonce"))
175+
}
176+
ethashConfig := ethash.Config{
177+
PowMode: i.PowMode,
178+
DatasetDir: i.EthashDir,
179+
CacheDir: i.EthashDir,
180+
DatasetsInMem: 1,
181+
DatasetsOnDisk: 2,
182+
CachesInMem: 2,
183+
CachesOnDisk: 3,
184+
}
185+
engine := ethash.New(ethashConfig, nil, true)
186+
defer engine.Close()
187+
// Use a buffered chan for results.
188+
// If the testmode is used, the sealer will return quickly, and complain
189+
// "Sealing result is not read by miner" if it cannot write the result.
190+
results := make(chan *types.Block, 1)
191+
if err := engine.Seal(nil, block, results, nil); err != nil {
192+
panic(fmt.Sprintf("failed to seal block: %v", err))
193+
}
194+
found := <-results
195+
return block.WithSeal(found.Header()), nil
196+
}
197+
198+
// sealClique seals the given block using clique.
199+
func (i *bbInput) sealClique(block *types.Block) (*types.Block, error) {
200+
// If any clique value overwrites an explicit header value, fail
201+
// to avoid silently building a block with unexpected values.
202+
if i.Header.Extra != nil {
203+
return nil, NewError(ErrorConfig, fmt.Errorf("sealing with clique will overwrite provided extra data"))
204+
}
205+
header := block.Header()
206+
if i.Clique.Voted != nil {
207+
if i.Header.Coinbase != nil {
208+
return nil, NewError(ErrorConfig, fmt.Errorf("sealing with clique and voting will overwrite provided coinbase"))
209+
}
210+
header.Coinbase = *i.Clique.Voted
211+
}
212+
if i.Clique.Authorize != nil {
213+
if i.Header.Nonce != nil {
214+
return nil, NewError(ErrorConfig, fmt.Errorf("sealing with clique and voting will overwrite provided nonce"))
215+
}
216+
if *i.Clique.Authorize {
217+
header.Nonce = [8]byte{}
218+
} else {
219+
header.Nonce = [8]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
220+
}
221+
}
222+
// Extra is fixed 32 byte vanity and 65 byte signature
223+
header.Extra = make([]byte, 32+65)
224+
copy(header.Extra[0:32], i.Clique.Vanity.Bytes()[:])
225+
226+
// Sign the seal hash and fill in the rest of the extra data
227+
h := clique.SealHash(header)
228+
sighash, err := crypto.Sign(h[:], i.Clique.Key)
229+
if err != nil {
230+
return nil, err
231+
}
232+
copy(header.Extra[32:], sighash)
233+
block = block.WithSeal(header)
234+
return block, nil
235+
}
236+
237+
// BuildBlock constructs a block from the given inputs.
238+
func BuildBlock(ctx *cli.Context) error {
239+
// Configure the go-ethereum logger
240+
glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.TerminalFormat(false)))
241+
glogger.Verbosity(log.Lvl(ctx.Int(VerbosityFlag.Name)))
242+
log.Root().SetHandler(glogger)
243+
244+
baseDir, err := createBasedir(ctx)
245+
if err != nil {
246+
return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err))
247+
}
248+
inputData, err := readInput(ctx)
249+
if err != nil {
250+
return err
251+
}
252+
block := inputData.ToBlock()
253+
block, err = inputData.SealBlock(block)
254+
if err != nil {
255+
return err
256+
}
257+
return dispatchBlock(ctx, baseDir, block)
258+
}
259+
260+
func readInput(ctx *cli.Context) (*bbInput, error) {
261+
var (
262+
headerStr = ctx.String(InputHeaderFlag.Name)
263+
ommersStr = ctx.String(InputOmmersFlag.Name)
264+
txsStr = ctx.String(InputTxsRlpFlag.Name)
265+
cliqueStr = ctx.String(SealCliqueFlag.Name)
266+
ethashOn = ctx.Bool(SealEthashFlag.Name)
267+
ethashDir = ctx.String(SealEthashDirFlag.Name)
268+
ethashMode = ctx.String(SealEthashModeFlag.Name)
269+
inputData = &bbInput{}
270+
)
271+
if ethashOn && cliqueStr != "" {
272+
return nil, NewError(ErrorConfig, fmt.Errorf("both ethash and clique sealing specified, only one may be chosen"))
273+
}
274+
if ethashOn {
275+
inputData.Ethash = ethashOn
276+
inputData.EthashDir = ethashDir
277+
switch ethashMode {
278+
case "normal":
279+
inputData.PowMode = ethash.ModeNormal
280+
case "test":
281+
inputData.PowMode = ethash.ModeTest
282+
case "fake":
283+
inputData.PowMode = ethash.ModeFake
284+
default:
285+
return nil, NewError(ErrorConfig, fmt.Errorf("unknown pow mode: %s, supported modes: test, fake, normal", ethashMode))
286+
}
287+
}
288+
if headerStr == stdinSelector || ommersStr == stdinSelector || txsStr == stdinSelector || cliqueStr == stdinSelector {
289+
decoder := json.NewDecoder(os.Stdin)
290+
if err := decoder.Decode(inputData); err != nil {
291+
return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err))
292+
}
293+
}
294+
if cliqueStr != stdinSelector && cliqueStr != "" {
295+
var clique cliqueInput
296+
if err := readFile(cliqueStr, "clique", &clique); err != nil {
297+
return nil, err
298+
}
299+
inputData.Clique = &clique
300+
}
301+
if headerStr != stdinSelector {
302+
var env header
303+
if err := readFile(headerStr, "header", &env); err != nil {
304+
return nil, err
305+
}
306+
inputData.Header = &env
307+
}
308+
if ommersStr != stdinSelector && ommersStr != "" {
309+
var ommers []string
310+
if err := readFile(ommersStr, "ommers", &ommers); err != nil {
311+
return nil, err
312+
}
313+
inputData.OmmersRlp = ommers
314+
}
315+
if txsStr != stdinSelector {
316+
var txs string
317+
if err := readFile(txsStr, "txs", &txs); err != nil {
318+
return nil, err
319+
}
320+
inputData.TxRlp = txs
321+
}
322+
// Deserialize rlp txs and ommers
323+
var (
324+
ommers = []*types.Header{}
325+
txs = []*types.Transaction{}
326+
)
327+
if inputData.TxRlp != "" {
328+
if err := rlp.DecodeBytes(common.FromHex(inputData.TxRlp), &txs); err != nil {
329+
return nil, NewError(ErrorRlp, fmt.Errorf("unable to decode transaction from rlp data: %v", err))
330+
}
331+
inputData.Txs = txs
332+
}
333+
for _, str := range inputData.OmmersRlp {
334+
type extblock struct {
335+
Header *types.Header
336+
Txs []*types.Transaction
337+
Ommers []*types.Header
338+
}
339+
var ommer *extblock
340+
if err := rlp.DecodeBytes(common.FromHex(str), &ommer); err != nil {
341+
return nil, NewError(ErrorRlp, fmt.Errorf("unable to decode ommer from rlp data: %v", err))
342+
}
343+
ommers = append(ommers, ommer.Header)
344+
}
345+
inputData.Ommers = ommers
346+
347+
return inputData, nil
348+
}
349+
350+
// dispatchOutput writes the output data to either stderr or stdout, or to the specified
351+
// files
352+
func dispatchBlock(ctx *cli.Context, baseDir string, block *types.Block) error {
353+
raw, _ := rlp.EncodeToBytes(block)
354+
355+
type blockInfo struct {
356+
Rlp hexutil.Bytes `json:"rlp"`
357+
Hash common.Hash `json:"hash"`
358+
}
359+
var enc blockInfo
360+
enc.Rlp = raw
361+
enc.Hash = block.Hash()
362+
363+
b, err := json.MarshalIndent(enc, "", " ")
364+
if err != nil {
365+
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err))
366+
}
367+
switch dest := ctx.String(OutputBlockFlag.Name); dest {
368+
case "stdout":
369+
os.Stdout.Write(b)
370+
os.Stdout.WriteString("\n")
371+
case "stderr":
372+
os.Stderr.Write(b)
373+
os.Stderr.WriteString("\n")
374+
default:
375+
if err := saveFile(baseDir, dest, enc); err != nil {
376+
return err
377+
}
378+
}
379+
return nil
380+
}

0 commit comments

Comments
 (0)