mirror of https://github.com/ethereum/go-ethereum
all: add support for EIP-2718, EIP-2930 transactions (#21502)
This adds support for EIP-2718 typed transactions as well as EIP-2930 access list transactions (tx type 1). These EIPs are scheduled for the Berlin fork. There very few changes to existing APIs in core/types, and several new APIs to deal with access list transactions. In particular, there are two new constructor functions for transactions: types.NewTx and types.SignNewTx. Since the canonical encoding of typed transactions is not RLP-compatible, Transaction now has new methods for encoding and decoding: MarshalBinary and UnmarshalBinary. The existing EIP-155 signer does not support the new transaction types. All code dealing with transaction signatures should be updated to use the newer EIP-2930 signer. To make this easier for future updates, we have added new constructor functions for types.Signer: types.LatestSigner and types.LatestSignerForChainID. This change also adds support for the YoloV3 testnet. Co-authored-by: Martin Holst Swende <martin@swende.se> Co-authored-by: Felix Lange <fjl@twurst.com> Co-authored-by: Ryan Schneider <ryanleeschneider@gmail.com>pull/22380/head
parent
7a3c890009
commit
bbfb1e4008
@ -0,0 +1,11 @@ |
|||||||
|
{ |
||||||
|
"0x000000000000000000000000000000000000aaaa": { |
||||||
|
"balance": "0x03", |
||||||
|
"code": "0x5854505854", |
||||||
|
"nonce": "0x1" |
||||||
|
}, |
||||||
|
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { |
||||||
|
"balance": "0x100000", |
||||||
|
"nonce": "0x00" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
{ |
||||||
|
"currentCoinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", |
||||||
|
"currentDifficulty": "0x20000", |
||||||
|
"currentGasLimit": "0x1000000000", |
||||||
|
"currentNumber": "0x1000000", |
||||||
|
"currentTimestamp": "0x04" |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
## EIP-2930 testing |
||||||
|
|
||||||
|
This test contains testcases for EIP-2930, which uses transactions with access lists. |
||||||
|
|
||||||
|
### Prestate |
||||||
|
|
||||||
|
The alloc portion contains one contract (`0x000000000000000000000000000000000000aaaa`), containing the |
||||||
|
following code: `0x5854505854`: `PC ;SLOAD; POP; PC; SLOAD`. |
||||||
|
|
||||||
|
Essentialy, this contract does `SLOAD(0)` and `SLOAD(3)`. |
||||||
|
|
||||||
|
The alloc also contains some funds on `0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b`. |
||||||
|
|
||||||
|
## Transactions |
||||||
|
|
||||||
|
There are three transactions, each invokes the contract above. |
||||||
|
|
||||||
|
1. ACL-transaction, which contains some non-used slots |
||||||
|
2. Regular transaction |
||||||
|
3. ACL-transaction, which contains the slots `1` and `3` in `0x000000000000000000000000000000000000aaaa` |
||||||
|
|
||||||
|
## Execution |
||||||
|
|
||||||
|
Running it yields: |
||||||
|
``` |
||||||
|
dir=./testdata/8 && ./evm t8n --state.fork=Berlin --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --trace && cat trace-* | grep SLOAD |
||||||
|
{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} |
||||||
|
{"pc":4,"op":84,"gas":"0x47c86","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} |
||||||
|
{"pc":1,"op":84,"gas":"0x49cf6","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} |
||||||
|
{"pc":4,"op":84,"gas":"0x494be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} |
||||||
|
{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} |
||||||
|
{"pc":4,"op":84,"gas":"0x48456","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} |
||||||
|
|
||||||
|
``` |
||||||
|
|
||||||
|
Simlarly, we can provide the input transactions via `stdin` instead of as file: |
||||||
|
|
||||||
|
``` |
||||||
|
dir=./testdata/8 \ |
||||||
|
&& cat $dir/txs.json | jq "{txs: .}" \ |
||||||
|
| ./evm t8n --state.fork=Berlin \ |
||||||
|
--input.alloc=$dir/alloc.json \ |
||||||
|
--input.txs=stdin \ |
||||||
|
--input.env=$dir/env.json \ |
||||||
|
--trace \ |
||||||
|
&& cat trace-* | grep SLOAD |
||||||
|
|
||||||
|
{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} |
||||||
|
{"pc":4,"op":84,"gas":"0x47c86","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} |
||||||
|
{"pc":1,"op":84,"gas":"0x49cf6","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} |
||||||
|
{"pc":4,"op":84,"gas":"0x494be","gasCost":"0x834","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} |
||||||
|
{"pc":1,"op":84,"gas":"0x484be","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x0"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} |
||||||
|
{"pc":4,"op":84,"gas":"0x48456","gasCost":"0x64","memory":"0x","memSize":0,"stack":["0x3"],"returnStack":[],"returnData":"0x","depth":1,"refund":0,"opName":"SLOAD","error":""} |
||||||
|
``` |
||||||
|
|
||||||
|
If we try to execute it on older rules: |
||||||
|
``` |
||||||
|
dir=./testdata/8 && ./evm t8n --state.fork=Istanbul --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json |
||||||
|
INFO [01-21|23:21:51.265] rejected tx index=0 hash="d2818d…6ab3da" error="tx type not supported" |
||||||
|
INFO [01-21|23:21:51.265] rejected tx index=1 hash="26ea00…81c01b" from=0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B error="nonce too high: address 0xa94f5374Fce5edBC8E2a8697C15331677e6EbF0B, tx: 1 state: 0" |
||||||
|
INFO [01-21|23:21:51.265] rejected tx index=2 hash="698d01…369cee" error="tx type not supported" |
||||||
|
``` |
||||||
|
Number `1` and `3` are not applicable, and therefore number `2` has wrong nonce. |
@ -0,0 +1,58 @@ |
|||||||
|
[ |
||||||
|
{ |
||||||
|
"gas": "0x4ef00", |
||||||
|
"gasPrice": "0x1", |
||||||
|
"chainId": "0x1", |
||||||
|
"input": "0x", |
||||||
|
"nonce": "0x0", |
||||||
|
"to": "0x000000000000000000000000000000000000aaaa", |
||||||
|
"value": "0x1", |
||||||
|
"type" : "0x1", |
||||||
|
"accessList": [ |
||||||
|
{"address": "0x0000000000000000000000000000000000000000", |
||||||
|
"storageKeys": [ |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000" |
||||||
|
] |
||||||
|
} |
||||||
|
], |
||||||
|
"v": "0x0", |
||||||
|
"r": "0x0", |
||||||
|
"s": "0x0", |
||||||
|
"secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"gas": "0x4ef00", |
||||||
|
"gasPrice": "0x1", |
||||||
|
"input": "0x", |
||||||
|
"nonce": "0x1", |
||||||
|
"to": "0x000000000000000000000000000000000000aaaa", |
||||||
|
"value": "0x2", |
||||||
|
"v": "0x0", |
||||||
|
"r": "0x0", |
||||||
|
"s": "0x0", |
||||||
|
"secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"gas": "0x4ef00", |
||||||
|
"gasPrice": "0x1", |
||||||
|
"chainId": "0x1", |
||||||
|
"input": "0x", |
||||||
|
"nonce": "0x2", |
||||||
|
"to": "0x000000000000000000000000000000000000aaaa", |
||||||
|
"value": "0x1", |
||||||
|
"type" : "0x1", |
||||||
|
"accessList": [ |
||||||
|
{"address": "0x000000000000000000000000000000000000aaaa", |
||||||
|
"storageKeys": [ |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000000", |
||||||
|
"0x0000000000000000000000000000000000000000000000000000000000000003" |
||||||
|
] |
||||||
|
} |
||||||
|
], |
||||||
|
"v": "0x0", |
||||||
|
"r": "0x0", |
||||||
|
"s": "0x0", |
||||||
|
"secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" |
||||||
|
} |
||||||
|
] |
@ -0,0 +1,115 @@ |
|||||||
|
// 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 types |
||||||
|
|
||||||
|
import ( |
||||||
|
"math/big" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
) |
||||||
|
|
||||||
|
//go:generate gencodec -type AccessTuple -out gen_access_tuple.go
|
||||||
|
|
||||||
|
// AccessList is an EIP-2930 access list.
|
||||||
|
type AccessList []AccessTuple |
||||||
|
|
||||||
|
// AccessTuple is the element type of an access list.
|
||||||
|
type AccessTuple struct { |
||||||
|
Address common.Address `json:"address" gencodec:"required"` |
||||||
|
StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` |
||||||
|
} |
||||||
|
|
||||||
|
// StorageKeys returns the total number of storage keys in the access list.
|
||||||
|
func (al AccessList) StorageKeys() int { |
||||||
|
sum := 0 |
||||||
|
for _, tuple := range al { |
||||||
|
sum += len(tuple.StorageKeys) |
||||||
|
} |
||||||
|
return sum |
||||||
|
} |
||||||
|
|
||||||
|
// AccessListTx is the data of EIP-2930 access list transactions.
|
||||||
|
type AccessListTx struct { |
||||||
|
ChainID *big.Int // destination chain ID
|
||||||
|
Nonce uint64 // nonce of sender account
|
||||||
|
GasPrice *big.Int // wei per gas
|
||||||
|
Gas uint64 // gas limit
|
||||||
|
To *common.Address `rlp:"nil"` // nil means contract creation
|
||||||
|
Value *big.Int // wei amount
|
||||||
|
Data []byte // contract invocation input data
|
||||||
|
AccessList AccessList // EIP-2930 access list
|
||||||
|
V, R, S *big.Int // signature values
|
||||||
|
} |
||||||
|
|
||||||
|
// copy creates a deep copy of the transaction data and initializes all fields.
|
||||||
|
func (tx *AccessListTx) copy() TxData { |
||||||
|
cpy := &AccessListTx{ |
||||||
|
Nonce: tx.Nonce, |
||||||
|
To: tx.To, // TODO: copy pointed-to address
|
||||||
|
Data: common.CopyBytes(tx.Data), |
||||||
|
Gas: tx.Gas, |
||||||
|
// These are copied below.
|
||||||
|
AccessList: make(AccessList, len(tx.AccessList)), |
||||||
|
Value: new(big.Int), |
||||||
|
ChainID: new(big.Int), |
||||||
|
GasPrice: new(big.Int), |
||||||
|
V: new(big.Int), |
||||||
|
R: new(big.Int), |
||||||
|
S: new(big.Int), |
||||||
|
} |
||||||
|
copy(cpy.AccessList, tx.AccessList) |
||||||
|
if tx.Value != nil { |
||||||
|
cpy.Value.Set(tx.Value) |
||||||
|
} |
||||||
|
if tx.ChainID != nil { |
||||||
|
cpy.ChainID.Set(tx.ChainID) |
||||||
|
} |
||||||
|
if tx.GasPrice != nil { |
||||||
|
cpy.GasPrice.Set(tx.GasPrice) |
||||||
|
} |
||||||
|
if tx.V != nil { |
||||||
|
cpy.V.Set(tx.V) |
||||||
|
} |
||||||
|
if tx.R != nil { |
||||||
|
cpy.R.Set(tx.R) |
||||||
|
} |
||||||
|
if tx.S != nil { |
||||||
|
cpy.S.Set(tx.S) |
||||||
|
} |
||||||
|
return cpy |
||||||
|
} |
||||||
|
|
||||||
|
// accessors for innerTx.
|
||||||
|
|
||||||
|
func (tx *AccessListTx) txType() byte { return AccessListTxType } |
||||||
|
func (tx *AccessListTx) chainID() *big.Int { return tx.ChainID } |
||||||
|
func (tx *AccessListTx) protected() bool { return true } |
||||||
|
func (tx *AccessListTx) accessList() AccessList { return tx.AccessList } |
||||||
|
func (tx *AccessListTx) data() []byte { return tx.Data } |
||||||
|
func (tx *AccessListTx) gas() uint64 { return tx.Gas } |
||||||
|
func (tx *AccessListTx) gasPrice() *big.Int { return tx.GasPrice } |
||||||
|
func (tx *AccessListTx) value() *big.Int { return tx.Value } |
||||||
|
func (tx *AccessListTx) nonce() uint64 { return tx.Nonce } |
||||||
|
func (tx *AccessListTx) to() *common.Address { return tx.To } |
||||||
|
|
||||||
|
func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) { |
||||||
|
return tx.V, tx.R, tx.S |
||||||
|
} |
||||||
|
|
||||||
|
func (tx *AccessListTx) setSignatureValues(chainID, v, r, s *big.Int) { |
||||||
|
tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s |
||||||
|
} |
@ -1,58 +0,0 @@ |
|||||||
// Copyright 2014 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 types |
|
||||||
|
|
||||||
import ( |
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/rlp" |
|
||||||
) |
|
||||||
|
|
||||||
type DerivableList interface { |
|
||||||
Len() int |
|
||||||
GetRlp(i int) []byte |
|
||||||
} |
|
||||||
|
|
||||||
// Hasher is the tool used to calculate the hash of derivable list.
|
|
||||||
type Hasher interface { |
|
||||||
Reset() |
|
||||||
Update([]byte, []byte) |
|
||||||
Hash() common.Hash |
|
||||||
} |
|
||||||
|
|
||||||
func DeriveSha(list DerivableList, hasher Hasher) common.Hash { |
|
||||||
hasher.Reset() |
|
||||||
|
|
||||||
// StackTrie requires values to be inserted in increasing
|
|
||||||
// hash order, which is not the order that `list` provides
|
|
||||||
// hashes in. This insertion sequence ensures that the
|
|
||||||
// order is correct.
|
|
||||||
|
|
||||||
var buf []byte |
|
||||||
for i := 1; i < list.Len() && i <= 0x7f; i++ { |
|
||||||
buf = rlp.AppendUint64(buf[:0], uint64(i)) |
|
||||||
hasher.Update(buf, list.GetRlp(i)) |
|
||||||
} |
|
||||||
if list.Len() > 0 { |
|
||||||
buf = rlp.AppendUint64(buf[:0], 0) |
|
||||||
hasher.Update(buf, list.GetRlp(0)) |
|
||||||
} |
|
||||||
for i := 0x80; i < list.Len(); i++ { |
|
||||||
buf = rlp.AppendUint64(buf[:0], uint64(i)) |
|
||||||
hasher.Update(buf, list.GetRlp(i)) |
|
||||||
} |
|
||||||
return hasher.Hash() |
|
||||||
} |
|
@ -0,0 +1,43 @@ |
|||||||
|
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||||
|
|
||||||
|
package types |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"errors" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
) |
||||||
|
|
||||||
|
// MarshalJSON marshals as JSON.
|
||||||
|
func (a AccessTuple) MarshalJSON() ([]byte, error) { |
||||||
|
type AccessTuple struct { |
||||||
|
Address common.Address `json:"address" gencodec:"required"` |
||||||
|
StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` |
||||||
|
} |
||||||
|
var enc AccessTuple |
||||||
|
enc.Address = a.Address |
||||||
|
enc.StorageKeys = a.StorageKeys |
||||||
|
return json.Marshal(&enc) |
||||||
|
} |
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (a *AccessTuple) UnmarshalJSON(input []byte) error { |
||||||
|
type AccessTuple struct { |
||||||
|
Address *common.Address `json:"address" gencodec:"required"` |
||||||
|
StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` |
||||||
|
} |
||||||
|
var dec AccessTuple |
||||||
|
if err := json.Unmarshal(input, &dec); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if dec.Address == nil { |
||||||
|
return errors.New("missing required field 'address' for AccessTuple") |
||||||
|
} |
||||||
|
a.Address = *dec.Address |
||||||
|
if dec.StorageKeys == nil { |
||||||
|
return errors.New("missing required field 'storageKeys' for AccessTuple") |
||||||
|
} |
||||||
|
a.StorageKeys = dec.StorageKeys |
||||||
|
return nil |
||||||
|
} |
@ -1,101 +0,0 @@ |
|||||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
|
||||||
|
|
||||||
package types |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/json" |
|
||||||
"errors" |
|
||||||
"math/big" |
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common" |
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil" |
|
||||||
) |
|
||||||
|
|
||||||
var _ = (*txdataMarshaling)(nil) |
|
||||||
|
|
||||||
// MarshalJSON marshals as JSON.
|
|
||||||
func (t txdata) MarshalJSON() ([]byte, error) { |
|
||||||
type txdata struct { |
|
||||||
AccountNonce hexutil.Uint64 `json:"nonce" gencodec:"required"` |
|
||||||
Price *hexutil.Big `json:"gasPrice" gencodec:"required"` |
|
||||||
GasLimit hexutil.Uint64 `json:"gas" gencodec:"required"` |
|
||||||
Recipient *common.Address `json:"to" rlp:"nil"` |
|
||||||
Amount *hexutil.Big `json:"value" gencodec:"required"` |
|
||||||
Payload hexutil.Bytes `json:"input" gencodec:"required"` |
|
||||||
V *hexutil.Big `json:"v" gencodec:"required"` |
|
||||||
R *hexutil.Big `json:"r" gencodec:"required"` |
|
||||||
S *hexutil.Big `json:"s" gencodec:"required"` |
|
||||||
Hash *common.Hash `json:"hash" rlp:"-"` |
|
||||||
} |
|
||||||
var enc txdata |
|
||||||
enc.AccountNonce = hexutil.Uint64(t.AccountNonce) |
|
||||||
enc.Price = (*hexutil.Big)(t.Price) |
|
||||||
enc.GasLimit = hexutil.Uint64(t.GasLimit) |
|
||||||
enc.Recipient = t.Recipient |
|
||||||
enc.Amount = (*hexutil.Big)(t.Amount) |
|
||||||
enc.Payload = t.Payload |
|
||||||
enc.V = (*hexutil.Big)(t.V) |
|
||||||
enc.R = (*hexutil.Big)(t.R) |
|
||||||
enc.S = (*hexutil.Big)(t.S) |
|
||||||
enc.Hash = t.Hash |
|
||||||
return json.Marshal(&enc) |
|
||||||
} |
|
||||||
|
|
||||||
// UnmarshalJSON unmarshals from JSON.
|
|
||||||
func (t *txdata) UnmarshalJSON(input []byte) error { |
|
||||||
type txdata struct { |
|
||||||
AccountNonce *hexutil.Uint64 `json:"nonce" gencodec:"required"` |
|
||||||
Price *hexutil.Big `json:"gasPrice" gencodec:"required"` |
|
||||||
GasLimit *hexutil.Uint64 `json:"gas" gencodec:"required"` |
|
||||||
Recipient *common.Address `json:"to" rlp:"nil"` |
|
||||||
Amount *hexutil.Big `json:"value" gencodec:"required"` |
|
||||||
Payload *hexutil.Bytes `json:"input" gencodec:"required"` |
|
||||||
V *hexutil.Big `json:"v" gencodec:"required"` |
|
||||||
R *hexutil.Big `json:"r" gencodec:"required"` |
|
||||||
S *hexutil.Big `json:"s" gencodec:"required"` |
|
||||||
Hash *common.Hash `json:"hash" rlp:"-"` |
|
||||||
} |
|
||||||
var dec txdata |
|
||||||
if err := json.Unmarshal(input, &dec); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
if dec.AccountNonce == nil { |
|
||||||
return errors.New("missing required field 'nonce' for txdata") |
|
||||||
} |
|
||||||
t.AccountNonce = uint64(*dec.AccountNonce) |
|
||||||
if dec.Price == nil { |
|
||||||
return errors.New("missing required field 'gasPrice' for txdata") |
|
||||||
} |
|
||||||
t.Price = (*big.Int)(dec.Price) |
|
||||||
if dec.GasLimit == nil { |
|
||||||
return errors.New("missing required field 'gas' for txdata") |
|
||||||
} |
|
||||||
t.GasLimit = uint64(*dec.GasLimit) |
|
||||||
if dec.Recipient != nil { |
|
||||||
t.Recipient = dec.Recipient |
|
||||||
} |
|
||||||
if dec.Amount == nil { |
|
||||||
return errors.New("missing required field 'value' for txdata") |
|
||||||
} |
|
||||||
t.Amount = (*big.Int)(dec.Amount) |
|
||||||
if dec.Payload == nil { |
|
||||||
return errors.New("missing required field 'input' for txdata") |
|
||||||
} |
|
||||||
t.Payload = *dec.Payload |
|
||||||
if dec.V == nil { |
|
||||||
return errors.New("missing required field 'v' for txdata") |
|
||||||
} |
|
||||||
t.V = (*big.Int)(dec.V) |
|
||||||
if dec.R == nil { |
|
||||||
return errors.New("missing required field 'r' for txdata") |
|
||||||
} |
|
||||||
t.R = (*big.Int)(dec.R) |
|
||||||
if dec.S == nil { |
|
||||||
return errors.New("missing required field 's' for txdata") |
|
||||||
} |
|
||||||
t.S = (*big.Int)(dec.S) |
|
||||||
if dec.Hash != nil { |
|
||||||
t.Hash = dec.Hash |
|
||||||
} |
|
||||||
return nil |
|
||||||
} |
|
@ -0,0 +1,112 @@ |
|||||||
|
// Copyright 2014 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 types |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/rlp" |
||||||
|
"golang.org/x/crypto/sha3" |
||||||
|
) |
||||||
|
|
||||||
|
// hasherPool holds LegacyKeccak256 hashers for rlpHash.
|
||||||
|
var hasherPool = sync.Pool{ |
||||||
|
New: func() interface{} { return sha3.NewLegacyKeccak256() }, |
||||||
|
} |
||||||
|
|
||||||
|
// deriveBufferPool holds temporary encoder buffers for DeriveSha and TX encoding.
|
||||||
|
var encodeBufferPool = sync.Pool{ |
||||||
|
New: func() interface{} { return new(bytes.Buffer) }, |
||||||
|
} |
||||||
|
|
||||||
|
func rlpHash(x interface{}) (h common.Hash) { |
||||||
|
sha := hasherPool.Get().(crypto.KeccakState) |
||||||
|
defer hasherPool.Put(sha) |
||||||
|
sha.Reset() |
||||||
|
rlp.Encode(sha, x) |
||||||
|
sha.Read(h[:]) |
||||||
|
return h |
||||||
|
} |
||||||
|
|
||||||
|
// prefixedRlpHash writes the prefix into the hasher before rlp-encoding the
|
||||||
|
// given interface. It's used for typed transactions.
|
||||||
|
func prefixedRlpHash(prefix byte, x interface{}) (h common.Hash) { |
||||||
|
sha := hasherPool.Get().(crypto.KeccakState) |
||||||
|
defer hasherPool.Put(sha) |
||||||
|
sha.Reset() |
||||||
|
sha.Write([]byte{prefix}) |
||||||
|
rlp.Encode(sha, x) |
||||||
|
sha.Read(h[:]) |
||||||
|
return h |
||||||
|
} |
||||||
|
|
||||||
|
// TrieHasher is the tool used to calculate the hash of derivable list.
|
||||||
|
// This is internal, do not use.
|
||||||
|
type TrieHasher interface { |
||||||
|
Reset() |
||||||
|
Update([]byte, []byte) |
||||||
|
Hash() common.Hash |
||||||
|
} |
||||||
|
|
||||||
|
// DerivableList is the input to DeriveSha.
|
||||||
|
// It is implemented by the 'Transactions' and 'Receipts' types.
|
||||||
|
// This is internal, do not use these methods.
|
||||||
|
type DerivableList interface { |
||||||
|
Len() int |
||||||
|
EncodeIndex(int, *bytes.Buffer) |
||||||
|
} |
||||||
|
|
||||||
|
func encodeForDerive(list DerivableList, i int, buf *bytes.Buffer) []byte { |
||||||
|
buf.Reset() |
||||||
|
list.EncodeIndex(i, buf) |
||||||
|
// It's really unfortunate that we need to do perform this copy.
|
||||||
|
// StackTrie holds onto the values until Hash is called, so the values
|
||||||
|
// written to it must not alias.
|
||||||
|
return common.CopyBytes(buf.Bytes()) |
||||||
|
} |
||||||
|
|
||||||
|
// DeriveSha creates the tree hashes of transactions and receipts in a block header.
|
||||||
|
func DeriveSha(list DerivableList, hasher TrieHasher) common.Hash { |
||||||
|
hasher.Reset() |
||||||
|
|
||||||
|
valueBuf := encodeBufferPool.Get().(*bytes.Buffer) |
||||||
|
defer encodeBufferPool.Put(valueBuf) |
||||||
|
|
||||||
|
// StackTrie requires values to be inserted in increasing hash order, which is not the
|
||||||
|
// order that `list` provides hashes in. This insertion sequence ensures that the
|
||||||
|
// order is correct.
|
||||||
|
var indexBuf []byte |
||||||
|
for i := 1; i < list.Len() && i <= 0x7f; i++ { |
||||||
|
indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) |
||||||
|
value := encodeForDerive(list, i, valueBuf) |
||||||
|
hasher.Update(indexBuf, value) |
||||||
|
} |
||||||
|
if list.Len() > 0 { |
||||||
|
indexBuf = rlp.AppendUint64(indexBuf[:0], 0) |
||||||
|
value := encodeForDerive(list, 0, valueBuf) |
||||||
|
hasher.Update(indexBuf, value) |
||||||
|
} |
||||||
|
for i := 0x80; i < list.Len(); i++ { |
||||||
|
indexBuf = rlp.AppendUint64(indexBuf[:0], uint64(i)) |
||||||
|
value := encodeForDerive(list, i, valueBuf) |
||||||
|
hasher.Update(indexBuf, value) |
||||||
|
} |
||||||
|
return hasher.Hash() |
||||||
|
} |
@ -0,0 +1,212 @@ |
|||||||
|
package types_test |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"math/big" |
||||||
|
mrand "math/rand" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil" |
||||||
|
"github.com/ethereum/go-ethereum/core/types" |
||||||
|
"github.com/ethereum/go-ethereum/crypto" |
||||||
|
"github.com/ethereum/go-ethereum/rlp" |
||||||
|
"github.com/ethereum/go-ethereum/trie" |
||||||
|
) |
||||||
|
|
||||||
|
func TestDeriveSha(t *testing.T) { |
||||||
|
txs, err := genTxs(0) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
for len(txs) < 1000 { |
||||||
|
exp := types.DeriveSha(txs, new(trie.Trie)) |
||||||
|
got := types.DeriveSha(txs, trie.NewStackTrie(nil)) |
||||||
|
if !bytes.Equal(got[:], exp[:]) { |
||||||
|
t.Fatalf("%d txs: got %x exp %x", len(txs), got, exp) |
||||||
|
} |
||||||
|
newTxs, err := genTxs(uint64(len(txs) + 1)) |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
txs = append(txs, newTxs...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TestEIP2718DeriveSha tests that the input to the DeriveSha function is correct.
|
||||||
|
func TestEIP2718DeriveSha(t *testing.T) { |
||||||
|
for _, tc := range []struct { |
||||||
|
rlpData string |
||||||
|
exp string |
||||||
|
}{ |
||||||
|
{ |
||||||
|
rlpData: "0xb8a701f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9", |
||||||
|
exp: "01 01f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9\n80 01f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9\n", |
||||||
|
}, |
||||||
|
} { |
||||||
|
d := &hashToHumanReadable{} |
||||||
|
var t1, t2 types.Transaction |
||||||
|
rlp.DecodeBytes(common.FromHex(tc.rlpData), &t1) |
||||||
|
rlp.DecodeBytes(common.FromHex(tc.rlpData), &t2) |
||||||
|
txs := types.Transactions{&t1, &t2} |
||||||
|
types.DeriveSha(txs, d) |
||||||
|
if tc.exp != string(d.data) { |
||||||
|
t.Fatalf("Want\n%v\nhave:\n%v", tc.exp, string(d.data)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func BenchmarkDeriveSha200(b *testing.B) { |
||||||
|
txs, err := genTxs(200) |
||||||
|
if err != nil { |
||||||
|
b.Fatal(err) |
||||||
|
} |
||||||
|
var exp common.Hash |
||||||
|
var got common.Hash |
||||||
|
b.Run("std_trie", func(b *testing.B) { |
||||||
|
b.ResetTimer() |
||||||
|
b.ReportAllocs() |
||||||
|
for i := 0; i < b.N; i++ { |
||||||
|
exp = types.DeriveSha(txs, new(trie.Trie)) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
b.Run("stack_trie", func(b *testing.B) { |
||||||
|
b.ResetTimer() |
||||||
|
b.ReportAllocs() |
||||||
|
for i := 0; i < b.N; i++ { |
||||||
|
got = types.DeriveSha(txs, trie.NewStackTrie(nil)) |
||||||
|
} |
||||||
|
}) |
||||||
|
if got != exp { |
||||||
|
b.Errorf("got %x exp %x", got, exp) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestFuzzDeriveSha(t *testing.T) { |
||||||
|
// increase this for longer runs -- it's set to quite low for travis
|
||||||
|
rndSeed := mrand.Int() |
||||||
|
for i := 0; i < 10; i++ { |
||||||
|
seed := rndSeed + i |
||||||
|
exp := types.DeriveSha(newDummy(i), new(trie.Trie)) |
||||||
|
got := types.DeriveSha(newDummy(i), trie.NewStackTrie(nil)) |
||||||
|
if !bytes.Equal(got[:], exp[:]) { |
||||||
|
printList(newDummy(seed)) |
||||||
|
t.Fatalf("seed %d: got %x exp %x", seed, got, exp) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TestDerivableList contains testcases found via fuzzing
|
||||||
|
func TestDerivableList(t *testing.T) { |
||||||
|
type tcase []string |
||||||
|
tcs := []tcase{ |
||||||
|
{ |
||||||
|
"0xc041", |
||||||
|
}, |
||||||
|
{ |
||||||
|
"0xf04cf757812428b0763112efb33b6f4fad7deb445e", |
||||||
|
"0xf04cf757812428b0763112efb33b6f4fad7deb445e", |
||||||
|
}, |
||||||
|
{ |
||||||
|
"0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", |
||||||
|
"0x6cd850eca0a7ac46bb1748d7b9cb88aa3bd21c57d852c28198ad8fa422c4595032e88a4494b4778b36b944fe47a52b8c5cd312910139dfcb4147ab8e972cc456bcb063f25dd78f54c4d34679e03142c42c662af52947d45bdb6e555751334ace76a5080ab5a0256a1d259855dfc5c0b8023b25befbb13fd3684f9f755cbd3d63544c78ee2001452dd54633a7593ade0b183891a0a4e9c7844e1254005fbe592b1b89149a502c24b6e1dca44c158aebedf01beae9c30cabe16a", |
||||||
|
"0x14abd5c47c0be87b0454596baad2", |
||||||
|
"0xca410605310cdc3bb8d4977ae4f0143df54a724ed873457e2272f39d66e0460e971d9d", |
||||||
|
}, |
||||||
|
} |
||||||
|
for i, tc := range tcs[1:] { |
||||||
|
exp := types.DeriveSha(flatList(tc), new(trie.Trie)) |
||||||
|
got := types.DeriveSha(flatList(tc), trie.NewStackTrie(nil)) |
||||||
|
if !bytes.Equal(got[:], exp[:]) { |
||||||
|
t.Fatalf("case %d: got %x exp %x", i, got, exp) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func genTxs(num uint64) (types.Transactions, error) { |
||||||
|
key, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
var addr = crypto.PubkeyToAddress(key.PublicKey) |
||||||
|
newTx := func(i uint64) (*types.Transaction, error) { |
||||||
|
signer := types.NewEIP155Signer(big.NewInt(18)) |
||||||
|
utx := types.NewTransaction(i, addr, new(big.Int), 0, new(big.Int).SetUint64(10000000), nil) |
||||||
|
tx, err := types.SignTx(utx, signer, key) |
||||||
|
return tx, err |
||||||
|
} |
||||||
|
var txs types.Transactions |
||||||
|
for i := uint64(0); i < num; i++ { |
||||||
|
tx, err := newTx(i) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
txs = append(txs, tx) |
||||||
|
} |
||||||
|
return txs, nil |
||||||
|
} |
||||||
|
|
||||||
|
type dummyDerivableList struct { |
||||||
|
len int |
||||||
|
seed int |
||||||
|
} |
||||||
|
|
||||||
|
func newDummy(seed int) *dummyDerivableList { |
||||||
|
d := &dummyDerivableList{} |
||||||
|
src := mrand.NewSource(int64(seed)) |
||||||
|
// don't use lists longer than 4K items
|
||||||
|
d.len = int(src.Int63() & 0x0FFF) |
||||||
|
d.seed = seed |
||||||
|
return d |
||||||
|
} |
||||||
|
|
||||||
|
func (d *dummyDerivableList) Len() int { |
||||||
|
return d.len |
||||||
|
} |
||||||
|
|
||||||
|
func (d *dummyDerivableList) EncodeIndex(i int, w *bytes.Buffer) { |
||||||
|
src := mrand.NewSource(int64(d.seed + i)) |
||||||
|
// max item size 256, at least 1 byte per item
|
||||||
|
size := 1 + src.Int63()&0x00FF |
||||||
|
io.CopyN(w, mrand.New(src), size) |
||||||
|
} |
||||||
|
|
||||||
|
func printList(l types.DerivableList) { |
||||||
|
fmt.Printf("list length: %d\n", l.Len()) |
||||||
|
fmt.Printf("{\n") |
||||||
|
for i := 0; i < l.Len(); i++ { |
||||||
|
var buf bytes.Buffer |
||||||
|
l.EncodeIndex(i, &buf) |
||||||
|
fmt.Printf("\"0x%x\",\n", buf.Bytes()) |
||||||
|
} |
||||||
|
fmt.Printf("},\n") |
||||||
|
} |
||||||
|
|
||||||
|
type flatList []string |
||||||
|
|
||||||
|
func (f flatList) Len() int { |
||||||
|
return len(f) |
||||||
|
} |
||||||
|
func (f flatList) EncodeIndex(i int, w *bytes.Buffer) { |
||||||
|
w.Write(hexutil.MustDecode(f[i])) |
||||||
|
} |
||||||
|
|
||||||
|
type hashToHumanReadable struct { |
||||||
|
data []byte |
||||||
|
} |
||||||
|
|
||||||
|
func (d *hashToHumanReadable) Reset() { |
||||||
|
d.data = make([]byte, 0) |
||||||
|
} |
||||||
|
|
||||||
|
func (d *hashToHumanReadable) Update(i []byte, i2 []byte) { |
||||||
|
l := fmt.Sprintf("%x %x\n", i, i2) |
||||||
|
d.data = append(d.data, []byte(l)...) |
||||||
|
} |
||||||
|
|
||||||
|
func (d *hashToHumanReadable) Hash() common.Hash { |
||||||
|
return common.Hash{} |
||||||
|
} |
@ -0,0 +1,111 @@ |
|||||||
|
// 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 types |
||||||
|
|
||||||
|
import ( |
||||||
|
"math/big" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
) |
||||||
|
|
||||||
|
// LegacyTx is the transaction data of regular Ethereum transactions.
|
||||||
|
type LegacyTx struct { |
||||||
|
Nonce uint64 // nonce of sender account
|
||||||
|
GasPrice *big.Int // wei per gas
|
||||||
|
Gas uint64 // gas limit
|
||||||
|
To *common.Address `rlp:"nil"` // nil means contract creation
|
||||||
|
Value *big.Int // wei amount
|
||||||
|
Data []byte // contract invocation input data
|
||||||
|
V, R, S *big.Int // signature values
|
||||||
|
} |
||||||
|
|
||||||
|
// NewTransaction creates an unsigned legacy transaction.
|
||||||
|
// Deprecated: use NewTx instead.
|
||||||
|
func NewTransaction(nonce uint64, to common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { |
||||||
|
return NewTx(&LegacyTx{ |
||||||
|
Nonce: nonce, |
||||||
|
To: &to, |
||||||
|
Value: amount, |
||||||
|
Gas: gasLimit, |
||||||
|
GasPrice: gasPrice, |
||||||
|
Data: data, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// NewContractCreation creates an unsigned legacy transaction.
|
||||||
|
// Deprecated: use NewTx instead.
|
||||||
|
func NewContractCreation(nonce uint64, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { |
||||||
|
return NewTx(&LegacyTx{ |
||||||
|
Nonce: nonce, |
||||||
|
Value: amount, |
||||||
|
Gas: gasLimit, |
||||||
|
GasPrice: gasPrice, |
||||||
|
Data: data, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// copy creates a deep copy of the transaction data and initializes all fields.
|
||||||
|
func (tx *LegacyTx) copy() TxData { |
||||||
|
cpy := &LegacyTx{ |
||||||
|
Nonce: tx.Nonce, |
||||||
|
To: tx.To, // TODO: copy pointed-to address
|
||||||
|
Data: common.CopyBytes(tx.Data), |
||||||
|
Gas: tx.Gas, |
||||||
|
// These are initialized below.
|
||||||
|
Value: new(big.Int), |
||||||
|
GasPrice: new(big.Int), |
||||||
|
V: new(big.Int), |
||||||
|
R: new(big.Int), |
||||||
|
S: new(big.Int), |
||||||
|
} |
||||||
|
if tx.Value != nil { |
||||||
|
cpy.Value.Set(tx.Value) |
||||||
|
} |
||||||
|
if tx.GasPrice != nil { |
||||||
|
cpy.GasPrice.Set(tx.GasPrice) |
||||||
|
} |
||||||
|
if tx.V != nil { |
||||||
|
cpy.V.Set(tx.V) |
||||||
|
} |
||||||
|
if tx.R != nil { |
||||||
|
cpy.R.Set(tx.R) |
||||||
|
} |
||||||
|
if tx.S != nil { |
||||||
|
cpy.S.Set(tx.S) |
||||||
|
} |
||||||
|
return cpy |
||||||
|
} |
||||||
|
|
||||||
|
// accessors for innerTx.
|
||||||
|
|
||||||
|
func (tx *LegacyTx) txType() byte { return LegacyTxType } |
||||||
|
func (tx *LegacyTx) chainID() *big.Int { return deriveChainId(tx.V) } |
||||||
|
func (tx *LegacyTx) accessList() AccessList { return nil } |
||||||
|
func (tx *LegacyTx) data() []byte { return tx.Data } |
||||||
|
func (tx *LegacyTx) gas() uint64 { return tx.Gas } |
||||||
|
func (tx *LegacyTx) gasPrice() *big.Int { return tx.GasPrice } |
||||||
|
func (tx *LegacyTx) value() *big.Int { return tx.Value } |
||||||
|
func (tx *LegacyTx) nonce() uint64 { return tx.Nonce } |
||||||
|
func (tx *LegacyTx) to() *common.Address { return tx.To } |
||||||
|
|
||||||
|
func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) { |
||||||
|
return tx.V, tx.R, tx.S |
||||||
|
} |
||||||
|
|
||||||
|
func (tx *LegacyTx) setSignatureValues(chainID, v, r, s *big.Int) { |
||||||
|
tx.V, tx.R, tx.S = v, r, s |
||||||
|
} |
@ -0,0 +1,187 @@ |
|||||||
|
package types |
||||||
|
|
||||||
|
import ( |
||||||
|
"encoding/json" |
||||||
|
"errors" |
||||||
|
"math/big" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil" |
||||||
|
) |
||||||
|
|
||||||
|
// txJSON is the JSON representation of transactions.
|
||||||
|
type txJSON struct { |
||||||
|
Type hexutil.Uint64 `json:"type"` |
||||||
|
|
||||||
|
// Common transaction fields:
|
||||||
|
Nonce *hexutil.Uint64 `json:"nonce"` |
||||||
|
GasPrice *hexutil.Big `json:"gasPrice"` |
||||||
|
Gas *hexutil.Uint64 `json:"gas"` |
||||||
|
Value *hexutil.Big `json:"value"` |
||||||
|
Data *hexutil.Bytes `json:"input"` |
||||||
|
V *hexutil.Big `json:"v"` |
||||||
|
R *hexutil.Big `json:"r"` |
||||||
|
S *hexutil.Big `json:"s"` |
||||||
|
To *common.Address `json:"to"` |
||||||
|
|
||||||
|
// Access list transaction fields:
|
||||||
|
ChainID *hexutil.Big `json:"chainId,omitempty"` |
||||||
|
AccessList *AccessList `json:"accessList,omitempty"` |
||||||
|
|
||||||
|
// Only used for encoding:
|
||||||
|
Hash common.Hash `json:"hash"` |
||||||
|
} |
||||||
|
|
||||||
|
// MarshalJSON marshals as JSON with a hash.
|
||||||
|
func (t *Transaction) MarshalJSON() ([]byte, error) { |
||||||
|
var enc txJSON |
||||||
|
// These are set for all tx types.
|
||||||
|
enc.Hash = t.Hash() |
||||||
|
enc.Type = hexutil.Uint64(t.Type()) |
||||||
|
|
||||||
|
// Other fields are set conditionally depending on tx type.
|
||||||
|
switch tx := t.inner.(type) { |
||||||
|
case *LegacyTx: |
||||||
|
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) |
||||||
|
enc.Gas = (*hexutil.Uint64)(&tx.Gas) |
||||||
|
enc.GasPrice = (*hexutil.Big)(tx.GasPrice) |
||||||
|
enc.Value = (*hexutil.Big)(tx.Value) |
||||||
|
enc.Data = (*hexutil.Bytes)(&tx.Data) |
||||||
|
enc.To = t.To() |
||||||
|
enc.V = (*hexutil.Big)(tx.V) |
||||||
|
enc.R = (*hexutil.Big)(tx.R) |
||||||
|
enc.S = (*hexutil.Big)(tx.S) |
||||||
|
case *AccessListTx: |
||||||
|
enc.ChainID = (*hexutil.Big)(tx.ChainID) |
||||||
|
enc.AccessList = &tx.AccessList |
||||||
|
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce) |
||||||
|
enc.Gas = (*hexutil.Uint64)(&tx.Gas) |
||||||
|
enc.GasPrice = (*hexutil.Big)(tx.GasPrice) |
||||||
|
enc.Value = (*hexutil.Big)(tx.Value) |
||||||
|
enc.Data = (*hexutil.Bytes)(&tx.Data) |
||||||
|
enc.To = t.To() |
||||||
|
enc.V = (*hexutil.Big)(tx.V) |
||||||
|
enc.R = (*hexutil.Big)(tx.R) |
||||||
|
enc.S = (*hexutil.Big)(tx.S) |
||||||
|
} |
||||||
|
return json.Marshal(&enc) |
||||||
|
} |
||||||
|
|
||||||
|
// UnmarshalJSON unmarshals from JSON.
|
||||||
|
func (t *Transaction) UnmarshalJSON(input []byte) error { |
||||||
|
var dec txJSON |
||||||
|
if err := json.Unmarshal(input, &dec); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// Decode / verify fields according to transaction type.
|
||||||
|
var inner TxData |
||||||
|
switch dec.Type { |
||||||
|
case LegacyTxType: |
||||||
|
var itx LegacyTx |
||||||
|
inner = &itx |
||||||
|
if dec.To != nil { |
||||||
|
itx.To = dec.To |
||||||
|
} |
||||||
|
if dec.Nonce == nil { |
||||||
|
return errors.New("missing required field 'nonce' in transaction") |
||||||
|
} |
||||||
|
itx.Nonce = uint64(*dec.Nonce) |
||||||
|
if dec.GasPrice == nil { |
||||||
|
return errors.New("missing required field 'gasPrice' in transaction") |
||||||
|
} |
||||||
|
itx.GasPrice = (*big.Int)(dec.GasPrice) |
||||||
|
if dec.Gas == nil { |
||||||
|
return errors.New("missing required field 'gas' in transaction") |
||||||
|
} |
||||||
|
itx.Gas = uint64(*dec.Gas) |
||||||
|
if dec.Value == nil { |
||||||
|
return errors.New("missing required field 'value' in transaction") |
||||||
|
} |
||||||
|
itx.Value = (*big.Int)(dec.Value) |
||||||
|
if dec.Data == nil { |
||||||
|
return errors.New("missing required field 'input' in transaction") |
||||||
|
} |
||||||
|
itx.Data = *dec.Data |
||||||
|
if dec.V == nil { |
||||||
|
return errors.New("missing required field 'v' in transaction") |
||||||
|
} |
||||||
|
itx.V = (*big.Int)(dec.V) |
||||||
|
if dec.R == nil { |
||||||
|
return errors.New("missing required field 'r' in transaction") |
||||||
|
} |
||||||
|
itx.R = (*big.Int)(dec.R) |
||||||
|
if dec.S == nil { |
||||||
|
return errors.New("missing required field 's' in transaction") |
||||||
|
} |
||||||
|
itx.S = (*big.Int)(dec.S) |
||||||
|
withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 |
||||||
|
if withSignature { |
||||||
|
if err := sanityCheckSignature(itx.V, itx.R, itx.S, true); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
case AccessListTxType: |
||||||
|
var itx AccessListTx |
||||||
|
inner = &itx |
||||||
|
// Access list is optional for now.
|
||||||
|
if dec.AccessList != nil { |
||||||
|
itx.AccessList = *dec.AccessList |
||||||
|
} |
||||||
|
if dec.ChainID == nil { |
||||||
|
return errors.New("missing required field 'chainId' in transaction") |
||||||
|
} |
||||||
|
itx.ChainID = (*big.Int)(dec.ChainID) |
||||||
|
if dec.To != nil { |
||||||
|
itx.To = dec.To |
||||||
|
} |
||||||
|
if dec.Nonce == nil { |
||||||
|
return errors.New("missing required field 'nonce' in transaction") |
||||||
|
} |
||||||
|
itx.Nonce = uint64(*dec.Nonce) |
||||||
|
if dec.GasPrice == nil { |
||||||
|
return errors.New("missing required field 'gasPrice' in transaction") |
||||||
|
} |
||||||
|
itx.GasPrice = (*big.Int)(dec.GasPrice) |
||||||
|
if dec.Gas == nil { |
||||||
|
return errors.New("missing required field 'gas' in transaction") |
||||||
|
} |
||||||
|
itx.Gas = uint64(*dec.Gas) |
||||||
|
if dec.Value == nil { |
||||||
|
return errors.New("missing required field 'value' in transaction") |
||||||
|
} |
||||||
|
itx.Value = (*big.Int)(dec.Value) |
||||||
|
if dec.Data == nil { |
||||||
|
return errors.New("missing required field 'input' in transaction") |
||||||
|
} |
||||||
|
itx.Data = *dec.Data |
||||||
|
if dec.V == nil { |
||||||
|
return errors.New("missing required field 'v' in transaction") |
||||||
|
} |
||||||
|
itx.V = (*big.Int)(dec.V) |
||||||
|
if dec.R == nil { |
||||||
|
return errors.New("missing required field 'r' in transaction") |
||||||
|
} |
||||||
|
itx.R = (*big.Int)(dec.R) |
||||||
|
if dec.S == nil { |
||||||
|
return errors.New("missing required field 's' in transaction") |
||||||
|
} |
||||||
|
itx.S = (*big.Int)(dec.S) |
||||||
|
withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 |
||||||
|
if withSignature { |
||||||
|
if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
default: |
||||||
|
return ErrTxTypeNotSupported |
||||||
|
} |
||||||
|
|
||||||
|
// Now set the inner transaction.
|
||||||
|
t.setDecoded(inner, 0) |
||||||
|
|
||||||
|
// TODO: check hash here?
|
||||||
|
return nil |
||||||
|
} |
Loading…
Reference in new issue