Skip to content

Commit 7ed4e1d

Browse files
Celo Typed Transactions (#1736)
* Implement CeloDynamicFee Transaction Type This transaction type (0x7c) is like the Dynamic Fee transaction type (0x02) except that it also has 3 Celo specific fields: FeeCurrency, GatewayFeeRecipient, and GatewayFee. * bindv2: Use the latest signer in auth This now uses the latest signer function which will always get the correct signer rather than constantly updating the signer for each hardfork. * Update signer
1 parent 975420d commit 7ed4e1d

8 files changed

+260
-30
lines changed

accounts/abi/bind_v2/auth.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func NewTransactor(keyin io.Reader, passphrase string) (*TransactOpts, error) {
6262
// Deprecated: Use NewKeyStoreTransactorWithChainID instead.
6363
func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account) (*TransactOpts, error) {
6464
log.Warn("WARNING: NewKeyStoreTransactor has been deprecated in favour of NewTransactorWithChainID")
65-
signer := types.HomesteadSigner{}
65+
signer := types.LatestSignerForChainID(nil)
6666
return &TransactOpts{
6767
From: account.Address,
6868
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {
@@ -86,7 +86,7 @@ func NewKeyStoreTransactor(keystore *keystore.KeyStore, account accounts.Account
8686
func NewKeyedTransactor(key *ecdsa.PrivateKey) *TransactOpts {
8787
log.Warn("WARNING: NewKeyedTransactor has been deprecated in favour of NewKeyedTransactorWithChainID")
8888
keyAddr := crypto.PubkeyToAddress(key.PublicKey)
89-
signer := types.HomesteadSigner{}
89+
signer := types.LatestSignerForChainID(nil)
9090
return &TransactOpts{
9191
From: keyAddr,
9292
Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) {

accounts/external/backend.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
219219
switch tx.Type() {
220220
case types.LegacyTxType, types.AccessListTxType:
221221
args.GasPrice = (*hexutil.Big)(tx.GasPrice())
222-
case types.DynamicFeeTxType:
222+
case types.DynamicFeeTxType, types.CeloDynamicFeeTxType:
223223
args.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap())
224224
args.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap())
225225
default:

core/tx_pool.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,7 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
666666
return ErrTxTypeNotSupported
667667
}
668668
// Reject dynamic fee transactions until EIP-1559 activates.
669-
if !pool.espresso && tx.Type() == types.DynamicFeeTxType {
669+
if !pool.espresso && (tx.Type() == types.DynamicFeeTxType || tx.Type() == types.CeloDynamicFeeTxType) {
670670
return ErrTxTypeNotSupported
671671
}
672672
// Reject transactions over defined size to prevent DOS attacks

core/types/celo_dynamic_fee_tx.go

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright 2021 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package types
18+
19+
import (
20+
"math/big"
21+
22+
"github.com/celo-org/celo-blockchain/common"
23+
)
24+
25+
type CeloDynamicFeeTx struct {
26+
ChainID *big.Int
27+
Nonce uint64
28+
GasTipCap *big.Int
29+
GasFeeCap *big.Int
30+
Gas uint64
31+
FeeCurrency *common.Address `rlp:"nil"` // nil means native currency
32+
GatewayFeeRecipient *common.Address `rlp:"nil"` // nil means no gateway fee is paid
33+
GatewayFee *big.Int `rlp:"nil"`
34+
To *common.Address `rlp:"nil"` // nil means contract creation
35+
Value *big.Int
36+
Data []byte
37+
AccessList AccessList
38+
39+
// Signature values
40+
V *big.Int `json:"v" gencodec:"required"`
41+
R *big.Int `json:"r" gencodec:"required"`
42+
S *big.Int `json:"s" gencodec:"required"`
43+
}
44+
45+
// copy creates a deep copy of the transaction data and initializes all fields.
46+
func (tx *CeloDynamicFeeTx) copy() TxData {
47+
cpy := &CeloDynamicFeeTx{
48+
Nonce: tx.Nonce,
49+
To: tx.To, // TODO: copy pointed-to address
50+
Data: common.CopyBytes(tx.Data),
51+
Gas: tx.Gas,
52+
FeeCurrency: tx.FeeCurrency,
53+
GatewayFeeRecipient: tx.GatewayFeeRecipient,
54+
// These are copied below.
55+
AccessList: make(AccessList, len(tx.AccessList)),
56+
GatewayFee: new(big.Int),
57+
Value: new(big.Int),
58+
ChainID: new(big.Int),
59+
GasTipCap: new(big.Int),
60+
GasFeeCap: new(big.Int),
61+
V: new(big.Int),
62+
R: new(big.Int),
63+
S: new(big.Int),
64+
}
65+
copy(cpy.AccessList, tx.AccessList)
66+
if tx.Value != nil {
67+
cpy.Value.Set(tx.Value)
68+
}
69+
if tx.ChainID != nil {
70+
cpy.ChainID.Set(tx.ChainID)
71+
}
72+
if tx.GasTipCap != nil {
73+
cpy.GasTipCap.Set(tx.GasTipCap)
74+
}
75+
if tx.GasFeeCap != nil {
76+
cpy.GasFeeCap.Set(tx.GasFeeCap)
77+
}
78+
if tx.GatewayFee != nil {
79+
cpy.GatewayFee.Set(tx.GatewayFee)
80+
}
81+
if tx.V != nil {
82+
cpy.V.Set(tx.V)
83+
}
84+
if tx.R != nil {
85+
cpy.R.Set(tx.R)
86+
}
87+
if tx.S != nil {
88+
cpy.S.Set(tx.S)
89+
}
90+
return cpy
91+
}
92+
93+
// accessors for innerTx.
94+
func (tx *CeloDynamicFeeTx) txType() byte { return CeloDynamicFeeTxType }
95+
func (tx *CeloDynamicFeeTx) chainID() *big.Int { return tx.ChainID }
96+
func (tx *CeloDynamicFeeTx) protected() bool { return true }
97+
func (tx *CeloDynamicFeeTx) accessList() AccessList { return tx.AccessList }
98+
func (tx *CeloDynamicFeeTx) data() []byte { return tx.Data }
99+
func (tx *CeloDynamicFeeTx) gas() uint64 { return tx.Gas }
100+
func (tx *CeloDynamicFeeTx) gasFeeCap() *big.Int { return tx.GasFeeCap }
101+
func (tx *CeloDynamicFeeTx) gasTipCap() *big.Int { return tx.GasTipCap }
102+
func (tx *CeloDynamicFeeTx) gasPrice() *big.Int { return tx.GasFeeCap }
103+
func (tx *CeloDynamicFeeTx) value() *big.Int { return tx.Value }
104+
func (tx *CeloDynamicFeeTx) nonce() uint64 { return tx.Nonce }
105+
func (tx *CeloDynamicFeeTx) to() *common.Address { return tx.To }
106+
func (tx *CeloDynamicFeeTx) feeCurrency() *common.Address { return tx.FeeCurrency }
107+
func (tx *CeloDynamicFeeTx) gatewayFeeRecipient() *common.Address { return tx.GatewayFeeRecipient }
108+
func (tx *CeloDynamicFeeTx) gatewayFee() *big.Int { return tx.GatewayFee }
109+
func (tx *CeloDynamicFeeTx) ethCompatible() bool { return false }
110+
111+
func (tx *CeloDynamicFeeTx) rawSignatureValues() (v, r, s *big.Int) {
112+
return tx.V, tx.R, tx.S
113+
}
114+
115+
func (tx *CeloDynamicFeeTx) setSignatureValues(chainID, v, r, s *big.Int) {
116+
tx.ChainID, tx.V, tx.R, tx.S = chainID, v, r, s
117+
}

core/types/receipt.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ func (r *Receipt) DecodeRLP(s *rlp.Stream) error {
176176
return errEmptyTypedReceipt
177177
}
178178
r.Type = b[0]
179-
if r.Type == AccessListTxType || r.Type == DynamicFeeTxType {
179+
if r.Type == AccessListTxType || r.Type == DynamicFeeTxType || r.Type == CeloDynamicFeeTxType {
180180
var dec receiptRLP
181181
if err := rlp.DecodeBytes(b[1:], &dec); err != nil {
182182
return err
@@ -346,6 +346,9 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) {
346346
case DynamicFeeTxType:
347347
w.WriteByte(DynamicFeeTxType)
348348
rlp.Encode(w, data)
349+
case CeloDynamicFeeTxType:
350+
w.WriteByte(CeloDynamicFeeTxType)
351+
rlp.Encode(w, data)
349352
default:
350353
// For unsupported types, write nothing. Since this is for
351354
// DeriveSha, the error will be caught matching the derived hash

core/types/transaction.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ var (
5050

5151
// Transaction types.
5252
const (
53-
LegacyTxType = iota
54-
AccessListTxType = 0x01
55-
DynamicFeeTxType = 0x02
53+
LegacyTxType = iota
54+
AccessListTxType = 0x01
55+
DynamicFeeTxType = 0x02
56+
CeloDynamicFeeTxType = 0x7c // Counting down
5657
)
5758

5859
// Transaction is an Ethereum transaction.
@@ -204,6 +205,10 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
204205
var inner DynamicFeeTx
205206
err := rlp.DecodeBytes(b[1:], &inner)
206207
return &inner, err
208+
case CeloDynamicFeeTxType:
209+
var inner CeloDynamicFeeTx
210+
err := rlp.DecodeBytes(b[1:], &inner)
211+
return &inner, err
207212
default:
208213
return nil, ErrTxTypeNotSupported
209214
}

core/types/transaction_marshalling.go

+79
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
106106
enc.V = (*hexutil.Big)(tx.V)
107107
enc.R = (*hexutil.Big)(tx.R)
108108
enc.S = (*hexutil.Big)(tx.S)
109+
case *CeloDynamicFeeTx:
110+
enc.ChainID = (*hexutil.Big)(tx.ChainID)
111+
enc.AccessList = &tx.AccessList
112+
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
113+
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
114+
enc.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap)
115+
enc.MaxPriorityFeePerGas = (*hexutil.Big)(tx.GasTipCap)
116+
enc.FeeCurrency = tx.FeeCurrency // todo: check if needs deep copy
117+
enc.GatewayFeeRecipient = tx.GatewayFeeRecipient
118+
enc.GatewayFee = (*hexutil.Big)(tx.GatewayFee)
119+
enc.Value = (*hexutil.Big)(tx.Value)
120+
enc.Data = (*hexutil.Bytes)(&tx.Data)
121+
enc.To = t.To()
122+
enc.V = (*hexutil.Big)(tx.V)
123+
enc.R = (*hexutil.Big)(tx.R)
124+
enc.S = (*hexutil.Big)(tx.S)
109125
}
110126
return json.Marshal(&enc)
111127
}
@@ -282,6 +298,69 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
282298
}
283299
}
284300

301+
case CeloDynamicFeeTxType:
302+
var itx CeloDynamicFeeTx
303+
inner = &itx
304+
// Access list is optional for now.
305+
if dec.AccessList != nil {
306+
itx.AccessList = *dec.AccessList
307+
}
308+
if dec.ChainID == nil {
309+
return errors.New("missing required field 'chainId' in transaction")
310+
}
311+
itx.ChainID = (*big.Int)(dec.ChainID)
312+
if dec.To != nil {
313+
itx.To = dec.To
314+
}
315+
if dec.Nonce == nil {
316+
return errors.New("missing required field 'nonce' in transaction")
317+
}
318+
itx.Nonce = uint64(*dec.Nonce)
319+
if dec.MaxPriorityFeePerGas == nil {
320+
return errors.New("missing required field 'maxPriorityFeePerGas' for txdata")
321+
}
322+
itx.GasTipCap = (*big.Int)(dec.MaxPriorityFeePerGas)
323+
if dec.MaxFeePerGas == nil {
324+
return errors.New("missing required field 'maxFeePerGas' for txdata")
325+
}
326+
itx.GasFeeCap = (*big.Int)(dec.MaxFeePerGas)
327+
if dec.Gas == nil {
328+
return errors.New("missing required field 'gas' for txdata")
329+
}
330+
itx.Gas = uint64(*dec.Gas)
331+
itx.FeeCurrency = dec.FeeCurrency
332+
itx.GatewayFeeRecipient = dec.GatewayFeeRecipient
333+
itx.GatewayFee = new(big.Int)
334+
if dec.GatewayFee != nil {
335+
itx.GatewayFee.Set((*big.Int)(dec.GatewayFee))
336+
}
337+
if dec.Value == nil {
338+
return errors.New("missing required field 'value' in transaction")
339+
}
340+
itx.Value = (*big.Int)(dec.Value)
341+
if dec.Data == nil {
342+
return errors.New("missing required field 'input' in transaction")
343+
}
344+
itx.Data = *dec.Data
345+
if dec.V == nil {
346+
return errors.New("missing required field 'v' in transaction")
347+
}
348+
itx.V = (*big.Int)(dec.V)
349+
if dec.R == nil {
350+
return errors.New("missing required field 'r' in transaction")
351+
}
352+
itx.R = (*big.Int)(dec.R)
353+
if dec.S == nil {
354+
return errors.New("missing required field 's' in transaction")
355+
}
356+
itx.S = (*big.Int)(dec.S)
357+
withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
358+
if withSignature {
359+
if err := sanityCheckSignature(itx.V, itx.R, itx.S, false); err != nil {
360+
return err
361+
}
362+
}
363+
285364
default:
286365
return ErrTxTypeNotSupported
287366
}

core/types/transaction_signing.go

+48-22
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ func NewLondonSigner(chainId *big.Int) Signer {
177177
}
178178

179179
func (s londonSigner) Sender(tx *Transaction) (common.Address, error) {
180-
if tx.Type() != DynamicFeeTxType {
180+
if !(tx.Type() == DynamicFeeTxType || tx.Type() == CeloDynamicFeeTxType) {
181181
return s.eip2930Signer.Sender(tx)
182182
}
183183
V, R, S := tx.RawSignatureValues()
@@ -196,15 +196,21 @@ func (s londonSigner) Equal(s2 Signer) bool {
196196
}
197197

198198
func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
199-
txdata, ok := tx.inner.(*DynamicFeeTx)
200-
if !ok {
199+
switch txdata := tx.inner.(type) {
200+
case *DynamicFeeTx:
201+
// Check that chain ID of tx matches the signer. We also accept ID zero here,
202+
// because it indicates that the chain ID was not specified in the tx.
203+
if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
204+
return nil, nil, nil, ErrInvalidChainId
205+
}
206+
case *CeloDynamicFeeTx:
207+
if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
208+
return nil, nil, nil, ErrInvalidChainId
209+
}
210+
default:
201211
return s.eip2930Signer.SignatureValues(tx, sig)
202212
}
203-
// Check that chain ID of tx matches the signer. We also accept ID zero here,
204-
// because it indicates that the chain ID was not specified in the tx.
205-
if txdata.ChainID.Sign() != 0 && txdata.ChainID.Cmp(s.chainId) != 0 {
206-
return nil, nil, nil, ErrInvalidChainId
207-
}
213+
208214
R, S, _ = decodeSignature(sig)
209215
V = big.NewInt(int64(sig[64]))
210216
return R, S, V, nil
@@ -213,22 +219,42 @@ func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big
213219
// Hash returns the hash to be signed by the sender.
214220
// It does not uniquely identify the transaction.
215221
func (s londonSigner) Hash(tx *Transaction) common.Hash {
216-
if tx.Type() != DynamicFeeTxType {
222+
switch tx.Type() {
223+
case DynamicFeeTxType:
224+
return prefixedRlpHash(
225+
tx.Type(),
226+
[]interface{}{
227+
s.chainId,
228+
tx.Nonce(),
229+
tx.GasTipCap(),
230+
tx.GasFeeCap(),
231+
tx.Gas(),
232+
tx.To(),
233+
tx.Value(),
234+
tx.Data(),
235+
tx.AccessList(),
236+
})
237+
case CeloDynamicFeeTxType:
238+
return prefixedRlpHash(
239+
tx.Type(),
240+
[]interface{}{
241+
s.chainId,
242+
tx.Nonce(),
243+
tx.GasTipCap(),
244+
tx.GasFeeCap(),
245+
tx.Gas(),
246+
tx.FeeCurrency(),
247+
tx.GatewayFeeRecipient(),
248+
tx.GatewayFee(),
249+
tx.To(),
250+
tx.Value(),
251+
tx.Data(),
252+
tx.AccessList(),
253+
})
254+
default:
217255
return s.eip2930Signer.Hash(tx)
218256
}
219-
return prefixedRlpHash(
220-
tx.Type(),
221-
[]interface{}{
222-
s.chainId,
223-
tx.Nonce(),
224-
tx.GasTipCap(),
225-
tx.GasFeeCap(),
226-
tx.Gas(),
227-
tx.To(),
228-
tx.Value(),
229-
tx.Data(),
230-
tx.AccessList(),
231-
})
257+
232258
}
233259

234260
type eip2930Signer struct{ EIP155Signer }

0 commit comments

Comments
 (0)