forked from ledgerwatch/erigon-lib
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
start adding blob transaction parsing to txpool
- Loading branch information
1 parent
ad53d90
commit 46aa242
Showing
4 changed files
with
353 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,225 @@ | ||
package types | ||
|
||
// Minimal Blob Transaction parser for txpool purposes | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/holiman/uint256" | ||
"github.com/protolambda/ztyp/codec" | ||
"github.com/protolambda/ztyp/view" | ||
) | ||
|
||
const ( | ||
FieldElementsPerBlob = 4096 | ||
) | ||
|
||
type BlobTxNetworkWrapper struct { | ||
Tx SignedBlobTx | ||
BlobKZGs [][48]byte | ||
Blobs [][FieldElementsPerBlob * 32]byte | ||
KZGAggregatedProof [48]byte | ||
} | ||
|
||
type SignedBlobTx struct { | ||
Message BlobTx | ||
Signature ECDSASignature | ||
} | ||
|
||
func (sbtx SignedBlobTx) FixedLength() uint64 { return 0 } | ||
func (sbtx *SignedBlobTx) Deserialize(dr *codec.DecodingReader) error { | ||
err := dr.Container(&sbtx.Message, &sbtx.Signature) | ||
if err != nil { | ||
return fmt.Errorf("failed to deserialize SignedBlobTx: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
type BlobTx struct { | ||
ChainID uint256.Int | ||
Nonce uint64 | ||
MaxPriorityFeePerGas uint256.Int | ||
MaxFeePerGas uint256.Int | ||
Gas uint64 | ||
Creation bool // true if To field is nil, indicating contract creation | ||
Value uint256.Int | ||
DataLen int // length of the Data in bytes | ||
AccessListAddressCount int // number of addresses in access list | ||
AccessListKeyCount int // number of storage keys in access list | ||
|
||
BlobVersionedHashes [][32]byte | ||
} | ||
|
||
func (tx BlobTx) FixedLength() uint64 { return 0 } | ||
func (tx *BlobTx) Deserialize(dr *codec.DecodingReader) error { | ||
var chainID view.Uint256View | ||
var nonce view.Uint64View | ||
var maxPriorityFeePerGas view.Uint256View | ||
var maxFeePerGas view.Uint256View | ||
var gas view.Uint64View | ||
var hasToAddress addressView | ||
var value view.Uint256View | ||
var data dataView | ||
var accessList accessListView | ||
var maxFeePerDataGas view.Uint256View | ||
var blobVersionedHashes blobVersionedHashesView | ||
err := dr.Container(&chainID, &nonce, &maxPriorityFeePerGas, &maxFeePerGas, &gas, &hasToAddress, &value, &data, &accessList, &maxFeePerDataGas, &blobVersionedHashes) | ||
if err != nil { | ||
return fmt.Errorf("failed to deserialize BlobTx: %w", err) | ||
} | ||
tx.ChainID = uint256.Int(chainID) | ||
tx.Nonce = uint64(nonce) | ||
tx.MaxPriorityFeePerGas = uint256.Int(maxPriorityFeePerGas) | ||
tx.MaxFeePerGas = uint256.Int(maxFeePerGas) | ||
tx.Gas = uint64(gas) | ||
if !hasToAddress { | ||
tx.Creation = true | ||
} | ||
tx.Value = uint256.Int(value) | ||
tx.DataLen = len(data) | ||
tx.BlobVersionedHashes = [][32]byte(blobVersionedHashes) | ||
tx.AccessListAddressCount = accessList.addresses | ||
tx.AccessListKeyCount = accessList.keys | ||
return nil | ||
} | ||
|
||
// For deserializing To field, true if address is non-nil | ||
type addressView bool | ||
|
||
func (av addressView) FixedLength() uint64 { return 0 } | ||
func (av *addressView) Deserialize(dr *codec.DecodingReader) error { | ||
len := dr.Scope() | ||
b, err := dr.ReadByte() | ||
if len == 1 { | ||
if err != nil { | ||
return err | ||
} | ||
if b != 0 { | ||
return fmt.Errorf("expected 0 byte, got %v", b) | ||
} | ||
*av = false | ||
return nil | ||
} | ||
if len != 21 { | ||
return fmt.Errorf("expected 1 or 21 bytes, got %v", len) | ||
} | ||
if b != 1 { | ||
return fmt.Errorf("expected byte == 1, got %v", b) | ||
} | ||
*av = true | ||
dr.Skip(20) | ||
return nil | ||
} | ||
|
||
// For deserializing the Data field | ||
type dataView []byte | ||
|
||
func (dv dataView) FixedLength() uint64 { return 0 } | ||
func (dv *dataView) Deserialize(dr *codec.DecodingReader) error { | ||
err := dr.ByteList((*[]byte)(dv), 1<<24 /*MAX_CALLDATA_SIZE*/) | ||
if err != nil { | ||
return fmt.Errorf("failed to deserialize dataView: %w", err) | ||
} | ||
return nil | ||
} | ||
|
||
// For deserializing access list field | ||
type accessListView struct { | ||
addresses int | ||
keys int | ||
} | ||
|
||
func (alv accessListView) FixedLength() uint64 { return 0 } | ||
func (alv *accessListView) Deserialize(dr *codec.DecodingReader) error { | ||
// an access list is a list of access list tuples | ||
tuples := []*tuple{} | ||
add := func() codec.Deserializable { | ||
alv.addresses++ | ||
tuple := new(tuple) | ||
tuples = append(tuples, tuple) | ||
return tuple | ||
} | ||
err := dr.List(add, 0, 1<<24) | ||
if err != nil { | ||
return err | ||
} | ||
for i := range tuples { | ||
alv.keys += int(*tuples[i]) | ||
} | ||
return nil | ||
} | ||
|
||
type tuple int // count of keys in the access list tuple | ||
|
||
func (t tuple) FixedLength() uint64 { return 0 } | ||
func (t *tuple) Deserialize(dr *codec.DecodingReader) error { | ||
// an access list tuple consists of 20 bytes for the address, and then 4 bytes for the | ||
// "offset", followed by the list of 32-byte storage keys. | ||
scope := dr.Scope() | ||
if scope < 24 { | ||
return fmt.Errorf("expected scope >= 24, got %v", scope) | ||
} | ||
// subtract address & offset | ||
scope -= 24 | ||
if scope%32 != 0 { | ||
return fmt.Errorf("expected multiple of 32 bytes got: %v", scope) | ||
} | ||
length := scope / 32 | ||
if length > 1<<24 { | ||
return fmt.Errorf("too many storage keys: %v", length) | ||
} | ||
*t = tuple(length) | ||
return nil | ||
} | ||
|
||
type blobVersionedHashesView [][32]byte | ||
|
||
func (b blobVersionedHashesView) FixedLength() uint64 { return 0 } | ||
func (b *blobVersionedHashesView) Deserialize(dr *codec.DecodingReader) error { | ||
*b = nil | ||
scope := dr.Scope() | ||
if scope == 0 { | ||
return nil | ||
} | ||
if scope%32 != 0 { | ||
return fmt.Errorf("scope not a multiple of 32. got: %v", scope) | ||
} | ||
length := scope / 32 | ||
if length > 1<<24 /*MAX_VERSIONED_HASHES_LIST_SIZE*/ { | ||
return fmt.Errorf("access list too long: %v", length) | ||
} | ||
hashes := make([]byte, scope) | ||
_, err := dr.Read(hashes) | ||
if err != nil { | ||
return err | ||
} | ||
*b = make([][32]byte, length) | ||
for i := 0; i < int(length); i++ { | ||
copy((*b)[i][:], hashes[i*32:i*32+32]) | ||
} | ||
return nil | ||
} | ||
|
||
type ECDSASignature struct { | ||
V byte | ||
R [32]byte | ||
S [32]byte | ||
} | ||
|
||
func (sig ECDSASignature) FixedLength() uint64 { return 1 + 32 + 32 } | ||
func (sig *ECDSASignature) Deserialize(dr *codec.DecodingReader) error { | ||
len := sig.FixedLength() | ||
scope := dr.Scope() | ||
if scope != len { | ||
return fmt.Errorf("failed to decode signature: expected %v bytes got %v", len, scope) | ||
} | ||
data := make([]byte, len) | ||
_, err := dr.Read(data) | ||
if err != nil { | ||
return err | ||
} | ||
sig.V = data[0] | ||
copy(sig.R[:], data[1:33]) | ||
copy(sig.S[:], data[33:len]) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package types | ||
|
||
import ( | ||
"bytes" | ||
"encoding/hex" | ||
"testing" | ||
|
||
"github.com/protolambda/ztyp/codec" | ||
) | ||
|
||
var ( | ||
// signedBlobTxHex is the SSZ encoding of the following tx: | ||
// { | ||
// "nonce": "0xa", | ||
// "gasPrice": null, | ||
// "maxPriorityFeePerGas": "0x2a", | ||
// "maxFeePerGas": "0xa", | ||
// "gas": "0x1e241", | ||
// "value": "0x64", | ||
// "input": "0x616263646566", | ||
// "v": "0x1", | ||
// "r": "0xe995f26f2f424703e00ef9c9709248dc6587f3045e2dd536eedf96651a4b680d", | ||
// "s": "0x13836dded49612eb61c61e9c61aa343a26f4ba37b5d53da3b6d9326b64a09668", | ||
// "to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", | ||
// "chainId": "0x1", | ||
// "accessList": [ | ||
// { | ||
// "address": "0x0000000000000000000000000000000000000001", | ||
// "storageKeys": [ | ||
// "0x0000000000000000000000000000000000000000000000000000000000000000", | ||
// "0x0100000000000000000000000000000000000000000000000000000000000000" | ||
// ] | ||
// }, | ||
// { | ||
// "address": "0x0000000000000000000000000000000000000002", | ||
// "storageKeys": [ | ||
// "0x0200000000000000000000000000000000000000000000000000000000000000" | ||
// ] | ||
// } | ||
// ], | ||
// "maxFeePerDataGas": "0x0", | ||
// "blobVersionedHashes": [ | ||
// "0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014", | ||
// "0x00000000000000000000000000000000000000000000000000000000deadbeef" | ||
// ], | ||
// "kzgAggregatedProof": "0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", | ||
// "hash": "0xabfab29ef05293b52e448f5e85eae4a99c1496cdf59f59987a37ba90912c8801" | ||
// } | ||
|
||
signedBlobTxHex = "45000000010d684b1a6596dfee36d52d5e04f38765dc489270c9f90ee00347422f6ff295e96896a0646b32d9b6a33dd5b537baf4263a34aa619c1ec661eb1296d4de6d831301000000000000000000000000000000000000000000000000000000000000000a000000000000002a000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000041e2010000000000c00000006400000000000000000000000000000000000000000000000000000000000000d5000000db00000000000000000000000000000000000000000000000000000000000000000000007301000001095e7baea6a6c7c4c2dfeb977efac326af552d876162636465660800000060000000000000000000000000000000000000000000000118000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002180000000200000000000000000000000000000000000000000000000000000000000000010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c44401400000000000000000000000000000000000000000000000000000000deadbeef" | ||
|
||
// Same as above only with nil To field to test contract creation indicator | ||
signedBlobTxNoRecipientHex = "45000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000a000000000000002a000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000041e2010000000000c00000006400000000000000000000000000000000000000000000000000000000000000c1000000c700000000000000000000000000000000000000000000000000000000000000000000005f010000006162636465660800000060000000000000000000000000000000000000000000000118000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002180000000200000000000000000000000000000000000000000000000000000000000000010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c44401400000000000000000000000000000000000000000000000000000000deadbeef" | ||
) | ||
|
||
func signedBlobTxFromHex(hexStr string) (*SignedBlobTx, error) { | ||
txBytes, err := hex.DecodeString(hexStr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
tx := SignedBlobTx{} | ||
buf := bytes.NewReader(txBytes) | ||
dr := codec.NewDecodingReader(buf, uint64(len(txBytes))) | ||
err = tx.Deserialize(dr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &tx, nil | ||
} | ||
|
||
func TestParseSignedBlobTx(t *testing.T) { | ||
tx, err := signedBlobTxFromHex(signedBlobTxHex) | ||
if err != nil { | ||
t.Fatalf("couldn't create test case: %v", err) | ||
} | ||
msg := tx.Message | ||
if msg.ChainID.Uint64() != 1 { | ||
t.Errorf("Expected chain id == 1, got: %v", msg.ChainID.Uint64()) | ||
} | ||
if msg.Nonce != 10 { | ||
t.Errorf("Expected nonce == 10, got: %v", msg.Nonce) | ||
} | ||
if msg.MaxPriorityFeePerGas.Uint64() != 42 { | ||
t.Errorf("Expected MaxPriorityFeePerGas == 42, got %v", msg.MaxPriorityFeePerGas.Uint64()) | ||
} | ||
if msg.MaxFeePerGas.Uint64() != 10 { | ||
t.Errorf("Expected MaxFeePerGas == 10, got %v", msg.MaxFeePerGas.Uint64()) | ||
} | ||
if msg.Gas != 123457 { | ||
t.Errorf("Expected Gas == 123457, got %v", msg.Gas) | ||
} | ||
if msg.Creation == true { | ||
t.Errorf("Expected !msg.Creation") | ||
} | ||
if msg.Value.Uint64() != 100 { | ||
t.Errorf("Expected msg.Value == 100, got %v", msg.Value.Uint64()) | ||
} | ||
if msg.DataLen != 6 { | ||
t.Errorf("Expected DataLen == 6, got %v", msg.DataLen) | ||
} | ||
if len(msg.BlobVersionedHashes) != 2 { | ||
t.Errorf("Expected 2 blob hashes, got %v", len(msg.BlobVersionedHashes)) | ||
} | ||
if msg.AccessListAddressCount != 2 { | ||
t.Errorf("Expected 2 addresses in access list, got %v", msg.AccessListAddressCount) | ||
} | ||
if msg.AccessListKeyCount != 3 { | ||
t.Errorf("Expected 3 keys in access list, got %v", msg.AccessListKeyCount) | ||
} | ||
|
||
sig := tx.Signature | ||
if sig.V != 1 { | ||
t.Errorf("Expected sig.V == 1, got %v", sig.V) | ||
} | ||
|
||
// Test "Creation == true" | ||
tx, err = signedBlobTxFromHex(signedBlobTxNoRecipientHex) | ||
if err != nil { | ||
t.Fatalf("couldn't create test case: %v", err) | ||
} | ||
if tx.Message.Creation == false { | ||
t.Errorf("Expected msg.Creation") | ||
} | ||
} |