Skip to content

Commit

Permalink
Merge pull request #5696 from filecoin-project/feat/eth-estimate-gas
Browse files Browse the repository at this point in the history
feat: compute a better gas limit for recursive external contract calls
  • Loading branch information
diwufeiwen authored Feb 7, 2023
2 parents 554a814 + 3186d16 commit 3c789e4
Show file tree
Hide file tree
Showing 29 changed files with 234 additions and 54 deletions.
2 changes: 1 addition & 1 deletion app/submodule/chain/account_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ func (accountAPI *accountAPI) StateAccountKey(ctx context.Context, addr address.
if err != nil {
return address.Undef, fmt.Errorf("loading tipset %s: %w", tsk, err)
}
return accountAPI.chain.Stmgr.ResolveToKeyAddress(ctx, addr, ts)
return accountAPI.chain.Stmgr.ResolveToDeterministicAddress(ctx, addr, ts)
}
2 changes: 1 addition & 1 deletion app/submodule/chain/chaininfo_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ func (cia *chainInfoAPI) ResolveToKeyAddr(ctx context.Context, addr address.Addr
if ts == nil {
ts = cia.chain.ChainReader.GetHead()
}
return cia.chain.Stmgr.ResolveToKeyAddress(ctx, addr, ts)
return cia.chain.Stmgr.ResolveToDeterministicAddress(ctx, addr, ts)
}

