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

Problem: concurrent write base fee in fee history #364

Merged
merged 5 commits into from
Oct 19, 2023
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (rpc) [#1685](https://github.com/evmos/ethermint/pull/1685) Fix parse for websocket connID.
- (rpc) [#1773](https://github.com/evmos/ethermint/pull/1773) Avoid channel get changed when concurrent subscribe happens.
* (mempool) [#310](https://github.com/crypto-org-chain/ethermint/pull/310) disable vesting messages in check tx mode.
* (rpc) [#364](https://github.com/crypto-org-chain/ethermint/pull/364) Only use NextBaseFee as last item to avoid concurrent write in `eth_feeHistory`.

### Improvements

Expand Down
1 change: 1 addition & 0 deletions cmd/ethermintd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ func queryCommand() *cobra.Command {
rpc.BlockCommand(),
authcmd.QueryTxsByEventsCmd(),
authcmd.QueryTxCmd(),
rpc.QueryEventForTxCmd(),
)

app.ModuleBasics.AddQueryCommands(cmd)
Expand Down
14 changes: 13 additions & 1 deletion rpc/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
"github.com/evmos/ethermint/rpc/types"

Check failure on line 35 in rpc/backend/backend.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

ST1019: package "github.com/evmos/ethermint/rpc/types" is being imported more than once (stylecheck)
rpctypes "github.com/evmos/ethermint/rpc/types"

Check failure on line 36 in rpc/backend/backend.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

ST1019(related information): other import of "github.com/evmos/ethermint/rpc/types" (stylecheck)
"github.com/evmos/ethermint/server/config"
ethermint "github.com/evmos/ethermint/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
Expand Down Expand Up @@ -144,6 +145,14 @@

var bAttributeKeyEthereumBloom = []byte(evmtypes.AttributeKeyEthereumBloom)

type ProcessBlocker func(
tendermintBlock *tmrpctypes.ResultBlock,
ethBlock *map[string]interface{},
rewardPercentiles []float64,
tendermintBlockResult *tmrpctypes.ResultBlockResults,
targetOneFeeHistory *types.OneFeeHistory,
) error

// Backend implements the BackendI interface
type Backend struct {
ctx context.Context
Expand All @@ -154,6 +163,7 @@
cfg config.Config
allowUnprotectedTxs bool
indexer ethermint.EVMTxIndexer
processBlocker ProcessBlocker
}

// NewBackend creates a new Backend instance for cosmos and ethereum namespaces
Expand All @@ -174,7 +184,7 @@
panic(err)
}

return &Backend{
b := &Backend{
ctx: context.Background(),
clientCtx: clientCtx,
queryClient: rpctypes.NewQueryClient(clientCtx),
Expand All @@ -184,4 +194,6 @@
allowUnprotectedTxs: allowUnprotectedTxs,
indexer: indexer,
}
b.processBlocker = b.processBlock
return b
}
7 changes: 5 additions & 2 deletions rpc/backend/chain_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,15 +252,18 @@ func (b *Backend) FeeHistory(
}

oneFeeHistory := rpctypes.OneFeeHistory{}
err = b.processBlock(tendermintblock, &ethBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory)
err = b.processBlocker(tendermintblock, &ethBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory)
if err != nil {
chanErr <- err
return
}

// copy
thisBaseFee[index] = (*hexutil.Big)(oneFeeHistory.BaseFee)
thisBaseFee[index+1] = (*hexutil.Big)(oneFeeHistory.NextBaseFee)
// only use NextBaseFee as last item to avoid concurrent write
if int(index) == len(thisBaseFee)-2 {
thisBaseFee[index+1] = (*hexutil.Big)(oneFeeHistory.NextBaseFee)
}
thisGasUsedRatio[index] = oneFeeHistory.GasUsedRatio
if calculateRewards {
for j := 0; j < rewardCount; j++ {
Expand Down
69 changes: 62 additions & 7 deletions rpc/backend/chain_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,13 +320,14 @@ func (suite *BackendTestSuite) TestGlobalMinGasPrice() {

func (suite *BackendTestSuite) TestFeeHistory() {
testCases := []struct {
name string
registerMock func(validator sdk.AccAddress)
userBlockCount ethrpc.DecimalOrHex
latestBlock ethrpc.BlockNumber
expFeeHistory *rpc.FeeHistoryResult
validator sdk.AccAddress
expPass bool
name string
registerMock func(validator sdk.AccAddress)
userBlockCount ethrpc.DecimalOrHex
latestBlock ethrpc.BlockNumber
expFeeHistory *rpc.FeeHistoryResult
validator sdk.AccAddress
expPass bool
targetNewBaseFees []*big.Int
}{
{
"fail - can't get params ",
Expand All @@ -341,6 +342,7 @@ func (suite *BackendTestSuite) TestFeeHistory() {
nil,
nil,
false,
nil,
},
{
"fail - user block count higher than max block count ",
Expand All @@ -355,6 +357,7 @@ func (suite *BackendTestSuite) TestFeeHistory() {
nil,
nil,
false,
nil,
},
{
"fail - Tendermint block fetching error ",
Expand All @@ -371,6 +374,7 @@ func (suite *BackendTestSuite) TestFeeHistory() {
nil,
nil,
false,
nil,
},
{
"fail - Eth block fetching error",
Expand All @@ -388,6 +392,7 @@ func (suite *BackendTestSuite) TestFeeHistory() {
nil,
nil,
true,
nil,
},
{
"fail - Invalid base fee",
Expand All @@ -409,6 +414,7 @@ func (suite *BackendTestSuite) TestFeeHistory() {
nil,
sdk.AccAddress(tests.GenerateAddress().Bytes()),
false,
nil,
},
{
"pass - Valid FeeHistoryResults object",
Expand Down Expand Up @@ -438,6 +444,39 @@ func (suite *BackendTestSuite) TestFeeHistory() {
},
sdk.AccAddress(tests.GenerateAddress().Bytes()),
true,
nil,
},
{
"pass - Concurrent FeeHistoryResults object",
func(validator sdk.AccAddress) {
var header metadata.MD
baseFee := sdk.NewInt(1)
queryClient := suite.backend.queryClient.QueryClient.(*mocks.EVMQueryClient)
fQueryClient := suite.backend.queryClient.FeeMarket.(*mocks.FeeMarketQueryClient)
client := suite.backend.clientCtx.Client.(*mocks.Client)
suite.backend.cfg.JSONRPC.FeeHistoryCap = 2
RegisterBlock(client, ethrpc.BlockNumber(1).Int64(), nil)
RegisterBlockResults(client, 1)
RegisterBaseFee(queryClient, baseFee)
RegisterValidatorAccount(queryClient, validator)
RegisterConsensusParams(client, 1)
RegisterParams(queryClient, &header, 1)
RegisterParamsWithoutHeader(queryClient, 1)
RegisterFeeMarketParams(fQueryClient, 1)
},
1,
1,
&rpc.FeeHistoryResult{
OldestBlock: (*hexutil.Big)(big.NewInt(1)),
BaseFee: []*hexutil.Big{(*hexutil.Big)(big.NewInt(1)), (*hexutil.Big)(big.NewInt(0))},
GasUsedRatio: []float64{0},
Reward: [][]*hexutil.Big{{(*hexutil.Big)(big.NewInt(0)), (*hexutil.Big)(big.NewInt(0)), (*hexutil.Big)(big.NewInt(0)), (*hexutil.Big)(big.NewInt(0))}},
},
sdk.AccAddress(tests.GenerateAddress().Bytes()),
true,
[]*big.Int{
big.NewInt(0), // for overwrite overlap
},
},
}

Expand All @@ -446,6 +485,22 @@ func (suite *BackendTestSuite) TestFeeHistory() {
suite.SetupTest() // reset test and queries
tc.registerMock(tc.validator)

called := 0
if len(tc.targetNewBaseFees) > 0 {
suite.backend.processBlocker = func(
tendermintBlock *tmrpctypes.ResultBlock,
ethBlock *map[string]interface{},
rewardPercentiles []float64,
tendermintBlockResult *tmrpctypes.ResultBlockResults,
targetOneFeeHistory *rpc.OneFeeHistory,
) error {
suite.backend.processBlock(tendermintBlock, ethBlock, rewardPercentiles, tendermintBlockResult, targetOneFeeHistory)
targetOneFeeHistory.NextBaseFee = tc.targetNewBaseFees[called]
called += 1
return nil
}
}

feeHistory, err := suite.backend.FeeHistory(tc.userBlockCount, tc.latestBlock, []float64{25, 50, 75, 100})
if tc.expPass {
suite.Require().NoError(err)
Expand Down
14 changes: 14 additions & 0 deletions tests/integration_tests/configs/cosmovisor.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ config {
base_fee:: super.base_fee,
},
},
gov: {
voting_params: {
voting_period: '10s',
},
deposit_params: {
max_deposit_period: '10s',
min_deposit: [
{
denom: 'aphoton',
amount: '1',
},
],
},
},
},
},
},
Expand Down
4 changes: 1 addition & 3 deletions tests/integration_tests/configs/default.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,8 @@
},
},
gov: {
voting_params: {
params: {
voting_period: '10s',
},
deposit_params: {
max_deposit_period: '10s',
min_deposit: [
{
Expand Down
41 changes: 39 additions & 2 deletions tests/integration_tests/cosmoscli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import subprocess
import tempfile

import requests
Expand All @@ -13,10 +14,10 @@ class ChainCommand:
def __init__(self, cmd):
self.cmd = cmd

def __call__(self, cmd, *args, stdin=None, **kwargs):
def __call__(self, cmd, *args, stdin=None, stderr=subprocess.STDOUT, **kwargs):
"execute chain-maind"
args = " ".join(build_cli_args_safe(cmd, *args, **kwargs))
return interact(f"{self.cmd} {args}", input=stdin)
return interact(f"{self.cmd} {args}", input=stdin, stderr=stderr)


class CosmosCLI:
Expand Down Expand Up @@ -700,6 +701,7 @@ def gov_propose(self, proposer, kind, proposal, **kwargs):

def gov_vote(self, voter, proposal_id, option, **kwargs):
kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)
kwargs.setdefault("broadcast_mode", "sync")
return json.loads(
self.raw(
"tx",
Expand Down Expand Up @@ -839,3 +841,38 @@ def rollback(self):

def migrate_keystore(self):
return self.raw("keys", "migrate", home=self.data_dir)

def get_default_kwargs(self):
return {
"gas_prices": DEFAULT_GAS_PRICE,
"gas": "auto",
"gas_adjustment": "1.5",
}

def event_query_tx_for(self, hash):
return json.loads(
self.raw(
"query",
"event-query-tx-for",
hash,
"-y",
home=self.data_dir,
stderr=subprocess.DEVNULL,
)
)

def submit_gov_proposal(self, proposal, **kwargs):
default_kwargs = self.get_default_kwargs()
kwargs.setdefault("broadcast_mode", "sync")
return json.loads(
self.raw(
"tx",
"gov",
"submit-proposal",
proposal,
"-y",
home=self.data_dir,
stderr=subprocess.DEVNULL,
**(default_kwargs | kwargs),
)
)
Loading
Loading