Add types package
This commit is contained in:
parent
8eb2ef63b8
commit
0ee401ca60
@ -13,15 +13,30 @@ func (h Hash) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf(`"%#x"`, h[:])), nil
|
||||
}
|
||||
|
||||
func (h Hash) UnmarshalJSON(data []byte) error {
|
||||
_, err := hex.Decode(h[32-len(data):], bytes.TrimPrefix(bytes.Trim(data, `"`), []byte("0x")))
|
||||
func (h *Hash) UnmarshalJSON(data []byte) error {
|
||||
d := bytes.TrimPrefix(bytes.Trim(data, `"`), []byte("0x"))
|
||||
_, err := hex.Decode(h[(64 - len(d)) / 2:], d)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h Hash) Bytes() []byte {
|
||||
return ([]byte)(h[:])
|
||||
}
|
||||
|
||||
func (h Hash) String() string {
|
||||
return fmt.Sprintf("%#x", h[:])
|
||||
}
|
||||
|
||||
func HexToHash(data string) Hash {
|
||||
h := Hash{}
|
||||
b, _ := hex.DecodeString(strings.TrimPrefix(strings.Trim(data, `"`), "0x"))
|
||||
copy(h[32-len(data):], b)
|
||||
copy(h[32 - len(b):], b)
|
||||
return h
|
||||
}
|
||||
|
||||
func BytesToHash(b []byte) Hash {
|
||||
h := Hash{}
|
||||
copy(h[32-len(b):], b)
|
||||
return h
|
||||
}
|
||||
|
||||
@ -31,19 +46,31 @@ func (h Address) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf(`"%#x"`, h[:])), nil
|
||||
}
|
||||
|
||||
func (h Address) UnmarshalJSON(data []byte) error {
|
||||
_, err := hex.Decode(h[20-len(data):], bytes.TrimPrefix(bytes.Trim(data, `"`), []byte("0x")))
|
||||
func (h *Address) UnmarshalJSON(data []byte) error {
|
||||
d := bytes.TrimPrefix(bytes.Trim(data, `"`), []byte("0x"))
|
||||
_, err := hex.Decode(h[(40 - len(d))/2:], d)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h Address) String() string {
|
||||
return fmt.Sprintf("%#x", h[:])
|
||||
}
|
||||
|
||||
|
||||
func HexToAddress(data string) Address {
|
||||
h := Address{}
|
||||
b, _ := hex.DecodeString(strings.TrimPrefix(strings.Trim(data, `"`), "0x"))
|
||||
copy(h[20-len(data):], b)
|
||||
copy(h[20 - len(b):], b)
|
||||
return h
|
||||
}
|
||||
|
||||
func BytesToAddress(b []byte) Address {
|
||||
h := Address{}
|
||||
copy(h[20-len(b):], b)
|
||||
return h
|
||||
}
|
||||
|
||||
|
||||
type ChainEvent struct {
|
||||
Block []byte // RLP Encoded block
|
||||
Hash Hash
|
||||
@ -73,3 +100,10 @@ type API struct {
|
||||
Service interface{}
|
||||
Public bool
|
||||
}
|
||||
|
||||
|
||||
func CopyBytes(a []byte) []byte {
|
||||
b := make([]byte, len(a))
|
||||
copy(b[:], a[:])
|
||||
return b
|
||||
}
|
||||
|
6
go.mod
6
go.mod
@ -2,4 +2,8 @@ module github.com/openrelayxyz/plugeth-utils
|
||||
|
||||
go 1.16
|
||||
|
||||
require github.com/holiman/uint256 v1.2.0
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/holiman/uint256 v1.2.0
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||
)
|
||||
|
11
go.sum
11
go.sum
@ -1,2 +1,13 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/holiman/uint256 v1.2.0 h1:gpSYcPLWGv4sG43I2mVLiDZCNDh/EpGjSk8tmtxitHM=
|
||||
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -2,10 +2,12 @@ package restricted
|
||||
|
||||
import (
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/params"
|
||||
)
|
||||
|
||||
type Backend interface {
|
||||
core.Backend
|
||||
// General Ethereum API
|
||||
ChainDb() Database
|
||||
ChainConfig() *params.ChainConfig
|
||||
}
|
||||
|
116
restricted/types/access_list_tx.go
Normal file
116
restricted/types/access_list_tx.go
Normal file
@ -0,0 +1,116 @@
|
||||
// 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/openrelayxyz/plugeth-utils/core"
|
||||
)
|
||||
|
||||
//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 core.Address `json:"address" gencodec:"required"`
|
||||
StorageKeys []core.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 *core.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: core.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) gasTipCap() *big.Int { return tx.GasPrice }
|
||||
func (tx *AccessListTx) gasFeeCap() *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() *core.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
|
||||
}
|
385
restricted/types/block.go
Normal file
385
restricted/types/block.go
Normal file
@ -0,0 +1,385 @@
|
||||
// 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 contains data types related to Ethereum consensus.
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/hexutil"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/rlp"
|
||||
)
|
||||
|
||||
var (
|
||||
EmptyRootHash = core.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
EmptyUncleHash = rlpHash([]*Header(nil))
|
||||
)
|
||||
|
||||
// A BlockNonce is a 64-bit hash which proves (combined with the
|
||||
// mix-hash) that a sufficient amount of computation has been carried
|
||||
// out on a block.
|
||||
type BlockNonce [8]byte
|
||||
|
||||
// EncodeNonce converts the given integer to a block nonce.
|
||||
func EncodeNonce(i uint64) BlockNonce {
|
||||
var n BlockNonce
|
||||
binary.BigEndian.PutUint64(n[:], i)
|
||||
return n
|
||||
}
|
||||
|
||||
// Uint64 returns the integer value of a block nonce.
|
||||
func (n BlockNonce) Uint64() uint64 {
|
||||
return binary.BigEndian.Uint64(n[:])
|
||||
}
|
||||
|
||||
// MarshalText encodes n as a hex string with 0x prefix.
|
||||
func (n BlockNonce) MarshalText() ([]byte, error) {
|
||||
return hexutil.Bytes(n[:]).MarshalText()
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (n *BlockNonce) UnmarshalText(input []byte) error {
|
||||
return hexutil.UnmarshalFixedText("BlockNonce", input, n[:])
|
||||
}
|
||||
|
||||
//go:generate gencodec -type Header -field-override headerMarshaling -out gen_header_json.go
|
||||
|
||||
// Header represents a block header in the Ethereum blockchain.
|
||||
type Header struct {
|
||||
ParentHash core.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash core.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
Coinbase core.Address `json:"miner" gencodec:"required"`
|
||||
Root core.Hash `json:"stateRoot" gencodec:"required"`
|
||||
TxHash core.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||
ReceiptHash core.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
Bloom Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Difficulty *big.Int `json:"difficulty" gencodec:"required"`
|
||||
Number *big.Int `json:"number" gencodec:"required"`
|
||||
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Time uint64 `json:"timestamp" gencodec:"required"`
|
||||
Extra []byte `json:"extraData" gencodec:"required"`
|
||||
MixDigest core.Hash `json:"mixHash"`
|
||||
Nonce BlockNonce `json:"nonce"`
|
||||
|
||||
// BaseFee was added by EIP-1559 and is ignored in legacy headers.
|
||||
BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
|
||||
}
|
||||
|
||||
// field type overrides for gencodec
|
||||
type headerMarshaling struct {
|
||||
Difficulty *hexutil.Big
|
||||
Number *hexutil.Big
|
||||
GasLimit hexutil.Uint64
|
||||
GasUsed hexutil.Uint64
|
||||
Time hexutil.Uint64
|
||||
Extra hexutil.Bytes
|
||||
BaseFee *hexutil.Big
|
||||
Hash core.Hash `json:"hash"` // adds call to Hash() in MarshalJSON
|
||||
}
|
||||
|
||||
// Hash returns the block hash of the header, which is simply the keccak256 hash of its
|
||||
// RLP encoding.
|
||||
func (h *Header) Hash() core.Hash {
|
||||
return rlpHash(h)
|
||||
}
|
||||
|
||||
var headerSize = float64(reflect.TypeOf(Header{}).Size())
|
||||
|
||||
// Size returns the approximate memory used by all internal contents. It is used
|
||||
// to approximate and limit the memory consumption of various caches.
|
||||
func (h *Header) Size() float64 {
|
||||
return headerSize + float64(len(h.Extra)+(h.Difficulty.BitLen()+h.Number.BitLen())/8)
|
||||
}
|
||||
|
||||
// SanityCheck checks a few basic things -- these checks are way beyond what
|
||||
// any 'sane' production values should hold, and can mainly be used to prevent
|
||||
// that the unbounded fields are stuffed with junk data to add processing
|
||||
// overhead
|
||||
func (h *Header) SanityCheck() error {
|
||||
if h.Number != nil && !h.Number.IsUint64() {
|
||||
return fmt.Errorf("too large block number: bitlen %d", h.Number.BitLen())
|
||||
}
|
||||
if h.Difficulty != nil {
|
||||
if diffLen := h.Difficulty.BitLen(); diffLen > 80 {
|
||||
return fmt.Errorf("too large block difficulty: bitlen %d", diffLen)
|
||||
}
|
||||
}
|
||||
if eLen := len(h.Extra); eLen > 100*1024 {
|
||||
return fmt.Errorf("too large block extradata: size %d", eLen)
|
||||
}
|
||||
if h.BaseFee != nil {
|
||||
if bfLen := h.BaseFee.BitLen(); bfLen > 256 {
|
||||
return fmt.Errorf("too large base fee: bitlen %d", bfLen)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmptyBody returns true if there is no additional 'body' to complete the header
|
||||
// that is: no transactions and no uncles.
|
||||
func (h *Header) EmptyBody() bool {
|
||||
return h.TxHash == EmptyRootHash && h.UncleHash == EmptyUncleHash
|
||||
}
|
||||
|
||||
// EmptyReceipts returns true if there are no receipts for this header/block.
|
||||
func (h *Header) EmptyReceipts() bool {
|
||||
return h.ReceiptHash == EmptyRootHash
|
||||
}
|
||||
|
||||
// Body is a simple (mutable, non-safe) data container for storing and moving
|
||||
// a block's data contents (transactions and uncles) together.
|
||||
type Body struct {
|
||||
Transactions []*Transaction
|
||||
Uncles []*Header
|
||||
}
|
||||
|
||||
// Block represents an entire block in the Ethereum blockchain.
|
||||
type Block struct {
|
||||
header *Header
|
||||
uncles []*Header
|
||||
transactions Transactions
|
||||
|
||||
// caches
|
||||
hash atomic.Value
|
||||
size atomic.Value
|
||||
|
||||
// Td is used by package core to store the total difficulty
|
||||
// of the chain up to and including the block.
|
||||
td *big.Int
|
||||
|
||||
// These fields are used by package eth to track
|
||||
// inter-peer block relay.
|
||||
ReceivedAt time.Time
|
||||
ReceivedFrom interface{}
|
||||
}
|
||||
|
||||
// "external" block encoding. used for eth protocol, etc.
|
||||
type extblock struct {
|
||||
Header *Header
|
||||
Txs []*Transaction
|
||||
Uncles []*Header
|
||||
}
|
||||
|
||||
// NewBlock creates a new block. The input data is copied,
|
||||
// changes to header and to the field values will not affect the
|
||||
// block.
|
||||
//
|
||||
// The values of TxHash, UncleHash, ReceiptHash and Bloom in header
|
||||
// are ignored and set to values derived from the given txs, uncles
|
||||
// and receipts.
|
||||
func NewBlock(header *Header, txs []*Transaction, uncles []*Header, receipts []*Receipt, hasher TrieHasher) *Block {
|
||||
b := &Block{header: CopyHeader(header), td: new(big.Int)}
|
||||
|
||||
// TODO: panic if len(txs) != len(receipts)
|
||||
if len(txs) == 0 {
|
||||
b.header.TxHash = EmptyRootHash
|
||||
} else {
|
||||
b.header.TxHash = DeriveSha(Transactions(txs), hasher)
|
||||
b.transactions = make(Transactions, len(txs))
|
||||
copy(b.transactions, txs)
|
||||
}
|
||||
|
||||
if len(receipts) == 0 {
|
||||
b.header.ReceiptHash = EmptyRootHash
|
||||
} else {
|
||||
b.header.ReceiptHash = DeriveSha(Receipts(receipts), hasher)
|
||||
b.header.Bloom = CreateBloom(receipts)
|
||||
}
|
||||
|
||||
if len(uncles) == 0 {
|
||||
b.header.UncleHash = EmptyUncleHash
|
||||
} else {
|
||||
b.header.UncleHash = CalcUncleHash(uncles)
|
||||
b.uncles = make([]*Header, len(uncles))
|
||||
for i := range uncles {
|
||||
b.uncles[i] = CopyHeader(uncles[i])
|
||||
}
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// NewBlockWithHeader creates a block with the given header data. The
|
||||
// header data is copied, changes to header and to the field values
|
||||
// will not affect the block.
|
||||
func NewBlockWithHeader(header *Header) *Block {
|
||||
return &Block{header: CopyHeader(header)}
|
||||
}
|
||||
|
||||
// CopyHeader creates a deep copy of a block header to prevent side effects from
|
||||
// modifying a header variable.
|
||||
func CopyHeader(h *Header) *Header {
|
||||
cpy := *h
|
||||
if cpy.Difficulty = new(big.Int); h.Difficulty != nil {
|
||||
cpy.Difficulty.Set(h.Difficulty)
|
||||
}
|
||||
if cpy.Number = new(big.Int); h.Number != nil {
|
||||
cpy.Number.Set(h.Number)
|
||||
}
|
||||
if h.BaseFee != nil {
|
||||
cpy.BaseFee = new(big.Int).Set(h.BaseFee)
|
||||
}
|
||||
if len(h.Extra) > 0 {
|
||||
cpy.Extra = make([]byte, len(h.Extra))
|
||||
copy(cpy.Extra, h.Extra)
|
||||
}
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// DecodeRLP decodes the Ethereum
|
||||
func (b *Block) DecodeRLP(s *rlp.Stream) error {
|
||||
var eb extblock
|
||||
_, size, _ := s.Kind()
|
||||
if err := s.Decode(&eb); err != nil {
|
||||
return err
|
||||
}
|
||||
b.header, b.uncles, b.transactions = eb.Header, eb.Uncles, eb.Txs
|
||||
b.size.Store(float64(rlp.ListSize(size)))
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeRLP serializes b into the Ethereum RLP block format.
|
||||
func (b *Block) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, extblock{
|
||||
Header: b.header,
|
||||
Txs: b.transactions,
|
||||
Uncles: b.uncles,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: copies
|
||||
|
||||
func (b *Block) Uncles() []*Header { return b.uncles }
|
||||
func (b *Block) Transactions() Transactions { return b.transactions }
|
||||
|
||||
func (b *Block) Transaction(hash core.Hash) *Transaction {
|
||||
for _, transaction := range b.transactions {
|
||||
if transaction.Hash() == hash {
|
||||
return transaction
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Block) Number() *big.Int { return new(big.Int).Set(b.header.Number) }
|
||||
func (b *Block) GasLimit() uint64 { return b.header.GasLimit }
|
||||
func (b *Block) GasUsed() uint64 { return b.header.GasUsed }
|
||||
func (b *Block) Difficulty() *big.Int { return new(big.Int).Set(b.header.Difficulty) }
|
||||
func (b *Block) Time() uint64 { return b.header.Time }
|
||||
|
||||
func (b *Block) NumberU64() uint64 { return b.header.Number.Uint64() }
|
||||
func (b *Block) MixDigest() core.Hash { return b.header.MixDigest }
|
||||
func (b *Block) Nonce() uint64 { return binary.BigEndian.Uint64(b.header.Nonce[:]) }
|
||||
func (b *Block) Bloom() Bloom { return b.header.Bloom }
|
||||
func (b *Block) Coinbase() core.Address { return b.header.Coinbase }
|
||||
func (b *Block) Root() core.Hash { return b.header.Root }
|
||||
func (b *Block) ParentHash() core.Hash { return b.header.ParentHash }
|
||||
func (b *Block) TxHash() core.Hash { return b.header.TxHash }
|
||||
func (b *Block) ReceiptHash() core.Hash { return b.header.ReceiptHash }
|
||||
func (b *Block) UncleHash() core.Hash { return b.header.UncleHash }
|
||||
func (b *Block) Extra() []byte { return core.CopyBytes(b.header.Extra) }
|
||||
|
||||
func (b *Block) BaseFee() *big.Int {
|
||||
if b.header.BaseFee == nil {
|
||||
return nil
|
||||
}
|
||||
return new(big.Int).Set(b.header.BaseFee)
|
||||
}
|
||||
|
||||
func (b *Block) Header() *Header { return CopyHeader(b.header) }
|
||||
|
||||
// Body returns the non-header content of the block.
|
||||
func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles} }
|
||||
|
||||
// Size returns the true RLP encoded storage size of the block, either by encoding
|
||||
// and returning it, or returning a previsouly cached value.
|
||||
func (b *Block) Size() float64 {
|
||||
if size := b.size.Load(); size != nil {
|
||||
return size.(float64)
|
||||
}
|
||||
c := writeCounter(0)
|
||||
rlp.Encode(&c, b)
|
||||
b.size.Store(float64(c))
|
||||
return float64(c)
|
||||
}
|
||||
|
||||
// SanityCheck can be used to prevent that unbounded fields are
|
||||
// stuffed with junk data to add processing overhead
|
||||
func (b *Block) SanityCheck() error {
|
||||
return b.header.SanityCheck()
|
||||
}
|
||||
|
||||
type writeCounter float64
|
||||
|
||||
func (c *writeCounter) Write(b []byte) (int, error) {
|
||||
*c += writeCounter(len(b))
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
func CalcUncleHash(uncles []*Header) core.Hash {
|
||||
if len(uncles) == 0 {
|
||||
return EmptyUncleHash
|
||||
}
|
||||
return rlpHash(uncles)
|
||||
}
|
||||
|
||||
// WithSeal returns a new block with the data from b but the header replaced with
|
||||
// the sealed one.
|
||||
func (b *Block) WithSeal(header *Header) *Block {
|
||||
cpy := *header
|
||||
|
||||
return &Block{
|
||||
header: &cpy,
|
||||
transactions: b.transactions,
|
||||
uncles: b.uncles,
|
||||
}
|
||||
}
|
||||
|
||||
// WithBody returns a new block with the given transaction and uncle contents.
|
||||
func (b *Block) WithBody(transactions []*Transaction, uncles []*Header) *Block {
|
||||
block := &Block{
|
||||
header: CopyHeader(b.header),
|
||||
transactions: make([]*Transaction, len(transactions)),
|
||||
uncles: make([]*Header, len(uncles)),
|
||||
}
|
||||
copy(block.transactions, transactions)
|
||||
for i := range uncles {
|
||||
block.uncles[i] = CopyHeader(uncles[i])
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
// Hash returns the keccak256 hash of b's header.
|
||||
// The hash is computed on the first call and cached thereafter.
|
||||
func (b *Block) Hash() core.Hash {
|
||||
if hash := b.hash.Load(); hash != nil {
|
||||
return hash.(core.Hash)
|
||||
}
|
||||
v := b.header.Hash()
|
||||
b.hash.Store(v)
|
||||
return v
|
||||
}
|
||||
|
||||
type Blocks []*Block
|
160
restricted/types/bloom9.go
Normal file
160
restricted/types/bloom9.go
Normal file
@ -0,0 +1,160 @@
|
||||
// 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 (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/hexutil"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/crypto"
|
||||
)
|
||||
|
||||
type bytesBacked interface {
|
||||
Bytes() []byte
|
||||
}
|
||||
|
||||
const (
|
||||
// BloomByteLength represents the number of bytes used in a header log bloom.
|
||||
BloomByteLength = 256
|
||||
|
||||
// BloomBitLength represents the number of bits used in a header log bloom.
|
||||
BloomBitLength = 8 * BloomByteLength
|
||||
)
|
||||
|
||||
// Bloom represents a 2048 bit bloom filter.
|
||||
type Bloom [BloomByteLength]byte
|
||||
|
||||
// BytesToBloom converts a byte slice to a bloom filter.
|
||||
// It panics if b is not of suitable size.
|
||||
func BytesToBloom(b []byte) Bloom {
|
||||
var bloom Bloom
|
||||
bloom.SetBytes(b)
|
||||
return bloom
|
||||
}
|
||||
|
||||
// SetBytes sets the content of b to the given bytes.
|
||||
// It panics if d is not of suitable size.
|
||||
func (b *Bloom) SetBytes(d []byte) {
|
||||
if len(b) < len(d) {
|
||||
panic(fmt.Sprintf("bloom bytes too big %d %d", len(b), len(d)))
|
||||
}
|
||||
copy(b[BloomByteLength-len(d):], d)
|
||||
}
|
||||
|
||||
// Add adds d to the filter. Future calls of Test(d) will return true.
|
||||
func (b *Bloom) Add(d []byte) {
|
||||
b.add(d, make([]byte, 6))
|
||||
}
|
||||
|
||||
// add is internal version of Add, which takes a scratch buffer for reuse (needs to be at least 6 bytes)
|
||||
func (b *Bloom) add(d []byte, buf []byte) {
|
||||
i1, v1, i2, v2, i3, v3 := bloomValues(d, buf)
|
||||
b[i1] |= v1
|
||||
b[i2] |= v2
|
||||
b[i3] |= v3
|
||||
}
|
||||
|
||||
// Big converts b to a big integer.
|
||||
// Note: Converting a bloom filter to a big.Int and then calling GetBytes
|
||||
// does not return the same bytes, since big.Int will trim leading zeroes
|
||||
func (b Bloom) Big() *big.Int {
|
||||
return new(big.Int).SetBytes(b[:])
|
||||
}
|
||||
|
||||
// Bytes returns the backing byte slice of the bloom
|
||||
func (b Bloom) Bytes() []byte {
|
||||
return b[:]
|
||||
}
|
||||
|
||||
// Test checks if the given topic is present in the bloom filter
|
||||
func (b Bloom) Test(topic []byte) bool {
|
||||
i1, v1, i2, v2, i3, v3 := bloomValues(topic, make([]byte, 6))
|
||||
return v1 == v1&b[i1] &&
|
||||
v2 == v2&b[i2] &&
|
||||
v3 == v3&b[i3]
|
||||
}
|
||||
|
||||
// MarshalText encodes b as a hex string with 0x prefix.
|
||||
func (b Bloom) MarshalText() ([]byte, error) {
|
||||
return hexutil.Bytes(b[:]).MarshalText()
|
||||
}
|
||||
|
||||
// UnmarshalText b as a hex string with 0x prefix.
|
||||
func (b *Bloom) UnmarshalText(input []byte) error {
|
||||
return hexutil.UnmarshalFixedText("Bloom", input, b[:])
|
||||
}
|
||||
|
||||
// CreateBloom creates a bloom filter out of the give Receipts (+Logs)
|
||||
func CreateBloom(receipts Receipts) Bloom {
|
||||
buf := make([]byte, 6)
|
||||
var bin Bloom
|
||||
for _, receipt := range receipts {
|
||||
for _, log := range receipt.Logs {
|
||||
bin.add(log.Address[:], buf)
|
||||
for _, b := range log.Topics {
|
||||
bin.add(b[:], buf)
|
||||
}
|
||||
}
|
||||
}
|
||||
return bin
|
||||
}
|
||||
|
||||
// LogsBloom returns the bloom bytes for the given logs
|
||||
func LogsBloom(logs []*Log) []byte {
|
||||
buf := make([]byte, 6)
|
||||
var bin Bloom
|
||||
for _, log := range logs {
|
||||
bin.add(log.Address[:], buf)
|
||||
for _, b := range log.Topics {
|
||||
bin.add(b[:], buf)
|
||||
}
|
||||
}
|
||||
return bin[:]
|
||||
}
|
||||
|
||||
// Bloom9 returns the bloom filter for the given data
|
||||
func Bloom9(data []byte) []byte {
|
||||
var b Bloom
|
||||
b.SetBytes(data)
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
// bloomValues returns the bytes (index-value pairs) to set for the given data
|
||||
func bloomValues(data []byte, hashbuf []byte) (uint, byte, uint, byte, uint, byte) {
|
||||
sha := hasherPool.Get().(crypto.KeccakState)
|
||||
sha.Reset()
|
||||
sha.Write(data)
|
||||
sha.Read(hashbuf)
|
||||
hasherPool.Put(sha)
|
||||
// The actual bits to flip
|
||||
v1 := byte(1 << (hashbuf[1] & 0x7))
|
||||
v2 := byte(1 << (hashbuf[3] & 0x7))
|
||||
v3 := byte(1 << (hashbuf[5] & 0x7))
|
||||
// The indices for the bytes to OR in
|
||||
i1 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf)&0x7ff)>>3) - 1
|
||||
i2 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[2:])&0x7ff)>>3) - 1
|
||||
i3 := BloomByteLength - uint((binary.BigEndian.Uint16(hashbuf[4:])&0x7ff)>>3) - 1
|
||||
|
||||
return i1, v1, i2, v2, i3, v3
|
||||
}
|
||||
|
||||
// BloomLookup is a convenience-method to check presence int he bloom filter
|
||||
func BloomLookup(bin Bloom, topic bytesBacked) bool {
|
||||
return bin.Test(topic.Bytes())
|
||||
}
|
104
restricted/types/dynamic_fee_tx.go
Normal file
104
restricted/types/dynamic_fee_tx.go
Normal file
@ -0,0 +1,104 @@
|
||||
// Copyright 2021 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/openrelayxyz/plugeth-utils/core"
|
||||
)
|
||||
|
||||
type DynamicFeeTx struct {
|
||||
ChainID *big.Int
|
||||
Nonce uint64
|
||||
GasTipCap *big.Int
|
||||
GasFeeCap *big.Int
|
||||
Gas uint64
|
||||
To *core.Address `rlp:"nil"` // nil means contract creation
|
||||
Value *big.Int
|
||||
Data []byte
|
||||
AccessList AccessList
|
||||
|
||||
// Signature values
|
||||
V *big.Int `json:"v" gencodec:"required"`
|
||||
R *big.Int `json:"r" gencodec:"required"`
|
||||
S *big.Int `json:"s" gencodec:"required"`
|
||||
}
|
||||
|
||||
// copy creates a deep copy of the transaction data and initializes all fields.
|
||||
func (tx *DynamicFeeTx) copy() TxData {
|
||||
cpy := &DynamicFeeTx{
|
||||
Nonce: tx.Nonce,
|
||||
To: tx.To, // TODO: copy pointed-to address
|
||||
Data: core.CopyBytes(tx.Data),
|
||||
Gas: tx.Gas,
|
||||
// These are copied below.
|
||||
AccessList: make(AccessList, len(tx.AccessList)),
|
||||
Value: new(big.Int),
|
||||
ChainID: new(big.Int),
|
||||
GasTipCap: new(big.Int),
|
||||
GasFeeCap: 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.GasTipCap != nil {
|
||||
cpy.GasTipCap.Set(tx.GasTipCap)
|
||||
}
|
||||
if tx.GasFeeCap != nil {
|
||||
cpy.GasFeeCap.Set(tx.GasFeeCap)
|
||||
}
|
||||
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 *DynamicFeeTx) txType() byte { return DynamicFeeTxType }
|
||||
func (tx *DynamicFeeTx) chainID() *big.Int { return tx.ChainID }
|
||||
func (tx *DynamicFeeTx) protected() bool { return true }
|
||||
func (tx *DynamicFeeTx) accessList() AccessList { return tx.AccessList }
|
||||
func (tx *DynamicFeeTx) data() []byte { return tx.Data }
|
||||
func (tx *DynamicFeeTx) gas() uint64 { return tx.Gas }
|
||||
func (tx *DynamicFeeTx) gasFeeCap() *big.Int { return tx.GasFeeCap }
|
||||
func (tx *DynamicFeeTx) gasTipCap() *big.Int { return tx.GasTipCap }
|
||||
func (tx *DynamicFeeTx) gasPrice() *big.Int { return tx.GasFeeCap }
|
||||
func (tx *DynamicFeeTx) value() *big.Int { return tx.Value }
|
||||
func (tx *DynamicFeeTx) nonce() uint64 { return tx.Nonce }
|
||||
func (tx *DynamicFeeTx) to() *core.Address { return tx.To }
|
||||
|
||||
func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) {
|
||||
return tx.V, tx.R, tx.S
|
||||
}
|
||||
|
||||
func (tx *DynamicFeeTx) setSignatureValues(chainID, v, r, s *big.Int) {
|
||||
tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
|
||||
}
|
43
restricted/types/gen_access_tuple.go
Normal file
43
restricted/types/gen_access_tuple.go
Normal file
@ -0,0 +1,43 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (a AccessTuple) MarshalJSON() ([]byte, error) {
|
||||
type AccessTuple struct {
|
||||
Address core.Address `json:"address" gencodec:"required"`
|
||||
StorageKeys []core.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 *core.Address `json:"address" gencodec:"required"`
|
||||
StorageKeys []core.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
|
||||
}
|
144
restricted/types/gen_header_json.go
Normal file
144
restricted/types/gen_header_json.go
Normal file
@ -0,0 +1,144 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*headerMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (h Header) MarshalJSON() ([]byte, error) {
|
||||
type Header struct {
|
||||
ParentHash core.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash core.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
Coinbase core.Address `json:"miner" gencodec:"required"`
|
||||
Root core.Hash `json:"stateRoot" gencodec:"required"`
|
||||
TxHash core.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||
ReceiptHash core.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
Bloom Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
||||
Number *hexutil.Big `json:"number" gencodec:"required"`
|
||||
GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Time hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Extra hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
MixDigest core.Hash `json:"mixHash"`
|
||||
Nonce BlockNonce `json:"nonce"`
|
||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||
Hash core.Hash `json:"hash"`
|
||||
}
|
||||
var enc Header
|
||||
enc.ParentHash = h.ParentHash
|
||||
enc.UncleHash = h.UncleHash
|
||||
enc.Coinbase = h.Coinbase
|
||||
enc.Root = h.Root
|
||||
enc.TxHash = h.TxHash
|
||||
enc.ReceiptHash = h.ReceiptHash
|
||||
enc.Bloom = h.Bloom
|
||||
enc.Difficulty = (*hexutil.Big)(h.Difficulty)
|
||||
enc.Number = (*hexutil.Big)(h.Number)
|
||||
enc.GasLimit = hexutil.Uint64(h.GasLimit)
|
||||
enc.GasUsed = hexutil.Uint64(h.GasUsed)
|
||||
enc.Time = hexutil.Uint64(h.Time)
|
||||
enc.Extra = h.Extra
|
||||
enc.MixDigest = h.MixDigest
|
||||
enc.Nonce = h.Nonce
|
||||
enc.BaseFee = (*hexutil.Big)(h.BaseFee)
|
||||
enc.Hash = h.Hash()
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (h *Header) UnmarshalJSON(input []byte) error {
|
||||
type Header struct {
|
||||
ParentHash *core.Hash `json:"parentHash" gencodec:"required"`
|
||||
UncleHash *core.Hash `json:"sha3Uncles" gencodec:"required"`
|
||||
Coinbase *core.Address `json:"miner" gencodec:"required"`
|
||||
Root *core.Hash `json:"stateRoot" gencodec:"required"`
|
||||
TxHash *core.Hash `json:"transactionsRoot" gencodec:"required"`
|
||||
ReceiptHash *core.Hash `json:"receiptsRoot" gencodec:"required"`
|
||||
Bloom *Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"`
|
||||
Number *hexutil.Big `json:"number" gencodec:"required"`
|
||||
GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"`
|
||||
Extra *hexutil.Bytes `json:"extraData" gencodec:"required"`
|
||||
MixDigest *core.Hash `json:"mixHash"`
|
||||
Nonce *BlockNonce `json:"nonce"`
|
||||
BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"`
|
||||
}
|
||||
var dec Header
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.ParentHash == nil {
|
||||
return errors.New("missing required field 'parentHash' for Header")
|
||||
}
|
||||
h.ParentHash = *dec.ParentHash
|
||||
if dec.UncleHash == nil {
|
||||
return errors.New("missing required field 'sha3Uncles' for Header")
|
||||
}
|
||||
h.UncleHash = *dec.UncleHash
|
||||
if dec.Coinbase == nil {
|
||||
return errors.New("missing required field 'miner' for Header")
|
||||
}
|
||||
h.Coinbase = *dec.Coinbase
|
||||
if dec.Root == nil {
|
||||
return errors.New("missing required field 'stateRoot' for Header")
|
||||
}
|
||||
h.Root = *dec.Root
|
||||
if dec.TxHash == nil {
|
||||
return errors.New("missing required field 'transactionsRoot' for Header")
|
||||
}
|
||||
h.TxHash = *dec.TxHash
|
||||
if dec.ReceiptHash == nil {
|
||||
return errors.New("missing required field 'receiptsRoot' for Header")
|
||||
}
|
||||
h.ReceiptHash = *dec.ReceiptHash
|
||||
if dec.Bloom == nil {
|
||||
return errors.New("missing required field 'logsBloom' for Header")
|
||||
}
|
||||
h.Bloom = *dec.Bloom
|
||||
if dec.Difficulty == nil {
|
||||
return errors.New("missing required field 'difficulty' for Header")
|
||||
}
|
||||
h.Difficulty = (*big.Int)(dec.Difficulty)
|
||||
if dec.Number == nil {
|
||||
return errors.New("missing required field 'number' for Header")
|
||||
}
|
||||
h.Number = (*big.Int)(dec.Number)
|
||||
if dec.GasLimit == nil {
|
||||
return errors.New("missing required field 'gasLimit' for Header")
|
||||
}
|
||||
h.GasLimit = uint64(*dec.GasLimit)
|
||||
if dec.GasUsed == nil {
|
||||
return errors.New("missing required field 'gasUsed' for Header")
|
||||
}
|
||||
h.GasUsed = uint64(*dec.GasUsed)
|
||||
if dec.Time == nil {
|
||||
return errors.New("missing required field 'timestamp' for Header")
|
||||
}
|
||||
h.Time = uint64(*dec.Time)
|
||||
if dec.Extra == nil {
|
||||
return errors.New("missing required field 'extraData' for Header")
|
||||
}
|
||||
h.Extra = *dec.Extra
|
||||
if dec.MixDigest != nil {
|
||||
h.MixDigest = *dec.MixDigest
|
||||
}
|
||||
if dec.Nonce != nil {
|
||||
h.Nonce = *dec.Nonce
|
||||
}
|
||||
if dec.BaseFee != nil {
|
||||
h.BaseFee = (*big.Int)(dec.BaseFee)
|
||||
}
|
||||
return nil
|
||||
}
|
90
restricted/types/gen_log_json.go
Normal file
90
restricted/types/gen_log_json.go
Normal file
@ -0,0 +1,90 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*logMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (l Log) MarshalJSON() ([]byte, error) {
|
||||
type Log struct {
|
||||
Address core.Address `json:"address" gencodec:"required"`
|
||||
Topics []core.Hash `json:"topics" gencodec:"required"`
|
||||
Data hexutil.Bytes `json:"data" gencodec:"required"`
|
||||
BlockNumber hexutil.Uint64 `json:"blockNumber" rlp:"-"`
|
||||
TxHash core.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
|
||||
TxIndex hexutil.Uint `json:"transactionIndex" rlp:"-"`
|
||||
BlockHash core.Hash `json:"blockHash" rlp:"-"`
|
||||
Index hexutil.Uint `json:"logIndex" rlp:"-"`
|
||||
Removed bool `json:"removed" rlp:"-"`
|
||||
}
|
||||
var enc Log
|
||||
enc.Address = l.Address
|
||||
enc.Topics = l.Topics
|
||||
enc.Data = l.Data
|
||||
enc.BlockNumber = hexutil.Uint64(l.BlockNumber)
|
||||
enc.TxHash = l.TxHash
|
||||
enc.TxIndex = hexutil.Uint(l.TxIndex)
|
||||
enc.BlockHash = l.BlockHash
|
||||
enc.Index = hexutil.Uint(l.Index)
|
||||
enc.Removed = l.Removed
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (l *Log) UnmarshalJSON(input []byte) error {
|
||||
type Log struct {
|
||||
Address *core.Address `json:"address" gencodec:"required"`
|
||||
Topics []core.Hash `json:"topics" gencodec:"required"`
|
||||
Data *hexutil.Bytes `json:"data" gencodec:"required"`
|
||||
BlockNumber *hexutil.Uint64 `json:"blockNumber" rlp:"-"`
|
||||
TxHash *core.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
|
||||
TxIndex *hexutil.Uint `json:"transactionIndex" rlp:"-"`
|
||||
BlockHash *core.Hash `json:"blockHash" rlp:"-"`
|
||||
Index *hexutil.Uint `json:"logIndex" rlp:"-"`
|
||||
Removed *bool `json:"removed" rlp:"-"`
|
||||
}
|
||||
var dec Log
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.Address == nil {
|
||||
return errors.New("missing required field 'address' for Log")
|
||||
}
|
||||
l.Address = *dec.Address
|
||||
if dec.Topics == nil {
|
||||
return errors.New("missing required field 'topics' for Log")
|
||||
}
|
||||
l.Topics = dec.Topics
|
||||
if dec.Data == nil {
|
||||
return errors.New("missing required field 'data' for Log")
|
||||
}
|
||||
l.Data = *dec.Data
|
||||
if dec.BlockNumber != nil {
|
||||
l.BlockNumber = uint64(*dec.BlockNumber)
|
||||
}
|
||||
if dec.TxHash == nil {
|
||||
return errors.New("missing required field 'transactionHash' for Log")
|
||||
}
|
||||
l.TxHash = *dec.TxHash
|
||||
if dec.TxIndex != nil {
|
||||
l.TxIndex = uint(*dec.TxIndex)
|
||||
}
|
||||
if dec.BlockHash != nil {
|
||||
l.BlockHash = *dec.BlockHash
|
||||
}
|
||||
if dec.Index != nil {
|
||||
l.Index = uint(*dec.Index)
|
||||
}
|
||||
if dec.Removed != nil {
|
||||
l.Removed = *dec.Removed
|
||||
}
|
||||
return nil
|
||||
}
|
110
restricted/types/gen_receipt_json.go
Normal file
110
restricted/types/gen_receipt_json.go
Normal file
@ -0,0 +1,110 @@
|
||||
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/hexutil"
|
||||
)
|
||||
|
||||
var _ = (*receiptMarshaling)(nil)
|
||||
|
||||
// MarshalJSON marshals as JSON.
|
||||
func (r Receipt) MarshalJSON() ([]byte, error) {
|
||||
type Receipt struct {
|
||||
Type hexutil.Uint64 `json:"type,omitempty"`
|
||||
PostState hexutil.Bytes `json:"root"`
|
||||
Status hexutil.Uint64 `json:"status"`
|
||||
CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"`
|
||||
Bloom Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Logs []*Log `json:"logs" gencodec:"required"`
|
||||
TxHash core.Hash `json:"transactionHash" gencodec:"required"`
|
||||
ContractAddress core.Address `json:"contractAddress"`
|
||||
GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
BlockHash core.Hash `json:"blockHash,omitempty"`
|
||||
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
|
||||
TransactionIndex hexutil.Uint `json:"transactionIndex"`
|
||||
}
|
||||
var enc Receipt
|
||||
enc.Type = hexutil.Uint64(r.Type)
|
||||
enc.PostState = r.PostState
|
||||
enc.Status = hexutil.Uint64(r.Status)
|
||||
enc.CumulativeGasUsed = hexutil.Uint64(r.CumulativeGasUsed)
|
||||
enc.Bloom = r.Bloom
|
||||
enc.Logs = r.Logs
|
||||
enc.TxHash = r.TxHash
|
||||
enc.ContractAddress = r.ContractAddress
|
||||
enc.GasUsed = hexutil.Uint64(r.GasUsed)
|
||||
enc.BlockHash = r.BlockHash
|
||||
enc.BlockNumber = (*hexutil.Big)(r.BlockNumber)
|
||||
enc.TransactionIndex = hexutil.Uint(r.TransactionIndex)
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from JSON.
|
||||
func (r *Receipt) UnmarshalJSON(input []byte) error {
|
||||
type Receipt struct {
|
||||
Type *hexutil.Uint64 `json:"type,omitempty"`
|
||||
PostState *hexutil.Bytes `json:"root"`
|
||||
Status *hexutil.Uint64 `json:"status"`
|
||||
CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"`
|
||||
Bloom *Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Logs []*Log `json:"logs" gencodec:"required"`
|
||||
TxHash *core.Hash `json:"transactionHash" gencodec:"required"`
|
||||
ContractAddress *core.Address `json:"contractAddress"`
|
||||
GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"`
|
||||
BlockHash *core.Hash `json:"blockHash,omitempty"`
|
||||
BlockNumber *hexutil.Big `json:"blockNumber,omitempty"`
|
||||
TransactionIndex *hexutil.Uint `json:"transactionIndex"`
|
||||
}
|
||||
var dec Receipt
|
||||
if err := json.Unmarshal(input, &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
if dec.Type != nil {
|
||||
r.Type = uint8(*dec.Type)
|
||||
}
|
||||
if dec.PostState != nil {
|
||||
r.PostState = *dec.PostState
|
||||
}
|
||||
if dec.Status != nil {
|
||||
r.Status = uint64(*dec.Status)
|
||||
}
|
||||
if dec.CumulativeGasUsed == nil {
|
||||
return errors.New("missing required field 'cumulativeGasUsed' for Receipt")
|
||||
}
|
||||
r.CumulativeGasUsed = uint64(*dec.CumulativeGasUsed)
|
||||
if dec.Bloom == nil {
|
||||
return errors.New("missing required field 'logsBloom' for Receipt")
|
||||
}
|
||||
r.Bloom = *dec.Bloom
|
||||
if dec.Logs == nil {
|
||||
return errors.New("missing required field 'logs' for Receipt")
|
||||
}
|
||||
r.Logs = dec.Logs
|
||||
if dec.TxHash == nil {
|
||||
return errors.New("missing required field 'transactionHash' for Receipt")
|
||||
}
|
||||
r.TxHash = *dec.TxHash
|
||||
if dec.ContractAddress != nil {
|
||||
r.ContractAddress = *dec.ContractAddress
|
||||
}
|
||||
if dec.GasUsed == nil {
|
||||
return errors.New("missing required field 'gasUsed' for Receipt")
|
||||
}
|
||||
r.GasUsed = uint64(*dec.GasUsed)
|
||||
if dec.BlockHash != nil {
|
||||
r.BlockHash = *dec.BlockHash
|
||||
}
|
||||
if dec.BlockNumber != nil {
|
||||
r.BlockNumber = (*big.Int)(dec.BlockNumber)
|
||||
}
|
||||
if dec.TransactionIndex != nil {
|
||||
r.TransactionIndex = uint(*dec.TransactionIndex)
|
||||
}
|
||||
return nil
|
||||
}
|
113
restricted/types/hashing.go
Normal file
113
restricted/types/hashing.go
Normal file
@ -0,0 +1,113 @@
|
||||
// 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/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/crypto"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/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) },
|
||||
}
|
||||
|
||||
// rlpHash encodes x and hashes the encoded bytes.
|
||||
func rlpHash(x interface{}) (h core.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 x.
|
||||
// It's used for typed transactions.
|
||||
func prefixedRlpHash(prefix byte, x interface{}) (h core.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() core.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 core.CopyBytes(buf.Bytes())
|
||||
}
|
||||
|
||||
// DeriveSha creates the tree hashes of transactions and receipts in a block header.
|
||||
func DeriveSha(list DerivableList, hasher TrieHasher) core.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()
|
||||
}
|
146
restricted/types/hashing_test.go
Normal file
146
restricted/types/hashing_test.go
Normal file
@ -0,0 +1,146 @@
|
||||
// Copyright 2021 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_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
mrand "math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/hexutil"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/types"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/crypto"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/rlp"
|
||||
)
|
||||
|
||||
func fromHex(data string) []byte {
|
||||
d, _ := hex.DecodeString(data)
|
||||
return d
|
||||
}
|
||||
|
||||
// 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: "b8a701f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9",
|
||||
exp: "01 01f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9\n80 01f8a486796f6c6f763380843b9aca008262d4948a8eafb1cf62bfbeb1741769dae1a9dd479961928080f838f7940000000000000000000000000000000000001337e1a0000000000000000000000000000000000000000000000000000000000000000080a0775101f92dcca278a56bfe4d613428624a1ebfc3cd9e0bcc1de80c41455b9021a06c9deac205afe7b124907d4ba54a9f46161498bd3990b90d175aac12c9a40ee9\n",
|
||||
},
|
||||
} {
|
||||
d := &hashToHumanReadable{}
|
||||
var t1, t2 types.Transaction
|
||||
rlp.DecodeBytes(fromHex(tc.rlpData), &t1)
|
||||
rlp.DecodeBytes(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 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() core.Hash {
|
||||
return core.Hash{}
|
||||
}
|
112
restricted/types/legacy_tx.go
Normal file
112
restricted/types/legacy_tx.go
Normal file
@ -0,0 +1,112 @@
|
||||
// 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/openrelayxyz/plugeth-utils/core"
|
||||
)
|
||||
|
||||
// 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 *core.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 core.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: core.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) gasTipCap() *big.Int { return tx.GasPrice }
|
||||
func (tx *LegacyTx) gasFeeCap() *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() *core.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
|
||||
}
|
60
restricted/types/log.go
Normal file
60
restricted/types/log.go
Normal file
@ -0,0 +1,60 @@
|
||||
// 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/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/hexutil"
|
||||
)
|
||||
|
||||
//go:generate gencodec -type Log -field-override logMarshaling -out gen_log_json.go
|
||||
|
||||
// Log represents a contract log event. These events are generated by the LOG opcode and
|
||||
// stored/indexed by the node.
|
||||
type Log struct {
|
||||
// Consensus fields:
|
||||
// address of the contract that generated the event
|
||||
Address core.Address `json:"address" gencodec:"required"`
|
||||
// list of topics provided by the contract.
|
||||
Topics []core.Hash `json:"topics" gencodec:"required"`
|
||||
// supplied by the contract, usually ABI-encoded
|
||||
Data []byte `json:"data" gencodec:"required"`
|
||||
|
||||
// Derived fields. These fields are filled in by the node
|
||||
// but not secured by consensus.
|
||||
// block in which the transaction was included
|
||||
BlockNumber uint64 `json:"blockNumber" rlp:"-"`
|
||||
// hash of the transaction
|
||||
TxHash core.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
|
||||
// index of the transaction in the block
|
||||
TxIndex uint `json:"transactionIndex" rlp:"-"`
|
||||
// hash of the block in which the transaction was included
|
||||
BlockHash core.Hash `json:"blockHash" rlp:"-"`
|
||||
// index of the log in the block
|
||||
Index uint `json:"logIndex" rlp:"-"`
|
||||
|
||||
// The Removed field is true if this log was reverted due to a chain reorganisation.
|
||||
// You must pay attention to this field if you receive logs through a filter query.
|
||||
Removed bool `json:"removed" rlp:"-"`
|
||||
}
|
||||
|
||||
type logMarshaling struct {
|
||||
Data hexutil.Bytes
|
||||
BlockNumber hexutil.Uint64
|
||||
TxIndex hexutil.Uint
|
||||
Index hexutil.Uint
|
||||
}
|
132
restricted/types/log_test.go
Normal file
132
restricted/types/log_test.go
Normal file
@ -0,0 +1,132 @@
|
||||
// Copyright 2016 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/hexutil"
|
||||
)
|
||||
|
||||
var unmarshalLogTests = map[string]struct {
|
||||
input string
|
||||
want *Log
|
||||
wantError error
|
||||
}{
|
||||
"ok": {
|
||||
input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x000000000000000000000000000000000000000000000001a055690d9db80000","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
|
||||
want: &Log{
|
||||
Address: core.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"),
|
||||
BlockHash: core.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"),
|
||||
BlockNumber: 2019236,
|
||||
Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000001a055690d9db80000"),
|
||||
Index: 2,
|
||||
TxIndex: 3,
|
||||
TxHash: core.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"),
|
||||
Topics: []core.Hash{
|
||||
core.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
|
||||
core.HexToHash("0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"empty data": {
|
||||
input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
|
||||
want: &Log{
|
||||
Address: core.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"),
|
||||
BlockHash: core.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"),
|
||||
BlockNumber: 2019236,
|
||||
Data: []byte{},
|
||||
Index: 2,
|
||||
TxIndex: 3,
|
||||
TxHash: core.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"),
|
||||
Topics: []core.Hash{
|
||||
core.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
|
||||
core.HexToHash("0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"missing block fields (pending logs)": {
|
||||
input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","data":"0x","logIndex":"0x0","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
|
||||
want: &Log{
|
||||
Address: core.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"),
|
||||
BlockHash: core.Hash{},
|
||||
BlockNumber: 0,
|
||||
Data: []byte{},
|
||||
Index: 0,
|
||||
TxIndex: 3,
|
||||
TxHash: core.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"),
|
||||
Topics: []core.Hash{
|
||||
core.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"Removed: true": {
|
||||
input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","data":"0x","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3","removed":true}`,
|
||||
want: &Log{
|
||||
Address: core.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"),
|
||||
BlockHash: core.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"),
|
||||
BlockNumber: 2019236,
|
||||
Data: []byte{},
|
||||
Index: 2,
|
||||
TxIndex: 3,
|
||||
TxHash: core.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"),
|
||||
Topics: []core.Hash{
|
||||
core.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
|
||||
},
|
||||
Removed: true,
|
||||
},
|
||||
},
|
||||
"missing data": {
|
||||
input: `{"address":"0xecf8f87f810ecf450940c9f60066b4a7a501d6a7","blockHash":"0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056","blockNumber":"0x1ecfa4","logIndex":"0x2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615","0x000000000000000000000000f9dff387dcb5cc4cca5b91adb07a95f54e9f1bb6"],"transactionHash":"0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e","transactionIndex":"0x3"}`,
|
||||
wantError: fmt.Errorf("missing required field 'data' for Log"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestUnmarshalLog(t *testing.T) {
|
||||
dumper := spew.ConfigState{DisableMethods: true, Indent: " "}
|
||||
for name, test := range unmarshalLogTests {
|
||||
var log *Log
|
||||
err := json.Unmarshal([]byte(test.input), &log)
|
||||
checkError(t, name, err, test.wantError)
|
||||
if test.wantError == nil && err == nil {
|
||||
if !reflect.DeepEqual(log, test.want) {
|
||||
t.Errorf("test %q:\nGOT %sWANT %s", name, dumper.Sdump(log), dumper.Sdump(test.want))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkError(t *testing.T, testname string, got, want error) bool {
|
||||
if got == nil {
|
||||
if want != nil {
|
||||
t.Errorf("test %q: got no error, want %q", testname, want)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if want == nil {
|
||||
t.Errorf("test %q: unexpected error %q", testname, got)
|
||||
} else if got.Error() != want.Error() {
|
||||
t.Errorf("test %q: got error %q, want %q", testname, got, want)
|
||||
}
|
||||
return false
|
||||
}
|
308
restricted/types/receipt.go
Normal file
308
restricted/types/receipt.go
Normal file
@ -0,0 +1,308 @@
|
||||
// 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"unsafe"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/hexutil"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/crypto"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/params"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/rlp"
|
||||
)
|
||||
|
||||
//go:generate gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go
|
||||
|
||||
var (
|
||||
receiptStatusFailedRLP = []byte{}
|
||||
receiptStatusSuccessfulRLP = []byte{0x01}
|
||||
)
|
||||
|
||||
// This error is returned when a typed receipt is decoded, but the string is empty.
|
||||
var errEmptyTypedReceipt = errors.New("empty typed receipt bytes")
|
||||
|
||||
const (
|
||||
// ReceiptStatusFailed is the status code of a transaction if execution failed.
|
||||
ReceiptStatusFailed = uint64(0)
|
||||
|
||||
// ReceiptStatusSuccessful is the status code of a transaction if execution succeeded.
|
||||
ReceiptStatusSuccessful = uint64(1)
|
||||
)
|
||||
|
||||
// Receipt represents the results of a transaction.
|
||||
type Receipt struct {
|
||||
// Consensus fields: These fields are defined by the Yellow Paper
|
||||
Type uint8 `json:"type,omitempty"`
|
||||
PostState []byte `json:"root"`
|
||||
Status uint64 `json:"status"`
|
||||
CumulativeGasUsed uint64 `json:"cumulativeGasUsed" gencodec:"required"`
|
||||
Bloom Bloom `json:"logsBloom" gencodec:"required"`
|
||||
Logs []*Log `json:"logs" gencodec:"required"`
|
||||
|
||||
// Implementation fields: These fields are added by geth when processing a transaction.
|
||||
// They are stored in the chain database.
|
||||
TxHash core.Hash `json:"transactionHash" gencodec:"required"`
|
||||
ContractAddress core.Address `json:"contractAddress"`
|
||||
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
|
||||
|
||||
// Inclusion information: These fields provide information about the inclusion of the
|
||||
// transaction corresponding to this receipt.
|
||||
BlockHash core.Hash `json:"blockHash,omitempty"`
|
||||
BlockNumber *big.Int `json:"blockNumber,omitempty"`
|
||||
TransactionIndex uint `json:"transactionIndex"`
|
||||
}
|
||||
|
||||
type receiptMarshaling struct {
|
||||
Type hexutil.Uint64
|
||||
PostState hexutil.Bytes
|
||||
Status hexutil.Uint64
|
||||
CumulativeGasUsed hexutil.Uint64
|
||||
GasUsed hexutil.Uint64
|
||||
BlockNumber *hexutil.Big
|
||||
TransactionIndex hexutil.Uint
|
||||
}
|
||||
|
||||
// receiptRLP is the consensus encoding of a receipt.
|
||||
type receiptRLP struct {
|
||||
PostStateOrStatus []byte
|
||||
CumulativeGasUsed uint64
|
||||
Bloom Bloom
|
||||
Logs []*Log
|
||||
}
|
||||
|
||||
// storedReceiptRLP is the storage encoding of a receipt.
|
||||
type storedReceiptRLP struct {
|
||||
PostStateOrStatus []byte
|
||||
CumulativeGasUsed uint64
|
||||
Logs []*Log
|
||||
}
|
||||
|
||||
// NewReceipt creates a barebone transaction receipt, copying the init fields.
|
||||
// Deprecated: create receipts using a struct literal instead.
|
||||
func NewReceipt(root []byte, failed bool, cumulativeGasUsed uint64) *Receipt {
|
||||
r := &Receipt{
|
||||
Type: LegacyTxType,
|
||||
PostState: core.CopyBytes(root),
|
||||
CumulativeGasUsed: cumulativeGasUsed,
|
||||
}
|
||||
if failed {
|
||||
r.Status = ReceiptStatusFailed
|
||||
} else {
|
||||
r.Status = ReceiptStatusSuccessful
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder, and flattens the consensus fields of a receipt
|
||||
// into an RLP stream. If no post state is present, byzantium fork is assumed.
|
||||
func (r *Receipt) EncodeRLP(w io.Writer) error {
|
||||
data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs}
|
||||
if r.Type == LegacyTxType {
|
||||
return rlp.Encode(w, data)
|
||||
}
|
||||
buf := encodeBufferPool.Get().(*bytes.Buffer)
|
||||
defer encodeBufferPool.Put(buf)
|
||||
buf.Reset()
|
||||
buf.WriteByte(r.Type)
|
||||
if err := rlp.Encode(buf, data); err != nil {
|
||||
return err
|
||||
}
|
||||
return rlp.Encode(w, buf.Bytes())
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder, and loads the consensus fields of a receipt
|
||||
// from an RLP stream.
|
||||
func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
|
||||
kind, _, err := s.Kind()
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case kind == rlp.List:
|
||||
// It's a legacy receipt.
|
||||
var dec receiptRLP
|
||||
if err := s.Decode(&dec); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Type = LegacyTxType
|
||||
return r.setFromRLP(dec)
|
||||
case kind == rlp.String:
|
||||
// It's an EIP-2718 typed tx receipt.
|
||||
b, err := s.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return errEmptyTypedReceipt
|
||||
}
|
||||
r.Type = b[0]
|
||||
if r.Type == AccessListTxType || r.Type == DynamicFeeTxType {
|
||||
var dec receiptRLP
|
||||
if err := rlp.DecodeBytes(b[1:], &dec); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.setFromRLP(dec)
|
||||
}
|
||||
return ErrTxTypeNotSupported
|
||||
default:
|
||||
return rlp.ErrExpectedList
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Receipt) setFromRLP(data receiptRLP) error {
|
||||
r.CumulativeGasUsed, r.Bloom, r.Logs = data.CumulativeGasUsed, data.Bloom, data.Logs
|
||||
return r.setStatus(data.PostStateOrStatus)
|
||||
}
|
||||
|
||||
func (r *Receipt) setStatus(postStateOrStatus []byte) error {
|
||||
switch {
|
||||
case bytes.Equal(postStateOrStatus, receiptStatusSuccessfulRLP):
|
||||
r.Status = ReceiptStatusSuccessful
|
||||
case bytes.Equal(postStateOrStatus, receiptStatusFailedRLP):
|
||||
r.Status = ReceiptStatusFailed
|
||||
case len(postStateOrStatus) == len(core.Hash{}):
|
||||
r.PostState = postStateOrStatus
|
||||
default:
|
||||
return fmt.Errorf("invalid receipt status %x", postStateOrStatus)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Receipt) statusEncoding() []byte {
|
||||
if len(r.PostState) == 0 {
|
||||
if r.Status == ReceiptStatusFailed {
|
||||
return receiptStatusFailedRLP
|
||||
}
|
||||
return receiptStatusSuccessfulRLP
|
||||
}
|
||||
return r.PostState
|
||||
}
|
||||
|
||||
// Size returns the approximate memory used by all internal contents. It is used
|
||||
// to approximate and limit the memory consumption of various caches.
|
||||
func (r *Receipt) Size() float64 {
|
||||
size := float64(unsafe.Sizeof(*r)) + float64(len(r.PostState))
|
||||
size += float64(len(r.Logs)) * float64(unsafe.Sizeof(Log{}))
|
||||
for _, log := range r.Logs {
|
||||
size += float64(len(log.Topics)*32 + len(log.Data))
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// ReceiptForStorage is a wrapper around a Receipt that flattens and parses the
|
||||
// entire content of a receipt, as opposed to only the consensus fields originally.
|
||||
type ReceiptForStorage Receipt
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error {
|
||||
enc := &storedReceiptRLP{
|
||||
PostStateOrStatus: (*Receipt)(r).statusEncoding(),
|
||||
CumulativeGasUsed: r.CumulativeGasUsed,
|
||||
Logs: r.Logs,
|
||||
}
|
||||
return rlp.Encode(w, enc)
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder.
|
||||
func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error {
|
||||
var stored storedReceiptRLP
|
||||
if err := s.Decode(&stored); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
r.CumulativeGasUsed = stored.CumulativeGasUsed
|
||||
r.Logs = stored.Logs
|
||||
r.Bloom = CreateBloom(Receipts{(*Receipt)(r)})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Receipts implements DerivableList for receipts.
|
||||
type Receipts []*Receipt
|
||||
|
||||
// Len returns the number of receipts in this list.
|
||||
func (rs Receipts) Len() int { return len(rs) }
|
||||
|
||||
// EncodeIndex encodes the i'th receipt to w.
|
||||
func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
|
||||
r := rs[i]
|
||||
data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs}
|
||||
switch r.Type {
|
||||
case LegacyTxType:
|
||||
rlp.Encode(w, data)
|
||||
case AccessListTxType:
|
||||
w.WriteByte(AccessListTxType)
|
||||
rlp.Encode(w, data)
|
||||
case DynamicFeeTxType:
|
||||
w.WriteByte(DynamicFeeTxType)
|
||||
rlp.Encode(w, data)
|
||||
default:
|
||||
// For unsupported types, write nothing. Since this is for
|
||||
// DeriveSha, the error will be caught matching the derived hash
|
||||
// to the block.
|
||||
}
|
||||
}
|
||||
|
||||
// DeriveFields fills the receipts with their computed fields based on consensus
|
||||
// data and contextual infos like containing block and transactions.
|
||||
func (r Receipts) DeriveFields(config *params.ChainConfig, hash core.Hash, number uint64, txs Transactions) error {
|
||||
signer := MakeSigner(config, new(big.Int).SetUint64(number))
|
||||
|
||||
logIndex := uint(0)
|
||||
if len(txs) != len(r) {
|
||||
return errors.New("transaction and receipt count mismatch")
|
||||
}
|
||||
for i := 0; i < len(r); i++ {
|
||||
// The transaction type and hash can be retrieved from the transaction itself
|
||||
r[i].Type = txs[i].Type()
|
||||
r[i].TxHash = txs[i].Hash()
|
||||
|
||||
// block location fields
|
||||
r[i].BlockHash = hash
|
||||
r[i].BlockNumber = new(big.Int).SetUint64(number)
|
||||
r[i].TransactionIndex = uint(i)
|
||||
|
||||
// The contract address can be derived from the transaction itself
|
||||
if txs[i].To() == nil {
|
||||
// Deriving the signer is expensive, only do if it's actually needed
|
||||
from, _ := Sender(signer, txs[i])
|
||||
r[i].ContractAddress = crypto.CreateAddress(from, txs[i].Nonce())
|
||||
}
|
||||
// The used gas can be calculated based on previous r
|
||||
if i == 0 {
|
||||
r[i].GasUsed = r[i].CumulativeGasUsed
|
||||
} else {
|
||||
r[i].GasUsed = r[i].CumulativeGasUsed - r[i-1].CumulativeGasUsed
|
||||
}
|
||||
// The derived log fields can simply be set from the block and transaction
|
||||
for j := 0; j < len(r[i].Logs); j++ {
|
||||
r[i].Logs[j].BlockNumber = number
|
||||
r[i].Logs[j].BlockHash = hash
|
||||
r[i].Logs[j].TxHash = r[i].TxHash
|
||||
r[i].Logs[j].TxIndex = uint(i)
|
||||
r[i].Logs[j].Index = logIndex
|
||||
logIndex++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
233
restricted/types/receipt_test.go
Normal file
233
restricted/types/receipt_test.go
Normal file
@ -0,0 +1,233 @@
|
||||
// Copyright 2019 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"
|
||||
"math"
|
||||
"math/big"
|
||||
"testing"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/crypto"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/params"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/rlp"
|
||||
)
|
||||
|
||||
func TestDecodeEmptyTypedReceipt(t *testing.T) {
|
||||
input := []byte{0x80}
|
||||
var r Receipt
|
||||
err := rlp.DecodeBytes(input, &r)
|
||||
if err != errEmptyTypedReceipt {
|
||||
t.Fatal("wrong error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that receipt data can be correctly derived from the contextual infos
|
||||
func TestDeriveFields(t *testing.T) {
|
||||
// Create a few transactions to have receipts for
|
||||
to2 := core.HexToAddress("0x2")
|
||||
to3 := core.HexToAddress("0x3")
|
||||
txs := Transactions{
|
||||
NewTx(&LegacyTx{
|
||||
Nonce: 1,
|
||||
Value: big.NewInt(1),
|
||||
Gas: 1,
|
||||
GasPrice: big.NewInt(1),
|
||||
}),
|
||||
NewTx(&LegacyTx{
|
||||
To: &to2,
|
||||
Nonce: 2,
|
||||
Value: big.NewInt(2),
|
||||
Gas: 2,
|
||||
GasPrice: big.NewInt(2),
|
||||
}),
|
||||
NewTx(&AccessListTx{
|
||||
To: &to3,
|
||||
Nonce: 3,
|
||||
Value: big.NewInt(3),
|
||||
Gas: 3,
|
||||
GasPrice: big.NewInt(3),
|
||||
}),
|
||||
}
|
||||
// Create the corresponding receipts
|
||||
receipts := Receipts{
|
||||
&Receipt{
|
||||
Status: ReceiptStatusFailed,
|
||||
CumulativeGasUsed: 1,
|
||||
Logs: []*Log{
|
||||
{Address: core.BytesToAddress([]byte{0x11})},
|
||||
{Address: core.BytesToAddress([]byte{0x01, 0x11})},
|
||||
},
|
||||
TxHash: txs[0].Hash(),
|
||||
ContractAddress: core.BytesToAddress([]byte{0x01, 0x11, 0x11}),
|
||||
GasUsed: 1,
|
||||
},
|
||||
&Receipt{
|
||||
PostState: core.Hash{2}.Bytes(),
|
||||
CumulativeGasUsed: 3,
|
||||
Logs: []*Log{
|
||||
{Address: core.BytesToAddress([]byte{0x22})},
|
||||
{Address: core.BytesToAddress([]byte{0x02, 0x22})},
|
||||
},
|
||||
TxHash: txs[1].Hash(),
|
||||
ContractAddress: core.BytesToAddress([]byte{0x02, 0x22, 0x22}),
|
||||
GasUsed: 2,
|
||||
},
|
||||
&Receipt{
|
||||
Type: AccessListTxType,
|
||||
PostState: core.Hash{3}.Bytes(),
|
||||
CumulativeGasUsed: 6,
|
||||
Logs: []*Log{
|
||||
{Address: core.BytesToAddress([]byte{0x33})},
|
||||
{Address: core.BytesToAddress([]byte{0x03, 0x33})},
|
||||
},
|
||||
TxHash: txs[2].Hash(),
|
||||
ContractAddress: core.BytesToAddress([]byte{0x03, 0x33, 0x33}),
|
||||
GasUsed: 3,
|
||||
},
|
||||
}
|
||||
// Clear all the computed fields and re-derive them
|
||||
number := big.NewInt(1)
|
||||
hash := core.BytesToHash([]byte{0x03, 0x14})
|
||||
|
||||
clearComputedFieldsOnReceipts(t, receipts)
|
||||
if err := receipts.DeriveFields(params.TestChainConfig, hash, number.Uint64(), txs); err != nil {
|
||||
t.Fatalf("DeriveFields(...) = %v, want <nil>", err)
|
||||
}
|
||||
// Iterate over all the computed fields and check that they're correct
|
||||
signer := MakeSigner(params.TestChainConfig, number)
|
||||
|
||||
logIndex := uint(0)
|
||||
for i := range receipts {
|
||||
if receipts[i].Type != txs[i].Type() {
|
||||
t.Errorf("receipts[%d].Type = %d, want %d", i, receipts[i].Type, txs[i].Type())
|
||||
}
|
||||
if receipts[i].TxHash != txs[i].Hash() {
|
||||
t.Errorf("receipts[%d].TxHash = %s, want %s", i, receipts[i].TxHash.String(), txs[i].Hash().String())
|
||||
}
|
||||
if receipts[i].BlockHash != hash {
|
||||
t.Errorf("receipts[%d].BlockHash = %s, want %s", i, receipts[i].BlockHash.String(), hash.String())
|
||||
}
|
||||
if receipts[i].BlockNumber.Cmp(number) != 0 {
|
||||
t.Errorf("receipts[%c].BlockNumber = %s, want %s", i, receipts[i].BlockNumber.String(), number.String())
|
||||
}
|
||||
if receipts[i].TransactionIndex != uint(i) {
|
||||
t.Errorf("receipts[%d].TransactionIndex = %d, want %d", i, receipts[i].TransactionIndex, i)
|
||||
}
|
||||
if receipts[i].GasUsed != txs[i].Gas() {
|
||||
t.Errorf("receipts[%d].GasUsed = %d, want %d", i, receipts[i].GasUsed, txs[i].Gas())
|
||||
}
|
||||
if txs[i].To() != nil && receipts[i].ContractAddress != (core.Address{}) {
|
||||
t.Errorf("receipts[%d].ContractAddress = %s, want %s", i, receipts[i].ContractAddress.String(), (core.Address{}).String())
|
||||
}
|
||||
from, _ := Sender(signer, txs[i])
|
||||
contractAddress := crypto.CreateAddress(from, txs[i].Nonce())
|
||||
if txs[i].To() == nil && receipts[i].ContractAddress != contractAddress {
|
||||
t.Errorf("receipts[%d].ContractAddress = %s, want %s", i, receipts[i].ContractAddress.String(), contractAddress.String())
|
||||
}
|
||||
for j := range receipts[i].Logs {
|
||||
if receipts[i].Logs[j].BlockNumber != number.Uint64() {
|
||||
t.Errorf("receipts[%d].Logs[%d].BlockNumber = %d, want %d", i, j, receipts[i].Logs[j].BlockNumber, number.Uint64())
|
||||
}
|
||||
if receipts[i].Logs[j].BlockHash != hash {
|
||||
t.Errorf("receipts[%d].Logs[%d].BlockHash = %s, want %s", i, j, receipts[i].Logs[j].BlockHash.String(), hash.String())
|
||||
}
|
||||
if receipts[i].Logs[j].TxHash != txs[i].Hash() {
|
||||
t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String())
|
||||
}
|
||||
if receipts[i].Logs[j].TxHash != txs[i].Hash() {
|
||||
t.Errorf("receipts[%d].Logs[%d].TxHash = %s, want %s", i, j, receipts[i].Logs[j].TxHash.String(), txs[i].Hash().String())
|
||||
}
|
||||
if receipts[i].Logs[j].TxIndex != uint(i) {
|
||||
t.Errorf("receipts[%d].Logs[%d].TransactionIndex = %d, want %d", i, j, receipts[i].Logs[j].TxIndex, i)
|
||||
}
|
||||
if receipts[i].Logs[j].Index != logIndex {
|
||||
t.Errorf("receipts[%d].Logs[%d].Index = %d, want %d", i, j, receipts[i].Logs[j].Index, logIndex)
|
||||
}
|
||||
logIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTypedReceiptEncodingDecoding reproduces a flaw that existed in the receipt
|
||||
// rlp decoder, which failed due to a shadowing error.
|
||||
func TestTypedReceiptEncodingDecoding(t *testing.T) {
|
||||
payload, _ := hex.DecodeString("f9043eb9010c01f90108018262d4b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010c01f901080182cd14b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f901090183013754b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0b9010d01f90109018301a194b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0")
|
||||
check := func(bundle []*Receipt) {
|
||||
t.Helper()
|
||||
for i, receipt := range bundle {
|
||||
if got, want := receipt.Type, uint8(1); got != want {
|
||||
t.Fatalf("bundle %d: got %x, want %x", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
var bundle []*Receipt
|
||||
rlp.DecodeBytes(payload, &bundle)
|
||||
check(bundle)
|
||||
}
|
||||
{
|
||||
var bundle []*Receipt
|
||||
r := bytes.NewReader(payload)
|
||||
s := rlp.NewStream(r, uint64(len(payload)))
|
||||
if err := s.Decode(&bundle); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
check(bundle)
|
||||
}
|
||||
}
|
||||
|
||||
func clearComputedFieldsOnReceipts(t *testing.T, receipts Receipts) {
|
||||
t.Helper()
|
||||
|
||||
for _, receipt := range receipts {
|
||||
clearComputedFieldsOnReceipt(t, receipt)
|
||||
}
|
||||
}
|
||||
|
||||
func clearComputedFieldsOnReceipt(t *testing.T, receipt *Receipt) {
|
||||
t.Helper()
|
||||
|
||||
receipt.TxHash = core.Hash{}
|
||||
receipt.BlockHash = core.Hash{}
|
||||
receipt.BlockNumber = big.NewInt(math.MaxUint32)
|
||||
receipt.TransactionIndex = math.MaxUint32
|
||||
receipt.ContractAddress = core.Address{}
|
||||
receipt.GasUsed = 0
|
||||
|
||||
clearComputedFieldsOnLogs(t, receipt.Logs)
|
||||
}
|
||||
|
||||
func clearComputedFieldsOnLogs(t *testing.T, logs []*Log) {
|
||||
t.Helper()
|
||||
|
||||
for _, log := range logs {
|
||||
clearComputedFieldsOnLog(t, log)
|
||||
}
|
||||
}
|
||||
|
||||
func clearComputedFieldsOnLog(t *testing.T, log *Log) {
|
||||
t.Helper()
|
||||
|
||||
log.BlockNumber = math.MaxUint32
|
||||
log.BlockHash = core.Hash{}
|
||||
log.TxHash = core.Hash{}
|
||||
log.TxIndex = math.MaxUint32
|
||||
log.Index = math.MaxUint32
|
||||
}
|
638
restricted/types/transaction.go
Normal file
638
restricted/types/transaction.go
Normal file
@ -0,0 +1,638 @@
|
||||
// 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"
|
||||
"container/heap"
|
||||
"errors"
|
||||
"io"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/crypto"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/rlp"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidSig = errors.New("invalid transaction v, r, s values")
|
||||
ErrUnexpectedProtection = errors.New("transaction type does not supported EIP-155 protected signatures")
|
||||
ErrInvalidTxType = errors.New("transaction type not valid in this context")
|
||||
ErrTxTypeNotSupported = errors.New("transaction type not supported")
|
||||
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
|
||||
errEmptyTypedTx = errors.New("empty typed transaction bytes")
|
||||
)
|
||||
|
||||
// Transaction types.
|
||||
const (
|
||||
LegacyTxType = iota
|
||||
AccessListTxType
|
||||
DynamicFeeTxType
|
||||
)
|
||||
|
||||
// Transaction is an Ethereum transaction.
|
||||
type Transaction struct {
|
||||
inner TxData // Consensus contents of a transaction
|
||||
time time.Time // Time first seen locally (spam avoidance)
|
||||
|
||||
// caches
|
||||
hash atomic.Value
|
||||
size atomic.Value
|
||||
from atomic.Value
|
||||
}
|
||||
|
||||
func bigMin(a, b *big.Int) *big.Int {
|
||||
if a.Cmp(b) > 0 { return b }
|
||||
return a
|
||||
}
|
||||
|
||||
// NewTx creates a new transaction.
|
||||
func NewTx(inner TxData) *Transaction {
|
||||
tx := new(Transaction)
|
||||
tx.setDecoded(inner.copy(), 0)
|
||||
return tx
|
||||
}
|
||||
|
||||
// TxData is the underlying data of a transaction.
|
||||
//
|
||||
// This is implemented by DynamicFeeTx, LegacyTx and AccessListTx.
|
||||
type TxData interface {
|
||||
txType() byte // returns the type ID
|
||||
copy() TxData // creates a deep copy and initializes all fields
|
||||
|
||||
chainID() *big.Int
|
||||
accessList() AccessList
|
||||
data() []byte
|
||||
gas() uint64
|
||||
gasPrice() *big.Int
|
||||
gasTipCap() *big.Int
|
||||
gasFeeCap() *big.Int
|
||||
value() *big.Int
|
||||
nonce() uint64
|
||||
to() *core.Address
|
||||
|
||||
rawSignatureValues() (v, r, s *big.Int)
|
||||
setSignatureValues(chainID, v, r, s *big.Int)
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder
|
||||
func (tx *Transaction) EncodeRLP(w io.Writer) error {
|
||||
if tx.Type() == LegacyTxType {
|
||||
return rlp.Encode(w, tx.inner)
|
||||
}
|
||||
// It's an EIP-2718 typed TX envelope.
|
||||
buf := encodeBufferPool.Get().(*bytes.Buffer)
|
||||
defer encodeBufferPool.Put(buf)
|
||||
buf.Reset()
|
||||
if err := tx.encodeTyped(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
return rlp.Encode(w, buf.Bytes())
|
||||
}
|
||||
|
||||
// encodeTyped writes the canonical encoding of a typed transaction to w.
|
||||
func (tx *Transaction) encodeTyped(w *bytes.Buffer) error {
|
||||
w.WriteByte(tx.Type())
|
||||
return rlp.Encode(w, tx.inner)
|
||||
}
|
||||
|
||||
// MarshalBinary returns the canonical encoding of the transaction.
|
||||
// For legacy transactions, it returns the RLP encoding. For EIP-2718 typed
|
||||
// transactions, it returns the type and payload.
|
||||
func (tx *Transaction) MarshalBinary() ([]byte, error) {
|
||||
if tx.Type() == LegacyTxType {
|
||||
return rlp.EncodeToBytes(tx.inner)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err := tx.encodeTyped(&buf)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder
|
||||
func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
|
||||
kind, size, err := s.Kind()
|
||||
switch {
|
||||
case err != nil:
|
||||
return err
|
||||
case kind == rlp.List:
|
||||
// It's a legacy transaction.
|
||||
var inner LegacyTx
|
||||
err := s.Decode(&inner)
|
||||
if err == nil {
|
||||
tx.setDecoded(&inner, int(rlp.ListSize(size)))
|
||||
}
|
||||
return err
|
||||
case kind == rlp.String:
|
||||
// It's an EIP-2718 typed TX envelope.
|
||||
var b []byte
|
||||
if b, err = s.Bytes(); err != nil {
|
||||
return err
|
||||
}
|
||||
inner, err := tx.decodeTyped(b)
|
||||
if err == nil {
|
||||
tx.setDecoded(inner, len(b))
|
||||
}
|
||||
return err
|
||||
default:
|
||||
return rlp.ErrExpectedList
|
||||
}
|
||||
}
|
||||
|
||||
// UnmarshalBinary decodes the canonical encoding of transactions.
|
||||
// It supports legacy RLP transactions and EIP2718 typed transactions.
|
||||
func (tx *Transaction) UnmarshalBinary(b []byte) error {
|
||||
if len(b) > 0 && b[0] > 0x7f {
|
||||
// It's a legacy transaction.
|
||||
var data LegacyTx
|
||||
err := rlp.DecodeBytes(b, &data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx.setDecoded(&data, len(b))
|
||||
return nil
|
||||
}
|
||||
// It's an EIP2718 typed transaction envelope.
|
||||
inner, err := tx.decodeTyped(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx.setDecoded(inner, len(b))
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeTyped decodes a typed transaction from the canonical format.
|
||||
func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
|
||||
if len(b) == 0 {
|
||||
return nil, errEmptyTypedTx
|
||||
}
|
||||
switch b[0] {
|
||||
case AccessListTxType:
|
||||
var inner AccessListTx
|
||||
err := rlp.DecodeBytes(b[1:], &inner)
|
||||
return &inner, err
|
||||
case DynamicFeeTxType:
|
||||
var inner DynamicFeeTx
|
||||
err := rlp.DecodeBytes(b[1:], &inner)
|
||||
return &inner, err
|
||||
default:
|
||||
return nil, ErrTxTypeNotSupported
|
||||
}
|
||||
}
|
||||
|
||||
// setDecoded sets the inner transaction and size after decoding.
|
||||
func (tx *Transaction) setDecoded(inner TxData, size int) {
|
||||
tx.inner = inner
|
||||
tx.time = time.Now()
|
||||
if size > 0 {
|
||||
tx.size.Store(float64(size))
|
||||
}
|
||||
}
|
||||
|
||||
func sanityCheckSignature(v *big.Int, r *big.Int, s *big.Int, maybeProtected bool) error {
|
||||
if isProtectedV(v) && !maybeProtected {
|
||||
return ErrUnexpectedProtection
|
||||
}
|
||||
|
||||
var plainV byte
|
||||
if isProtectedV(v) {
|
||||
chainID := deriveChainId(v).Uint64()
|
||||
plainV = byte(v.Uint64() - 35 - 2*chainID)
|
||||
} else if maybeProtected {
|
||||
// Only EIP-155 signatures can be optionally protected. Since
|
||||
// we determined this v value is not protected, it must be a
|
||||
// raw 27 or 28.
|
||||
plainV = byte(v.Uint64() - 27)
|
||||
} else {
|
||||
// If the signature is not optionally protected, we assume it
|
||||
// must already be equal to the recovery id.
|
||||
plainV = byte(v.Uint64())
|
||||
}
|
||||
if !crypto.ValidateSignatureValues(plainV, r, s, false) {
|
||||
return ErrInvalidSig
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isProtectedV(V *big.Int) bool {
|
||||
if V.BitLen() <= 8 {
|
||||
v := V.Uint64()
|
||||
return v != 27 && v != 28 && v != 1 && v != 0
|
||||
}
|
||||
// anything not 27 or 28 is considered protected
|
||||
return true
|
||||
}
|
||||
|
||||
// Protected says whether the transaction is replay-protected.
|
||||
func (tx *Transaction) Protected() bool {
|
||||
switch tx := tx.inner.(type) {
|
||||
case *LegacyTx:
|
||||
return tx.V != nil && isProtectedV(tx.V)
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Type returns the transaction type.
|
||||
func (tx *Transaction) Type() uint8 {
|
||||
return tx.inner.txType()
|
||||
}
|
||||
|
||||
// ChainId returns the EIP155 chain ID of the transaction. The return value will always be
|
||||
// non-nil. For legacy transactions which are not replay-protected, the return value is
|
||||
// zero.
|
||||
func (tx *Transaction) ChainId() *big.Int {
|
||||
return tx.inner.chainID()
|
||||
}
|
||||
|
||||
// Data returns the input data of the transaction.
|
||||
func (tx *Transaction) Data() []byte { return tx.inner.data() }
|
||||
|
||||
// AccessList returns the access list of the transaction.
|
||||
func (tx *Transaction) AccessList() AccessList { return tx.inner.accessList() }
|
||||
|
||||
// Gas returns the gas limit of the transaction.
|
||||
func (tx *Transaction) Gas() uint64 { return tx.inner.gas() }
|
||||
|
||||
// GasPrice returns the gas price of the transaction.
|
||||
func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.inner.gasPrice()) }
|
||||
|
||||
// GasTipCap returns the gasTipCap per gas of the transaction.
|
||||
func (tx *Transaction) GasTipCap() *big.Int { return new(big.Int).Set(tx.inner.gasTipCap()) }
|
||||
|
||||
// GasFeeCap returns the fee cap per gas of the transaction.
|
||||
func (tx *Transaction) GasFeeCap() *big.Int { return new(big.Int).Set(tx.inner.gasFeeCap()) }
|
||||
|
||||
// Value returns the ether amount of the transaction.
|
||||
func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.inner.value()) }
|
||||
|
||||
// Nonce returns the sender account nonce of the transaction.
|
||||
func (tx *Transaction) Nonce() uint64 { return tx.inner.nonce() }
|
||||
|
||||
// To returns the recipient address of the transaction.
|
||||
// For contract-creation transactions, To returns nil.
|
||||
func (tx *Transaction) To() *core.Address {
|
||||
// Copy the pointed-to address.
|
||||
ito := tx.inner.to()
|
||||
if ito == nil {
|
||||
return nil
|
||||
}
|
||||
cpy := *ito
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// Cost returns gas * gasPrice + value.
|
||||
func (tx *Transaction) Cost() *big.Int {
|
||||
total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
|
||||
total.Add(total, tx.Value())
|
||||
return total
|
||||
}
|
||||
|
||||
// RawSignatureValues returns the V, R, S signature values of the transaction.
|
||||
// The return values should not be modified by the caller.
|
||||
func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) {
|
||||
return tx.inner.rawSignatureValues()
|
||||
}
|
||||
|
||||
// GasFeeCapCmp compares the fee cap of two transactions.
|
||||
func (tx *Transaction) GasFeeCapCmp(other *Transaction) int {
|
||||
return tx.inner.gasFeeCap().Cmp(other.inner.gasFeeCap())
|
||||
}
|
||||
|
||||
// GasFeeCapIntCmp compares the fee cap of the transaction against the given fee cap.
|
||||
func (tx *Transaction) GasFeeCapIntCmp(other *big.Int) int {
|
||||
return tx.inner.gasFeeCap().Cmp(other)
|
||||
}
|
||||
|
||||
// GasTipCapCmp compares the gasTipCap of two transactions.
|
||||
func (tx *Transaction) GasTipCapCmp(other *Transaction) int {
|
||||
return tx.inner.gasTipCap().Cmp(other.inner.gasTipCap())
|
||||
}
|
||||
|
||||
// GasTipCapIntCmp compares the gasTipCap of the transaction against the given gasTipCap.
|
||||
func (tx *Transaction) GasTipCapIntCmp(other *big.Int) int {
|
||||
return tx.inner.gasTipCap().Cmp(other)
|
||||
}
|
||||
|
||||
// EffectiveGasTip returns the effective miner gasTipCap for the given base fee.
|
||||
// Note: if the effective gasTipCap is negative, this method returns both error
|
||||
// the actual negative value, _and_ ErrGasFeeCapTooLow
|
||||
func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) {
|
||||
if baseFee == nil {
|
||||
return tx.GasTipCap(), nil
|
||||
}
|
||||
var err error
|
||||
gasFeeCap := tx.GasFeeCap()
|
||||
if gasFeeCap.Cmp(baseFee) == -1 {
|
||||
err = ErrGasFeeCapTooLow
|
||||
}
|
||||
return bigMin(tx.GasTipCap(), gasFeeCap.Sub(gasFeeCap, baseFee)), err
|
||||
}
|
||||
|
||||
// EffectiveGasTipValue is identical to EffectiveGasTip, but does not return an
|
||||
// error in case the effective gasTipCap is negative
|
||||
func (tx *Transaction) EffectiveGasTipValue(baseFee *big.Int) *big.Int {
|
||||
effectiveTip, _ := tx.EffectiveGasTip(baseFee)
|
||||
return effectiveTip
|
||||
}
|
||||
|
||||
// EffectiveGasTipCmp compares the effective gasTipCap of two transactions assuming the given base fee.
|
||||
func (tx *Transaction) EffectiveGasTipCmp(other *Transaction, baseFee *big.Int) int {
|
||||
if baseFee == nil {
|
||||
return tx.GasTipCapCmp(other)
|
||||
}
|
||||
return tx.EffectiveGasTipValue(baseFee).Cmp(other.EffectiveGasTipValue(baseFee))
|
||||
}
|
||||
|
||||
// EffectiveGasTipIntCmp compares the effective gasTipCap of a transaction to the given gasTipCap.
|
||||
func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) int {
|
||||
if baseFee == nil {
|
||||
return tx.GasTipCapIntCmp(other)
|
||||
}
|
||||
return tx.EffectiveGasTipValue(baseFee).Cmp(other)
|
||||
}
|
||||
|
||||
// Hash returns the transaction hash.
|
||||
func (tx *Transaction) Hash() core.Hash {
|
||||
if hash := tx.hash.Load(); hash != nil {
|
||||
return hash.(core.Hash)
|
||||
}
|
||||
|
||||
var h core.Hash
|
||||
if tx.Type() == LegacyTxType {
|
||||
h = rlpHash(tx.inner)
|
||||
} else {
|
||||
h = prefixedRlpHash(tx.Type(), tx.inner)
|
||||
}
|
||||
tx.hash.Store(h)
|
||||
return h
|
||||
}
|
||||
|
||||
// Size returns the true RLP encoded storage size of the transaction, either by
|
||||
// encoding and returning it, or returning a previously cached value.
|
||||
func (tx *Transaction) Size() float64 {
|
||||
if size := tx.size.Load(); size != nil {
|
||||
return size.(float64)
|
||||
}
|
||||
c := writeCounter(0)
|
||||
rlp.Encode(&c, &tx.inner)
|
||||
tx.size.Store(float64(c))
|
||||
return float64(c)
|
||||
}
|
||||
|
||||
// WithSignature returns a new transaction with the given signature.
|
||||
// This signature needs to be in the [R || S || V] format where V is 0 or 1.
|
||||
func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error) {
|
||||
r, s, v, err := signer.SignatureValues(tx, sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cpy := tx.inner.copy()
|
||||
cpy.setSignatureValues(signer.ChainID(), v, r, s)
|
||||
return &Transaction{inner: cpy, time: tx.time}, nil
|
||||
}
|
||||
|
||||
// Transactions implements DerivableList for transactions.
|
||||
type Transactions []*Transaction
|
||||
|
||||
// Len returns the length of s.
|
||||
func (s Transactions) Len() int { return len(s) }
|
||||
|
||||
// EncodeIndex encodes the i'th transaction to w. Note that this does not check for errors
|
||||
// because we assume that *Transaction will only ever contain valid txs that were either
|
||||
// constructed by decoding or via public API in this package.
|
||||
func (s Transactions) EncodeIndex(i int, w *bytes.Buffer) {
|
||||
tx := s[i]
|
||||
if tx.Type() == LegacyTxType {
|
||||
rlp.Encode(w, tx.inner)
|
||||
} else {
|
||||
tx.encodeTyped(w)
|
||||
}
|
||||
}
|
||||
|
||||
// TxDifference returns a new set which is the difference between a and b.
|
||||
func TxDifference(a, b Transactions) Transactions {
|
||||
keep := make(Transactions, 0, len(a))
|
||||
|
||||
remove := make(map[core.Hash]struct{})
|
||||
for _, tx := range b {
|
||||
remove[tx.Hash()] = struct{}{}
|
||||
}
|
||||
|
||||
for _, tx := range a {
|
||||
if _, ok := remove[tx.Hash()]; !ok {
|
||||
keep = append(keep, tx)
|
||||
}
|
||||
}
|
||||
|
||||
return keep
|
||||
}
|
||||
|
||||
// TxByNonce implements the sort interface to allow sorting a list of transactions
|
||||
// by their nonces. This is usually only useful for sorting transactions from a
|
||||
// single account, otherwise a nonce comparison doesn't make much sense.
|
||||
type TxByNonce Transactions
|
||||
|
||||
func (s TxByNonce) Len() int { return len(s) }
|
||||
func (s TxByNonce) Less(i, j int) bool { return s[i].Nonce() < s[j].Nonce() }
|
||||
func (s TxByNonce) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// TxWithMinerFee wraps a transaction with its gas price or effective miner gasTipCap
|
||||
type TxWithMinerFee struct {
|
||||
tx *Transaction
|
||||
minerFee *big.Int
|
||||
}
|
||||
|
||||
// NewTxWithMinerFee creates a wrapped transaction, calculating the effective
|
||||
// miner gasTipCap if a base fee is provided.
|
||||
// Returns error in case of a negative effective miner gasTipCap.
|
||||
func NewTxWithMinerFee(tx *Transaction, baseFee *big.Int) (*TxWithMinerFee, error) {
|
||||
minerFee, err := tx.EffectiveGasTip(baseFee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TxWithMinerFee{
|
||||
tx: tx,
|
||||
minerFee: minerFee,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TxByPriceAndTime implements both the sort and the heap interface, making it useful
|
||||
// for all at once sorting as well as individually adding and removing elements.
|
||||
type TxByPriceAndTime []*TxWithMinerFee
|
||||
|
||||
func (s TxByPriceAndTime) Len() int { return len(s) }
|
||||
func (s TxByPriceAndTime) Less(i, j int) bool {
|
||||
// If the prices are equal, use the time the transaction was first seen for
|
||||
// deterministic sorting
|
||||
cmp := s[i].minerFee.Cmp(s[j].minerFee)
|
||||
if cmp == 0 {
|
||||
return s[i].tx.time.Before(s[j].tx.time)
|
||||
}
|
||||
return cmp > 0
|
||||
}
|
||||
func (s TxByPriceAndTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
func (s *TxByPriceAndTime) Push(x interface{}) {
|
||||
*s = append(*s, x.(*TxWithMinerFee))
|
||||
}
|
||||
|
||||
func (s *TxByPriceAndTime) Pop() interface{} {
|
||||
old := *s
|
||||
n := len(old)
|
||||
x := old[n-1]
|
||||
*s = old[0 : n-1]
|
||||
return x
|
||||
}
|
||||
|
||||
// TransactionsByPriceAndNonce represents a set of transactions that can return
|
||||
// transactions in a profit-maximizing sorted order, while supporting removing
|
||||
// entire batches of transactions for non-executable accounts.
|
||||
type TransactionsByPriceAndNonce struct {
|
||||
txs map[core.Address]Transactions // Per account nonce-sorted list of transactions
|
||||
heads TxByPriceAndTime // Next transaction for each unique account (price heap)
|
||||
signer Signer // Signer for the set of transactions
|
||||
baseFee *big.Int // Current base fee
|
||||
}
|
||||
|
||||
// NewTransactionsByPriceAndNonce creates a transaction set that can retrieve
|
||||
// price sorted transactions in a nonce-honouring way.
|
||||
//
|
||||
// Note, the input map is reowned so the caller should not interact any more with
|
||||
// if after providing it to the constructor.
|
||||
func NewTransactionsByPriceAndNonce(signer Signer, txs map[core.Address]Transactions, baseFee *big.Int) *TransactionsByPriceAndNonce {
|
||||
// Initialize a price and received time based heap with the head transactions
|
||||
heads := make(TxByPriceAndTime, 0, len(txs))
|
||||
for from, accTxs := range txs {
|
||||
acc, _ := Sender(signer, accTxs[0])
|
||||
wrapped, err := NewTxWithMinerFee(accTxs[0], baseFee)
|
||||
// Remove transaction if sender doesn't match from, or if wrapping fails.
|
||||
if acc != from || err != nil {
|
||||
delete(txs, from)
|
||||
continue
|
||||
}
|
||||
heads = append(heads, wrapped)
|
||||
txs[from] = accTxs[1:]
|
||||
}
|
||||
heap.Init(&heads)
|
||||
|
||||
// Assemble and return the transaction set
|
||||
return &TransactionsByPriceAndNonce{
|
||||
txs: txs,
|
||||
heads: heads,
|
||||
signer: signer,
|
||||
baseFee: baseFee,
|
||||
}
|
||||
}
|
||||
|
||||
// Peek returns the next transaction by price.
|
||||
func (t *TransactionsByPriceAndNonce) Peek() *Transaction {
|
||||
if len(t.heads) == 0 {
|
||||
return nil
|
||||
}
|
||||
return t.heads[0].tx
|
||||
}
|
||||
|
||||
// Shift replaces the current best head with the next one from the same account.
|
||||
func (t *TransactionsByPriceAndNonce) Shift() {
|
||||
acc, _ := Sender(t.signer, t.heads[0].tx)
|
||||
if txs, ok := t.txs[acc]; ok && len(txs) > 0 {
|
||||
if wrapped, err := NewTxWithMinerFee(txs[0], t.baseFee); err == nil {
|
||||
t.heads[0], t.txs[acc] = wrapped, txs[1:]
|
||||
heap.Fix(&t.heads, 0)
|
||||
return
|
||||
}
|
||||
}
|
||||
heap.Pop(&t.heads)
|
||||
}
|
||||
|
||||
// Pop removes the best transaction, *not* replacing it with the next one from
|
||||
// the same account. This should be used when a transaction cannot be executed
|
||||
// and hence all subsequent ones should be discarded from the same account.
|
||||
func (t *TransactionsByPriceAndNonce) Pop() {
|
||||
heap.Pop(&t.heads)
|
||||
}
|
||||
|
||||
// Message is a fully derived transaction and implements core.Message
|
||||
//
|
||||
// NOTE: In a future PR this will be removed.
|
||||
type Message struct {
|
||||
to *core.Address
|
||||
from core.Address
|
||||
nonce uint64
|
||||
amount *big.Int
|
||||
gasLimit uint64
|
||||
gasPrice *big.Int
|
||||
gasFeeCap *big.Int
|
||||
gasTipCap *big.Int
|
||||
data []byte
|
||||
accessList AccessList
|
||||
checkNonce bool
|
||||
}
|
||||
|
||||
func NewMessage(from core.Address, to *core.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, checkNonce bool) Message {
|
||||
return Message{
|
||||
from: from,
|
||||
to: to,
|
||||
nonce: nonce,
|
||||
amount: amount,
|
||||
gasLimit: gasLimit,
|
||||
gasPrice: gasPrice,
|
||||
gasFeeCap: gasFeeCap,
|
||||
gasTipCap: gasTipCap,
|
||||
data: data,
|
||||
accessList: accessList,
|
||||
checkNonce: checkNonce,
|
||||
}
|
||||
}
|
||||
|
||||
// AsMessage returns the transaction as a core.Message.
|
||||
func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
|
||||
msg := Message{
|
||||
nonce: tx.Nonce(),
|
||||
gasLimit: tx.Gas(),
|
||||
gasPrice: new(big.Int).Set(tx.GasPrice()),
|
||||
gasFeeCap: new(big.Int).Set(tx.GasFeeCap()),
|
||||
gasTipCap: new(big.Int).Set(tx.GasTipCap()),
|
||||
to: tx.To(),
|
||||
amount: tx.Value(),
|
||||
data: tx.Data(),
|
||||
accessList: tx.AccessList(),
|
||||
checkNonce: true,
|
||||
}
|
||||
// If baseFee provided, set gasPrice to effectiveGasPrice.
|
||||
if baseFee != nil {
|
||||
msg.gasPrice = bigMin(msg.gasPrice.Add(msg.gasTipCap, baseFee), msg.gasFeeCap)
|
||||
}
|
||||
var err error
|
||||
msg.from, err = Sender(s, tx)
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func (m Message) From() core.Address { return m.from }
|
||||
func (m Message) To() *core.Address { return m.to }
|
||||
func (m Message) GasPrice() *big.Int { return m.gasPrice }
|
||||
func (m Message) GasFeeCap() *big.Int { return m.gasFeeCap }
|
||||
func (m Message) GasTipCap() *big.Int { return m.gasTipCap }
|
||||
func (m Message) Value() *big.Int { return m.amount }
|
||||
func (m Message) Gas() uint64 { return m.gasLimit }
|
||||
func (m Message) Nonce() uint64 { return m.nonce }
|
||||
func (m Message) Data() []byte { return m.data }
|
||||
func (m Message) AccessList() AccessList { return m.accessList }
|
||||
func (m Message) CheckNonce() bool { return m.checkNonce }
|
275
restricted/types/transaction_marshalling.go
Normal file
275
restricted/types/transaction_marshalling.go
Normal file
@ -0,0 +1,275 @@
|
||||
// Copyright 2021 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 (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/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"`
|
||||
MaxPriorityFeePerGas *hexutil.Big `json:"maxPriorityFeePerGas"`
|
||||
MaxFeePerGas *hexutil.Big `json:"maxFeePerGas"`
|
||||
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 *core.Address `json:"to"`
|
||||
|
||||
// Access list transaction fields:
|
||||
ChainID *hexutil.Big `json:"chainId,omitempty"`
|
||||
AccessList *AccessList `json:"accessList,omitempty"`
|
||||
|
||||
// Only used for encoding:
|
||||
Hash core.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)
|
||||
case *DynamicFeeTx:
|
||||
enc.ChainID = (*hexutil.Big)(tx.ChainID)
|
||||
enc.AccessList = &tx.AccessList
|
||||
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
|
||||
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
|
||||
enc.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap)
|
||||
enc.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
case DynamicFeeTxType:
|
||||
var itx DynamicFeeTx
|
||||
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.MaxPriorityFeePerGas == nil {
|
||||
return errors.New("missing required field 'maxPriorityFeePerGas' for txdata")
|
||||
}
|
||||
itx.GasTipCap = (*big.Int)(dec.MaxPriorityFeePerGas)
|
||||
if dec.MaxFeePerGas == nil {
|
||||
return errors.New("missing required field 'maxFeePerGas' for txdata")
|
||||
}
|
||||
itx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas)
|
||||
if dec.Gas == nil {
|
||||
return errors.New("missing required field 'gas' for txdata")
|
||||
}
|
||||
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
|
||||
}
|
520
restricted/types/transaction_signing.go
Normal file
520
restricted/types/transaction_signing.go
Normal file
@ -0,0 +1,520 @@
|
||||
// Copyright 2016 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 (
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/crypto"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/params"
|
||||
)
|
||||
|
||||
var ErrInvalidChainId = errors.New("invalid chain id for signer")
|
||||
|
||||
// sigCache is used to cache the derived sender and contains
|
||||
// the signer used to derive it.
|
||||
type sigCache struct {
|
||||
signer Signer
|
||||
from core.Address
|
||||
}
|
||||
|
||||
// MakeSigner returns a Signer based on the given chain config and block number.
|
||||
func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
|
||||
var signer Signer
|
||||
switch {
|
||||
case config.IsLondon(blockNumber):
|
||||
signer = NewLondonSigner(config.ChainID)
|
||||
case config.IsBerlin(blockNumber):
|
||||
signer = NewEIP2930Signer(config.ChainID)
|
||||
case config.IsEIP155(blockNumber):
|
||||
signer = NewEIP155Signer(config.ChainID)
|
||||
case config.IsHomestead(blockNumber):
|
||||
signer = HomesteadSigner{}
|
||||
default:
|
||||
signer = FrontierSigner{}
|
||||
}
|
||||
return signer
|
||||
}
|
||||
|
||||
// LatestSigner returns the 'most permissive' Signer available for the given chain
|
||||
// configuration. Specifically, this enables support of EIP-155 replay protection and
|
||||
// EIP-2930 access list transactions when their respective forks are scheduled to occur at
|
||||
// any block number in the chain config.
|
||||
//
|
||||
// Use this in transaction-handling code where the current block number is unknown. If you
|
||||
// have the current block number available, use MakeSigner instead.
|
||||
func LatestSigner(config *params.ChainConfig) Signer {
|
||||
if config.ChainID != nil {
|
||||
if config.LondonBlock != nil {
|
||||
return NewLondonSigner(config.ChainID)
|
||||
}
|
||||
if config.BerlinBlock != nil {
|
||||
return NewEIP2930Signer(config.ChainID)
|
||||
}
|
||||
if config.EIP155Block != nil {
|
||||
return NewEIP155Signer(config.ChainID)
|
||||
}
|
||||
}
|
||||
return HomesteadSigner{}
|
||||
}
|
||||
|
||||
// LatestSignerForChainID returns the 'most permissive' Signer available. Specifically,
|
||||
// this enables support for EIP-155 replay protection and all implemented EIP-2718
|
||||
// transaction types if chainID is non-nil.
|
||||
//
|
||||
// Use this in transaction-handling code where the current block number and fork
|
||||
// configuration are unknown. If you have a ChainConfig, use LatestSigner instead.
|
||||
// If you have a ChainConfig and know the current block number, use MakeSigner instead.
|
||||
func LatestSignerForChainID(chainID *big.Int) Signer {
|
||||
if chainID == nil {
|
||||
return HomesteadSigner{}
|
||||
}
|
||||
return NewLondonSigner(chainID)
|
||||
}
|
||||
|
||||
// SignTx signs the transaction using the given signer and private key.
|
||||
func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) {
|
||||
h := s.Hash(tx)
|
||||
sig, err := crypto.Sign(h[:], prv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tx.WithSignature(s, sig)
|
||||
}
|
||||
|
||||
// SignNewTx creates a transaction and signs it.
|
||||
func SignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) (*Transaction, error) {
|
||||
tx := NewTx(txdata)
|
||||
h := s.Hash(tx)
|
||||
sig, err := crypto.Sign(h[:], prv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tx.WithSignature(s, sig)
|
||||
}
|
||||
|
||||
// MustSignNewTx creates a transaction and signs it.
|
||||
// This panics if the transaction cannot be signed.
|
||||
func MustSignNewTx(prv *ecdsa.PrivateKey, s Signer, txdata TxData) *Transaction {
|
||||
tx, err := SignNewTx(prv, s, txdata)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
// Sender returns the address derived from the signature (V, R, S) using secp256k1
|
||||
// elliptic curve and an error if it failed deriving or upon an incorrect
|
||||
// signature.
|
||||
//
|
||||
// Sender may cache the address, allowing it to be used regardless of
|
||||
// signing method. The cache is invalidated if the cached signer does
|
||||
// not match the signer used in the current call.
|
||||
func Sender(signer Signer, tx *Transaction) (core.Address, error) {
|
||||
if sc := tx.from.Load(); sc != nil {
|
||||
sigCache := sc.(sigCache)
|
||||
// If the signer used to derive from in a previous
|
||||
// call is not the same as used current, invalidate
|
||||
// the cache.
|
||||
if sigCache.signer.Equal(signer) {
|
||||
return sigCache.from, nil
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := signer.Sender(tx)
|
||||
if err != nil {
|
||||
return core.Address{}, err
|
||||
}
|
||||
tx.from.Store(sigCache{signer: signer, from: addr})
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// Signer encapsulates transaction signature handling. The name of this type is slightly
|
||||
// misleading because Signers don't actually sign, they're just for validating and
|
||||
// processing of signatures.
|
||||
//
|
||||
// Note that this interface is not a stable API and may change at any time to accommodate
|
||||
// new protocol rules.
|
||||
type Signer interface {
|
||||
// Sender returns the sender address of the transaction.
|
||||
Sender(tx *Transaction) (core.Address, error)
|
||||
|
||||
// SignatureValues returns the raw R, S, V values corresponding to the
|
||||
// given signature.
|
||||
SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error)
|
||||
ChainID() *big.Int
|
||||
|
||||
// Hash returns 'signature hash', i.e. the transaction hash that is signed by the
|
||||
// private key. This hash does not uniquely identify the transaction.
|
||||
Hash(tx *Transaction) core.Hash
|
||||
|
||||
// Equal returns true if the given signer is the same as the receiver.
|
||||
Equal(Signer) bool
|
||||
}
|
||||
|
||||
type londonSigner struct{ eip2930Signer }
|
||||
|
||||
// NewLondonSigner returns a signer that accepts
|
||||
// - EIP-1559 dynamic fee transactions
|
||||
// - EIP-2930 access list transactions,
|
||||
// - EIP-155 replay protected transactions, and
|
||||
// - legacy Homestead transactions.
|
||||
func NewLondonSigner(chainId *big.Int) Signer {
|
||||
return londonSigner{eip2930Signer{NewEIP155Signer(chainId)}}
|
||||
}
|
||||
|
||||
func (s londonSigner) Sender(tx *Transaction) (core.Address, error) {
|
||||
if tx.Type() != DynamicFeeTxType {
|
||||
return s.eip2930Signer.Sender(tx)
|
||||
}
|
||||
V, R, S := tx.RawSignatureValues()
|
||||
// DynamicFee txs are defined to use 0 and 1 as their recovery
|
||||
// id, add 27 to become equivalent to unprotected Homestead signatures.
|
||||
V = new(big.Int).Add(V, big.NewInt(27))
|
||||
if tx.ChainId().Cmp(s.chainId) != 0 {
|
||||
return core.Address{}, ErrInvalidChainId
|
||||
}
|
||||
return recoverPlain(s.Hash(tx), R, S, V, true)
|
||||
}
|
||||
|
||||
func (s londonSigner) Equal(s2 Signer) bool {
|
||||
x, ok := s2.(londonSigner)
|
||||
return ok && x.chainId.Cmp(s.chainId) == 0
|
||||
}
|
||||
|
||||
func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
|
||||
txdata, ok := tx.inner.(*DynamicFeeTx)
|
||||
if !ok {
|
||||
return s.eip2930Signer.SignatureValues(tx, sig)
|
||||
}
|
||||
// Check that chain ID of tx matches the signer. We also accept ID zero here,
|
||||
// because it indicates that the chain ID was not specified in the tx.
|
||||
if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
|
||||
return nil, nil, nil, ErrInvalidChainId
|
||||
}
|
||||
R, S, _ = decodeSignature(sig)
|
||||
V = big.NewInt(int64(sig[64]))
|
||||
return R, S, V, nil
|
||||
}
|
||||
|
||||
// Hash returns the hash to be signed by the sender.
|
||||
// It does not uniquely identify the transaction.
|
||||
func (s londonSigner) Hash(tx *Transaction) core.Hash {
|
||||
if tx.Type() != DynamicFeeTxType {
|
||||
return s.eip2930Signer.Hash(tx)
|
||||
}
|
||||
return prefixedRlpHash(
|
||||
tx.Type(),
|
||||
[]interface{}{
|
||||
s.chainId,
|
||||
tx.Nonce(),
|
||||
tx.GasTipCap(),
|
||||
tx.GasFeeCap(),
|
||||
tx.Gas(),
|
||||
tx.To(),
|
||||
tx.Value(),
|
||||
tx.Data(),
|
||||
tx.AccessList(),
|
||||
})
|
||||
}
|
||||
|
||||
type eip2930Signer struct{ EIP155Signer }
|
||||
|
||||
// NewEIP2930Signer returns a signer that accepts EIP-2930 access list transactions,
|
||||
// EIP-155 replay protected transactions, and legacy Homestead transactions.
|
||||
func NewEIP2930Signer(chainId *big.Int) Signer {
|
||||
return eip2930Signer{NewEIP155Signer(chainId)}
|
||||
}
|
||||
|
||||
func (s eip2930Signer) ChainID() *big.Int {
|
||||
return s.chainId
|
||||
}
|
||||
|
||||
func (s eip2930Signer) Equal(s2 Signer) bool {
|
||||
x, ok := s2.(eip2930Signer)
|
||||
return ok && x.chainId.Cmp(s.chainId) == 0
|
||||
}
|
||||
|
||||
func (s eip2930Signer) Sender(tx *Transaction) (core.Address, error) {
|
||||
V, R, S := tx.RawSignatureValues()
|
||||
switch tx.Type() {
|
||||
case LegacyTxType:
|
||||
if !tx.Protected() {
|
||||
return HomesteadSigner{}.Sender(tx)
|
||||
}
|
||||
V = new(big.Int).Sub(V, s.chainIdMul)
|
||||
V.Sub(V, big8)
|
||||
case AccessListTxType:
|
||||
// AL txs are defined to use 0 and 1 as their recovery
|
||||
// id, add 27 to become equivalent to unprotected Homestead signatures.
|
||||
V = new(big.Int).Add(V, big.NewInt(27))
|
||||
default:
|
||||
return core.Address{}, ErrTxTypeNotSupported
|
||||
}
|
||||
if tx.ChainId().Cmp(s.chainId) != 0 {
|
||||
return core.Address{}, ErrInvalidChainId
|
||||
}
|
||||
return recoverPlain(s.Hash(tx), R, S, V, true)
|
||||
}
|
||||
|
||||
func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
|
||||
switch txdata := tx.inner.(type) {
|
||||
case *LegacyTx:
|
||||
return s.EIP155Signer.SignatureValues(tx, sig)
|
||||
case *AccessListTx:
|
||||
// Check that chain ID of tx matches the signer. We also accept ID zero here,
|
||||
// because it indicates that the chain ID was not specified in the tx.
|
||||
if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
|
||||
return nil, nil, nil, ErrInvalidChainId
|
||||
}
|
||||
R, S, _ = decodeSignature(sig)
|
||||
V = big.NewInt(int64(sig[64]))
|
||||
default:
|
||||
return nil, nil, nil, ErrTxTypeNotSupported
|
||||
}
|
||||
return R, S, V, nil
|
||||
}
|
||||
|
||||
// Hash returns the hash to be signed by the sender.
|
||||
// It does not uniquely identify the transaction.
|
||||
func (s eip2930Signer) Hash(tx *Transaction) core.Hash {
|
||||
switch tx.Type() {
|
||||
case LegacyTxType:
|
||||
return rlpHash([]interface{}{
|
||||
tx.Nonce(),
|
||||
tx.GasPrice(),
|
||||
tx.Gas(),
|
||||
tx.To(),
|
||||
tx.Value(),
|
||||
tx.Data(),
|
||||
s.chainId, uint(0), uint(0),
|
||||
})
|
||||
case AccessListTxType:
|
||||
return prefixedRlpHash(
|
||||
tx.Type(),
|
||||
[]interface{}{
|
||||
s.chainId,
|
||||
tx.Nonce(),
|
||||
tx.GasPrice(),
|
||||
tx.Gas(),
|
||||
tx.To(),
|
||||
tx.Value(),
|
||||
tx.Data(),
|
||||
tx.AccessList(),
|
||||
})
|
||||
default:
|
||||
// This _should_ not happen, but in case someone sends in a bad
|
||||
// json struct via RPC, it's probably more prudent to return an
|
||||
// empty hash instead of killing the node with a panic
|
||||
//panic("Unsupported transaction type: %d", tx.typ)
|
||||
return core.Hash{}
|
||||
}
|
||||
}
|
||||
|
||||
// EIP155Signer implements Signer using the EIP-155 rules. This accepts transactions which
|
||||
// are replay-protected as well as unprotected homestead transactions.
|
||||
type EIP155Signer struct {
|
||||
chainId, chainIdMul *big.Int
|
||||
}
|
||||
|
||||
func NewEIP155Signer(chainId *big.Int) EIP155Signer {
|
||||
if chainId == nil {
|
||||
chainId = new(big.Int)
|
||||
}
|
||||
return EIP155Signer{
|
||||
chainId: chainId,
|
||||
chainIdMul: new(big.Int).Mul(chainId, big.NewInt(2)),
|
||||
}
|
||||
}
|
||||
|
||||
func (s EIP155Signer) ChainID() *big.Int {
|
||||
return s.chainId
|
||||
}
|
||||
|
||||
func (s EIP155Signer) Equal(s2 Signer) bool {
|
||||
eip155, ok := s2.(EIP155Signer)
|
||||
return ok && eip155.chainId.Cmp(s.chainId) == 0
|
||||
}
|
||||
|
||||
var big8 = big.NewInt(8)
|
||||
|
||||
func (s EIP155Signer) Sender(tx *Transaction) (core.Address, error) {
|
||||
if tx.Type() != LegacyTxType {
|
||||
return core.Address{}, ErrTxTypeNotSupported
|
||||
}
|
||||
if !tx.Protected() {
|
||||
return HomesteadSigner{}.Sender(tx)
|
||||
}
|
||||
if tx.ChainId().Cmp(s.chainId) != 0 {
|
||||
return core.Address{}, ErrInvalidChainId
|
||||
}
|
||||
V, R, S := tx.RawSignatureValues()
|
||||
V = new(big.Int).Sub(V, s.chainIdMul)
|
||||
V.Sub(V, big8)
|
||||
return recoverPlain(s.Hash(tx), R, S, V, true)
|
||||
}
|
||||
|
||||
// SignatureValues returns signature values. This signature
|
||||
// needs to be in the [R || S || V] format where V is 0 or 1.
|
||||
func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
|
||||
if tx.Type() != LegacyTxType {
|
||||
return nil, nil, nil, ErrTxTypeNotSupported
|
||||
}
|
||||
R, S, V = decodeSignature(sig)
|
||||
if s.chainId.Sign() != 0 {
|
||||
V = big.NewInt(int64(sig[64] + 35))
|
||||
V.Add(V, s.chainIdMul)
|
||||
}
|
||||
return R, S, V, nil
|
||||
}
|
||||
|
||||
// Hash returns the hash to be signed by the sender.
|
||||
// It does not uniquely identify the transaction.
|
||||
func (s EIP155Signer) Hash(tx *Transaction) core.Hash {
|
||||
return rlpHash([]interface{}{
|
||||
tx.Nonce(),
|
||||
tx.GasPrice(),
|
||||
tx.Gas(),
|
||||
tx.To(),
|
||||
tx.Value(),
|
||||
tx.Data(),
|
||||
s.chainId, uint(0), uint(0),
|
||||
})
|
||||
}
|
||||
|
||||
// HomesteadTransaction implements TransactionInterface using the
|
||||
// homestead rules.
|
||||
type HomesteadSigner struct{ FrontierSigner }
|
||||
|
||||
func (s HomesteadSigner) ChainID() *big.Int {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s HomesteadSigner) Equal(s2 Signer) bool {
|
||||
_, ok := s2.(HomesteadSigner)
|
||||
return ok
|
||||
}
|
||||
|
||||
// SignatureValues returns signature values. This signature
|
||||
// needs to be in the [R || S || V] format where V is 0 or 1.
|
||||
func (hs HomesteadSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) {
|
||||
return hs.FrontierSigner.SignatureValues(tx, sig)
|
||||
}
|
||||
|
||||
func (hs HomesteadSigner) Sender(tx *Transaction) (core.Address, error) {
|
||||
if tx.Type() != LegacyTxType {
|
||||
return core.Address{}, ErrTxTypeNotSupported
|
||||
}
|
||||
v, r, s := tx.RawSignatureValues()
|
||||
return recoverPlain(hs.Hash(tx), r, s, v, true)
|
||||
}
|
||||
|
||||
type FrontierSigner struct{}
|
||||
|
||||
func (s FrontierSigner) ChainID() *big.Int {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s FrontierSigner) Equal(s2 Signer) bool {
|
||||
_, ok := s2.(FrontierSigner)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (fs FrontierSigner) Sender(tx *Transaction) (core.Address, error) {
|
||||
if tx.Type() != LegacyTxType {
|
||||
return core.Address{}, ErrTxTypeNotSupported
|
||||
}
|
||||
v, r, s := tx.RawSignatureValues()
|
||||
return recoverPlain(fs.Hash(tx), r, s, v, false)
|
||||
}
|
||||
|
||||
// SignatureValues returns signature values. This signature
|
||||
// needs to be in the [R || S || V] format where V is 0 or 1.
|
||||
func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) {
|
||||
if tx.Type() != LegacyTxType {
|
||||
return nil, nil, nil, ErrTxTypeNotSupported
|
||||
}
|
||||
r, s, v = decodeSignature(sig)
|
||||
return r, s, v, nil
|
||||
}
|
||||
|
||||
// Hash returns the hash to be signed by the sender.
|
||||
// It does not uniquely identify the transaction.
|
||||
func (fs FrontierSigner) Hash(tx *Transaction) core.Hash {
|
||||
return rlpHash([]interface{}{
|
||||
tx.Nonce(),
|
||||
tx.GasPrice(),
|
||||
tx.Gas(),
|
||||
tx.To(),
|
||||
tx.Value(),
|
||||
tx.Data(),
|
||||
})
|
||||
}
|
||||
|
||||
func decodeSignature(sig []byte) (r, s, v *big.Int) {
|
||||
if len(sig) != crypto.SignatureLength {
|
||||
panic(fmt.Sprintf("wrong size for signature: got %d, want %d", len(sig), crypto.SignatureLength))
|
||||
}
|
||||
r = new(big.Int).SetBytes(sig[:32])
|
||||
s = new(big.Int).SetBytes(sig[32:64])
|
||||
v = new(big.Int).SetBytes([]byte{sig[64] + 27})
|
||||
return r, s, v
|
||||
}
|
||||
|
||||
func recoverPlain(sighash core.Hash, R, S, Vb *big.Int, homestead bool) (core.Address, error) {
|
||||
if Vb.BitLen() > 8 {
|
||||
return core.Address{}, ErrInvalidSig
|
||||
}
|
||||
V := byte(Vb.Uint64() - 27)
|
||||
if !crypto.ValidateSignatureValues(V, R, S, homestead) {
|
||||
return core.Address{}, ErrInvalidSig
|
||||
}
|
||||
// encode the signature in uncompressed format
|
||||
r, s := R.Bytes(), S.Bytes()
|
||||
sig := make([]byte, crypto.SignatureLength)
|
||||
copy(sig[32-len(r):32], r)
|
||||
copy(sig[64-len(s):64], s)
|
||||
sig[64] = V
|
||||
// recover the public key from the signature
|
||||
pub, err := crypto.Ecrecover(sighash[:], sig)
|
||||
if err != nil {
|
||||
return core.Address{}, err
|
||||
}
|
||||
if len(pub) == 0 || pub[0] != 4 {
|
||||
return core.Address{}, errors.New("invalid public key")
|
||||
}
|
||||
var addr core.Address
|
||||
copy(addr[:], crypto.Keccak256(pub[1:])[12:])
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// deriveChainId derives the chain id from the given v parameter
|
||||
func deriveChainId(v *big.Int) *big.Int {
|
||||
if v.BitLen() <= 64 {
|
||||
v := v.Uint64()
|
||||
if v == 27 || v == 28 {
|
||||
return new(big.Int)
|
||||
}
|
||||
return new(big.Int).SetUint64((v - 35) / 2)
|
||||
}
|
||||
v = new(big.Int).Sub(v, big.NewInt(35))
|
||||
return v.Div(v, big.NewInt(2))
|
||||
}
|
140
restricted/types/transaction_signing_test.go
Normal file
140
restricted/types/transaction_signing_test.go
Normal file
@ -0,0 +1,140 @@
|
||||
// Copyright 2016 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 (
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/crypto"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/rlp"
|
||||
)
|
||||
|
||||
func TestEIP155Signing(t *testing.T) {
|
||||
key, _ := crypto.GenerateKey()
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
|
||||
signer := NewEIP155Signer(big.NewInt(18))
|
||||
tx, err := SignTx(NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil), signer, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
from, err := Sender(signer, tx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if from != addr {
|
||||
t.Errorf("exected from and address to be equal. Got %x want %x", from, addr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEIP155ChainId(t *testing.T) {
|
||||
key, _ := crypto.GenerateKey()
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
|
||||
signer := NewEIP155Signer(big.NewInt(18))
|
||||
tx, err := SignTx(NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil), signer, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !tx.Protected() {
|
||||
t.Fatal("expected tx to be protected")
|
||||
}
|
||||
|
||||
if tx.ChainId().Cmp(signer.chainId) != 0 {
|
||||
t.Error("expected chainId to be", signer.chainId, "got", tx.ChainId())
|
||||
}
|
||||
|
||||
tx = NewTransaction(0, addr, new(big.Int), 0, new(big.Int), nil)
|
||||
tx, err = SignTx(tx, HomesteadSigner{}, key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if tx.Protected() {
|
||||
t.Error("didn't expect tx to be protected")
|
||||
}
|
||||
|
||||
if tx.ChainId().Sign() != 0 {
|
||||
t.Error("expected chain id to be 0 got", tx.ChainId())
|
||||
}
|
||||
}
|
||||
|
||||
func TestEIP155SigningVitalik(t *testing.T) {
|
||||
// Test vectors come from http://vitalik.ca/files/eip155_testvec.txt
|
||||
for i, test := range []struct {
|
||||
txRlp, addr string
|
||||
}{
|
||||
{"f864808504a817c800825208943535353535353535353535353535353535353535808025a0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116da0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d", "0xf0f6f18bca1b28cd68e4357452947e021241e9ce"},
|
||||
{"f864018504a817c80182a410943535353535353535353535353535353535353535018025a0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bcaa0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", "0x23ef145a395ea3fa3deb533b8a9e1b4c6c25d112"},
|
||||
{"f864028504a817c80282f618943535353535353535353535353535353535353535088025a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5", "0x2e485e0c23b4c3c542628a5f672eeab0ad4888be"},
|
||||
{"f865038504a817c803830148209435353535353535353535353535353535353535351b8025a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4e0a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de", "0x82a88539669a3fd524d669e858935de5e5410cf0"},
|
||||
{"f865048504a817c80483019a28943535353535353535353535353535353535353535408025a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c063a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c060", "0xf9358f2538fd5ccfeb848b64a96b743fcc930554"},
|
||||
{"f865058504a817c8058301ec309435353535353535353535353535353535353535357d8025a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1", "0xa8f7aba377317440bc5b26198a363ad22af1f3a4"},
|
||||
{"f866068504a817c80683023e3894353535353535353535353535353535353535353581d88025a06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2fa06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2d", "0xf1f571dc362a0e5b2696b8e775f8491d3e50de35"},
|
||||
{"f867078504a817c807830290409435353535353535353535353535353535353535358201578025a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021", "0xd37922162ab7cea97c97a87551ed02c9a38b7332"},
|
||||
{"f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10", "0x9bddad43f934d313c2b79ca28a432dd2b7281029"},
|
||||
{"f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb", "0x3c24d7329e92f84f08556ceb6df1cdb0104ca49f"},
|
||||
} {
|
||||
signer := NewEIP155Signer(big.NewInt(1))
|
||||
|
||||
var tx *Transaction
|
||||
b, _ := hex.DecodeString(test.txRlp)
|
||||
err := rlp.DecodeBytes(b, &tx)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
from, err := Sender(signer, tx)
|
||||
if err != nil {
|
||||
t.Errorf("%d: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
addr := core.HexToAddress(test.addr)
|
||||
if from != addr {
|
||||
t.Errorf("%d: expected %x got %x", i, addr, from)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestChainId(t *testing.T) {
|
||||
key, _ := defaultTestKey()
|
||||
|
||||
tx := NewTransaction(0, core.Address{}, new(big.Int), 0, new(big.Int), nil)
|
||||
|
||||
var err error
|
||||
tx, err = SignTx(tx, NewEIP155Signer(big.NewInt(1)), key)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = Sender(NewEIP155Signer(big.NewInt(2)), tx)
|
||||
if err != ErrInvalidChainId {
|
||||
t.Error("expected error:", ErrInvalidChainId)
|
||||
}
|
||||
|
||||
_, err = Sender(NewEIP155Signer(big.NewInt(1)), tx)
|
||||
if err != nil {
|
||||
t.Error("expected no error")
|
||||
}
|
||||
}
|
535
restricted/types/transaction_test.go
Normal file
535
restricted/types/transaction_test.go
Normal file
@ -0,0 +1,535 @@
|
||||
// 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"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/openrelayxyz/plugeth-utils/core"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/crypto"
|
||||
"github.com/openrelayxyz/plugeth-utils/restricted/rlp"
|
||||
)
|
||||
|
||||
func fromHex(data string) []byte {
|
||||
d, _ := hex.DecodeString(data)
|
||||
return d
|
||||
}
|
||||
|
||||
// The values in those tests are from the Transaction Tests
|
||||
// at github.com/ethereum/tests.
|
||||
var (
|
||||
testAddr = core.HexToAddress("b94f5374fce5edbc8e2a8697c15331677e6ebf0b")
|
||||
|
||||
emptyTx = NewTransaction(
|
||||
0,
|
||||
core.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"),
|
||||
big.NewInt(0), 0, big.NewInt(0),
|
||||
nil,
|
||||
)
|
||||
|
||||
rightvrsTx, _ = NewTransaction(
|
||||
3,
|
||||
testAddr,
|
||||
big.NewInt(10),
|
||||
2000,
|
||||
big.NewInt(1),
|
||||
fromHex("5544"),
|
||||
).WithSignature(
|
||||
HomesteadSigner{},
|
||||
fromHex("98ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4a8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a301"),
|
||||
)
|
||||
|
||||
emptyEip2718Tx = NewTx(&AccessListTx{
|
||||
ChainID: big.NewInt(1),
|
||||
Nonce: 3,
|
||||
To: &testAddr,
|
||||
Value: big.NewInt(10),
|
||||
Gas: 25000,
|
||||
GasPrice: big.NewInt(1),
|
||||
Data: fromHex("5544"),
|
||||
})
|
||||
|
||||
signedEip2718Tx, _ = emptyEip2718Tx.WithSignature(
|
||||
NewEIP2930Signer(big.NewInt(1)),
|
||||
fromHex("c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b266032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d3752101"),
|
||||
)
|
||||
)
|
||||
|
||||
func TestDecodeEmptyTypedTx(t *testing.T) {
|
||||
input := []byte{0x80}
|
||||
var tx Transaction
|
||||
err := rlp.DecodeBytes(input, &tx)
|
||||
if err != errEmptyTypedTx {
|
||||
t.Fatal("wrong error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionSigHash(t *testing.T) {
|
||||
var homestead HomesteadSigner
|
||||
if homestead.Hash(emptyTx) != core.HexToHash("c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386") {
|
||||
t.Errorf("empty transaction hash mismatch, got %x", emptyTx.Hash())
|
||||
}
|
||||
if homestead.Hash(rightvrsTx) != core.HexToHash("fe7a79529ed5f7c3375d06b26b186a8644e0e16c373d7a12be41c62d6042b77a") {
|
||||
t.Errorf("RightVRS transaction hash mismatch, got %x", rightvrsTx.Hash())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionEncode(t *testing.T) {
|
||||
txb, err := rlp.EncodeToBytes(rightvrsTx)
|
||||
if err != nil {
|
||||
t.Fatalf("encode error: %v", err)
|
||||
}
|
||||
should := fromHex("f86103018207d094b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a8255441ca098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa08887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3")
|
||||
if !bytes.Equal(txb, should) {
|
||||
t.Errorf("encoded RLP mismatch, got %x", txb)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEIP2718TransactionSigHash(t *testing.T) {
|
||||
s := NewEIP2930Signer(big.NewInt(1))
|
||||
if s.Hash(emptyEip2718Tx) != core.HexToHash("49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3") {
|
||||
t.Errorf("empty EIP-2718 transaction hash mismatch, got %x", s.Hash(emptyEip2718Tx))
|
||||
}
|
||||
if s.Hash(signedEip2718Tx) != core.HexToHash("49b486f0ec0a60dfbbca2d30cb07c9e8ffb2a2ff41f29a1ab6737475f6ff69f3") {
|
||||
t.Errorf("signed EIP-2718 transaction hash mismatch, got %x", s.Hash(signedEip2718Tx))
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks signature operations on access list transactions.
|
||||
func TestEIP2930Signer(t *testing.T) {
|
||||
|
||||
var (
|
||||
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
keyAddr = crypto.PubkeyToAddress(key.PublicKey)
|
||||
signer1 = NewEIP2930Signer(big.NewInt(1))
|
||||
signer2 = NewEIP2930Signer(big.NewInt(2))
|
||||
tx0 = NewTx(&AccessListTx{Nonce: 1})
|
||||
tx1 = NewTx(&AccessListTx{ChainID: big.NewInt(1), Nonce: 1})
|
||||
tx2, _ = SignNewTx(key, signer2, &AccessListTx{ChainID: big.NewInt(2), Nonce: 1})
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
tx *Transaction
|
||||
signer Signer
|
||||
wantSignerHash core.Hash
|
||||
wantSenderErr error
|
||||
wantSignErr error
|
||||
wantHash core.Hash // after signing
|
||||
}{
|
||||
{
|
||||
tx: tx0,
|
||||
signer: signer1,
|
||||
wantSignerHash: core.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"),
|
||||
wantSenderErr: ErrInvalidChainId,
|
||||
wantHash: core.HexToHash("1ccd12d8bbdb96ea391af49a35ab641e219b2dd638dea375f2bc94dd290f2549"),
|
||||
},
|
||||
{
|
||||
tx: tx1,
|
||||
signer: signer1,
|
||||
wantSenderErr: ErrInvalidSig,
|
||||
wantSignerHash: core.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"),
|
||||
wantHash: core.HexToHash("1ccd12d8bbdb96ea391af49a35ab641e219b2dd638dea375f2bc94dd290f2549"),
|
||||
},
|
||||
{
|
||||
// This checks what happens when trying to sign an unsigned tx for the wrong chain.
|
||||
tx: tx1,
|
||||
signer: signer2,
|
||||
wantSenderErr: ErrInvalidChainId,
|
||||
wantSignerHash: core.HexToHash("367967247499343401261d718ed5aa4c9486583e4d89251afce47f4a33c33362"),
|
||||
wantSignErr: ErrInvalidChainId,
|
||||
},
|
||||
{
|
||||
// This checks what happens when trying to re-sign a signed tx for the wrong chain.
|
||||
tx: tx2,
|
||||
signer: signer1,
|
||||
wantSenderErr: ErrInvalidChainId,
|
||||
wantSignerHash: core.HexToHash("846ad7672f2a3a40c1f959cd4a8ad21786d620077084d84c8d7c077714caa139"),
|
||||
wantSignErr: ErrInvalidChainId,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
sigHash := test.signer.Hash(test.tx)
|
||||
if sigHash != test.wantSignerHash {
|
||||
t.Errorf("test %d: wrong sig hash: got %x, want %x", i, sigHash, test.wantSignerHash)
|
||||
}
|
||||
sender, err := Sender(test.signer, test.tx)
|
||||
if err != test.wantSenderErr {
|
||||
t.Errorf("test %d: wrong Sender error %q", i, err)
|
||||
}
|
||||
if err == nil && sender != keyAddr {
|
||||
t.Errorf("test %d: wrong sender address %x", i, sender)
|
||||
}
|
||||
signedTx, err := SignTx(test.tx, test.signer, key)
|
||||
if err != test.wantSignErr {
|
||||
t.Fatalf("test %d: wrong SignTx error %q", i, err)
|
||||
}
|
||||
if signedTx != nil {
|
||||
if signedTx.Hash() != test.wantHash {
|
||||
t.Errorf("test %d: wrong tx hash after signing: got %x, want %x", i, signedTx.Hash(), test.wantHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEIP2718TransactionEncode(t *testing.T) {
|
||||
// RLP representation
|
||||
{
|
||||
have, err := rlp.EncodeToBytes(signedEip2718Tx)
|
||||
if err != nil {
|
||||
t.Fatalf("encode error: %v", err)
|
||||
}
|
||||
want := fromHex("b86601f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521")
|
||||
if !bytes.Equal(have, want) {
|
||||
t.Errorf("encoded RLP mismatch, got %x", have)
|
||||
}
|
||||
}
|
||||
// Binary representation
|
||||
{
|
||||
have, err := signedEip2718Tx.MarshalBinary()
|
||||
if err != nil {
|
||||
t.Fatalf("encode error: %v", err)
|
||||
}
|
||||
want := fromHex("01f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521")
|
||||
if !bytes.Equal(have, want) {
|
||||
t.Errorf("encoded RLP mismatch, got %x", have)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func decodeTx(data []byte) (*Transaction, error) {
|
||||
var tx Transaction
|
||||
t, err := &tx, rlp.Decode(bytes.NewReader(data), &tx)
|
||||
return t, err
|
||||
}
|
||||
|
||||
func defaultTestKey() (*ecdsa.PrivateKey, core.Address) {
|
||||
key, _ := crypto.HexToECDSA("45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8")
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
return key, addr
|
||||
}
|
||||
|
||||
func TestRecipientEmpty(t *testing.T) {
|
||||
_, addr := defaultTestKey()
|
||||
tx, err := decodeTx(fromHex("f8498080808080011ca09b16de9d5bdee2cf56c28d16275a4da68cd30273e2525f3959f5d62557489921a0372ebd8fb3345f7db7b5a86d42e24d36e983e259b0664ceb8c227ec9af572f3d"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
from, err := Sender(HomesteadSigner{}, tx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if addr != from {
|
||||
t.Fatal("derived address doesn't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecipientNormal(t *testing.T) {
|
||||
_, addr := defaultTestKey()
|
||||
|
||||
tx, err := decodeTx(fromHex("f85d80808094000000000000000000000000000000000000000080011ca0527c0d8f5c63f7b9f41324a7c8a563ee1190bcbf0dac8ab446291bdbf32f5c79a0552c4ef0a09a04395074dab9ed34d3fbfb843c2f2546cc30fe89ec143ca94ca6"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
from, err := Sender(HomesteadSigner{}, tx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if addr != from {
|
||||
t.Fatal("derived address doesn't match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTransactionPriceNonceSortLegacy(t *testing.T) {
|
||||
testTransactionPriceNonceSort(t, nil)
|
||||
}
|
||||
|
||||
func TestTransactionPriceNonceSort1559(t *testing.T) {
|
||||
testTransactionPriceNonceSort(t, big.NewInt(0))
|
||||
testTransactionPriceNonceSort(t, big.NewInt(5))
|
||||
testTransactionPriceNonceSort(t, big.NewInt(50))
|
||||
}
|
||||
|
||||
// Tests that transactions can be correctly sorted according to their price in
|
||||
// decreasing order, but at the same time with increasing nonces when issued by
|
||||
// the same account.
|
||||
func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) {
|
||||
// Generate a batch of accounts to start with
|
||||
keys := make([]*ecdsa.PrivateKey, 25)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
}
|
||||
signer := LatestSignerForChainID(big.NewInt(1))
|
||||
|
||||
// Generate a batch of transactions with overlapping values, but shifted nonces
|
||||
groups := map[core.Address]Transactions{}
|
||||
expectedCount := 0
|
||||
for start, key := range keys {
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
count := 25
|
||||
for i := 0; i < 25; i++ {
|
||||
var tx *Transaction
|
||||
gasFeeCap := rand.Intn(50)
|
||||
if baseFee == nil {
|
||||
tx = NewTx(&LegacyTx{
|
||||
Nonce: uint64(start + i),
|
||||
To: &core.Address{},
|
||||
Value: big.NewInt(100),
|
||||
Gas: 100,
|
||||
GasPrice: big.NewInt(int64(gasFeeCap)),
|
||||
Data: nil,
|
||||
})
|
||||
} else {
|
||||
tx = NewTx(&DynamicFeeTx{
|
||||
Nonce: uint64(start + i),
|
||||
To: &core.Address{},
|
||||
Value: big.NewInt(100),
|
||||
Gas: 100,
|
||||
GasFeeCap: big.NewInt(int64(gasFeeCap)),
|
||||
GasTipCap: big.NewInt(int64(rand.Intn(gasFeeCap + 1))),
|
||||
Data: nil,
|
||||
})
|
||||
if count == 25 && int64(gasFeeCap) < baseFee.Int64() {
|
||||
count = i
|
||||
}
|
||||
}
|
||||
tx, err := SignTx(tx, signer, key)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to sign tx: %s", err)
|
||||
}
|
||||
groups[addr] = append(groups[addr], tx)
|
||||
}
|
||||
expectedCount += count
|
||||
}
|
||||
// Sort the transactions and cross check the nonce ordering
|
||||
txset := NewTransactionsByPriceAndNonce(signer, groups, baseFee)
|
||||
|
||||
txs := Transactions{}
|
||||
for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
|
||||
txs = append(txs, tx)
|
||||
txset.Shift()
|
||||
}
|
||||
if len(txs) != expectedCount {
|
||||
t.Errorf("expected %d transactions, found %d", expectedCount, len(txs))
|
||||
}
|
||||
for i, txi := range txs {
|
||||
fromi, _ := Sender(signer, txi)
|
||||
|
||||
// Make sure the nonce order is valid
|
||||
for j, txj := range txs[i+1:] {
|
||||
fromj, _ := Sender(signer, txj)
|
||||
if fromi == fromj && txi.Nonce() > txj.Nonce() {
|
||||
t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce())
|
||||
}
|
||||
}
|
||||
// If the next tx has different from account, the price must be lower than the current one
|
||||
if i+1 < len(txs) {
|
||||
next := txs[i+1]
|
||||
fromNext, _ := Sender(signer, next)
|
||||
tip, err := txi.EffectiveGasTip(baseFee)
|
||||
nextTip, nextErr := next.EffectiveGasTip(baseFee)
|
||||
if err != nil || nextErr != nil {
|
||||
t.Errorf("error calculating effective tip")
|
||||
}
|
||||
if fromi != fromNext && tip.Cmp(nextTip) < 0 {
|
||||
t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that if multiple transactions have the same price, the ones seen earlier
|
||||
// are prioritized to avoid network spam attacks aiming for a specific ordering.
|
||||
func TestTransactionTimeSort(t *testing.T) {
|
||||
// Generate a batch of accounts to start with
|
||||
keys := make([]*ecdsa.PrivateKey, 5)
|
||||
for i := 0; i < len(keys); i++ {
|
||||
keys[i], _ = crypto.GenerateKey()
|
||||
}
|
||||
signer := HomesteadSigner{}
|
||||
|
||||
// Generate a batch of transactions with overlapping prices, but different creation times
|
||||
groups := map[core.Address]Transactions{}
|
||||
for start, key := range keys {
|
||||
addr := crypto.PubkeyToAddress(key.PublicKey)
|
||||
|
||||
tx, _ := SignTx(NewTransaction(0, core.Address{}, big.NewInt(100), 100, big.NewInt(1), nil), signer, key)
|
||||
tx.time = time.Unix(0, int64(len(keys)-start))
|
||||
|
||||
groups[addr] = append(groups[addr], tx)
|
||||
}
|
||||
// Sort the transactions and cross check the nonce ordering
|
||||
txset := NewTransactionsByPriceAndNonce(signer, groups, nil)
|
||||
|
||||
txs := Transactions{}
|
||||
for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
|
||||
txs = append(txs, tx)
|
||||
txset.Shift()
|
||||
}
|
||||
if len(txs) != len(keys) {
|
||||
t.Errorf("expected %d transactions, found %d", len(keys), len(txs))
|
||||
}
|
||||
for i, txi := range txs {
|
||||
fromi, _ := Sender(signer, txi)
|
||||
if i+1 < len(txs) {
|
||||
next := txs[i+1]
|
||||
fromNext, _ := Sender(signer, next)
|
||||
|
||||
if txi.GasPrice().Cmp(next.GasPrice()) < 0 {
|
||||
t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice())
|
||||
}
|
||||
// Make sure time order is ascending if the txs have the same gas price
|
||||
if txi.GasPrice().Cmp(next.GasPrice()) == 0 && txi.time.After(next.time) {
|
||||
t.Errorf("invalid received time ordering: tx #%d (A=%x T=%v) > tx #%d (A=%x T=%v)", i, fromi[:4], txi.time, i+1, fromNext[:4], next.time)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTransactionCoding tests serializing/de-serializing to/from rlp and JSON.
|
||||
func TestTransactionCoding(t *testing.T) {
|
||||
key, err := crypto.GenerateKey()
|
||||
if err != nil {
|
||||
t.Fatalf("could not generate key: %v", err)
|
||||
}
|
||||
var (
|
||||
signer = NewEIP2930Signer(big.NewInt(1))
|
||||
addr = core.HexToAddress("0x0000000000000000000000000000000000000001")
|
||||
recipient = core.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87")
|
||||
accesses = AccessList{{Address: addr, StorageKeys: []core.Hash{{0}}}}
|
||||
)
|
||||
for i := uint64(0); i < 500; i++ {
|
||||
var txdata TxData
|
||||
switch i % 5 {
|
||||
case 0:
|
||||
// Legacy tx.
|
||||
txdata = &LegacyTx{
|
||||
Nonce: i,
|
||||
To: &recipient,
|
||||
Gas: 1,
|
||||
GasPrice: big.NewInt(2),
|
||||
Data: []byte("abcdef"),
|
||||
}
|
||||
case 1:
|
||||
// Legacy tx contract creation.
|
||||
txdata = &LegacyTx{
|
||||
Nonce: i,
|
||||
Gas: 1,
|
||||
GasPrice: big.NewInt(2),
|
||||
Data: []byte("abcdef"),
|
||||
}
|
||||
case 2:
|
||||
// Tx with non-zero access list.
|
||||
txdata = &AccessListTx{
|
||||
ChainID: big.NewInt(1),
|
||||
Nonce: i,
|
||||
To: &recipient,
|
||||
Gas: 123457,
|
||||
GasPrice: big.NewInt(10),
|
||||
AccessList: accesses,
|
||||
Data: []byte("abcdef"),
|
||||
}
|
||||
case 3:
|
||||
// Tx with empty access list.
|
||||
txdata = &AccessListTx{
|
||||
ChainID: big.NewInt(1),
|
||||
Nonce: i,
|
||||
To: &recipient,
|
||||
Gas: 123457,
|
||||
GasPrice: big.NewInt(10),
|
||||
Data: []byte("abcdef"),
|
||||
}
|
||||
case 4:
|
||||
// Contract creation with access list.
|
||||
txdata = &AccessListTx{
|
||||
ChainID: big.NewInt(1),
|
||||
Nonce: i,
|
||||
Gas: 123457,
|
||||
GasPrice: big.NewInt(10),
|
||||
AccessList: accesses,
|
||||
}
|
||||
}
|
||||
tx, err := SignNewTx(key, signer, txdata)
|
||||
if err != nil {
|
||||
t.Fatalf("could not sign transaction: %v", err)
|
||||
}
|
||||
// RLP
|
||||
parsedTx, err := encodeDecodeBinary(tx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertEqual(parsedTx, tx)
|
||||
|
||||
// JSON
|
||||
parsedTx, err = encodeDecodeJSON(tx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assertEqual(parsedTx, tx)
|
||||
}
|
||||
}
|
||||
|
||||
func encodeDecodeJSON(tx *Transaction) (*Transaction, error) {
|
||||
data, err := json.Marshal(tx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("json encoding failed: %v", err)
|
||||
}
|
||||
var parsedTx = &Transaction{}
|
||||
if err := json.Unmarshal(data, &parsedTx); err != nil {
|
||||
return nil, fmt.Errorf("json decoding failed: %v", err)
|
||||
}
|
||||
return parsedTx, nil
|
||||
}
|
||||
|
||||
func encodeDecodeBinary(tx *Transaction) (*Transaction, error) {
|
||||
data, err := tx.MarshalBinary()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rlp encoding failed: %v", err)
|
||||
}
|
||||
var parsedTx = &Transaction{}
|
||||
if err := parsedTx.UnmarshalBinary(data); err != nil {
|
||||
return nil, fmt.Errorf("rlp decoding failed: %v", err)
|
||||
}
|
||||
return parsedTx, nil
|
||||
}
|
||||
|
||||
func assertEqual(orig *Transaction, cpy *Transaction) error {
|
||||
// compare nonce, price, gaslimit, recipient, amount, payload, V, R, S
|
||||
if want, got := orig.Hash(), cpy.Hash(); want != got {
|
||||
return fmt.Errorf("parsed tx differs from original tx, want %v, got %v", want, got)
|
||||
}
|
||||
if want, got := orig.ChainId(), cpy.ChainId(); want.Cmp(got) != 0 {
|
||||
return fmt.Errorf("invalid chain id, want %d, got %d", want, got)
|
||||
}
|
||||
if orig.AccessList() != nil {
|
||||
if !reflect.DeepEqual(orig.AccessList(), cpy.AccessList()) {
|
||||
return fmt.Errorf("access list wrong!")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user