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

[CIP-50] optional replay protection #1748

Merged
merged 5 commits into from
Dec 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
}

func applyTransaction(msg types.Message, config *params.ChainConfig, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM, vmRunner vm.EVMRunner, sysCtx *SysContractCallCtx) (*types.Receipt, error) {
if config.IsDonut(blockNumber) && !tx.Protected() {
if config.IsDonut(blockNumber) && !config.IsEspresso(blockNumber) && !tx.Protected() {
return nil, ErrUnprotectedTransaction
}

Expand Down
20 changes: 1 addition & 19 deletions core/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,20 +506,6 @@ func (pool *TxPool) setGasLimit(gasLimit uint64) {
}
}

// handleDonutActivation removes from the pool all transactions without EIP-155 replay protection
func (pool *TxPool) handleDonutActivation() {
toRemove := make(map[common.Hash]struct{})
pool.all.Range(func(hash common.Hash, tx *types.Transaction, _ bool) bool {
if !tx.Protected() {
toRemove[hash] = struct{}{}
}
return true
}, true, true)
for hash := range toRemove {
pool.removeTx(hash, true)
}
}

// Nonce returns the next nonce of an account, with all transactions executable
// by the pool already applied on top.
func (pool *TxPool) Nonce(addr common.Address) uint64 {
Expand Down Expand Up @@ -651,7 +637,7 @@ func (pool *TxPool) ctx() *txPoolContext {
// validateTx checks whether a transaction is valid according to the consensus
// rules and adheres to some heuristic limits of the local node (price and size).
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if pool.donut && !tx.Protected() {
if pool.donut && !pool.espresso && !tx.Protected() {
return ErrUnprotectedTransaction
}
if tx.EthCompatible() && !pool.donut {
Expand Down Expand Up @@ -1391,12 +1377,8 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {

// Update all fork indicator by next pending block number.
next := new(big.Int).Add(newHead.Number, big.NewInt(1))
wasDonut := pool.donut
pool.istanbul = pool.chainconfig.IsIstanbul(next)
pool.donut = pool.chainconfig.IsDonut(next)
if pool.donut && !wasDonut {
pool.handleDonutActivation()
}
pool.espresso = pool.chainconfig.IsEspresso(next)
}

Expand Down
60 changes: 45 additions & 15 deletions core/tx_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -893,42 +893,72 @@ func TestTransactionGapFilling(t *testing.T) {
// (a) to set pool.donut = false at its start (so we can add unprotected transactions)
// (b) different functions to generate protected vs unprotected transactions, since we will
// need to update transaction() and the others to use replay protection
func TestHandleDonutActivation(t *testing.T) {
func TestPoolReAcceptingUnprotectedTxsFromEFork(t *testing.T) {
t.Parallel()

// Create a test account and fund it
pool, key := setupTxPool()
// Create a test account and fund it
defer pool.Stop()

account := crypto.PubkeyToAddress(key.PublicKey)
pool.currentState.AddBalance(account, big.NewInt(1000000))

// flag it as before donut
pool.donut = false
pool.espresso = false

pool.AddRemotesSync([]*types.Transaction{
protectedTransaction(0, 100000, key),
transaction(1, 100000, key),
protectedTransaction(2, 100000, key),
transaction(7, 100000, key),
protectedTransaction(8, 100000, key),
transaction(9, 100000, key),
transaction(10, 100000, key),

protectedTransaction(10, 100000, key),
transaction(11, 100000, key),
})

pending, queued := pool.Stats()
if pending != 2 {
t.Fatalf("before donut, pending transactions mismatched: have %d, want %d", pending, 2)
}
if queued != 2 {
t.Fatalf("before donut, queued transactions mismatched: have %d, want %d", queued, 2)
}

// In donut fork
pool.donut = true

pool.AddRemotesSync([]*types.Transaction{
protectedTransaction(2, 100000, key),
transaction(3, 100000, key),

protectedTransaction(12, 100000, key),
transaction(13, 100000, key),
})

pending, queued = pool.Stats()
if pending != 3 {
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 3)
t.Fatalf("after donut, pending transactions mismatched: have %d, want %d", pending, 3)
}
if queued != 4 {
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 4)
if queued != 3 {
t.Fatalf("after donut, queued transactions mismatched: have %d, want %d", queued, 3)
}

pool.handleDonutActivation()
// In E fork
// flag it as E hard fork
pool.espresso = true
pool.AddRemotesSync([]*types.Transaction{
transaction(3, 100000, key),
protectedTransaction(4, 100000, key),

transaction(13, 100000, key),
protectedTransaction(14, 100000, key),
})

pending, queued = pool.Stats()
if pending != 1 {
t.Fatalf("pending transactions mismatched: have %d, want %d", pending, 1)
if pending != 5 {
t.Fatalf("after espresso, pending transactions mismatched: have %d, want %d", pending, 5)
}
if queued != 2 {
t.Fatalf("queued transactions mismatched: have %d, want %d", queued, 2)
if queued != 5 {
t.Fatalf("after espresso, queued transactions mismatched: have %d, want %d", queued, 5)
}

if err := validateTxPoolInternals(pool); err != nil {
Expand Down
9 changes: 7 additions & 2 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ import (

// EthAPIBackend implements ethapi.Backend for full nodes
type EthAPIBackend struct {
extRPCEnabled bool
eth *Ethereum
extRPCEnabled bool
allowUnprotectedTxs bool
eth *Ethereum
}

// ChainConfig returns the active chain configuration.
Expand Down Expand Up @@ -360,6 +361,10 @@ func (b *EthAPIBackend) ExtRPCEnabled() bool {
return b.extRPCEnabled
}

func (b *EthAPIBackend) UnprotectedAllowed() bool {
return b.allowUnprotectedTxs
}

func (b *EthAPIBackend) RPCGasCap() uint64 {
return b.eth.config.RPCGasCap
}
Expand Down
2 changes: 1 addition & 1 deletion eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, chainDb)
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))

eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), eth}
eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), true, eth}

// Setup DNS discovery iterators.
dnsclient := dnsdisc.NewClient(dnsdisc.Config{})
Expand Down
12 changes: 9 additions & 3 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1623,15 +1623,21 @@ func SubmitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (c
if err := checkFeeFromCeloTx(ctx, b, tx); err != nil {
return common.Hash{}, err
}
currentBlockNumber := b.CurrentBlock().Number()
if !tx.Protected() {
// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
if !b.UnprotectedAllowed() {
// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC for this node")
}
if b.ChainConfig().IsDonut(currentBlockNumber) && !b.ChainConfig().IsEspresso(currentBlockNumber) {
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
}
}
if err := b.SendTx(ctx, tx); err != nil {
return common.Hash{}, err
}
// Print a log with full tx details for manual investigations and interventions
signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())
signer := types.MakeSigner(b.ChainConfig(), currentBlockNumber)
from, err := types.Sender(signer, tx)
if err != nil {
return common.Hash{}, err
Expand Down
5 changes: 3 additions & 2 deletions internal/ethapi/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ type Backend interface {
ChainDb() ethdb.Database
AccountManager() *accounts.Manager
ExtRPCEnabled() bool
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
UnprotectedAllowed() bool // allows only for EIP155 transactions.

// Blockchain API
SetHead(number uint64)
Expand Down
4 changes: 4 additions & 0 deletions les/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,10 @@ func (b *LesApiBackend) ExtRPCEnabled() bool {
return b.extRPCEnabled
}

func (b *LesApiBackend) UnprotectedAllowed() bool {
return b.allowUnprotectedTxs
}

func (b *LesApiBackend) RPCGasCap() uint64 {
return b.eth.config.RPCGasCap
}
Expand Down
2 changes: 1 addition & 1 deletion les/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*LightEthereum, error) {
rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig)
}

leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, leth}
leth.ApiBackend = &LesApiBackend{stack.Config().ExtRPCEnabled(), true, leth}

leth.chainreader = &LightChainReader{
config: leth.chainConfig,
Expand Down
2 changes: 1 addition & 1 deletion light/txpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
err error
)

if pool.donut && !tx.Protected() {
if pool.donut && !pool.espresso && !tx.Protected() {
return core.ErrUnprotectedTransaction
}
if tx.EthCompatible() && !pool.donut {
Expand Down
4 changes: 2 additions & 2 deletions node/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ type Config struct {
trustedNodesWarning bool
oldGethResourceWarning bool

// AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC.
AllowUnprotectedTxs bool `toml:",omitempty"`
// // AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC.
// AllowUnprotectedTxs bool `toml:",omitempty"`
}

// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into
Expand Down