feat(ethclient & gethclient): support blob transaction (#661)

* feat: support blob transaction

* sync tx encoding/decoding

* more fixes

* add uint236 support in rlp encoding decoding

* revert a format change

* trigger ci
This commit is contained in:
colin 2024-03-14 17:51:30 +08:00 committed by GitHub
parent ccec84ce63
commit 4553f5f269
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 284 additions and 20 deletions

60
consensus/misc/eip4844.go Normal file
View file

@ -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)
}

View file

@ -17,9 +17,11 @@
package types package types
import ( import (
"bytes"
"math/big" "math/big"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/rlp"
) )
//go:generate gencodec -type AccessTuple -out gen_access_tuple.go //go:generate gencodec -type AccessTuple -out gen_access_tuple.go
@ -113,3 +115,11 @@ func (tx *AccessListTx) rawSignatureValues() (v, r, s *big.Int) {
func (tx *AccessListTx) setSignatureValues(chainID, 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 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)
}

View file

@ -17,9 +17,11 @@
package types package types
import ( import (
"bytes"
"math/big" "math/big"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/rlp"
) )
type DynamicFeeTx struct { type DynamicFeeTx struct {
@ -101,3 +103,11 @@ func (tx *DynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) {
func (tx *DynamicFeeTx) setSignatureValues(chainID, 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 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)
}

View file

@ -1,9 +1,11 @@
package types package types
import ( import (
"bytes"
"math/big" "math/big"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/rlp"
) )
// payload, RLP encoded // payload, RLP encoded
@ -52,3 +54,11 @@ func (tx *L1MessageTx) rawSignatureValues() (v, r, s *big.Int) {
func (tx *L1MessageTx) setSignatureValues(chainID, v, r, s *big.Int) { func (tx *L1MessageTx) setSignatureValues(chainID, v, r, s *big.Int) {
// this is a noop for l1 message transactions // 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)
}

View file

@ -17,6 +17,7 @@
package types package types
import ( import (
"bytes"
"math/big" "math/big"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
@ -110,3 +111,11 @@ func (tx *LegacyTx) rawSignatureValues() (v, r, s *big.Int) {
func (tx *LegacyTx) setSignatureValues(chainID, v, r, s *big.Int) { func (tx *LegacyTx) setSignatureValues(chainID, v, r, s *big.Int) {
tx.V, tx.R, tx.S = v, r, s 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)")
}

View file

@ -41,6 +41,7 @@ var (
ErrTxTypeNotSupported = errors.New("transaction type not supported") ErrTxTypeNotSupported = errors.New("transaction type not supported")
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee") ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
errEmptyTypedTx = errors.New("empty typed transaction bytes") errEmptyTypedTx = errors.New("empty typed transaction bytes")
errShortTypedTx = errors.New("typed transaction too short")
errInvalidYParity = errors.New("'yParity' field must be 0 or 1") errInvalidYParity = errors.New("'yParity' field must be 0 or 1")
errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match") errVYParityMismatch = errors.New("'v' and 'yParity' fields do not match")
errVYParityMissing = errors.New("missing 'yParity' or 'v' field in transaction") errVYParityMissing = errors.New("missing 'yParity' or 'v' field in transaction")
@ -94,6 +95,9 @@ type TxData interface {
rawSignatureValues() (v, r, s *big.Int) rawSignatureValues() (v, r, s *big.Int)
setSignatureValues(chainID, v, r, s *big.Int) setSignatureValues(chainID, v, r, s *big.Int)
encode(*bytes.Buffer) error
decode([]byte) error
} }
// EncodeRLP implements rlp.Encoder // EncodeRLP implements rlp.Encoder
@ -114,7 +118,7 @@ func (tx *Transaction) EncodeRLP(w io.Writer) error {
// encodeTyped writes the canonical encoding of a typed transaction to w. // encodeTyped writes the canonical encoding of a typed transaction to w.
func (tx *Transaction) encodeTyped(w *bytes.Buffer) error { func (tx *Transaction) encodeTyped(w *bytes.Buffer) error {
w.WriteByte(tx.Type()) w.WriteByte(tx.Type())
return rlp.Encode(w, tx.inner) return tx.inner.encode(w)
} }
// MarshalBinary returns the canonical encoding of the transaction. // MarshalBinary returns the canonical encoding of the transaction.
@ -143,7 +147,9 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
tx.setDecoded(&inner, int(rlp.ListSize(size))) tx.setDecoded(&inner, int(rlp.ListSize(size)))
} }
return err return err
case kind == rlp.String: case kind == rlp.Byte:
return errShortTypedTx
default:
// It's an EIP-2718 typed TX envelope. // It's an EIP-2718 typed TX envelope.
var b []byte var b []byte
if b, err = s.Bytes(); err != nil { if b, err = s.Bytes(); err != nil {
@ -154,8 +160,6 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
tx.setDecoded(inner, len(b)) tx.setDecoded(inner, len(b))
} }
return err return err
default:
return rlp.ErrExpectedList
} }
} }
@ -183,29 +187,24 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error {
// decodeTyped decodes a typed transaction from the canonical format. // decodeTyped decodes a typed transaction from the canonical format.
func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
if len(b) == 0 { if len(b) <= 1 {
return nil, errEmptyTypedTx return nil, errShortTypedTx
} }
var inner TxData
switch b[0] { switch b[0] {
case AccessListTxType: case AccessListTxType:
var inner AccessListTx inner = new(AccessListTx)
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case DynamicFeeTxType: case DynamicFeeTxType:
var inner DynamicFeeTx inner = new(DynamicFeeTx)
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case BlobTxType: case BlobTxType:
var inner BlobTx inner = new(BlobTx)
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case L1MessageTxType: case L1MessageTxType:
var inner L1MessageTx inner = new(L1MessageTx)
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
default: default:
return nil, ErrTxTypeNotSupported return nil, ErrTxTypeNotSupported
} }
err := inner.decode(b[1:])
return inner, err
} }
// setDecoded sets the inner transaction and size after decoding. // setDecoded sets the inner transaction and size after decoding.

View file

@ -25,6 +25,7 @@ import (
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/crypto/kzg4844"
) )
// txJSON is the JSON representation of transactions. // txJSON is the JSON representation of transactions.
@ -48,6 +49,11 @@ type txJSON struct {
S *hexutil.Big `json:"s"` S *hexutil.Big `json:"s"`
YParity *hexutil.Uint64 `json:"yParity,omitempty"` 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: // Only used for encoding:
Hash common.Hash `json:"hash"` Hash common.Hash `json:"hash"`
@ -155,6 +161,11 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) {
enc.S = (*hexutil.Big)(itx.S.ToBig()) enc.S = (*hexutil.Big)(itx.S.ToBig())
yparity := itx.V.Uint64() yparity := itx.V.Uint64()
enc.YParity = (*hexutil.Uint64)(&yparity) 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) return json.Marshal(&enc)
} }

