Unverified Commit 8a24b563 authored by Martin Holst Swende's avatar Martin Holst Swende Committed by GitHub

cmd/evm: implement input txs via rlp in t8n tool (#23138)

In many cases, it's desireable to use already-signed transactions as input to the state transition, instead of having the evm sign them internally (for example to use malformed or not-yet-valid transactions). This PR adds support + docs for that feature.
parent 0658712f
This diff is collapsed.
...@@ -79,8 +79,10 @@ var ( ...@@ -79,8 +79,10 @@ var (
Value: "env.json", Value: "env.json",
} }
InputTxsFlag = cli.StringFlag{ InputTxsFlag = cli.StringFlag{
Name: "input.txs", Name: "input.txs",
Usage: "`stdin` or file name of where to find the transactions to apply.", 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." +
"The '.rlp' format is identical to the output.body format.",
Value: "txs.json", Value: "txs.json",
} }
RewardFlag = cli.Int64Flag{ RewardFlag = cli.Int64Flag{
......
...@@ -25,6 +25,7 @@ import ( ...@@ -25,6 +25,7 @@ import (
"math/big" "math/big"
"os" "os"
"path" "path"
"strings"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
...@@ -72,6 +73,7 @@ type input struct { ...@@ -72,6 +73,7 @@ type input struct {
Alloc core.GenesisAlloc `json:"alloc,omitempty"` Alloc core.GenesisAlloc `json:"alloc,omitempty"`
Env *stEnv `json:"env,omitempty"` Env *stEnv `json:"env,omitempty"`
Txs []*txWithKey `json:"txs,omitempty"` Txs []*txWithKey `json:"txs,omitempty"`
TxRlp string `json:"txsRlp,omitempty"`
} }
func Main(ctx *cli.Context) error { func Main(ctx *cli.Context) error {
...@@ -199,11 +201,44 @@ func Main(ctx *cli.Context) error { ...@@ -199,11 +201,44 @@ func Main(ctx *cli.Context) error {
} }
defer inFile.Close() defer inFile.Close()
decoder := json.NewDecoder(inFile) decoder := json.NewDecoder(inFile)
if err := decoder.Decode(&txsWithKeys); err != nil { if strings.HasSuffix(txStr, ".rlp") {
return NewError(ErrorJson, fmt.Errorf("failed unmarshaling txs-file: %v", err)) var body hexutil.Bytes
if err := decoder.Decode(&body); err != nil {
return err
}
var txs types.Transactions
if err := rlp.DecodeBytes(body, &txs); err != nil {
return err
}
for _, tx := range txs {
txsWithKeys = append(txsWithKeys, &txWithKey{
key: nil,
tx: tx,
})
}
} else {
if err := decoder.Decode(&txsWithKeys); err != nil {
return NewError(ErrorJson, fmt.Errorf("failed unmarshaling txs-file: %v", err))
}
} }
} else { } else {
txsWithKeys = inputData.Txs if len(inputData.TxRlp) > 0 {
// Decode the body of already signed transactions
body := common.FromHex(inputData.TxRlp)
var txs types.Transactions
if err := rlp.DecodeBytes(body, &txs); err != nil {
return err
}
for _, tx := range txs {
txsWithKeys = append(txsWithKeys, &txWithKey{
key: nil,
tx: tx,
})
}
} else {
// JSON encoded transactions
txsWithKeys = inputData.Txs
}
} }
// We may have to sign the transactions. // We may have to sign the transactions.
signer := types.MakeSigner(chainConfig, big.NewInt(int64(prestate.Env.Number))) signer := types.MakeSigner(chainConfig, big.NewInt(int64(prestate.Env.Number)))
...@@ -365,6 +400,7 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a ...@@ -365,6 +400,7 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err))
} }
os.Stdout.Write(b) os.Stdout.Write(b)
os.Stdout.Write([]byte("\n"))
} }
if len(stdErrObject) > 0 { if len(stdErrObject) > 0 {
b, err := json.MarshalIndent(stdErrObject, "", " ") b, err := json.MarshalIndent(stdErrObject, "", " ")
...@@ -372,6 +408,7 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a ...@@ -372,6 +408,7 @@ func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, a
return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err))
} }
os.Stderr.Write(b) os.Stderr.Write(b)
os.Stderr.Write([]byte("\n"))
} }
return nil return nil
} }
{
"0x1111111111111111111111111111111111111111" : {
"balance" : "0x010000000000",
"code" : "0xfe",
"nonce" : "0x01",
"storage" : {
}
},
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
"balance" : "0x010000000000",
"code" : "0x",
"nonce" : "0x01",
"storage" : {
}
},
"0xd02d72e067e77158444ef2020ff2d325f929b363" : {
"balance" : "0x01000000000000",
"code" : "0x",
"nonce" : "0x01",
"storage" : {
}
}
}
\ No newline at end of file
{
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
"currentDifficulty" : "0x020000",
"currentNumber" : "0x01",
"currentTimestamp" : "0x079e",
"previousHash" : "0xcb23ee65a163121f640673b41788ee94633941405f95009999b502eedfbbfd4f",
"currentGasLimit" : "0x40000000",
"currentBaseFee" : "0x036b",
"blockHashes" : {
"0" : "0xcb23ee65a163121f640673b41788ee94633941405f95009999b502eedfbbfd4f"
}
}
\ No newline at end of file
## Input transactions in RLP form
This testdata folder is used to examplify how transaction input can be provided in rlp form.
Please see the README in `evm` folder for how this is performed.
\ No newline at end of file
[
{
"input" : "0x",
"gas" : "0x84d0",
"nonce" : "0x1",
"to" : "0x1111111111111111111111111111111111111111",
"value" : "0x0",
"v" : "0x0",
"r" : "0x0",
"s" : "0x0",
"secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298",
"chainId" : "0x1",
"type" : "0x2",
"maxFeePerGas" : "0xfa0",
"maxPriorityFeePerGas" : "0x0",
"accessList" : []
},
{
"input" : "0x",
"gas" : "0x84d0",
"nonce" : "0x2",
"to" : "0x1111111111111111111111111111111111111111",
"value" : "0x0",
"v" : "0x0",
"r" : "0x0",
"s" : "0x0",
"secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298",
"chainId" : "0x1",
"type" : "0x2",
"maxFeePerGas" : "0xfa0",
"maxPriorityFeePerGas" : "0x0",
"accessList" : []
}
]
\ No newline at end of file
...@@ -11,6 +11,8 @@ function showjson(){ ...@@ -11,6 +11,8 @@ function showjson(){
function demo(){ function demo(){
echo "$ticks" echo "$ticks"
echo "$1" echo "$1"
$1
echo ""
echo "$ticks" echo "$ticks"
echo "" echo ""
} }
...@@ -152,9 +154,7 @@ echo "" ...@@ -152,9 +154,7 @@ echo ""
echo "The \`BLOCKHASH\` opcode requires blockhashes to be provided by the caller, inside the \`env\`." 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 "If a required blockhash is not provided, the exit code should be \`4\`:"
echo "Example where blockhashes are provided: " 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" demo "./evm --verbosity=1 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-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2" cmd="cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2"
tick && echo $cmd && tick tick && echo $cmd && tick
echo "$ticks" echo "$ticks"
...@@ -164,13 +164,11 @@ echo "" ...@@ -164,13 +164,11 @@ echo ""
echo "In this example, the caller has not provided the required blockhash:" 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" 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 && echo $cmd && $cmd
tick
$cmd
errc=$? errc=$?
tick tick
echo "Error code: $errc" echo "Error code: $errc"
echo ""
echo "### Chaining" echo "### Chaining"
echo "" echo ""
...@@ -189,3 +187,28 @@ echo "" ...@@ -189,3 +187,28 @@ echo ""
echo "In order to meaningfully chain invocations, one would need to provide meaningful new \`env\`, otherwise the" 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 "actual blocknumber (exposed to the EVM) would not increase."
echo "" 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\`.:"
demo "./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 "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: "
demo "./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 "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"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment