fn check_action_sequence(actions: &[SyncAction]) -> anyhow::Result<()> {
        // Rules for the sequence:
        // 1. Must start with either `OpenBatch` or `L2Block`, both of which may be met only once.
        // 2. Followed by a sequence of `Tx` actions which consists of 0 or more elements.
        // 3. Must have either `SealL2Block` or `SealBatch` at the end.

        let mut opened = false;
        let mut l2_block_sealed = false;

        for action in actions {
            match action {
                SyncAction::OpenBatch { .. } | SyncAction::L2Block { .. } => {
                    anyhow::ensure!(!opened, "Unexpected OpenBatch / L2Block: {actions:?}");
                    opened = true;
                }
                SyncAction::Tx(_) => {
                    anyhow::ensure!(opened && !l2_block_sealed, "Unexpected Tx: {actions:?}");
                }
                SyncAction::SealL2Block | SyncAction::SealBatch => {
                    anyhow::ensure!(
                        opened && !l2_block_sealed,
                        "Unexpected SealL2Block / SealBatch: {actions:?}"
                    );
                    l2_block_sealed = true;
                }
            }
        }
        anyhow::ensure!(l2_block_sealed, "Incomplete sequence: {actions:?}");
        Ok(())
    }
Checks whether the action sequence is valid.
Returned error is meant to be used as a panic message, since an invalid sequence represents an unrecoverable
error. This function itself does not panic for the ease of testing.
core/node/node_sync/src/sync_action.rs