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

Improve RPC API compatibility for Ethers.js #1957

Merged
merged 31 commits into from
Sep 27, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9e3fc5d
Add minimal ts project
piersy Sep 9, 2022
9ebdd19
Add ethers.js dependency
piersy Sep 9, 2022
11fa86e
Add etherejs api test (currently failing)
piersy Sep 9, 2022
70232a0
Add dummy data for gas limit field
piersy Sep 9, 2022
33de2a5
Build ethersjs project before executing test
piersy Sep 9, 2022
8e2c1c9
Return real gas limit in place of dummy data
piersy Sep 12, 2022
aec0159
Return baseFee if it is acessible
piersy Sep 12, 2022
84bd94c
Use framework for ethers.js compatibility tests
piersy Sep 13, 2022
c624df4
Store package lock
piersy Sep 14, 2022
9d175c1
Add make rule to install ethersjs project deps
piersy Sep 14, 2022
cb1479c
Update ci to prepare ehtersjs project
piersy Sep 14, 2022
bd71fd1
Add dockerfile for a node/golang CI executor
piersy Sep 14, 2022
9895733
Run e2e coverage CI tests verbosely
piersy Sep 14, 2022
45e386b
Add debug
piersy Sep 14, 2022
dd17619
Add more debug
piersy Sep 14, 2022
a0174f4
Change address format
piersy Sep 15, 2022
2bdc31e
Update log message for clarity
piersy Sep 21, 2022
b354bc4
Update node-golang dockerfile
piersy Sep 21, 2022
ccd6e59
Merge branch 'master' into piersy/ethersjs-compatibility
piersy Sep 21, 2022
5fd28ff
Update readme to specify a minimum go version
piersy Sep 22, 2022
e4e0216
Default to single quotes in typescript
piersy Sep 22, 2022
80d55aa
Merge branch 'master' into piersy/ethersjs-compatibility
piersy Sep 22, 2022
0195bec
Flag to disable RPC compatibilty fields
piersy Sep 26, 2022
16dc1a7
Merge remote-tracking branch 'origin/master' into piersy/ethersjs-com…
piersy Sep 27, 2022
99b9750
Split the tests under 2 describe headings
piersy Sep 27, 2022
0fa5309
Document that js tests shoudn't be run standalone
piersy Sep 27, 2022
af4224f
Move ethersjs test project under e2e_test
piersy Sep 27, 2022
419c504
Do not return block fields that cant be retreived
piersy Sep 27, 2022
986b06c
Fix typos
hbandura Sep 27, 2022
f8dc1ed
Remove unused code
hbandura Sep 27, 2022
8f3e4f6
Merge branch 'master' into piersy/ethersjs-compatibility
gastonponti Sep 27, 2022
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
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ geth:

prepare: prepare-system-contracts prepare-ethersjs-project

prepare-ethersjs-project: ./ethersjs-api-check/node_modules
prepare-ethersjs-project: ./e2e_test/ethersjs-api-check/node_modules

./ethersjs-api-check/node_modules: ./ethersjs-api-check/package.json ./ethersjs-api-check/package-lock.json
@cd ./ethersjs-api-check && npm ci
./e2e_test/ethersjs-api-check/node_modules: ./e2e_test/ethersjs-api-check/package.json ./e2e_test/ethersjs-api-check/package-lock.json
@cd ./e2e_test/ethersjs-api-check && npm ci

