eth_feeHistory: migrate to using TipSetState.

This commit is contained in:
Raúl Kripalani 2023-03-11 17:24:56 +00:00
parent d1c6ab7dc6
commit f7a979d825
4 changed files with 79 additions and 74 deletions

View File

@ -44,14 +44,6 @@ type EthTx struct {
S EthBigInt `json:"s"` S EthBigInt `json:"s"`
} }
func (tx *EthTx) Reward(blkBaseFee big.Int) EthBigInt {
availablePriorityFee := big.Sub(big.Int(tx.MaxFeePerGas), blkBaseFee)
if big.Cmp(big.Int(tx.MaxPriorityFeePerGas), availablePriorityFee) <= 0 {
return tx.MaxPriorityFeePerGas
}
return EthBigInt(availablePriorityFee)
}
type EthTxArgs struct { type EthTxArgs struct {
ChainID int `json:"chainId"` ChainID int `json:"chainId"`
Nonce int `json:"nonce"` Nonce int `json:"nonce"`

View File

@ -219,4 +219,17 @@ func (m *Message) ValidForBlockInclusion(minGas int64, version network.Version)
return nil return nil
} }
// EffectiveGasPremium returns the effective gas premium claimable by the miner
// given the supplied base fee.
//
// Filecoin clamps the gas premium at GasFeeCap - BaseFee, if lower than the
// specified premium.
func (m *Message) EffectiveGasPremium(baseFee abi.TokenAmount) abi.TokenAmount {
available := big.Sub(m.GasFeeCap, baseFee)
if big.Cmp(m.GasPremium, available) <= 0 {
return m.GasPremium
}
return available
}
const TestGasLimit = 100e6 const TestGasLimit = 100e6

View File

