From 31d61714a1c99063ffecf872afe2592842e6e815 Mon Sep 17 00:00:00 2001 From: Roberto Bayardo Date: Fri, 13 Jan 2023 12:47:15 -0800 Subject: [PATCH] start integrating blob tx parser into txpool (#4) --- types/blob_txn.go | 6 ++++++ types/blob_txn_test.go | 19 ++++++++----------- types/txn.go | 39 +++++++++++++++++++++++++++++++++++++++ types/txn_test.go | 23 +++++++++++++++++++++++ 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/types/blob_txn.go b/types/blob_txn.go index 48e59b00a..8592bced0 100644 --- a/types/blob_txn.go +++ b/types/blob_txn.go @@ -122,6 +122,7 @@ type BlobTx struct { Creation bool // true if To field is nil, indicating contract creation Value uint256.Int DataLen int // length of the Data in bytes + DataNonZeroLen int AccessListAddressCount int // number of addresses in access list AccessListKeyCount int // number of storage keys in access list @@ -155,6 +156,11 @@ func (tx *BlobTx) Deserialize(dr *codec.DecodingReader) error { } tx.Value = uint256.Int(value) tx.DataLen = len(data) + for _, byt := range data { + if byt != 0 { + tx.DataNonZeroLen++ + } + } tx.BlobVersionedHashes = [][32]byte(blobVersionedHashes) tx.AccessListAddressCount = accessList.addresses tx.AccessListKeyCount = accessList.keys diff --git a/types/blob_txn_test.go b/types/blob_txn_test.go index fe2bfef2b..7cea15c12 100644 --- a/types/blob_txn_test.go +++ b/types/blob_txn_test.go @@ -3,11 +3,12 @@ package types import ( "bytes" _ "embed" - "encoding/hex" "strings" "testing" "github.com/protolambda/ztyp/codec" + + "github.com/ledgerwatch/erigon-lib/common" ) var ( @@ -51,24 +52,20 @@ var ( signedBlobTxHex = "45000000010d684b1a6596dfee36d52d5e04f38765dc489270c9f90ee00347422f6ff295e96896a0646b32d9b6a33dd5b537baf4263a34aa619c1ec661eb1296d4de6d831301000000000000000000000000000000000000000000000000000000000000000a000000000000002a000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000041e2010000000000c00000006400000000000000000000000000000000000000000000000000000000000000d5000000db00000000000000000000000000000000000000000000000000000000000000000000007301000001095e7baea6a6c7c4c2dfeb977efac326af552d876162636465660800000060000000000000000000000000000000000000000000000118000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002180000000200000000000000000000000000000000000000000000000000000000000000010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c44401400000000000000000000000000000000000000000000000000000000deadbeef" - // Same tx as above only with nil To field to test contract creation indicator + // Same tx as above only with nil To field to test contract creation indicator, and no signature signedBlobTxNoRecipientHex = "45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000a000000000000002a000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000041e2010000000000c00000006400000000000000000000000000000000000000000000000000000000000000c1000000c700000000000000000000000000000000000000000000000000000000000000000000005f010000006162636465660800000060000000000000000000000000000000000000000000000118000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002180000000200000000000000000000000000000000000000000000000000000000000000010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c44401400000000000000000000000000000000000000000000000000000000deadbeef" - // blobTxNetworkWrapperHex is an ssz encoded BlobTxNetworkWrapper with 2 valid blobs & a valid - // aggregated kzg proof + // blobTxNetworkWrapperHex is an ssz encoded BlobTxNetworkWrapper with 2 valid blobs + + // versioned hashes, a valid aggregated kzg proof, and all other fields 0. //go:embed testdata/blobtx.txt blobTxNetworkWrapperHex string ) func txFromHex(hexStr string, tx codec.Deserializable) error { - txBytes, err := hex.DecodeString(hexStr) - if err != nil { - return err - } + txBytes := common.MustDecodeHex(hexStr) buf := bytes.NewReader(txBytes) dr := codec.NewDecodingReader(buf, uint64(len(txBytes))) - err = tx.Deserialize(dr) - if err != nil { + if err := tx.Deserialize(dr); err != nil { return err } return nil @@ -138,7 +135,7 @@ func TestParseBlobTxNetworkWrapper(t *testing.T) { t.Fatalf("couldn't create test case: %v", err) } l1, l2, l3 := len(tx.BlobKZGs), len(tx.Blobs), len(tx.Tx.Message.BlobVersionedHashes) - if l1 != 2 || l2 != 2 || l3 = l3 { + if l1 != 2 || l2 != 2 || l3 != 2 { t.Errorf("Expected 2 each of kzgs / blobs / hashes, got: %v %v %v", l1, l2, l3) } } diff --git a/types/txn.go b/types/txn.go index cb6340ee6..05d64f2a4 100644 --- a/types/txn.go +++ b/types/txn.go @@ -28,6 +28,7 @@ import ( "github.com/holiman/uint256" "github.com/ledgerwatch/secp256k1" + "github.com/protolambda/ztyp/codec" "golang.org/x/crypto/sha3" "github.com/ledgerwatch/erigon-lib/common/length" @@ -103,6 +104,7 @@ const ( AccessListTxType int = 1 DynamicFeeTxType int = 2 StarknetTxType int = 3 + BlobTxType int = 5 ) var ErrParseTxn = fmt.Errorf("%w transaction", rlp.ErrParse) @@ -153,6 +155,13 @@ func (ctx *TxParseContext) ParseTransaction(payload []byte, pos int, slot *TxSlo // If it is non-legacy transaction, the transaction type follows, and then the the list if !legacy { txType = int(payload[p]) + if txType == BlobTxType { + // TODO: Parsing the blob transaction requires we know the "scope" of the encoding + // since it is SSZ format. We assume the scope ends at the last byte of the payload + // argument; this currently seems to always be the case, but does not seem to be + // mandated by this function's contract. + return len(payload), ctx.ParseBlobTransaction(payload[p:], slot, sender) + } if _, err = ctx.Keccak1.Write(payload[p : p+1]); err != nil { return 0, fmt.Errorf("%w: computing IdHash (hashing type Prefix): %s", ErrParseTxn, err) } @@ -471,6 +480,36 @@ func (ctx *TxParseContext) ParseTransaction(payload []byte, pos int, slot *TxSlo return p, nil } +func (ctx *TxParseContext) ParseBlobTransaction(payload []byte, slot *TxSlot, sender []byte) error { + slot.Rlp = payload // includes type byte + ctx.Keccak1.Write(payload) + ctx.Keccak1.(io.Reader).Read(slot.IDHash[:32]) + + payload = payload[1:] + // payload should now include the SSZ encoded tx and nothing more + tx := BlobTxNetworkWrapper{} + buf := bytes.NewReader(payload) + dr := codec.NewDecodingReader(buf, uint64(len(payload))) + if err := tx.Deserialize(dr); err != nil { + return fmt.Errorf("%w: deserializing blob tx ssz: %s", ErrParseTxn, err) + } + m := tx.Tx.Message + slot.Nonce = m.Nonce + slot.Tip = m.MaxPriorityFeePerGas + slot.FeeCap = m.MaxFeePerGas + slot.Gas = m.Gas + slot.Creation = m.Creation + slot.Value = m.Value + slot.DataLen = m.DataLen + slot.DataNonZeroLen = m.DataNonZeroLen + slot.AlAddrCount = m.AccessListAddressCount + slot.AlStorCount = m.AccessListKeyCount + + // TODO: extract sender, validate blobs. + + return nil +} + type PeerID *types.H512 type Hashes []byte // flatten list of 32-byte hashes diff --git a/types/txn_test.go b/types/txn_test.go index 020355821..7ea9ab0ec 100644 --- a/types/txn_test.go +++ b/types/txn_test.go @@ -19,15 +19,38 @@ package types import ( "bytes" "strconv" + "strings" "testing" "github.com/holiman/uint256" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/hexutility" + "github.com/ledgerwatch/erigon-lib/rlp" ) +func TestParseBlobTransaction(t *testing.T) { + require := require.New(t) + ctx := NewTxParseContext(*new(uint256.Int).SetUint64(1)) + tx, txSender := &TxSlot{}, [20]byte{} + + // Prepare the payload, which is an RLP encoded string consisting of the blob tx type byte + // followed by the ssz encoded transaction data. + ssz := common.MustDecodeHex(strings.TrimSpace(blobTxNetworkWrapperHex)) + stringLen := rlp.StringLen(len(ssz) + 1) + payload := make([]byte, stringLen) + rlp.EncodeString(append([]byte{byte(BlobTxType)}, ssz...), payload) + parseEnd, err := ctx.ParseTransaction(payload, 0, tx, txSender[:], false /* hasEnvelope */, nil) + + require.NoError(err) + require.Equal(len(payload), parseEnd) + + // TODO: test that tx fields are set properly. Need a better input transaction as this example + // has zeros for all fields other than the blobs. +} + func TestParseTransactionRLP(t *testing.T) { for _, testSet := range allNetsTestCases { testSet := testSet