Skip to content

Commit 4828757

Browse files
fix: ETH RPC API: ETH Call should use the parent state root of the subsequent tipset (#11905)
* fix eth call * tests * changes as per review * changes as per review * Update node/impl/full/eth.go Co-authored-by: Rod Vagg <[email protected]> * fix as per review --------- Co-authored-by: Rod Vagg <[email protected]>
1 parent 06f8fdc commit 4828757

File tree

3 files changed

+74
-49
lines changed

3 files changed

+74
-49
lines changed

chain/stmgr/call.go

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ import (
2525
"github.com/filecoin-project/lotus/chain/vm"
2626
)
2727

28+
type execMessageStrategy int
29+
30+
const (
31+
execNoMessages execMessageStrategy = iota // apply no prior or current tipset messages
32+
execAllMessages // apply all prior and current tipset messages
33+
execSameSenderMessages // apply all prior messages and any current tipset messages from the same sender
34+
)
35+
2836
var ErrExpensiveFork = errors.New("refusing explicit call due to state fork at epoch")
2937

3038
// Call applies the given message to the given tipset's parent state, at the epoch following the
@@ -48,12 +56,24 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.
4856
msg.Value = types.NewInt(0)
4957
}
5058

51-
return sm.callInternal(ctx, msg, nil, ts, cid.Undef, sm.GetNetworkVersion, false, false)
59+
return sm.callInternal(ctx, msg, nil, ts, cid.Undef, sm.GetNetworkVersion, false, execSameSenderMessages)
60+
}
61+
62+
// ApplyOnStateWithGas applies the given message on top of the given state root with gas tracing enabled
63+
func (sm *StateManager) ApplyOnStateWithGas(ctx context.Context, stateCid cid.Cid, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) {
64+
return sm.callInternal(ctx, msg, nil, ts, stateCid, sm.GetNetworkVersion, true, execNoMessages)
5265
}
5366

5467
// CallWithGas calculates the state for a given tipset, and then applies the given message on top of that state.
5568
func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet, applyTsMessages bool) (*api.InvocResult, error) {
56-
return sm.callInternal(ctx, msg, priorMsgs, ts, cid.Undef, sm.GetNetworkVersion, true, applyTsMessages)
69+
var strategy execMessageStrategy
70+
if applyTsMessages {
71+
strategy = execAllMessages
72+
} else {
73+
strategy = execSameSenderMessages
74+
}
75+
76+
return sm.callInternal(ctx, msg, priorMsgs, ts, cid.Undef, sm.GetNetworkVersion, true, strategy)
5777
}
5878

5979
// CallAtStateAndVersion allows you to specify a message to execute on the given stateCid and network version.
@@ -64,14 +84,14 @@ func (sm *StateManager) CallAtStateAndVersion(ctx context.Context, msg *types.Me
6484
nvGetter := func(context.Context, abi.ChainEpoch) network.Version {
6585
return v
6686
}
67-
68-
return sm.callInternal(ctx, msg, nil, nil, stateCid, nvGetter, true, false)
87+
return sm.callInternal(ctx, msg, nil, nil, stateCid, nvGetter, true, execSameSenderMessages)
6988
}
7089

