eip-4844: modified DecodeTransaction version 1 (#7442)

Blob transactions are SSZ encoded, so it had to be added to decoding.
There are 2 encoding forms: `network` and `minimal` (usual). Network
encoded blob transactions include "wrapper data" which are `kzgs`,
`blobs` and `proofs`, and decoded by `DecodeWrappedTransaction`. For
previous types of transactions the network encoding is no different.
Execution-payloads / blocks use the minimal encoding of transactions. In
the transaction-pool and local transaction-journal the network encoding
is used.

Concerns: 
1. Possible performance reduction caused by these changes, not sure if
streams are better then slices. Go slices in this modifications are
read-only, so they should be referred to the same underlying array and
passed by a reference.
2. If `DecodeWrappedTransaction` and `DecodeTransaction` will create
confusion and should be merged into one function.
This commit is contained in:
racytech 2023-05-09 23:44:53 +06:00 committed by GitHub
parent b4fc18ad14
commit 42e8db3958
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 111 additions and 92 deletions

View File

@ -979,7 +979,7 @@ func scanTxs(chaindata string) error {
return err
}
var tr types.Transaction
if tr, err = types.DecodeTransaction(rlp.NewStream(bytes.NewReader(v), 0)); err != nil {
if tr, err = types.DecodeTransaction(v); err != nil {
return err
}
if _, ok := trTypes[tr.Type()]; !ok {

View File

@ -14,7 +14,6 @@ import (
"github.com/ledgerwatch/erigon/common/hexutil"
"github.com/ledgerwatch/erigon/core/rawdb"
types2 "github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/rlp"
"github.com/ledgerwatch/erigon/rpc"
"github.com/ledgerwatch/erigon/turbo/rpchelper"
)
@ -99,8 +98,7 @@ func (api *APIImpl) GetTransactionByHash(ctx context.Context, txnHash common.Has
return nil, err
}
if len(reply.RlpTxs[0]) > 0 {
s := rlp.NewStream(bytes.NewReader(reply.RlpTxs[0]), uint64(len(reply.RlpTxs[0])))
txn, err := types2.DecodeTransaction(s)
txn, err := types2.DecodeTransaction(reply.RlpTxs[0])
if err != nil {
return nil, err
}

View File

@ -1,7 +1,6 @@
package commands
import (
"bytes"
"context"
"errors"
"fmt"
@ -17,12 +16,11 @@ import (
"github.com/ledgerwatch/erigon/crypto"
"github.com/ledgerwatch/erigon/eth/ethconfig"
"github.com/ledgerwatch/erigon/params"
"github.com/ledgerwatch/erigon/rlp"
)
// SendRawTransaction implements eth_sendRawTransaction. Creates new message call transaction or a contract creation for previously-signed transactions.
func (api *APIImpl) SendRawTransaction(ctx context.Context, encodedTx hexutility.Bytes) (common.Hash, error) {
txn, err := types.DecodeTransaction(rlp.NewStream(bytes.NewReader(encodedTx), uint64(len(encodedTx))))
txn, err := types.DecodeWrappedTransaction(encodedTx)
if err != nil {
return common.Hash{}, err
}

View File

@ -1,7 +1,6 @@
package commands
import (
"bytes"
"context"
"fmt"
@ -13,7 +12,6 @@ import (
"github.com/ledgerwatch/erigon/common/hexutil"
"github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/rlp"
)
// NetAPI the interface for the net_ RPC commands
@ -53,8 +51,7 @@ func (api *TxPoolAPIImpl) Content(ctx context.Context) (map[string]map[string]ma
baseFee := make(map[libcommon.Address][]types.Transaction, 8)
queued := make(map[libcommon.Address][]types.Transaction, 8)
for i := range reply.Txs {
stream := rlp.NewStream(bytes.NewReader(reply.Txs[i].RlpTx), 0)
txn, err := types.DecodeTransaction(stream)
txn, err := types.DecodeTransaction(reply.Txs[i].RlpTx)
if err != nil {
return nil, err
}

View File

@ -1,7 +1,6 @@
package sentry
import (
"bytes"
"context"
"errors"
"math"
@ -72,7 +71,7 @@ func (cs *MultiClient) BroadcastNewBlock(ctx context.Context, header *types.Head
txs := make([]types.Transaction, len(body.Transactions))
for i, tx := range body.Transactions {
var err error
if txs[i], err = types.DecodeTransaction(rlp.NewStream(bytes.NewReader(tx), 0)); err != nil {
if txs[i], err = types.DecodeTransaction(tx); err != nil {
log.Error("broadcastNewBlock", "err", err)
return
}

View File

@ -404,7 +404,7 @@ func CanonicalTxnByID(db kv.Getter, id uint64, blockHash libcommon.Hash, transac
if len(v) == 0 {
return nil, nil
}
txn, err := types.DecodeTransaction(rlp.NewStream(bytes.NewReader(v), uint64(len(v))))
txn, err := types.DecodeTransaction(v)
if err != nil {
return nil, err
}
@ -416,17 +416,13 @@ func CanonicalTransactions(db kv.Getter, baseTxId uint64, amount uint32) ([]type
return []types.Transaction{}, nil
}
txIdKey := make([]byte, 8)
reader := bytes.NewReader(nil)
stream := rlp.NewStream(reader, 0)
txs := make([]types.Transaction, amount)
binary.BigEndian.PutUint64(txIdKey, baseTxId)
i := uint32(0)
if err := db.ForAmount(kv.EthTx, txIdKey, amount, func(k, v []byte) error {
var decodeErr error
reader.Reset(v)
stream.Reset(reader, 0)
if txs[i], decodeErr = types.DecodeTransaction(stream); decodeErr != nil {
if txs[i], decodeErr = types.UnmarshalTransactionFromBinary(v); decodeErr != nil {
return decodeErr
}
i++
@ -443,17 +439,13 @@ func NonCanonicalTransactions(db kv.Getter, baseTxId uint64, amount uint32) ([]t
return []types.Transaction{}, nil
}
txIdKey := make([]byte, 8)
reader := bytes.NewReader(nil)
stream := rlp.NewStream(reader, 0)
txs := make([]types.Transaction, amount)
binary.BigEndian.PutUint64(txIdKey, baseTxId)
i := uint32(0)
if err := db.ForAmount(kv.NonCanonicalTxs, txIdKey, amount, func(k, v []byte) error {
var decodeErr error
reader.Reset(v)
stream.Reset(reader, 0)
if txs[i], decodeErr = types.DecodeTransaction(stream); decodeErr != nil {
if txs[i], decodeErr = types.DecodeTransaction(v); decodeErr != nil {
return decodeErr
}
i++

View File

@ -1080,7 +1080,7 @@ func (bb *Body) DecodeRLP(s *rlp.Stream) error {
return err
}
var tx Transaction
for tx, err = DecodeTransaction(s); err == nil; tx, err = DecodeTransaction(s) {
for tx, err = DecodeRLPTransaction(s); err == nil; tx, err = DecodeRLPTransaction(s) {
bb.Transactions = append(bb.Transactions, tx)
}
if !errors.Is(err, rlp.EOL) {
@ -1259,7 +1259,7 @@ func (bb *Block) DecodeRLP(s *rlp.Stream) error {
return err
}
var tx Transaction
for tx, err = DecodeTransaction(s); err == nil; tx, err = DecodeTransaction(s) {
for tx, err = DecodeRLPTransaction(s); err == nil; tx, err = DecodeRLPTransaction(s) {
bb.transactions = append(bb.transactions, tx)
}
if !errors.Is(err, rlp.EOL) {

View File

@ -126,7 +126,7 @@ func (tm TransactionMisc) From() *atomic.Value {
return &tm.from
}
func DecodeTransaction(s *rlp.Stream) (Transaction, error) {
func DecodeRLPTransaction(s *rlp.Stream) (Transaction, error) {
kind, size, err := s.Kind()
if err != nil {
return nil, err
@ -138,44 +138,98 @@ func DecodeTransaction(s *rlp.Stream) (Transaction, error) {
}
return tx, nil
}
if rlp.String == kind {
s.NewList(size) // Hack - convert String (envelope) into List
if rlp.String != kind {
return nil, fmt.Errorf("Not an RLP encoded transaction. If this is a canonical encoded transaction, use UnmarshalTransactionFromBinary instead. Got %v for kind, expected String", kind)
}
// Decode the EIP-2718 typed TX envelope.
var b []byte
if b, err = s.Bytes(); err != nil {
return nil, err
}
if len(b) != 1 {
return nil, fmt.Errorf("%w, got %d bytes", rlp.ErrWrongTxTypePrefix, len(b))
if len(b) == 0 {
return nil, rlp.EOL
}
var tx Transaction
switch b[0] {
case AccessListTxType:
t := &AccessListTx{}
if err = t.DecodeRLP(s); err != nil {
return nil, err
}
tx = t
case DynamicFeeTxType:
t := &DynamicFeeTransaction{}
if err = t.DecodeRLP(s); err != nil {
return nil, err
}
tx = t
default:
return nil, fmt.Errorf("%w, got: %d", rlp.ErrUnknownTxTypePrefix, b[0])
}
if kind == rlp.String {
if err = s.ListEnd(); err != nil {
return nil, err
}
}
return tx, nil
return UnmarshalTransactionFromBinary(b)
}
func UnmarshalTransactionFromBinary(data []byte) (Transaction, error) {
// DecodeWrappedTransaction decodes network encoded transaction with or without
// envelope. When transaction is not network encoded use DecodeTransaction.
func DecodeWrappedTransaction(data []byte) (Transaction, error) {
if len(data) == 0 {
return nil, io.EOF
}
if data[0] < 0x80 { // the encoding is canonical, not RLP
// EIP-4844 tx differs from previous types of transactions in network
// encoding. It's SSZ encoded and includes blobs and kzgs.
// Previous types have no different encoding.
return UnmarshalWrappedTransactionFromBinary(data)
}
s := rlp.NewStream(bytes.NewReader(data), uint64(len(data)))
return DecodeTransaction(s)
return DecodeRLPTransaction(s)
}
// DecodeTransaction decodes a transaction either in RLP or canonical format
func DecodeTransaction(data []byte) (Transaction, error) {
if len(data) == 0 {
return nil, io.EOF
}
if data[0] < 0x80 {
// the encoding is canonical, not RLP
return UnmarshalTransactionFromBinary(data)
}
s := rlp.NewStream(bytes.NewReader(data), uint64(len(data)))
return DecodeRLPTransaction(s)
}
// Parse transaction without envelope.
func UnmarshalTransactionFromBinary(data []byte) (Transaction, error) {
if len(data) <= 1 {
return nil, fmt.Errorf("short input: %v", len(data))
}
switch data[0] {
case BlobTxType:
t := &SignedBlobTx{}
if err := DecodeSSZ(data[1:], t); err != nil {
return nil, err
}
return t, nil
case AccessListTxType:
s := rlp.NewStream(bytes.NewReader(data[1:]), uint64(len(data)-1))
t := &AccessListTx{}
if err := t.DecodeRLP(s); err != nil {
return nil, err
}
return t, nil
case DynamicFeeTxType:
s := rlp.NewStream(bytes.NewReader(data[1:]), uint64(len(data)-1))
t := &DynamicFeeTransaction{}
if err := t.DecodeRLP(s); err != nil {
return nil, err
}
return t, nil
default:
if data[0] >= 0x80 {
// Tx is type legacy which is RLP encoded
return DecodeTransaction(data)
}
return nil, ErrTxTypeNotSupported
}
}
// Parse network encoded transaction without envelope.
func UnmarshalWrappedTransactionFromBinary(data []byte) (Transaction, error) {
if len(data) <= 1 {
return nil, fmt.Errorf("short input: %v", len(data))
}
if data[0] != BlobTxType {
return UnmarshalTransactionFromBinary(data)
}
t := &BlobTxWrapper{}
if err := DecodeSSZ(data[1:], t); err != nil {
return nil, err
}
return t, nil
}
func MarshalTransactionsBinary(txs Transactions) ([][]byte, error) {
@ -201,8 +255,7 @@ func DecodeTransactions(txs [][]byte) ([]Transaction, error) {
result := make([]Transaction, len(txs))
var err error
for i := range txs {
s := rlp.NewStream(bytes.NewReader(txs[i]), uint64(len(txs[i])))
result[i], err = DecodeTransaction(s)
result[i], err = UnmarshalTransactionFromBinary(txs[i])
if err != nil {
return nil, err
}

View File

@ -17,7 +17,6 @@
package types
import (
"bytes"
"math/big"
"testing"
@ -26,7 +25,6 @@ import (
"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/crypto"
"github.com/ledgerwatch/erigon/rlp"
)
func TestEIP1559Signing(t *testing.T) {
@ -118,7 +116,7 @@ func TestEIP155SigningVitalik(t *testing.T) {
} {
signer := LatestSignerForChainID(big.NewInt(1))
tx, err := DecodeTransaction(rlp.NewStream(bytes.NewReader(common.Hex2Bytes(test.txRlp)), 0))
tx, err := DecodeTransaction(common.Hex2Bytes(test.txRlp))
if err != nil {
t.Errorf("%d: %v", i, err)
continue

View File

@ -103,7 +103,7 @@ var (
func TestDecodeEmptyInput(t *testing.T) {
input := []byte{}
_, err := DecodeTransaction(rlp.NewStream(bytes.NewReader(input), 0))
_, err := DecodeTransaction(input)
if !errors.Is(err, io.EOF) {
t.Fatal("wrong error:", err)
}
@ -111,7 +111,7 @@ func TestDecodeEmptyInput(t *testing.T) {
func TestDecodeEmptyTypedTx(t *testing.T) {
input := []byte{0x80}
_, err := DecodeTransaction(rlp.NewStream(bytes.NewReader(input), 0))
_, err := DecodeTransaction(input)
if !errors.Is(err, rlp.EOL) {
t.Fatal("wrong error:", err)
}
@ -267,7 +267,7 @@ func TestEIP1559TransactionEncode(t *testing.T) {
if !bytes.Equal(have, want) {
t.Errorf("encoded RLP mismatch, got %x", have)
}
_, err := DecodeTransaction(rlp.NewStream(bytes.NewReader(buf.Bytes()), 0))
_, err := DecodeTransaction(buf.Bytes())
if err != nil {
t.Fatalf("decode error: %v", err)
}
@ -276,7 +276,7 @@ func TestEIP1559TransactionEncode(t *testing.T) {
}
func decodeTx(data []byte) (Transaction, error) {
return DecodeTransaction(rlp.NewStream(bytes.NewReader(data), 0))
return DecodeTransaction(data)
}
func defaultTestKey() (*ecdsa.PrivateKey, libcommon.Address) {

View File

@ -242,7 +242,7 @@ func (tp *TransactionsPacket) DecodeRLP(s *rlp.Stream) error {
return err
}
var tx types.Transaction
for tx, err = types.DecodeTransaction(s); err == nil; tx, err = types.DecodeTransaction(s) {
for tx, err = types.DecodeRLPTransaction(s); err == nil; tx, err = types.DecodeRLPTransaction(s) {
*tp = append(*tp, tx)
}
if !errors.Is(err, rlp.EOL) {
@ -557,7 +557,7 @@ func (ptp *PooledTransactionsPacket) DecodeRLP(s *rlp.Stream) error {
return err
}
var tx types.Transaction
for tx, err = types.DecodeTransaction(s); err == nil; tx, err = types.DecodeTransaction(s) {
for tx, err = types.DecodeRLPTransaction(s); err == nil; tx, err = types.DecodeRLPTransaction(s) {
*ptp = append(*ptp, tx)
}
if !errors.Is(err, rlp.EOL) {
@ -654,7 +654,7 @@ func (ptp66 *PooledTransactionsPacket66) DecodeRLP(s *rlp.Stream) error {
return err
}
var tx types.Transaction
for tx, err = types.DecodeTransaction(s); err == nil; tx, err = types.DecodeTransaction(s) {
for tx, err = types.DecodeRLPTransaction(s); err == nil; tx, err = types.DecodeRLPTransaction(s) {
ptp66.PooledTransactionsPacket = append(ptp66.PooledTransactionsPacket, tx)
}
if !errors.Is(err, rlp.EOL) {

View File

@ -153,7 +153,7 @@ func TestEth66Messages(t *testing.T) {
} {
var tx types.Transaction
rlpdata := common.FromHex(hexrlp)
tx, err1 := types.DecodeTransaction(rlp.NewStream(bytes.NewReader(rlpdata), 0))
tx, err1 := types.DecodeTransaction(rlpdata)
if err1 != nil {
t.Fatal(err1)
}

View File

@ -1,7 +1,6 @@
package stagedsync
import (
"bytes"
"errors"
"fmt"
"io"
@ -22,7 +21,6 @@ import (
types2 "github.com/ledgerwatch/erigon-lib/types"
"github.com/ledgerwatch/erigon/core/types/accounts"
"github.com/ledgerwatch/erigon/rlp"
"github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/turbo/services"
@ -219,13 +217,8 @@ func getNextTransactions(
}
var txs []types.Transaction //nolint:prealloc
reader := bytes.NewReader([]byte{})
stream := new(rlp.Stream)
for i := range txSlots.Txs {
reader.Reset(txSlots.Txs[i])
stream.Reset(reader, uint64(len(txSlots.Txs[i])))
transaction, err := types.DecodeTransaction(stream)
transaction, err := types.DecodeWrappedTransaction(txSlots.Txs[i])
if err == io.EOF {
continue
}

View File

@ -17,7 +17,6 @@
package tracetest
import (
"bytes"
"encoding/json"
"math/big"
"os"
@ -42,7 +41,6 @@ import (
"github.com/ledgerwatch/erigon/crypto"
"github.com/ledgerwatch/erigon/eth/tracers"
"github.com/ledgerwatch/erigon/params"
"github.com/ledgerwatch/erigon/rlp"
"github.com/ledgerwatch/erigon/tests"
// Force-load native and js packages, to trigger registration
@ -234,7 +232,7 @@ func BenchmarkTracers(b *testing.B) {
func benchTracer(b *testing.B, tracerName string, test *callTracerTest) {
// Configure a blockchain with the given prestate
tx, err := types.DecodeTransaction(rlp.NewStream(bytes.NewReader(common.FromHex(test.Input)), 0))
tx, err := types.DecodeTransaction(common.FromHex(test.Input))
if err != nil {
b.Fatalf("failed to parse testcase input: %v", err)
}

View File

@ -17,7 +17,6 @@
package tests
import (
"bytes"
"errors"
"fmt"
"math/big"
@ -31,7 +30,6 @@ import (
"github.com/ledgerwatch/erigon/common/math"
"github.com/ledgerwatch/erigon/core"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/rlp"
)
// TransactionTest checks RLP decoding and sender derivation of transactions.
@ -62,7 +60,7 @@ type ttFork struct {
func (tt *TransactionTest) Run(chainID *big.Int) error {
validateTx := func(rlpData hexutility.Bytes, signer types.Signer, rules *chain.Rules) (*libcommon.Address, *libcommon.Hash, uint64, error) {
tx, err := types.DecodeTransaction(rlp.NewStream(bytes.NewReader(rlpData), 0))
tx, err := types.DecodeTransaction(rlpData)
if err != nil {
return nil, nil, 0, err
}

View File

@ -526,8 +526,7 @@ func (ff *Filters) OnNewTx(reply *txpool.OnAddReply) {
if len(rlpTx) == 0 {
continue
}
s := rlp.NewStream(bytes.NewReader(rlpTx), uint64(len(rlpTx)))
txs[i], decodeErr = types.DecodeTransaction(s)
txs[i], decodeErr = types.DecodeWrappedTransaction(rlpTx)
if decodeErr != nil {
// ignoring what we can't unmarshal
log.Warn("OnNewTx rpc filters, unprocessable payload", "err", decodeErr, "data", hex.EncodeToString(rlpTx))

View File

@ -528,8 +528,6 @@ func (back *BlockReaderWithSnapshots) txsFromSnapshot(baseTxnID uint64, txsAmoun
txnOffset := txsSeg.IdxTxnHash.OrdinalLookup(baseTxnID - txsSeg.IdxTxnHash.BaseDataID())
gg := txsSeg.Seg.MakeGetter()
gg.Reset(txnOffset)
reader := bytes.NewReader(buf)
stream := rlp.NewStream(reader, 0)
for i := uint32(0); i < txsAmount; i++ {
if !gg.HasNext() {
return nil, nil, nil
@ -540,9 +538,7 @@ func (back *BlockReaderWithSnapshots) txsFromSnapshot(baseTxnID uint64, txsAmoun
}
senders[i].SetBytes(buf[1 : 1+20])
txRlp := buf[1+20:]
reader.Reset(txRlp)
stream.Reset(reader, 0)
txs[i], err = types.DecodeTransaction(stream)
txs[i], err = types.DecodeTransaction(txRlp)
if err != nil {
return nil, nil, err
}
@ -562,7 +558,7 @@ func (back *BlockReaderWithSnapshots) txnByID(txnID uint64, sn *TxnSegment, buf
buf, _ = gg.Next(buf[:0])
sender, txnRlp := buf[1:1+20], buf[1+20:]
txn, err = types.DecodeTransaction(rlp.NewStream(bytes.NewReader(txnRlp), uint64(len(txnRlp))))
txn, err = types.DecodeTransaction(txnRlp)
if err != nil {
return
}
@ -590,7 +586,7 @@ func (back *BlockReaderWithSnapshots) txnByHash(txnHash libcommon.Hash, segments
senderByte, txnRlp := buf[1:1+20], buf[1+20:]
sender := *(*libcommon.Address)(senderByte)
txn, err = types.DecodeTransaction(rlp.NewStream(bytes.NewReader(txnRlp), uint64(len(txnRlp))))
txn, err = types.DecodeTransaction(txnRlp)
if err != nil {
return
}