// Copyright 2021 Evmos Foundation // This file is part of Evmos' Ethermint library. // // The Ethermint library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The Ethermint library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the Ethermint library. If not, see https://github.com/evmos/ethermint/blob/main/LICENSE package backend import ( "fmt" "math/big" "strconv" rpctypes "github.com/cerc-io/laconicd/rpc/types" ethermint "github.com/cerc-io/laconicd/types" evmtypes "github.com/cerc-io/laconicd/x/evm/types" feemarkettypes "github.com/cerc-io/laconicd/x/feemarket/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" tmrpctypes "github.com/tendermint/tendermint/rpc/core/types" ) // ChainID is the EIP-155 replay-protection chain id for the current ethereum chain config. func (b *Backend) ChainID() (*hexutil.Big, error) { eip155ChainID, err := ethermint.ParseChainID(b.clientCtx.ChainID) if err != nil { panic(err) } // if current block is at or past the EIP-155 replay-protection fork block, return chainID from config bn, err := b.BlockNumber() if err != nil { b.logger.Debug("failed to fetch latest block number", "error", err.Error()) return (*hexutil.Big)(eip155ChainID), nil } if config := b.ChainConfig(); config.IsEIP155(new(big.Int).SetUint64(uint64(bn))) { return (*hexutil.Big)(config.ChainID), nil } return nil, fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block") } // ChainConfig returns the latest ethereum chain configuration func (b *Backend) ChainConfig() *params.ChainConfig { params, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{}) if err != nil { return nil } return params.Params.ChainConfig.EthereumConfig(b.chainID) } // GlobalMinGasPrice returns MinGasPrice param from FeeMarket func (b *Backend) GlobalMinGasPrice() (sdk.Dec, error) { res, err := b.queryClient.FeeMarket.Params(b.ctx, &feemarkettypes.QueryParamsRequest{}) if err != nil { return sdk.ZeroDec(), err } return res.Params.MinGasPrice, nil } // BaseFee returns the base fee tracked by the Fee Market module. // If the base fee is not enabled globally, the query returns nil. // If the London hard fork is not activated at the current height, the query will // return nil. func (b *Backend) BaseFee(blockRes *tmrpctypes.ResultBlockResults) (*big.Int, error) { // return BaseFee if London hard fork is activated and feemarket is enabled res, err := b.queryClient.BaseFee(rpctypes.ContextWithHeight(blockRes.Height), &evmtypes.QueryBaseFeeRequest{}) if err != nil || res.BaseFee == nil { // we can't tell if it's london HF not enabled or the state is pruned, // in either case, we'll fallback to parsing from begin blocker event, // faster to iterate reversely for i := len(blockRes.BeginBlockEvents) - 1; i >= 0; i-- { evt := blockRes.BeginBlockEvents[i] if evt.Type == feemarkettypes.EventTypeFeeMarket && len(evt.Attributes) > 0 { baseFee, err := strconv.ParseInt(string(evt.Attributes[0].Value), 10, 64) if err == nil { return big.NewInt(baseFee), nil } break } } return nil, err } if res.BaseFee == nil { return nil, nil } return res.BaseFee.BigInt(), nil } // CurrentHeader returns the latest block header func (b *Backend) CurrentHeader() *ethtypes.Header { header, _ := b.HeaderByNumber(rpctypes.EthLatestBlockNumber) return header } // PendingTransactions returns the transactions that are in the transaction pool // and have a from address that is one of the accounts this node manages. func (b *Backend) PendingTransactions() ([]*sdk.Tx, error) { res, err := b.clientCtx.Client.UnconfirmedTxs(b.ctx, nil) if err != nil { return nil, err } result := make([]*sdk.Tx, 0, len(res.Txs)) for _, txBz := range res.Txs { tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz) if err != nil { return nil, err } result = append(result, &tx) } return result, nil } // GetCoinbase is the address that staking rewards will be send to (alias for Etherbase). func (b *Backend) GetCoinbase() (sdk.AccAddress, error) { node, err := b.clientCtx.GetNode() if err != nil { return nil, err } status, err := node.Status(b.ctx) if err != nil { return nil, err } req := &evmtypes.QueryValidatorAccountRequest{ ConsAddress: sdk.ConsAddress(status.ValidatorInfo.Address).String(), } res, err := b.queryClient.ValidatorAccount(b.ctx, req) if err != nil { return nil, err } address, _ := sdk.AccAddressFromBech32(res.AccountAddress) return address, nil } // FeeHistory returns data relevant for fee estimation based on the specified range of blocks. func (b *Backend) FeeHistory( userBlockCount rpc.DecimalOrHex, // number blocks to fetch, maximum is 100 lastBlock rpc.BlockNumber, // the block to start search , to oldest rewardPercentiles []float64, // percentiles to fetch reward ) (*rpctypes.FeeHistoryResult, error) { blockEnd := int64(lastBlock) if blockEnd < 0 { blockNumber, err := b.BlockNumber() if err != nil { return nil, err } blockEnd = int64(blockNumber) } blocks := int64(userBlockCount) maxBlockCount := int64(b.cfg.JSONRPC.FeeHistoryCap) if blocks > maxBlockCount { return nil, fmt.Errorf("FeeHistory user block count %d higher than %d", blocks, maxBlockCount) } if blockEnd+1 < blocks { blocks = blockEnd + 1 } // Ensure not trying to retrieve before genesis. blockStart := blockEnd + 1 - blocks oldestBlock := (*hexutil.Big)(big.NewInt(blockStart)) // prepare space reward := make([][]*hexutil.Big, blocks) rewardCount := len(rewardPercentiles) for i := 0; i < int(blocks); i++ { reward[i] = make([]*hexutil.Big, rewardCount) } thisBaseFee := make([]*hexutil.Big, blocks+1) thisGasUsedRatio := make([]float64, blocks) // rewards should only be calculated if reward percentiles were included calculateRewards := rewardCount != 0 // fetch block for blockID := blockStart; blockID <= blockEnd; blockID++ { index := int32(blockID - blockStart) // tendermint block tendermintblock, err := b.TendermintBlockByNumber(rpctypes.BlockNumber(blockID)) if tendermintblock == nil { return nil, err } // eth block ethBlock, err := b.GetBlockByNumber(rpctypes.BlockNumber(blockID), true) if ethBlock == nil { return nil, err } // tendermint block result tendermintBlockResult, err := b.TendermintBlockResultByNumber(&tendermintblock.Block.Height) if tendermintBlockResult == nil { b.logger.Debug("block result not found", "height", tendermintblock.Block.Height, "error", err.Error()) return nil, err } oneFeeHistory := rpctypes.OneFeeHistory{} err = b.processBlock(tendermintblock, ðBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory) if err != nil { return nil, err } // copy thisBaseFee[index] = (*hexutil.Big)(oneFeeHistory.BaseFee) thisBaseFee[index+1] = (*hexutil.Big)(oneFeeHistory.NextBaseFee) thisGasUsedRatio[index] = oneFeeHistory.GasUsedRatio if calculateRewards { for j := 0; j < rewardCount; j++ { reward[index][j] = (*hexutil.Big)(oneFeeHistory.Reward[j]) if reward[index][j] == nil { reward[index][j] = (*hexutil.Big)(big.NewInt(0)) } } } } feeHistory := rpctypes.FeeHistoryResult{ OldestBlock: oldestBlock, BaseFee: thisBaseFee, GasUsedRatio: thisGasUsedRatio, } if calculateRewards { feeHistory.Reward = reward } return &feeHistory, nil } // SuggestGasTipCap returns the suggested tip cap // Although we don't support tx prioritization yet, but we return a positive value to help client to // mitigate the base fee changes. func (b *Backend) SuggestGasTipCap(baseFee *big.Int) (*big.Int, error) { if baseFee == nil { // london hardfork not enabled or feemarket not enabled return big.NewInt(0), nil } params, err := b.queryClient.FeeMarket.Params(b.ctx, &feemarkettypes.QueryParamsRequest{}) if err != nil { return nil, err } // calculate the maximum base fee delta in current block, assuming all block gas limit is consumed // ``` // GasTarget = GasLimit / ElasticityMultiplier // Delta = BaseFee * (GasUsed - GasTarget) / GasTarget / Denominator // ``` // The delta is at maximum when `GasUsed` is equal to `GasLimit`, which is: // ``` // MaxDelta = BaseFee * (GasLimit - GasLimit / ElasticityMultiplier) / (GasLimit / ElasticityMultiplier) / Denominator // = BaseFee * (ElasticityMultiplier - 1) / Denominator // ``` maxDelta := baseFee.Int64() * (int64(params.Params.ElasticityMultiplier) - 1) / int64(params.Params.BaseFeeChangeDenominator) if maxDelta < 0 { // impossible if the parameter validation passed. maxDelta = 0 } return big.NewInt(maxDelta), nil }