Skip to content

Commit

Permalink
internal/ethapi: add support for blobs in eth_fillTransaction (ethere…
Browse files Browse the repository at this point in the history
…um#28839)

This change adds support for blob-transaction in certain API-endpoints, e.g. eth_fillTransaction. A follow-up PR will add support for signing such transactions.
  • Loading branch information
s1na authored Feb 8, 2024
1 parent 2732fb1 commit ac5aa67
Show file tree
Hide file tree
Showing 5 changed files with 366 additions and 13 deletions.
11 changes: 11 additions & 0 deletions core/types/transaction_marshalling.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/holiman/uint256"
)

Expand All @@ -47,6 +48,11 @@ type txJSON struct {
S *hexutil.Big `json:"s"`
YParity *hexutil.Uint64 `json:"yParity,omitempty"`

// Blob transaction sidecar encoding:
Blobs []kzg4844.Blob `json:"blobs,omitempty"`
Commitments []kzg4844.Commitment `json:"commitments,omitempty"`
Proofs []kzg4844.Proof `json:"proofs,omitempty"`

// Only used for encoding:
Hash common.Hash `json:"hash"`
}
Expand Down Expand Up @@ -142,6 +148,11 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
enc.S = (*hexutil.Big)(itx.S.ToBig())
yparity := itx.V.Uint64()
enc.YParity = (*hexutil.Uint64)(&yparity)
if sidecar := itx.Sidecar; sidecar != nil {
enc.Blobs = itx.Sidecar.Blobs
enc.Commitments = itx.Sidecar.Commitments
enc.Proofs = itx.Sidecar.Proofs
}
}
return json.Marshal(&enc)
}
Expand Down
39 changes: 39 additions & 0 deletions crypto/kzg4844/kzg4844.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,60 @@ import (
"embed"
"errors"
"hash"
"reflect"
"sync/atomic"

"github.com/ethereum/go-ethereum/common/hexutil"
)

//go:embed trusted_setup.json
var content embed.FS

var (
blobT = reflect.TypeOf(Blob{})
commitmentT = reflect.TypeOf(Commitment{})
proofT = reflect.TypeOf(Proof{})
)

// Blob represents a 4844 data blob.
type Blob [131072]byte

// UnmarshalJSON parses a blob in hex syntax.
func (b *Blob) UnmarshalJSON(input []byte) error {
return hexutil.UnmarshalFixedJSON(blobT, input, b[:])
}

// MarshalText returns the hex representation of b.
func (b Blob) MarshalText() ([]byte, error) {
return hexutil.Bytes(b[:]).MarshalText()
}

// Commitment is a serialized commitment to a polynomial.
type Commitment [48]byte

// UnmarshalJSON parses a commitment in hex syntax.
func (c *Commitment) UnmarshalJSON(input []byte) error {
return hexutil.UnmarshalFixedJSON(commitmentT, input, c[:])
}

// MarshalText returns the hex representation of c.
func (c Commitment) MarshalText() ([]byte, error) {
return hexutil.Bytes(c[:]).MarshalText()
}

// Proof is a serialized commitment to the quotient polynomial.
type Proof [48]byte

// UnmarshalJSON parses a proof in hex syntax.
func (p *Proof) UnmarshalJSON(input []byte) error {
return hexutil.UnmarshalFixedJSON(proofT, input, p[:])
}

// MarshalText returns the hex representation of p.
func (p Proof) MarshalText() ([]byte, error) {
return hexutil.Bytes(p[:]).MarshalText()
}

// Point is a BLS field element.
type Point [32]byte

