forked from cerc-io/plugeth
519 lines
18 KiB
Bash
519 lines
18 KiB
Bash
#!/bin/bash
|
|
ticks="\`\`\`"
|
|
|
|
function showjson(){
|
|
echo "\`$1\`:"
|
|
echo "${ticks}json"
|
|
cat $1
|
|
echo ""
|
|
echo "$ticks"
|
|
}
|
|
function demo(){
|
|
echo "$ticks"
|
|
echo "$1"
|
|
$1
|
|
echo ""
|
|
echo "$ticks"
|
|
echo ""
|
|
}
|
|
function tick(){
|
|
echo "$ticks"
|
|
}
|
|
|
|
function code(){
|
|
echo "$ticks$1"
|
|
}
|
|
|
|
cat << "EOF"
|
|
# EVM tool
|
|
|
|
The EVM tool provides a few useful subcommands to facilitate testing at the EVM
|
|
layer.
|
|
|
|
* transition tool (`t8n`) : a stateless state transition utility
|
|
* transaction tool (`t9n`) : a transaction validation utility
|
|
* block builder tool (`b11r`): a block assembler utility
|
|
|
|
## State transition tool (`t8n`)
|
|
|
|
|
|
The `evm t8n` tool is a stateless state transition utility. It is a utility
|
|
which can
|
|
|
|
1. Take a prestate, including
|
|
- Accounts,
|
|
- Block context information,
|
|
- Previous blockshashes (*optional)
|
|
2. Apply a set of transactions,
|
|
3. Apply a mining-reward (*optional),
|
|
4. And generate a post-state, including
|
|
- State root, transaction root, receipt root,
|
|
- Information about rejected transactions,
|
|
- Optionally: a full or partial post-state dump
|
|
|
|
### Specification
|
|
|
|
The idea is to specify the behaviour of this binary very _strict_, so that other
|
|
node implementors can build replicas based on their own state-machines, and the
|
|
state generators can swap between a \`geth\`-based implementation and a \`parityvm\`-based
|
|
implementation.
|
|
|
|
#### Command line params
|
|
|
|
Command line params that need to be supported are
|
|
|
|
```
|
|
EOF
|
|
./evm t8n -h | grep "\-\-trace\.\|\-\-output\.\|\-\-state\.\|\-\-input"
|
|
cat << "EOF"
|
|
```
|
|
#### Objects
|
|
|
|
The transition tool uses JSON objects to read and write data related to the transition operation. The
|
|
following object definitions are required.
|
|
|
|
##### `alloc`
|
|
|
|
The `alloc` object defines the prestate that transition will begin with.
|
|
|
|
```go
|
|
// Map of address to account definition.
|
|
type Alloc map[common.Address]Account
|
|
// Genesis account. Each field is optional.
|
|
type Account struct {
|
|
Code []byte `json:"code"`
|
|
Storage map[common.Hash]common.Hash `json:"storage"`
|
|
Balance *big.Int `json:"balance"`
|
|
Nonce uint64 `json:"nonce"`
|
|
SecretKey []byte `json:"secretKey"`
|
|
}
|
|
```
|
|
|
|
##### `env`
|
|
|
|
The `env` object defines the environmental context in which the transition will
|
|
take place.
|
|
|
|
```go
|
|
type Env struct {
|
|
// required
|
|
CurrentCoinbase common.Address `json:"currentCoinbase"`
|
|
CurrentGasLimit uint64 `json:"currentGasLimit"`
|
|
CurrentNumber uint64 `json:"currentNumber"`
|
|
CurrentTimestamp uint64 `json:"currentTimestamp"`
|
|
Withdrawals []*Withdrawal `json:"withdrawals"`
|
|
// optional
|
|
CurrentDifficulty *big.Int `json:"currentDifficulty"`
|
|
CurrentRandom *big.Int `json:"currentRandom"`
|
|
CurrentBaseFee *big.Int `json:"currentBaseFee"`
|
|
ParentDifficulty *big.Int `json:"parentDifficulty"`
|
|
ParentGasUsed uint64 `json:"parentGasUsed"`
|
|
ParentGasLimit uint64 `json:"parentGasLimit"`
|
|
ParentTimestamp uint64 `json:"parentTimestamp"`
|
|
BlockHashes map[uint64]common.Hash `json:"blockHashes"`
|
|
ParentUncleHash common.Hash `json:"parentUncleHash"`
|
|
Ommers []Ommer `json:"ommers"`
|
|
}
|
|
type Ommer struct {
|
|
Delta uint64 `json:"delta"`
|
|
Address common.Address `json:"address"`
|
|
}
|
|
type Withdrawal struct {
|
|
Index uint64 `json:"index"`
|
|
ValidatorIndex uint64 `json:"validatorIndex"`
|
|
Recipient common.Address `json:"recipient"`
|
|
Amount *big.Int `json:"amount"`
|
|
}
|
|
```
|
|
|
|
##### `txs`
|
|
|
|
The `txs` object is an array of any of the transaction types: `LegacyTx`,
|
|
`AccessListTx`, or `DynamicFeeTx`.
|
|
|
|
```go
|
|
type LegacyTx struct {
|
|
Nonce uint64 `json:"nonce"`
|
|
GasPrice *big.Int `json:"gasPrice"`
|
|
Gas uint64 `json:"gas"`
|
|
To *common.Address `json:"to"`
|
|
Value *big.Int `json:"value"`
|
|
Data []byte `json:"data"`
|
|
V *big.Int `json:"v"`
|
|
R *big.Int `json:"r"`
|
|
S *big.Int `json:"s"`
|
|
SecretKey *common.Hash `json:"secretKey"`
|
|
}
|
|
type AccessList []AccessTuple
|
|
type AccessTuple struct {
|
|
Address common.Address `json:"address" gencodec:"required"`
|
|
StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"`
|
|
}
|
|
type AccessListTx struct {
|
|
ChainID *big.Int `json:"chainId"`
|
|
Nonce uint64 `json:"nonce"`
|
|
GasPrice *big.Int `json:"gasPrice"`
|
|
Gas uint64 `json:"gas"`
|
|
To *common.Address `json:"to"`
|
|
Value *big.Int `json:"value"`
|
|
Data []byte `json:"data"`
|
|
AccessList AccessList `json:"accessList"`
|
|
V *big.Int `json:"v"`
|
|
R *big.Int `json:"r"`
|
|
S *big.Int `json:"s"`
|
|
SecretKey *common.Hash `json:"secretKey"`
|
|
}
|
|
type DynamicFeeTx struct {
|
|
ChainID *big.Int `json:"chainId"`
|
|
Nonce uint64 `json:"nonce"`
|
|
GasTipCap *big.Int `json:"maxPriorityFeePerGas"`
|
|
GasFeeCap *big.Int `json:"maxFeePerGas"`
|
|
Gas uint64 `json:"gas"`
|
|
To *common.Address `json:"to"`
|
|
Value *big.Int `json:"value"`
|
|
Data []byte `json:"data"`
|
|
AccessList AccessList `json:"accessList"`
|
|
V *big.Int `json:"v"`
|
|
R *big.Int `json:"r"`
|
|
S *big.Int `json:"s"`
|
|
SecretKey *common.Hash `json:"secretKey"`
|
|
}
|
|
```
|
|
|
|
##### `result`
|
|
|
|
The `result` object is output after a transition is executed. It includes
|
|
information about the post-transition environment.
|
|
|
|
```go
|
|
type ExecutionResult struct {
|
|
StateRoot common.Hash `json:"stateRoot"`
|
|
TxRoot common.Hash `json:"txRoot"`
|
|
ReceiptRoot common.Hash `json:"receiptsRoot"`
|
|
LogsHash common.Hash `json:"logsHash"`
|
|
Bloom types.Bloom `json:"logsBloom"`
|
|
Receipts types.Receipts `json:"receipts"`
|
|
Rejected []*rejectedTx `json:"rejected,omitempty"`
|
|
Difficulty *big.Int `json:"currentDifficulty"`
|
|
GasUsed uint64 `json:"gasUsed"`
|
|
BaseFee *big.Int `json:"currentBaseFee,omitempty"`
|
|
}
|
|
```
|
|
|
|
#### Error codes and output
|
|
|
|
All logging should happen against the `stderr`.
|
|
There are a few (not many) errors that can occur, those are defined below.
|
|
|
|
##### EVM-based errors (`2` to `9`)
|
|
|
|
- Other EVM error. Exit code `2`
|
|
- Failed configuration: when a non-supported or invalid fork was specified. Exit code `3`.
|
|
- Block history is not supplied, but needed for a `BLOCKHASH` operation. If `BLOCKHASH`
|
|
is invoked targeting a block which history has not been provided for, the program will
|
|
exit with code `4`.
|
|
|
|
##### IO errors (`10`-`20`)
|
|
|
|
- Invalid input json: the supplied data could not be marshalled.
|
|
The program will exit with code `10`
|
|
- IO problems: failure to load or save files, the program will exit with code `11`
|
|
|
|
```
|
|
# This should exit with 3
|
|
./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null
|
|
EOF
|
|
./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null
|
|
exitcode=$?
|
|
if [ $exitcode != 3 ]; then
|
|
echo "Failed, exitcode should be 3,was $exitcode"
|
|
else
|
|
echo "exitcode:$exitcode OK"
|
|
fi
|
|
cat << "EOF"
|
|
```
|
|
#### Forks
|
|
### Basic usage
|
|
|
|
The chain configuration to be used for a transition is specified via the
|
|
`--state.fork` CLI flag. A list of possible values and configurations can be
|
|
found in [`tests/init.go`](tests/init.go).
|
|
|
|
#### Examples
|
|
##### Basic usage
|
|
|
|
Invoking it with the provided example files
|
|
EOF
|
|
cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin"
|
|
tick;echo "$cmd"; tick
|
|
$cmd 2>/dev/null
|
|
echo "Two resulting files:"
|
|
echo ""
|
|
showjson alloc.json
|
|
showjson result.json
|
|
echo ""
|
|
|
|
echo "We can make them spit out the data to e.g. \`stdout\` like this:"
|
|
cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout --state.fork=Berlin"
|
|
tick;echo "$cmd"; tick
|
|
output=`$cmd 2>/dev/null`
|
|
echo "Output:"
|
|
echo "${ticks}json"
|
|
echo "$output"
|
|
echo "$ticks"
|
|
|
|
cat << "EOF"
|
|
|
|
#### About Ommers
|
|
|
|
Mining rewards and ommer rewards might need to be added. This is how those are applied:
|
|
|
|
- `block_reward` is the block mining reward for the miner (`0xaa`), of a block at height `N`.
|
|
- For each ommer (mined by `0xbb`), with blocknumber `N-delta`
|
|
- (where `delta` is the difference between the current block and the ommer)
|
|
- The account `0xbb` (ommer miner) is awarded `(8-delta)/ 8 * block_reward`
|
|
- The account `0xaa` (block miner) is awarded `block_reward / 32`
|
|
|
|
To make `t8n` apply these, the following inputs are required:
|
|
|
|
- `--state.reward`
|
|
- For ethash, it is `5000000000000000000` `wei`,
|
|
- If this is not defined, mining rewards are not applied,
|
|
- A value of `0` is valid, and causes accounts to be 'touched'.
|
|
- For each ommer, the tool needs to be given an `address\` and a `delta`. This
|
|
is done via the `ommers` field in `env`.
|
|
|
|
Note: the tool does not verify that e.g. the normal uncle rules apply,
|
|
and allows e.g two uncles at the same height, or the uncle-distance. This means that
|
|
the tool allows for negative uncle reward (distance > 8)
|
|
|
|
Example:
|
|
EOF
|
|
|
|
showjson ./testdata/5/env.json
|
|
|
|
echo "When applying this, using a reward of \`0x08\`"
|
|
cmd="./evm t8n --input.alloc=./testdata/5/alloc.json -input.txs=./testdata/5/txs.json --input.env=./testdata/5/env.json --output.alloc=stdout --state.reward=0x80 --state.fork=Berlin"
|
|
output=`$cmd 2>/dev/null`
|
|
echo "Output:"
|
|
echo "${ticks}json"
|
|
echo "$output"
|
|
echo "$ticks"
|
|
|
|
echo "#### Future EIPS"
|
|
echo ""
|
|
echo "It is also possible to experiment with future eips that are not yet defined in a hard fork."
|
|
echo "Example, putting EIP-1344 into Frontier: "
|
|
cmd="./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --input.txs=./testdata/1/txs.json --input.env=/testdata/1/env.json"
|
|
tick;echo "$cmd"; tick
|
|
echo ""
|
|
|
|
echo "#### Block history"
|
|
echo ""
|
|
echo "The \`BLOCKHASH\` opcode requires blockhashes to be provided by the caller, inside the \`env\`."
|
|
echo "If a required blockhash is not provided, the exit code should be \`4\`:"
|
|
echo "Example where blockhashes are provided: "
|
|
demo "./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace --state.fork=Berlin"
|
|
cmd="cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2"
|
|
tick && echo $cmd && tick
|
|
echo "$ticks"
|
|
cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2
|
|
echo "$ticks"
|
|
echo ""
|
|
|
|
echo "In this example, the caller has not provided the required blockhash:"
|
|
cmd="./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace --state.fork=Berlin"
|
|
tick && echo $cmd && $cmd 2>&1
|
|
errc=$?
|
|
tick
|
|
echo "Error code: $errc"
|
|
echo ""
|
|
|
|
echo "#### Chaining"
|
|
echo ""
|
|
echo "Another thing that can be done, is to chain invocations:"
|
|
cmd1="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin --output.alloc=stdout"
|
|
cmd2="./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json --state.fork=Berlin"
|
|
echo "$ticks"
|
|
echo "$cmd1 | $cmd2"
|
|
output=$($cmd1 | $cmd2 )
|
|
echo $output
|
|
echo "$ticks"
|
|
echo "What happened here, is that we first applied two identical transactions, so the second one was rejected. "
|
|
echo "Then, taking the poststate alloc as the input for the next state, we tried again to include"
|
|
echo "the same two transactions: this time, both failed due to too low nonce."
|
|
echo ""
|
|
echo "In order to meaningfully chain invocations, one would need to provide meaningful new \`env\`, otherwise the"
|
|
echo "actual blocknumber (exposed to the EVM) would not increase."
|
|
echo ""
|
|
|
|
echo "#### Transactions in RLP form"
|
|
echo ""
|
|
echo "It is possible to provide already-signed transactions as input to, using an \`input.txs\` which ends with the \`rlp\` suffix."
|
|
echo "The input format for RLP-form transactions is _identical_ to the _output_ format for block bodies. Therefore, it's fully possible"
|
|
echo "to use the evm to go from \`json\` input to \`rlp\` input."
|
|
echo ""
|
|
echo "The following command takes **json** the transactions in \`./testdata/13/txs.json\` and signs them. After execution, they are output to \`signed_txs.rlp\`.:"
|
|
cmd="./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./testdata/13/txs.json --input.env=./testdata/13/env.json --output.result=alloc_jsontx.json --output.body=signed_txs.rlp"
|
|
echo "$ticks"
|
|
echo $cmd
|
|
$cmd 2>&1
|
|
echo "$ticks"
|
|
echo ""
|
|
echo "The \`output.body\` is the rlp-list of transactions, encoded in hex and placed in a string a'la \`json\` encoding rules:"
|
|
demo "cat signed_txs.rlp"
|
|
echo "We can use \`rlpdump\` to check what the contents are: "
|
|
echo "$ticks"
|
|
echo "rlpdump -hex \$(cat signed_txs.rlp | jq -r )"
|
|
rlpdump -hex $(cat signed_txs.rlp | jq -r )
|
|
echo "$ticks"
|
|
echo "Now, we can now use those (or any other already signed transactions), as input, like so: "
|
|
cmd="./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./signed_txs.rlp --input.env=./testdata/13/env.json --output.result=alloc_rlptx.json"
|
|
echo "$ticks"
|
|
echo $cmd
|
|
$cmd 2>&1
|
|
echo "$ticks"
|
|
echo "You might have noticed that the results from these two invocations were stored in two separate files. "
|
|
echo "And we can now finally check that they match."
|
|
echo "$ticks"
|
|
echo "cat alloc_jsontx.json | jq .stateRoot && cat alloc_rlptx.json | jq .stateRoot"
|
|
cat alloc_jsontx.json | jq .stateRoot && cat alloc_rlptx.json | jq .stateRoot
|
|
echo "$ticks"
|
|
|
|
cat << "EOF"
|
|
|
|
## Transaction tool
|
|
|
|
The transaction tool is used to perform static validity checks on transactions such as:
|
|
* intrinsic gas calculation
|
|
* max values on integers
|
|
* fee semantics, such as `maxFeePerGas < maxPriorityFeePerGas`
|
|
* newer tx types on old forks
|
|
|
|
### Examples
|
|
|
|
EOF
|
|
|
|
cmd="./evm t9n --state.fork Homestead --input.txs testdata/15/signed_txs.rlp"
|
|
tick;echo "$cmd";
|
|
$cmd 2>/dev/null
|
|
tick
|
|
|
|
cmd="./evm t9n --state.fork London --input.txs testdata/15/signed_txs.rlp"
|
|
tick;echo "$cmd";
|
|
$cmd 2>/dev/null
|
|
tick
|
|
|
|
cat << "EOF"
|
|
## Block builder tool (b11r)
|
|
|
|
The `evm b11r` tool is used to assemble and seal full block rlps.
|
|
|
|
### Specification
|
|
|
|
#### Command line params
|
|
|
|
Command line params that need to be supported are:
|
|
|
|
```
|
|
--input.header value `stdin` or file name of where to find the block header to use. (default: "header.json")
|
|
--input.ommers value `stdin` or file name of where to find the list of ommer header RLPs to use.
|
|
--input.txs value `stdin` or file name of where to find the transactions list in RLP form. (default: "txs.rlp")
|
|
--output.basedir value Specifies where output files are placed. Will be created if it does not exist.
|
|
--output.block value Determines where to put the alloc of the post-state. (default: "block.json")
|
|
<file> - into the file <file>
|
|
`stdout` - into the stdout output
|
|
`stderr` - into the stderr output
|
|
--seal.clique value Seal block with Clique. `stdin` or file name of where to find the Clique sealing data.
|
|
--seal.ethash Seal block with ethash. (default: false)
|
|
--seal.ethash.dir value Path to ethash DAG. If none exists, a new DAG will be generated.
|
|
--seal.ethash.mode value Defines the type and amount of PoW verification an ethash engine makes. (default: "normal")
|
|
--verbosity value Sets the verbosity level. (default: 3)
|
|
```
|
|
|
|
#### Objects
|
|
|
|
##### `header`
|
|
|
|
The `header` object is a consensus 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"`
|
|
}
|
|
```
|
|
#### `ommers`
|
|
|
|
The `ommers` object is a list of RLP-encoded ommer blocks in hex
|
|
representation.
|
|
|
|
```go=
|
|
type Ommers []string
|
|
```
|
|
|
|
#### `txs`
|
|
|
|
The `txs` object is a list of RLP-encoded transactions in hex representation.
|
|
|
|
```go=
|
|
type Txs []string
|
|
```
|
|
|
|
#### `clique`
|
|
|
|
The `clique` object provides the necessary information to complete a clique
|
|
seal of the block.
|
|
|
|
```go=
|
|
var CliqueInfo struct {
|
|
Key *common.Hash `json:"secretKey"`
|
|
Voted *common.Address `json:"voted"`
|
|
Authorize *bool `json:"authorize"`
|
|
Vanity common.Hash `json:"vanity"`
|
|
}
|
|
```
|
|
|
|
#### `output`
|
|
|
|
The `output` object contains two values, the block RLP and the block hash.
|
|
|
|
```go=
|
|
type BlockInfo struct {
|
|
Rlp []byte `json:"rlp"`
|
|
Hash common.Hash `json:"hash"`
|
|
}
|
|
```
|
|
|
|
## A Note on Encoding
|
|
|
|
The encoding of values for `evm` utility attempts to be relatively flexible. It
|
|
generally supports hex-encoded or decimal-encoded numeric values, and
|
|
hex-encoded byte values (like `common.Address`, `common.Hash`, etc). When in
|
|
doubt, the [`execution-apis`](https://github.com/ethereum/execution-apis) way
|
|
of encoding should always be accepted.
|
|
|
|
## Testing
|
|
|
|
There are many test cases in the [`cmd/evm/testdata`](./testdata) directory.
|
|
These fixtures are used to power the `t8n` tests in
|
|
[`t8n_test.go`](./t8n_test.go). The best way to verify correctness of new `evm`
|
|
implementations is to execute these and verify the output and error codes match
|
|
the expected values.
|
|
|
|
EOF
|