diff --git a/jsonrpc/backend/backend.go b/jsonrpc/backend/backend.go index 505f895..b03a593 100644 --- a/jsonrpc/backend/backend.go +++ b/jsonrpc/backend/backend.go @@ -2,6 +2,7 @@ package backend import ( "context" + "fmt" "sync" "time" @@ -39,6 +40,10 @@ type JSONRPCBackend struct { txLookupCache *lru.Cache[common.Hash, *rpctypes.RPCTransaction] receiptCache *lru.Cache[common.Hash, *coretypes.Receipt] + // fee cache + feeDenom string + feeDecimals uint8 + mut sync.Mutex // mutex for accMuts accMuts map[string]*AccMut @@ -65,6 +70,7 @@ const ( // NewJSONRPCBackend creates a new JSONRPCBackend instance func NewJSONRPCBackend( + ctx context.Context, app *app.MinitiaApp, logger log.Logger, svrCtx *server.Context, @@ -86,8 +92,7 @@ func NewJSONRPCBackend( return nil, err } - ctx := context.Background() - return &JSONRPCBackend{ + b := &JSONRPCBackend{ app: app, logger: logger, @@ -113,7 +118,57 @@ func NewJSONRPCBackend( svrCtx: svrCtx, clientCtx: clientCtx, cfg: cfg, - }, nil + } + + // start fee fetcher + go b.feeFetcher() + + return b, nil +} + +func (b *JSONRPCBackend) feeFetcher() { + fetcher := func() (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("feeFetcher panic: %v", r) + } + }() + + queryCtx, err := b.getQueryCtx() + if err != nil { + return err + } + + params, err := b.app.EVMKeeper.Params.Get(queryCtx) + if err != nil { + return err + } + + feeDenom := params.FeeDenom + decimals, err := b.app.EVMKeeper.ERC20Keeper().GetDecimals(queryCtx, feeDenom) + if err != nil { + return err + } + + b.feeDenom = feeDenom + b.feeDecimals = decimals + + return nil + } + + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + if err := fetcher(); err != nil { + b.logger.Error("failed to fetch fee", "err", err) + } + case <-b.ctx.Done(): + return + } + } } type AccMut struct { diff --git a/jsonrpc/backend/eth.go b/jsonrpc/backend/eth.go index ac76657..41ff3d4 100644 --- a/jsonrpc/backend/eth.go +++ b/jsonrpc/backend/eth.go @@ -25,17 +25,17 @@ func (b *JSONRPCBackend) GetBalance(address common.Address, blockNrOrHash rpc.Bl return nil, err } - feeDenom, decimals, err := b.feeDenomWithDecimals() - if err != nil { - return nil, err + // jsonrpc is not ready for querying + if b.feeDenom == "" { + return nil, errors.New("jsonrpc is not ready") } - balance, err := b.app.EVMKeeper.ERC20Keeper().GetBalance(queryCtx, sdk.AccAddress(address[:]), feeDenom) + balance, err := b.app.EVMKeeper.ERC20Keeper().GetBalance(queryCtx, sdk.AccAddress(address[:]), b.feeDenom) if err != nil { return nil, err } - return (*hexutil.Big)(types.ToEthersUint(decimals, balance.BigInt())), nil + return (*hexutil.Big)(types.ToEthersUint(b.feeDecimals, balance.BigInt())), nil } func (b *JSONRPCBackend) Call(args rpctypes.TransactionArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *rpctypes.StateOverride, blockOverrides *rpctypes.BlockOverrides) (hexutil.Bytes, error) { diff --git a/jsonrpc/backend/gas.go b/jsonrpc/backend/gas.go index 38d0fad..fe6e5e5 100644 --- a/jsonrpc/backend/gas.go +++ b/jsonrpc/backend/gas.go @@ -39,9 +39,9 @@ func (b *JSONRPCBackend) EstimateGas(args rpctypes.TransactionArgs, blockNrOrHas return hexutil.Uint64(0), err } - _, decimals, err := b.feeDenomWithDecimals() - if err != nil { - return hexutil.Uint64(0), err + // jsonrpc is not ready for querying + if b.feeDenom == "" { + return hexutil.Uint64(0), NewInternalError("jsonrpc is not ready") } sdkMsgs := []sdk.Msg{} @@ -49,14 +49,14 @@ func (b *JSONRPCBackend) EstimateGas(args rpctypes.TransactionArgs, blockNrOrHas sdkMsgs = append(sdkMsgs, &types.MsgCreate{ Sender: sender, Code: hexutil.Encode(args.GetData()), - Value: math.NewIntFromBigInt(types.FromEthersUnit(decimals, args.Value.ToInt())), + Value: math.NewIntFromBigInt(types.FromEthersUnit(b.feeDecimals, args.Value.ToInt())), }) } else { sdkMsgs = append(sdkMsgs, &types.MsgCall{ Sender: sender, ContractAddr: args.To.Hex(), Input: hexutil.Encode(args.GetData()), - Value: math.NewIntFromBigInt(types.FromEthersUnit(decimals, args.Value.ToInt())), + Value: math.NewIntFromBigInt(types.FromEthersUnit(b.feeDecimals, args.Value.ToInt())), }) } @@ -90,39 +90,6 @@ func (b *JSONRPCBackend) EstimateGas(args rpctypes.TransactionArgs, blockNrOrHas return hexutil.Uint64(gasInfo.GasUsed), nil } -func (b *JSONRPCBackend) feeDenom() (string, error) { - queryCtx, err := b.getQueryCtx() - if err != nil { - return "", err - } - - params, err := b.app.EVMKeeper.Params.Get(queryCtx) - if err != nil { - return "", err - } - - return params.FeeDenom, nil -} - -func (b *JSONRPCBackend) feeDenomWithDecimals() (string, uint8, error) { - feeDenom, err := b.feeDenom() - if err != nil { - return "", 0, err - } - - queryCtx, err := b.getQueryCtx() - if err != nil { - return "", 0, err - } - - decimals, err := b.app.EVMKeeper.ERC20Keeper().GetDecimals(queryCtx, feeDenom) - if err != nil { - return "", 0, err - } - - return feeDenom, decimals, nil -} - func (b *JSONRPCBackend) GasPrice() (*hexutil.Big, error) { queryCtx, err := b.getQueryCtx() if err != nil { @@ -134,17 +101,17 @@ func (b *JSONRPCBackend) GasPrice() (*hexutil.Big, error) { return nil, err } - feeDenom, decimals, err := b.feeDenomWithDecimals() - if err != nil { - return nil, err + // jsonrpc is not ready for querying + if b.feeDenom == "" { + return nil, NewInternalError("jsonrpc is not ready") } // multiply by 1e9 to prevent decimal drops - gasPrice := params.MinGasPrices.AmountOf(feeDenom). + gasPrice := params.MinGasPrices.AmountOf(b.feeDenom). MulTruncate(math.LegacyNewDec(1e9)). TruncateInt().BigInt() - return (*hexutil.Big)(types.ToEthersUint(decimals+9, gasPrice)), nil + return (*hexutil.Big)(types.ToEthersUint(b.feeDecimals+9, gasPrice)), nil } func (b *JSONRPCBackend) MaxPriorityFeePerGas() (*hexutil.Big, error) { diff --git a/jsonrpc/jsonrpc.go b/jsonrpc/jsonrpc.go index 88a7e9b..b33d036 100644 --- a/jsonrpc/jsonrpc.go +++ b/jsonrpc/jsonrpc.go @@ -64,7 +64,7 @@ func StartJSONRPC( rpcServer := rpc.NewServer() rpcServer.SetBatchLimits(jsonRPCConfig.BatchRequestLimit, jsonRPCConfig.BatchResponseMaxSize) - bkd, err := backend.NewJSONRPCBackend(app, logger, svrCtx, clientCtx, jsonRPCConfig) + bkd, err := backend.NewJSONRPCBackend(ctx, app, logger, svrCtx, clientCtx, jsonRPCConfig) if err != nil { return err } @@ -73,37 +73,37 @@ func StartJSONRPC( { Namespace: EthNamespace, Version: apiVersion, - Service: ethns.NewEthAPI(logger, bkd), + Service: ethns.NewEthAPI(ctx, logger, bkd), Public: true, }, { Namespace: EthNamespace, Version: apiVersion, - Service: filters.NewFilterAPI(app, bkd, logger), + Service: filters.NewFilterAPI(ctx, app, bkd, logger), Public: true, }, { Namespace: NetNamespace, Version: apiVersion, - Service: netns.NewNetAPI(logger, bkd), + Service: netns.NewNetAPI(ctx, logger, bkd), Public: true, }, { Namespace: Web3Namespace, Version: apiVersion, - Service: web3ns.NewWeb3API(logger, bkd), + Service: web3ns.NewWeb3API(ctx, logger, bkd), Public: true, }, { Namespace: TxPoolNamespace, Version: apiVersion, - Service: txpoolns.NewTxPoolAPI(logger, bkd), + Service: txpoolns.NewTxPoolAPI(ctx, logger, bkd), Public: true, }, { Namespace: CosmosNamespace, Version: apiVersion, - Service: cosmosns.NewCosmosAPI(logger, bkd), + Service: cosmosns.NewCosmosAPI(ctx, logger, bkd), Public: true, }, } diff --git a/jsonrpc/namespaces/cosmos/api.go b/jsonrpc/namespaces/cosmos/api.go index 5689814..ccc7abf 100644 --- a/jsonrpc/namespaces/cosmos/api.go +++ b/jsonrpc/namespaces/cosmos/api.go @@ -17,9 +17,9 @@ type CosmosAPI struct { } // NewCosmosAPI creates an instance of the public ETH Web3 API. -func NewCosmosAPI(logger log.Logger, backend *backend.JSONRPCBackend) *CosmosAPI { +func NewCosmosAPI(ctx context.Context, logger log.Logger, backend *backend.JSONRPCBackend) *CosmosAPI { api := &CosmosAPI{ - ctx: context.TODO(), + ctx: ctx, logger: logger.With("client", "json-rpc"), backend: backend, } diff --git a/jsonrpc/namespaces/eth/api.go b/jsonrpc/namespaces/eth/api.go index b5d3abc..549bdd5 100644 --- a/jsonrpc/namespaces/eth/api.go +++ b/jsonrpc/namespaces/eth/api.go @@ -107,9 +107,9 @@ type EthAPI struct { } // NewEthAPI creates an instance of the public ETH Web3 API. -func NewEthAPI(logger log.Logger, backend *backend.JSONRPCBackend) *EthAPI { +func NewEthAPI(ctx context.Context, logger log.Logger, backend *backend.JSONRPCBackend) *EthAPI { api := &EthAPI{ - ctx: context.TODO(), + ctx: ctx, logger: logger.With("client", "json-rpc"), backend: backend, } diff --git a/jsonrpc/namespaces/eth/filters/api.go b/jsonrpc/namespaces/eth/filters/api.go index 092c837..9332a06 100644 --- a/jsonrpc/namespaces/eth/filters/api.go +++ b/jsonrpc/namespaces/eth/filters/api.go @@ -42,6 +42,8 @@ type filter struct { // FilterAPI is the eth_ filter namespace API type FilterAPI struct { + ctx context.Context + app *app.MinitiaApp backend *backend.JSONRPCBackend @@ -63,9 +65,11 @@ type FilterAPI struct { } // NewFiltersAPI returns a new instance -func NewFilterAPI(app *app.MinitiaApp, backend *backend.JSONRPCBackend, logger log.Logger) *FilterAPI { +func NewFilterAPI(ctx context.Context, app *app.MinitiaApp, backend *backend.JSONRPCBackend, logger log.Logger) *FilterAPI { logger = logger.With("api", "filter") api := &FilterAPI{ + ctx: ctx, + app: app, backend: backend, @@ -98,23 +102,27 @@ func (api *FilterAPI) clearUnusedFilters() { var toUninstall []*subscription for { - <-ticker.C - api.filtersMut.Lock() - for id, f := range api.filters { - if time.Since(f.lastUsed) > timeout { - toUninstall = append(toUninstall, f.s) - delete(api.filters, id) + select { + case <-ticker.C: + api.filtersMut.Lock() + for id, f := range api.filters { + if time.Since(f.lastUsed) > timeout { + toUninstall = append(toUninstall, f.s) + delete(api.filters, id) + } } - } - api.filtersMut.Unlock() + api.filtersMut.Unlock() - // Unsubscribes are processed outside the lock to avoid the following scenario: - // event loop attempts broadcasting events to still active filters while - // Unsubscribe is waiting for it to process the uninstall request. - for _, s := range toUninstall { - api.uninstallSubscription(s) + // Unsubscribes are processed outside the lock to avoid the following scenario: + // event loop attempts broadcasting events to still active filters while + // Unsubscribe is waiting for it to process the uninstall request. + for _, s := range toUninstall { + api.uninstallSubscription(s) + } + toUninstall = nil + case <-api.ctx.Done(): + return } - toUninstall = nil } } @@ -155,6 +163,8 @@ func (api *FilterAPI) eventLoop() { case s := <-api.uninstall: delete(api.subscriptions, s.id) close(s.err) + case <-api.ctx.Done(): + return } } } diff --git a/jsonrpc/namespaces/net/api.go b/jsonrpc/namespaces/net/api.go index 23c8732..fc3b8ff 100644 --- a/jsonrpc/namespaces/net/api.go +++ b/jsonrpc/namespaces/net/api.go @@ -24,9 +24,9 @@ type NetAPI struct { } // NewNetAPI creates a new net API instance -func NewNetAPI(logger log.Logger, backend *backend.JSONRPCBackend) *NetAPI { +func NewNetAPI(ctx context.Context, logger log.Logger, backend *backend.JSONRPCBackend) *NetAPI { return &NetAPI{ - ctx: context.TODO(), + ctx: ctx, logger: logger, backend: backend, } diff --git a/jsonrpc/namespaces/txpool/api.go b/jsonrpc/namespaces/txpool/api.go index b64bb69..5cc78f8 100644 --- a/jsonrpc/namespaces/txpool/api.go +++ b/jsonrpc/namespaces/txpool/api.go @@ -32,9 +32,9 @@ type TxPoolAPI struct { } // NewTxPoolAPI creates a new txpool API instance. -func NewTxPoolAPI(logger log.Logger, backend *backend.JSONRPCBackend) *TxPoolAPI { +func NewTxPoolAPI(ctx context.Context, logger log.Logger, backend *backend.JSONRPCBackend) *TxPoolAPI { return &TxPoolAPI{ - ctx: context.TODO(), + ctx: ctx, logger: logger, backend: backend, } diff --git a/jsonrpc/namespaces/web3/api.go b/jsonrpc/namespaces/web3/api.go index 115e26a..cfb7f95 100644 --- a/jsonrpc/namespaces/web3/api.go +++ b/jsonrpc/namespaces/web3/api.go @@ -25,9 +25,9 @@ type Web3API struct { } // NewWeb3API creates a new net API instance -func NewWeb3API(logger log.Logger, backend *backend.JSONRPCBackend) *Web3API { +func NewWeb3API(ctx context.Context, logger log.Logger, backend *backend.JSONRPCBackend) *Web3API { return &Web3API{ - ctx: context.TODO(), + ctx: ctx, logger: logger, backend: backend, }