Skip to content

Conversation

@Fraser999
Copy link
Contributor

@Fraser999 Fraser999 commented Apr 28, 2025

Summary

This introduces CheckedAction, CheckedTransaction and Checked... wrappers for each action type.

Background

We want to have stricter checks performed when CheckTx is called on a new tx in order to avoid accepting txs which subsequently fail execution.

Checks on txs and their included actions are now split into two categories: mutable and immutable. On construction of a checked object, both types of checks are run. Subsequently, only the mutable checks are re-run, and this is currently only done at the start of executing the action/transaction.

Nonce and balance checks are excluded from these checks as the Mempool only puts forward for execution txs which should have valid nonces and sufficient account balances.

In order to ensure such checks have been performed, the sequencer now deals almost exclusively in checked flavours of actions and txs.

As a result of this, many tests which previously used txs had to be refactored to ensure that constructing the equivalent checked txs would succeed.

Changes

  • Introduced new Checked... types for actions and Transaction. Replaced usage throughout sequencer codebase of the unchecked flavours of these.
  • Changed ExpandedBlockData::user_submitted_transactions to UN-parsed transactions so that all checking can be done when constructing a CheckedTransaction.
  • Unified several test utilities into a single, top-level test_utils module. As well as pre-existing helper functions, there is now a Fixture for intializing a new chain along with several builders/initializers to aid in constructing objects to be used in tests. These are provided with doc comments which should explain how to use them.
  • Disabled benchmarks for now. These are already not functioning on MacOS (albeit there is an open PR fix(sequencer): allow benchmarks to run on macOS and fix issues #1842 to address this). I would prefer to handle updating the benchmarks as part of that PR rather than adding to this one.

Testing

  • Existing tests were generally refactored to retain their original intent.
  • Individual checked actions were provided with extensive unit test coverage. Each one generally tests the construction AND execution of the specific action. To generate failures during execution, the unit tests construct the action to be tested while the global state makes construction possible (e.g signer is sudo), and then modify the global state to make execution fail (e.g. change the sudo address). All such state changes are done by executing other checked actions rather than directly manipulating global state, to ensure that such state changes remain feasible in the future.
  • These new action unit tests made the tests in tests_execute_transaction.rs largely redundant, hence that file has been deleted. Coverage was also provided for IbcRelay where none existed before, but the setup difficulty for testing Ics20Withdrawal is such that only a subset of the code paths are tested (as is the case currently).
  • CheckedTransaction also has extensive unit test coverage for construction and execution, and covers any deleted tests from tests_execute_transaction.rs not already catered for by the action unit tests.

Changelogs

Changelogs updated.

Metrics

  • Added metrics:
    • ASTRIA_SEQUENCER_CHECK_TX_FAILED_ACTION_CHECKS
    • ASTRIA_SEQUENCER_CHECK_TX_DURATION_SECONDS_CHECK_ACTIONS
    • ASTRIA_SEQUENCER_CHECK_TX_DURATION_SECONDS_RECHECK
  • Renamed metric ASTRIA_SEQUENCER_CHECK_TX_REMOVED_TOO_LARGE to
    ASTRIA_SEQUENCER_CHECK_TX_FAILED_TX_TOO_LARGE
  • Deleted metrics:
    • ASTRIA_SEQUENCER_CHECK_TX_REMOVED_FAILED_STATELESS
    • ASTRIA_SEQUENCER_CHECK_TX_DURATION_SECONDS_PARSE_TX
    • ASTRIA_SEQUENCER_CHECK_TX_DURATION_SECONDS_CHECK_STATELESS
    • ASTRIA_SEQUENCER_CHECK_TX_DURATION_SECONDS_CHECK_TRACKED
    • ASTRIA_SEQUENCER_CHECK_TX_DURATION_SECONDS_CHECK_CHAIN_ID
    • ASTRIA_SEQUENCER_CHECK_TX_DURATION_SECONDS_CHECK_REMOVED
    • ASTRIA_SEQUENCER_CHECK_TX_DURATION_SECONDS_CONVERT_ADDRESS

Breaking Changelist

  • There should be no consensus, API or state-breaking changes other than the metrics listed above. All checks being done on actions and transactions remain the same, they are simply performed at a different stage of the flow now.

Related Issues

Closes #1958.
Closes #1620.

@github-actions github-actions bot added the sequencer pertaining to the astria-sequencer crate label Apr 28, 2025
@Fraser999 Fraser999 requested a review from ethanoroshiba April 28, 2025 23:53
Copy link
Contributor

@ethanoroshiba ethanoroshiba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly just suggestions and nits, but all the logic here looks solid and quite tidy to me. Nice work! I do think we should try to followup fairly quickly to take the legacy bits out, and it's also probably a good idea to keep this synced with Mainnet until it gets pushed to validators.

Copy link
Contributor

@SuperFluffy SuperFluffy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

astria-core changes look good to me - not yet approving because there seem to be some inaccuracies in the ABCI error codes that are mentioned in the changelog.

Wanna make sure we have a second look.

Copy link
Contributor

@SuperFluffy SuperFluffy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving for astria-core. Thank you for amending the changelog. :)

