Skip to content

Commit

Permalink
handle EIP-1559 blocks & fee burning (#88)
Browse files Browse the repository at this point in the history
  • Loading branch information
roberto-bayardo authored Jan 27, 2022
1 parent 7081025 commit 4497658
Show file tree
Hide file tree
Showing 12 changed files with 2,559 additions and 7 deletions.
65 changes: 58 additions & 7 deletions ethereum/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ const (

maxTraceConcurrency = int64(16) // nolint:gomnd
semaphoreTraceWeight = int64(1) // nolint:gomnd

// eip1559TxType is the EthTypes.Transaction.Type() value that indicates this transaction
// follows EIP-1559.
eip1559TxType = 2
)

// Client allows for querying a set of specific Ethereum endpoints in an
Expand Down Expand Up @@ -367,12 +371,19 @@ func (ec *Client) getBlock(
for i, tx := range body.Transactions {
txs[i] = tx.tx
receipt := receipts[i]
gasUsedBig := new(big.Int).SetUint64(receipt.GasUsed)
feeAmount := gasUsedBig.Mul(gasUsedBig, txs[i].GasPrice())

gasUsed := new(big.Int).SetUint64(receipt.GasUsed)
gasPrice, err := effectiveGasPrice(txs[i], head.BaseFee)
if err != nil {
return nil, nil, fmt.Errorf("%w: failure getting effective gas price", err)
}
loadedTxs[i] = tx.LoadedTransaction()
loadedTxs[i].Transaction = txs[i]
loadedTxs[i].FeeAmount = feeAmount
loadedTxs[i].FeeAmount = new(big.Int).Mul(gasUsed, gasPrice)
if head.BaseFee != nil { // EIP-1559
loadedTxs[i].FeeBurned = new(big.Int).Mul(gasUsed, head.BaseFee)
} else {
loadedTxs[i].FeeBurned = nil
}
loadedTxs[i].Miner = MustChecksum(head.Coinbase.Hex())
loadedTxs[i].Receipt = receipt

Expand All @@ -388,6 +399,21 @@ func (ec *Client) getBlock(
return types.NewBlockWithHeader(&head).WithBody(txs, uncles), loadedTxs, nil
}

// effectiveGasPrice returns the price of gas charged to this transaction to be included in the
// block.
func effectiveGasPrice(tx *EthTypes.Transaction, baseFee *big.Int) (*big.Int, error) {
if tx.Type() != eip1559TxType {
return tx.GasPrice(), nil
}
// For EIP-1559 the gas price is determined by the base fee & miner tip instead
// of the tx-specified gas price.
tip, err := tx.EffectiveGasTip(baseFee)
if err != nil {
return nil, err
}
return new(big.Int).Add(tip, baseFee), nil
}

func (ec *Client) getBlockTraces(
ctx context.Context,
blockHash common.Hash,
Expand Down Expand Up @@ -754,6 +780,7 @@ type loadedTransaction struct {
BlockNumber *string
BlockHash *common.Hash
FeeAmount *big.Int
FeeBurned *big.Int // nil if no fees were burned
Miner string
Status bool

Expand All @@ -763,7 +790,13 @@ type loadedTransaction struct {
}

func feeOps(tx *loadedTransaction) []*RosettaTypes.Operation {
return []*RosettaTypes.Operation{
var minerEarnedAmount *big.Int
if tx.FeeBurned == nil {
minerEarnedAmount = tx.FeeAmount
} else {
minerEarnedAmount = new(big.Int).Sub(tx.FeeAmount, tx.FeeBurned)
}
ops := []*RosettaTypes.Operation{
{
OperationIdentifier: &RosettaTypes.OperationIdentifier{
Index: 0,
Expand All @@ -774,7 +807,7 @@ func feeOps(tx *loadedTransaction) []*RosettaTypes.Operation {
Address: MustChecksum(tx.From.String()),
},
Amount: &RosettaTypes.Amount{
Value: new(big.Int).Neg(tx.FeeAmount).String(),
Value: new(big.Int).Neg(minerEarnedAmount).String(),
Currency: Currency,
},
},
Expand All @@ -794,11 +827,29 @@ func feeOps(tx *loadedTransaction) []*RosettaTypes.Operation {
Address: MustChecksum(tx.Miner),
},
Amount: &RosettaTypes.Amount{
Value: tx.FeeAmount.String(),
Value: minerEarnedAmount.String(),
Currency: Currency,
},
},
}
if tx.FeeBurned == nil {
return ops
}
burntOp := &RosettaTypes.Operation{
OperationIdentifier: &RosettaTypes.OperationIdentifier{
Index: 2, // nolint:gomnd
},
Type: FeeOpType,
Status: RosettaTypes.String(SuccessStatus),
Account: &RosettaTypes.AccountIdentifier{
Address: MustChecksum(tx.From.String()),
},
Amount: &RosettaTypes.Amount{
Value: new(big.Int).Neg(tx.FeeBurned).String(),
Currency: Currency,
},
}
return append(ops, burntOp)
}

// transactionReceipt returns the receipt of a transaction by transaction hash.
Expand Down
116 changes: 116 additions & 0 deletions ethereum/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2339,6 +2339,122 @@ func TestBlock_468194(t *testing.T) {
mockGraphQL.AssertExpectations(t)
}

// Block with EIP-1559 base fee & txs. This block taken from mainnet:
// https://etherscan.io/block/0x68985b6b06bb5c6012393145729babb983fc16c50ec5207972ddda02de02f7e2
// This block has 7 transactions, all EIP-1559 type except the last.
func TestBlock_13998626(t *testing.T) {
mockJSONRPC := &mocks.JSONRPC{}
mockGraphQL := &mocks.GraphQL{}

tc, err := testTraceConfig()
assert.NoError(t, err)
c := &Client{
c: mockJSONRPC,
g: mockGraphQL,
tc: tc,
p: params.RopstenChainConfig,
traceSemaphore: semaphore.NewWeighted(100),
}

ctx := context.Background()
mockJSONRPC.On(
"CallContext",
ctx,
mock.Anything,
"eth_getBlockByNumber",
"0xd59a22",
true,
).Return(
nil,
).Run(
func(args mock.Arguments) {
r := args.Get(1).(*json.RawMessage)

file, err := ioutil.ReadFile("testdata/block_13998626.json")
assert.NoError(t, err)

*r = json.RawMessage(file)
},
).Once()
mockJSONRPC.On(
"CallContext",
ctx,
mock.Anything,
"debug_traceBlockByHash",
common.HexToHash("0x68985b6b06bb5c6012393145729babb983fc16c50ec5207972ddda02de02f7e2"),
tc,
).Return(
nil,
).Run(
func(args mock.Arguments) {
r := args.Get(1).(*json.RawMessage)

file, err := ioutil.ReadFile(
"testdata/block_trace_0x68985b6b06bb5c6012393145729babb983fc16c50ec5207972ddda02de02f7e2.json") // nolint
assert.NoError(t, err)
*r = json.RawMessage(file)
},
).Once()
mockJSONRPC.On(
"BatchCallContext",
ctx,
mock.Anything,
).Return(
nil,
).Run(
func(args mock.Arguments) {
r := args.Get(1).([]rpc.BatchElem)
assert.Len(t, r, 7)
for i, txHash := range []string{
"0xf121c8c07ed51b6ac2d11fe3f0892bff2221ec9168280d12581ea8ff45e71421",
"0xef0748860f1c1ba28a5ae3ae9d2d1133940f7c8090fc862acf48de42b00ae2b5",
"0xb240b922161bb0aeaa5ebe67e6cf77311092bd945b9582b8deba61e2ebdde74f",
"0xfac8149f95c20f62264991fe15dc74ca77c92ad6e4329496548277fb4d520509",
"0x0a4cd36d72c2ed4767c1d228a7aa0638c3e46397f48b6b09f35ed455c851bb04",
"0x9ee03d5922b2a901e3fc05d8a6351165b9f211162363c790c98746ef229e395c",
"0x0d4a4f924858a5b19f6b931a914701d4258e73fa738da3d38eb3be1d1e862a7a",
} {
assert.Equal(
t,
txHash,
r[i].Args[0],
)

file, err := ioutil.ReadFile(
"testdata/tx_receipt_" + txHash + ".json",
) // nolint
assert.NoError(t, err)

receipt := new(types.Receipt)
assert.NoError(t, receipt.UnmarshalJSON(file))
*(r[i].Result.(**types.Receipt)) = receipt
}
},
).Once()

correctRaw, err := ioutil.ReadFile("testdata/block_response_13998626.json")
assert.NoError(t, err)
var correctResp *RosettaTypes.BlockResponse
assert.NoError(t, json.Unmarshal(correctRaw, &correctResp))

resp, err := c.Block(
ctx,
&RosettaTypes.PartialBlockIdentifier{
Index: RosettaTypes.Int64(13998626),
},
)
assert.NoError(t, err)

// Ensure types match
jsonResp, err := jsonifyBlock(resp)

assert.NoError(t, err)
assert.Equal(t, correctResp.Block, jsonResp)

mockJSONRPC.AssertExpectations(t)
mockGraphQL.AssertExpectations(t)
}

func TestPendingNonceAt(t *testing.T) {
mockJSONRPC := &mocks.JSONRPC{}
mockGraphQL := &mocks.GraphQL{}
Expand Down
Loading

0 comments on commit 4497658

Please sign in to comment.