diff --git a/core/types.go b/core/types.go
index 618f82e..d9639ff 100644
--- a/core/types.go
+++ b/core/types.go
@@ -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
+}
diff --git a/go.mod b/go.mod
index 3d73e23..799397f 100644
--- a/go.mod
+++ b/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
+)
diff --git a/go.sum b/go.sum
index 6252fa8..0567f18 100644
--- a/go.sum
+++ b/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=
diff --git a/restricted/interface.go b/restricted/interface.go
index d776129..8e84d62 100644
--- a/restricted/interface.go
+++ b/restricted/interface.go
@@ -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
}
diff --git a/restricted/types/access_list_tx.go b/restricted/types/access_list_tx.go
new file mode 100644
index 0000000..7c92bbb
--- /dev/null
+++ b/restricted/types/access_list_tx.go
@@ -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 .
+
+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
+}
diff --git a/restricted/types/block.go b/restricted/types/block.go
new file mode 100644
index 0000000..2ff3e11
--- /dev/null
+++ b/restricted/types/block.go
@@ -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 .
+
+// 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
diff --git a/restricted/types/bloom9.go b/restricted/types/bloom9.go
new file mode 100644
index 0000000..1fd12a7
--- /dev/null
+++ b/restricted/types/bloom9.go
@@ -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 .
+
+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())
+}
diff --git a/restricted/types/dynamic_fee_tx.go b/restricted/types/dynamic_fee_tx.go
new file mode 100644
index 0000000..fcf763f
--- /dev/null
+++ b/restricted/types/dynamic_fee_tx.go
@@ -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 .
+
+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
+}
diff --git a/restricted/types/gen_access_tuple.go b/restricted/types/gen_access_tuple.go
new file mode 100644
index 0000000..f2a8853
--- /dev/null
+++ b/restricted/types/gen_access_tuple.go
@@ -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
+}
diff --git a/restricted/types/gen_header_json.go b/restricted/types/gen_header_json.go
new file mode 100644
index 0000000..081eba7
--- /dev/null
+++ b/restricted/types/gen_header_json.go
@@ -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
+}
diff --git a/restricted/types/gen_log_json.go b/restricted/types/gen_log_json.go
new file mode 100644
index 0000000..e0af499
--- /dev/null
+++ b/restricted/types/gen_log_json.go
@@ -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
+}
diff --git a/restricted/types/gen_receipt_json.go b/restricted/types/gen_receipt_json.go
new file mode 100644
index 0000000..9317fcb
--- /dev/null
+++ b/restricted/types/gen_receipt_json.go
@@ -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
+}
diff --git a/restricted/types/hashing.go b/restricted/types/hashing.go
new file mode 100644
index 0000000..886d957
--- /dev/null
+++ b/restricted/types/hashing.go
@@ -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 .
+
+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()
+}
diff --git a/restricted/types/hashing_test.go b/restricted/types/hashing_test.go
new file mode 100644
index 0000000..8082e77
--- /dev/null
+++ b/restricted/types/hashing_test.go
@@ -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 .
+
+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{}
+}
diff --git a/restricted/types/legacy_tx.go b/restricted/types/legacy_tx.go
new file mode 100644
index 0000000..01b5144
--- /dev/null
+++ b/restricted/types/legacy_tx.go
@@ -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 .
+
+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
+}
diff --git a/restricted/types/log.go b/restricted/types/log.go
new file mode 100644
index 0000000..3175185
--- /dev/null
+++ b/restricted/types/log.go
@@ -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 .
+
+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
+}
diff --git a/restricted/types/log_test.go b/restricted/types/log_test.go
new file mode 100644
index 0000000..cbc58da
--- /dev/null
+++ b/restricted/types/log_test.go
@@ -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 .
+
+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
+}
diff --git a/restricted/types/receipt.go b/restricted/types/receipt.go
new file mode 100644
index 0000000..62c1ee1
--- /dev/null
+++ b/restricted/types/receipt.go
@@ -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 .
+
+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
+}
diff --git a/restricted/types/receipt_test.go b/restricted/types/receipt_test.go
new file mode 100644
index 0000000..d43e8e1
--- /dev/null
+++ b/restricted/types/receipt_test.go
@@ -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 .
+
+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 ", 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
+}
diff --git a/restricted/types/transaction.go b/restricted/types/transaction.go
new file mode 100644
index 0000000..3d122ee
--- /dev/null
+++ b/restricted/types/transaction.go
@@ -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 .
+
+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 }
diff --git a/restricted/types/transaction_marshalling.go b/restricted/types/transaction_marshalling.go
new file mode 100644
index 0000000..89dec5e
--- /dev/null
+++ b/restricted/types/transaction_marshalling.go
@@ -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 .
+
+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
+}
diff --git a/restricted/types/transaction_signing.go b/restricted/types/transaction_signing.go
new file mode 100644
index 0000000..93c9e8d
--- /dev/null
+++ b/restricted/types/transaction_signing.go
@@ -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 .
+
+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))
+}
diff --git a/restricted/types/transaction_signing_test.go b/restricted/types/transaction_signing_test.go
new file mode 100644
index 0000000..d9af532
--- /dev/null
+++ b/restricted/types/transaction_signing_test.go
@@ -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 .
+
+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")
+ }
+}
diff --git a/restricted/types/transaction_test.go b/restricted/types/transaction_test.go
new file mode 100644
index 0000000..397e2b8
--- /dev/null
+++ b/restricted/types/transaction_test.go
@@ -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 .
+
+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
+}