Skip to content

Commit

Permalink
start adding blob transaction parsing to txpool (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
roberto-bayardo committed Jan 25, 2023
1 parent d1c5438 commit d42e318
Show file tree
Hide file tree
Showing 4 changed files with 355 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ require (
github.com/pion/webrtc/v3 v3.1.42 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/protolambda/ztyp v0.2.2 // indirect
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
github.com/valyala/fastrand v1.1.0 // indirect
github.com/valyala/histogram v1.2.0 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d h1:dg1dEPuWpEqDnvIw251EVy4zlP8gWbsGj4BsUKCRpYs=
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/holiman/uint256 v1.2.0/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o=
github.com/holiman/uint256 v1.2.1/go.mod h1:y4ga/t+u+Xwd7CpDgZESaRcWy0I7XMlTMA25ApIH5Jw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
Expand Down Expand Up @@ -331,6 +332,10 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE=
github.com/quasilyte/go-ruleguard/dsl v0.3.22/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/protolambda/ztyp v0.2.2 h1:rVcL3vBu9W/aV646zF6caLS/dyn9BN8NYiuJzicLNyY=
github.com/protolambda/ztyp v0.2.2/go.mod h1:9bYgKGqg3wJqT9ac1gI2hnVb0STQq7p/1lapqrqY1dU=
github.com/quasilyte/go-ruleguard/dsl v0.3.21 h1:vNkC6fC6qMLzCOGbnIHOd5ixUGgTbp3Z4fGnUgULlDA=
github.com/quasilyte/go-ruleguard/dsl v0.3.21/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
Expand Down
225 changes: 225 additions & 0 deletions types/blob_txn.go
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
}
124 changes: 124 additions & 0 deletions types/blob_txn_test.go
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")
}
}

0 comments on commit d42e318

Please sign in to comment.