diff --git a/accounts/external/backend.go b/accounts/external/backend.go index 344042c8bf..49b5f2e934 100644 --- a/accounts/external/backend.go +++ b/accounts/external/backend.go @@ -215,7 +215,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio switch tx.Type() { case types.LegacyTxType, types.AccessListTxType: args.GasPrice = (*hexutil.Big)(tx.GasPrice()) - case types.DynamicFeeTxType, types.CeloDynamicFeeTxType: + case types.DynamicFeeTxType, types.CeloDynamicFeeTxType, types.CeloDenominatedTxType: args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap()) args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap()) default: diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 9d777284bb..bff4675d88 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -287,7 +287,7 @@ func New(config Config, chain BlockChain) *LegacyPool { // pool, specifically, whether it is a Legacy, AccessList or Dynamic transaction. func (pool *LegacyPool) Filter(tx *types.Transaction) bool { switch tx.Type() { - case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.CeloDynamicFeeTxType: + case types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, types.CeloDynamicFeeTxType, types.CeloDenominatedTxType: return true default: return false @@ -626,7 +626,8 @@ func (pool *LegacyPool) validateTxBasics(tx *types.Transaction, local bool) erro types.LegacyTxType, types.AccessListTxType, types.DynamicFeeTxType, - types.CeloDynamicFeeTxType), + types.CeloDynamicFeeTxType, + types.CeloDenominatedTxType), MaxSize: txMaxSize, MinTip: pool.gasTip.Load(), } diff --git a/core/types/celo_denominated_tx.go b/core/types/celo_denominated_tx.go new file mode 100644 index 0000000000..f074e243e3 --- /dev/null +++ b/core/types/celo_denominated_tx.go @@ -0,0 +1,120 @@ +package types + +import ( + "bytes" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +type CeloDenominatedTx struct { + ChainID *big.Int + Nonce uint64 + GasTipCap *big.Int + GasFeeCap *big.Int + Gas uint64 + To *common.Address `rlp:"nil"` // nil means contract creation + Value *big.Int + Data []byte + AccessList AccessList + + FeeCurrency *common.Address `rlp:"nil"` // nil means native currency + MaxFeeInFeeCurrency *big.Int + + // Signature values + V *big.Int `json:"v" gencodec:"required"` + R *big.Int `json:"r" gencodec:"required"` + S *big.Int `json:"s" gencodec:"required"` +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *CeloDenominatedTx) copy() TxData { + cpy := &CeloDenominatedTx{ + Nonce: tx.Nonce, + To: copyAddressPtr(tx.To), + Data: common.CopyBytes(tx.Data), + Gas: tx.Gas, + FeeCurrency: copyAddressPtr(tx.FeeCurrency), + // These are copied below. + AccessList: make(AccessList, len(tx.AccessList)), + Value: new(big.Int), + ChainID: new(big.Int), + GasTipCap: new(big.Int), + GasFeeCap: new(big.Int), + V: new(big.Int), + R: new(big.Int), + S: new(big.Int), + } + if tx.MaxFeeInFeeCurrency != nil { + cpy.MaxFeeInFeeCurrency = new(big.Int).Set(tx.MaxFeeInFeeCurrency) + } + copy(cpy.AccessList, tx.AccessList) + 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.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 *CeloDenominatedTx) txType() byte { return CeloDenominatedTxType } +func (tx *CeloDenominatedTx) chainID() *big.Int { return tx.ChainID } +func (tx *CeloDenominatedTx) accessList() AccessList { return tx.AccessList } +func (tx *CeloDenominatedTx) data() []byte { return tx.Data } +func (tx *CeloDenominatedTx) gas() uint64 { return tx.Gas } +func (tx *CeloDenominatedTx) gasFeeCap() *big.Int { return tx.GasFeeCap } +func (tx *CeloDenominatedTx) gasTipCap() *big.Int { return tx.GasTipCap } +func (tx *CeloDenominatedTx) gasPrice() *big.Int { return tx.GasFeeCap } +func (tx *CeloDenominatedTx) value() *big.Int { return tx.Value } +func (tx *CeloDenominatedTx) nonce() uint64 { return tx.Nonce } +func (tx *CeloDenominatedTx) to() *common.Address { return tx.To } +func (tx *CeloDenominatedTx) isSystemTx() bool { return false } + +func (tx *CeloDenominatedTx) effectiveGasPrice(dst *big.Int, baseFee *big.Int) *big.Int { + if baseFee == nil { + return dst.Set(tx.GasFeeCap) + } + tip := dst.Sub(tx.GasFeeCap, baseFee) + if tip.Cmp(tx.GasTipCap) > 0 { + tip.Set(tx.GasTipCap) + } + return tip.Add(tip, baseFee) +} + +func (tx *CeloDenominatedTx) rawSignatureValues() (v, r, s *big.Int) { + return tx.V, tx.R, tx.S +} + +func (tx *CeloDenominatedTx) setSignatureValues(chainID, v, r, s *big.Int) { + tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s +} + +func (tx *CeloDenominatedTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} + +func (tx *CeloDenominatedTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} + +func (tx *CeloDenominatedTx) feeCurrency() *common.Address { return tx.FeeCurrency } + +func (tx *CeloDenominatedTx) maxFeeInFeeCurrency() *big.Int { return tx.MaxFeeInFeeCurrency } diff --git a/core/types/celo_dynamic_fee_tx.go b/core/types/celo_dynamic_fee_tx.go index 0ff5186b31..800addd11e 100644 --- a/core/types/celo_dynamic_fee_tx.go +++ b/core/types/celo_dynamic_fee_tx.go @@ -114,4 +114,5 @@ func (tx *CeloDynamicFeeTx) decode(input []byte) error { return rlp.DecodeBytes(input, tx) } -func (tx *CeloDynamicFeeTx) feeCurrency() *common.Address { return tx.FeeCurrency } +func (tx *CeloDynamicFeeTx) feeCurrency() *common.Address { return tx.FeeCurrency } +func (tx *CeloDynamicFeeTx) maxFeeInFeeCurrency() *big.Int { return nil } diff --git a/core/types/celo_transaction_signing.go b/core/types/celo_transaction_signing.go index 1df278929f..1dffdbe408 100644 --- a/core/types/celo_transaction_signing.go +++ b/core/types/celo_transaction_signing.go @@ -36,7 +36,7 @@ func NewCel2Signer(chainId *big.Int) Signer { } func (s cel2Signer) Sender(tx *Transaction) (common.Address, error) { - if tx.Type() != CeloDynamicFeeTxType { + if tx.Type() != CeloDynamicFeeTxType && tx.Type() != CeloDenominatedTxType { return s.londonSigner.Sender(tx) } V, R, S := tx.RawSignatureValues() @@ -55,13 +55,14 @@ func (s cel2Signer) Equal(s2 Signer) bool { } func (s cel2Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { - txdata, ok := tx.inner.(*CeloDynamicFeeTx) - if !ok { + if tx.Type() != CeloDynamicFeeTxType && tx.Type() != CeloDenominatedTxType { 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.Cmp(s.chainId) != 0 { + chainID := tx.inner.chainID() + if chainID.Sign() != 0 && chainID.Cmp(s.chainId) != 0 { return nil, nil, nil, ErrInvalidChainId } R, S, _ = decodeSignature(sig) @@ -88,5 +89,22 @@ func (s cel2Signer) Hash(tx *Transaction) common.Hash { tx.FeeCurrency(), }) } + if tx.Type() == CeloDenominatedTxType { + return prefixedRlpHash( + tx.Type(), + []interface{}{ + s.chainId, + tx.Nonce(), + tx.GasTipCap(), + tx.GasFeeCap(), + tx.Gas(), + tx.To(), + tx.Value(), + tx.Data(), + tx.AccessList(), + tx.FeeCurrency(), + tx.MaxFeeInFeeCurrency(), + }) + } return s.londonSigner.Hash(tx) } diff --git a/core/types/deposit_tx.go b/core/types/deposit_tx.go index 4b6c673eb9..bbfec79323 100644 --- a/core/types/deposit_tx.go +++ b/core/types/deposit_tx.go @@ -102,4 +102,5 @@ func (tx *DepositTx) decode(input []byte) error { return rlp.DecodeBytes(input, tx) } -func (tx *DepositTx) feeCurrency() *common.Address { return nil } +func (tx *DepositTx) feeCurrency() *common.Address { return nil } +func (tx *DepositTx) maxFeeInFeeCurrency() *big.Int { return nil } diff --git a/core/types/receipt.go b/core/types/receipt.go index f63ed4a5d6..a6dc1b8388 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -94,6 +94,9 @@ type Receipt struct { // The BaseFee is stored in fee currency for fee currency txs. We need // this field to calculate the EffectiveGasPrice for fee currency txs. BaseFee *big.Int `json:"baseFee,omitempty"` + // The FeeInFeeCurrency is stored in CELO denominated txs. Its value is + // exactly what the tx fee was in the FeeCurrency of the tx. + FeeInFeeCurrency *big.Int `json:"feeInFeeCurrency,omitempty"` } type receiptMarshaling struct { @@ -148,6 +151,15 @@ type celoDynamicReceiptRLP struct { BaseFee *big.Int } +type celoDenominatedReceiptRLP struct { + PostStateOrStatus []byte + CumulativeGasUsed uint64 + Bloom Bloom + Logs []*Log + // FeeInFeeCurrency was introduced as mandatory in Cel2 ONLY for the CeloDenominatedFeeTxs + FeeInFeeCurrency *big.Int +} + // storedReceiptRLP is the storage encoding of a receipt. type storedReceiptRLP struct { PostStateOrStatus []byte @@ -170,6 +182,14 @@ type celoDynamicFeeStoredReceiptRLP struct { BaseFee *big.Int } +type celoDenominatedStoredReceiptRLP struct { + CeloDenominatedReceiptMarker string // Marker to distinguish this from storedReceiptRLP + PostStateOrStatus []byte + CumulativeGasUsed uint64 + Logs []*Log + FeeInFeeCurrency *big.Int +} + // LegacyOptimismStoredReceiptRLP is the pre bedrock storage encoding of a // receipt. It will only exist in the database if it was migrated using the // migration tool. Nodes that sync using snap-sync will not have any of these @@ -275,6 +295,9 @@ func (r *Receipt) encodeTyped(data *receiptRLP, w *bytes.Buffer) error { case CeloDynamicFeeTxType: withBaseFee := &celoDynamicReceiptRLP{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs, r.BaseFee} return rlp.Encode(w, withBaseFee) + case CeloDenominatedTxType: + withFeeInFeeCurrency := &celoDenominatedReceiptRLP{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs, r.FeeInFeeCurrency} + return rlp.Encode(w, withFeeInFeeCurrency) case DepositTxType: withNonce := &depositReceiptRLP{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs, r.DepositNonce, r.DepositReceiptVersion} return rlp.Encode(w, withNonce) @@ -365,6 +388,15 @@ func (r *Receipt) decodeTyped(b []byte) error { r.Type = b[0] r.BaseFee = data.BaseFee return r.setFromRLP(receiptRLP{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs}) + case CeloDenominatedTxType: + var data celoDenominatedReceiptRLP + err := rlp.DecodeBytes(b[1:], &data) + if err != nil { + return err + } + r.Type = b[0] + r.FeeInFeeCurrency = data.FeeInFeeCurrency + return r.setFromRLP(receiptRLP{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs}) case DepositTxType: var data depositReceiptRLP err := rlp.DecodeBytes(b[1:], &data) @@ -434,6 +466,10 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error { listIndex := w.List() w.ListEnd(listIndex) } + if r.Type == CeloDenominatedTxType { + // Mark receipt as CeloDenominated receipt by starting with an empty string + w.WriteString("") + } w.WriteBytes((*Receipt)(r).statusEncoding()) w.WriteUint64(r.CumulativeGasUsed) logList := w.List() @@ -452,6 +488,9 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error { if r.Type == CeloDynamicFeeTxType { w.WriteBigInt(r.BaseFee) } + if r.Type == CeloDenominatedTxType { + w.WriteBigInt(r.FeeInFeeCurrency) + } w.ListEnd(outerList) return w.Flush() } @@ -460,12 +499,20 @@ func (r *ReceiptForStorage) EncodeRLP(_w io.Writer) error { // To distinguish these receipts from the very similar normal receipts, an // empty list is added as the first element of the RLP-serialized struct. func isCeloDynamicFeeReceipt(blob []byte) bool { + return isFirstElementA(blob, 0xc0) // an empty short list +} + +func isCeloDenominatedFeeReceipt(blob []byte) bool { + return isFirstElementA(blob, 0x80) // an empty short string +} + +func isFirstElementA(blob []byte, value byte) bool { listHeaderSize := 1 // Length of the list header representing the struct in bytes if blob[0] > 0xf7 { listHeaderSize += int(blob[0]) - 0xf7 } firstListElement := blob[listHeaderSize] // First byte of first list element - return firstListElement == 0xc0 + return firstListElement == value } // DecodeRLP implements rlp.Decoder, and loads both consensus and implementation @@ -480,6 +527,9 @@ func (r *ReceiptForStorage) DecodeRLP(s *rlp.Stream) error { if isCeloDynamicFeeReceipt(blob) { return decodeStoredCeloDynamicFeeReceiptRLP(r, blob) } + if isCeloDenominatedFeeReceipt(blob) { + return decodeStoredCeloDenominatedReceiptRLP(r, blob) + } if err := decodeStoredReceiptRLP(r, blob); err == nil { return nil } @@ -531,6 +581,21 @@ func decodeStoredCeloDynamicFeeReceiptRLP(r *ReceiptForStorage, blob []byte) err return nil } +func decodeStoredCeloDenominatedReceiptRLP(r *ReceiptForStorage, blob []byte) error { + var stored celoDenominatedStoredReceiptRLP + if err := rlp.DecodeBytes(blob, &stored); err != nil { + return err + } + if err := (*Receipt)(r).setStatus(stored.PostStateOrStatus); err != nil { + return err + } + r.CumulativeGasUsed = stored.CumulativeGasUsed + r.Logs = stored.Logs + r.Bloom = CreateBloom(Receipts{(*Receipt)(r)}) + r.FeeInFeeCurrency = stored.FeeInFeeCurrency + return nil +} + func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error { var stored storedReceiptRLP if err := rlp.DecodeBytes(blob, &stored); err != nil { @@ -573,6 +638,9 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { case CeloDynamicFeeTxType: celoDynamicData := &celoDynamicReceiptRLP{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs, r.BaseFee} rlp.Encode(w, celoDynamicData) + case CeloDenominatedTxType: + celoDenominatedData := &celoDenominatedReceiptRLP{data.PostStateOrStatus, data.CumulativeGasUsed, data.Bloom, data.Logs, r.FeeInFeeCurrency} + rlp.Encode(w, celoDenominatedData) case DepositTxType: if r.DepositReceiptVersion != nil { // post-canyon receipt hash computation update diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 599d8fe8a6..5b704e569c 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -117,6 +117,24 @@ var ( BaseFee: new(big.Int).SetUint64(1), Type: CeloDynamicFeeTxType, } + celoDenominatedReceipt = &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + Logs: []*Log{ + { + Address: common.BytesToAddress([]byte{0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + { + Address: common.BytesToAddress([]byte{0x01, 0x11}), + Topics: []common.Hash{common.HexToHash("dead"), common.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + }, + FeeInFeeCurrency: new(big.Int).SetUint64(1), + Type: CeloDenominatedTxType, + } depositReceiptNoNonce = &Receipt{ Status: ReceiptStatusFailed, CumulativeGasUsed: 1, @@ -660,6 +678,24 @@ func TestReceiptMarshalBinary(t *testing.T) { if !bytes.Equal(have, celoDynamicFeeWant) { t.Errorf("encoded RLP mismatch, got %x want %x", have, celoDynamicFeeWant) } + + // Celo Denominated Receipt + buf.Reset() + celoDenominatedReceipt.Bloom = CreateBloom(Receipts{celoDenominatedReceipt}) + have, err = celoDenominatedReceipt.MarshalBinary() + if err != nil { + t.Fatalf("marshal binary error: %v", err) + } + celoDenominatedReceipts := Receipts{celoDenominatedReceipt} + celoDenominatedReceipts.EncodeIndex(0, buf) + haveEncodeIndex = buf.Bytes() + if !bytes.Equal(have, haveEncodeIndex) { + t.Errorf("BinaryMarshal and EncodeIndex mismatch, got %x want %x", have, haveEncodeIndex) + } + celoDenominatedWant := common.FromHex("7af901c68001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff01") + if !bytes.Equal(have, celoDenominatedWant) { + t.Errorf("encoded RLP mismatch, got %x want %x", have, celoDenominatedWant) + } } func TestReceiptUnmarshalBinary(t *testing.T) { @@ -695,6 +731,17 @@ func TestReceiptUnmarshalBinary(t *testing.T) { if !reflect.DeepEqual(got1559Receipt, eip1559Receipt) { t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", got1559Receipt, eip1559Receipt) } + + // Celo Denominated Receipt + celoDenominatedRctBinary := common.FromHex("7af901c68001b9010000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000000000000000010000080000000000000000000004000000000000000000000000000040000000000000000000000000000800000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000f8bef85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100fff85d940000000000000000000000000000000000000111f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff01") + gotCeloDenominatedReceipt := new(Receipt) + if err := gotCeloDenominatedReceipt.UnmarshalBinary(celoDenominatedRctBinary); err != nil { + t.Fatalf("unmarshal binary error: %v", err) + } + celoDenominatedReceipt.Bloom = CreateBloom(Receipts{celoDenominatedReceipt}) + if !reflect.DeepEqual(gotCeloDenominatedReceipt, celoDenominatedReceipt) { + t.Errorf("receipt unmarshalled from binary mismatch, got %v want %v", gotCeloDenominatedReceipt, celoDenominatedReceipt) + } } func clearComputedFieldsOnReceipts(receipts []*Receipt) []*Receipt { @@ -1010,6 +1057,7 @@ func TestRoundTripReceiptForStorage(t *testing.T) { {name: "AccessList", rcpt: accessListReceipt}, {name: "EIP1559", rcpt: eip1559Receipt}, {name: "CeloDynamicFee", rcpt: celoDynamicFeeReceipt}, + {name: "CeloDenominated", rcpt: celoDenominatedReceipt}, {name: "DepositNoNonce", rcpt: depositReceiptNoNonce}, {name: "DepositWithNonce", rcpt: depositReceiptWithNonce}, {name: "DepositWithNonceAndVersion", rcpt: depositReceiptWithNonceAndVersion}, @@ -1031,6 +1079,9 @@ func TestRoundTripReceiptForStorage(t *testing.T) { if test.rcpt.Type == CeloDynamicFeeTxType { require.Equal(t, test.rcpt.EffectiveGasPrice, d.EffectiveGasPrice) } + if test.rcpt.Type == CeloDenominatedTxType { + require.Equal(t, test.rcpt.FeeInFeeCurrency, d.FeeInFeeCurrency) + } }) } } diff --git a/core/types/transaction.go b/core/types/transaction.go index 17622bc7ef..d53023df16 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -48,7 +48,8 @@ const ( DynamicFeeTxType = 0x02 BlobTxType = 0x03 // CeloDynamicFeeTxType = 0x7c old Celo tx type with gateway fee - CeloDynamicFeeTxType = 0x7b + CeloDynamicFeeTxType = 0x7b + CeloDenominatedTxType = 0x7a ) // Transaction is an Ethereum transaction. @@ -107,6 +108,7 @@ type TxData interface { // Celo specific fields feeCurrency() *common.Address + maxFeeInFeeCurrency() *big.Int } // EncodeRLP implements rlp.Encoder @@ -213,6 +215,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { inner = new(DynamicFeeTx) case CeloDynamicFeeTxType: inner = new(CeloDynamicFeeTx) + case CeloDenominatedTxType: + inner = new(CeloDenominatedTx) case BlobTxType: inner = new(BlobTx) case DepositTxType: @@ -606,6 +610,11 @@ func (tx *Transaction) FeeCurrency() *common.Address { return copyAddressPtr(tx.inner.feeCurrency()) } +// MaxFeeInFeeCurrency returns the max fee in the fee_currency for celo denominated txs. +func (tx *Transaction) MaxFeeInFeeCurrency() *big.Int { + return new(big.Int).Set(tx.inner.maxFeeInFeeCurrency()) +} + // Transactions implements DerivableList for transactions. type Transactions []*Transaction diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index ecb8903826..f662802a7c 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -59,7 +59,8 @@ type txJSON struct { Hash common.Hash `json:"hash"` // Celo specific fields - FeeCurrency *common.Address `json:"feeCurrency"` // nil means native currency + FeeCurrency *common.Address `json:"feeCurrency"` // nil means native currency + MaxFeeInFeeCurrency *hexutil.Big `json:"maxFeeInFeeCurrency"` // max fee for CELO denominated txs } // yParityValue returns the YParity value from JSON. For backwards-compatibility reasons, @@ -417,6 +418,67 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { } } + case CeloDenominatedTxType: + var itx CeloDenominatedTx + inner = &itx + if dec.ChainID == nil { + return errors.New("missing required field 'chainId' in transaction") + } + itx.ChainID = (*big.Int)(dec.ChainID) + if dec.Nonce == nil { + return errors.New("missing required field 'nonce' in transaction") + } + itx.Nonce = uint64(*dec.Nonce) + if dec.To != nil { + itx.To = dec.To + } + if dec.Gas == nil { + return errors.New("missing required field 'gas' for txdata") + } + itx.Gas = uint64(*dec.Gas) + if dec.MaxPriorityFeePerGas == nil { + return errors.New("missing required field 'maxPriorityFeePerGas' for txdata") + } + itx.GasTipCap = (*big.Int)(dec.MaxPriorityFeePerGas) + if dec.MaxFeePerGas == nil { + return errors.New("missing required field 'maxFeePerGas' for txdata") + } + itx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas) + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = (*big.Int)(dec.Value) + if dec.Input == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.FeeCurrency = dec.FeeCurrency + if dec.MaxFeeInFeeCurrency == nil { + return errors.New("missing required field 'maxFeeInFeeCurrency' in transaction") + } + itx.MaxFeeInFeeCurrency = (*big.Int)(dec.MaxFeeInFeeCurrency) + itx.Data = *dec.Input + if dec.V == nil { + return errors.New("missing required field 'v' in transaction") + } + if dec.AccessList != nil { + itx.AccessList = *dec.AccessList + } + itx.V = (*big.Int)(dec.V) + if dec.R == nil { + return errors.New("missing required field 'r' in transaction") + } + itx.R = (*big.Int)(dec.R) + if dec.S == nil { + return errors.New("missing required field 's' in transaction") + } + itx.S = (*big.Int)(dec.S) + withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0 + if withSignature { + if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil { + return err + } + } + case BlobTxType: var itx BlobTx inner = &itx diff --git a/core/types/tx_access_list.go b/core/types/tx_access_list.go index 6c06b86181..618b3de863 100644 --- a/core/types/tx_access_list.go +++ b/core/types/tx_access_list.go @@ -129,4 +129,5 @@ func (tx *AccessListTx) decode(input []byte) error { return rlp.DecodeBytes(input, tx) } -func (tx *AccessListTx) feeCurrency() *common.Address { return nil } +func (tx *AccessListTx) feeCurrency() *common.Address { return nil } +func (tx *AccessListTx) maxFeeInFeeCurrency() *big.Int { return nil } diff --git a/core/types/tx_blob.go b/core/types/tx_blob.go index 33edc22414..640ffd050d 100644 --- a/core/types/tx_blob.go +++ b/core/types/tx_blob.go @@ -246,4 +246,5 @@ func blobHash(commit *kzg4844.Commitment) common.Hash { return vhash } -func (tx *BlobTx) feeCurrency() *common.Address { return nil } +func (tx *BlobTx) feeCurrency() *common.Address { return nil } +func (tx *BlobTx) maxFeeInFeeCurrency() *big.Int { return nil } diff --git a/core/types/tx_dynamic_fee.go b/core/types/tx_dynamic_fee.go index 1b559b08ac..e23accb299 100644 --- a/core/types/tx_dynamic_fee.go +++ b/core/types/tx_dynamic_fee.go @@ -125,4 +125,5 @@ func (tx *DynamicFeeTx) decode(input []byte) error { return rlp.DecodeBytes(input, tx) } -func (tx *DynamicFeeTx) feeCurrency() *common.Address { return nil } +func (tx *DynamicFeeTx) feeCurrency() *common.Address { return nil } +func (tx *DynamicFeeTx) maxFeeInFeeCurrency() *big.Int { return nil } diff --git a/core/types/tx_legacy.go b/core/types/tx_legacy.go index a66dff8397..d1489d5292 100644 --- a/core/types/tx_legacy.go +++ b/core/types/tx_legacy.go @@ -125,4 +125,5 @@ func (tx *LegacyTx) decode([]byte) error { panic("decode called on LegacyTx)") } -func (tx *LegacyTx) feeCurrency() *common.Address { return nil } +func (tx *LegacyTx) feeCurrency() *common.Address { return nil } +func (tx *LegacyTx) maxFeeInFeeCurrency() *big.Int { return nil } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 491d145e49..87e916c26f 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1574,7 +1574,8 @@ type RPCTransaction struct { DepositReceiptVersion *hexutil.Uint64 `json:"depositReceiptVersion,omitempty"` // Celo - FeeCurrency *common.Address `json:"feeCurrency,omitempty"` + FeeCurrency *common.Address `json:"feeCurrency,omitempty"` + MaxFeeInFeeCurrency *hexutil.Big `json:"maxFeeInFeeCurrency,omitempty"` } // newRPCTransaction returns a transaction that will serialize to the RPC @@ -1597,7 +1598,8 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber R: (*hexutil.Big)(r), S: (*hexutil.Big)(s), // Celo - FeeCurrency: tx.FeeCurrency(), + FeeCurrency: tx.FeeCurrency(), + MaxFeeInFeeCurrency: (*hexutil.Big)(tx.MaxFeeInFeeCurrency()), } if blockHash != (common.Hash{}) { result.BlockHash = &blockHash @@ -1639,7 +1641,7 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber result.ChainID = (*hexutil.Big)(tx.ChainId()) result.YParity = &yparity - case types.DynamicFeeTxType, types.CeloDynamicFeeTxType: + case types.DynamicFeeTxType, types.CeloDynamicFeeTxType, types.CeloDenominatedTxType: al := tx.AccessList() yparity := hexutil.Uint64(v.Sign()) result.Accesses = &al