Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core/types, params: add blob transaction type, RLP encoded for now #27049

Merged
merged 5 commits into from
Apr 21, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions core/types/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ var (

// Transaction types.
const (
LegacyTxType = iota
AccessListTxType
DynamicFeeTxType
LegacyTxType = 0x00
AccessListTxType = 0x01
DynamicFeeTxType = 0x02
BlobTxType = 0x05
karalabe marked this conversation as resolved.
Show resolved Hide resolved
)

// Transaction is an Ethereum transaction.
Expand Down Expand Up @@ -82,6 +83,9 @@ type TxData interface {
value() *big.Int
nonce() uint64
to() *common.Address
blobGas() uint64
blobGasFeeCap() *big.Int
blobHashes() []common.Hash

rawSignatureValues() (v, r, s *big.Int)
setSignatureValues(chainID, v, r, s *big.Int)
Expand Down Expand Up @@ -192,6 +196,10 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
var inner DynamicFeeTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case BlobTxType:
var inner BlobTx
err := rlp.DecodeBytes(b[1:], &inner) // TODO(karalabe): This needs to be ssz
return &inner, err
default:
return nil, ErrTxTypeNotSupported
}
Expand Down Expand Up @@ -281,6 +289,15 @@ func (tx *Transaction) GasTipCap() *big.Int { return new(big.Int).Set(tx.inner.g
// GasFeeCap returns the fee cap per gas of the transaction.
func (tx *Transaction) GasFeeCap() *big.Int { return new(big.Int).Set(tx.inner.gasFeeCap()) }

// BlobGas returns the data gas limit of the transaction for blob transactions, 0 otherwise.
func (tx *Transaction) BlobGas() uint64 { return tx.inner.blobGas() }

// BlobGasFeeCap returns the data gas fee cap per data gas of the transaction for blob transactions, nil otherwise.
func (tx *Transaction) BlobGasFeeCap() *big.Int { return tx.inner.blobGasFeeCap() }

// BlobHashes returns the hases of the blob commitments for blob transactions, nil otherwise.
func (tx *Transaction) BlobHashes() []common.Hash { return tx.inner.blobHashes() }

// Value returns the ether amount of the transaction.
func (tx *Transaction) Value() *big.Int { return new(big.Int).Set(tx.inner.value()) }

Expand All @@ -293,9 +310,12 @@ func (tx *Transaction) To() *common.Address {
return copyAddressPtr(tx.inner.to())
}

// Cost returns gas * gasPrice + value.
// Cost returns (gas * gasPrice) + (blobGas * blobGasPrice) + value.
func (tx *Transaction) Cost() *big.Int {
total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
if tx.Type() == BlobTxType {
total.Add(total, new(big.Int).Mul(tx.BlobGasFeeCap(), new(big.Int).SetUint64(tx.BlobGas())))
}
total.Add(total, tx.Value())
return total
}
Expand Down Expand Up @@ -364,6 +384,16 @@ func (tx *Transaction) EffectiveGasTipIntCmp(other *big.Int, baseFee *big.Int) i
return tx.EffectiveGasTipValue(baseFee).Cmp(other)
}

// BlobGasFeeCapCmp compares the blob fee cap of two transactions.
func (tx *Transaction) BlobGasFeeCapCmp(other *Transaction) int {
return tx.inner.blobGasFeeCap().Cmp(other.inner.blobGasFeeCap())
}

// BlobGasFeeCapIntCmp compares the blob fee cap of the transaction against the given blob fee cap.
func (tx *Transaction) BlobGasFeeCapIntCmp(other *big.Int) int {
return tx.inner.blobGasFeeCap().Cmp(other)
}

// Hash returns the transaction hash.
func (tx *Transaction) Hash() common.Hash {
if hash := tx.hash.Load(); hash != nil {
Expand All @@ -387,7 +417,11 @@ func (tx *Transaction) Size() uint64 {
return size.(uint64)
}
c := writeCounter(0)
rlp.Encode(&c, &tx.inner)
if tx.Type() == BlobTxType {
rlp.Encode(&c, &tx.inner) // TODO(karalabe): Replace with SSZ encoding
} else {
rlp.Encode(&c, &tx.inner)
}

size := uint64(c)
if tx.Type() != LegacyTxType {
Expand Down
74 changes: 73 additions & 1 deletion core/types/transaction_signing.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int) Signer {
// have the current block number available, use MakeSigner instead.
func LatestSigner(config *params.ChainConfig) Signer {
if config.ChainID != nil {
if config.CancunTime != nil {
return NewCancunSigner(config.ChainID)
}
if config.LondonBlock != nil {
return NewLondonSigner(config.ChainID)
}
Expand All @@ -87,7 +90,7 @@ func LatestSignerForChainID(chainID *big.Int) Signer {
if chainID == nil {
return HomesteadSigner{}
}
return NewLondonSigner(chainID)
return NewCancunSigner(chainID)
}

// SignTx signs the transaction using the given signer and private key.
Expand Down Expand Up @@ -170,6 +173,75 @@ type Signer interface {
Equal(Signer) bool
}

type cancunSigner struct{ londonSigner }

// NewCancunSigner returns a signer that accepts
// - EIP-4844 blob transactions
// - EIP-1559 dynamic fee transactions
// - EIP-2930 access list transactions,
// - EIP-155 replay protected transactions, and
// - legacy Homestead transactions.
func NewCancunSigner(chainId *big.Int) Signer {
return cancunSigner{londonSigner{eip2930Signer{NewEIP155Signer(chainId)}}}
}

func (s cancunSigner) Sender(tx *Transaction) (common.Address, error) {
if tx.Type() != BlobTxType {
return s.londonSigner.Sender(tx)
}
V, R, S := tx.RawSignatureValues()
// Blob txs are defined to use 0 and 1 as their recovery
// id, add 27 to become equivalent to unprotected Homestead signatures.
V = new(big.Int).Add(V, big.NewInt(27))
if tx.ChainId().Cmp(s.chainId) != 0 {
return common.Address{}, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, tx.ChainId(), s.chainId)
}
return recoverPlain(s.Hash(tx), R, S, V, true)
}

func (s cancunSigner) Equal(s2 Signer) bool {
x, ok := s2.(cancunSigner)
return ok && x.chainId.Cmp(s.chainId) == 0
}

func (s cancunSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
txdata, ok := tx.inner.(*BlobTx)
if !ok {
return s.londonSigner.SignatureValues(tx, sig)
}
// Check that chain ID of tx matches the signer. We also accept ID zero here,
// because it indicates that the chain ID was not specified in the tx.
if txdata.ChainID.Sign() != 0 && txdata.ChainID.ToBig().Cmp(s.chainId) != 0 {
return nil, nil, nil, fmt.Errorf("%w: have %d want %d", ErrInvalidChainId, txdata.ChainID, s.chainId)
}
R, S, _ = decodeSignature(sig)
V = big.NewInt(int64(sig[64]))
return R, S, V, nil
}

// Hash returns the hash to be signed by the sender.
// It does not uniquely identify the transaction.
func (s cancunSigner) Hash(tx *Transaction) common.Hash {
if tx.Type() != BlobTxType {
return s.londonSigner.Hash(tx)
}
return prefixedRlpHash(
tx.Type(),
[]interface{}{
s.chainId,
tx.Nonce(),
tx.GasTipCap(),
tx.GasFeeCap(),
tx.Gas(),
tx.To(),
tx.Value(),
tx.Data(),
tx.AccessList(),
tx.BlobGasFeeCap(),
tx.BlobHashes(),
})
}

type londonSigner struct{ eip2930Signer }

// NewLondonSigner returns a signer that accepts
Expand Down
25 changes: 14 additions & 11 deletions core/types/tx_access_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,20 @@ func (tx *AccessListTx) copy() TxData {
}

// accessors for innerTx.
func (tx *AccessListTx) txType() byte { return AccessListTxType }
func (tx *AccessListTx) chainID() *big.Int { return tx.ChainID }
func (tx *AccessListTx) accessList() AccessList { return tx.AccessList }
func (tx *AccessListTx) data() []byte { return tx.Data }
func (tx *AccessListTx) gas() uint64 { return tx.Gas }
func (tx *AccessListTx) gasPrice() *big.Int { return tx.GasPrice }
func (tx *AccessListTx) gasTipCap() *big.Int { return tx.GasPrice }
func (tx *AccessListTx) gasFeeCap() *big.Int { return tx.GasPrice }
func (tx *AccessListTx) value() *big.Int { return tx.Value }
func (tx *AccessListTx) nonce() uint64 { return tx.Nonce }
func (tx *AccessListTx) to() *common.Address { return tx.To }
func (tx *AccessListTx) txType() byte { return AccessListTxType }
func (tx *AccessListTx) chainID() *big.Int { return tx.ChainID }
func (tx *AccessListTx) accessList() AccessList { return tx.AccessList }
func (tx *AccessListTx) data() []byte { return tx.Data }
func (tx *AccessListTx) gas() uint64 { return tx.Gas }
func (tx *AccessListTx) gasPrice() *big.Int { return tx.GasPrice }
func (tx *AccessListTx) gasTipCap() *big.Int { return tx.GasPrice }
func (tx *AccessListTx) gasFeeCap() *big.Int { return tx.GasPrice }
func (tx *AccessListTx) value() *big.Int { return tx.Value }
func (tx *AccessListTx) nonce() uint64 { return tx.Nonce }
func (tx *AccessListTx) to() *common.Address { return tx.To }
func (tx *AccessListTx) blobGas() uint64 { return 0 }
func (tx *AccessListTx) blobGasFeeCap() *big.Int { return nil }
func (tx *AccessListTx) blobHashes() []common.Hash { return nil }

func (tx *AccessListTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
return dst.Set(tx.GasPrice)
Expand Down
132 changes: 132 additions & 0 deletions core/types/tx_blob.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// 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 types

import (
"math/big"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)

// BlobTx represents an EIP-4844 transaction.
type BlobTx struct {
ChainID *uint256.Int
Nonce uint64
GasTipCap *uint256.Int // a.k.a. maxPriorityFeePerGas
GasFeeCap *uint256.Int // a.k.a. maxFeePerGas
Gas uint64
To *common.Address // `rlp:"nil"` // nil means contract creation
Value *uint256.Int
Data []byte
AccessList AccessList
BlobFeeCap *uint256.Int // a.k.a. maxFeePerDataGas
BlobHashes []common.Hash

// Signature values
V *uint256.Int `json:"v" gencodec:"required"`
R *uint256.Int `json:"r" gencodec:"required"`
S *uint256.Int `json:"s" gencodec:"required"`
}

// copy creates a deep copy of the transaction data and initializes all fields.
func (tx *BlobTx) copy() TxData {
cpy := &BlobTx{
Nonce: tx.Nonce,
To: tx.To, //copyAddressPtr(tx.To),
karalabe marked this conversation as resolved.
Show resolved Hide resolved
Data: common.CopyBytes(tx.Data),
Gas: tx.Gas,
// These are copied below.
AccessList: make(AccessList, len(tx.AccessList)),
BlobHashes: make([]common.Hash, len(tx.BlobHashes)),
Value: new(uint256.Int),
ChainID: new(uint256.Int),
GasTipCap: new(uint256.Int),
GasFeeCap: new(uint256.Int),
BlobFeeCap: new(uint256.Int),
V: new(uint256.Int),
R: new(uint256.Int),
S: new(uint256.Int),
}
copy(cpy.AccessList, tx.AccessList)
copy(cpy.BlobHashes, tx.BlobHashes)

if tx.Value != nil {
cpy.Value.Set(tx.Value)
}
if tx.ChainID != nil {
cpy.ChainID.Set(tx.ChainID)
}
if tx.GasTipCap != nil {
cpy.GasTipCap.Set(tx.GasTipCap)
}
if tx.GasFeeCap != nil {
cpy.GasFeeCap.Set(tx.GasFeeCap)
}
if tx.BlobFeeCap != nil {
cpy.BlobFeeCap.Set(tx.BlobFeeCap)
}
if tx.V != nil {
cpy.V.Set(tx.V)
}
if tx.R != nil {
cpy.R.Set(tx.R)
}
if tx.S != nil {
cpy.S.Set(tx.S)
}
return cpy
}

// accessors for innerTx.
func (tx *BlobTx) txType() byte { return BlobTxType }
func (tx *BlobTx) chainID() *big.Int { return tx.ChainID.ToBig() }
func (tx *BlobTx) accessList() AccessList { return nil /*tx.AccessList*/ }
func (tx *BlobTx) data() []byte { return tx.Data }
func (tx *BlobTx) gas() uint64 { return tx.Gas }
func (tx *BlobTx) gasFeeCap() *big.Int { return tx.GasFeeCap.ToBig() }
func (tx *BlobTx) gasTipCap() *big.Int { return tx.GasTipCap.ToBig() }
func (tx *BlobTx) gasPrice() *big.Int { return tx.GasFeeCap.ToBig() }
func (tx *BlobTx) value() *big.Int { return tx.Value.ToBig() }
func (tx *BlobTx) nonce() uint64 { return tx.Nonce }
func (tx *BlobTx) to() *common.Address { return tx.To }
func (tx *BlobTx) blobGas() uint64 { return params.BlobTxDataGasPerBlob * uint64(len(tx.BlobHashes)) }
func (tx *BlobTx) blobGasFeeCap() *big.Int { return tx.BlobFeeCap.ToBig() }
func (tx *BlobTx) blobHashes() []common.Hash { return tx.BlobHashes }

func (tx *BlobTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
if baseFee == nil {
return dst.Set(tx.GasFeeCap.ToBig())
}
tip := dst.Sub(tx.GasFeeCap.ToBig(), baseFee)
if tip.Cmp(tx.GasTipCap.ToBig()) > 0 {
tip.Set(tx.GasTipCap.ToBig())
}
return tip.Add(tip, baseFee)
}

func (tx *BlobTx) rawSignatureValues() (v, r, s *big.Int) {
return tx.V.ToBig(), tx.R.ToBig(), tx.S.ToBig()
}

func (tx *BlobTx) setSignatureValues(chainID, v, r, s *big.Int) {
tx.ChainID.SetFromBig(chainID)
tx.V.SetFromBig(v)
tx.R.SetFromBig(r)
tx.S.SetFromBig(s)
}
26 changes: 15 additions & 11 deletions core/types/tx_dynamic_fee.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/ethereum/go-ethereum/common"
)

// DynamicFeeTx represents an EIP-1559 transaction.
type DynamicFeeTx struct {
ChainID *big.Int
Nonce uint64
Expand Down Expand Up @@ -82,17 +83,20 @@ func (tx *DynamicFeeTx) copy() TxData {
}

// accessors for innerTx.
func (tx *DynamicFeeTx) txType() byte { return DynamicFeeTxType }
func (tx *DynamicFeeTx) chainID() *big.Int { return tx.ChainID }
func (tx *DynamicFeeTx) accessList() AccessList { return tx.AccessList }
func (tx *DynamicFeeTx) data() []byte { return tx.Data }
func (tx *DynamicFeeTx) gas() uint64 { return tx.Gas }
func (tx *DynamicFeeTx) gasFeeCap() *big.Int { return tx.GasFeeCap }
func (tx *DynamicFeeTx) gasTipCap() *big.Int { return tx.GasTipCap }
func (tx *DynamicFeeTx) gasPrice() *big.Int { return tx.GasFeeCap }
func (tx *DynamicFeeTx) value() *big.Int { return tx.Value }
func (tx *DynamicFeeTx) nonce() uint64 { return tx.Nonce }
func (tx *DynamicFeeTx) to() *common.Address { return tx.To }
func (tx *DynamicFeeTx) txType() byte { return DynamicFeeTxType }
func (tx *DynamicFeeTx) chainID() *big.Int { return tx.ChainID }
func (tx *DynamicFeeTx) accessList() AccessList { return tx.AccessList }
func (tx *DynamicFeeTx) data() []byte { return tx.Data }
func (tx *DynamicFeeTx) gas() uint64 { return tx.Gas }
func (tx *DynamicFeeTx) gasFeeCap() *big.Int { return tx.GasFeeCap }
func (tx *DynamicFeeTx) gasTipCap() *big.Int { return tx.GasTipCap }
func (tx *DynamicFeeTx) gasPrice() *big.Int { return tx.GasFeeCap }
func (tx *DynamicFeeTx) value() *big.Int { return tx.Value }
func (tx *DynamicFeeTx) nonce() uint64 { return tx.Nonce }
func (tx *DynamicFeeTx) to() *common.Address { return tx.To }
func (tx *DynamicFeeTx) blobGas() uint64 { return 0 }
func (tx *DynamicFeeTx) blobGasFeeCap() *big.Int { return nil }
func (tx *DynamicFeeTx) blobHashes() []common.Hash { return nil }

func (tx *DynamicFeeTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int {
if baseFee == nil {
Expand Down
Loading