Skip to content

Commit

Permalink
feat(ethclient & gethclient): support blob transaction (#661)
Browse files Browse the repository at this point in the history
* feat: support blob transaction

* sync tx encoding/decoding

* more fixes

* add uint236 support in rlp encoding decoding

* revert a format change

* trigger ci
  • Loading branch information
colinlyguo authored Mar 14, 2024
1 parent ccec84c commit 4553f5f
Show file tree
Hide file tree
Showing 14 changed files with 284 additions and 20 deletions.
60 changes: 60 additions & 0 deletions consensus/misc/eip4844.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2023 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package misc

import (
"math/big"

"github.com/scroll-tech/go-ethereum/params"
)

var (
minBlobGasPrice = big.NewInt(params.BlobTxMinBlobGasprice)
blobGaspriceUpdateFraction = big.NewInt(params.BlobTxBlobGaspriceUpdateFraction)
)

// CalcExcessBlobGas calculates the excess blob gas after applying the set of
// blobs on top of the excess blob gas.
func CalcExcessBlobGas(parentExcessBlobGas uint64, parentBlobGasUsed uint64) uint64 {
excessBlobGas := parentExcessBlobGas + parentBlobGasUsed
if excessBlobGas < params.BlobTxTargetBlobGasPerBlock {
return 0
}
return excessBlobGas - params.BlobTxTargetBlobGasPerBlock
}

// CalcBlobFee calculates the blobfee from the header's excess blob gas field.
func CalcBlobFee(excessBlobGas uint64) *big.Int {
return fakeExponential(minBlobGasPrice, new(big.Int).SetUint64(excessBlobGas), blobGaspriceUpdateFraction)
}

// fakeExponential approximates factor * e ** (numerator / denominator) using
// Taylor expansion.
func fakeExponential(factor, numerator, denominator *big.Int) *big.Int {
var (
output = new(big.Int)
accum = new(big.Int).Mul(factor, denominator)
)
for i := 1; accum.Sign() > 0; i++ {
output.Add(output, accum)

accum.Mul(accum, numerator)
accum.Div(accum, denominator)
accum.Div(accum, big.NewInt(int64(i)))
}
return output.Div(output, denominator)
}
10 changes: 10 additions & 0 deletions core/types/access_list_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package types

import (
"bytes"
"math/big"

"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/rlp"
)

//go:generate gencodec -type AccessTuple -out gen_access_tuple.go
Expand Down Expand Up @@ -113,3 +115,11 @@ func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) {
func (tx *AccessListTx) setSignatureValues(chainID, v, r, s *big.Int) {
tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
}

func (tx *AccessListTx) encode(b *bytes.Buffer) error {
return rlp.Encode(b, tx)
}

func (tx *AccessListTx) decode(input []byte) error {
return rlp.DecodeBytes(input, tx)
}
10 changes: 10 additions & 0 deletions core/types/dynamic_fee_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
package types

import (
"bytes"
"math/big"

"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/rlp"
)

type DynamicFeeTx struct {
Expand Down Expand Up @@ -101,3 +103,11 @@ func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) {
func (tx *DynamicFeeTx) setSignatureValues(chainID, v, r, s *big.Int) {
tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
}

func (tx *DynamicFeeTx) encode(b *bytes.Buffer) error {
return rlp.Encode(b, tx)
}

func (tx *DynamicFeeTx) decode(input []byte) error {
return rlp.DecodeBytes(input, tx)
}
10 changes: 10 additions & 0 deletions core/types/l1_message_tx.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package types

import (
"bytes"
"math/big"

"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/rlp"
)

// payload, RLP encoded
Expand Down Expand Up @@ -52,3 +54,11 @@ func (tx *L1MessageTx) rawSignatureValues() (v, r, s *big.Int) {
func (tx *L1MessageTx) setSignatureValues(chainID, v, r, s *big.Int) {
// this is a noop for l1 message transactions
}

func (tx *L1MessageTx) encode(b *bytes.Buffer) error {
return rlp.Encode(b, tx)
}

func (tx *L1MessageTx) decode(input []byte) error {
return rlp.DecodeBytes(input, tx)
}
9 changes: 9 additions & 0 deletions core/types/legacy_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package types

import (
"bytes"
"math/big"

"github.com/scroll-tech/go-ethereum/common"
Expand Down Expand Up @@ -110,3 +111,11 @@ func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) {
func (tx *LegacyTx) setSignatureValues(chainID, v, r, s *big.Int) {
tx.V, tx.R, tx.S = v, r, s
}

func (tx *LegacyTx) encode(*bytes.Buffer) error {
panic("encode called on LegacyTx")
}

func (tx *LegacyTx) decode([]byte) error {
panic("decode called on LegacyTx)")
}
35 changes: 17 additions & 18 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ var (
ErrTxTypeNotSupported = errors.New("transaction type not supported")
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
errEmptyTypedTx = errors.New("empty typed transaction bytes")
errShortTypedTx = errors.New("typed transaction too short")
errInvalidYParity = errors.New("'yParity' field must be 0 or 1")
errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match")
errVYParityMissing = errors.New("missing 'yParity' or 'v' field in transaction")
Expand Down Expand Up @@ -94,6 +95,9 @@ type TxData interface {

rawSignatureValues() (v, r, s *big.Int)
setSignatureValues(chainID, v, r, s *big.Int)

encode(*bytes.Buffer) error
decode([]byte) error
}

// EncodeRLP implements rlp.Encoder
Expand All @@ -114,7 +118,7 @@ func (tx *Transaction) EncodeRLP(w io.Writer) error {
// encodeTyped writes the canonical encoding of a typed transaction to w.
func (tx *Transaction) encodeTyped(w *bytes.Buffer) error {
w.WriteByte(tx.Type())
return rlp.Encode(w, tx.inner)
return tx.inner.encode(w)
}

// MarshalBinary returns the canonical encoding of the transaction.
Expand Down Expand Up @@ -143,7 +147,9 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
tx.setDecoded(&inner, int(rlp.ListSize(size)))
}
return err
case kind == rlp.String:
case kind == rlp.Byte:
return errShortTypedTx
default:
// It's an EIP-2718 typed TX envelope.
var b []byte
if b, err = s.Bytes(); err != nil {
Expand All @@ -154,8 +160,6 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
tx.setDecoded(inner, len(b))
}
return err
default:
return rlp.ErrExpectedList
}
}

Expand Down Expand Up @@ -183,29 +187,24 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error {

// decodeTyped decodes a typed transaction from the canonical format.
func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
if len(b) == 0 {
return nil, errEmptyTypedTx
if len(b) <= 1 {
return nil, errShortTypedTx
}
var inner TxData
switch b[0] {
case AccessListTxType:
var inner AccessListTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
inner = new(AccessListTx)
case DynamicFeeTxType:
var inner DynamicFeeTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
inner = new(DynamicFeeTx)
case BlobTxType:
var inner BlobTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
inner = new(BlobTx)
case L1MessageTxType:
var inner L1MessageTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
inner = new(L1MessageTx)
default:
return nil, ErrTxTypeNotSupported
}
err := inner.decode(b[1:])
return inner, err
}

// setDecoded sets the inner transaction and size after decoding.
Expand Down
11 changes: 11 additions & 0 deletions core/types/transaction_marshalling.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/crypto/kzg4844"
)

// txJSON is the JSON representation of transactions.
Expand All @@ -48,6 +49,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 @@ -155,6 +161,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
15 changes: 15 additions & 0 deletions ethclient/ethclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -719,5 +719,20 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
if msg.GasPrice != nil {
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
}
if msg.GasFeeCap != nil {
arg["maxFeePerGas"] = (*hexutil.Big)(msg.GasFeeCap)
}
if msg.GasTipCap != nil {
arg["maxPriorityFeePerGas"] = (*hexutil.Big)(msg.GasTipCap)
}
if msg.AccessList != nil {
arg["accessList"] = msg.AccessList
}
if msg.BlobGasFeeCap != nil {
arg["maxFeePerBlobGas"] = (*hexutil.Big)(msg.BlobGasFeeCap)
}
if msg.BlobHashes != nil {
arg["blobVersionedHashes"] = msg.BlobHashes
}
return arg
}
15 changes: 15 additions & 0 deletions ethclient/gethclient/gethclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,21 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
if msg.GasPrice != nil {
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice)
}
if msg.GasFeeCap != nil {
arg["maxFeePerGas"] = (*hexutil.Big)(msg.GasFeeCap)
}
if msg.GasTipCap != nil {
arg["maxPriorityFeePerGas"] = (*hexutil.Big)(msg.GasTipCap)
}
if msg.AccessList != nil {
arg["accessList"] = msg.AccessList
}
if msg.BlobGasFeeCap != nil {
arg["maxFeePerBlobGas"] = (*hexutil.Big)(msg.BlobGasFeeCap)
}
if msg.BlobHashes != nil {
arg["blobVersionedHashes"] = msg.BlobHashes
}
return arg
}

