Skip to content

Commit a9fbe65

Browse files
committed
Fix endpoint crashes due to non-replay protected EVM transactions
1 parent fc9e0e6 commit a9fbe65

File tree

7 files changed

+83
-23
lines changed

7 files changed

+83
-23
lines changed

eth/types/types.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,10 +359,14 @@ func NewTransaction(
359359
V: (*hexutil.Big)(v),
360360
R: (*hexutil.Big)(r),
361361
S: (*hexutil.Big)(s),
362-
ChainID: (*hexutil.Big)(networkID),
363362
size: tx.Size(),
364363
}
365364

365+
// if a legacy transaction has an EIP-155 chain id, include it explicitly
366+
if id := tx.ChainId(); id.Sign() != 0 {
367+
result.ChainID = (*hexutil.Big)(id)
368+
}
369+
366370
// After the Pectra hard-fork, the full list of supported tx types is:
367371
// LegacyTxType = 0x00
368372
// AccessListTxType = 0x01
@@ -391,6 +395,7 @@ func NewTransaction(
391395
yparity := hexutil.Uint64(v.Sign())
392396
result.Accesses = &al
393397
result.YParity = &yparity
398+
result.ChainID = (*hexutil.Big)(tx.ChainId())
394399
}
395400

396401
if tx.Type() > types.AccessListTxType {
@@ -399,17 +404,20 @@ func NewTransaction(
399404
// Since BaseFee is `0`, this is the max gas price
400405
// the sender is willing to pay.
401406
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
407+
result.ChainID = (*hexutil.Big)(tx.ChainId())
402408
}
403409

404410
if tx.Type() > types.DynamicFeeTxType {
405411
result.MaxFeePerBlobGas = (*hexutil.Big)(tx.BlobGasFeeCap())
406412
result.BlobVersionedHashes = tx.BlobHashes()
413+
result.ChainID = (*hexutil.Big)(tx.ChainId())
407414
}
408415

409416
// The `AuthorizationList` field became available with the introduction
410417
// of https://eip7702.io/#specification, under the `SetCodeTxType`
411418
if tx.Type() > types.BlobTxType {
412419
result.AuthorizationList = tx.SetCodeAuthorizations()
420+
result.ChainID = (*hexutil.Big)(tx.ChainId())
413421
}
414422

415423
return result, nil

models/transaction.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ type Transaction interface {
4444
GasFeeCap() *big.Int
4545
GasTipCap() *big.Int
4646
GasPrice() *big.Int
47+
ChainId() *big.Int
4748
BlobGas() uint64
4849
BlobGasFeeCap() *big.Int
4950
BlobHashes() []common.Hash
@@ -117,6 +118,10 @@ func (dc DirectCall) GasPrice() *big.Int {
117118
return BaseFeePerGas
118119
}
119120

121+
func (dc DirectCall) ChainId() *big.Int {
122+
return big.NewInt(0)
123+
}
124+
120125
func (dc DirectCall) BlobGas() uint64 {
121126
return 0
122127
}
@@ -160,10 +165,7 @@ func (tc TransactionCall) Hash() common.Hash {
160165
}
161166

162167
func (tc TransactionCall) From() (common.Address, error) {
163-
return gethTypes.Sender(
164-
gethTypes.LatestSignerForChainID(tc.ChainId()),
165-
tc.Transaction,
166-
)
168+
return DeriveTxSender(tc.Transaction)
167169
}
168170

169171
func (tc TransactionCall) MarshalBinary() ([]byte, error) {
@@ -305,3 +307,22 @@ func ValidateTransaction(
305307

306308
return nil
307309
}
310+
311+
// DeriveTxSender returns the address derived from the signature (V, R, S)
312+
// using secp256k1 elliptic curve and an error if it failed deriving or
313+
// upon an incorrect signature.
314+
func DeriveTxSender(tx *gethTypes.Transaction) (common.Address, error) {
315+
var signer gethTypes.Signer
316+
if chainID := tx.ChainId(); chainID.Sign() != 0 {
317+
signer = gethTypes.LatestSignerForChainID(chainID)
318+
} else {
319+
signer = gethTypes.HomesteadSigner{}
320+
}
321+
322+
from, err := gethTypes.Sender(signer, tx)
323+
if err != nil {
324+
return common.Address{}, fmt.Errorf("failed to derive the sender: %w", err)
325+
}
326+
327+
return from, nil
328+
}

services/requester/batch_tx_pool.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,11 @@ func (t *BatchTxPool) Add(
120120
t.txMux.Lock()
121121
defer t.txMux.Unlock()
122122

123-
from, err := gethTypes.Sender(gethTypes.LatestSignerForChainID(tx.ChainId()), tx)
123+
from, err := models.DeriveTxSender(tx)
124124
if err != nil {
125-
return fmt.Errorf("failed to derive the sender: %w", err)
125+
return err
126126
}
127+
127128
txData, err := tx.MarshalBinary()
128129
if err != nil {
129130
return err

services/requester/requester.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,9 +208,9 @@ func (e *EVM) SendRawTransaction(ctx context.Context, data []byte) (common.Hash,
208208
return common.Hash{}, err
209209
}
210210

211-
from, err := types.Sender(types.LatestSignerForChainID(tx.ChainId()), tx)
211+
from, err := models.DeriveTxSender(tx)
212212
if err != nil {
213-
return common.Hash{}, fmt.Errorf("failed to derive the sender: %w", err)
213+
return common.Hash{}, err
214214
}
215215

216216
if e.config.TxRequestLimit > 0 {

tests/fixtures/multicall3.byte

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

tests/web3js/eth_filter_endpoints_test.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,6 @@ describe('eth_getFilterChanges', async () => {
421421
transactionIndex: '0x1',
422422
value: '0x388fb0',
423423
type: '0x0',
424-
chainId: '0x286',
425424
v: '0xff',
426425
r: '0x30000000000000000',
427426
s: '0x3'

tests/web3js/eth_multicall3_contract_test.js

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,60 @@
1+
const fs = require('fs')
2+
const utils = require('web3-utils')
13
const { assert } = require('chai')
24
const conf = require('./config')
35
const helpers = require('./helpers')
46
const web3 = conf.web3
57

6-
it('deploy contract and interact', async () => {
7-
let deployed = await helpers.deployContract("storage")
8+
it('deploys mutlicall3 contract and interacts', async () => {
9+
let deployed = await helpers.deployContract('storage')
810
let contractAddress = deployed.receipt.contractAddress
911

1012
// get the default deployed value on contract
1113
const initValue = 1337
12-
let callRetrieve = await deployed.contract.methods.retrieve().encodeABI()
13-
result = await web3.eth.call({ to: contractAddress, data: callRetrieve }, "latest")
14+
let callRetrieve = deployed.contract.methods.retrieve().encodeABI()
15+
result = await web3.eth.call({ to: contractAddress, data: callRetrieve }, 'latest')
1416
assert.equal(result, initValue)
1517

16-
let multicall3 = await helpers.deployContract("multicall3")
17-
let multicall3Address = multicall3.receipt.contractAddress
18+
let transfer = await helpers.signAndSend({
19+
from: conf.eoa.address,
20+
to: '0x05f32b3cc3888453ff71b01135b34ff8e41263f2',
21+
value: utils.toWei('1.0', 'ether'),
22+
gasPrice: conf.minGasPrice,
23+
gasLimit: 55_000,
24+
})
25+
assert.equal(transfer.receipt.status, conf.successStatus)
26+
27+
let multicall3DeploymentTx = await fs.promises.readFile(`${__dirname}/../fixtures/multicall3.byte`, 'utf8')
28+
let response = await helpers.callRPCMethod(
29+
'eth_sendRawTransaction',
30+
[multicall3DeploymentTx]
31+
)
32+
assert.equal(200, response.status)
33+
34+
let txHash = response.body.result
35+
let receipt = await web3.eth.getTransactionReceipt(txHash)
36+
let multicall3Address = receipt.contractAddress
1837

1938
// make sure deploy results are correct
20-
assert.equal(multicall3.receipt.status, conf.successStatus)
21-
assert.isString(multicall3.receipt.transactionHash)
39+
assert.equal(receipt.status, conf.successStatus)
40+
assert.equal(receipt.from, '0x05f32B3cC3888453ff71B01135B34FF8e41263F2')
41+
assert.isString(receipt.transactionHash)
2242
assert.isString(multicall3Address)
2343

24-
let callSum20 = await deployed.contract.methods.sum(10, 10).encodeABI()
25-
let callSum50 = await deployed.contract.methods.sum(10, 40).encodeABI()
26-
let callAggregate3 = await multicall3.contract.methods.aggregate3(
44+
let tx = await web3.eth.getTransaction(txHash)
45+
assert.equal(tx.from, '0x05f32B3cC3888453ff71B01135B34FF8e41263F2')
46+
assert.isUndefined(tx.chainId)
47+
48+
let block = await web3.eth.getBlock(receipt.blockNumber, true)
49+
assert.equal(block.transactions[0].from, '0x05f32B3cC3888453ff71B01135B34FF8e41263F2')
50+
assert.isUndefined(block.transactions[0].chainId)
51+
52+
let multicall3ABI = require('../fixtures/multicall3ABI.json')
53+
let multicall3 = new web3.eth.Contract(multicall3ABI, multicall3Address, { handleReverted: true })
54+
55+
let callSum20 = deployed.contract.methods.sum(10, 10).encodeABI()
56+
let callSum50 = deployed.contract.methods.sum(10, 40).encodeABI()
57+
let callAggregate3 = multicall3.methods.aggregate3(
2758
[
2859
{
2960
target: contractAddress,
@@ -43,7 +74,7 @@ it('deploy contract and interact', async () => {
4374
to: multicall3Address,
4475
data: callAggregate3
4576
},
46-
"latest"
77+
'latest'
4778
)
4879
let decodedResult = web3.eth.abi.decodeParameter(
4980
{

0 commit comments

Comments
 (0)