diff --git a/api/api_ethereum.go b/api/api_ethereum.go index 0184898150..0b58788ed7 100644 --- a/api/api_ethereum.go +++ b/api/api_ethereum.go @@ -23,6 +23,7 @@ import ( "errors" "fmt" "math/big" + "strconv" "strings" "sync/atomic" "time" @@ -314,7 +315,7 @@ func (api *EthereumAPI) MaxPriorityFeePerGas(ctx context.Context) (*hexutil.Big, return api.publicKlayAPI.GasPrice(ctx) } -type feeHistoryResult struct { +type FeeHistoryResult struct { OldestBlock *hexutil.Big `json:"oldestBlock"` Reward [][]*hexutil.Big `json:"reward,omitempty"` BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"` @@ -324,9 +325,49 @@ type feeHistoryResult struct { // DecimalOrHex unmarshals a non-negative decimal or hex parameter into a uint64. type DecimalOrHex uint64 -func (api *EthereumAPI) FeeHistory(ctx context.Context, blockCount DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*feeHistoryResult, error) { - // TODO-Klaytn: Not implemented yet. - return nil, nil +// UnmarshalJSON implements json.Unmarshaler. +func (dh *DecimalOrHex) UnmarshalJSON(data []byte) error { + input := strings.TrimSpace(string(data)) + if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' { + input = input[1 : len(input)-1] + } + + value, err := strconv.ParseUint(input, 10, 64) + if err != nil { + value, err = hexutil.DecodeUint64(input) + } + if err != nil { + return err + } + *dh = DecimalOrHex(value) + return nil +} + +func (api *EthereumAPI) FeeHistory(ctx context.Context, blockCount DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*FeeHistoryResult, error) { + oldest, reward, baseFee, gasUsed, err := api.publicKlayAPI.b.FeeHistory(ctx, int(blockCount), lastBlock, rewardPercentiles) + if err != nil { + return nil, err + } + results := &FeeHistoryResult{ + OldestBlock: (*hexutil.Big)(oldest), + GasUsedRatio: gasUsed, + } + if reward != nil { + results.Reward = make([][]*hexutil.Big, len(reward)) + for i, w := range reward { + results.Reward[i] = make([]*hexutil.Big, len(w)) + for j, v := range w { + results.Reward[i][j] = (*hexutil.Big)(v) + } + } + } + if baseFee != nil { + results.BaseFee = make([]*hexutil.Big, len(baseFee)) + for i, v := range baseFee { + results.BaseFee[i] = (*hexutil.Big)(v) + } + } + return results, nil } // Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not @@ -1310,7 +1351,7 @@ func EthDoCall(ctx context.Context, b Backend, args EthTransactionArgs, blockNrO // TODO-Klaytn: Klaytn is using fixed baseFee as now but, if we change this fixed baseFee as dynamic baseFee, we should update this logic too. fixedBaseFee := new(big.Int).SetUint64(params.BaseFee) - intrinsicGas, err := types.IntrinsicGas(args.data(), args.To == nil, b.ChainConfig().Rules(header.Number)) + intrinsicGas, err := types.IntrinsicGas(args.data(), nil, args.To == nil, b.ChainConfig().Rules(header.Number)) if err != nil { return nil, 0, err } diff --git a/api/backend.go b/api/backend.go index f478de3639..000de1b49d 100644 --- a/api/backend.go +++ b/api/backend.go @@ -51,6 +51,7 @@ type Backend interface { AccountManager() accounts.AccountManager RPCGasCap() *big.Int // global gas cap for klay_call over rpc: DoS protection Engine() consensus.Engine + FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) // BlockChain API SetHead(number uint64) diff --git a/api/mocks/backend_mock.go b/api/mocks/backend_mock.go index 28f2a3d149..0946b1135c 100644 --- a/api/mocks/backend_mock.go +++ b/api/mocks/backend_mock.go @@ -176,6 +176,24 @@ func (mr *MockBackendMockRecorder) EventMux() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EventMux", reflect.TypeOf((*MockBackend)(nil).EventMux)) } +// FeeHistory mocks base method. +func (m *MockBackend) FeeHistory(arg0 context.Context, arg1 int, arg2 rpc.BlockNumber, arg3 []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FeeHistory", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(*big.Int) + ret1, _ := ret[1].([][]*big.Int) + ret2, _ := ret[2].([]*big.Int) + ret3, _ := ret[3].([]float64) + ret4, _ := ret[4].(error) + return ret0, ret1, ret2, ret3, ret4 +} + +// FeeHistory indicates an expected call of FeeHistory. +func (mr *MockBackendMockRecorder) FeeHistory(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FeeHistory", reflect.TypeOf((*MockBackend)(nil).FeeHistory), arg0, arg1, arg2, arg3) +} + // GetBlockReceipts mocks base method. func (m *MockBackend) GetBlockReceipts(arg0 context.Context, arg1 common.Hash) types.Receipts { m.ctrl.T.Helper() diff --git a/log/log_modules.go b/log/log_modules.go index 634ee38678..75347ed90c 100644 --- a/log/log_modules.go +++ b/log/log_modules.go @@ -124,6 +124,7 @@ const ( ChainDataFetcher KAS FORK + NodeCnGasPrice // ModuleNameLen should be placed at the end of the list. ModuleNameLen @@ -199,4 +200,5 @@ var moduleNames = [ModuleNameLen]string{ "datasync/chaindatafetcher", "kas", "fork", + "node/cn/gasprice", } diff --git a/node/cn/api_backend.go b/node/cn/api_backend.go index 007c0192cc..575baa8b40 100644 --- a/node/cn/api_backend.go +++ b/node/cn/api_backend.go @@ -318,3 +318,7 @@ func (b *CNAPIBackend) RPCGasCap() *big.Int { func (b *CNAPIBackend) Engine() consensus.Engine { return b.cn.engine } + +func (b *CNAPIBackend) FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { + return b.gpo.FeeHistory(ctx, blockCount, lastBlock, rewardPercentiles) +} diff --git a/node/cn/config.go b/node/cn/config.go index 38b21deb01..df3c218569 100644 --- a/node/cn/config.go +++ b/node/cn/config.go @@ -57,8 +57,10 @@ func GetDefaultConfig() *Config { TxPool: blockchain.DefaultTxPoolConfig, GPO: gasprice.Config{ - Blocks: 20, - Percentile: 60, + Blocks: 20, + Percentile: 60, + MaxHeaderHistory: 1024, + MaxBlockHistory: 1024, }, WsEndpoint: "localhost:8546", diff --git a/node/cn/gasprice/feehistory.go b/node/cn/gasprice/feehistory.go new file mode 100644 index 0000000000..dec105d32e --- /dev/null +++ b/node/cn/gasprice/feehistory.go @@ -0,0 +1,267 @@ +// Modifications Copyright 2022 The Klaytn Authors +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see . +// +// This file is derived from eth/gasprice/feehistory.go (2021/11/09). +// Modified and improved for the klaytn development. + +package gasprice + +import ( + "context" + "errors" + "fmt" + "math/big" + "sort" + "sync/atomic" + + "github.com/klaytn/klaytn/blockchain/types" + "github.com/klaytn/klaytn/common" + "github.com/klaytn/klaytn/log" + "github.com/klaytn/klaytn/networks/rpc" + "github.com/klaytn/klaytn/params" +) + +var ( + logger = log.NewModuleLogger(log.NodeCnGasPrice) + errInvalidPercentile = errors.New("invalid reward percentile") + errRequestBeyondHead = errors.New("request beyond head block") +) + +const ( + // maxBlockFetchers is the max number of goroutines to spin up to pull blocks + // for the fee history calculation. + maxBlockFetchers = 1 +) + +// blockFees represents a single block for processing +type blockFees struct { + // set by the caller + blockNumber uint64 + header *types.Header + block *types.Block // only set if reward percentiles are requested + receipts types.Receipts + // filled by processBlock + results processedFees + err error +} + +// processedFees contains the results of a processed block and is also used for caching +type processedFees struct { + reward []*big.Int + baseFee, nextBaseFee *big.Int + gasUsedRatio float64 +} + +// txGasAndReward is sorted in ascending order based on reward +type ( + txGasAndReward struct { + gasUsed uint64 + reward *big.Int + } + sortGasAndReward []txGasAndReward +) + +func (s sortGasAndReward) Len() int { return len(s) } +func (s sortGasAndReward) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} +func (s sortGasAndReward) Less(i, j int) bool { + return s[i].reward.Cmp(s[j].reward) < 0 +} + +// processBlock takes a blockFees structure with the blockNumber, the header and optionally +// the block field filled in, retrieves the block from the backend if not present yet and +// fills in the rest of the fields. +func (oracle *Oracle) processBlock(bf *blockFees, percentiles []float64) { + chainconfig := oracle.backend.ChainConfig() + // TODO-Klaytn: If we implement baseFee feature like Ethereum does, we should set it from header, not constant. + bf.results.baseFee = new(big.Int).SetUint64(params.BaseFee) + // TODO-Klaytn: If we implement baseFee feature like Ethereum does, we should calculate nextBaseFee from parent block header. + bf.results.nextBaseFee = new(big.Int).SetUint64(params.BaseFee) + // There is no GasLimit in Klaytn, so it is enough to use pre-defined constant in api package as now. + bf.results.gasUsedRatio = float64(bf.header.GasUsed) / float64(params.UpperGasLimit) + if len(percentiles) == 0 { + // rewards were not requested, return null + return + } + if bf.block == nil || (bf.receipts == nil && len(bf.block.Transactions()) != 0) { + logger.Error("Block or receipts are missing while reward percentiles are requested") + return + } + + bf.results.reward = make([]*big.Int, len(percentiles)) + if len(bf.block.Transactions()) == 0 { + // return an all zero row if there are no transactions to gather data from + for i := range bf.results.reward { + bf.results.reward[i] = new(big.Int) + } + return + } + + sorter := make(sortGasAndReward, len(bf.block.Transactions())) + for i := range bf.block.Transactions() { + // TODO-Klaytn: If we change the fixed unit price policy and add baseFee feature, we should re-calculate reward. + reward := new(big.Int).SetUint64(chainconfig.UnitPrice) // As now, reward is always same because the baseFee in Klaytn is 0 and gasPrice is fixed. + sorter[i] = txGasAndReward{gasUsed: bf.receipts[i].GasUsed, reward: reward} + } + sort.Sort(sorter) + + var txIndex int + sumGasUsed := sorter[0].gasUsed + + for i, p := range percentiles { + thresholdGasUsed := uint64(float64(bf.block.GasUsed()) * p / 100) + for sumGasUsed < thresholdGasUsed && txIndex < len(bf.block.Transactions())-1 { + txIndex++ + sumGasUsed += sorter[txIndex].gasUsed + } + bf.results.reward[i] = sorter[txIndex].reward + } +} + +// resolveBlockRange resolves the specified block range to absolute block numbers while also +// enforcing backend specific limitations. +// Pending block does not exist in Klaytn, so there is no logic to look up pending blocks. +// This part has a different implementation with Ethereum. +// Note: an error is only returned if retrieving the head header has failed. If there are no +// retrievable blocks in the specified range then zero block count is returned with no error. +func (oracle *Oracle) resolveBlockRange(ctx context.Context, lastBlock rpc.BlockNumber, blocks int) (uint64, int, error) { + var headBlock rpc.BlockNumber + // query either pending block or head header and set headBlock + if lastBlock == rpc.PendingBlockNumber { + // pending block not supported by backend, process until latest block + lastBlock = rpc.LatestBlockNumber + blocks-- + if blocks == 0 { + return 0, 0, nil + } + } + // if pending block is not fetched then we retrieve the head header to get the head block number + if latestHeader, err := oracle.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber); err == nil { + headBlock = rpc.BlockNumber(latestHeader.Number.Uint64()) + } else { + return 0, 0, err + } + if lastBlock == rpc.LatestBlockNumber { + lastBlock = headBlock + } else if lastBlock > headBlock { + return 0, 0, fmt.Errorf("%w: requested %d, head %d", errRequestBeyondHead, lastBlock, headBlock) + } + // ensure not trying to retrieve before genesis + if rpc.BlockNumber(blocks) > lastBlock+1 { + blocks = int(lastBlock + 1) + } + return uint64(lastBlock), blocks, nil +} + +// FeeHistory returns data relevant for fee estimation based on the specified range of blocks. +// The range can be specified either with absolute block numbers or ending with the latest +// or pending block. Backends may or may not support gathering data from the pending block +// or blocks older than a certain age (specified in maxHistory). The first block of the +// actually processed range is returned to avoid ambiguity when parts of the requested range +// are not available or when the head has changed during processing this request. +// Three arrays are returned based on the processed blocks: +// - reward: the requested percentiles of effective priority fees per gas of transactions in each +// block, sorted in ascending order and weighted by gas used. +// - baseFee: base fee per gas in the given block +// - gasUsedRatio: gasUsed/gasLimit in the given block +// Note: baseFee includes the next block after the newest of the returned range, because this +// value can be derived from the newest block. +func (oracle *Oracle) FeeHistory( + ctx context.Context, blocks int, + unresolvedLastBlock rpc.BlockNumber, + rewardPercentiles []float64, +) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { + if blocks < 1 { + return common.Big0, nil, nil, nil, nil // returning with no data and no error means there are no retrievable blocks + } + maxFeeHistory := oracle.maxHeaderHistory + if len(rewardPercentiles) != 0 { + maxFeeHistory = oracle.maxBlockHistory + } + if blocks > maxFeeHistory { + logger.Warn("Sanitizing fee history length", "requested", blocks, "truncated", maxFeeHistory) + blocks = maxFeeHistory + } + for i, p := range rewardPercentiles { + if p < 0 || p > 100 { + return common.Big0, nil, nil, nil, fmt.Errorf("%w: %f", errInvalidPercentile, p) + } + if i > 0 && p < rewardPercentiles[i-1] { + return common.Big0, nil, nil, nil, fmt.Errorf("%w: #%d:%f > #%d:%f", errInvalidPercentile, i-1, rewardPercentiles[i-1], i, p) + } + } + var err error + lastBlock, blocks, err := oracle.resolveBlockRange(ctx, unresolvedLastBlock, blocks) + if err != nil || blocks == 0 { + return common.Big0, nil, nil, nil, err + } + oldestBlock := lastBlock + 1 - uint64(blocks) + + var ( + next = oldestBlock + results = make(chan *blockFees, blocks) + ) + for i := 0; i < maxBlockFetchers && i < blocks; i++ { + go func() { + for { + // Retrieve the next block number to fetch with this goroutine + blockNumber := atomic.AddUint64(&next, 1) - 1 + if blockNumber > lastBlock { + return + } + + fees := &blockFees{blockNumber: blockNumber} + if len(rewardPercentiles) != 0 { + fees.block, fees.err = oracle.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNumber)) + if fees.block != nil && fees.err == nil { + fees.receipts = oracle.backend.GetBlockReceipts(ctx, fees.block.Hash()) + fees.header = fees.block.Header() + } + } else { + fees.header, fees.err = oracle.backend.HeaderByNumber(ctx, rpc.BlockNumber(blockNumber)) + } + if fees.header != nil && fees.err == nil { + oracle.processBlock(fees, rewardPercentiles) + } + // send to results even if empty to guarantee that blocks items are sent in total + results <- fees + } + }() + } + var ( + reward = make([][]*big.Int, blocks) + baseFee = make([]*big.Int, blocks+1) + gasUsedRatio = make([]float64, blocks) + blockCount = blocks + ) + for ; blocks > 0; blocks-- { + fees := <-results + if fees.err != nil { + return common.Big0, nil, nil, nil, fees.err + } + i := int(fees.blockNumber - oldestBlock) + reward[i], baseFee[i], baseFee[i+1], gasUsedRatio[i] = fees.results.reward, fees.results.baseFee, fees.results.nextBaseFee, fees.results.gasUsedRatio + } + if len(rewardPercentiles) != 0 { + reward = reward[:blockCount] + } else { + reward = nil + } + baseFee, gasUsedRatio = baseFee[:blockCount+1], gasUsedRatio[:blockCount] + return new(big.Int).SetUint64(oldestBlock), reward, baseFee, gasUsedRatio, nil +} diff --git a/node/cn/gasprice/feehistory_test.go b/node/cn/gasprice/feehistory_test.go new file mode 100644 index 0000000000..f00bb387c9 --- /dev/null +++ b/node/cn/gasprice/feehistory_test.go @@ -0,0 +1,89 @@ +// Modifications Copyright 2022 The Klaytn Authors +// Copyright 2021 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see . +// +// This file is derived from eth/gasprice/feehistory.go (2021/11/09). +// Modified and improved for the klaytn development + +package gasprice + +import ( + "context" + "errors" + "testing" + + "github.com/klaytn/klaytn/networks/rpc" +) + +func TestFeeHistory(t *testing.T) { + var cases = []struct { + maxHeader, maxBlock int + count int + last rpc.BlockNumber + percent []float64 + expFirst uint64 + expCount int + expErr error + }{ + {1000, 1000, 10, 30, nil, 21, 10, nil}, + {1000, 1000, 10, 30, []float64{0, 10}, 21, 10, nil}, + {1000, 1000, 10, 30, []float64{20, 10}, 0, 0, errInvalidPercentile}, + {1000, 1000, 1000000000, 30, nil, 0, 31, nil}, + {1000, 1000, 1000000000, rpc.LatestBlockNumber, nil, 0, 33, nil}, + {1000, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead}, + {1000, 1000, 10, 40, nil, 0, 0, errRequestBeyondHead}, + {20, 2, 100, rpc.LatestBlockNumber, nil, 13, 20, nil}, + {20, 2, 100, rpc.LatestBlockNumber, []float64{0, 10}, 31, 2, nil}, + {20, 2, 100, 32, []float64{0, 10}, 31, 2, nil}, + {1000, 1000, 1, rpc.PendingBlockNumber, nil, 0, 0, nil}, + {1000, 1000, 2, rpc.PendingBlockNumber, nil, 32, 1, nil}, + } + for i, c := range cases { + config := Config{ + MaxHeaderHistory: c.maxHeader, + MaxBlockHistory: c.maxBlock, + } + backend := newTestBackend(t) + oracle := NewOracle(backend, config) + + first, reward, baseFee, ratio, err := oracle.FeeHistory(context.Background(), c.count, c.last, c.percent) + + expReward := c.expCount + if len(c.percent) == 0 { + expReward = 0 + } + expBaseFee := c.expCount + if expBaseFee != 0 { + expBaseFee++ + } + + if first.Uint64() != c.expFirst { + t.Fatalf("Test case %d: first block mismatch, want %d, got %d", i, c.expFirst, first) + } + if len(reward) != expReward { + t.Fatalf("Test case %d: reward array length mismatch, want %d, got %d", i, expReward, len(reward)) + } + if len(baseFee) != expBaseFee { + t.Fatalf("Test case %d: baseFee array length mismatch, want %d, got %d", i, expBaseFee, len(baseFee)) + } + if len(ratio) != c.expCount { + t.Fatalf("Test case %d: gasUsedRatio array length mismatch, want %d, got %d", i, c.expCount, len(ratio)) + } + if err != c.expErr && !errors.Is(err, c.expErr) { + t.Fatalf("Test case %d: error mismatch, want %v, got %v", i, c.expErr, err) + } + } +} diff --git a/node/cn/gasprice/gasprice.go b/node/cn/gasprice/gasprice.go index cc67211ae3..1e25696695 100644 --- a/node/cn/gasprice/gasprice.go +++ b/node/cn/gasprice/gasprice.go @@ -25,7 +25,9 @@ import ( "math/big" "sync" - "github.com/klaytn/klaytn/api" + "github.com/klaytn/klaytn/blockchain/types" + "github.com/klaytn/klaytn/networks/rpc" + "github.com/klaytn/klaytn/common" "github.com/klaytn/klaytn/params" ) @@ -33,26 +35,37 @@ import ( var maxPrice = big.NewInt(500 * params.Ston) type Config struct { - Blocks int - Percentile int - Default *big.Int `toml:",omitempty"` + Blocks int + Percentile int + MaxHeaderHistory int + MaxBlockHistory int + Default *big.Int `toml:",omitempty"` +} + +// OracleBackend includes all necessary background APIs for oracle. +type OracleBackend interface { + HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) + BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) + GetBlockReceipts(ctx context.Context, hash common.Hash) types.Receipts + ChainConfig() *params.ChainConfig } // Oracle recommends gas prices based on the content of recent // blocks. Suitable for both light and full clients. type Oracle struct { - backend api.Backend + backend OracleBackend lastHead common.Hash lastPrice *big.Int cacheLock sync.RWMutex fetchLock sync.Mutex - checkBlocks, maxEmpty, maxBlocks int - percentile int + checkBlocks, maxEmpty, maxBlocks int + percentile int + maxHeaderHistory, maxBlockHistory int } // NewOracle returns a new oracle. -func NewOracle(backend api.Backend, params Config) *Oracle { +func NewOracle(backend OracleBackend, params Config) *Oracle { blocks := params.Blocks if blocks < 1 { blocks = 1 @@ -65,12 +78,14 @@ func NewOracle(backend api.Backend, params Config) *Oracle { percent = 100 } return &Oracle{ - backend: backend, - lastPrice: params.Default, - checkBlocks: blocks, - maxEmpty: blocks / 2, - maxBlocks: blocks * 5, - percentile: percent, + backend: backend, + lastPrice: params.Default, + checkBlocks: blocks, + maxEmpty: blocks / 2, + maxBlocks: blocks * 5, + percentile: percent, + maxHeaderHistory: params.MaxHeaderHistory, + maxBlockHistory: params.MaxBlockHistory, } } diff --git a/node/cn/gasprice/gasprice_test.go b/node/cn/gasprice/gasprice_test.go index c1a199f9cf..34939511f1 100644 --- a/node/cn/gasprice/gasprice_test.go +++ b/node/cn/gasprice/gasprice_test.go @@ -17,14 +17,92 @@ package gasprice import ( + "context" "math/big" "testing" + "github.com/klaytn/klaytn/blockchain" + "github.com/klaytn/klaytn/blockchain/types" + "github.com/klaytn/klaytn/blockchain/vm" + "github.com/klaytn/klaytn/common" + "github.com/klaytn/klaytn/common/math" + "github.com/klaytn/klaytn/consensus/gxhash" + "github.com/klaytn/klaytn/crypto" + "github.com/klaytn/klaytn/networks/rpc" + "github.com/klaytn/klaytn/params" + "github.com/klaytn/klaytn/storage/database" + "github.com/golang/mock/gomock" mock_api "github.com/klaytn/klaytn/api/mocks" "github.com/stretchr/testify/assert" ) +const testHead = 32 + +type testBackend struct { + chain *blockchain.BlockChain +} + +func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + if number > testHead { + return nil, nil + } + if number == rpc.LatestBlockNumber { + number = testHead + } + return b.chain.GetHeaderByNumber(uint64(number)), nil +} + +func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + if number > testHead { + return nil, nil + } + if number == rpc.LatestBlockNumber { + number = testHead + } + return b.chain.GetBlockByNumber(uint64(number)), nil +} + +func (b *testBackend) GetBlockReceipts(ctx context.Context, hash common.Hash) types.Receipts { + return b.chain.GetReceiptsByBlockHash(hash) +} + +func (b *testBackend) ChainConfig() *params.ChainConfig { + return b.chain.Config() +} + +func newTestBackend(t *testing.T) *testBackend { + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr = crypto.PubkeyToAddress(key.PublicKey) + + gspec = &blockchain.Genesis{ + Config: params.TestChainConfig, + Alloc: blockchain.GenesisAlloc{addr: {Balance: big.NewInt(math.MaxInt64)}}, + } + db = database.NewMemoryDBManager() + genesis = gspec.MustCommit(db) + ) + // Generate testing blocks + blocks, _ := blockchain.GenerateChain(gspec.Config, genesis, gxhash.NewFaker(), db, testHead+1, func(i int, b *blockchain.BlockGen) { + b.SetRewardbase(addr) + + toaddr := common.Address{} + data := make([]byte, 1) + gas, _ := types.IntrinsicGas(data, nil, false, params.TestChainConfig.Rules(big.NewInt(0))) + signer := types.NewEIP155Signer(params.TestChainConfig.ChainID) + tx, _ := types.SignTx(types.NewTransaction(b.TxNonce(addr), toaddr, big.NewInt(1), gas, nil, data), signer, key) + b.AddTx(tx) + }) + // Construct testing chain + chain, err := blockchain.NewBlockChain(db, nil, gspec.Config, gxhash.NewFaker(), vm.Config{}) + if err != nil { + t.Fatalf("Failed to create local chain, %v", err) + } + chain.InsertChain(blocks) + return &testBackend{chain: chain} +} + func TestGasPrice_NewOracle(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish()