Skip to content

Commit 021d106

Browse files
author
Or Neeman
authored
Implement CIP35 for Donut hard fork (#1431)
Starting at the Donut hard-fork activation: (a) adds support for ethereum-compatible transactions as specified in CIP35, (b) make replay protection (using EIP-155) mandatory. * Remove rlp:"nil" tag from txdata.GatewayFee. Big ints are encoded and decoded as RLP scalars, which are not nullable. The 'nil' tag had no effect, so this commit removes it. * rlp: handle case of normal EOF in Stream.readFull() * Add support for eth-compatible transactions * Make replay protection mandatory once Donut is activated
1 parent 60c3de2 commit 021d106

File tree

25 files changed

+434
-54
lines changed

25 files changed

+434
-54
lines changed

accounts/abi/bind/backends/simulated.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,7 @@ func (m callmsg) GatewayFee() *big.Int { return m.CallMsg.Gatewa
685685
func (m callmsg) Gas() uint64 { return m.CallMsg.Gas }
686686
func (m callmsg) Value() *big.Int { return m.CallMsg.Value }
687687
func (m callmsg) Data() []byte { return m.CallMsg.Data }
688+
func (m callmsg) EthCompatible() bool { return m.CallMsg.EthCompatible }
688689

689690
// filterBackend implements filters.Backend to support filtering for logs without
690691
// taking bloom-bits acceleration structures into account.

cmd/geth/retesteth_copypaste.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type RPCTransaction struct {
4040
V *hexutil.Big `json:"v"`
4141
R *hexutil.Big `json:"r"`
4242
S *hexutil.Big `json:"s"`
43+
EthCompatible bool `json:"ethCompatible"`
4344
}
4445

4546
// newRPCTransaction returns a transaction that will serialize to the RPC
@@ -53,17 +54,18 @@ func newRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber
5354
v, r, s := tx.RawSignatureValues()
5455

5556
result := &RPCTransaction{
56-
From: from,
57-
Gas: hexutil.Uint64(tx.Gas()),
58-
GasPrice: (*hexutil.Big)(tx.GasPrice()),
59-
Hash: tx.Hash(),
60-
Input: hexutil.Bytes(tx.Data()),
61-
Nonce: hexutil.Uint64(tx.Nonce()),
62-
To: tx.To(),
63-
Value: (*hexutil.Big)(tx.Value()),
64-
V: (*hexutil.Big)(v),
65-
R: (*hexutil.Big)(r),
66-
S: (*hexutil.Big)(s),
57+
From: from,
58+
Gas: hexutil.Uint64(tx.Gas()),
59+
GasPrice: (*hexutil.Big)(tx.GasPrice()),
60+
Hash: tx.Hash(),
61+
Input: hexutil.Bytes(tx.Data()),
62+
Nonce: hexutil.Uint64(tx.Nonce()),
63+
To: tx.To(),
64+
Value: (*hexutil.Big)(tx.Value()),
65+
V: (*hexutil.Big)(v),
66+
R: (*hexutil.Big)(r),
67+
S: (*hexutil.Big)(s),
68+
EthCompatible: tx.EthCompatible(),
6769
}
6870
if blockHash != (common.Hash{}) {
6971
result.BlockHash = blockHash

contract_comm/evm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import (
3232
)
3333

3434
var (
35-
emptyMessage = types.NewMessage(common.HexToAddress("0x0"), nil, 0, common.Big0, 0, common.Big0, nil, nil, common.Big0, []byte{}, false)
35+
emptyMessage = types.NewMessage(common.HexToAddress("0x0"), nil, 0, common.Big0, 0, common.Big0, nil, nil, common.Big0, []byte{}, false, false)
3636
internalEvmHandlerSingleton *InternalEVMHandler
3737
)
3838

core/error.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,12 @@ var (
7676
// ErrIntrinsicGas is returned if the transaction is specified to use less gas
7777
// than required to start the invocation.
7878
ErrIntrinsicGas = errors.New("intrinsic gas too low")
79+
80+
// ErrEthCompatibleTransactionsNotSupported is returned if the transaction omits the 3 Celo-only
81+
// fields (FeeCurrency & co.) but support for this kind of transaction is not enabled.
82+
ErrEthCompatibleTransactionsNotSupported = errors.New("support for eth-compatible transactions is not enabled")
83+
84+
// ErrUnprotectedTransaction is returned if replay protection is required (post-Donut) but the transaction doesn't
85+
// use it.
86+
ErrUnprotectedTransaction = errors.New("replay protection is required")
7987
)

core/state_processor.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
112112
// for the transaction, gas used and an error if the transaction failed,
113113
// indicating the block was invalid.
114114
func ApplyTransaction(config *params.ChainConfig, bc vm.ChainContext, txFeeRecipient *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, cfg vm.Config) (*types.Receipt, error) {
115+
if config.IsDonut(header.Number) && !tx.Protected() {
116+
return nil, ErrUnprotectedTransaction
117+
}
115118
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
116119
if err != nil {
117120
return nil, err

core/state_transition.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
349349
// First check this message satisfies all consensus rules before
350350
// applying the message. The rules include these clauses
351351
//
352+
// 0. If the message is from an eth-compatible transaction, that we support those
353+
// and that none of the non-eth-compatible fields are present
352354
// 1. the nonce of the message caller is correct
353355
// 2. the gas price meets the minimum gas price
354356
// 3. caller has enough balance (in the right currency) to cover transaction fee
@@ -357,6 +359,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
357359
// 6. there is no overflow when calculating intrinsic gas
358360
// 7. caller has enough balance to cover asset transfer for **topmost** call
359361

362+
// Clause 0
363+
if st.msg.EthCompatible() && !st.evm.ChainConfig().IsDonut(st.evm.BlockNumber) {
364+
return nil, ErrEthCompatibleTransactionsNotSupported
365+
}
366+
if err := vm.CheckEthCompatibility(st.msg); err != nil {
367+
return nil, err
368+
}
369+
360370
// Check clauses 1-2
361371
if err := st.preCheck(); err != nil {
362372
return nil, err

core/tx_pool.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ type TxPool struct {
256256
mu sync.RWMutex
257257

258258
istanbul bool // Fork indicator whether we are in the istanbul stage.
259+
donut bool // Fork indicator for the Donut fork.
259260

260261
currentState *state.StateDB // Current state in the blockchain head
261262
pendingNonces *txNoncer // Pending state tracking virtual nonces
@@ -487,6 +488,20 @@ func (pool *TxPool) SetGasLimit(gasLimit uint64) {
487488
}
488489
}
489490

491+
// handleDonutActivation removes from the pool all transactions without EIP-155 replay protection
492+
func (pool *TxPool) handleDonutActivation() {
493+
toRemove := make(map[common.Hash]struct{})
494+
pool.all.Range(func(hash common.Hash, tx *types.Transaction) bool {
495+
if !tx.Protected() {
496+
toRemove[hash] = struct{}{}
497+
}
498+
return true
499+
})
500+
for hash := range toRemove {
501+
pool.removeTx(hash, true)
502+
}
503+
}
504+
490505
// Nonce returns the next nonce of an account, with all transactions executable
491506
// by the pool already applied on top.
492507
func (pool *TxPool) Nonce(addr common.Address) uint64 {
@@ -577,6 +592,15 @@ func (pool *TxPool) local() map[common.Address]types.Transactions {
577592
// validateTx checks whether a transaction is valid according to the consensus
578593
// rules and adheres to some heuristic limits of the local node (price and size).
579594
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
595+
if pool.donut && !tx.Protected() {
596+
return ErrUnprotectedTransaction
597+
}
598+
if tx.EthCompatible() && !pool.donut {
599+
return ErrEthCompatibleTransactionsNotSupported
600+
}
601+
if err := tx.CheckEthCompatibility(); err != nil {
602+
return err
603+
}
580604
// Reject transactions over defined size to prevent DOS attacks
581605
if uint64(tx.Size()) > txMaxSize {
582606
return ErrOversizedData
@@ -1272,6 +1296,11 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
12721296
// Update all fork indicator by next pending block number.
12731297
next := new(big.Int).Add(newHead.Number, big.NewInt(1))
12741298
pool.istanbul = pool.chainconfig.IsIstanbul(next)
1299+
wasDonut := pool.donut
1300+
pool.donut = pool.chainconfig.IsDonut(next)
1301+
if pool.donut && !wasDonut {
1302+
pool.handleDonutActivation()
1303+
}
12751304
}
12761305

12771306
// promoteExecutables moves transactions that have become processable from the

core/tx_pool_test.go

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,13 @@ import (
3838
"github.com/celo-org/celo-blockchain/params"
3939
)
4040

41-
// testTxPoolConfig is a transaction pool configuration without stateful disk
42-
// sideeffects used during testing.
43-
var testTxPoolConfig TxPoolConfig
41+
var (
42+
// testTxPoolConfig is a transaction pool configuration without stateful disk
43+
// sideeffects used during testing.
44+
testTxPoolConfig TxPoolConfig
45+
// eip155Signer to use for generating replay-protected transactions
46+
eip155Signer = types.NewEIP155Signer(params.TestChainConfig.ChainID)
47+
)
4448

4549
func init() {
4650
testTxPoolConfig = DefaultTxPoolConfig
@@ -103,6 +107,11 @@ func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key
103107
return tx
104108
}
105109

110+
func protectedTransaction(nonce uint64, gaslimit uint64, key *ecdsa.PrivateKey) *types.Transaction {
111+
tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(100), gaslimit, big.NewInt(1), nil, nil, nil, nil), eip155Signer, key)
112+
return tx
113+
}
114+
106115
func setupTxPool() (*TxPool, *ecdsa.PrivateKey) {
107116
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
108117
blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)}
@@ -759,6 +768,54 @@ func TestTransactionGapFilling(t *testing.T) {
759768
}
760769
}
761770

771+
// Tests that pool.handleDonutActivation() removes transactions without replay protection
772+
// When we change TestChangeConfig to enable Donut this test will need:
773+
// (a) to set pool.donut = false at its start (so we can add unprotected transactions)
774+
// (b) different functions to generate protected vs unprotected transactions, since we will
775+
// need to update transaction() and the others to use replay protection
776+
func TestHandleDonutActivation(t *testing.T) {
777+
t.Parallel()
778+
779+
// Create a test account and fund it
780+
pool, key := setupTxPool()
781+
defer pool.Stop()
782+
783+
account := crypto.PubkeyToAddress(key.PublicKey)
784+
pool.currentState.AddBalance(account, big.NewInt(1000000))
785+
786+
pool.AddRemotesSync([]*types.Transaction{
787+
protectedTransaction(0, 100000, key),
788+
transaction(1, 100000, key),
789+
protectedTransaction(2, 100000, key),
790+
transaction(7, 100000, key),
791+
protectedTransaction(8, 100000, key),
792+
transaction(9, 100000, key),
793+
transaction(10, 100000, key),
794+
})
795+
796+
pending, queued := pool.Stats()
797+
if pending != 3 {
798+
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
799+
}
800+
if queued != 4 {
801+
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 4)
802+
}
803+
804+
pool.handleDonutActivation()
805+
806+
pending, queued = pool.Stats()
807+
if pending != 1 {
808+
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1)
809+
}
810+
if queued != 2 {
811+
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
812+
}
813+
814+
if err := validateTxPoolInternals(pool); err != nil {
815+
t.Fatalf("pool internal state corrupted: %v", err)
816+
}
817+
}
818+
762819
// Tests that if the transaction count belonging to a single account goes above
763820
// some threshold, the higher transactions are dropped to prevent DOS attacks.
764821
func TestTransactionQueueAccountLimiting(t *testing.T) {

core/types/gen_tx_json.go

Lines changed: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)