7190
// - If no tipset is specified, the first tipset without an expensive migration or one in its parent is used.
7291
// - If executing a message at a given tipset or its parent would trigger an expensive migration, the call will
7392
// fail with ErrExpensiveFork.
74-
func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet, stateCid cid.Cid, nvGetter rand.NetworkVersionGetter, checkGas, applyTsMessages bool) (*api.InvocResult, error) {
93+
func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet, stateCid cid.Cid,
94+
nvGetter rand.NetworkVersionGetter, checkGas bool, strategy execMessageStrategy) (*api.InvocResult, error) {
7595
ctx, span := trace.StartSpan(ctx, "statemanager.callInternal")
7696
defer span.End()
7797

@@ -95,7 +115,7 @@ func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, pr
95115
return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err)
96116
}
97117
// Checks for expensive forks from the parents to the tipset, including nil tipsets
98-
if !sm.hasExpensiveForkBetween(pts.Height(), ts.Height()+1) {
118+
if !sm.HasExpensiveForkBetween(pts.Height(), ts.Height()+1) {
99119
break
100120
}
101121

@@ -106,7 +126,7 @@ func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, pr
106126
if err != nil {
107127
return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err)
108128
}
109-
if sm.hasExpensiveForkBetween(pts.Height(), ts.Height()+1) {
129+
if sm.HasExpensiveForkBetween(pts.Height(), ts.Height()+1) {
110130
return nil, ErrExpensiveFork
111131
}
112132
}
@@ -117,24 +137,6 @@ func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, pr
117137
if stateCid == cid.Undef {
118138
stateCid = ts.ParentState()
119139
}
120-
tsMsgs, err := sm.cs.MessagesForTipset(ctx, ts)
121-
if err != nil {
122-
return nil, xerrors.Errorf("failed to lookup messages for parent tipset: %w", err)
123-
}
124-
125-
if applyTsMessages {
126-
priorMsgs = append(tsMsgs, priorMsgs...)
127-
} else {
128-
var filteredTsMsgs []types.ChainMsg
129-
for _, tsMsg := range tsMsgs {
130-
//TODO we should technically be normalizing the filecoin address of from when we compare here
131-
if tsMsg.VMMessage().From == msg.VMMessage().From {
132-
filteredTsMsgs = append(filteredTsMsgs, tsMsg)
133-
}
134-
}
135-
priorMsgs = append(filteredTsMsgs, priorMsgs...)
136-
}
137-
138140
// Technically, the tipset we're passing in here should be ts+1, but that may not exist.
139141
stateCid, err = sm.HandleStateForks(ctx, stateCid, ts.Height(), nil, ts)
140142
if err != nil {
@@ -169,18 +171,40 @@ func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, pr
169171
if err != nil {
170172
return nil, xerrors.Errorf("failed to set up vm: %w", err)
171173
}
172-
for i, m := range priorMsgs {
173-
_, err = vmi.ApplyMessage(ctx, m)
174+
175+
switch strategy {
176+
case execNoMessages:
177+
// Do nothing
178+
case execAllMessages, execSameSenderMessages:
179+
tsMsgs, err := sm.cs.MessagesForTipset(ctx, ts)
174180
if err != nil {
175-
return nil, xerrors.Errorf("applying prior message (%d, %s): %w", i, m.Cid(), err)
181+
return nil, xerrors.Errorf("failed to lookup messages for parent tipset: %w", err)
182+
}
183+
if strategy == execAllMessages {
184+
priorMsgs = append(tsMsgs, priorMsgs...)
185+
} else if strategy == execSameSenderMessages {
186+
var filteredTsMsgs []types.ChainMsg
187+
for _, tsMsg := range tsMsgs {
188+
//TODO we should technically be normalizing the filecoin address of from when we compare here
189+
if tsMsg.VMMessage().From == msg.VMMessage().From {
190+
filteredTsMsgs = append(filteredTsMsgs, tsMsg)
191+
}
192+
}
193+
priorMsgs = append(filteredTsMsgs, priorMsgs...)
194+
}
195+
for i, m := range priorMsgs {
196+
_, err = vmi.ApplyMessage(ctx, m)
197+
if err != nil {
198+
return nil, xerrors.Errorf("applying prior message (%d, %s): %w", i, m.Cid(), err)
199+
}
176200
}
177-
}
178201

179-
// We flush to get the VM's view of the state tree after applying the above messages
180-
// This is needed to get the correct nonce from the actor state to match the VM
181-
stateCid, err = vmi.Flush(ctx)
182-
if err != nil {
183-
return nil, xerrors.Errorf("flushing vm: %w", err)
202+
// We flush to get the VM's view of the state tree after applying the above messages
203+
// This is needed to get the correct nonce from the actor state to match the VM
204+
stateCid, err = vmi.Flush(ctx)
205+
if err != nil {
206+
return nil, xerrors.Errorf("flushing vm: %w", err)
207+
}
184208
}
185209

186210
stTree, err := state.LoadStateTree(cbor.NewCborStore(buffStore), stateCid)

chain/stmgr/forks.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ func (sm *StateManager) HandleStateForks(ctx context.Context, root cid.Cid, heig
227227
// Returns true executing tipsets between the specified heights would trigger an expensive
228228
// migration. NOTE: migrations occurring _at_ the target height are not included, as they're
229229
// executed _after_ the target height.
230-
func (sm *StateManager) hasExpensiveForkBetween(parent, height abi.ChainEpoch) bool {
230+
func (sm *StateManager) HasExpensiveForkBetween(parent, height abi.ChainEpoch) bool {
231231
for h := parent; h < height; h++ {
232232
if _, ok := sm.expensiveUpgrades[h]; ok {
233233
return true

node/impl/full/eth.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,25 +1028,26 @@ func (a *EthModule) applyMessage(ctx context.Context, msg *types.Message, tsk ty
10281028
return nil, xerrors.Errorf("cannot get tipset: %w", err)
10291029
}
10301030

1031-
applyTsMessages := true
1032-
if os.Getenv("LOTUS_SKIP_APPLY_TS_MESSAGE_CALL_WITH_GAS") == "1" {
1033-
applyTsMessages = false
1034-
}
1035-
1036-
// Try calling until we find a height with no migration.
1037-
for {
1038-
res, err = a.StateManager.CallWithGas(ctx, msg, []types.ChainMsg{}, ts, applyTsMessages)
1039-
if err != stmgr.ErrExpensiveFork {
1040-
break
1041-
}
1042-
ts, err = a.Chain.GetTipSetFromKey(ctx, ts.Parents())
1031+
if ts.Height() > 0 {
1032+
pts, err := a.Chain.GetTipSetFromKey(ctx, ts.Parents())
10431033
if err != nil {
1044-
return nil, xerrors.Errorf("getting parent tipset: %w", err)
1034+
return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err)
1035+
}
1036+
// Check for expensive forks from the parents to the tipset, including nil tipsets
1037+
if a.StateManager.HasExpensiveForkBetween(pts.Height(), ts.Height()+1) {
1038+
return nil, stmgr.ErrExpensiveFork
10451039
}
10461040
}
1041+
1042+
st, _, err := a.StateManager.TipSetState(ctx, ts)
1043+
if err != nil {
1044+
return nil, xerrors.Errorf("cannot get tipset state: %w", err)
1045+
}
1046+
res, err = a.StateManager.ApplyOnStateWithGas(ctx, st, msg, ts)
10471047
if err != nil {
1048-
return nil, xerrors.Errorf("CallWithGas failed: %w", err)
1048+
return nil, xerrors.Errorf("ApplyWithGasOnState failed: %w", err)
10491049
}
1050+
10501051
if res.MsgRct.ExitCode.IsError() {
10511052
reason := parseEthRevert(res.MsgRct.Return)
10521053
return nil, xerrors.Errorf("message execution failed: exit %s, revert reason: %s, vm error: %s", res.MsgRct.ExitCode, reason, res.Error)

0 commit comments

Comments
 (0)