// ************Drand****************//
Expand Down
133 changes: 131 additions & 2 deletions app/submodule/eth/eth_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@ import (
builtintypes "github.com/filecoin-project/go-state-types/builtin"
"github.com/filecoin-project/go-state-types/builtin/v10/eam"
"github.com/filecoin-project/go-state-types/builtin/v10/evm"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/venus/pkg/chain"
"github.com/filecoin-project/venus/pkg/constants"
"github.com/filecoin-project/venus/pkg/crypto"
"github.com/filecoin-project/venus/pkg/ethhashlookup"
"github.com/filecoin-project/venus/pkg/events"
"github.com/filecoin-project/venus/pkg/fork"
"github.com/filecoin-project/venus/pkg/messagepool"
"github.com/filecoin-project/venus/pkg/statemanger"
"github.com/filecoin-project/venus/pkg/vm"
"github.com/filecoin-project/venus/pkg/vm/vmcontext"
"github.com/filecoin-project/venus/venus-shared/actors"
Expand Down Expand Up @@ -751,12 +754,138 @@ func (a *ethAPI) EthEstimateGas(ctx context.Context, tx types.EthCall) (types.Et
// gas estimation actually run.
msg.GasLimit = 0

msg, err = a.mpool.GasEstimateMessageGas(ctx, msg, nil, types.EmptyTSK)
ts, err := a.chain.ChainHead(ctx)
if err != nil {
return types.EthUint64(0), err
}
msg, err = a.mpool.GasEstimateMessageGas(ctx, msg, nil, ts.Key())
if err != nil {
return types.EthUint64(0), fmt.Errorf("failed to estimate gas: %w", err)
}

expectedGas, err := ethGasSearch(ctx, a.em.chainModule.Stmgr, a.em.mpoolModule.MPool, msg, ts)
if err != nil {
log.Errorw("expected gas", "err", err)
}

return types.EthUint64(expectedGas), nil
}

// gasSearch does an exponential search to find a gas value to execute the
// message with. It first finds a high gas limit that allows the message to execute
// by doubling the previous gas limit until it succeeds then does a binary
// search till it gets within a range of 1%
func gasSearch(
ctx context.Context,
smgr *statemanger.Stmgr,
msgIn *types.Message,
priorMsgs []types.ChainMsg,
ts *types.TipSet,
) (int64, error) {
msg := *msgIn

high := msg.GasLimit
low := msg.GasLimit

canSucceed := func(limit int64) (bool, error) {
msg.GasLimit = limit

res, err := smgr.CallWithGas(ctx, &msg, priorMsgs, ts)
if err != nil {
return false, fmt.Errorf("CallWithGas failed: %w", err)
}

if res.Receipt.ExitCode.IsSuccess() {
return true, nil
}

return false, nil
}

for {
ok, err := canSucceed(high)
if err != nil {
return -1, fmt.Errorf("searching for high gas limit failed: %w", err)
}
if ok {
break
}

low = high
high = high * 2

if high > constants.BlockGasLimit {
high = constants.BlockGasLimit
break
}
}

checkThreshold := high / 100
for (high - low) > checkThreshold {
median := (low + high) / 2
ok, err := canSucceed(median)
if err != nil {
return -1, fmt.Errorf("searching for optimal gas limit failed: %w", err)
}

if ok {
high = median
} else {
low = median
}

checkThreshold = median / 100
}

return high, nil
}

func traceContainsExitCode(et types.ExecutionTrace, ex exitcode.ExitCode) bool {
if et.MsgRct.ExitCode == ex {
return true
}

for _, et := range et.Subcalls {
if traceContainsExitCode(et, ex) {
return true
}
}

return false
}

// ethGasSearch executes a message for gas estimation using the previously estimated gas.
// If the message fails due to an out of gas error then a gas search is performed.
// See gasSearch.
func ethGasSearch(
ctx context.Context,
stmgr *statemanger.Stmgr,
mpool *messagepool.MessagePool,
msgIn *types.Message,
ts *types.TipSet,
) (int64, error) {
msg := *msgIn
currTS := ts

res, priorMsgs, ts, err := mpool.GasEstimateCallWithGas(ctx, &msg, currTS)
if err != nil {
return -1, fmt.Errorf("gas estimation failed: %w", err)
}

if res.MsgRct.ExitCode.IsSuccess() {
return msg.GasLimit, nil
}
if traceContainsExitCode(res.ExecutionTrace, exitcode.SysErrOutOfGas) {
ret, err := gasSearch(ctx, stmgr, &msg, priorMsgs, ts)
if err != nil {
return -1, fmt.Errorf("gas estimation search failed: %w", err)
}

ret = int64(float64(ret) * mpool.GetConfig().GasLimitOverestimation)
return ret, nil
}

return types.EthUint64(msg.GasLimit), nil
return -1, fmt.Errorf("message execution failed: exit %s, reason: %s", res.MsgRct.ExitCode, res.Error)
}

func (a *ethAPI) EthCall(ctx context.Context, tx types.EthCall, blkParam string) (types.EthBytes, error) {
Expand Down
2 changes: 1 addition & 1 deletion app/submodule/mining/mining_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ func (miningAPI *MiningAPI) MinerGetBaseInfo(ctx context.Context, maddr address.
if err != nil {
return nil, fmt.Errorf("failed to load latest state: %v", err)
}
worker, err := st.ResolveToKeyAddr(ctx, info.Worker)
worker, err := st.ResolveToDeterministicAddress(ctx, info.Worker)
if err != nil {
return nil, fmt.Errorf("resolving worker address: %v", err)
}
Expand Down
2 changes: 1 addition & 1 deletion app/submodule/wallet/wallet_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func (walletAPI *WalletAPI) WalletDelete(ctx context.Context, addr address.Addre

// WalletSign signs the given bytes using the given address.
func (walletAPI *WalletAPI) WalletSign(ctx context.Context, k address.Address, msg []byte, meta types.MsgMeta) (*crypto.Signature, error) {
keyAddr, err := walletAPI.walletModule.Chain.Stmgr.ResolveToKeyAddress(ctx, k, nil)
keyAddr, err := walletAPI.walletModule.Chain.Stmgr.ResolveToDeterministicAddress(ctx, k, nil)
if err != nil {
return nil, fmt.Errorf("ResolveTokeyAddress failed:%v", err)
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/chain/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -1374,15 +1374,15 @@ func (store *Store) LookupID(ctx context.Context, ts *types.TipSet, addr address
return st.LookupID(addr)
}

// ResolveToKeyAddr get key address of specify address.
// ResolveToDeterministicAddress get key address of specify address.
// if ths addr is bls/secpk address, return directly, other get the pubkey and generate address
func (store *Store) ResolveToKeyAddr(ctx context.Context, ts *types.TipSet, addr address.Address) (address.Address, error) {
func (store *Store) ResolveToDeterministicAddress(ctx context.Context, ts *types.TipSet, addr address.Address) (address.Address, error) {
st, err := store.StateView(ctx, ts)
if err != nil {
return address.Undef, errors.Wrap(err, "failed to load latest state")
}

return st.ResolveToKeyAddr(ctx, addr)
return st.ResolveToDeterministicAddress(ctx, addr)
}

// StateView return state view at ts epoch
Expand Down
2 changes: 1 addition & 1 deletion pkg/consensus/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,7 @@ func (bv *BlockValidator) checkBlockMessages(ctx context.Context,

// Verify that all secp message signatures are correct
for i, msg := range blksecpMsgs {
signer, err := stateView.ResolveToKeyAddr(ctx, msg.Message.From)
signer, err := stateView.ResolveToDeterministicAddress(ctx, msg.Message.From)
if err != nil {
return errors.Wrapf(err, "failed to load signer address for %v", signer)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/consensusfault/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
)

type FaultStateView interface {
ResolveToKeyAddr(ctx context.Context, address address.Address) (address.Address, error)
ResolveToDeterministicAddress(ctx context.Context, address address.Address) (address.Address, error)
MinerInfo(ctx context.Context, maddr address.Address, nv network.Version) (*miner.MinerInfo, error)
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/fvm/fvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ func (x *FvmExtern) workerKeyAtLookback(ctx context.Context, minerID address.Add
if err != nil {
return address.Undef, 0, err
}
raddr, err := vm.ResolveToKeyAddr(ctx, st, info.Worker, cstWithGas)
raddr, err := vm.ResolveToDeterministicAddress(ctx, st, info.Worker, cstWithGas)
if err != nil {
return address.Undef, 0, err
}
Expand Down
54 changes: 52 additions & 2 deletions pkg/messagepool/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/filecoin-project/venus/pkg/constants"
"github.com/filecoin-project/venus/pkg/fork"
"github.com/filecoin-project/venus/pkg/vm"
"github.com/filecoin-project/venus/pkg/vm/vmcontext"
"github.com/filecoin-project/venus/venus-shared/actors/builtin"
"github.com/filecoin-project/venus/venus-shared/types"
)
Expand Down Expand Up @@ -202,7 +203,7 @@ func (mp *MessagePool) GasEstimateGasLimit(ctx context.Context, msgIn *types.Mes
msg.GasFeeCap = big.NewInt(int64(constants.MinimumBaseFee) + 1)
msg.GasPremium = big.NewInt(1)

fromA, err := mp.sm.ResolveToKeyAddress(ctx, msgIn.From, currTS)
fromA, err := mp.sm.ResolveToDeterministicAddress(ctx, msgIn.From, currTS)
if err != nil {
return -1, fmt.Errorf("getting key address: %w", err)
}
Expand All @@ -219,6 +220,55 @@ func (mp *MessagePool) GasEstimateGasLimit(ctx context.Context, msgIn *types.Mes
return mp.evalMessageGasLimit(ctx, msgIn, priorMsgs, ts)
}

// GasEstimateCallWithGas invokes a message "msgIn" on the earliest available tipset with pending
// messages in the message pool. The function returns the result of the message invocation, the
// pending messages, the tipset used for the invocation, and an error if occurred.
// The returned information can be used to make subsequent calls to CallWithGas with the same parameters.
func (mp *MessagePool) GasEstimateCallWithGas(
ctx context.Context,
msgIn *types.Message,
currTS *types.TipSet,
) (*types.InvocResult, []types.ChainMsg, *types.TipSet, error) {
msg := *msgIn
fromA, err := mp.sm.ResolveToDeterministicAddress(ctx, msgIn.From, currTS)
if err != nil {
return nil, []types.ChainMsg{}, nil, fmt.Errorf("getting key address: %w", err)
}

pending, ts := mp.PendingFor(ctx, fromA)
priorMsgs := make([]types.ChainMsg, 0, len(pending))
for _, m := range pending {
if m.Message.Nonce == msg.Nonce {
break
}
priorMsgs = append(priorMsgs, m)
}

// Try calling until we find a height with no migration.
var res *vmcontext.Ret
for {
res, err = mp.sm.CallWithGas(ctx, &msg, priorMsgs, ts)
if err != fork.ErrExpensiveFork {
break
}
ts, err = mp.api.ChainTipSet(ctx, ts.Parents())
if err != nil {
return nil, []types.ChainMsg{}, nil, fmt.Errorf("getting parent tipset: %w", err)
}
}
if err != nil {
return nil, []types.ChainMsg{}, nil, fmt.Errorf("CallWithGas failed: %w", err)
}

return &types.InvocResult{
MsgCid: msg.Cid(),
Msg: &msg,
MsgRct: &res.Receipt,
ExecutionTrace: res.GasTracker.ExecutionTrace,
Duration: res.Duration,
}, priorMsgs, ts, nil
}

func (mp *MessagePool) evalMessageGasLimit(ctx context.Context, msgIn *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet) (int64, error) {
msg := *msgIn
msg.GasLimit = constants.BlockGasLimit
Expand Down Expand Up @@ -364,7 +414,7 @@ func (mp *MessagePool) GasBatchEstimateMessageGas(ctx context.Context, estimateM
return nil, fmt.Errorf("getting tipset: %w", err)
}

fromA, err := mp.sm.ResolveToKeyAddress(ctx, estimateMessages[0].Msg.From, currTS)
fromA, err := mp.sm.ResolveToDeterministicAddress(ctx, estimateMessages[0].Msg.From, currTS)
if err != nil {
return nil, fmt.Errorf("getting key address: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/messagepool/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,15 +134,15 @@ func (mpp *mpoolProvider) StateAccountKeyAtFinality(ctx context.Context, addr ad
return address.Undef, fmt.Errorf("failed to load lookback tipset: %w", err)
}
}
return mpp.stmgr.ResolveToKeyAddress(ctx, addr, ts)
return mpp.stmgr.ResolveToDeterministicAddress(ctx, addr, ts)
}

func (mpp *mpoolProvider) StateNetworkVersion(ctx context.Context, height abi.ChainEpoch) network.Version {
return mpp.stmgr.GetNetworkVersion(ctx, height)
}

func (mpp *mpoolProvider) StateAccountKey(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) {
return mpp.stmgr.ResolveToKeyAddress(ctx, addr, ts)
return mpp.stmgr.ResolveToDeterministicAddress(ctx, addr, ts)
}

func (mpp *mpoolProvider) MessagesForBlock(ctx context.Context, h *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) {
Expand Down
7 changes: 4 additions & 3 deletions pkg/migration/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"testing"
"time"

"github.com/filecoin-project/venus/fixtures/networks"
"github.com/filecoin-project/venus/pkg/config"
Expand All @@ -30,9 +31,8 @@ func TestMigration(t *testing.T) {
for nt, paramsCfg := range cfgs {
cfg := config.NewDefaultConfig()
cfg.NetworkParams.NetworkType = nt
repoPath := t.TempDir()
assert.Nil(t, os.RemoveAll(repoPath))
t.Log(repoPath)
repoPath := filepath.Join(os.TempDir(), fmt.Sprintf("TestMigration%d", time.Now().UnixNano()))

assert.Nil(t, repo.InitFSRepo(repoPath, 0, cfg))

assert.Nil(t, TryToMigrate(repoPath))
Expand All @@ -41,6 +41,7 @@ func TestMigration(t *testing.T) {
newCfg := fsRepo.Config()
assert.Equal(t, paramsCfg.NetworkType, newCfg.NetworkParams.NetworkType)
assert.EqualValuesf(t, config.NewDefaultConfig().NetworkParams.ForkUpgradeParam, newCfg.NetworkParams.ForkUpgradeParam, fmt.Sprintf("current network type %d", paramsCfg.NetworkType))
assert.NoError(t, fsRepo.Close())

cfgTmp, err := config.ReadFile(filepath.Join(repoPath, "config.json"))
assert.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/paychmgr/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (sm *mockStateManager) setPaychState(a address.Address, actor *types.Actor,
sm.paychState[a] = mockPchState{actor, state}
}

func (sm *mockStateManager) ResolveToKeyAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) {
func (sm *mockStateManager) ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) {
sm.lk.Lock()
defer sm.lk.Unlock()
keyAddr, ok := sm.accountState[addr]
Expand Down
2 changes: 1 addition & 1 deletion pkg/paychmgr/paych.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ func (ca *channelAccessor) checkVoucherValidUnlocked(ctx context.Context, ch add
return nil, err
}

from, err := ca.api.ResolveToKeyAddress(ctx, f, nil)
from, err := ca.api.ResolveToDeterministicAddress(ctx, f, nil)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 3c789e4

Please sign in to comment.