Expand Down
3 changes: 2 additions & 1 deletion internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1812,13 +1812,14 @@ func (s *TransactionAPI) SendTransaction(ctx context.Context, args TransactionAr
// on a given unsigned transaction, and returns it to the caller for further
// processing (signing + broadcast).
func (s *TransactionAPI) FillTransaction(ctx context.Context, args TransactionArgs) (*SignTransactionResult, error) {
args.blobSidecarAllowed = true

// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
return nil, err
}
// Assemble the transaction and obtain rlp
tx := args.toTransaction()
// TODO(s1na): fill in blob proofs, commitments
data, err := tx.MarshalBinary()
if err != nil {
return nil, err
Expand Down
191 changes: 191 additions & 0 deletions internal/ethapi/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
Expand All @@ -45,6 +46,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/internal/blocktest"
Expand Down Expand Up @@ -1079,6 +1081,195 @@ func TestSendBlobTransaction(t *testing.T) {
}
}

func TestFillBlobTransaction(t *testing.T) {
t.Parallel()
// Initialize test accounts
var (
key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
to = crypto.PubkeyToAddress(key.PublicKey)
genesis = &core.Genesis{
Config: params.MergedTestChainConfig,
Alloc: core.GenesisAlloc{},
}
emptyBlob = kzg4844.Blob{}
emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob)
emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit)
emptyBlobHash common.Hash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit)
)
b := newTestBackend(t, 1, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) {
b.SetPoS()
})
api := NewTransactionAPI(b, nil)
type result struct {
Hashes []common.Hash
Sidecar *types.BlobTxSidecar
}
suite := []struct {
name string
args TransactionArgs
err string
want *result
}{
{
name: "TestInvalidParamsCombination1",
args: TransactionArgs{
From: &b.acc.Address,
To: &to,
Value: (*hexutil.Big)(big.NewInt(1)),
Blobs: []kzg4844.Blob{{}},
Proofs: []kzg4844.Proof{{}},
},
err: `blob proofs provided while commitments were not`,
},
{
name: "TestInvalidParamsCombination2",
args: TransactionArgs{
From: &b.acc.Address,
To: &to,
Value: (*hexutil.Big)(big.NewInt(1)),
Blobs: []kzg4844.Blob{{}},
Commitments: []kzg4844.Commitment{{}},
},
err: `blob commitments provided while proofs were not`,
},
{
name: "TestInvalidParamsCount1",
args: TransactionArgs{
From: &b.acc.Address,
To: &to,
Value: (*hexutil.Big)(big.NewInt(1)),
Blobs: []kzg4844.Blob{{}},
Commitments: []kzg4844.Commitment{{}, {}},
Proofs: []kzg4844.Proof{{}, {}},
},
err: `number of blobs and commitments mismatch (have=2, want=1)`,
},
{
name: "TestInvalidParamsCount2",
args: TransactionArgs{
From: &b.acc.Address,
To: &to,
Value: (*hexutil.Big)(big.NewInt(1)),
Blobs: []kzg4844.Blob{{}, {}},
Commitments: []kzg4844.Commitment{{}, {}},
Proofs: []kzg4844.Proof{{}},
},
err: `number of blobs and proofs mismatch (have=1, want=2)`,
},
{
name: "TestInvalidProofVerification",
args: TransactionArgs{
From: &b.acc.Address,
To: &to,
Value: (*hexutil.Big)(big.NewInt(1)),
Blobs: []kzg4844.Blob{{}, {}},
Commitments: []kzg4844.Commitment{{}, {}},
Proofs: []kzg4844.Proof{{}, {}},
},
err: `failed to verify blob proof: short buffer`,
},
{
name: "TestGenerateBlobHashes",
args: TransactionArgs{
From: &b.acc.Address,
To: &to,
Value: (*hexutil.Big)(big.NewInt(1)),
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
},
want: &result{
Hashes: []common.Hash{emptyBlobHash},
Sidecar: &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
},
},
},
{
name: "TestValidBlobHashes",
args: TransactionArgs{
From: &b.acc.Address,
To: &to,
Value: (*hexutil.Big)(big.NewInt(1)),
BlobHashes: []common.Hash{emptyBlobHash},
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
},
want: &result{
Hashes: []common.Hash{emptyBlobHash},
Sidecar: &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
},
},
},
{
name: "TestInvalidBlobHashes",
args: TransactionArgs{
From: &b.acc.Address,
To: &to,
Value: (*hexutil.Big)(big.NewInt(1)),
BlobHashes: []common.Hash{{0x01, 0x22}},
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
},
err: fmt.Sprintf("blob hash verification failed (have=%s, want=%s)", common.Hash{0x01, 0x22}, emptyBlobHash),
},
{
name: "TestGenerateBlobProofs",
args: TransactionArgs{
From: &b.acc.Address,
To: &to,
Value: (*hexutil.Big)(big.NewInt(1)),
Blobs: []kzg4844.Blob{emptyBlob},
},
want: &result{
Hashes: []common.Hash{emptyBlobHash},
Sidecar: &types.BlobTxSidecar{
Blobs: []kzg4844.Blob{emptyBlob},
Commitments: []kzg4844.Commitment{emptyBlobCommit},
Proofs: []kzg4844.Proof{emptyBlobProof},
},
},
},
}
for _, tc := range suite {
t.Run(tc.name, func(t *testing.T) {
res, err := api.FillTransaction(context.Background(), tc.args)
if len(tc.err) > 0 {
if err == nil {
t.Fatalf("missing error. want: %s", tc.err)
} else if err != nil && err.Error() != tc.err {
t.Fatalf("error mismatch. want: %s, have: %s", tc.err, err.Error())
}
return
}
if err != nil && len(tc.err) == 0 {
t.Fatalf("expected no error. have: %s", err)
}
if res == nil {
t.Fatal("result missing")
}
want, err := json.Marshal(tc.want)
if err != nil {
t.Fatalf("failed to encode expected: %v", err)
}
have, err := json.Marshal(result{Hashes: res.Tx.BlobHashes(), Sidecar: res.Tx.BlobTxSidecar()})
if err != nil {
t.Fatalf("failed to encode computed sidecar: %v", err)
}
if !bytes.Equal(have, want) {
t.Errorf("blob sidecar mismatch. Have: %s, want: %s", have, want)
}
})
}
}

func argsFromTransaction(tx *types.Transaction, from common.Address) TransactionArgs {
var (
gas = tx.Gas()
Expand Down
Loading

0 comments on commit ac5aa67

Please sign in to comment.