api: ethrpc: implement eth_feeHistory (#9539)

Co-authored-by: Raúl Kripalani <raul@protocol.ai>
This commit is contained in:
Kevin Li 2022-10-22 11:31:58 -04:00 committed by vyzo
parent c0cbcda1c2
commit 64afdfc642
8 changed files with 135 additions and 0 deletions

View File

@ -786,6 +786,7 @@ type FullNode interface {
NetListening(ctx context.Context) (bool, error) //perm:read
EthProtocolVersion(ctx context.Context) (EthUint64, error) //perm:read
EthGasPrice(ctx context.Context) (EthBigInt, error) //perm:read
EthFeeHistory(ctx context.Context, blkCount uint64, newestBlk string) (EthFeeHistory, error) //perm:read
EthMaxPriorityFeePerGas(ctx context.Context) (EthBigInt, error) //perm:read
EthEstimateGas(ctx context.Context, tx EthCall) (EthUint64, error) //perm:read

View File

@ -372,6 +372,8 @@ func init() {
ethhash, _ := api.EthHashFromCid(c)
addExample(&ethhash)
ethFeeHistoryReward := [][]api.EthBigInt{}
addExample(&ethFeeHistoryReward)
}
func GetAPIType(name, pkg string) (i interface{}, t reflect.Type, permStruct []reflect.Type) {

View File

@ -417,3 +417,10 @@ func (h EthHash) ToCid() cid.Cid {
return cid.NewCidV1(cid.DagCBOR, mh)
}
type EthFeeHistory struct {
OldestBlock uint64 `json:"oldestBlock"`
BaseFeePerGas []EthBigInt `json:"baseFeePerGas"`
GasUsedRatio []float64 `json:"gasUsedRatio"`
Reward *[][]EthBigInt `json:"reward,omitempty"`
}

View File

@ -996,6 +996,21 @@ func (mr *MockFullNodeMockRecorder) EthEstimateGas(arg0, arg1 interface{}) *gomo
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthEstimateGas", reflect.TypeOf((*MockFullNode)(nil).EthEstimateGas), arg0, arg1)
}
// EthFeeHistory mocks base method.
func (m *MockFullNode) EthFeeHistory(arg0 context.Context, arg1 uint64, arg2 string) (api.EthFeeHistory, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EthFeeHistory", arg0, arg1, arg2)
ret0, _ := ret[0].(api.EthFeeHistory)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// EthFeeHistory indicates an expected call of EthFeeHistory.
func (mr *MockFullNodeMockRecorder) EthFeeHistory(arg0, arg1, arg2 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthFeeHistory", reflect.TypeOf((*MockFullNode)(nil).EthFeeHistory), arg0, arg1, arg2)
}
// EthGasPrice mocks base method.
func (m *MockFullNode) EthGasPrice(arg0 context.Context) (api.EthBigInt, error) {
m.ctrl.T.Helper()

View File

@ -230,6 +230,8 @@ type FullNodeStruct struct {
EthEstimateGas func(p0 context.Context, p1 EthCall) (EthUint64, error) `perm:"read"`
EthFeeHistory func(p0 context.Context, p1 uint64, p2 string) (EthFeeHistory, error) `perm:"read"`
EthGasPrice func(p0 context.Context) (EthBigInt, error) `perm:"read"`
EthGetBalance func(p0 context.Context, p1 EthAddress, p2 string) (EthBigInt, error) `perm:"read"`
@ -1894,6 +1896,17 @@ func (s *FullNodeStub) EthEstimateGas(p0 context.Context, p1 EthCall) (EthUint64
return *new(EthUint64), ErrNotSupported
}
func (s *FullNodeStruct) EthFeeHistory(p0 context.Context, p1 uint64, p2 string) (EthFeeHistory, error) {
if s.Internal.EthFeeHistory == nil {
return *new(EthFeeHistory), ErrNotSupported
}
return s.Internal.EthFeeHistory(p0, p1, p2)
}
func (s *FullNodeStub) EthFeeHistory(p0 context.Context, p1 uint64, p2 string) (EthFeeHistory, error) {
return *new(EthFeeHistory), ErrNotSupported
}
func (s *FullNodeStruct) EthGasPrice(p0 context.Context) (EthBigInt, error) {
if s.Internal.EthGasPrice == nil {
return *new(EthBigInt), ErrNotSupported

View File

@ -71,6 +71,7 @@
* [EthCall](#EthCall)
* [EthChainId](#EthChainId)
* [EthEstimateGas](#EthEstimateGas)
* [EthFeeHistory](#EthFeeHistory)
* [EthGasPrice](#EthGasPrice)
* [EthGetBalance](#EthGetBalance)
* [EthGetBlockByHash](#EthGetBlockByHash)
@ -2228,6 +2229,33 @@ Inputs:
Response: `"0x5"`
### EthFeeHistory
Perms: read
Inputs:
```json
[
42,
"string value"
]
```
Response:
```json
{
"oldestBlock": 42,
"baseFeePerGas": [
"0x0"
],
"gasUsedRatio": [
12.3
],
"reward": []
}
```
### EthGasPrice

View File

@ -45,6 +45,7 @@ type EthModuleAPI interface {
EthGetCode(ctx context.Context, address api.EthAddress, blkOpt string) (api.EthBytes, error)
EthGetStorageAt(ctx context.Context, address api.EthAddress, position api.EthBytes, blkParam string) (api.EthBytes, error)
EthGetBalance(ctx context.Context, address api.EthAddress, blkParam string) (api.EthBigInt, error)
EthFeeHistory(ctx context.Context, blkCount uint64, newestBlk string) (api.EthFeeHistory, error)
EthChainId(ctx context.Context) (api.EthUint64, error)
NetVersion(ctx context.Context) (string, error)
NetListening(ctx context.Context) (bool, error)
@ -375,6 +376,73 @@ func (a *EthModule) EthChainId(ctx context.Context) (api.EthUint64, error) {
return api.EthUint64(build.Eip155ChainId), nil
}
func (a *EthModule) EthFeeHistory(ctx context.Context, blkCount uint64, newestBlkNum string) (api.EthFeeHistory, error) {
if blkCount > 1024 {
return api.EthFeeHistory{}, fmt.Errorf("block count should be smaller than 1024")
}
newestBlkHeight := uint64(a.Chain.GetHeaviestTipSet().Height())
// TODO https://github.com/filecoin-project/ref-fvm/issues/1016
var blkNum api.EthUint64
err := blkNum.UnmarshalJSON([]byte(`"` + newestBlkNum + `"`))
if err == nil && uint64(blkNum) < newestBlkHeight {
newestBlkHeight = uint64(blkNum)
}
// Deal with the case that the chain is shorter than the number of
// requested blocks.
oldestBlkHeight := uint64(1)
if blkCount <= newestBlkHeight {
oldestBlkHeight = newestBlkHeight - blkCount + 1
}
ts, err := a.Chain.GetTipsetByHeight(ctx, abi.ChainEpoch(newestBlkHeight), nil, false)
if err != nil {
return api.EthFeeHistory{}, fmt.Errorf("cannot load find block height: %v", newestBlkHeight)
}
// FIXME: baseFeePerGas should include the next block after the newest of the returned range, because this
// can be inferred from the newest block. we use the newest block's baseFeePerGas for now but need to fix it
// In other words, due to deferred execution, we might not be returning the most useful value here for the client.
baseFeeArray := []api.EthBigInt{api.EthBigInt(ts.Blocks()[0].ParentBaseFee)}
gasUsedRatioArray := []float64{}
for ts.Height() >= abi.ChainEpoch(oldestBlkHeight) {
// Unfortunately we need to rebuild the full message view so we can
// totalize gas used in the tipset.
block, err := a.ethBlockFromFilecoinTipSet(ctx, ts, false)
if err != nil {
return api.EthFeeHistory{}, fmt.Errorf("cannot create eth block: %v", err)
}
// both arrays should be reversed at the end
baseFeeArray = append(baseFeeArray, api.EthBigInt(ts.Blocks()[0].ParentBaseFee))
gasUsedRatioArray = append(gasUsedRatioArray, float64(block.GasUsed)/float64(build.BlockGasLimit))
parentTsKey := ts.Parents()
ts, err = a.Chain.LoadTipSet(ctx, parentTsKey)
if err != nil {
return api.EthFeeHistory{}, fmt.Errorf("cannot load tipset key: %v", parentTsKey)
}
}
// Reverse the arrays; we collected them newest to oldest; the client expects oldest to newest.
for i, j := 0, len(baseFeeArray)-1; i < j; i, j = i+1, j-1 {
baseFeeArray[i], baseFeeArray[j] = baseFeeArray[j], baseFeeArray[i]
}
for i, j := 0, len(gasUsedRatioArray)-1; i < j; i, j = i+1, j-1 {
gasUsedRatioArray[i], gasUsedRatioArray[j] = gasUsedRatioArray[j], gasUsedRatioArray[i]
}
return api.EthFeeHistory{
OldestBlock: oldestBlkHeight,
BaseFeePerGas: baseFeeArray,
GasUsedRatio: gasUsedRatioArray,
}, nil
}
func (a *EthModule) NetVersion(ctx context.Context) (string, error) {
// Note that networkId is not encoded in hex
nv, err := a.StateNetworkVersion(ctx, types.EmptyTSK)

View File

@ -91,6 +91,7 @@ func FullNodeHandler(a v1api.FullNode, permissioned bool, opts ...jsonrpc.Server
rpcServer.AliasMethod("eth_getStorageAt", "Filecoin.EthGetStorageAt")
rpcServer.AliasMethod("eth_getBalance", "Filecoin.EthGetBalance")
rpcServer.AliasMethod("eth_chainId", "Filecoin.EthChainId")
rpcServer.AliasMethod("eth_feeHistory", "Filecoin.EthFeeHistory")
rpcServer.AliasMethod("eth_protocolVersion", "Filecoin.EthProtocolVersion")
rpcServer.AliasMethod("eth_maxPriorityFeePerGas", "Filecoin.EthMaxPriorityFeePerGas")
rpcServer.AliasMethod("eth_gasPrice", "Filecoin.EthGasPrice")