Expand Down
4 changes: 4 additions & 0 deletions interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ type CallMsg struct {
Data []byte // input data, usually an ABI-encoded contract method invocation

AccessList types.AccessList // EIP-2930 access list.

// For BlobTxType
BlobGasFeeCap *big.Int
BlobHashes []common.Hash
}

// A ContractCaller provides contract calls, essentially transactions that are executed by
Expand Down
6 changes: 5 additions & 1 deletion params/protocol_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,11 @@ const (
RefundQuotient uint64 = 2
RefundQuotientEIP3529 uint64 = 5

BlobTxBlobGasPerBlob = 1 << 17 // Gas consumption of a single data blob (== blob byte size)
BlobTxBlobGasPerBlob = 1 << 17 // Gas consumption of a single data blob (== blob byte size)
BlobTxMinBlobGasprice = 1 // Minimum gas price for data blobs
BlobTxBlobGaspriceUpdateFraction = 3338477 // Controls the maximum rate of change for blob gas price

BlobTxTargetBlobGasPerBlock = 3 * BlobTxBlobGasPerBlob // Target consumable blob gas for data blobs per block (for 1559-like pricing)
)

// Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations
Expand Down
2 changes: 1 addition & 1 deletion params/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
const (
VersionMajor = 5 // Major version component of the current release
VersionMinor = 1 // Minor version component of the current release
VersionPatch = 22 // Patch version component of the current release
VersionPatch = 23 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string
)

Expand Down
Loading

0 comments on commit 4553f5f

Please sign in to comment.