-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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 RPC API: ETH Call should use the parent state root of the subsequent tipset #11905
base: master
Are you sure you want to change the base?
Conversation
@aarshkshah1992 - can you please assign a reviewer? |
Well, this is bringing back past nightmares with respect to #10216. |
@Stebalien Are you saying that this wont work ? |
node/impl/full/eth.go
Outdated
// Try calling until we find a height with no migration. | ||
for { | ||
res, err = a.StateManager.CallWithGas(ctx, msg, []types.ChainMsg{}, ts, applyTsMessages) | ||
st, _, err := a.StateManager.TipSetState(ctx, ts) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Working through the logic here - TipSetState
will end up executing the messages on top of parent state if the requested tipset doesn't already have a height+1 not on a different fork that we can use to get the state from.
When you get down to the bottom of tipSetState()
, having failed to find a valid existing height+1 state, you run ExecuteTipSet
, which itself will call HandleStateForks
which the callInternal()
code above in this PR does so a bunch of work to avoid calling if it can tell that there's an "expensive" migration.
So we might need to port that logic down to here to handle the expensive-migration case.
But other than that, I think this results in roughly the same outcome of having all current tipset messages applied to produce a new state and then applying only the requested msg
on top of that.
I'm not sure if/how null rounds come in to play with all of this though, maybe it doesn't matter as per the discussion in #11205.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that TipSetState
already handles state forks for us, can we just not re-do it in callInternal
for our use case ? We can control that behaviour in callInternal
with a flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
already handles state forks for us,
but that's the problem, imagine if we did this across an expensive migration boundary and triggered a migration just to get the state at this tipset.
All of this logic exists to check that condition:
Lines 104 to 132 in 187e837
if ts == nil { | |
ts = sm.cs.GetHeaviestTipSet() | |
// Search back till we find a height with no fork, or we reach the beginning. | |
// We need the _previous_ height to have no fork, because we'll | |
// run the fork logic in `sm.TipSetState`. We need the _current_ | |
// height to have no fork, because we'll run it inside this | |
// function before executing the given message. | |
for ts.Height() > 0 { | |
pts, err = sm.cs.GetTipSetFromKey(ctx, ts.Parents()) | |
if err != nil { | |
return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err) | |
} | |
// Checks for expensive forks from the parents to the tipset, including nil tipsets | |
if !sm.hasExpensiveForkBetween(pts.Height(), ts.Height()+1) { | |
break | |
} | |
ts = pts | |
} | |
} else if ts.Height() > 0 { | |
pts, err = sm.cs.GetTipSetFromKey(ctx, ts.Parents()) | |
if err != nil { | |
return nil, xerrors.Errorf("failed to find a non-forking epoch: %w", err) | |
} | |
if sm.hasExpensiveForkBetween(pts.Height(), ts.Height()+1) { | |
return nil, ErrExpensiveFork | |
} | |
} |
But that's only applied after you've potentially executed that tipset because TipSetState
can result in an ExecuteTipSet
if it doesn't already have it handy, that executes ApplyBlocks
which in turn runs HandleStateForks
.
So I think, if you run this, it'll still do what it says in the comment at the top of the for
("Try calling until we find a height with no migration."), but it'll actually perform a migration before it checks whether there's an expensive migration.
I believe what we want is to run an hasExpensiveForkBetween(pts.Height(), ts.Height()+1)
ourselves and walk back up parents until it's false
. But currently hasExpensiveForkBetween
is a private method of StateManager
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahaa good catch @rvagg I have fixed this.
Hey @raulk This PR needs a review from someone who understands the ETH RPC stack really well. But @Stebalien is fully focused on F3 currently. Would you or Mikers or Fridrick be able to help review this by any chance ? |
@rvagg Have addressed both your review comments. Please can you take one final look at the PR ? |
@rvagg @aarshkshah1992 can we get a status update here? What's needed to get this fully reviewed? |
node/impl/full/eth.go
Outdated
applyTsMessages := true | ||
if os.Getenv("LOTUS_SKIP_APPLY_TS_MESSAGE_CALL_WITH_GAS") == "1" { | ||
applyTsMessages = false | ||
// Walk back until we find a tipset where no state migration is needed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused. Unless I'm misreading this, wouldn't this make the behaviour non-deterministic for an Eth user agent? An Eth wallet/library/dapp can't know if a given Filecoin epoch happened to run an "expensive fork". How is it even useful for a user agent to have its message applied on an epoch prior to the requested epoch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@raulk Have removed this. If the user specifies a tipset that causes an expensive fork, we simply error out.
Note: The erroring out on an expensive fork is pre-existing behaviour that we are not changing with this PR.
@momack2 just waiting on expert eyes to review so the blind don't lead the blind down a dodgy alleyway |
Co-authored-by: Rod Vagg <rod@vagg.org>
All checks have completed ❌ Failed Test / Test (itest-gateway) (pull_request) |
@raulk I agree. We have removed the looping so if the user specifies a tipset that can cause an expensive fork, we just error out the request(should be few of those as Rodd mentions). |
@momack2 We were waiting on a review from someone who understands the ETH<>Filecoin stack well. Now that Raul has reviewed it, we can land this. Have addressed the latest round of review. Will merge on a green tick. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't mind hearing @raulk's response to the fact that we error on a migration epoch if he has time to chime in. But, it's also something that can be easily changed at a future date if we hear objections after this lands.
It might be good to bring up in a sync meeting actually, just that specific point.
Otherwise this lgtm.
Related Issues
Closes #11205
Proposed Changes
ETH Call should use the parent state root of the subsequent tipset. This can be obtained from the
TipsetState
on theStatemanager
Additional Info
Checklist
Before you mark the PR ready for review, please make sure that:
<PR type>: <area>: <change being made>
fix: mempool: Introduce a cache for valid signatures
PR type
: fix, feat, build, chore, ci, docs, perf, refactor, revert, style, testarea
, e.g. api, chain, state, market, mempool, multisig, networking, paych, proving, sealing, wallet, deps