@ -689,49 +689,43 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (eth
return ethtypes.EthFeeHistory{}, fmt.Errorf("bad block parameter %s: %s", params.NewestBlkNum, err) return ethtypes.EthFeeHistory{}, fmt.Errorf("bad block parameter %s: %s", params.NewestBlkNum, err)
} }
oldestBlkHeight := uint64(1) var (
basefee = ts.Blocks()[0].ParentBaseFee
oldestBlkHeight = uint64(1)
// NOTE: baseFeePerGas should include the next block after the newest of the returned range, // NOTE: baseFeePerGas should include the next block after the newest of the returned range,
// because the next base fee can be inferred from the messages in the newest block. // because the next base fee can be inferred from the messages in the newest block.
// However, this is NOT the case in Filecoin due to deferred execution, so the best // However, this is NOT the case in Filecoin due to deferred execution, so the best
// we can do is duplicate the last value. // we can do is duplicate the last value.
baseFeeArray := []ethtypes.EthBigInt{ethtypes.EthBigInt(ts.Blocks()[0].ParentBaseFee)} baseFeeArray = []ethtypes.EthBigInt{ethtypes.EthBigInt(basefee)}
gasUsedRatioArray := []float64{} rewardsArray = make([][]ethtypes.EthBigInt, 0)
rewardsArray := make([][]ethtypes.EthBigInt, 0) gasUsedRatioArray = []float64{}
blocksIncluded int
)
blocksIncluded := 0
for blocksIncluded < int(params.BlkCount) && ts.Height() > 0 { for blocksIncluded < int(params.BlkCount) && ts.Height() > 0 {
compOutput, err := a.StateCompute(ctx, ts.Height(), nil, ts.Key()) msgs, rcpts, err := messagesAndReceipts(ctx, ts, a.Chain, a.StateAPI)
if err != nil { if err != nil {
return ethtypes.EthFeeHistory{}, xerrors.Errorf("cannot lookup the status of tipset: %v: %w", ts, err) return ethtypes.EthFeeHistory{}, xerrors.Errorf("failed to retrieve messages and receipts for height %d: %w", ts.Height(), err)
} }
txGasRewards := gasRewardSorter{} txGasRewards := gasRewardSorter{}
for _, msg := range compOutput.Trace { for i, msg := range msgs {
if msg.Msg.From == builtintypes.SystemActorAddr { if msg.VMMessage().From == builtintypes.SystemActorAddr {
continue continue
} }
smsgCid, err := getSignedMessage(ctx, a.Chain, msg.MsgCid) effectivePremium := msg.VMMessage().EffectiveGasPremium(basefee)
if err != nil {
return ethtypes.EthFeeHistory{}, xerrors.Errorf("failed to get signed msg %s: %w", msg.MsgCid, err)
}
tx, err := newEthTxFromSignedMessage(ctx, smsgCid, a.StateAPI)
if err != nil {
return ethtypes.EthFeeHistory{}, err
}
txGasRewards = append(txGasRewards, gasRewardTuple{ txGasRewards = append(txGasRewards, gasRewardTuple{
reward: tx.Reward(ts.Blocks()[0].ParentBaseFee), premium: effectivePremium,
gas: uint64(msg.MsgRct.GasUsed), gasUsed: rcpts[i].GasUsed,
}) })
} }
rewards, totalGasUsed := calculateRewardsAndGasUsed(rewardPercentiles, txGasRewards) rewards, totalGasUsed := calculateRewardsAndGasUsed(rewardPercentiles, txGasRewards)
// arrays should be reversed at the end // arrays should be reversed at the end
baseFeeArray = append(baseFeeArray, ethtypes.EthBigInt(ts.Blocks()[0].ParentBaseFee)) baseFeeArray = append(baseFeeArray, ethtypes.EthBigInt(basefee))
gasUsedRatioArray = append(gasUsedRatioArray, float64(totalGasUsed)/float64(build.BlockGasLimit)) gasUsedRatioArray = append(gasUsedRatioArray, float64(totalGasUsed)/float64(build.BlockGasLimit))
rewardsArray = append(rewardsArray, rewards) rewardsArray = append(rewardsArray, rewards)
oldestBlkHeight = uint64(ts.Height()) oldestBlkHeight = uint64(ts.Height())
@ -1792,23 +1786,9 @@ func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTx
return ethtypes.EthBlock{}, err return ethtypes.EthBlock{}, err
} }
msgs, err := cs.MessagesForTipset(ctx, ts) msgs, rcpts, err := messagesAndReceipts(ctx, ts, cs, sa)
if err != nil { if err != nil {
return ethtypes.EthBlock{}, xerrors.Errorf("error loading messages for tipset: %v: %w", ts, err) return ethtypes.EthBlock{}, xerrors.Errorf("failed to retrieve messages and receipts: %w", err)
}
_, rcptRoot, err := sa.StateManager.TipSetState(ctx, ts)
if err != nil {
return ethtypes.EthBlock{}, xerrors.Errorf("failed to compute state: %w", err)
}
rcpts, err := cs.ReadReceipts(ctx, rcptRoot)
if err != nil {
return ethtypes.EthBlock{}, xerrors.Errorf("error loading receipts for tipset: %v: %w", ts, err)
}
if len(msgs) != len(rcpts) {
return ethtypes.EthBlock{}, xerrors.Errorf("receipts and message array lengths didn't match for tipset: %v: %w", ts, err)
} }
block := ethtypes.NewEthBlock(len(msgs) > 0) block := ethtypes.NewEthBlock(len(msgs) > 0)
@ -1858,6 +1838,29 @@ func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTx
return block, nil return block, nil
} }
func messagesAndReceipts(ctx context.Context, ts *types.TipSet, cs *store.ChainStore, sa StateAPI) ([]types.ChainMsg, []types.MessageReceipt, error) {
msgs, err := cs.MessagesForTipset(ctx, ts)
if err != nil {
return nil, nil, xerrors.Errorf("error loading messages for tipset: %v: %w", ts, err)
}
_, rcptRoot, err := sa.StateManager.TipSetState(ctx, ts)
if err != nil {
return nil, nil, xerrors.Errorf("failed to compute state: %w", err)
}
rcpts, err := cs.ReadReceipts(ctx, rcptRoot)
if err != nil {
return nil, nil, xerrors.Errorf("error loading receipts for tipset: %v: %w", ts, err)
}
if len(msgs) != len(rcpts) {
return nil, nil, xerrors.Errorf("receipts and message array lengths didn't match for tipset: %v: %w", ts, err)
}
return msgs, rcpts, nil
}
// lookupEthAddress makes its best effort at finding the Ethereum address for a // lookupEthAddress makes its best effort at finding the Ethereum address for a
// Filecoin address. It does the following: // Filecoin address. It does the following:
// //
@ -2358,10 +2361,10 @@ func parseEthRevert(ret []byte) string {
return ethtypes.EthBytes(cbytes).String() return ethtypes.EthBytes(cbytes).String()
} }
func calculateRewardsAndGasUsed(rewardPercentiles []float64, txGasRewards gasRewardSorter) ([]ethtypes.EthBigInt, uint64) { func calculateRewardsAndGasUsed(rewardPercentiles []float64, txGasRewards gasRewardSorter) ([]ethtypes.EthBigInt, int64) {
var totalGasUsed uint64 var gasUsedTotal int64
for _, tx := range txGasRewards { for _, tx := range txGasRewards {
totalGasUsed += tx.gas gasUsedTotal += tx.gasUsed
} }
rewards := make([]ethtypes.EthBigInt, len(rewardPercentiles)) rewards := make([]ethtypes.EthBigInt, len(rewardPercentiles))
@ -2370,23 +2373,23 @@ func calculateRewardsAndGasUsed(rewardPercentiles []float64, txGasRewards gasRew
} }
if len(txGasRewards) == 0 { if len(txGasRewards) == 0 {
return rewards, totalGasUsed return rewards, gasUsedTotal
} }
sort.Stable(txGasRewards) sort.Stable(txGasRewards)
var idx int var idx int
var sum uint64 var sum int64
for i, percentile := range rewardPercentiles { for i, percentile := range rewardPercentiles {
threshold := uint64(float64(totalGasUsed) * percentile / 100) threshold := int64(float64(gasUsedTotal) * percentile / 100)
for sum < threshold && idx < len(txGasRewards)-1 { for sum < threshold && idx < len(txGasRewards)-1 {
sum += txGasRewards[idx].gas sum += txGasRewards[idx].gasUsed
idx++ idx++
} }
rewards[i] = txGasRewards[idx].reward rewards[i] = ethtypes.EthBigInt(txGasRewards[idx].premium)
} }
return rewards, totalGasUsed return rewards, gasUsedTotal
} }
func getSignedMessage(ctx context.Context, cs *store.ChainStore, msgCid cid.Cid) (*types.SignedMessage, error) { func getSignedMessage(ctx context.Context, cs *store.ChainStore, msgCid cid.Cid) (*types.SignedMessage, error) {
@ -2409,8 +2412,8 @@ func getSignedMessage(ctx context.Context, cs *store.ChainStore, msgCid cid.Cid)
} }
type gasRewardTuple struct { type gasRewardTuple struct {
gas uint64 gasUsed int64
reward ethtypes.EthBigInt premium abi.TokenAmount
} }
// sorted in ascending order // sorted in ascending order
@ -2421,5 +2424,5 @@ func (g gasRewardSorter) Swap(i, j int) {
g[i], g[j] = g[j], g[i] g[i], g[j] = g[j], g[i]
} }
func (g gasRewardSorter) Less(i, j int) bool { func (g gasRewardSorter) Less(i, j int) bool {
return g[i].reward.Int.Cmp(g[j].reward.Int) == -1 return g[i].premium.Int.Cmp(g[j].premium.Int) == -1
} }

