core/types: transaction and receipt encoding/decoding optimizations (#27976)

Just some minor optimizations I figured out a while ago. By using ReadBytes instead of
Bytes on the rlp stream, we can save the allocation of a temporary buffer for the typed tx
payload.

If kind == rlp.Byte, the size reported by Stream.Kind will be zero, but we need a buffer
of size 1 for ReadBytes. Since typed txs always have to be longer than 1 byte, we can just
return an error for kind == rlp.Byte.

There is a also a small change for Log: since the first three fields of Log are the ones that 
should appear in the canon encoding, we can simply ignore the remaining fields via 
struct tag. Doing this removes an indirection through the rlpLog type.

---------

Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
Felix Lange 2023-08-25 20:03:41 +02:00 committed by GitHub
parent 6b98d18789
commit 9bbb9df185
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 58 additions and 53 deletions

View File

@ -18,12 +18,12 @@ func (l Log) MarshalJSON() ([]byte, error) {
Address common.Address `json:"address" gencodec:"required"` Address common.Address `json:"address" gencodec:"required"`
Topics []common.Hash `json:"topics" gencodec:"required"` Topics []common.Hash `json:"topics" gencodec:"required"`
Data hexutil.Bytes `json:"data" gencodec:"required"` Data hexutil.Bytes `json:"data" gencodec:"required"`
BlockNumber hexutil.Uint64 `json:"blockNumber"` BlockNumber hexutil.Uint64 `json:"blockNumber" rlp:"-"`
TxHash common.Hash `json:"transactionHash" gencodec:"required"` TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
TxIndex hexutil.Uint `json:"transactionIndex"` TxIndex hexutil.Uint `json:"transactionIndex" rlp:"-"`
BlockHash common.Hash `json:"blockHash"` BlockHash common.Hash `json:"blockHash" rlp:"-"`
Index hexutil.Uint `json:"logIndex"` Index hexutil.Uint `json:"logIndex" rlp:"-"`
Removed bool `json:"removed"` Removed bool `json:"removed" rlp:"-"`
} }
var enc Log var enc Log
enc.Address = l.Address enc.Address = l.Address
@ -44,12 +44,12 @@ func (l *Log) UnmarshalJSON(input []byte) error {
Address *common.Address `json:"address" gencodec:"required"` Address *common.Address `json:"address" gencodec:"required"`
Topics []common.Hash `json:"topics" gencodec:"required"` Topics []common.Hash `json:"topics" gencodec:"required"`
Data *hexutil.Bytes `json:"data" gencodec:"required"` Data *hexutil.Bytes `json:"data" gencodec:"required"`
BlockNumber *hexutil.Uint64 `json:"blockNumber"` BlockNumber *hexutil.Uint64 `json:"blockNumber" rlp:"-"`
TxHash *common.Hash `json:"transactionHash" gencodec:"required"` TxHash *common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
TxIndex *hexutil.Uint `json:"transactionIndex"` TxIndex *hexutil.Uint `json:"transactionIndex" rlp:"-"`
BlockHash *common.Hash `json:"blockHash"` BlockHash *common.Hash `json:"blockHash" rlp:"-"`
Index *hexutil.Uint `json:"logIndex"` Index *hexutil.Uint `json:"logIndex" rlp:"-"`
Removed *bool `json:"removed"` Removed *bool `json:"removed" rlp:"-"`
} }
var dec Log var dec Log
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {

View File

@ -8,7 +8,7 @@ package types
import "github.com/ethereum/go-ethereum/rlp" import "github.com/ethereum/go-ethereum/rlp"
import "io" import "io"
func (obj *rlpLog) EncodeRLP(_w io.Writer) error { func (obj *Log) EncodeRLP(_w io.Writer) error {
w := rlp.NewEncoderBuffer(_w) w := rlp.NewEncoderBuffer(_w)
_tmp0 := w.List() _tmp0 := w.List()
w.WriteBytes(obj.Address[:]) w.WriteBytes(obj.Address[:])

View File

@ -18,6 +18,8 @@ package types
import ( import (
"bytes" "bytes"
"fmt"
"math"
"sync" "sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -36,6 +38,22 @@ var encodeBufferPool = sync.Pool{
New: func() interface{} { return new(bytes.Buffer) }, New: func() interface{} { return new(bytes.Buffer) },
} }
// getPooledBuffer retrieves a buffer from the pool and creates a byte slice of the
// requested size from it.
//
// The caller should return the *bytes.Buffer object back into encodeBufferPool after use!
// The returned byte slice must not be used after returning the buffer.
func getPooledBuffer(size uint64) ([]byte, *bytes.Buffer, error) {
if size > math.MaxInt {
return nil, nil, fmt.Errorf("can't get buffer of size %d", size)
}
buf := encodeBufferPool.Get().(*bytes.Buffer)
buf.Reset()
buf.Grow(int(size))
b := buf.Bytes()[:int(size)]
return b, buf, nil
}
// rlpHash encodes x and hashes the encoded bytes. // rlpHash encodes x and hashes the encoded bytes.
func rlpHash(x interface{}) (h common.Hash) { func rlpHash(x interface{}) (h common.Hash) {
sha := hasherPool.Get().(crypto.KeccakState) sha := hasherPool.Get().(crypto.KeccakState)

View File

@ -17,13 +17,11 @@
package types package types
import ( import (
"io"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp"
) )
//go:generate go run ../../rlp/rlpgen -type Log -out gen_log_rlp.go
//go:generate go run github.com/fjl/gencodec -type Log -field-override logMarshaling -out gen_log_json.go //go:generate go run github.com/fjl/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 // Log represents a contract log event. These events are generated by the LOG opcode and
@ -40,19 +38,19 @@ type Log struct {
// Derived fields. These fields are filled in by the node // Derived fields. These fields are filled in by the node
// but not secured by consensus. // but not secured by consensus.
// block in which the transaction was included // block in which the transaction was included
BlockNumber uint64 `json:"blockNumber"` BlockNumber uint64 `json:"blockNumber" rlp:"-"`
// hash of the transaction // hash of the transaction
TxHash common.Hash `json:"transactionHash" gencodec:"required"` TxHash common.Hash `json:"transactionHash" gencodec:"required" rlp:"-"`
// index of the transaction in the block // index of the transaction in the block
TxIndex uint `json:"transactionIndex"` TxIndex uint `json:"transactionIndex" rlp:"-"`
// hash of the block in which the transaction was included // hash of the block in which the transaction was included
BlockHash common.Hash `json:"blockHash"` BlockHash common.Hash `json:"blockHash" rlp:"-"`
// index of the log in the block // index of the log in the block
Index uint `json:"logIndex"` Index uint `json:"logIndex" rlp:"-"`
// The Removed field is true if this log was reverted due to a chain reorganisation. // 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. // You must pay attention to this field if you receive logs through a filter query.
Removed bool `json:"removed"` Removed bool `json:"removed" rlp:"-"`
} }
type logMarshaling struct { type logMarshaling struct {
@ -61,28 +59,3 @@ type logMarshaling struct {
TxIndex hexutil.Uint TxIndex hexutil.Uint
Index hexutil.Uint Index hexutil.Uint
} }
//go:generate go run ../../rlp/rlpgen -type rlpLog -out gen_log_rlp.go
// rlpLog is used to RLP-encode both the consensus and storage formats.
type rlpLog struct {
Address common.Address
Topics []common.Hash
Data []byte
}
// EncodeRLP implements rlp.Encoder.
func (l *Log) EncodeRLP(w io.Writer) error {
rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data}
return rlp.Encode(w, &rl)
}
// DecodeRLP implements rlp.Decoder.
func (l *Log) DecodeRLP(s *rlp.Stream) error {
var dec rlpLog
err := s.Decode(&dec)
if err == nil {
l.Address, l.Topics, l.Data = dec.Address, dec.Topics, dec.Data
}
return err
}

View File

@ -153,7 +153,7 @@ func (r *Receipt) MarshalBinary() ([]byte, error) {
// DecodeRLP implements rlp.Decoder, and loads the consensus fields of a receipt // DecodeRLP implements rlp.Decoder, and loads the consensus fields of a receipt
// from an RLP stream. // from an RLP stream.
func (r *Receipt) DecodeRLP(s *rlp.Stream) error { func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
kind, _, err := s.Kind() kind, size, err := s.Kind()
switch { switch {
case err != nil: case err != nil:
return err return err
@ -165,12 +165,18 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
} }
r.Type = LegacyTxType r.Type = LegacyTxType
return r.setFromRLP(dec) return r.setFromRLP(dec)
case kind == rlp.Byte:
return errShortTypedReceipt
default: default:
// It's an EIP-2718 typed tx receipt. // It's an EIP-2718 typed tx receipt.
b, err := s.Bytes() b, buf, err := getPooledBuffer(size)
if err != nil { if err != nil {
return err return err
} }
defer encodeBufferPool.Put(buf)
if err := s.ReadBytes(b); err != nil {
return err
}
return r.decodeTyped(b) return r.decodeTyped(b)
} }
} }
@ -264,7 +270,7 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error {
w.WriteUint64(r.CumulativeGasUsed) w.WriteUint64(r.CumulativeGasUsed)
logList := w.List() logList := w.List()
for _, log := range r.Logs { for _, log := range r.Logs {
if err := rlp.Encode(w, log); err != nil { if err := log.EncodeRLP(w); err != nil {
return err return err
} }
} }

View File

@ -145,15 +145,23 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
tx.setDecoded(&inner, rlp.ListSize(size)) tx.setDecoded(&inner, rlp.ListSize(size))
} }
return err return err
case kind == rlp.Byte:
return errShortTypedTx
default: default:
// It's an EIP-2718 typed TX envelope. // It's an EIP-2718 typed TX envelope.
var b []byte // First read the tx payload bytes into a temporary buffer.
if b, err = s.Bytes(); err != nil { b, buf, err := getPooledBuffer(size)
if err != nil {
return err return err
} }
defer encodeBufferPool.Put(buf)
if err := s.ReadBytes(b); err != nil {
return err
}
// Now decode the inner transaction.
inner, err := tx.decodeTyped(b) inner, err := tx.decodeTyped(b)
if err == nil { if err == nil {
tx.setDecoded(inner, uint64(len(b))) tx.setDecoded(inner, size)
} }
return err return err
} }