Copy link
Contributor

@ethanoroshiba ethanoroshiba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reaffirming approval with a couple comments

let tx_len = tx_bytes.len();
info!(transaction_hash = %tx_hash_base_64, "executing transaction");
let tx_len = tx.encoded_bytes().len();
info!(tx_id = %tx.id(), "executing transaction");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related to this PR but since we're touching this code, do you think it would be better if this was debug!? Just thinking since we're aiming to support hundreds up to a thousand TPS, the info logs could get messy very quickly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point, but I'd probably leave as is for now since as you mentioned, this PR doesn't add a new log line here. We can look to change in the future if/when it becomes a noticeable problem.

if let Err(outcome) = stateless_checks(tx.clone(), &state, metrics).await {
return outcome;
}
let checked_tx = match CheckedTransaction::new(tx_bytes, &state).await {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing this is best put for a followup, but it would be nice if upon a recheck and the transaction still existing in mempool, we took the CheckedTransaction straight from the mempool and re-ran mutable checks, instead of constructing a new one each time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, if the tx were in the mempool, we'd have returned from this function just before here, so we don't actually construct a new one in that case.

We might want to change the behaviour in the future so that check_tx does actually rerun the mutable checks, in which case we'd certainly want to do as you suggest.

return CheckTxOutcome::FailedChecks(error);
}
};
metrics.record_check_tx_duration_seconds_check_actions(tx_status_end.elapsed());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the metric and function names seem to be a bit at odds here, since the metric would imply the amount of time that the function takes to complete (as it's called check_tx). Maybe it would be best to rename one or the other?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed offline, this name is probably ok as is - we have a few metrics all with similar prefixes.

let expanded_block_data =
ExpandedBlockData::new_from_typed_data(&data, with_extended_commit_info).unwrap();

let rollup_data_bytes = sequence_data
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a duplicate of rollup_data_bytes defined previously?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It more or less is. The first one (which I should probably rename for clarity) is an iterator over (&RollupId, &Bytes) while this one is a Vec of (RollupId, Bytes).

I changed this in 9bf25b6 to create the second instance using the first one so it's clear they're essentially the same.

Comment on lines +109 to +112
self.checked_bridge_lock
.run_mutable_checks(&state)
.await
.wrap_err("mutable checks for bridge lock failed for bridge transfer")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is invalid, as bridge transfers are intended to only be sent from bridge accounts. change the check here to bail if tx sender is not a bridge account

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's incorrect. self.checked_bridge_lock is an instance of CheckedBridgeLockImpl<false>, and CheckedBridgeLockImpl<false>::run_mutable_checks skips checking for the signer being a bridge account altogether.

The line above (self.checked_bridge_unlock.run_mutable_checks) actually ensures the signer is the authorized bridge account withdrawer, which implicitly ensures the signer is also a bridge account afaik.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah i missed that this was on CheckedBridgeLockImpl<false>, sorry! however this should still check that the receiver of the transfer is a bridge account as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's covered when we construct the CheckedBridgeTransfer. It's calling CheckedBridgeLockImpl::<false>::new, which calls state.get_bridge_account_ibc_asset(&action.to) which ensures the receiver is a bridge account.

This is deemed an immutable check rather than part of the mutable checks since we're only interested in checks which can mutate from passing to failing. We don't care about the other way round since the CheckedAction will fail to be constructed if the checks fail.

Since there's no way to "unset" or uninitialize a bridge account, then this check that the receiver is a bridge account only needs to be run once, during construction.

@Fraser999 Fraser999 enabled auto-merge May 12, 2025 22:42
@Fraser999 Fraser999 added this pull request to the merge queue May 13, 2025
Merged via the queue into main with commit 5f428a9 May 13, 2025
51 checks passed
@Fraser999 Fraser999 deleted the fraser/1958-check-tx branch May 13, 2025 03:24
github-merge-queue bot pushed a commit that referenced this pull request May 16, 2025
## Summary
This updates the sequencer benchmarks to actually run on macOS, and
fixes a couple of issues around the mempool benchmarks.

## Background
The benchmarks could previously only be run on Linux. Since they're also
not being run regularly, they got broken at some point. They were
further disabled during the large PR #2142.

Since I was working on fixing them anyway, I took the opportunity to
improve the initialization of the test data. This has resulted in the
total time to execute the full mempool suite dropping from ~6.5 mins to
~1.5 mins and means all tests are now able to complete 100 iterations
(several could only manage one iteration previously).

## Changes
- Used an item from the `astria-sequencer` crate in the benchmark's
`main` function to resolve the issue of the tests not running on macOS.
- Fixed an issue where the tests were reusing identical txs, causing
initialization to fail with an `InsertionError::AlreadyPresent` error.
- Memoized test data to reduce test initialization times.
- Refactored `PendingTransactionsForAccount::find_demotables` to use an
existing method, deduplicating code.
- Refactored tests to use updated test utilities.

## Testing
Confirmed the benchmarks ran on macOS locally. The `app` ones take a
very long time to run and this should be investigated and fixed. To run
only the mempool ones:
```
cargo bench --features=benchmark -qp astria-sequencer mempool
```

## Changelogs
No updates required.

## Related Issues
Closes #1355.
ethanoroshiba pushed a commit that referenced this pull request May 20, 2025
## Summary
This updates the sequencer benchmarks to actually run on macOS, and
fixes a couple of issues around the mempool benchmarks.

## Background
The benchmarks could previously only be run on Linux. Since they're also
not being run regularly, they got broken at some point. They were
further disabled during the large PR #2142.

Since I was working on fixing them anyway, I took the opportunity to
improve the initialization of the test data. This has resulted in the
total time to execute the full mempool suite dropping from ~6.5 mins to
~1.5 mins and means all tests are now able to complete 100 iterations
(several could only manage one iteration previously).

## Changes
- Used an item from the `astria-sequencer` crate in the benchmark's
`main` function to resolve the issue of the tests not running on macOS.
- Fixed an issue where the tests were reusing identical txs, causing
initialization to fail with an `InsertionError::AlreadyPresent` error.
- Memoized test data to reduce test initialization times.
- Refactored `PendingTransactionsForAccount::find_demotables` to use an
existing method, deduplicating code.
- Refactored tests to use updated test utilities.

## Testing
Confirmed the benchmarks ran on macOS locally. The `app` ones take a
very long time to run and this should be investigated and fixed. To run
only the mempool ones:
```
cargo bench --features=benchmark -qp astria-sequencer mempool
```

## Changelogs
No updates required.

## Related Issues
Closes #1355.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sequencer pertaining to the astria-sequencer crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make CheckTx checks stricter sequencer: use address bytes directly in handle_check_tx() logic

5 participants