Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ETH Call should use the parent state root of the subsequent tipset #6364

Merged
merged 2 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 15 additions & 19 deletions app/submodule/eth/eth_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -836,34 +836,30 @@ func (a *ethAPI) applyMessage(ctx context.Context, msg *types.Message, tsk types
return nil, fmt.Errorf("failed to got tipset %v", err)
}

applyTSMessages := true
if os.Getenv("VENUS_SKIP_APPLY_TS_MESSAGE_CALL_WITH_GAS") == "1" {
applyTSMessages = false
}

// Try calling until we find a height with no migration.
var res *types.InvocResult
for {
res, err = a.em.chainModule.Stmgr.CallWithGas(ctx, msg, []types.ChainMsg{}, ts, applyTSMessages)
if err != fork.ErrExpensiveFork {
break
}
ts, err = a.chain.ChainGetTipSet(ctx, ts.Parents())
if ts.Height() > 0 {
pts, err := a.chain.ChainGetTipSet(ctx, ts.Parents())
if err != nil {
return nil, fmt.Errorf("getting parent tipset: %w", err)
return nil, fmt.Errorf("failed to find a non-forking epoch: %w", err)
}
// Check for expensive forks from the parents to the tipset, including nil tipsets
if a.em.chainModule.Fork.HasExpensiveForkBetween(pts.Height(), ts.Height()+1) {
return nil, fork.ErrExpensiveFork
}
}

st, err := a.em.chainModule.ChainReader.GetTipSetStateRoot(ctx, ts)
if err != nil {
return nil, fmt.Errorf("CallWithGas failed: %w", err)
return nil, fmt.Errorf("cannot get tipset state: %w", err)
}
if res.MsgRct == nil {
return nil, fmt.Errorf("no message receipt")
res, err := a.em.chainModule.Stmgr.ApplyOnStateWithGas(ctx, st, msg, ts)
if err != nil {
return nil, fmt.Errorf("ApplyWithGasOnState failed: %w", err)
}

if res.MsgRct.ExitCode.IsError() {
reason := parseEthRevert(res.MsgRct.Return)
return nil, fmt.Errorf("message execution failed: exit %s, revert reason: %s, vm error: %s", res.MsgRct.ExitCode, reason, res.Error)
}

return res, nil
}

Expand Down Expand Up @@ -1195,7 +1191,7 @@ func (a *ethAPI) EthTraceReplayBlockTransactions(ctx context.Context, blkNum str
TransactionHash: *txHash,
Trace: env.traces,
StateDiff: nil,
VmTrace: nil,
VMTrace: nil,
})
}

Expand Down
91 changes: 62 additions & 29 deletions pkg/statemanger/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ import (
"github.com/filecoin-project/venus/pkg/vm"
)

type execMessageStrategy int

const (
execNoMessages execMessageStrategy = iota // apply no prior or current tipset messages
execAllMessages // apply all prior and current tipset messages
execSameSenderMessages // apply all prior messages and any current tipset messages from the same sender
)

// Call applies the given message to the given tipset's parent state, at the epoch following the
// tipset's parent. In the presence of null blocks, the height at which the message is invoked may
// be less than the specified tipset.
Expand All @@ -47,12 +55,23 @@ func (s *Stmgr) Call(ctx context.Context, msg *types.Message, ts *types.TipSet)
msg.Value = types.NewInt(0)
}

return s.callInternal(ctx, msg, nil, ts, cid.Undef, s.GetNetworkVersion, false, false)
return s.callInternal(ctx, msg, nil, ts, cid.Undef, s.GetNetworkVersion, false, execSameSenderMessages)
}

// ApplyOnStateWithGas applies the given message on top of the given state root with gas tracing enabled
func (s *Stmgr) ApplyOnStateWithGas(ctx context.Context, stateCid cid.Cid, msg *types.Message, ts *types.TipSet) (*types.InvocResult, error) {
return s.callInternal(ctx, msg, nil, ts, stateCid, s.GetNetworkVersion, true, execNoMessages)
}

// CallWithGas calculates the state for a given tipset, and then applies the given message on top of that state.
func (s *Stmgr) CallWithGas(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet, applyTSMessages bool) (*types.InvocResult, error) {
return s.callInternal(ctx, msg, priorMsgs, ts, cid.Undef, s.GetNetworkVersion, true, applyTSMessages)
var strategy execMessageStrategy
if applyTSMessages {
strategy = execAllMessages
} else {
strategy = execSameSenderMessages
}
return s.callInternal(ctx, msg, priorMsgs, ts, cid.Undef, s.GetNetworkVersion, true, strategy)
}

// CallAtStateAndVersion allows you to specify a message to execute on the given stateCid and network version.
Expand All @@ -64,13 +83,21 @@ func (s *Stmgr) CallAtStateAndVersion(ctx context.Context, msg *types.Message, s
return v
}

return s.callInternal(ctx, msg, nil, nil, stateCid, nvGetter, true, false)
return s.callInternal(ctx, msg, nil, nil, stateCid, nvGetter, true, execSameSenderMessages)
}

