Skip to content

Commit

Permalink
core/types: transaction and receipt encoding/decoding optimizations (e…
Browse files Browse the repository at this point in the history
…thereum#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 <[email protected]>
  • Loading branch information
fjl and holiman authored Aug 25, 2023
1 parent 6b98d18 commit 9bbb9df
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 53 deletions.
24 changes: 12 additions & 12 deletions core/types/gen_log_json.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion core/types/gen_log_rlp.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions core/types/hashing.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package types

import (
"bytes"
"fmt"
"math"
"sync"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -36,6 +38,22 @@ var encodeBufferPool = sync.Pool{
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.
func rlpHash(x interface{}) (h common.Hash) {
sha := hasherPool.Get().(crypto.KeccakState)
Expand Down
41 changes: 7 additions & 34 deletions core/types/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@
package types

import (
"io"

"github.com/ethereum/go-ethereum/common"
"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

// Log represents a contract log event. These events are generated by the LOG opcode and
Expand All @@ -40,19 +38,19 @@ type Log struct {
// 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"`
BlockNumber uint64 `json:"blockNumber" rlp:"-"`
// 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
TxIndex uint `json:"transactionIndex"`
TxIndex uint `json:"transactionIndex" rlp:"-"`
// 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 uint `json:"logIndex"`
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"`
Removed bool `json:"removed" rlp:"-"`
}

type logMarshaling struct {
Expand All @@ -61,28 +59,3 @@ type logMarshaling struct {
TxIndex 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
}
12 changes: 9 additions & 3 deletions core/types/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func (r *Receipt) MarshalBinary() ([]byte, error) {
// 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()
kind, size, err := s.Kind()
switch {
case err != nil:
return err
Expand All @@ -165,12 +165,18 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
}
r.Type = LegacyTxType
return r.setFromRLP(dec)
case kind == rlp.Byte:
return errShortTypedReceipt
default:
// It's an EIP-2718 typed tx receipt.
b, err := s.Bytes()
b, buf, err := getPooledBuffer(size)
if err != nil {
return err
}
defer encodeBufferPool.Put(buf)
if err := s.ReadBytes(b); err != nil {
return err
}
return r.decodeTyped(b)
}
}
Expand Down Expand Up @@ -264,7 +270,7 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error {
w.WriteUint64(r.CumulativeGasUsed)
logList := w.List()
for _, log := range r.Logs {
if err := rlp.Encode(w, log); err != nil {
if err := log.EncodeRLP(w); err != nil {
return err
}
}
Expand Down
14 changes: 11 additions & 3 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,23 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
tx.setDecoded(&inner, rlp.ListSize(size))
}
return err
case kind == rlp.Byte:
return errShortTypedTx
default:
// It's an EIP-2718 typed TX envelope.
var b []byte
if b, err = s.Bytes(); err != nil {
// First read the tx payload bytes into a temporary buffer.
b, buf, err := getPooledBuffer(size)
if err != nil {
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)
if err == nil {
tx.setDecoded(inner, uint64(len(b)))
tx.setDecoded(inner, size)
}
return err
}
Expand Down

0 comments on commit 9bbb9df

Please sign in to comment.