Restore block_hash to Batch

It's not only helpful (to easily check where Serai's view of the external
network is) but it's necessary in case of a non-trivial chain fork to determine
which blockchain Serai considers canonical.
This commit is contained in:
Luke Parker
2024-12-31 18:10:47 -05:00
parent 2240a50a0c
commit 6272c40561
10 changed files with 55 additions and 87 deletions

View File

@@ -160,6 +160,7 @@ impl<D: Db> ContinuallyRan for CanonicalEventStream<D> {
network: batch_network, network: batch_network,
publishing_session, publishing_session,
id, id,
external_network_block_hash,
in_instructions_hash, in_instructions_hash,
in_instruction_results, in_instruction_results,
} = this_batch } = this_batch
@@ -173,6 +174,7 @@ impl<D: Db> ContinuallyRan for CanonicalEventStream<D> {
batch = Some(ExecutedBatch { batch = Some(ExecutedBatch {
id: *id, id: *id,
publisher: *publishing_session, publisher: *publishing_session,
external_network_block_hash: *external_network_block_hash,
in_instructions_hash: *in_instructions_hash, in_instructions_hash: *in_instructions_hash,
in_instruction_results: in_instruction_results in_instruction_results: in_instruction_results
.iter() .iter()

View File

@@ -277,23 +277,14 @@ pub async fn main_loop<
} => { } => {
let scanner = scanner.as_mut().unwrap(); let scanner = scanner.as_mut().unwrap();
if let Some(messages::substrate::ExecutedBatch { if let Some(batch) = batch {
id,
publisher,
in_instructions_hash,
in_instruction_results,
}) = batch
{
let key_to_activate = let key_to_activate =
KeyToActivate::<KeyFor<S>>::try_recv(txn.as_mut().unwrap()).map(|key| key.0); KeyToActivate::<KeyFor<S>>::try_recv(txn.as_mut().unwrap()).map(|key| key.0);
// This is a cheap call as it internally just queues this to be done later // This is a cheap call as it internally just queues this to be done later
let _: () = scanner.acknowledge_batch( let _: () = scanner.acknowledge_batch(
txn.take().unwrap(), txn.take().unwrap(),
id, batch,
publisher,
in_instructions_hash,
in_instruction_results,
/* /*
`acknowledge_batch` takes burns to optimize handling returns with standard `acknowledge_batch` takes burns to optimize handling returns with standard
payments. That's why handling these with a Batch (and not waiting until the payments. That's why handling these with a Batch (and not waiting until the

View File

@@ -188,6 +188,7 @@ pub mod substrate {
pub struct ExecutedBatch { pub struct ExecutedBatch {
pub id: u32, pub id: u32,
pub publisher: Session, pub publisher: Session,
pub external_network_block_hash: [u8; 32],
pub in_instructions_hash: [u8; 32], pub in_instructions_hash: [u8; 32],
pub in_instruction_results: Vec<InInstructionResult>, pub in_instruction_results: Vec<InInstructionResult>,
} }

View File

@@ -10,6 +10,7 @@ use serai_in_instructions_primitives::{MAX_BATCH_SIZE, Batch};
use primitives::{EncodableG, task::ContinuallyRan}; use primitives::{EncodableG, task::ContinuallyRan};
use crate::{ use crate::{
db::{Returnable, ScannerGlobalDb, InInstructionData, ScanToBatchDb, BatchData, BatchToReportDb}, db::{Returnable, ScannerGlobalDb, InInstructionData, ScanToBatchDb, BatchData, BatchToReportDb},
index,
scan::next_to_scan_for_outputs_block, scan::next_to_scan_for_outputs_block,
ScannerFeed, KeyFor, ScannerFeed, KeyFor,
}; };
@@ -100,10 +101,16 @@ impl<D: Db, S: ScannerFeed> ContinuallyRan for BatchTask<D, S> {
// If this block is notable, create the Batch(s) for it // If this block is notable, create the Batch(s) for it
if notable { if notable {
let network = S::NETWORK; let network = S::NETWORK;
let external_network_block_hash = index::block_id(&txn, block_number);
let mut batch_id = BatchDb::<S>::acquire_batch_id(&mut txn); let mut batch_id = BatchDb::<S>::acquire_batch_id(&mut txn);
// start with empty batch // start with empty batch
let mut batches = vec![Batch { network, id: batch_id, instructions: vec![] }]; let mut batches = vec![Batch {
network,
id: batch_id,
external_network_block_hash,
instructions: vec![],
}];
// We also track the return information for the InInstructions within a Batch in case // We also track the return information for the InInstructions within a Batch in case
// they error // they error
let mut return_information = vec![vec![]]; let mut return_information = vec![vec![]];
@@ -123,7 +130,12 @@ impl<D: Db, S: ScannerFeed> ContinuallyRan for BatchTask<D, S> {
batch_id = BatchDb::<S>::acquire_batch_id(&mut txn); batch_id = BatchDb::<S>::acquire_batch_id(&mut txn);
// make a new batch with this instruction included // make a new batch with this instruction included
batches.push(Batch { network, id: batch_id, instructions: vec![in_instruction] }); batches.push(Batch {
network,
id: batch_id,
external_network_block_hash,
instructions: vec![in_instruction],
});
// Since we're allocating a new batch, allocate a new set of return addresses for it // Since we're allocating a new batch, allocate a new set of return addresses for it
return_information.push(vec![]); return_information.push(vec![]);
} }

View File

@@ -11,9 +11,9 @@ use borsh::{BorshSerialize, BorshDeserialize};
use serai_db::{Get, DbTxn, Db}; use serai_db::{Get, DbTxn, Db};
use serai_primitives::{NetworkId, Coin, Amount}; use serai_primitives::{NetworkId, Coin, Amount};
use serai_validator_sets_primitives::Session;
use serai_coins_primitives::OutInstructionWithBalance; use serai_coins_primitives::OutInstructionWithBalance;
use messages::substrate::ExecutedBatch;
use primitives::{task::*, Address, ReceivedOutput, Block, Payment}; use primitives::{task::*, Address, ReceivedOutput, Block, Payment};
// Logic for deciding where in its lifetime a multisig is. // Logic for deciding where in its lifetime a multisig is.
@@ -444,29 +444,17 @@ impl<S: ScannerFeed> Scanner<S> {
/// `queue_burns`. Doing so will cause them to be executed multiple times. /// `queue_burns`. Doing so will cause them to be executed multiple times.
/// ///
/// The calls to this function must be ordered with regards to `queue_burns`. /// The calls to this function must be ordered with regards to `queue_burns`.
#[allow(clippy::too_many_arguments)]
pub fn acknowledge_batch( pub fn acknowledge_batch(
&mut self, &mut self,
mut txn: impl DbTxn, mut txn: impl DbTxn,
batch_id: u32, batch: ExecutedBatch,
publisher: Session,
in_instructions_hash: [u8; 32],
in_instruction_results: Vec<messages::substrate::InInstructionResult>,
burns: Vec<OutInstructionWithBalance>, burns: Vec<OutInstructionWithBalance>,
key_to_activate: Option<KeyFor<S>>, key_to_activate: Option<KeyFor<S>>,
) { ) {
log::info!("acknowledging batch {batch_id}"); log::info!("acknowledging batch {}", batch.id);
// Queue acknowledging this block via the Substrate task // Queue acknowledging this block via the Substrate task
substrate::queue_acknowledge_batch::<S>( substrate::queue_acknowledge_batch::<S>(&mut txn, batch, burns, key_to_activate);
&mut txn,
batch_id,
publisher,
in_instructions_hash,
in_instruction_results,
burns,
key_to_activate,
);
// Commit this txn so this data is flushed // Commit this txn so this data is flushed
txn.commit(); txn.commit();
// Then run the Substrate task // Then run the Substrate task

View File

@@ -6,16 +6,14 @@ use borsh::{BorshSerialize, BorshDeserialize};
use serai_db::{Get, DbTxn, create_db, db_channel}; use serai_db::{Get, DbTxn, create_db, db_channel};
use serai_coins_primitives::OutInstructionWithBalance; use serai_coins_primitives::OutInstructionWithBalance;
use serai_validator_sets_primitives::Session;
use messages::substrate::ExecutedBatch;
use crate::{ScannerFeed, KeyFor}; use crate::{ScannerFeed, KeyFor};
#[derive(BorshSerialize, BorshDeserialize)] #[derive(BorshSerialize, BorshDeserialize)]
struct AcknowledgeBatchEncodable { struct AcknowledgeBatchEncodable {
batch_id: u32, batch: ExecutedBatch,
publisher: Session,
in_instructions_hash: [u8; 32],
in_instruction_results: Vec<messages::substrate::InInstructionResult>,
burns: Vec<OutInstructionWithBalance>, burns: Vec<OutInstructionWithBalance>,
key_to_activate: Option<Vec<u8>>, key_to_activate: Option<Vec<u8>>,
} }
@@ -27,10 +25,7 @@ enum ActionEncodable {
} }
pub(crate) struct AcknowledgeBatch<S: ScannerFeed> { pub(crate) struct AcknowledgeBatch<S: ScannerFeed> {
pub(crate) batch_id: u32, pub(crate) batch: ExecutedBatch,
pub(crate) publisher: Session,
pub(crate) in_instructions_hash: [u8; 32],
pub(crate) in_instruction_results: Vec<messages::substrate::InInstructionResult>,
pub(crate) burns: Vec<OutInstructionWithBalance>, pub(crate) burns: Vec<OutInstructionWithBalance>,
pub(crate) key_to_activate: Option<KeyFor<S>>, pub(crate) key_to_activate: Option<KeyFor<S>>,
} }
@@ -64,20 +59,14 @@ impl<S: ScannerFeed> SubstrateDb<S> {
pub(crate) fn queue_acknowledge_batch( pub(crate) fn queue_acknowledge_batch(
txn: &mut impl DbTxn, txn: &mut impl DbTxn,
batch_id: u32, batch: ExecutedBatch,
publisher: Session,
in_instructions_hash: [u8; 32],
in_instruction_results: Vec<messages::substrate::InInstructionResult>,
burns: Vec<OutInstructionWithBalance>, burns: Vec<OutInstructionWithBalance>,
key_to_activate: Option<KeyFor<S>>, key_to_activate: Option<KeyFor<S>>,
) { ) {
Actions::send( Actions::send(
txn, txn,
&ActionEncodable::AcknowledgeBatch(AcknowledgeBatchEncodable { &ActionEncodable::AcknowledgeBatch(AcknowledgeBatchEncodable {
batch_id, batch,
publisher,
in_instructions_hash,
in_instruction_results,
burns, burns,
key_to_activate: key_to_activate.map(|key| key.to_bytes().as_ref().to_vec()), key_to_activate: key_to_activate.map(|key| key.to_bytes().as_ref().to_vec()),
}), }),
@@ -91,17 +80,11 @@ impl<S: ScannerFeed> SubstrateDb<S> {
let action_encodable = Actions::try_recv(txn)?; let action_encodable = Actions::try_recv(txn)?;
Some(match action_encodable { Some(match action_encodable {
ActionEncodable::AcknowledgeBatch(AcknowledgeBatchEncodable { ActionEncodable::AcknowledgeBatch(AcknowledgeBatchEncodable {
batch_id, batch,
publisher,
in_instructions_hash,
in_instruction_results,
burns, burns,
key_to_activate, key_to_activate,
}) => Action::AcknowledgeBatch(AcknowledgeBatch { }) => Action::AcknowledgeBatch(AcknowledgeBatch {
batch_id, batch,
publisher,
in_instructions_hash,
in_instruction_results,
burns, burns,
key_to_activate: key_to_activate.map(|key| { key_to_activate: key_to_activate.map(|key| {
let mut repr = <KeyFor<S> as GroupEncoding>::Repr::default(); let mut repr = <KeyFor<S> as GroupEncoding>::Repr::default();

View File

@@ -3,12 +3,12 @@ use core::{marker::PhantomData, future::Future};
use serai_db::{Get, DbTxn, Db}; use serai_db::{Get, DbTxn, Db};
use serai_coins_primitives::{OutInstruction, OutInstructionWithBalance}; use serai_coins_primitives::{OutInstruction, OutInstructionWithBalance};
use serai_validator_sets_primitives::Session;
use messages::substrate::ExecutedBatch;
use primitives::task::ContinuallyRan; use primitives::task::ContinuallyRan;
use crate::{ use crate::{
db::{ScannerGlobalDb, SubstrateToEventualityDb, AcknowledgedBatches}, db::{ScannerGlobalDb, SubstrateToEventualityDb, AcknowledgedBatches},
batch, ScannerFeed, KeyFor, index, batch, ScannerFeed, KeyFor,
}; };
mod db; mod db;
@@ -19,22 +19,11 @@ pub(crate) fn last_acknowledged_batch<S: ScannerFeed>(getter: &impl Get) -> Opti
} }
pub(crate) fn queue_acknowledge_batch<S: ScannerFeed>( pub(crate) fn queue_acknowledge_batch<S: ScannerFeed>(
txn: &mut impl DbTxn, txn: &mut impl DbTxn,
batch_id: u32, batch: ExecutedBatch,
publisher: Session,
in_instructions_hash: [u8; 32],
in_instruction_results: Vec<messages::substrate::InInstructionResult>,
burns: Vec<OutInstructionWithBalance>, burns: Vec<OutInstructionWithBalance>,
key_to_activate: Option<KeyFor<S>>, key_to_activate: Option<KeyFor<S>>,
) { ) {
SubstrateDb::<S>::queue_acknowledge_batch( SubstrateDb::<S>::queue_acknowledge_batch(txn, batch, burns, key_to_activate)
txn,
batch_id,
publisher,
in_instructions_hash,
in_instruction_results,
burns,
key_to_activate,
)
} }
pub(crate) fn queue_queue_burns<S: ScannerFeed>( pub(crate) fn queue_queue_burns<S: ScannerFeed>(
txn: &mut impl DbTxn, txn: &mut impl DbTxn,
@@ -73,40 +62,38 @@ impl<D: Db, S: ScannerFeed> ContinuallyRan for SubstrateTask<D, S> {
}; };
match action { match action {
Action::AcknowledgeBatch(AcknowledgeBatch { Action::AcknowledgeBatch(AcknowledgeBatch { batch, mut burns, key_to_activate }) => {
batch_id,
publisher,
in_instructions_hash,
in_instruction_results,
mut burns,
key_to_activate,
}) => {
// Check if we have the information for this batch // Check if we have the information for this batch
let Some(batch::BatchInfo { let Some(batch::BatchInfo {
block_number, block_number,
session_to_sign_batch, session_to_sign_batch,
external_key_for_session_to_sign_batch, external_key_for_session_to_sign_batch,
in_instructions_hash: expected_in_instructions_hash, in_instructions_hash,
}) = batch::take_info_for_batch::<S>(&mut txn, batch_id) }) = batch::take_info_for_batch::<S>(&mut txn, batch.id)
else { else {
// If we don't, drop this txn (restoring the action to the database) // If we don't, drop this txn (restoring the action to the database)
drop(txn); drop(txn);
return Ok(made_progress); return Ok(made_progress);
}; };
assert_eq!( assert_eq!(
publisher, session_to_sign_batch, batch.publisher, session_to_sign_batch,
"batch acknowledged on-chain was acknowledged by an unexpected publisher" "batch acknowledged on-chain was acknowledged by an unexpected publisher"
); );
assert_eq!( assert_eq!(
in_instructions_hash, expected_in_instructions_hash, batch.external_network_block_hash,
"batch acknowledged on-chain was distinct" index::block_id(&txn, block_number),
"batch acknowledged on-chain was for a distinct block"
);
assert_eq!(
batch.in_instructions_hash, in_instructions_hash,
"batch acknowledged on-chain had distinct InInstructions"
); );
SubstrateDb::<S>::set_last_acknowledged_batch(&mut txn, batch_id); SubstrateDb::<S>::set_last_acknowledged_batch(&mut txn, batch.id);
AcknowledgedBatches::send( AcknowledgedBatches::send(
&mut txn, &mut txn,
&external_key_for_session_to_sign_batch.0, &external_key_for_session_to_sign_batch.0,
batch_id, batch.id,
); );
// Mark we made progress and handle this // Mark we made progress and handle this
@@ -143,17 +130,17 @@ impl<D: Db, S: ScannerFeed> ContinuallyRan for SubstrateTask<D, S> {
// Return the balances for any InInstructions which failed to execute // Return the balances for any InInstructions which failed to execute
{ {
let return_information = batch::take_return_information::<S>(&mut txn, batch_id) let return_information = batch::take_return_information::<S>(&mut txn, batch.id)
.expect("didn't save the return information for Batch we published"); .expect("didn't save the return information for Batch we published");
assert_eq!( assert_eq!(
in_instruction_results.len(), batch.in_instruction_results.len(),
return_information.len(), return_information.len(),
"amount of InInstruction succeededs differed from amount of return information saved" "amount of InInstruction succeededs differed from amount of return information saved"
); );
// We map these into standard Burns // We map these into standard Burns
for (result, return_information) in for (result, return_information) in
in_instruction_results.into_iter().zip(return_information) batch.in_instruction_results.into_iter().zip(return_information)
{ {
if result == messages::substrate::InInstructionResult::Succeeded { if result == messages::substrate::InInstructionResult::Succeeded {
continue; continue;

View File

@@ -20,6 +20,7 @@ pub enum Event {
network: NetworkId, network: NetworkId,
publishing_session: Session, publishing_session: Session,
id: u32, id: u32,
external_network_block_hash: [u8; 32],
in_instructions_hash: [u8; 32], in_instructions_hash: [u8; 32],
in_instruction_results: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>, in_instruction_results: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
}, },

View File

@@ -63,6 +63,7 @@ pub mod pallet {
Batch { Batch {
network: NetworkId, network: NetworkId,
publishing_session: Session, publishing_session: Session,
external_network_block_hash: [u8; 32],
id: u32, id: u32,
in_instructions_hash: [u8; 32], in_instructions_hash: [u8; 32],
in_instruction_results: BitVec<u8, Lsb0>, in_instruction_results: BitVec<u8, Lsb0>,
@@ -356,6 +357,7 @@ pub mod pallet {
network: batch.network, network: batch.network,
publishing_session: if valid_by_prior { prior_session } else { current_session }, publishing_session: if valid_by_prior { prior_session } else { current_session },
id: batch.id, id: batch.id,
external_network_block_hash: batch.external_network_block_hash,
in_instructions_hash, in_instructions_hash,
in_instruction_results, in_instruction_results,
}); });

View File

@@ -106,6 +106,7 @@ pub struct InInstructionWithBalance {
pub struct Batch { pub struct Batch {
pub network: NetworkId, pub network: NetworkId,
pub id: u32, pub id: u32,
pub external_network_block_hash: [u8; 32],
pub instructions: Vec<InInstructionWithBalance>, pub instructions: Vec<InInstructionWithBalance>,
} }