core/types: faster RLP encoding of Header, StateAcccount, ReceiptForStorage (#24420)
This change makes use of the new code generator rlp/rlpgen to improve the performance of RLP encoding for Header and StateAccount. It also speeds up encoding of ReceiptForStorage using the new rlp.EncoderBuffer API. The change is much less transparent than I wanted it to be, because Header and StateAccount now have an EncodeRLP method defined with pointer receiver. It used to be possible to encode non-pointer values of these types, but the new method prevents that and attempting to encode unadressable values (even if part of another value) will return an error. The error can be surprising and may pop up in places that previously didn't expect any errors. To make things work, I also needed to update all code paths (mostly in unit tests) that lead to encoding of non-pointer values, and pass a pointer instead. Benchmark results: name old time/op new time/op delta EncodeRLP/legacy-header-8 328ns ± 0% 237ns ± 1% -27.63% (p=0.000 n=8+8) EncodeRLP/london-header-8 353ns ± 0% 247ns ± 1% -30.06% (p=0.000 n=8+8) EncodeRLP/receipt-for-storage-8 237ns ± 0% 123ns ± 0% -47.86% (p=0.000 n=8+7) EncodeRLP/receipt-full-8 297ns ± 0% 301ns ± 1% +1.39% (p=0.000 n=8+8) name old speed new speed delta EncodeRLP/legacy-header-8 1.66GB/s ± 0% 2.29GB/s ± 1% +38.19% (p=0.000 n=8+8) EncodeRLP/london-header-8 1.55GB/s ± 0% 2.22GB/s ± 1% +42.99% (p=0.000 n=8+8) EncodeRLP/receipt-for-storage-8 38.0MB/s ± 0% 64.8MB/s ± 0% +70.48% (p=0.000 n=8+7) EncodeRLP/receipt-full-8 910MB/s ± 0% 897MB/s ± 1% -1.37% (p=0.000 n=8+8) name old alloc/op new alloc/op delta EncodeRLP/legacy-header-8 0.00B 0.00B ~ (all equal) EncodeRLP/london-header-8 0.00B 0.00B ~ (all equal) EncodeRLP/receipt-for-storage-8 64.0B ± 0% 0.0B -100.00% (p=0.000 n=8+8) EncodeRLP/receipt-full-8 320B ± 0% 320B ± 0% ~ (all equal)
This commit is contained in:
@ -22,7 +22,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
//go:generate gencodec -type AccessTuple -out gen_access_tuple.go
|
||||
//go:generate go run github.com/fjl/gencodec@latest -type AccessTuple -out gen_access_tuple.go
|
||||
|
||||
// AccessList is an EIP-2930 access list.
|
||||
type AccessList []AccessTuple
|
||||
|
@ -63,7 +63,8 @@ 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
|
||||
//go:generate go run github.com/fjl/gencodec@latest -type Header -field-override headerMarshaling -out gen_header_json.go
|
||||
//go:generate go run ../../rlp/rlpgen -type Header -out gen_header_rlp.go
|
||||
|
||||
// Header represents a block header in the Ethereum blockchain.
|
||||
type Header struct {
|
||||
|
@ -285,7 +285,7 @@ func makeBenchBlock() *Block {
|
||||
func TestRlpDecodeParentHash(t *testing.T) {
|
||||
// A minimum one
|
||||
want := common.HexToHash("0x112233445566778899001122334455667788990011223344556677889900aabb")
|
||||
if rlpData, err := rlp.EncodeToBytes(Header{ParentHash: want}); err != nil {
|
||||
if rlpData, err := rlp.EncodeToBytes(&Header{ParentHash: want}); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
if have := HeaderParentHashFromRLP(rlpData); have != want {
|
||||
@ -299,7 +299,7 @@ func TestRlpDecodeParentHash(t *testing.T) {
|
||||
// | BaseFee | dynamic| *big.Int | 64 bits |
|
||||
mainnetTd := new(big.Int)
|
||||
mainnetTd.SetString("5ad3c2c71bbff854908", 16)
|
||||
if rlpData, err := rlp.EncodeToBytes(Header{
|
||||
if rlpData, err := rlp.EncodeToBytes(&Header{
|
||||
ParentHash: want,
|
||||
Difficulty: mainnetTd,
|
||||
Number: new(big.Int).SetUint64(math.MaxUint64),
|
||||
@ -316,7 +316,7 @@ func TestRlpDecodeParentHash(t *testing.T) {
|
||||
{
|
||||
// The rlp-encoding of the heder belowCauses _total_ length of 65540,
|
||||
// which is the first to blow the fast-path.
|
||||
h := Header{
|
||||
h := &Header{
|
||||
ParentHash: want,
|
||||
Extra: make([]byte, 65041),
|
||||
}
|
||||
|
27
core/types/gen_account_rlp.go
Normal file
27
core/types/gen_account_rlp.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Code generated by rlpgen. DO NOT EDIT.
|
||||
|
||||
//go:build !norlpgen
|
||||
// +build !norlpgen
|
||||
|
||||
package types
|
||||
|
||||
import "github.com/ethereum/go-ethereum/rlp"
|
||||
import "io"
|
||||
|
||||
func (obj *StateAccount) EncodeRLP(_w io.Writer) error {
|
||||
w := rlp.NewEncoderBuffer(_w)
|
||||
_tmp0 := w.List()
|
||||
w.WriteUint64(obj.Nonce)
|
||||
if obj.Balance == nil {
|
||||
w.Write(rlp.EmptyString)
|
||||
} else {
|
||||
if obj.Balance.Sign() == -1 {
|
||||
return rlp.ErrNegativeBigInt
|
||||
}
|
||||
w.WriteBigInt(obj.Balance)
|
||||
}
|
||||
w.WriteBytes(obj.Root[:])
|
||||
w.WriteBytes(obj.CodeHash)
|
||||
w.ListEnd(_tmp0)
|
||||
return w.Flush()
|
||||
}
|
56
core/types/gen_header_rlp.go
Normal file
56
core/types/gen_header_rlp.go
Normal file
@ -0,0 +1,56 @@
|
||||
// Code generated by rlpgen. DO NOT EDIT.
|
||||
|
||||
//go:build !norlpgen
|
||||
// +build !norlpgen
|
||||
|
||||
package types
|
||||
|
||||
import "github.com/ethereum/go-ethereum/rlp"
|
||||
import "io"
|
||||
|
||||
func (obj *Header) EncodeRLP(_w io.Writer) error {
|
||||
w := rlp.NewEncoderBuffer(_w)
|
||||
_tmp0 := w.List()
|
||||
w.WriteBytes(obj.ParentHash[:])
|
||||
w.WriteBytes(obj.UncleHash[:])
|
||||
w.WriteBytes(obj.Coinbase[:])
|
||||
w.WriteBytes(obj.Root[:])
|
||||
w.WriteBytes(obj.TxHash[:])
|
||||
w.WriteBytes(obj.ReceiptHash[:])
|
||||
w.WriteBytes(obj.Bloom[:])
|
||||
if obj.Difficulty == nil {
|
||||
w.Write(rlp.EmptyString)
|
||||
} else {
|
||||
if obj.Difficulty.Sign() == -1 {
|
||||
return rlp.ErrNegativeBigInt
|
||||
}
|
||||
w.WriteBigInt(obj.Difficulty)
|
||||
}
|
||||
if obj.Number == nil {
|
||||
w.Write(rlp.EmptyString)
|
||||
} else {
|
||||
if obj.Number.Sign() == -1 {
|
||||
return rlp.ErrNegativeBigInt
|
||||
}
|
||||
w.WriteBigInt(obj.Number)
|
||||
}
|
||||
w.WriteUint64(obj.GasLimit)
|
||||
w.WriteUint64(obj.GasUsed)
|
||||
w.WriteUint64(obj.Time)
|
||||
w.WriteBytes(obj.Extra)
|
||||
w.WriteBytes(obj.MixDigest[:])
|
||||
w.WriteBytes(obj.Nonce[:])
|
||||
_tmp1 := obj.BaseFee != nil
|
||||
if _tmp1 {
|
||||
if obj.BaseFee == nil {
|
||||
w.Write(rlp.EmptyString)
|
||||
} else {
|
||||
if obj.BaseFee.Sign() == -1 {
|
||||
return rlp.ErrNegativeBigInt
|
||||
}
|
||||
w.WriteBigInt(obj.BaseFee)
|
||||
}
|
||||
}
|
||||
w.ListEnd(_tmp0)
|
||||
return w.Flush()
|
||||
}
|
23
core/types/gen_log_rlp.go
Normal file
23
core/types/gen_log_rlp.go
Normal file
@ -0,0 +1,23 @@
|
||||
// Code generated by rlpgen. DO NOT EDIT.
|
||||
|
||||
//go:build !norlpgen
|
||||
// +build !norlpgen
|
||||
|
||||
package types
|
||||
|
||||
import "github.com/ethereum/go-ethereum/rlp"
|
||||
import "io"
|
||||
|
||||
func (obj *rlpLog) EncodeRLP(_w io.Writer) error {
|
||||
w := rlp.NewEncoderBuffer(_w)
|
||||
_tmp0 := w.List()
|
||||
w.WriteBytes(obj.Address[:])
|
||||
_tmp1 := w.List()
|
||||
for _, _tmp2 := range obj.Topics {
|
||||
w.WriteBytes(_tmp2[:])
|
||||
}
|
||||
w.ListEnd(_tmp1)
|
||||
w.WriteBytes(obj.Data)
|
||||
w.ListEnd(_tmp0)
|
||||
return w.Flush()
|
||||
}
|
@ -24,7 +24,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
//go:generate gencodec -type Log -field-override logMarshaling -out gen_log_json.go
|
||||
//go:generate go run github.com/fjl/gencodec@latest -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.
|
||||
@ -62,15 +62,14 @@ type logMarshaling struct {
|
||||
Index hexutil.Uint
|
||||
}
|
||||
|
||||
//go:generate go run ../../rlp/rlpgen -type rlpLog -out gen_log_rlp.go
|
||||
|
||||
type rlpLog struct {
|
||||
Address common.Address
|
||||
Topics []common.Hash
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// rlpStorageLog is the storage encoding of a log.
|
||||
type rlpStorageLog rlpLog
|
||||
|
||||
// legacyRlpStorageLog is the previous storage encoding of a log including some redundant fields.
|
||||
type legacyRlpStorageLog struct {
|
||||
Address common.Address
|
||||
@ -85,7 +84,8 @@ type legacyRlpStorageLog struct {
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (l *Log) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data})
|
||||
rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data}
|
||||
return rlp.Encode(w, &rl)
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder.
|
||||
@ -104,11 +104,8 @@ type LogForStorage Log
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (l *LogForStorage) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, rlpStorageLog{
|
||||
Address: l.Address,
|
||||
Topics: l.Topics,
|
||||
Data: l.Data,
|
||||
})
|
||||
rl := rlpLog{Address: l.Address, Topics: l.Topics, Data: l.Data}
|
||||
return rlp.Encode(w, &rl)
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder.
|
||||
@ -119,7 +116,7 @@ func (l *LogForStorage) DecodeRLP(s *rlp.Stream) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var dec rlpStorageLog
|
||||
var dec rlpLog
|
||||
err = rlp.DecodeBytes(blob, &dec)
|
||||
if err == nil {
|
||||
*l = LogForStorage{
|
||||
|
@ -31,7 +31,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
//go:generate gencodec -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go
|
||||
//go:generate go run github.com/fjl/gencodec@latest -type Receipt -field-override receiptMarshaling -out gen_receipt_json.go
|
||||
|
||||
var (
|
||||
receiptStatusFailedRLP = []byte{}
|
||||
@ -287,16 +287,20 @@ type ReceiptForStorage Receipt
|
||||
|
||||
// EncodeRLP implements rlp.Encoder, and flattens all content fields of a receipt
|
||||
// into an RLP stream.
|
||||
func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error {
|
||||
enc := &storedReceiptRLP{
|
||||
PostStateOrStatus: (*Receipt)(r).statusEncoding(),
|
||||
CumulativeGasUsed: r.CumulativeGasUsed,
|
||||
Logs: make([]*LogForStorage, len(r.Logs)),
|
||||
func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error {
|
||||
w := rlp.NewEncoderBuffer(_w)
|
||||
outerList := w.List()
|
||||
w.WriteBytes((*Receipt)(r).statusEncoding())
|
||||
w.WriteUint64(r.CumulativeGasUsed)
|
||||
logList := w.List()
|
||||
for _, log := range r.Logs {
|
||||
if err := rlp.Encode(w, log); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for i, log := range r.Logs {
|
||||
enc.Logs[i] = (*LogForStorage)(log)
|
||||
}
|
||||
return rlp.Encode(w, enc)
|
||||
w.ListEnd(logList)
|
||||
w.ListEnd(outerList)
|
||||
return w.Flush()
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder, and loads both consensus and implementation
|
||||
|
@ -22,6 +22,8 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
//go:generate go run ../../rlp/rlpgen -type StateAccount -out gen_account_rlp.go
|
||||
|
||||
// StateAccount is the Ethereum consensus representation of accounts.
|
||||
// These objects are stored in the main account trie.
|
||||
type StateAccount struct {
|
||||
|
Reference in New Issue
Block a user