# This rule checks out celo-monorepo under MONOREPO_PATH at the commit contained in
# monorepo_commit and compiles the system solidity contracts. It then copies the
Expand Down
6 changes: 3 additions & 3 deletions contracts/blockchain_parameters/blockchain_parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,16 @@ func getIntrinsicGasForAlternativeFeeCurrency(vmRunner vm.EVMRunner) (uint64, er
// GetBlockGasLimitOrDefault retrieves the block max gas limit
// In case of error, it returns the default value
func GetBlockGasLimitOrDefault(vmRunner vm.EVMRunner) uint64 {
val, err := getBlockGasLimit(vmRunner)
val, err := GetBlockGasLimit(vmRunner)
if err != nil {
logError("blockGasLimit", err)
return params.DefaultGasLimit
}
return val
}

// getBlockGasLimit retrieves the block max gas limit
func getBlockGasLimit(vmRunner vm.EVMRunner) (uint64, error) {
// GetBlockGasLimit retrieves the block max gas limit
func GetBlockGasLimit(vmRunner vm.EVMRunner) (uint64, error) {
var gasLimit *big.Int
err := blockGasLimitMethod.Query(vmRunner, &gasLimit)
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions contracts/blockchain_parameters/blockchain_parameters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ func TestGetIntrinsicGasForAlternativeFeeCurrencyOrDefault(t *testing.T) {
}

func TestGetBlockGasLimit(t *testing.T) {
testutil.TestFailOnFailingRunner(t, getBlockGasLimit)
testutil.TestFailsWhenContractNotDeployed(t, contracts.ErrSmartContractNotDeployed, getBlockGasLimit)
testutil.TestFailOnFailingRunner(t, GetBlockGasLimit)
testutil.TestFailsWhenContractNotDeployed(t, contracts.ErrSmartContractNotDeployed, GetBlockGasLimit)
t.Run("should return block gas limit", func(t *testing.T) {
g := NewGomegaWithT(t)

Expand All @@ -84,7 +84,7 @@ func TestGetBlockGasLimit(t *testing.T) {
},
)

gas, err := getBlockGasLimit(runner)
gas, err := GetBlockGasLimit(runner)
g.Expect(err).ToNot(HaveOccurred())
g.Expect(gas).To(Equal(uint64(50000)))
})
Expand Down
27 changes: 27 additions & 0 deletions contracts/gasprice_minimum/gasprice_minimum.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package gasprice_minimum

import (
"fmt"
"math/big"

"github.com/celo-org/celo-blockchain/common"
Expand Down Expand Up @@ -89,6 +90,32 @@ func GetGasPriceMinimum(vmRunner vm.EVMRunner, currency *common.Address) (*big.I
return gasPriceMinimum, err
}

// GetRealGasPriceMinimum is similar to GetRealGasPriceMinimum but if there is
// a problem retrieving the gas price minimum it will return the error and a
// nil gas price minimum.
func GetRealGasPriceMinimum(vmRunner vm.EVMRunner, currency *common.Address) (*big.Int, error) {
var currencyAddress common.Address
var err error

if currency == nil {
currencyAddress, err = contracts.GetRegisteredAddress(vmRunner, params.GoldTokenRegistryId)

if err != nil {
return nil, fmt.Errorf("failed to retrieve gold token address: %w", err)
}
} else {
currencyAddress = *currency
}

var gasPriceMinimum *big.Int
err = getGasPriceMinimumMethod.Query(vmRunner, &gasPriceMinimum, currencyAddress)
if err != nil {
return nil, fmt.Errorf("failed to retrieve gas price minimum for currency %v, error: %w", currencyAddress.String(), err)
}

return gasPriceMinimum, nil
}

func GetGasPriceMinimumFloor(vmRunner vm.EVMRunner) (*big.Int, error) {
var err error

Expand Down
8 changes: 4 additions & 4 deletions e2e_test/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,8 +491,8 @@ func TestEthersJSCompatibility(t *testing.T) {
// The tests don't seem to work on CI with IPV6 addresses so we convert to IPV4 here
addr := strings.Replace(network[0].Node.HTTPEndpoint(), "[::]", "127.0.0.1", 1)

cmd := exec.Command("npm", "run", "test", "--networkaddr="+addr, "--blocknum="+hexutil.Uint64(num).String(), "--", "--invert", "--grep", "block with pruned")
cmd.Dir = "../ethersjs-api-check/"
cmd := exec.Command("npm", "run", "test", "--networkaddr="+addr, "--blocknum="+hexutil.Uint64(num).String(), "--", "--grep", "ethers.js compatibility tests with state")
cmd.Dir = "./ethersjs-api-check/"
println("executing mocha test with", cmd.String())
output, err := cmd.CombinedOutput()
println(string(output))
Expand All @@ -507,8 +507,8 @@ func TestEthersJSCompatibility(t *testing.T) {
require.NoError(t, err)

// Execute typescript tests to check what happens with a pruned block.
cmd = exec.Command("npm", "run", "test", "--networkaddr="+addr, "--blocknum="+hexutil.Uint64(num).String(), "--", "--grep", "block with pruned")
cmd.Dir = "../ethersjs-api-check/"
cmd = exec.Command("npm", "run", "test", "--networkaddr="+addr, "--blocknum="+hexutil.Uint64(num).String(), "--", "--grep", "ethers.js compatibility tests with no state")
cmd.Dir = "./ethersjs-api-check/"
println("executing mocha test with", cmd.String())
output, err = cmd.CombinedOutput()
println(string(output))
Expand Down
File renamed without changes.
94 changes: 94 additions & 0 deletions e2e_test/ethersjs-api-check/test/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Note these tests are intended to be invoked only by our e2e tests, they
* should not be executed in a standalone fashion.
* See e2e_test.TestEthersJSCompatibility
*/
import {ethers} from 'ethers';
import {assert} from 'chai';
import 'mocha';

describe('ethers.js compatibility tests with state', () => {

it('provider.getBlock works (block has gasLimit set)', async () => {
let provider = new ethers.providers.JsonRpcProvider(process.env.npm_config_networkaddr);
let block = await provider.getBlock(process.env.npm_config_blocknum as string);

// These assertions trigger on undefined or null
assert.notEqual(block, null);
assert.notEqual(block.gasLimit, null);
});

it('EIP-1559 transactions supported (can get feeData)', async () => {
let provider = new ethers.providers.JsonRpcProvider(process.env.npm_config_networkaddr);

// The fee data is the construct used to determine if EIP-1559 transactions are supported, if it contains max
let feeData = await provider.getFeeData();

// These assertions trigger on undefined or null
assert.notEqual(feeData, null);
// If the following 2 fields are set then the network is assumed to support EIP-1559 transactions.
assert.notEqual(feeData.maxFeePerGas, null);
assert.notEqual(feeData.maxPriorityFeePerGas, null);
// We check the other 2 fields for completeness, they should also be set.
assert.notEqual(feeData.gasPrice, null);
assert.notEqual(feeData.lastBaseFeePerGas, null);
});

it('block has gasLimit', async () => {
let provider = new ethers.providers.JsonRpcProvider(process.env.npm_config_networkaddr);
const fullBlock = await provider.send(
'eth_getBlockByNumber',
[ethers.utils.hexValue(process.env.npm_config_blocknum as string), true]
)
assert.isTrue(fullBlock.hasOwnProperty('gasLimit'))
});

it('block has baseFeePerGas', async () => {
let provider = new ethers.providers.JsonRpcProvider(process.env.npm_config_networkaddr);
const fullBlock = await provider.send(
'eth_getBlockByNumber',
[ethers.utils.hexValue(process.env.npm_config_blocknum as string), true]
)
assert.isTrue(fullBlock.hasOwnProperty('baseFeePerGas'))
});

});

describe('ethers.js compatibility tests with no state', () => {

it('provider.getBlock throws exception (no gasLimit)', async () => {
let provider = new ethers.providers.JsonRpcProvider(process.env.npm_config_networkaddr);
try {
await provider.getBlock(process.env.npm_config_blocknum as string);
} catch (e) {
return
}
assert.fail("Expecting exception to be thrown when getting block")
});

it('block has no gasLimit', async () => {
let provider = new ethers.providers.JsonRpcProvider(process.env.npm_config_networkaddr);
const fullBlock = await provider.send(
'eth_getBlockByNumber',
[ethers.utils.hexValue(process.env.npm_config_blocknum as string), true]
)
assert.isFalse(fullBlock.hasOwnProperty('gasLimit'))
});

it('block has no baseFeePerGas', async () => {
let provider = new ethers.providers.JsonRpcProvider(process.env.npm_config_networkaddr);
const fullBlock = await provider.send(
'eth_getBlockByNumber',
[ethers.utils.hexValue(process.env.npm_config_blocknum as string), true]
)
assert.isFalse(fullBlock.hasOwnProperty('baseFeePerGas'))
});


});


// const fullBlock = await provider.send(
gastonponti marked this conversation as resolved.
Show resolved Hide resolved
// 'eth_getBlockByNumber',
// [ethers.utils.hexValue(blockNumber), true]
// )
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"rootDir": "src",
"rootDir": ".",
"outDir": "dist",
"moduleResolution": "node"
"moduleResolution": "node",
"useUnknownInCatchVariables": true
}
}
24 changes: 24 additions & 0 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package eth
import (
"context"
"errors"
"fmt"
"math/big"

ethereum "github.com/celo-org/celo-blockchain"
Expand Down Expand Up @@ -315,6 +316,15 @@ func (b *EthAPIBackend) GasPriceMinimumForHeader(ctx context.Context, currencyAd
return gpm.GetGasPriceMinimum(vmRunner, currencyAddress)
}

func (b *EthAPIBackend) RealGasPriceMinimumForHeader(ctx context.Context, currencyAddress *common.Address, header *types.Header) (*big.Int, error) {
state, err := b.eth.blockchain.StateAt(header.Root)
if err != nil {
return nil, err
}
vmRunner := b.eth.BlockChain().NewEVMRunner(header, state)
return gpm.GetRealGasPriceMinimum(vmRunner, currencyAddress)
}

func (b *EthAPIBackend) SuggestPrice(ctx context.Context, currencyAddress *common.Address) (*big.Int, error) {
vmRunner, err := b.eth.BlockChain().NewEVMRunnerForCurrentBlock()
if err != nil {
Expand All @@ -334,6 +344,20 @@ func (b *EthAPIBackend) GetBlockGasLimit(ctx context.Context, blockNrOrHash rpc.
return blockchain_parameters.GetBlockGasLimitOrDefault(vmRunner)
}

func (b *EthAPIBackend) GetRealBlockGasLimit(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (uint64, error) {
statedb, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return 0, fmt.Errorf("LesApiBackend failed to retreive state for block gas limit for block %v: %w", blockNrOrHash, err)
}

caller := b.eth.BlockChain().NewEVMRunner(header, statedb)
limit, err := blockchain_parameters.GetBlockGasLimit(caller)
if err != nil {
return 0, fmt.Errorf("LesApiBackend failed to retreive block gas limit from blockchain parameters constract for block %v: %w", blockNrOrHash, err)
}
return limit, nil
}

func (b *EthAPIBackend) NewEVMRunner(header *types.Header, state vm.StateDB) vm.EVMRunner {
return b.eth.BlockChain().NewEVMRunner(header, state)
}
Expand Down
44 changes: 0 additions & 44 deletions ethersjs-api-check/test/test.ts

This file was deleted.

21 changes: 10 additions & 11 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -749,21 +749,20 @@ func addEthCompatibilityFields(ctx context.Context, block map[string]interface{}
numhash := rpc.BlockNumberOrHash{
BlockHash: &hash,
}
gasLimit := b.GetBlockGasLimit(ctx, numhash)
block["gasLimit"] = hexutil.Uint64(gasLimit)
gasLimit, err := b.GetRealBlockGasLimit(ctx, numhash)
if err != nil {
log.Debug("Not adding gasLimit to RPC response, failed to retrieve it", "block", header.Number.Uint64(), "err", err)
} else {
block["gasLimit"] = hexutil.Uint64(gasLimit)
}

// Providing nil as the currency address gets the gas price minimum for the native celo asset.
baseFee, err := b.GasPriceMinimumForHeader(ctx, nil, header)

// GasPriceMinimumForHeader can return an error + non nil baseFee, an error
// and nil base fee or a base fee and a nil error. In all cases where a
// base fee is returned we want to add it as a field and if there was an
// error and nil base fee we log the failure.
if err != nil && baseFee == nil {
baseFee, err := b.RealGasPriceMinimumForHeader(ctx, nil, header)
if err != nil {
log.Debug("Not adding baseFeePerGas to RPC response, failed to retrieve gas price minimum", "block", header.Number.Uint64(), "err", err)
return
} else {
block["baseFeePerGas"] = (*hexutil.Big)(baseFee)
}
block["baseFeePerGas"] = (*hexutil.Big)(baseFee)
}
gastonponti marked this conversation as resolved.
Show resolved Hide resolved

// GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index. When fullTx is true
Expand Down
10 changes: 10 additions & 0 deletions internal/ethapi/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ type Backend interface {
SuggestGasTipCap(ctx context.Context, currencyAddress *common.Address) (*big.Int, error)
CurrentGasPriceMinimum(ctx context.Context, currencyAddress *common.Address) (*big.Int, error)
GasPriceMinimumForHeader(ctx context.Context, currencyAddress *common.Address, header *types.Header) (*big.Int, error)

// As opposed to GasPriceMinimumForHeader which returns a default value in
// some cases when it can't retrieve the gas price minimum, this function
// returns an error and no gas price minimum when it encounters a problem.
RealGasPriceMinimumForHeader(ctx context.Context, currencyAddress *common.Address, header *types.Header) (*big.Int, error)
SyncProgress() ethereum.SyncProgress

ChainDb() ethdb.Database
Expand Down Expand Up @@ -99,6 +104,11 @@ type Backend interface {
GatewayFee() *big.Int
GetIntrinsicGasForAlternativeFeeCurrency(ctx context.Context) uint64
GetBlockGasLimit(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) uint64

// As opposed to GetBlockGasLimit which returns a default value in the case
// that it can't retrieve the block gas limit, this function returns an
// error and no gas limit when it encounters a problem.
GetRealBlockGasLimit(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (uint64, error)
NewEVMRunner(*types.Header, vm.StateDB) vm.EVMRunner
Engine() consensus.Engine
}
Expand Down
Loading