View file

@ -719,5 +719,20 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
if msg.GasPrice != nil { if msg.GasPrice != nil {
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) 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 return arg
} }

View file

@ -213,6 +213,21 @@ func toCallArg(msg ethereum.CallMsg) interface{} {
if msg.GasPrice != nil { if msg.GasPrice != nil {
arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) 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 return arg
} }

View file

@ -123,6 +123,10 @@ type CallMsg struct {
Data []byte // input data, usually an ABI-encoded contract method invocation Data []byte // input data, usually an ABI-encoded contract method invocation
AccessList types.AccessList // EIP-2930 access list. 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 // A ContractCaller provides contract calls, essentially transactions that are executed by

View file

@ -160,7 +160,11 @@ const (
RefundQuotient uint64 = 2 RefundQuotient uint64 = 2
RefundQuotientEIP3529 uint64 = 5 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 // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations

View file

@ -24,7 +24,7 @@ import (
const ( const (
VersionMajor = 5 // Major version component of the current release VersionMajor = 5 // Major version component of the current release
VersionMinor = 1 // Minor 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 VersionMeta = "mainnet" // Version metadata to append to the version string
) )

View file

@ -27,6 +27,8 @@ import (
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
"github.com/holiman/uint256"
) )
//lint:ignore ST1012 EOL is not an error. //lint:ignore ST1012 EOL is not an error.
@ -50,6 +52,7 @@ var (
errUintOverflow = errors.New("rlp: uint overflow") errUintOverflow = errors.New("rlp: uint overflow")
errNoPointer = errors.New("rlp: interface given to Decode must be a pointer") errNoPointer = errors.New("rlp: interface given to Decode must be a pointer")
errDecodeIntoNil = errors.New("rlp: pointer given to Decode must not be nil") errDecodeIntoNil = errors.New("rlp: pointer given to Decode must not be nil")
errUint256Large = errors.New("rlp: value too large for uint256")
streamPool = sync.Pool{ streamPool = sync.Pool{
New: func() interface{} { return new(Stream) }, New: func() interface{} { return new(Stream) },
@ -146,6 +149,7 @@ func addErrorContext(err error, ctx string) error {
var ( var (
decoderInterface = reflect.TypeOf(new(Decoder)).Elem() decoderInterface = reflect.TypeOf(new(Decoder)).Elem()
bigInt = reflect.TypeOf(big.Int{}) bigInt = reflect.TypeOf(big.Int{})
u256Int = reflect.TypeOf(uint256.Int{})
) )
func makeDecoder(typ reflect.Type, tags tags) (dec decoder, err error) { func makeDecoder(typ reflect.Type, tags tags) (dec decoder, err error) {
@ -157,6 +161,10 @@ func makeDecoder(typ reflect.Type, tags tags) (dec decoder, err error) {
return decodeBigInt, nil return decodeBigInt, nil
case typ.AssignableTo(bigInt): case typ.AssignableTo(bigInt):
return decodeBigIntNoPtr, nil return decodeBigIntNoPtr, nil
case typ == reflect.PtrTo(u256Int):
return decodeU256, nil
case typ == u256Int:
return decodeU256NoPtr, nil
case kind == reflect.Ptr: case kind == reflect.Ptr:
return makePtrDecoder(typ, tags) return makePtrDecoder(typ, tags)
case reflect.PtrTo(typ).Implements(decoderInterface): case reflect.PtrTo(typ).Implements(decoderInterface):
@ -268,6 +276,24 @@ func decodeBigInt(s *Stream, val reflect.Value) error {
return nil return nil
} }
func decodeU256NoPtr(s *Stream, val reflect.Value) error {
return decodeU256(s, val.Addr())
}
func decodeU256(s *Stream, val reflect.Value) error {
i := val.Interface().(*uint256.Int)
if i == nil {
i = new(uint256.Int)
val.Set(reflect.ValueOf(i))
}
err := s.ReadUint256(i)
if err != nil {
return wrapStreamError(err, val.Type())
}
return nil
}
func makeListDecoder(typ reflect.Type, tag tags) (decoder, error) { func makeListDecoder(typ reflect.Type, tag tags) (decoder, error) {
etype := typ.Elem() etype := typ.Elem()
if etype.Kind() == reflect.Uint8 && !reflect.PtrTo(etype).Implements(decoderInterface) { if etype.Kind() == reflect.Uint8 && !reflect.PtrTo(etype).Implements(decoderInterface) {
@ -781,6 +807,45 @@ func (s *Stream) ListEnd() error {
return nil return nil
} }
// ReadUint256 decodes the next value as a uint256.
func (s *Stream) ReadUint256(dst *uint256.Int) error {
var buffer []byte
kind, size, err := s.Kind()
switch {
case err != nil:
return err
case kind == List:
return ErrExpectedString
case kind == Byte:
buffer = s.uintbuf[:1]
buffer[0] = s.byteval
s.kind = -1 // re-arm Kind
case size == 0:
// Avoid zero-length read.
s.kind = -1
case size <= uint64(len(s.uintbuf)):
// All possible uint256 values fit into s.uintbuf.
buffer = s.uintbuf[:size]
if err := s.readFull(buffer); err != nil {
return err
}
// Reject inputs where single byte encoding should have been used.
if size == 1 && buffer[0] < 128 {
return ErrCanonSize
}
default:
return errUint256Large
}
// Reject leading zero bytes.
if len(buffer) > 0 && buffer[0] == 0 {
return ErrCanonInt
}
// Set the integer bytes.
dst.SetBytes(buffer)
return nil
}
// Decode decodes a value and stores the result in the value pointed // Decode decodes a value and stores the result in the value pointed
// to by val. Please see the documentation for the Decode function // to by val. Please see the documentation for the Decode function
// to learn about the decoding rules. // to learn about the decoding rules.

View file

@ -17,11 +17,14 @@
package rlp package rlp
import ( import (
"encoding/binary"
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
"reflect" "reflect"
"sync" "sync"
"github.com/holiman/uint256"
) )
var ( var (
@ -189,6 +192,35 @@ func (w *encbuf) encodeUint(i uint64) {
} }
} }
func (w *encbuf) writeUint64(i uint64) {
if i == 0 {
w.str = append(w.str, 0x80)
} else if i < 128 {
// fits single byte
w.str = append(w.str, byte(i))
} else {
s := putint(w.sizebuf[1:], i)
w.sizebuf[0] = 0x80 + byte(s)
w.str = append(w.str, w.sizebuf[:s+1]...)
}
}
func (w *encbuf) writeUint256(z *uint256.Int) {
bitlen := z.BitLen()
if bitlen <= 64 {
w.writeUint64(z.Uint64())
return
}
nBytes := byte((bitlen + 7) / 8)
var b [33]byte
binary.BigEndian.PutUint64(b[1:9], z[3])
binary.BigEndian.PutUint64(b[9:17], z[2])
binary.BigEndian.PutUint64(b[17:25], z[1])
binary.BigEndian.PutUint64(b[25:33], z[0])
b[32-nBytes] = 0x80 + nBytes
w.str = append(w.str, b[32-nBytes:]...)
}
// list adds a new list header to the header stack. It returns the index // list adds a new list header to the header stack. It returns the index
// of the header. The caller must call listEnd with this index after encoding // of the header. The caller must call listEnd with this index after encoding
// the content of the list. // the content of the list.
@ -332,6 +364,10 @@ func makeWriter(typ reflect.Type, ts tags) (writer, error) {
return writeBigIntPtr, nil return writeBigIntPtr, nil
case typ.AssignableTo(bigInt): case typ.AssignableTo(bigInt):
return writeBigIntNoPtr, nil return writeBigIntNoPtr, nil
case typ == reflect.PtrTo(u256Int):
return writeU256IntPtr, nil
case typ == u256Int:
return writeU256IntNoPtr, nil
case kind == reflect.Ptr: case kind == reflect.Ptr:
return makePtrWriter(typ, ts) return makePtrWriter(typ, ts)
case reflect.PtrTo(typ).Implements(encoderInterface): case reflect.PtrTo(typ).Implements(encoderInterface):
@ -420,6 +456,22 @@ func writeBigInt(i *big.Int, w *encbuf) error {
return nil return nil
} }
func writeU256IntPtr(val reflect.Value, w *encbuf) error {
ptr := val.Interface().(*uint256.Int)
if ptr == nil {
w.str = append(w.str, 0x80)
return nil
}
w.writeUint256(ptr)
return nil
}
func writeU256IntNoPtr(val reflect.Value, w *encbuf) error {
i := val.Interface().(uint256.Int)
w.writeUint256(&i)
return nil
}
func writeBytes(val reflect.Value, w *encbuf) error { func writeBytes(val reflect.Value, w *encbuf) error {
w.encodeString(val.Bytes()) w.encodeString(val.Bytes())
return nil return nil