Merge remote-tracking branch 'origin/master' into hsanjuan/chain-export-range-rebased
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/ipfs/go-cid"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-jsonrpc"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
@@ -16,6 +17,10 @@ var ErrModuleDisabled = errors.New("module disabled, enable with Fevm.EnableEthR
|
||||
|
||||
type EthModuleDummy struct{}
|
||||
|
||||
func (e *EthModuleDummy) EthAddressToFilecoinAddress(ctx context.Context, ethAddress ethtypes.EthAddress) (address.Address, error) {
|
||||
return address.Undef, ErrModuleDisabled
|
||||
}
|
||||
|
||||
func (e *EthModuleDummy) EthGetMessageCidByTransactionHash(ctx context.Context, txHash *ethtypes.EthHash) (*cid.Cid, error) {
|
||||
return nil, ErrModuleDisabled
|
||||
}
|
||||
|
||||
+275
-40
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -30,6 +31,7 @@ import (
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin"
|
||||
builtinevm "github.com/filecoin-project/lotus/chain/actors/builtin/evm"
|
||||
"github.com/filecoin-project/lotus/chain/ethhashlookup"
|
||||
"github.com/filecoin-project/lotus/chain/events/filter"
|
||||
"github.com/filecoin-project/lotus/chain/messagepool"
|
||||
@@ -180,6 +182,10 @@ func (a *EthModule) EthAccounts(context.Context) ([]ethtypes.EthAddress, error)
|
||||
return []ethtypes.EthAddress{}, nil
|
||||
}
|
||||
|
||||
func (a *EthAPI) EthAddressToFilecoinAddress(ctx context.Context, ethAddress ethtypes.EthAddress) (address.Address, error) {
|
||||
return ethAddress.ToFilecoinAddress()
|
||||
}
|
||||
|
||||
func (a *EthModule) countTipsetMsgs(ctx context.Context, ts *types.TipSet) (int, error) {
|
||||
blkMsgs, err := a.Chain.BlockMsgsForTipset(ctx, ts)
|
||||
if err != nil {
|
||||
@@ -362,6 +368,26 @@ func (a *EthModule) EthGetTransactionCount(ctx context.Context, sender ethtypes.
|
||||
return ethtypes.EthUint64(0), xerrors.Errorf("cannot parse block param: %s", blkParam)
|
||||
}
|
||||
|
||||
// First, handle the case where the "sender" is an EVM actor.
|
||||
if actor, err := a.StateManager.LoadActor(ctx, addr, ts); err != nil {
|
||||
if xerrors.Is(err, types.ErrActorNotFound) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, xerrors.Errorf("failed to lookup contract %s: %w", sender, err)
|
||||
} else if builtinactors.IsEvmActor(actor.Code) {
|
||||
evmState, err := builtinevm.Load(a.Chain.ActorStore(ctx), actor)
|
||||
if err != nil {
|
||||
return 0, xerrors.Errorf("failed to load evm state: %w", err)
|
||||
}
|
||||
if alive, err := evmState.IsAlive(); err != nil {
|
||||
return 0, err
|
||||
} else if !alive {
|
||||
return 0, nil
|
||||
}
|
||||
nonce, err := evmState.Nonce()
|
||||
return ethtypes.EthUint64(nonce), err
|
||||
}
|
||||
|
||||
nonce, err := a.Mpool.GetNonce(ctx, addr, ts.Key())
|
||||
if err != nil {
|
||||
return ethtypes.EthUint64(0), nil
|
||||
@@ -505,13 +531,18 @@ func (a *EthModule) EthGetCode(ctx context.Context, ethAddr ethtypes.EthAddress,
|
||||
}
|
||||
|
||||
func (a *EthModule) EthGetStorageAt(ctx context.Context, ethAddr ethtypes.EthAddress, position ethtypes.EthBytes, blkParam string) (ethtypes.EthBytes, error) {
|
||||
ts, err := a.parseBlkParam(ctx, blkParam)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("cannot parse block param: %s", blkParam)
|
||||
}
|
||||
|
||||
l := len(position)
|
||||
if l > 32 {
|
||||
return nil, fmt.Errorf("supplied storage key is too long")
|
||||
}
|
||||
|
||||
// pad with zero bytes if smaller than 32 bytes
|
||||
position = append(make([]byte, 32-l, 32-l), position...)
|
||||
position = append(make([]byte, 32-l, 32), position...)
|
||||
|
||||
to, err := ethAddr.ToFilecoinAddress()
|
||||
if err != nil {
|
||||
@@ -524,8 +555,20 @@ func (a *EthModule) EthGetStorageAt(ctx context.Context, ethAddr ethtypes.EthAdd
|
||||
return nil, fmt.Errorf("failed to construct system sender address: %w", err)
|
||||
}
|
||||
|
||||
actor, err := a.StateManager.LoadActor(ctx, to, ts)
|
||||
if err != nil {
|
||||
if xerrors.Is(err, types.ErrActorNotFound) {
|
||||
return ethtypes.EthBytes(make([]byte, 32)), nil
|
||||
}
|
||||
return nil, xerrors.Errorf("failed to lookup contract %s: %w", ethAddr, err)
|
||||
}
|
||||
|
||||
if !builtinactors.IsEvmActor(actor.Code) {
|
||||
return ethtypes.EthBytes(make([]byte, 32)), nil
|
||||
}
|
||||
|
||||
params, err := actors.SerializeParams(&evm.GetStorageAtParams{
|
||||
StorageKey: position,
|
||||
StorageKey: *(*[32]byte)(position),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to serialize parameters: %w", err)
|
||||
@@ -542,8 +585,6 @@ func (a *EthModule) EthGetStorageAt(ctx context.Context, ethAddr ethtypes.EthAdd
|
||||
GasPremium: big.Zero(),
|
||||
}
|
||||
|
||||
ts := a.Chain.GetHeaviestTipSet()
|
||||
|
||||
// Try calling until we find a height with no migration.
|
||||
var res *api.InvocResult
|
||||
for {
|
||||
@@ -562,10 +603,22 @@ func (a *EthModule) EthGetStorageAt(ctx context.Context, ethAddr ethtypes.EthAdd
|
||||
}
|
||||
|
||||
if res.MsgRct == nil {
|
||||
return nil, fmt.Errorf("no message receipt")
|
||||
return nil, xerrors.Errorf("no message receipt")
|
||||
}
|
||||
|
||||
return res.MsgRct.Return, nil
|
||||
if res.MsgRct.ExitCode.IsError() {
|
||||
return nil, xerrors.Errorf("failed to lookup storage slot: %s", res.Error)
|
||||
}
|
||||
|
||||
var ret abi.CborBytes
|
||||
if err := ret.UnmarshalCBOR(bytes.NewReader(res.MsgRct.Return)); err != nil {
|
||||
return nil, xerrors.Errorf("failed to unmarshal storage slot: %w", err)
|
||||
}
|
||||
|
||||
// pad with zero bytes if smaller than 32 bytes
|
||||
ret = append(make([]byte, 32-len(ret), 32), ret...)
|
||||
|
||||
return ethtypes.EthBytes(ret), nil
|
||||
}
|
||||
|
||||
func (a *EthModule) EthGetBalance(ctx context.Context, address ethtypes.EthAddress, blkParam string) (ethtypes.EthBigInt, error) {
|
||||
@@ -601,6 +654,18 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (eth
|
||||
if params.BlkCount > 1024 {
|
||||
return ethtypes.EthFeeHistory{}, fmt.Errorf("block count should be smaller than 1024")
|
||||
}
|
||||
rewardPercentiles := make([]float64, 0)
|
||||
if params.RewardPercentiles != nil {
|
||||
rewardPercentiles = append(rewardPercentiles, *params.RewardPercentiles...)
|
||||
}
|
||||
for i, rp := range rewardPercentiles {
|
||||
if rp < 0 || rp > 100 {
|
||||
return ethtypes.EthFeeHistory{}, fmt.Errorf("invalid reward percentile: %f should be between 0 and 100", rp)
|
||||
}
|
||||
if i > 0 && rp < rewardPercentiles[i-1] {
|
||||
return ethtypes.EthFeeHistory{}, fmt.Errorf("invalid reward percentile: %f should be larger than %f", rp, rewardPercentiles[i-1])
|
||||
}
|
||||
}
|
||||
|
||||
ts, err := a.parseBlkParam(ctx, params.NewestBlkNum)
|
||||
if err != nil {
|
||||
@@ -619,18 +684,40 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (eth
|
||||
// we can do is duplicate the last value.
|
||||
baseFeeArray := []ethtypes.EthBigInt{ethtypes.EthBigInt(ts.Blocks()[0].ParentBaseFee)}
|
||||
gasUsedRatioArray := []float64{}
|
||||
rewardsArray := make([][]ethtypes.EthBigInt, 0)
|
||||
|
||||
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 := newEthBlockFromFilecoinTipSet(ctx, ts, false, a.Chain, a.StateAPI)
|
||||
msgs, err := a.Chain.MessagesForTipset(ctx, ts)
|
||||
if err != nil {
|
||||
return ethtypes.EthFeeHistory{}, fmt.Errorf("cannot create eth block: %v", err)
|
||||
return ethtypes.EthFeeHistory{}, xerrors.Errorf("error loading messages for tipset: %v: %w", ts, err)
|
||||
}
|
||||
|
||||
// both arrays should be reversed at the end
|
||||
txGasRewards := gasRewardSorter{}
|
||||
for txIdx, msg := range msgs {
|
||||
msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, msg.Cid(), api.LookbackNoLimit, false)
|
||||
if err != nil || msgLookup == nil {
|
||||
return ethtypes.EthFeeHistory{}, nil
|
||||
}
|
||||
|
||||
tx, err := newEthTxFromMessageLookup(ctx, msgLookup, txIdx, a.Chain, a.StateAPI)
|
||||
if err != nil {
|
||||
return ethtypes.EthFeeHistory{}, nil
|
||||
}
|
||||
|
||||
txGasRewards = append(txGasRewards, gasRewardTuple{
|
||||
reward: tx.Reward(ts.Blocks()[0].ParentBaseFee),
|
||||
gas: uint64(msgLookup.Receipt.GasUsed),
|
||||
})
|
||||
}
|
||||
|
||||
rewards, totalGasUsed := calculateRewardsAndGasUsed(rewardPercentiles, txGasRewards)
|
||||
|
||||
// arrays should be reversed at the end
|
||||
baseFeeArray = append(baseFeeArray, ethtypes.EthBigInt(ts.Blocks()[0].ParentBaseFee))
|
||||
gasUsedRatioArray = append(gasUsedRatioArray, float64(block.GasUsed)/float64(build.BlockGasLimit))
|
||||
gasUsedRatioArray = append(gasUsedRatioArray, float64(totalGasUsed)/float64(build.BlockGasLimit))
|
||||
rewardsArray = append(rewardsArray, rewards)
|
||||
|
||||
parentTsKey := ts.Parents()
|
||||
ts, err = a.Chain.LoadTipSet(ctx, parentTsKey)
|
||||
@@ -646,6 +733,9 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (eth
|
||||
for i, j := 0, len(gasUsedRatioArray)-1; i < j; i, j = i+1, j-1 {
|
||||
gasUsedRatioArray[i], gasUsedRatioArray[j] = gasUsedRatioArray[j], gasUsedRatioArray[i]
|
||||
}
|
||||
for i, j := 0, len(rewardsArray)-1; i < j; i, j = i+1, j-1 {
|
||||
rewardsArray[i], rewardsArray[j] = rewardsArray[j], rewardsArray[i]
|
||||
}
|
||||
|
||||
ret := ethtypes.EthFeeHistory{
|
||||
OldestBlock: ethtypes.EthUint64(oldestBlkHeight),
|
||||
@@ -653,13 +743,7 @@ func (a *EthModule) EthFeeHistory(ctx context.Context, p jsonrpc.RawParams) (eth
|
||||
GasUsedRatio: gasUsedRatioArray,
|
||||
}
|
||||
if params.RewardPercentiles != nil {
|
||||
// TODO: Populate reward percentiles
|
||||
// https://github.com/filecoin-project/lotus/issues/10236
|
||||
// We need to calculate the requested percentiles of effective gas premium
|
||||
// based on the newest block (I presume it's the newest, we need to dig in
|
||||
// as it's underspecified). Effective means we're clamped at the gas_fee_cap - base_fee.
|
||||
reward := make([][]ethtypes.EthBigInt, 0)
|
||||
ret.Reward = &reward
|
||||
ret.Reward = &rewardsArray
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
@@ -751,18 +835,20 @@ func (a *EthModule) ethCallToFilecoinMessage(ctx context.Context, tx ethtypes.Et
|
||||
}
|
||||
|
||||
var params []byte
|
||||
if len(tx.Data) > 0 {
|
||||
initcode := abi.CborBytes(tx.Data)
|
||||
params2, err := actors.SerializeParams(&initcode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to serialize params: %w", err)
|
||||
}
|
||||
params = params2
|
||||
}
|
||||
|
||||
var to address.Address
|
||||
var method abi.MethodNum
|
||||
if tx.To == nil {
|
||||
// this is a contract creation
|
||||
to = builtintypes.EthereumAddressManagerActorAddr
|
||||
|
||||
initcode := abi.CborBytes(tx.Data)
|
||||
params2, err := actors.SerializeParams(&initcode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to serialize Create params: %w", err)
|
||||
}
|
||||
params = params2
|
||||
method = builtintypes.MethodsEAM.CreateExternal
|
||||
} else {
|
||||
addr, err := tx.To.ToFilecoinAddress()
|
||||
@@ -770,15 +856,6 @@ func (a *EthModule) ethCallToFilecoinMessage(ctx context.Context, tx ethtypes.Et
|
||||
return nil, xerrors.Errorf("cannot get Filecoin address: %w", err)
|
||||
}
|
||||
to = addr
|
||||
|
||||
if len(tx.Data) > 0 {
|
||||
var buf bytes.Buffer
|
||||
if err := cbg.WriteByteArray(&buf, tx.Data); err != nil {
|
||||
return nil, fmt.Errorf("failed to encode tx input into a cbor byte-string")
|
||||
}
|
||||
params = buf.Bytes()
|
||||
}
|
||||
|
||||
method = builtintypes.MethodsEVM.InvokeContract
|
||||
}
|
||||
|
||||
@@ -815,7 +892,8 @@ func (a *EthModule) applyMessage(ctx context.Context, msg *types.Message, tsk ty
|
||||
return nil, xerrors.Errorf("CallWithGas failed: %w", err)
|
||||
}
|
||||
if res.MsgRct.ExitCode.IsError() {
|
||||
return nil, xerrors.Errorf("message execution failed: exit %s, msg receipt: %s, reason: %s", res.MsgRct.ExitCode, res.MsgRct.Return, res.Error)
|
||||
reason := parseEthRevert(res.MsgRct.Return)
|
||||
return nil, xerrors.Errorf("message execution failed: exit %s, revert reason: %s, vm error: %s", res.MsgRct.ExitCode, reason, res.Error)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
@@ -831,14 +909,24 @@ func (a *EthModule) EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (et
|
||||
msg.GasLimit = 0
|
||||
|
||||
ts := a.Chain.GetHeaviestTipSet()
|
||||
msg, err = a.GasAPI.GasEstimateMessageGas(ctx, msg, nil, ts.Key())
|
||||
gassedMsg, err := a.GasAPI.GasEstimateMessageGas(ctx, msg, nil, ts.Key())
|
||||
if err != nil {
|
||||
// On failure, GasEstimateMessageGas doesn't actually return the invocation result,
|
||||
// it just returns an error. That means we can't get the revert reason.
|
||||
//
|
||||
// So we re-execute the message with EthCall (well, applyMessage which contains the
|
||||
// guts of EthCall). This will give us an ethereum specific error with revert
|
||||
// information.
|
||||
msg.GasLimit = build.BlockGasLimit
|
||||
if _, err2 := a.applyMessage(ctx, msg, ts.Key()); err2 != nil {
|
||||
err = err2
|
||||
}
|
||||
return ethtypes.EthUint64(0), xerrors.Errorf("failed to estimate gas: %w", err)
|
||||
}
|
||||
|
||||
expectedGas, err := ethGasSearch(ctx, a.Chain, a.Stmgr, a.Mpool, msg, ts)
|
||||
expectedGas, err := ethGasSearch(ctx, a.Chain, a.Stmgr, a.Mpool, gassedMsg, ts)
|
||||
if err != nil {
|
||||
log.Errorw("expected gas", "err", err)
|
||||
return 0, xerrors.Errorf("gas search failed: %w", err)
|
||||
}
|
||||
|
||||
return ethtypes.EthUint64(expectedGas), nil
|
||||
@@ -976,7 +1064,7 @@ func (a *EthModule) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam s
|
||||
|
||||
invokeResult, err := a.applyMessage(ctx, msg, ts.Key())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to apply message: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if msg.To == builtintypes.EthereumAddressManagerActorAddr {
|
||||
@@ -1246,8 +1334,9 @@ func (e *EthEvent) uninstallFilter(ctx context.Context, f filter.Filter) error {
|
||||
}
|
||||
|
||||
const (
|
||||
EthSubscribeEventTypeHeads = "newHeads"
|
||||
EthSubscribeEventTypeLogs = "logs"
|
||||
EthSubscribeEventTypeHeads = "newHeads"
|
||||
EthSubscribeEventTypeLogs = "logs"
|
||||
EthSubscribeEventTypePendingTransactions = "newPendingTransactions"
|
||||
)
|
||||
|
||||
func (e *EthEvent) EthSubscribe(ctx context.Context, p jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error) {
|
||||
@@ -1309,6 +1398,15 @@ func (e *EthEvent) EthSubscribe(ctx context.Context, p jsonrpc.RawParams) (ethty
|
||||
_, _ = e.EthUnsubscribe(ctx, sub.id)
|
||||
return ethtypes.EthSubscriptionID{}, err
|
||||
}
|
||||
sub.addFilter(ctx, f)
|
||||
case EthSubscribeEventTypePendingTransactions:
|
||||
f, err := e.MemPoolFilterManager.Install(ctx)
|
||||
if err != nil {
|
||||
// clean up any previous filters added and stop the sub
|
||||
_, _ = e.EthUnsubscribe(ctx, sub.id)
|
||||
return ethtypes.EthSubscriptionID{}, err
|
||||
}
|
||||
|
||||
sub.addFilter(ctx, f)
|
||||
default:
|
||||
return ethtypes.EthSubscriptionID{}, xerrors.Errorf("unsupported event type: %s", params.EventType)
|
||||
@@ -1629,6 +1727,15 @@ func (e *ethSubscription) start(ctx context.Context) {
|
||||
}
|
||||
|
||||
e.send(ctx, ev)
|
||||
case *types.SignedMessage: // mpool txid
|
||||
evs, err := ethFilterResultFromMessages([]*types.SignedMessage{vt}, e.StateAPI)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, r := range evs.Results {
|
||||
e.send(ctx, r)
|
||||
}
|
||||
default:
|
||||
log.Warnf("unexpected subscription value type: %T", vt)
|
||||
}
|
||||
@@ -2081,11 +2188,13 @@ func (m *EthTxHashManager) ProcessSignedMessage(ctx context.Context, msg *types.
|
||||
ethTx, err := newEthTxFromSignedMessage(ctx, msg, m.StateAPI)
|
||||
if err != nil {
|
||||
log.Errorf("error converting filecoin message to eth tx: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = m.TransactionHashLookup.UpsertHash(ethTx.Hash, msg.Cid())
|
||||
if err != nil {
|
||||
log.Errorf("error inserting tx mapping to db: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2135,3 +2244,129 @@ func parseEthTopics(topics ethtypes.EthTopicSpec) (map[string][][]byte, error) {
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
const errorFunctionSelector = "\x08\xc3\x79\xa0" // Error(string)
|
||||
const panicFunctionSelector = "\x4e\x48\x7b\x71" // Panic(uint256)
|
||||
// Eth ABI (solidity) panic codes.
|
||||
var panicErrorCodes map[uint64]string = map[uint64]string{
|
||||
0x00: "Panic()",
|
||||
0x01: "Assert()",
|
||||
0x11: "ArithmeticOverflow()",
|
||||
0x12: "DivideByZero()",
|
||||
0x21: "InvalidEnumVariant()",
|
||||
0x22: "InvalidStorageArray()",
|
||||
0x31: "PopEmptyArray()",
|
||||
0x32: "ArrayIndexOutOfBounds()",
|
||||
0x41: "OutOfMemory()",
|
||||
0x51: "CalledUninitializedFunction()",
|
||||
}
|
||||
|
||||
// Parse an ABI encoded revert reason. This reason should be encoded as if it were the parameters to
|
||||
// an `Error(string)` function call.
|
||||
//
|
||||
// See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
|
||||
func parseEthRevert(ret []byte) string {
|
||||
if len(ret) == 0 {
|
||||
return "none"
|
||||
}
|
||||
var cbytes abi.CborBytes
|
||||
if err := cbytes.UnmarshalCBOR(bytes.NewReader(ret)); err != nil {
|
||||
return "ERROR: revert reason is not cbor encoded bytes"
|
||||
}
|
||||
if len(cbytes) == 0 {
|
||||
return "none"
|
||||
}
|
||||
// If it's not long enough to contain an ABI encoded response, return immediately.
|
||||
if len(cbytes) < 4+32 {
|
||||
return ethtypes.EthBytes(cbytes).String()
|
||||
}
|
||||
switch string(cbytes[:4]) {
|
||||
case panicFunctionSelector:
|
||||
cbytes := cbytes[4 : 4+32]
|
||||
// Read the and check the code.
|
||||
code, err := ethtypes.EthUint64FromBytes(cbytes)
|
||||
if err != nil {
|
||||
// If it's too big, just return the raw value.
|
||||
codeInt := big.PositiveFromUnsignedBytes(cbytes)
|
||||
return fmt.Sprintf("Panic(%s)", ethtypes.EthBigInt(codeInt).String())
|
||||
}
|
||||
if s, ok := panicErrorCodes[uint64(code)]; ok {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("Panic(0x%x)", code)
|
||||
case errorFunctionSelector:
|
||||
cbytes := cbytes[4:]
|
||||
cbytesLen := ethtypes.EthUint64(len(cbytes))
|
||||
// Read the and check the offset.
|
||||
offset, err := ethtypes.EthUint64FromBytes(cbytes[:32])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if cbytesLen < offset {
|
||||
break
|
||||
}
|
||||
|
||||
// Read and check the length.
|
||||
if cbytesLen-offset < 32 {
|
||||
break
|
||||
}
|
||||
start := offset + 32
|
||||
length, err := ethtypes.EthUint64FromBytes(cbytes[offset : offset+32])
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if cbytesLen-start < length {
|
||||
break
|
||||
}
|
||||
// Slice the error message.
|
||||
return fmt.Sprintf("Error(%s)", cbytes[start:start+length])
|
||||
}
|
||||
return ethtypes.EthBytes(cbytes).String()
|
||||
}
|
||||
|
||||
func calculateRewardsAndGasUsed(rewardPercentiles []float64, txGasRewards gasRewardSorter) ([]ethtypes.EthBigInt, uint64) {
|
||||
var totalGasUsed uint64
|
||||
for _, tx := range txGasRewards {
|
||||
totalGasUsed += tx.gas
|
||||
}
|
||||
|
||||
rewards := make([]ethtypes.EthBigInt, len(rewardPercentiles))
|
||||
for i := range rewards {
|
||||
rewards[i] = ethtypes.EthBigIntZero
|
||||
}
|
||||
|
||||
if len(txGasRewards) == 0 {
|
||||
return rewards, totalGasUsed
|
||||
}
|
||||
|
||||
sort.Stable(txGasRewards)
|
||||
|
||||
var idx int
|
||||
var sum uint64
|
||||
for i, percentile := range rewardPercentiles {
|
||||
threshold := uint64(float64(totalGasUsed) * percentile / 100)
|
||||
for sum < threshold && idx < len(txGasRewards)-1 {
|
||||
sum += txGasRewards[idx].gas
|
||||
idx++
|
||||
}
|
||||
rewards[i] = txGasRewards[idx].reward
|
||||
}
|
||||
|
||||
return rewards, totalGasUsed
|
||||
}
|
||||
|
||||
type gasRewardTuple struct {
|
||||
gas uint64
|
||||
reward ethtypes.EthBigInt
|
||||
}
|
||||
|
||||
// sorted in ascending order
|
||||
type gasRewardSorter []gasRewardTuple
|
||||
|
||||
func (g gasRewardSorter) Len() int { return len(g) }
|
||||
func (g gasRewardSorter) Swap(i, j int) {
|
||||
g[i], g[j] = g[j], g[i]
|
||||
}
|
||||
func (g gasRewardSorter) Less(i, j int) bool {
|
||||
return g[i].reward.Int.Cmp(g[j].reward.Int) == -1
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/filecoin-project/go-state-types/big"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
||||
)
|
||||
@@ -100,3 +102,65 @@ func TestEthLogFromEvent(t *testing.T) {
|
||||
require.Len(t, topics, 1)
|
||||
require.Equal(t, topics[0], ethtypes.EthHash{})
|
||||
}
|
||||
|
||||
func TestReward(t *testing.T) {
|
||||
baseFee := big.NewInt(100)
|
||||
testcases := []struct {
|
||||
maxFeePerGas, maxPriorityFeePerGas big.Int
|
||||
answer big.Int
|
||||
}{
|
||||
{maxFeePerGas: big.NewInt(600), maxPriorityFeePerGas: big.NewInt(200), answer: big.NewInt(200)},
|
||||
{maxFeePerGas: big.NewInt(600), maxPriorityFeePerGas: big.NewInt(300), answer: big.NewInt(300)},
|
||||
{maxFeePerGas: big.NewInt(600), maxPriorityFeePerGas: big.NewInt(500), answer: big.NewInt(500)},
|
||||
{maxFeePerGas: big.NewInt(600), maxPriorityFeePerGas: big.NewInt(600), answer: big.NewInt(500)},
|
||||
{maxFeePerGas: big.NewInt(600), maxPriorityFeePerGas: big.NewInt(1000), answer: big.NewInt(500)},
|
||||
{maxFeePerGas: big.NewInt(50), maxPriorityFeePerGas: big.NewInt(200), answer: big.NewInt(-50)},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
tx := ethtypes.EthTx{
|
||||
MaxFeePerGas: ethtypes.EthBigInt(tc.maxFeePerGas),
|
||||
MaxPriorityFeePerGas: ethtypes.EthBigInt(tc.maxPriorityFeePerGas),
|
||||
}
|
||||
reward := tx.Reward(baseFee)
|
||||
require.Equal(t, 0, reward.Int.Cmp(tc.answer.Int), reward, tc.answer)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRewardPercentiles(t *testing.T) {
|
||||
testcases := []struct {
|
||||
percentiles []float64
|
||||
txGasRewards gasRewardSorter
|
||||
answer []int64
|
||||
}{
|
||||
{
|
||||
percentiles: []float64{25, 50, 75},
|
||||
txGasRewards: []gasRewardTuple{},
|
||||
answer: []int64{0, 0, 0},
|
||||
},
|
||||
{
|
||||
percentiles: []float64{25, 50, 75, 100},
|
||||
txGasRewards: []gasRewardTuple{
|
||||
{gas: uint64(0), reward: ethtypes.EthBigInt(big.NewInt(300))},
|
||||
{gas: uint64(100), reward: ethtypes.EthBigInt(big.NewInt(200))},
|
||||
{gas: uint64(350), reward: ethtypes.EthBigInt(big.NewInt(100))},
|
||||
{gas: uint64(500), reward: ethtypes.EthBigInt(big.NewInt(600))},
|
||||
{gas: uint64(300), reward: ethtypes.EthBigInt(big.NewInt(700))},
|
||||
},
|
||||
answer: []int64{200, 700, 700, 700},
|
||||
},
|
||||
}
|
||||
for _, tc := range testcases {
|
||||
rewards, totalGasUsed := calculateRewardsAndGasUsed(tc.percentiles, tc.txGasRewards)
|
||||
gasUsed := uint64(0)
|
||||
for _, tx := range tc.txGasRewards {
|
||||
gasUsed += tx.gas
|
||||
}
|
||||
ans := []ethtypes.EthBigInt{}
|
||||
for _, bi := range tc.answer {
|
||||
ans = append(ans, ethtypes.EthBigInt(big.NewInt(bi)))
|
||||
}
|
||||
require.Equal(t, totalGasUsed, gasUsed)
|
||||
require.Equal(t, len(ans), len(tc.percentiles))
|
||||
require.Equal(t, ans, rewards)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user