Skip to content
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

feat(block-producer): switch to mempool version #562

Merged
merged 19 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ed72500
feat: block producer redesign skeleton (#502)
Mirko-von-Leipzig Oct 7, 2024
34a45d9
feat(block-producer): implement the skeleton (#515)
Mirko-von-Leipzig Oct 22, 2024
90311b3
feat(block-producer): inject failures and randomise work time (#522)
Mirko-von-Leipzig Oct 24, 2024
d784cce
refactor(block-producer): common graph type (#525)
Mirko-von-Leipzig Oct 25, 2024
fd02092
feat(block-producer): arc transaction data (#530)
Mirko-von-Leipzig Oct 29, 2024
ad20a30
feat(block-producer): refactor batch graph to use dependency graph (#…
Mirko-von-Leipzig Oct 30, 2024
225b6f4
feat(block-producer): inflight state custody of transaction data (#534)
Mirko-von-Leipzig Nov 2, 2024
621b68e
Merge next into next-block-producer
Mirko-von-Leipzig Nov 2, 2024
49846e3
chore: address comments
bobbinth Nov 2, 2024
74da4ca
Merge pull request #538 from 0xPolygonMiden/mirko-merge-next
Mirko-von-Leipzig Nov 3, 2024
2ceec74
feat(block-producer): promote redesign (#541)
Mirko-von-Leipzig Nov 5, 2024
16c3087
refactor: simplify shared mempool (#548)
polydez Nov 8, 2024
312d06a
feat(block-producer): improve mempool config (#543)
Mirko-von-Leipzig Nov 29, 2024
39828bf
fix(mempool): allow internal batch dependencies (#549)
Mirko-von-Leipzig Nov 29, 2024
3b1ac1f
fix(block-producer): handle reverted batches (#557)
Mirko-von-Leipzig Dec 4, 2024
9399917
feat(block-producer): merge in next (#561)
Mirko-von-Leipzig Dec 4, 2024
aa9ebb4
Merge branch 'next' into next-block-producer
Mirko-von-Leipzig Dec 4, 2024
536bcd0
chore: update changelog
Mirko-von-Leipzig Dec 4, 2024
25160ea
feat(block-producer): address mempool review comments
Mirko-von-Leipzig Dec 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Added `GetAccountProofs` endpoint (#506).
- Support Https in endpoint configuration (#556).
- Upgrade `block-producer` from FIFO queue to mempool dependency graph (#562).

### Changes

Expand Down
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ BUILD_PROTO=BUILD_PROTO=1

.PHONY: clippy
clippy: ## Runs Clippy with configs
cargo clippy --locked --workspace --all-targets --all-features -- -D warnings --allow clippy::arc_with_non_send_sync
cargo clippy --locked --workspace --all-targets --all-features -- -D warnings


.PHONY: fix
Expand Down
43 changes: 32 additions & 11 deletions bin/node/src/commands/start.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashMap;

use anyhow::{Context, Result};
use miden_node_block_producer::server::BlockProducer;
use miden_node_rpc::server::Rpc;
Expand All @@ -16,22 +18,41 @@ pub async fn start_node(config: NodeConfig) -> Result<()> {

// Start store. The store endpoint is available after loading completes.
let store = Store::init(store).await.context("Loading store")?;
join_set.spawn(async move { store.serve().await.context("Serving store") });
let store_id = join_set.spawn(async move { store.serve().await.context("Serving store") }).id();

// Start block-producer. The block-producer's endpoint is available after loading completes.
let block_producer =
BlockProducer::init(block_producer).await.context("Loading block-producer")?;
join_set.spawn(async move { block_producer.serve().await.context("Serving block-producer") });
let block_producer_id = join_set
.spawn(async move { block_producer.serve().await.context("Serving block-producer") })
.id();

// Start RPC component.
let rpc = Rpc::init(rpc).await.context("Loading RPC")?;
join_set.spawn(async move { rpc.serve().await.context("Serving RPC") });

// block on all tasks
while let Some(res) = join_set.join_next().await {
// For now, if one of the components fails, crash the node
res??;
}

Ok(())
let rpc_id = join_set.spawn(async move { rpc.serve().await.context("Serving RPC") }).id();

// Lookup table so we can identify the failed component.
let component_ids = HashMap::from([
(store_id, "store"),
(block_producer_id, "block-producer"),
(rpc_id, "rpc"),
]);

// SAFETY: The joinset is definitely not empty.
let component_result = join_set.join_next_with_id().await.unwrap();

// We expect components to run indefinitely, so we treat any return as fatal.
//
// Map all outcomes to an error, and provide component context.
let (id, err) = match component_result {
Ok((id, Ok(_))) => (id, Err(anyhow::anyhow!("Component completed unexpectedly"))),
Ok((id, Err(err))) => (id, Err(err)),
Err(join_err) => (join_err.id(), Err(join_err).context("Joining component task")),
};
let component = component_ids.get(&id).unwrap_or(&"unknown");

// We could abort and gracefully shutdown the other components, but since we're crashing the
// node there is no point.

err.context(format!("Component {component} failed"))
}
2 changes: 2 additions & 0 deletions crates/block-producer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ miden-objects = { workspace = true }
miden-processor = { workspace = true }
miden-stdlib = { workspace = true }
miden-tx = { workspace = true }
rand = { version = "0.8" }
serde = { version = "1.0", features = ["derive"] }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "net", "macros", "sync", "time"] }
Expand All @@ -38,6 +39,7 @@ miden-lib = { workspace = true, features = ["testing"] }
miden-node-test-macro = { path = "../test-macro" }
miden-objects = { workspace = true, features = ["testing"] }
miden-tx = { workspace = true, features = ["testing"] }
pretty_assertions = "1.4"
rand_chacha = { version = "0.3", default-features = false }
tokio = { workspace = true, features = ["test-util"] }
winterfell = { version = "0.10" }
53 changes: 14 additions & 39 deletions crates/block-producer/src/batch_builder/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,19 @@ use miden_objects::{
batches::BatchNoteTree,
crypto::hash::blake::{Blake3Digest, Blake3_256},
notes::{NoteHeader, NoteId, Nullifier},
transaction::{InputNoteCommitment, OutputNote, TransactionId},
AccountDeltaError, Digest, MAX_ACCOUNTS_PER_BATCH, MAX_INPUT_NOTES_PER_BATCH,
MAX_OUTPUT_NOTES_PER_BATCH,
transaction::{InputNoteCommitment, OutputNote, ProvenTransaction, TransactionId},
AccountDeltaError, Digest,
};
use tracing::instrument;

use crate::{errors::BuildBatchError, ProvenTransaction};
use crate::errors::BuildBatchError;

pub type BatchId = Blake3Digest<32>;

// TRANSACTION BATCH
// ================================================================================================

/// A batch of transactions that share a common proof. For any given account, at most 1 transaction
/// in the batch must be addressing that account (issue: #186).
/// A batch of transactions that share a common proof.
///
/// Note: Until recursive proofs are available in the Miden VM, we don't include the common proof.
#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -77,13 +75,11 @@ impl TransactionBatch {
/// for transforming unauthenticated notes into authenticated notes.
///
/// # Errors
///
/// Returns an error if:
/// - The number of output notes across all transactions exceeds 4096.
/// - There are duplicated output notes or unauthenticated notes found across all transactions
/// in the batch.
/// - Hashes for corresponding input notes and output notes don't match.
///
/// TODO: enforce limit on the number of created nullifiers.
#[instrument(target = "miden-block-producer", name = "new_batch", skip_all, err)]
pub fn new(
txs: Vec<ProvenTransaction>,
Expand All @@ -102,27 +98,19 @@ impl TransactionBatch {
vacant.insert(AccountUpdate::new(tx));
},
Entry::Occupied(occupied) => occupied.into_mut().merge_tx(tx).map_err(|error| {
BuildBatchError::AccountUpdateError {
account_id: tx.account_id(),
error,
txs: txs.clone(),
}
BuildBatchError::AccountUpdateError { account_id: tx.account_id(), error }
})?,
};

// Check unauthenticated input notes for duplicates:
for note in tx.get_unauthenticated_notes() {
let id = note.id();
if !unauthenticated_input_notes.insert(id) {
return Err(BuildBatchError::DuplicateUnauthenticatedNote(id, txs.clone()));
return Err(BuildBatchError::DuplicateUnauthenticatedNote(id));
}
}
}

if updated_accounts.len() > MAX_ACCOUNTS_PER_BATCH {
return Err(BuildBatchError::TooManyAccountsInBatch(txs));
}

// Populate batch produced nullifiers and match output notes with corresponding
// unauthenticated input notes in the same batch, which are removed from the unauthenticated
// input notes set.
Expand All @@ -137,7 +125,7 @@ impl TransactionBatch {
// Header is presented only for unauthenticated input notes.
let input_note = match input_note.header() {
Some(input_note_header) => {
if output_notes.remove_note(input_note_header, &txs)? {
if output_notes.remove_note(input_note_header)? {
continue;
}

Expand All @@ -155,16 +143,8 @@ impl TransactionBatch {
input_notes.push(input_note)
}

if input_notes.len() > MAX_INPUT_NOTES_PER_BATCH {
return Err(BuildBatchError::TooManyInputNotes(input_notes.len(), txs));
}

let output_notes = output_notes.into_notes();

if output_notes.len() > MAX_OUTPUT_NOTES_PER_BATCH {
return Err(BuildBatchError::TooManyNotesCreated(output_notes.len(), txs));
}

// Build the output notes SMT.
let output_notes_smt = BatchNoteTree::with_contiguous_leaves(
output_notes.iter().map(|note| (note.id(), note.metadata())),
Expand Down Expand Up @@ -250,7 +230,7 @@ impl OutputNoteTracker {
for tx in txs {
for note in tx.output_notes().iter() {
if output_note_index.insert(note.id(), output_notes.len()).is_some() {
return Err(BuildBatchError::DuplicateOutputNote(note.id(), txs.to_vec()));
return Err(BuildBatchError::DuplicateOutputNote(note.id()));
}
output_notes.push(Some(note.clone()));
}
Expand All @@ -259,11 +239,7 @@ impl OutputNoteTracker {
Ok(Self { output_notes, output_note_index })
}

pub fn remove_note(
&mut self,
input_note_header: &NoteHeader,
txs: &[ProvenTransaction],
) -> Result<bool, BuildBatchError> {
pub fn remove_note(&mut self, input_note_header: &NoteHeader) -> Result<bool, BuildBatchError> {
let id = input_note_header.id();
if let Some(note_index) = self.output_note_index.remove(&id) {
if let Some(output_note) = mem::take(&mut self.output_notes[note_index]) {
Expand All @@ -274,7 +250,6 @@ impl OutputNoteTracker {
id,
input_hash,
output_hash,
txs: txs.to_vec(),
});
}

Expand Down Expand Up @@ -323,7 +298,7 @@ mod tests {
));

match OutputNoteTracker::new(&txs) {
Err(BuildBatchError::DuplicateOutputNote(note_id, _)) => {
Err(BuildBatchError::DuplicateOutputNote(note_id)) => {
assert_eq!(note_id, duplicate_output_note.id())
},
res => panic!("Unexpected result: {res:?}"),
Expand All @@ -337,8 +312,8 @@ mod tests {

let note_to_remove = mock_note(4);

assert!(tracker.remove_note(note_to_remove.header(), &txs).unwrap());
assert!(!tracker.remove_note(note_to_remove.header(), &txs).unwrap());
assert!(tracker.remove_note(note_to_remove.header()).unwrap());
assert!(!tracker.remove_note(note_to_remove.header()).unwrap());

// Check that output notes are in the expected order and consumed note was removed
assert_eq!(
Expand All @@ -359,7 +334,7 @@ mod tests {
let duplicate_note = mock_note(5);
txs.push(mock_proven_tx(4, vec![duplicate_note.clone()], vec![mock_output_note(9)]));
match TransactionBatch::new(txs, Default::default()) {
Err(BuildBatchError::DuplicateUnauthenticatedNote(note_id, _)) => {
Err(BuildBatchError::DuplicateUnauthenticatedNote(note_id)) => {
assert_eq!(note_id, duplicate_note.id())
},
res => panic!("Unexpected result: {res:?}"),
Expand Down
Loading
Loading