Skip to content

Commit 07eb8a7

Browse files
authored
Merge transactions into a single collection in blocks. (#4272)
## Motivation Blocks contain operations and incoming message bundles as separate collections. It's impossible to process e.g. a message in between two operations. Also, transactions are not properly exposed in GraphQL. ## Proposal Merge the two collections, expose more information via GraphQL. (Note: This was almost entirely written using Claude Code.) ## Test Plan CI; tests have been updated. ## Release Plan - Nothing to do / These changes follow the usual release cycle. ## Links - Closes #1200 - [reviewer checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
1 parent 8f65a56 commit 07eb8a7

File tree

37 files changed

+812
-599
lines changed

37 files changed

+812
-599
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

linera-chain/src/block.rs

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use thiserror::Error;
2222
use crate::{
2323
data_types::{
2424
BlockExecutionOutcome, IncomingBundle, MessageBundle, OperationResult, OutgoingMessageExt,
25-
ProposedBlock,
25+
ProposedBlock, Transaction,
2626
},
2727
types::CertificateValue,
2828
};
@@ -258,15 +258,14 @@ impl<'de> Deserialize<'de> for Block {
258258
}
259259
let inner = Inner::deserialize(deserializer)?;
260260

261-
let bundles_hash = hashing::hash_vec(&inner.body.incoming_bundles);
261+
let transactions_hash = hashing::hash_vec(&inner.body.transactions);
262262
let messages_hash = hashing::hash_vec_vec(&inner.body.messages);
263263
let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
264264
inner: Cow::Borrowed(&inner.body.previous_message_blocks),
265265
});
266266
let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
267267
inner: Cow::Borrowed(&inner.body.previous_event_blocks),
268268
});
269-
let operations_hash = hashing::hash_vec(&inner.body.operations);
270269
let oracle_responses_hash = hashing::hash_vec_vec(&inner.body.oracle_responses);
271270
let events_hash = hashing::hash_vec_vec(&inner.body.events);
272271
let blobs_hash = hashing::hash_vec_vec(&inner.body.blobs);
@@ -280,8 +279,7 @@ impl<'de> Deserialize<'de> for Block {
280279
state_hash: inner.header.state_hash,
281280
previous_block_hash: inner.header.previous_block_hash,
282281
authenticated_signer: inner.header.authenticated_signer,
283-
bundles_hash,
284-
operations_hash,
282+
transactions_hash,
285283
messages_hash,
286284
previous_message_blocks_hash,
287285
previous_event_blocks_hash,
@@ -322,10 +320,8 @@ pub struct BlockHeader {
322320
pub authenticated_signer: Option<AccountOwner>,
323321

324322
// Inputs to the block, chosen by the block proposer.
325-
/// Cryptographic hash of all the incoming bundles in the block.
326-
pub bundles_hash: CryptoHash,
327-
/// Cryptographic hash of all the operations in the block.
328-
pub operations_hash: CryptoHash,
323+
/// Cryptographic hash of all the transactions in the block.
324+
pub transactions_hash: CryptoHash,
329325

330326
// Outcome of the block execution.
331327
/// Cryptographic hash of all the messages in the block.
@@ -346,12 +342,12 @@ pub struct BlockHeader {
346342

347343
/// The body of a block containing all the data included in the block.
348344
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
345+
#[graphql(complex)]
349346
pub struct BlockBody {
350-
/// A selection of incoming messages to be executed first. Successive messages of the same
351-
/// sender and height are grouped together for conciseness.
352-
pub incoming_bundles: Vec<IncomingBundle>,
353-
/// The operations to execute.
354-
pub operations: Vec<Operation>,
347+
/// The transactions to execute in this block. Each transaction can be either
348+
/// incoming messages or an operation.
349+
#[graphql(skip)]
350+
pub transactions: Vec<Transaction>,
355351
/// The list of outgoing messages for each transaction.
356352
pub messages: Vec<Vec<OutgoingMessage>>,
357353
/// The hashes and heights of previous blocks that sent messages to the same recipients.
@@ -368,17 +364,45 @@ pub struct BlockBody {
368364
pub operation_results: Vec<OperationResult>,
369365
}
370366

367+
impl BlockBody {
368+
/// Returns all operations in this block body.
369+
pub fn operations(&self) -> impl Iterator<Item = &Operation> {
370+
self.transactions.iter().filter_map(|tx| match tx {
371+
Transaction::ExecuteOperation(operation) => Some(operation),
372+
Transaction::ReceiveMessages(_) => None,
373+
})
374+
}
375+
376+
/// Returns all incoming bundles in this block body.
377+
pub fn incoming_bundles(&self) -> impl Iterator<Item = &IncomingBundle> {
378+
self.transactions.iter().filter_map(|tx| match tx {
379+
Transaction::ReceiveMessages(bundle) => Some(bundle),
380+
Transaction::ExecuteOperation(_) => None,
381+
})
382+
}
383+
}
384+
385+
#[async_graphql::ComplexObject]
386+
impl BlockBody {
387+
/// Metadata about the transactions in this block.
388+
async fn transaction_metadata(&self) -> Vec<crate::data_types::TransactionMetadata> {
389+
self.transactions
390+
.iter()
391+
.map(crate::data_types::TransactionMetadata::from_transaction)
392+
.collect()
393+
}
394+
}
395+
371396
impl Block {
372397
pub fn new(block: ProposedBlock, outcome: BlockExecutionOutcome) -> Self {
373-
let bundles_hash = hashing::hash_vec(&block.incoming_bundles);
398+
let transactions_hash = hashing::hash_vec(&block.transactions);
374399
let messages_hash = hashing::hash_vec_vec(&outcome.messages);
375400
let previous_message_blocks_hash = CryptoHash::new(&PreviousMessageBlocksMap {
376401
inner: Cow::Borrowed(&outcome.previous_message_blocks),
377402
});
378403
let previous_event_blocks_hash = CryptoHash::new(&PreviousEventBlocksMap {
379404
inner: Cow::Borrowed(&outcome.previous_event_blocks),
380405
});
381-
let operations_hash = hashing::hash_vec(&block.operations);
382406
let oracle_responses_hash = hashing::hash_vec_vec(&outcome.oracle_responses);
383407
let events_hash = hashing::hash_vec_vec(&outcome.events);
384408
let blobs_hash = hashing::hash_vec_vec(&outcome.blobs);
@@ -392,8 +416,7 @@ impl Block {
392416
state_hash: outcome.state_hash,
393417
previous_block_hash: block.previous_block_hash,
394418
authenticated_signer: block.authenticated_signer,
395-
bundles_hash,
396-
operations_hash,
419+
transactions_hash,
397420
messages_hash,
398421
previous_message_blocks_hash,
399422
previous_event_blocks_hash,
@@ -404,8 +427,7 @@ impl Block {
404427
};
405428

406429
let body = BlockBody {
407-
incoming_bundles: block.incoming_bundles,
408-
operations: block.operations,
430+
transactions: block.transactions,
409431
messages: outcome.messages,
410432
previous_message_blocks: outcome.previous_message_blocks,
411433
previous_event_blocks: outcome.previous_event_blocks,
@@ -483,8 +505,7 @@ impl Block {
483505
/// Returns all the published blob IDs in this block's operations.
484506
pub fn published_blob_ids(&self) -> BTreeSet<BlobId> {
485507
self.body
486-
.operations
487-
.iter()
508+
.operations()
488509
.flat_map(Operation::published_blob_ids)
489510
.collect()
490511
}
@@ -550,17 +571,15 @@ impl Block {
550571
let ProposedBlock {
551572
chain_id,
552573
epoch,
553-
incoming_bundles,
554-
operations,
574+
transactions,
555575
height,
556576
timestamp,
557577
authenticated_signer,
558578
previous_block_hash,
559579
} = block;
560580
*chain_id == self.header.chain_id
561581
&& *epoch == self.header.epoch
562-
&& *incoming_bundles == self.body.incoming_bundles
563-
&& *operations == self.body.operations
582+
&& *transactions == self.body.transactions
564583
&& *height == self.header.height
565584
&& *timestamp == self.header.timestamp
566585
&& *authenticated_signer == self.header.authenticated_signer
@@ -571,8 +590,7 @@ impl Block {
571590
let proposed_block = ProposedBlock {
572591
chain_id: self.header.chain_id,
573592
epoch: self.header.epoch,
574-
incoming_bundles: self.body.incoming_bundles,
575-
operations: self.body.operations,
593+
transactions: self.body.transactions,
576594
height: self.header.height,
577595
timestamp: self.header.timestamp,
578596
authenticated_signer: self.header.authenticated_signer,

linera-chain/src/block_tracker.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,22 +95,22 @@ impl<'resources, 'blobs> BlockExecutionTracker<'resources, 'blobs> {
9595
operation_results: Vec::new(),
9696
transaction_index: 0,
9797
published_blobs,
98-
expected_outcomes_count: proposal.incoming_bundles.len() + proposal.operations.len(),
98+
expected_outcomes_count: proposal.transactions.len(),
9999
})
100100
}
101101

102102
/// Executes a transaction in the context of the block.
103103
pub async fn execute_transaction<C>(
104104
&mut self,
105-
transaction: Transaction<'_>,
105+
transaction: &Transaction,
106106
round: Option<u32>,
107107
chain: &mut ExecutionStateView<C>,
108108
) -> Result<(), ChainError>
109109
where
110110
C: Context + Clone + Send + Sync + 'static,
111111
C::Extra: ExecutionRuntimeContext,
112112
{
113-
let chain_execution_context = self.chain_execution_context(&transaction);
113+
let chain_execution_context = self.chain_execution_context(transaction);
114114
let mut txn_tracker = self.new_transaction_tracker()?;
115115

116116
match transaction {
@@ -357,7 +357,7 @@ impl<'resources, 'blobs> BlockExecutionTracker<'resources, 'blobs> {
357357
}
358358

359359
/// Returns the execution context for the current transaction.
360-
pub fn chain_execution_context(&self, transaction: &Transaction<'_>) -> ChainExecutionContext {
360+
pub fn chain_execution_context(&self, transaction: &Transaction) -> ChainExecutionContext {
361361
match transaction {
362362
Transaction::ReceiveMessages(_) => {
363363
ChainExecutionContext::IncomingBundle(self.transaction_index)

0 commit comments

Comments
 (0)