// - If no tipset is specified, the first tipset without an expensive migration or one in its parent is used.
// - If executing a message at a given tipset or its parent would trigger an expensive migration, the call will
// fail with ErrExpensiveFork.
func (s *Stmgr) callInternal(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet, stateCid cid.Cid, nvGetter chain.NetworkVersionGetter, checkGas, applyTSMessages bool) (*types.InvocResult, error) {
func (s *Stmgr) callInternal(ctx context.Context,
msg *types.Message,
priorMsgs []types.ChainMsg,
ts *types.TipSet,
stateCid cid.Cid,
nvGetter chain.NetworkVersionGetter,
checkGas bool,
strategy execMessageStrategy,
) (*types.InvocResult, error) {
ctx, span := trace.StartSpan(ctx, "statemanager.callInternal")
defer span.End()

Expand Down Expand Up @@ -116,22 +143,6 @@ func (s *Stmgr) callInternal(ctx context.Context, msg *types.Message, priorMsgs
if stateCid == cid.Undef {
stateCid = ts.ParentState()
}
tsMsgs, err := s.ms.MessagesForTipset(ts)
if err != nil {
return nil, fmt.Errorf("failed to lookup messages for parent tipset: %w", err)
}
if applyTSMessages {
priorMsgs = append(tsMsgs, priorMsgs...)
} else {
var filteredTSMsgs []types.ChainMsg
for _, tsMsg := range tsMsgs {
//TODO we should technically be normalizing the filecoin address of from when we compare here
if tsMsg.VMMessage().From == msg.VMMessage().From {
filteredTSMsgs = append(filteredTSMsgs, tsMsg)
}
}
priorMsgs = append(filteredTSMsgs, priorMsgs...)
}

// Technically, the tipset we're passing in here should be ts+1, but that may not exist.
stateCid, err = s.fork.HandleStateForks(ctx, stateCid, ts.Height(), ts)
Expand Down Expand Up @@ -176,18 +187,40 @@ func (s *Stmgr) callInternal(ctx context.Context, msg *types.Message, priorMsgs
if err != nil {
return nil, fmt.Errorf("failed to set up vm: %w", err)
}
for i, m := range priorMsgs {
_, err = vmi.ApplyMessage(ctx, m)

switch strategy {
case execNoMessages:
// Do nothing
case execAllMessages, execSameSenderMessages:
tsMsgs, err := s.ms.MessagesForTipset(ts)
if err != nil {
return nil, fmt.Errorf("applying prior message (%d, %s): %w", i, m.Cid(), err)
return nil, fmt.Errorf("failed to lookup messages for parent tipset: %w", err)
}
if strategy == execAllMessages {
priorMsgs = append(tsMsgs, priorMsgs...)
} else if strategy == execSameSenderMessages {
var filteredTSMsgs []types.ChainMsg
for _, tsMsg := range tsMsgs {
//TODO we should technically be normalizing the filecoin address of from when we compare here
if tsMsg.VMMessage().From == msg.VMMessage().From {
filteredTSMsgs = append(filteredTSMsgs, tsMsg)
}
}
priorMsgs = append(filteredTSMsgs, priorMsgs...)
}
for i, m := range priorMsgs {
_, err = vmi.ApplyMessage(ctx, m)
if err != nil {
return nil, fmt.Errorf("applying prior message (%d, %s): %w", i, m.Cid(), err)
}
}
}

// We flush to get the VM's view of the state tree after applying the above messages
// This is needed to get the correct nonce from the actor state to match the VM
stateCid, err = vmi.Flush(ctx)
if err != nil {
return nil, fmt.Errorf("flushing vm: %w", err)
// We flush to get the VM's view of the state tree after applying the above messages
// This is needed to get the correct nonce from the actor state to match the VM
stateCid, err = vmi.Flush(ctx)
if err != nil {
return nil, fmt.Errorf("flushing vm: %w", err)
}
}

st, err := tree.LoadState(ctx, cbor.NewCborStore(buffStore), stateCid)
Expand Down
2 changes: 1 addition & 1 deletion venus-shared/actors/types/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,7 @@ type EthTraceReplayBlockTransaction struct {
StateDiff *string `json:"stateDiff"`
Trace []*EthTrace `json:"trace"`
TransactionHash EthHash `json:"transactionHash"`
VmTrace *string `json:"vmTrace"`
VMTrace *string `json:"vmTrace"`
}

type EthTraceTransaction struct {
Expand Down
1 change: 1 addition & 0 deletions venus-shared/compatible-checks/api-diff.txt
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ github.com/filecoin-project/venus/venus-shared/api/chain/v1.FullNode <> github.c
+ Concurrent
- CreateBackup
- Discover
> EthTraceReplayBlockTransactions {[func(context.Context, string, []string) ([]*types.EthTraceReplayBlockTransaction, error) <> func(context.Context, string, []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error)] base=func out type: #0 input; nested={[[]*types.EthTraceReplayBlockTransaction <> []*ethtypes.EthTraceReplayBlockTransaction] base=slice element; nested={[*types.EthTraceReplayBlockTransaction <> *ethtypes.EthTraceReplayBlockTransaction] base=pointed type; nested={[types.EthTraceReplayBlockTransaction <> ethtypes.EthTraceReplayBlockTransaction] base=struct field; nested={[types.EthTraceReplayBlockTransaction <> ethtypes.EthTraceReplayBlockTransaction] base=exported field name: #4 field, VMTrace != VmTrace; nested=nil}}}}}
+ GasBatchEstimateMessageGas
> GasEstimateMessageGas {[func(context.Context, *types.Message, *types.MessageSendSpec, types.TipSetKey) (*types.Message, error) <> func(context.Context, *types.Message, *api.MessageSendSpec, types.TipSetKey) (*types.Message, error)] base=func in type: #2 input; nested={[*types.MessageSendSpec <> *api.MessageSendSpec] base=pointed type; nested={[types.MessageSendSpec <> api.MessageSendSpec] base=struct field; nested={[types.MessageSendSpec <> api.MessageSendSpec] base=exported field name: #1 field, GasOverEstimation != MsgUuid; nested=nil}}}}
+ GetActor
Expand Down
Loading