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

handle EIP-1559 blocks & fee burning #88

Merged
merged 1 commit into from
Jan 27, 2022
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
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