-
Notifications
You must be signed in to change notification settings - Fork 289
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: add time to the sdk.Context used in PrepareProposal #2515
Conversation
// TestTimeInPrepareProposalContext checks for an edge case where the block time | ||
// needs to be included in the sdk.Context that is being used in the | ||
// antehandlers. If a time is not included in the context, then the second | ||
// transaction in this test will always be filtered out, result in vesting | ||
// accounts never being able to spend funds. | ||
func TestTimeInPrepareProposalContext(t *testing.T) { | ||
if testing.Short() { |
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.
we could have included this in the standard cosmos-sdk test that we're already running, however since the tx needs to be second it felt a bit too hacky to do that
Codecov Report
@@ Coverage Diff @@
## main #2515 +/- ##
==========================================
- Coverage 20.61% 20.58% -0.03%
==========================================
Files 131 131
Lines 15269 15273 +4
==========================================
- Hits 3147 3144 -3
- Misses 11819 11825 +6
- Partials 303 304 +1
|
Co-authored-by: Rootul P <rootulp@gmail.com>
app/prepare_proposal.go
Outdated
sdkCtx := app.NewProposalContext(core.Header{ | ||
ChainID: req.ChainId, | ||
Height: app.LastBlockHeight() + 1, | ||
Time: time.Now(), |
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.
There is a high possibility that time.Now() is greater than the block time proposed in the header (as it stems from the commit of the previous block). This could compromise the consistency property whereby a transaction seems valid in PrepareProposal but invalid in ProcessProposal. If the transaction isn't kicked from the mempool it could result in the chain halting (as proposers continue to propose a vesting transaction that they think is valid (it has a later time) but continues to get rejected (as validators are basing it off the last block time). Ideally we find a way to add the last block time to the context. If it's too difficult I would recommend setting Time to time.Now().Sub(TargetBlockTime)
as an extra precaution but if there are multi rounds the same consistency problem could occur
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.
good point! I totally blanked this morning on us running the entire antehandler on all txs in process proposal
we definitely shouldn't do this
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 think the only solution here is to get the time in the request from comet similar to how its done on celestia-core/main or comet
app/prepare_proposal.go
Outdated
sdkCtx := app.NewProposalContext(core.Header{ | ||
ChainID: req.ChainId, | ||
Height: app.LastBlockHeight() + 1, | ||
Time: time.Now(), |
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.
Yes, this must be the last block's time! Otherwise this is wrong. For example, when calculating the spendable amount, the only real reference the chain has is the last block, as the current block has not been finalized. Only use block time
This looks about right:
https://github.com/cosmos/cosmos-sdk/blob/c7e0bd7b54d037a36ef7d31cc89ecabdf2124913/baseapp/abci.go#L409-L416
But please double-check that the req
actually contains the correct time.
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.
Note: it does not seem to be the case that the correct time is set here:
https://github.com/celestiaorg/celestia-core/blob/d24c81afe0ecc4ccfbb7eb543a45a11794f04db4/state/execution.go#L118-L124
Only later in MakeBlock the time is set (correctly):
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.
Compare main
of vanilla cometBFT: https://github.com/cometbft/cometbft/blob/1c6f86c840c64f83f4409ccca6b664234866cc38/state/execution.go#L128C9-L139
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.
Thanks again @cmwaters and @liamsi for catching the time.Now bug, I don't know what I was thinking. Too many late nights deugging testground last week! 😅 . Per the first and second thread, the only solution to this is to use the same time that will be used in process proposal and deliver tx (the time in the header from the last commit). That's what's done in celestiaorg/celestia-core#1081, which should be merged before this PR. |
{ | ||
name: "create continuous vesting account with a start time in the future", | ||
msgFunc: func() (msgs []sdk.Msg, signer string) { | ||
_, _, err := cctx.Keyring.NewMnemonic(vestAccName, keyring.English, "", "", hd.Secp256k1) | ||
require.NoError(t, err) | ||
sendAcc := accounts[0] | ||
sendingAccAddr := testfactory.GetAddress(cctx.Keyring, sendAcc) | ||
vestAccAddr := testfactory.GetAddress(cctx.Keyring, vestAccName) | ||
msg := vestingtypes.NewMsgCreateVestingAccount( | ||
sendingAccAddr, | ||
vestAccAddr, | ||
sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(1000000))), | ||
time.Now().Unix(), | ||
time.Now().Add(time.Second*100).Unix(), | ||
false, | ||
) | ||
return []sdk.Msg{msg}, sendAcc | ||
}, | ||
expectedCode: abci.CodeTypeOK, | ||
}, | ||
{ | ||
name: "send funds from the vesting account after it has been created", | ||
msgFunc: func() (msgs []sdk.Msg, signer string) { | ||
sendAcc := accounts[1] | ||
sendingAccAddr := testfactory.GetAddress(cctx.Keyring, sendAcc) | ||
vestAccAddr := testfactory.GetAddress(cctx.Keyring, vestAccName) | ||
msg := banktypes.NewMsgSend( | ||
vestAccAddr, | ||
sendingAccAddr, | ||
sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(1))), | ||
) | ||
return []sdk.Msg{msg}, vestAccName | ||
}, | ||
expectedCode: abci.CodeTypeOK, | ||
}, | ||
} |
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.
one additional test we can do is to have a third test case where we attempt to send too much and expect it to fail. I have a commit with this ready, however I'm worried that it will cause flakiness do to the process requiring a specific time delay window.
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 don't think it will be flaky if you set the end time well in the future and try send all the funds
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.
well in the future and try send all the funds
yeah good point, sorry I was not clear enough. the thing that I was trying to test with the additional case here was not just that a tx would fail if there was not enough of a spendable balance, I think we have that somehwere already (#2004 iirc). That wouldn't be flakey.
I was trying to create a test that checks that puts us in the exact scenario we would get in if we used time.Now()
. Where a tx doesn't get filtered but is invalid.
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.
👏🏼
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.
Few minor comments but mostly looks good
// antehandlers. If a time is not included in the context, then the second | ||
// transaction in this test will always be filtered out, result in vesting | ||
// accounts never being able to spend funds. | ||
func TestTimeInPrepareProposalContext(t *testing.T) { |
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 feel like we defer to using integration tests too often when unit tests would suffice
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.
sendingAccAddr, | ||
vestAccAddr, | ||
sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(1000000))), | ||
time.Now().Unix(), |
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.
This is not a start time in the future as is stated in the test name
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.
will grab this in a followup
{ | ||
name: "create continuous vesting account with a start time in the future", | ||
msgFunc: func() (msgs []sdk.Msg, signer string) { | ||
_, _, err := cctx.Keyring.NewMnemonic(vestAccName, keyring.English, "", "", hd.Secp256k1) | ||
require.NoError(t, err) | ||
sendAcc := accounts[0] | ||
sendingAccAddr := testfactory.GetAddress(cctx.Keyring, sendAcc) | ||
vestAccAddr := testfactory.GetAddress(cctx.Keyring, vestAccName) | ||
msg := vestingtypes.NewMsgCreateVestingAccount( | ||
sendingAccAddr, | ||
vestAccAddr, | ||
sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(1000000))), | ||
time.Now().Unix(), | ||
time.Now().Add(time.Second*100).Unix(), | ||
false, | ||
) | ||
return []sdk.Msg{msg}, sendAcc | ||
}, | ||
expectedCode: abci.CodeTypeOK, | ||
}, | ||
{ | ||
name: "send funds from the vesting account after it has been created", | ||
msgFunc: func() (msgs []sdk.Msg, signer string) { | ||
sendAcc := accounts[1] | ||
sendingAccAddr := testfactory.GetAddress(cctx.Keyring, sendAcc) | ||
vestAccAddr := testfactory.GetAddress(cctx.Keyring, vestAccName) | ||
msg := banktypes.NewMsgSend( | ||
vestAccAddr, | ||
sendingAccAddr, | ||
sdk.NewCoins(sdk.NewCoin(app.BondDenom, sdk.NewInt(1))), | ||
) | ||
return []sdk.Msg{msg}, vestAccName | ||
}, | ||
expectedCode: abci.CodeTypeOK, | ||
}, | ||
} |
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 don't think it will be flaky if you set the end time well in the future and try send all the funds
## Overview I'm not sure if this fixes all of #2454, but it at least fixes one bug similar to it. The fix in this PR allows for the antehandlers that are ran during prepare proposal to have access to a time close to the actual block time. It does this by simply adding `time.Now` to the header used for antehandler state access. ## Checklist - [x] New and updated code has appropriate documentation - [x] New and updated code has new and/or updated testing - [x] Required CI checks are passing - [x] Visual proof for any user facing features like CLI or documentation updates - [x] Linked issues closed with keywords --------- Co-authored-by: Rootul P <rootulp@gmail.com> (cherry picked from commit 9617549) # Conflicts: # app/test/fuzz_abci_test.go
) (#2534) This is an automatic backport of pull request #2515 done by [Mergify](https://mergify.com). Cherry-pick of 9617549 has failed: ``` On branch mergify/bp/v1.x/pr-2515 Your branch is up to date with 'origin/v1.x'. You are currently cherry-picking commit 9617549. (fix conflicts and run "git cherry-pick --continue") (use "git cherry-pick --skip" to skip this patch) (use "git cherry-pick --abort" to cancel the cherry-pick operation) Changes to be committed: modified: Makefile modified: app/prepare_proposal.go new file: app/test/prepare_proposal_context_test.go modified: app/test/prepare_proposal_test.go modified: app/test/process_proposal_test.go modified: go.mod modified: go.sum Unmerged paths: (use "git add <file>..." to mark resolution) both modified: app/test/fuzz_abci_test.go ``` To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/github/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally --- <details> <summary>Mergify commands and options</summary> <br /> More conditions and actions can be found in the [documentation](https://docs.mergify.com/). You can also trigger Mergify actions by commenting on this pull request: - `@Mergifyio refresh` will re-evaluate the rules - `@Mergifyio rebase` will rebase this PR on its base branch - `@Mergifyio update` will merge the base branch into this PR - `@Mergifyio backport <destination>` will backport this PR on `<destination>` branch Additionally, on Mergify [dashboard](https://dashboard.mergify.com) you can: - look at your merge queues - generate the Mergify configuration with the config editor. Finally, you can contact us on https://mergify.com </details> --------- Co-authored-by: Evan Forbes <42654277+evan-forbes@users.noreply.github.com> Co-authored-by: evan-forbes <evan.samuel.forbes@gmail.com>
Overview
I'm not sure if this fixes all of #2454, but it at least fixes one bug similar to it.
The fix in this PR allows for the antehandlers that are ran during prepare proposal to have access to a time close to the actual block time. It does this by simply adding
time.Now
to the header used for antehandler state access.Checklist