mirror of https://github.com/ethereum/go-ethereum
cmd/evm: add state transition tool for testing (#20958)
This PR implements the EVM state transition tool, which is intended to be the replacement for our retesteth client implementation. Documentation is present in the cmd/evm/README.md file. Co-authored-by: Felix Lange <fjl@twurst.com>pull/21275/head
parent
dd91c7ce6a
commit
e376d2fb31
@ -0,0 +1,268 @@ |
||||
## EVM state transition tool |
||||
|
||||
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 has to be supported are |
||||
``` |
||||
|
||||
--trace Output full trace logs to files <txhash>.jsonl |
||||
--trace.nomemory Disable full memory dump in traces |
||||
--trace.nostack Disable stack output in traces |
||||
--output.alloc alloc Determines where to put the alloc of the post-state. |
||||
`stdout` - into the stdout output |
||||
`stderr` - into the stderr output |
||||
--output.result result Determines where to put the result (stateroot, txroot etc) of the post-state. |
||||
`stdout` - into the stdout output |
||||
`stderr` - into the stderr output |
||||
--state.fork value Name of ruleset to use. |
||||
--state.chainid value ChainID to use (default: 1) |
||||
--state.reward value Mining reward. Set to -1 to disable (default: 0) |
||||
|
||||
``` |
||||
|
||||
### 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` |
||||
|
||||
## Examples |
||||
### Basic usage |
||||
|
||||
Invoking it with the provided example files |
||||
``` |
||||
./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json |
||||
``` |
||||
Two resulting files: |
||||
|
||||
`alloc.json`: |
||||
```json |
||||
{ |
||||
"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { |
||||
"balance": "0xfeed1a9d", |
||||
"nonce": "0x1" |
||||
}, |
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { |
||||
"balance": "0x5ffd4878be161d74", |
||||
"nonce": "0xac" |
||||
}, |
||||
"0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { |
||||
"balance": "0xa410" |
||||
} |
||||
} |
||||
``` |
||||
`result.json`: |
||||
```json |
||||
{ |
||||
"stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", |
||||
"txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", |
||||
"receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", |
||||
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"receipts": [ |
||||
{ |
||||
"root": "0x", |
||||
"status": "0x1", |
||||
"cumulativeGasUsed": "0x5208", |
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"logs": null, |
||||
"transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", |
||||
"contractAddress": "0x0000000000000000000000000000000000000000", |
||||
"gasUsed": "0x5208", |
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"transactionIndex": "0x0" |
||||
} |
||||
], |
||||
"rejected": [ |
||||
1 |
||||
] |
||||
} |
||||
``` |
||||
|
||||
We can make them spit out the data to e.g. `stdout` like this: |
||||
``` |
||||
./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 |
||||
``` |
||||
Output: |
||||
```json |
||||
{ |
||||
"alloc": { |
||||
"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { |
||||
"balance": "0xfeed1a9d", |
||||
"nonce": "0x1" |
||||
}, |
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { |
||||
"balance": "0x5ffd4878be161d74", |
||||
"nonce": "0xac" |
||||
}, |
||||
"0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { |
||||
"balance": "0xa410" |
||||
} |
||||
}, |
||||
"result": { |
||||
"stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", |
||||
"txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", |
||||
"receiptRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", |
||||
"logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", |
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"receipts": [ |
||||
{ |
||||
"root": "0x", |
||||
"status": "0x1", |
||||
"cumulativeGasUsed": "0x5208", |
||||
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", |
||||
"logs": null, |
||||
"transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", |
||||
"contractAddress": "0x0000000000000000000000000000000000000000", |
||||
"gasUsed": "0x5208", |
||||
"blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", |
||||
"transactionIndex": "0x0" |
||||
} |
||||
], |
||||
"rejected": [ |
||||
1 |
||||
] |
||||
} |
||||
} |
||||
``` |
||||
|
||||
## 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 `state_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 `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: |
||||
`./testdata/5/env.json`: |
||||
```json |
||||
{ |
||||
"currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", |
||||
"currentDifficulty": "0x20000", |
||||
"currentGasLimit": "0x750a163df65e8a", |
||||
"currentNumber": "1", |
||||
"currentTimestamp": "1000", |
||||
"ommers": [ |
||||
{"delta": 1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, |
||||
{"delta": 2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" } |
||||
] |
||||
} |
||||
``` |
||||
When applying this, using a reward of `0x08` |
||||
Output: |
||||
```json |
||||
{ |
||||
"alloc": { |
||||
"0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": { |
||||
"balance": "0x88" |
||||
}, |
||||
"0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": { |
||||
"balance": "0x70" |
||||
}, |
||||
"0xcccccccccccccccccccccccccccccccccccccccc": { |
||||
"balance": "0x60" |
||||
} |
||||
} |
||||
} |
||||
``` |
||||
### Future EIPS |
||||
|
||||
It is also possible to experiment with future eips that are not yet defined in a hard fork. |
||||
Example, putting EIP-1344 into Frontier: |
||||
``` |
||||
./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --input.txs=./testdata/1/txs.json --input.env=/testdata/1/env.json |
||||
``` |
||||
|
||||
### Block history |
||||
|
||||
The `BLOCKHASH` opcode requires blockhashes to be provided by the caller, inside the `env`. |
||||
If a required blockhash is not provided, the exit code should be `4`: |
||||
Example where blockhashes are provided: |
||||
``` |
||||
./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace |
||||
``` |
||||
``` |
||||
cat trace-0.jsonl | grep BLOCKHASH -C2 |
||||
``` |
||||
``` |
||||
{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memory":"0x","memSize":0,"stack":[],"returnStack":[],"depth":1,"refund":0,"opName":"PUSH1","error":""} |
||||
{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memory":"0x","memSize":0,"stack":["0x1"],"returnStack":[],"depth":1,"refund":0,"opName":"BLOCKHASH","error":""} |
||||
{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memory":"0x","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"returnStack":[],"depth":1,"refund":0,"opName":"STOP","error":""} |
||||
{"output":"","gasUsed":"0x17","time":155861} |
||||
``` |
||||
|
||||
In this example, the caller has not provided the required blockhash: |
||||
``` |
||||
./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace |
||||
``` |
||||
``` |
||||
ERROR(4): getHash(3) invoked, blockhash for that block not provided |
||||
``` |
||||
Error code: 4 |
||||
### Chaining |
||||
|
||||
Another thing that can be done, is to chain invocations: |
||||
``` |
||||
./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.alloc=stdout | ./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json |
||||
INFO [06-29|11:52:04.934] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low" |
||||
INFO [06-29|11:52:04.936] rejected tx index=0 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low" |
||||
INFO [06-29|11:52:04.936] rejected tx index=1 hash="0557ba…18d673" from=0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192 error="nonce too low" |
||||
|
||||
``` |
||||
What happened here, is that we first applied two identical transactions, so the second one was rejected. |
||||
Then, taking the poststate alloc as the input for the next state, we tried again to include |
||||
the same two transactions: this time, both failed due to too low nonce. |
||||
|
||||
In order to meaningfully chain invocations, one would need to provide meaningful new `env`, otherwise the |
||||
actual blocknumber (exposed to the EVM) would not increase. |
||||
|
@ -0,0 +1,255 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package t8ntool |
||||
|
||||
import ( |
||||
"fmt" |
||||
"math/big" |
||||
"os" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/common/math" |
||||
"github.com/ethereum/go-ethereum/consensus/misc" |
||||
"github.com/ethereum/go-ethereum/core" |
||||
"github.com/ethereum/go-ethereum/core/rawdb" |
||||
"github.com/ethereum/go-ethereum/core/state" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/core/vm" |
||||
"github.com/ethereum/go-ethereum/crypto" |
||||
"github.com/ethereum/go-ethereum/ethdb" |
||||
"github.com/ethereum/go-ethereum/log" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
"github.com/ethereum/go-ethereum/rlp" |
||||
"golang.org/x/crypto/sha3" |
||||
) |
||||
|
||||
type Prestate struct { |
||||
Env stEnv `json:"env"` |
||||
Pre core.GenesisAlloc `json:"pre"` |
||||
} |
||||
|
||||
// ExecutionResult contains the execution status after running a state test, any
|
||||
// error that might have occurred and a dump of the final state if requested.
|
||||
type ExecutionResult struct { |
||||
StateRoot common.Hash `json:"stateRoot"` |
||||
TxRoot common.Hash `json:"txRoot"` |
||||
ReceiptRoot common.Hash `json:"receiptRoot"` |
||||
LogsHash common.Hash `json:"logsHash"` |
||||
Bloom types.Bloom `json:"logsBloom" gencodec:"required"` |
||||
Receipts types.Receipts `json:"receipts"` |
||||
Rejected []int `json:"rejected,omitempty"` |
||||
} |
||||
|
||||
type ommer struct { |
||||
Delta uint64 `json:"delta"` |
||||
Address common.Address `json:"address"` |
||||
} |
||||
|
||||
//go:generate gencodec -type stEnv -field-override stEnvMarshaling -out gen_stenv.go
|
||||
type stEnv struct { |
||||
Coinbase common.Address `json:"currentCoinbase" gencodec:"required"` |
||||
Difficulty *big.Int `json:"currentDifficulty" gencodec:"required"` |
||||
GasLimit uint64 `json:"currentGasLimit" gencodec:"required"` |
||||
Number uint64 `json:"currentNumber" gencodec:"required"` |
||||
Timestamp uint64 `json:"currentTimestamp" gencodec:"required"` |
||||
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` |
||||
Ommers []ommer `json:"ommers,omitempty"` |
||||
} |
||||
|
||||
type stEnvMarshaling struct { |
||||
Coinbase common.UnprefixedAddress |
||||
Difficulty *math.HexOrDecimal256 |
||||
GasLimit math.HexOrDecimal64 |
||||
Number math.HexOrDecimal64 |
||||
Timestamp math.HexOrDecimal64 |
||||
} |
||||
|
||||
// Apply applies a set of transactions to a pre-state
|
||||
func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, |
||||
txs types.Transactions, miningReward int64, |
||||
getTracerFn func(txIndex int) (tracer vm.Tracer, err error)) (*state.StateDB, *ExecutionResult, error) { |
||||
|
||||
// Capture errors for BLOCKHASH operation, if we haven't been supplied the
|
||||
// required blockhashes
|
||||
var hashError error |
||||
getHash := func(num uint64) common.Hash { |
||||
if pre.Env.BlockHashes == nil { |
||||
hashError = fmt.Errorf("getHash(%d) invoked, no blockhashes provided", num) |
||||
return common.Hash{} |
||||
} |
||||
h, ok := pre.Env.BlockHashes[math.HexOrDecimal64(num)] |
||||
if !ok { |
||||
hashError = fmt.Errorf("getHash(%d) invoked, blockhash for that block not provided", num) |
||||
} |
||||
return h |
||||
} |
||||
var ( |
||||
statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre) |
||||
signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number)) |
||||
gaspool = new(core.GasPool) |
||||
blockHash = common.Hash{0x13, 0x37} |
||||
rejectedTxs []int |
||||
includedTxs types.Transactions |
||||
gasUsed = uint64(0) |
||||
receipts = make(types.Receipts, 0) |
||||
txIndex = 0 |
||||
) |
||||
gaspool.AddGas(pre.Env.GasLimit) |
||||
vmContext := vm.Context{ |
||||
CanTransfer: core.CanTransfer, |
||||
Transfer: core.Transfer, |
||||
Coinbase: pre.Env.Coinbase, |
||||
BlockNumber: new(big.Int).SetUint64(pre.Env.Number), |
||||
Time: new(big.Int).SetUint64(pre.Env.Timestamp), |
||||
Difficulty: pre.Env.Difficulty, |
||||
GasLimit: pre.Env.GasLimit, |
||||
GetHash: getHash, |
||||
// GasPrice and Origin needs to be set per transaction
|
||||
} |
||||
// If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's
|
||||
// done in StateProcessor.Process(block, ...), right before transactions are applied.
|
||||
if chainConfig.DAOForkSupport && |
||||
chainConfig.DAOForkBlock != nil && |
||||
chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 { |
||||
misc.ApplyDAOHardFork(statedb) |
||||
} |
||||
|
||||
for i, tx := range txs { |
||||
msg, err := tx.AsMessage(signer) |
||||
if err != nil { |
||||
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "error", err) |
||||
rejectedTxs = append(rejectedTxs, i) |
||||
continue |
||||
} |
||||
tracer, err := getTracerFn(txIndex) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
vmConfig.Tracer = tracer |
||||
vmConfig.Debug = (tracer != nil) |
||||
statedb.Prepare(tx.Hash(), blockHash, txIndex) |
||||
vmContext.GasPrice = msg.GasPrice() |
||||
vmContext.Origin = msg.From() |
||||
|
||||
evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig) |
||||
snapshot := statedb.Snapshot() |
||||
// (ret []byte, usedGas uint64, failed bool, err error)
|
||||
msgResult, err := core.ApplyMessage(evm, msg, gaspool) |
||||
if err != nil { |
||||
statedb.RevertToSnapshot(snapshot) |
||||
log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From(), "error", err) |
||||
rejectedTxs = append(rejectedTxs, i) |
||||
continue |
||||
} |
||||
includedTxs = append(includedTxs, tx) |
||||
if hashError != nil { |
||||
return nil, nil, NewError(ErrorMissingBlockhash, hashError) |
||||
} |
||||
gasUsed += msgResult.UsedGas |
||||
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
|
||||
{ |
||||
var root []byte |
||||
if chainConfig.IsByzantium(vmContext.BlockNumber) { |
||||
statedb.Finalise(true) |
||||
} else { |
||||
root = statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)).Bytes() |
||||
} |
||||
|
||||
receipt := types.NewReceipt(root, msgResult.Failed(), gasUsed) |
||||
receipt.TxHash = tx.Hash() |
||||
receipt.GasUsed = msgResult.UsedGas |
||||
// if the transaction created a contract, store the creation address in the receipt.
|
||||
if msg.To() == nil { |
||||
receipt.ContractAddress = crypto.CreateAddress(evm.Context.Origin, tx.Nonce()) |
||||
} |
||||
// Set the receipt logs and create a bloom for filtering
|
||||
receipt.Logs = statedb.GetLogs(tx.Hash()) |
||||
receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) |
||||
// These three are non-consensus fields
|
||||
//receipt.BlockHash
|
||||
//receipt.BlockNumber =
|
||||
receipt.TransactionIndex = uint(txIndex) |
||||
receipts = append(receipts, receipt) |
||||
} |
||||
txIndex++ |
||||
} |
||||
statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)) |
||||
// Add mining reward?
|
||||
if miningReward > 0 { |
||||
// Add mining reward. The mining reward may be `0`, which only makes a difference in the cases
|
||||
// where
|
||||
// - the coinbase suicided, or
|
||||
// - there are only 'bad' transactions, which aren't executed. In those cases,
|
||||
// the coinbase gets no txfee, so isn't created, and thus needs to be touched
|
||||
var ( |
||||
blockReward = big.NewInt(miningReward) |
||||
minerReward = new(big.Int).Set(blockReward) |
||||
perOmmer = new(big.Int).Div(blockReward, big.NewInt(32)) |
||||
) |
||||
for _, ommer := range pre.Env.Ommers { |
||||
// Add 1/32th for each ommer included
|
||||
minerReward.Add(minerReward, perOmmer) |
||||
// Add (8-delta)/8
|
||||
reward := big.NewInt(8) |
||||
reward.Sub(reward, big.NewInt(0).SetUint64(ommer.Delta)) |
||||
reward.Mul(reward, blockReward) |
||||
reward.Div(reward, big.NewInt(8)) |
||||
statedb.AddBalance(ommer.Address, reward) |
||||
} |
||||
statedb.AddBalance(pre.Env.Coinbase, minerReward) |
||||
} |
||||
// Commit block
|
||||
root, err := statedb.Commit(chainConfig.IsEIP158(vmContext.BlockNumber)) |
||||
if err != nil { |
||||
fmt.Fprintf(os.Stderr, "Could not commit state: %v", err) |
||||
return nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) |
||||
} |
||||
execRs := &ExecutionResult{ |
||||
StateRoot: root, |
||||
TxRoot: types.DeriveSha(includedTxs), |
||||
ReceiptRoot: types.DeriveSha(receipts), |
||||
Bloom: types.CreateBloom(receipts), |
||||
LogsHash: rlpHash(statedb.Logs()), |
||||
Receipts: receipts, |
||||
Rejected: rejectedTxs, |
||||
} |
||||
return statedb, execRs, nil |
||||
} |
||||
|
||||
func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB { |
||||
sdb := state.NewDatabase(db) |
||||
statedb, _ := state.New(common.Hash{}, sdb, nil) |
||||
for addr, a := range accounts { |
||||
statedb.SetCode(addr, a.Code) |
||||
statedb.SetNonce(addr, a.Nonce) |
||||
statedb.SetBalance(addr, a.Balance) |
||||
for k, v := range a.Storage { |
||||
statedb.SetState(addr, k, v) |
||||
} |
||||
} |
||||
// Commit and re-open to start with a clean state.
|
||||
root, _ := statedb.Commit(false) |
||||
statedb, _ = state.New(root, sdb, nil) |
||||
return statedb |
||||
} |
||||
|
||||
func rlpHash(x interface{}) (h common.Hash) { |
||||
hw := sha3.NewLegacyKeccak256() |
||||
rlp.Encode(hw, x) |
||||
hw.Sum(h[:0]) |
||||
return h |
||||
} |
@ -0,0 +1,99 @@ |
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library 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 Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package t8ntool |
||||
|
||||
import ( |
||||
"fmt" |
||||
"strings" |
||||
|
||||
"github.com/ethereum/go-ethereum/core/vm" |
||||
"github.com/ethereum/go-ethereum/tests" |
||||
"gopkg.in/urfave/cli.v1" |
||||
) |
||||
|
||||
var ( |
||||
TraceFlag = cli.BoolFlag{ |
||||
Name: "trace", |
||||
Usage: "Output full trace logs to files <txhash>.jsonl", |
||||
} |
||||
TraceDisableMemoryFlag = cli.BoolFlag{ |
||||
Name: "trace.nomemory", |
||||
Usage: "Disable full memory dump in traces", |
||||
} |
||||
TraceDisableStackFlag = cli.BoolFlag{ |
||||
Name: "trace.nostack", |
||||
Usage: "Disable stack output in traces", |
||||
} |
||||
OutputAllocFlag = cli.StringFlag{ |
||||
Name: "output.alloc", |
||||
Usage: "Determines where to put the `alloc` of the post-state.\n" + |
||||
"\t`stdout` - into the stdout output\n" + |
||||
"\t`stderr` - into the stderr output\n" + |
||||
"\t<file> - into the file <file> ", |
||||
Value: "alloc.json", |
||||
} |
||||
OutputResultFlag = cli.StringFlag{ |
||||
Name: "output.result", |
||||
Usage: "Determines where to put the `result` (stateroot, txroot etc) of the post-state.\n" + |
||||
"\t`stdout` - into the stdout output\n" + |
||||
"\t`stderr` - into the stderr output\n" + |
||||
"\t<file> - into the file <file> ", |
||||
Value: "result.json", |
||||
} |
||||
InputAllocFlag = cli.StringFlag{ |
||||
Name: "input.alloc", |
||||
Usage: "`stdin` or file name of where to find the prestate alloc to use.", |
||||
Value: "alloc.json", |
||||
} |
||||
InputEnvFlag = cli.StringFlag{ |
||||
Name: "input.env", |
||||
Usage: "`stdin` or file name of where to find the prestate env to use.", |
||||
Value: "env.json", |
||||
} |
||||
InputTxsFlag = cli.StringFlag{ |
||||
Name: "input.txs", |
||||
Usage: "`stdin` or file name of where to find the transactions to apply.", |
||||
Value: "txs.json", |
||||
} |
||||
RewardFlag = cli.Int64Flag{ |
||||
Name: "state.reward", |
||||
Usage: "Mining reward. Set to -1 to disable", |
||||
Value: 0, |
||||
} |
||||
ChainIDFlag = cli.Int64Flag{ |
||||
Name: "state.chainid", |
||||
Usage: "ChainID to use", |
||||
Value: 1, |
||||
} |
||||
ForknameFlag = cli.StringFlag{ |
||||
Name: "state.fork", |
||||
Usage: fmt.Sprintf("Name of ruleset to use."+ |
||||
"\n\tAvailable forknames:"+ |
||||
"\n\t %v"+ |
||||
"\n\tAvailable extra eips:"+ |
||||
"\n\t %v"+ |
||||
"\n\tSyntax <forkname>(+ExtraEip)", |
||||
strings.Join(tests.AvailableForks(), "\n\t "), |
||||
strings.Join(vm.ActivateableEips(), ", ")), |
||||
Value: "Istanbul", |
||||
} |
||||
VerbosityFlag = cli.IntFlag{ |
||||
Name: "verbosity", |
||||
Usage: "sets the verbosity level", |
||||
Value: 3, |
||||
} |
||||
) |
@ -0,0 +1,80 @@ |
||||
// 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/math" |
||||
) |
||||
|
||||
var _ = (*stEnvMarshaling)(nil) |
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (s stEnv) MarshalJSON() ([]byte, error) { |
||||
type stEnv struct { |
||||
Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` |
||||
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` |
||||
GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` |
||||
Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` |
||||
Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` |
||||
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` |
||||
Ommers []ommer `json:"ommers,omitempty"` |
||||
} |
||||
var enc stEnv |
||||
enc.Coinbase = common.UnprefixedAddress(s.Coinbase) |
||||
enc.Difficulty = (*math.HexOrDecimal256)(s.Difficulty) |
||||
enc.GasLimit = math.HexOrDecimal64(s.GasLimit) |
||||
enc.Number = math.HexOrDecimal64(s.Number) |
||||
enc.Timestamp = math.HexOrDecimal64(s.Timestamp) |
||||
enc.BlockHashes = s.BlockHashes |
||||
enc.Ommers = s.Ommers |
||||
return json.Marshal(&enc) |
||||
} |
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (s *stEnv) UnmarshalJSON(input []byte) error { |
||||
type stEnv struct { |
||||
Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` |
||||
Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` |
||||
GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` |
||||
Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` |
||||
Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` |
||||
BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` |
||||
Ommers []ommer `json:"ommers,omitempty"` |
||||
} |
||||
var dec stEnv |
||||
if err := json.Unmarshal(input, &dec); err != nil { |
||||
return err |
||||
} |
||||
if dec.Coinbase == nil { |
||||
return errors.New("missing required field 'currentCoinbase' for stEnv") |
||||
} |
||||
s.Coinbase = common.Address(*dec.Coinbase) |
||||
if dec.Difficulty == nil { |
||||
return errors.New("missing required field 'currentDifficulty' for stEnv") |
||||
} |
||||
s.Difficulty = (*big.Int)(dec.Difficulty) |
||||
if dec.GasLimit == nil { |
||||
return errors.New("missing required field 'currentGasLimit' for stEnv") |
||||
} |
||||
s.GasLimit = uint64(*dec.GasLimit) |
||||
if dec.Number == nil { |
||||
return errors.New("missing required field 'currentNumber' for stEnv") |
||||
} |
||||
s.Number = uint64(*dec.Number) |
||||
if dec.Timestamp == nil { |
||||
return errors.New("missing required field 'currentTimestamp' for stEnv") |
||||
} |
||||
s.Timestamp = uint64(*dec.Timestamp) |
||||
if dec.BlockHashes != nil { |
||||
s.BlockHashes = dec.BlockHashes |
||||
} |
||||
if dec.Ommers != nil { |
||||
s.Ommers = dec.Ommers |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,276 @@ |
||||
// Copyright 2020 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" |
||||
"io/ioutil" |
||||
"math/big" |
||||
"os" |
||||
|
||||
"github.com/ethereum/go-ethereum/common" |
||||
"github.com/ethereum/go-ethereum/core" |
||||
"github.com/ethereum/go-ethereum/core/state" |
||||
"github.com/ethereum/go-ethereum/core/types" |
||||
"github.com/ethereum/go-ethereum/core/vm" |
||||
"github.com/ethereum/go-ethereum/log" |
||||
"github.com/ethereum/go-ethereum/params" |
||||
"github.com/ethereum/go-ethereum/tests" |
||||
"gopkg.in/urfave/cli.v1" |
||||
) |
||||
|
||||
const ( |
||||
ErrorEVM = 2 |
||||
ErrorVMConfig = 3 |
||||
ErrorMissingBlockhash = 4 |
||||
|
||||
ErrorJson = 10 |
||||
ErrorIO = 11 |
||||
|
||||
stdinSelector = "stdin" |
||||
) |
||||
|
||||
type NumberedError struct { |
||||
errorCode int |
||||
err error |
||||
} |
||||
|
||||
func NewError(errorCode int, err error) *NumberedError { |
||||
return &NumberedError{errorCode, err} |
||||
} |
||||
|
||||
func (n *NumberedError) Error() string { |
||||
return fmt.Sprintf("ERROR(%d): %v", n.errorCode, n.err.Error()) |
||||
} |
||||
|
||||
func (n *NumberedError) Code() int { |
||||
return n.errorCode |
||||
} |
||||
|
||||
type input struct { |
||||
Alloc core.GenesisAlloc `json:"alloc,omitempty"` |
||||
Env *stEnv `json:"env,omitempty"` |
||||
Txs types.Transactions `json:"txs,omitempty"` |
||||
} |
||||
|
||||
func Main(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) |
||||
|
||||
var ( |
||||
err error |
||||
tracer vm.Tracer |
||||
) |
||||
var getTracer func(txIndex int) (vm.Tracer, error) |
||||
|
||||
if ctx.Bool(TraceFlag.Name) { |
||||
// Configure the EVM logger
|
||||
logConfig := &vm.LogConfig{ |
||||
DisableStack: ctx.Bool(TraceDisableStackFlag.Name), |
||||
DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name), |
||||
Debug: true, |
||||
} |
||||
var prevFile *os.File |
||||
// This one closes the last file
|
||||
defer func() { |
||||
if prevFile != nil { |
||||
prevFile.Close() |
||||
} |
||||
}() |
||||
getTracer = func(txIndex int) (vm.Tracer, error) { |
||||
if prevFile != nil { |
||||
prevFile.Close() |
||||
} |
||||
traceFile, err := os.Create(fmt.Sprintf("trace-%d.jsonl", txIndex)) |
||||
if err != nil { |
||||
return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) |
||||
} |
||||
prevFile = traceFile |
||||
return vm.NewJSONLogger(logConfig, traceFile), nil |
||||
} |
||||
} else { |
||||
getTracer = func(txIndex int) (tracer vm.Tracer, err error) { |
||||
return nil, nil |
||||
} |
||||
} |
||||
// We need to load three things: alloc, env and transactions. May be either in
|
||||
// stdin input or in files.
|
||||
// Check if anything needs to be read from stdin
|
||||
var ( |
||||
prestate Prestate |
||||
txs types.Transactions // txs to apply
|
||||
allocStr = ctx.String(InputAllocFlag.Name) |
||||
|
||||
envStr = ctx.String(InputEnvFlag.Name) |
||||
txStr = ctx.String(InputTxsFlag.Name) |
||||
inputData = &input{} |
||||
) |
||||
|
||||
if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector { |
||||
decoder := json.NewDecoder(os.Stdin) |
||||
decoder.Decode(inputData) |
||||
} |
||||
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 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)) |
||||
} |
||||
inputData.Env = &env |
||||
} |
||||
|
||||
if txStr != stdinSelector { |
||||
inFile, err := os.Open(txStr) |
||||
if err != nil { |
||||
return NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) |
||||
} |
||||
defer inFile.Close() |
||||
decoder := json.NewDecoder(inFile) |
||||
var txs types.Transactions |
||||
if err := decoder.Decode(&txs); err != nil { |
||||
return NewError(ErrorJson, fmt.Errorf("Failed unmarshaling txs-file: %v", err)) |
||||
} |
||||
inputData.Txs = txs |
||||
} |
||||
|
||||
prestate.Pre = inputData.Alloc |
||||
prestate.Env = *inputData.Env |
||||
txs = inputData.Txs |
||||
|
||||
// Iterate over all the tests, run them and aggregate the results
|
||||
vmConfig := vm.Config{ |
||||
Tracer: tracer, |
||||
Debug: (tracer != nil), |
||||
} |
||||
// 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)) |
||||
} else { |
||||
chainConfig = cConf |
||||
vmConfig.ExtraEips = extraEips |
||||
} |
||||
// Set the chain id
|
||||
chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name)) |
||||
|
||||
// Run the test and aggregate the result
|
||||
state, result, err := prestate.Apply(vmConfig, chainConfig, txs, ctx.Int64(RewardFlag.Name), getTracer) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
// Dump the excution result
|
||||
//postAlloc := state.DumpGenesisFormat(false, false, false)
|
||||
collector := make(Alloc) |
||||
state.DumpToCollector(collector, false, false, false, nil, -1) |
||||
return dispatchOutput(ctx, result, collector) |
||||
|
||||
} |
||||
|
||||
type Alloc map[common.Address]core.GenesisAccount |
||||
|
||||
func (g Alloc) OnRoot(common.Hash) {} |
||||
|
||||
func (g Alloc) OnAccount(addr common.Address, dumpAccount state.DumpAccount) { |
||||
balance, _ := new(big.Int).SetString(dumpAccount.Balance, 10) |
||||
var storage map[common.Hash]common.Hash |
||||
if dumpAccount.Storage != nil { |
||||
storage = make(map[common.Hash]common.Hash) |
||||
for k, v := range dumpAccount.Storage { |
||||
storage[k] = common.HexToHash(v) |
||||
} |
||||
} |
||||
genesisAccount := core.GenesisAccount{ |
||||
Code: common.FromHex(dumpAccount.Code), |
||||
Storage: storage, |
||||
Balance: balance, |
||||
Nonce: dumpAccount.Nonce, |
||||
} |
||||
g[addr] = genesisAccount |
||||
} |
||||
|
||||
// saveFile marshalls the object to the given file
|
||||
func saveFile(filename string, data interface{}) error { |
||||
b, err := json.MarshalIndent(data, "", " ") |
||||
if err != nil { |
||||
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) |
||||
} |
||||
if err = ioutil.WriteFile(filename, b, 0644); err != nil { |
||||
return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err)) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// dispatchOutput writes the output data to either stderr or stdout, or to the specified
|
||||
// files
|
||||
func dispatchOutput(ctx *cli.Context, result *ExecutionResult, alloc Alloc) error { |
||||
stdOutObject := make(map[string]interface{}) |
||||
stdErrObject := make(map[string]interface{}) |
||||
dispatch := func(fName, name string, obj interface{}) error { |
||||
switch fName { |
||||
case "stdout": |
||||
stdOutObject[name] = obj |
||||
case "stderr": |
||||
stdErrObject[name] = obj |
||||
default: // save to file
|
||||
if err := saveFile(fName, obj); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
if err := dispatch(ctx.String(OutputAllocFlag.Name), "alloc", alloc); err != nil { |
||||
return err |
||||
} |
||||
if err := dispatch(ctx.String(OutputResultFlag.Name), "result", result); err != nil { |
||||
return err |
||||
} |
||||
if len(stdOutObject) > 0 { |
||||
b, err := json.MarshalIndent(stdOutObject, "", " ") |
||||
if err != nil { |
||||
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) |
||||
} |
||||
os.Stdout.Write(b) |
||||
} |
||||
if len(stdErrObject) > 0 { |
||||
b, err := json.MarshalIndent(stdErrObject, "", " ") |
||||
if err != nil { |
||||
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) |
||||
} |
||||
os.Stderr.Write(b) |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,23 @@ |
||||
{ |
||||
"root": "f4157bb27bcb1d1a63001434a249a80948f2e9fe1f53d551244c1dae826b5b23", |
||||
"accounts": { |
||||
"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { |
||||
"balance": "4276951709", |
||||
"nonce": 1, |
||||
"root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||
"codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" |
||||
}, |
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { |
||||
"balance": "6916764286133345652", |
||||
"nonce": 172, |
||||
"root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||
"codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" |
||||
}, |
||||
"0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { |
||||
"balance": "42500", |
||||
"nonce": 0, |
||||
"root": "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", |
||||
"codeHash": "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
{ |
||||
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { |
||||
"balance": "0x5ffd4878be161d74", |
||||
"code": "0x", |
||||
"nonce": "0xac", |
||||
"storage": {} |
||||
}, |
||||
"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ |
||||
"balance": "0xfeedbead", |
||||
"nonce" : "0x00" |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
{ |
||||
"currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", |
||||
"currentDifficulty": "0x20000", |
||||
"currentGasLimit": "0x750a163df65e8a", |
||||
"currentNumber": "1", |
||||
"currentTimestamp": "1000" |
||||
} |
@ -0,0 +1,26 @@ |
||||
[ |
||||
{ |
||||
"gas": "0x5208", |
||||
"gasPrice": "0x2", |
||||
"hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", |
||||
"input": "0x", |
||||
"nonce": "0x0", |
||||
"r": "0x9500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdb", |
||||
"s": "0x7235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600", |
||||
"to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", |
||||
"v": "0x1b", |
||||
"value": "0x1" |
||||
}, |
||||
{ |
||||
"gas": "0x5208", |
||||
"gasPrice": "0x2", |
||||
"hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", |
||||
"input": "0x", |
||||
"nonce": "0x0", |
||||
"r": "0x9500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdb", |
||||
"s": "0x7235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600", |
||||
"to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", |
||||
"v": "0x1b", |
||||
"value": "0x1" |
||||
} |
||||
] |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { |
||||
"balance" : "0x0de0b6b3a7640000", |
||||
"code" : "0x6001600053600160006001f0ff00", |
||||
"nonce" : "0x00", |
||||
"storage" : { |
||||
} |
||||
}, |
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { |
||||
"balance" : "0x0de0b6b3a7640000", |
||||
"code" : "0x", |
||||
"nonce" : "0x00", |
||||
"storage" : { |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
{ |
||||
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", |
||||
"currentDifficulty" : "0x020000", |
||||
"currentGasLimit" : "0x3b9aca00", |
||||
"currentNumber" : "0x01", |
||||
"currentTimestamp" : "0x03e8" |
||||
} |
@ -0,0 +1 @@ |
||||
These files examplify a selfdestruct to the `0`-address. |
@ -0,0 +1,14 @@ |
||||
[ |
||||
{ |
||||
"input" : "0x", |
||||
"gas" : "0x5f5e100", |
||||
"gasPrice" : "0x1", |
||||
"nonce" : "0x0", |
||||
"to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", |
||||
"value" : "0x186a0", |
||||
"v" : "0x1b", |
||||
"r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", |
||||
"s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", |
||||
"hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" |
||||
} |
||||
] |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { |
||||
"balance" : "0x0de0b6b3a7640000", |
||||
"code" : "0x600140", |
||||
"nonce" : "0x00", |
||||
"storage" : { |
||||
} |
||||
}, |
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { |
||||
"balance" : "0x0de0b6b3a7640000", |
||||
"code" : "0x", |
||||
"nonce" : "0x00", |
||||
"storage" : { |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
{ |
||||
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", |
||||
"currentDifficulty" : "0x020000", |
||||
"currentGasLimit" : "0x3b9aca00", |
||||
"currentNumber" : "0x05", |
||||
"currentTimestamp" : "0x03e8", |
||||
"blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"} |
||||
} |
@ -0,0 +1,2 @@ |
||||
These files examplify a transition where a transaction (excuted on block 5) requests |
||||
the blockhash for block `1`. |
@ -0,0 +1,14 @@ |
||||
[ |
||||
{ |
||||
"input" : "0x", |
||||
"gas" : "0x5f5e100", |
||||
"gasPrice" : "0x1", |
||||
"nonce" : "0x0", |
||||
"to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", |
||||
"value" : "0x186a0", |
||||
"v" : "0x1b", |
||||
"r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", |
||||
"s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", |
||||
"hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" |
||||
} |
||||
] |
@ -0,0 +1,16 @@ |
||||
{ |
||||
"0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { |
||||
"balance" : "0x0de0b6b3a7640000", |
||||
"code" : "0x600340", |
||||
"nonce" : "0x00", |
||||
"storage" : { |
||||
} |
||||
}, |
||||
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { |
||||
"balance" : "0x0de0b6b3a7640000", |
||||
"code" : "0x", |
||||
"nonce" : "0x00", |
||||
"storage" : { |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
{ |
||||
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", |
||||
"currentDifficulty" : "0x020000", |
||||
"currentGasLimit" : "0x3b9aca00", |
||||
"currentNumber" : "0x05", |
||||
"currentTimestamp" : "0x03e8", |
||||
"blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"} |
||||
} |
@ -0,0 +1,3 @@ |
||||
These files examplify a transition where a transaction (excuted on block 5) requests |
||||
the blockhash for block `4`, but where the hash for that block is missing. |
||||
It's expected that executing these should cause `exit` with errorcode `4`. |
@ -0,0 +1,14 @@ |
||||
[ |
||||
{ |
||||
"input" : "0x", |
||||
"gas" : "0x5f5e100", |
||||
"gasPrice" : "0x1", |
||||
"nonce" : "0x0", |
||||
"to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", |
||||
"value" : "0x186a0", |
||||
"v" : "0x1b", |
||||
"r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", |
||||
"s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", |
||||
"hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" |
||||
} |
||||
] |
@ -0,0 +1 @@ |
||||
{} |
@ -0,0 +1,11 @@ |
||||
{ |
||||
"currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", |
||||
"currentDifficulty": "0x20000", |
||||
"currentGasLimit": "0x750a163df65e8a", |
||||
"currentNumber": "1", |
||||
"currentTimestamp": "1000", |
||||
"ommers": [ |
||||
{"delta": 1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, |
||||
{"delta": 2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" } |
||||
] |
||||
} |
@ -0,0 +1 @@ |
||||
These files examplify a transition where there are no transcations, two ommers, at block `N-1` (delta 1) and `N-2` (delta 2). |
@ -0,0 +1 @@ |
||||
[] |
@ -0,0 +1,12 @@ |
||||
{ |
||||
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { |
||||
"balance": "0x5ffd4878be161d74", |
||||
"code": "0x", |
||||
"nonce": "0xac", |
||||
"storage": {} |
||||
}, |
||||
"0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ |
||||
"balance": "0xfeedbead", |
||||
"nonce" : "0x00" |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
{ |
||||
"currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", |
||||
"currentDifficulty": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffff020000", |
||||
"currentGasLimit": "0x750a163df65e8a", |
||||
"currentNumber": "5", |
||||
"currentTimestamp": "1000" |
||||
} |
@ -0,0 +1,7 @@ |
||||
This is a test for HomesteadToDao, checking if the |
||||
DAO-transition works |
||||
|
||||
Example: |
||||
``` |
||||
./statet8n --input.alloc=./testdata/7/alloc.json --input.txs=./testdata/7/txs.json --input.env=./testdata/7/env.json --output.alloc=stdout --state.fork=HomesteadToDaoAt5 |
||||
``` |
@ -0,0 +1 @@ |
||||
[] |
@ -0,0 +1,191 @@ |
||||
#!/bin/bash |
||||
ticks="\`\`\`" |
||||
|
||||
function showjson(){ |
||||
echo "\`$1\`:" |
||||
echo "${ticks}json" |
||||
cat $1 |
||||
echo "" |
||||
echo "$ticks" |
||||
} |
||||
function demo(){ |
||||
echo "$ticks" |
||||
echo "$1" |
||||
echo "$ticks" |
||||
echo "" |
||||
} |
||||
function tick(){ |
||||
echo "$ticks" |
||||
} |
||||
|
||||
cat << EOF |
||||
## EVM state transition tool |
||||
|
||||
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 has to be supported are |
||||
$(tick) |
||||
|
||||
` ./evm t8n -h | grep "trace\|output\|state\."` |
||||
|
||||
$(tick) |
||||
|
||||
### 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\` |
||||
|
||||
EOF |
||||
|
||||
# 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 |
||||
if [ $? != 3 ]; then |
||||
echo "Failed, exitcode should be 3" |
||||
fi |
||||
cat << EOF |
||||
## 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" |
||||
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" |
||||
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 \`state_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 \`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" |
||||
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: " |
||||
cmd="./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace" |
||||
tick && echo $cmd && tick |
||||
$cmd 2>&1 >/dev/null |
||||
cmd="cat trace-0.jsonl | grep BLOCKHASH -C2" |
||||
tick && echo $cmd && tick |
||||
echo "$ticks" |
||||
cat trace-0.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" |
||||
tick && echo $cmd && tick |
||||
tick |
||||
$cmd |
||||
errc=$? |
||||
tick |
||||
echo "Error code: $errc" |
||||
|
||||
|
||||
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 --output.alloc=stdout" |
||||
cmd2="./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json" |
||||
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 "" |
Loading…
Reference in new issue