View File

@ -117,11 +117,8 @@ func TestReward(t *testing.T) {
{maxFeePerGas: big.NewInt(50), maxPriorityFeePerGas: big.NewInt(200), answer: big.NewInt(-50)}, {maxFeePerGas: big.NewInt(50), maxPriorityFeePerGas: big.NewInt(200), answer: big.NewInt(-50)},
} }
for _, tc := range testcases { for _, tc := range testcases {
tx := ethtypes.EthTx{ msg := &types.Message{GasFeeCap: tc.maxFeePerGas, GasPremium: tc.maxPriorityFeePerGas}
MaxFeePerGas: ethtypes.EthBigInt(tc.maxFeePerGas), reward := msg.EffectiveGasPremium(baseFee)
MaxPriorityFeePerGas: ethtypes.EthBigInt(tc.maxPriorityFeePerGas),
}
reward := tx.Reward(baseFee)
require.Equal(t, 0, reward.Int.Cmp(tc.answer.Int), reward, tc.answer) require.Equal(t, 0, reward.Int.Cmp(tc.answer.Int), reward, tc.answer)
} }
} }
@ -140,20 +137,20 @@ func TestRewardPercentiles(t *testing.T) {
{ {
percentiles: []float64{25, 50, 75, 100}, percentiles: []float64{25, 50, 75, 100},
txGasRewards: []gasRewardTuple{ txGasRewards: []gasRewardTuple{
{gas: uint64(0), reward: ethtypes.EthBigInt(big.NewInt(300))}, {gasUsed: int64(0), premium: big.NewInt(300)},
{gas: uint64(100), reward: ethtypes.EthBigInt(big.NewInt(200))}, {gasUsed: int64(100), premium: big.NewInt(200)},
{gas: uint64(350), reward: ethtypes.EthBigInt(big.NewInt(100))}, {gasUsed: int64(350), premium: big.NewInt(100)},
{gas: uint64(500), reward: ethtypes.EthBigInt(big.NewInt(600))}, {gasUsed: int64(500), premium: big.NewInt(600)},
{gas: uint64(300), reward: ethtypes.EthBigInt(big.NewInt(700))}, {gasUsed: int64(300), premium: big.NewInt(700)},
}, },
answer: []int64{200, 700, 700, 700}, answer: []int64{200, 700, 700, 700},
}, },
} }
for _, tc := range testcases { for _, tc := range testcases {
rewards, totalGasUsed := calculateRewardsAndGasUsed(tc.percentiles, tc.txGasRewards) rewards, totalGasUsed := calculateRewardsAndGasUsed(tc.percentiles, tc.txGasRewards)
gasUsed := uint64(0) var gasUsed int64
for _, tx := range tc.txGasRewards { for _, tx := range tc.txGasRewards {
gasUsed += tx.gas gasUsed += tx.gasUsed
} }
ans := []ethtypes.EthBigInt{} ans := []ethtypes.EthBigInt{}
for _, bi := range tc.answer { for _, bi := range tc.answer {