Skip to content

chore: Create Midnight Transaction Builder #267

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

Draft
wants to merge 5 commits into
base: plat-6712-add-models-and-configuration-parsing
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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,403 changes: 1,369 additions & 34 deletions Cargo.lock

Large diffs are not rendered by default.

19 changes: 14 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,17 @@ actix-governor = "0.8"
actix-rt = "2.0.0"
actix-web = "4"
alloy = { version = "0.9", features = ["full"] }
apalis = { version = "0.7", features = ["limit", "retry", "catch-panic", "timeout"] }
apalis = { version = "0.7", features = [
"limit",
"retry",
"catch-panic",
"timeout",
] }
apalis-cron = { version = "0.7" }
apalis-redis = { version = "0.7" }
async-trait = "0.1"
base64 = { version = "0.22" }
backoff = { version = "0.4.0", features = ["tokio"] }
base64 = { version = "0.22" }
bech32 = "0.11.0"
bincode = { version = "1.3" }
bs58 = "0.5"
Expand All @@ -46,19 +52,21 @@ eyre = "0.6"
futures = "0.3"
google-cloud-auth = "0.20.0"
governor = "0.8.0"
hex = { version = "0.4"}
hex = { version = "0.4" }
hmac = { version = "0.12" }
http = { version = "1.3.1" }
itertools = "0.12.0" # Required by midnight
k256 = { version = "0.13" }
lazy_static = "1.5"
libsodium-sys = "0.2.7"
log = "0.4"
midnight-ledger-prototype = { git = "https://github.com/midnightntwrk/midnight-ledger-prototype", package = "midnight-ledger", tag = "ledger-4.0.0" }
midnight-node-ledger-helpers = { git = "https://github.com/midnightntwrk/midnight-node", package = "midnight-node-ledger-helpers", tag = "node-0.12.0" }
midnight-node-res = { git = "https://github.com/midnightntwrk/midnight-node", package = "midnight-node-res", tag = "node-0.12.0" }
mpl-token-metadata = { version = "5.1" }
num_enum = { version = "0.7", default-features = false }
once_cell = "1.17"
oz-keystore = { version = "0.1.4"}
oz-keystore = { version = "0.1.4" }
p256 = { version = "0.13.2" }
parking_lot = "0.12"
pem = { version = "3" }
Expand All @@ -67,7 +75,7 @@ rand = "0.9"
redis = { version = "0.31" }
regex = "1"
reqwest = { version = "0.12", features = ["json"] }
secrets = { version = "1.2"}
secrets = { version = "1.2" }
serde = { version = "1.0", features = ["derive", "alloc"] }
serde_json = "1"
sha2 = { version = "0.10" }
Expand All @@ -83,6 +91,7 @@ stellar-strkey = "0.0.12"
strum = { version = "0.27", default-features = false, features = ["derive"] }
strum_macros = "0.27"
subtle = "2.6"
subxt = "0.37.0"
sysinfo = "0.35"
thiserror = "2"
tokio = { version = "1.43", features = ["sync", "io-util", "time"] }
Expand Down
141 changes: 141 additions & 0 deletions src/domain/transaction/midnight/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//! Midnight transaction builder
//!
//! This module provides a builder pattern for constructing Midnight transactions
//! following the midnight-node patterns.
//!
//! IMPORTANT: When creating transactions, always include a change output back to the sender
//! if there's any remaining value after accounting for the recipient amount and fees.
//! This ensures the indexer recognizes the transaction as relevant to the sender's wallet
//! during synchronization.

use midnight_node_ledger_helpers::{
FromContext,
IntentInfo,
LedgerContext,
OfferInfo,
Proof,
ProofProvider,
// StandardTrasactionInfo has a typo and may be changed in the future
StandardTrasactionInfo,
Transaction,
WellFormedStrictness,
DB,
};
use std::sync::Arc;

use crate::models::TransactionError;

/// Builder for constructing Midnight transactions using midnight-node patterns
pub struct MidnightTransactionBuilder<D: DB> {
/// The ledger context containing wallet and network information
context: Option<Arc<LedgerContext<D>>>,
/// The proof provider for generating ZK proofs
proof_provider: Option<Box<dyn ProofProvider<D>>>,
/// Random seed for transaction building
rng_seed: Option<[u8; 32]>,
/// The guaranteed offer to be added
guaranteed_offer: Option<OfferInfo<D>>,
/// Intent info containing all fallible offers with segment information preserved
intent_info: Option<IntentInfo<D>>,
}

impl<D: DB> MidnightTransactionBuilder<D> {
/// Creates a new transaction builder
pub fn new() -> Self {
Self {
context: None,
proof_provider: None,
rng_seed: None,
guaranteed_offer: None,
intent_info: None,
}
}

/// Sets the ledger context
pub fn with_context(mut self, context: std::sync::Arc<LedgerContext<D>>) -> Self {
self.context = Some(context);
self
}

/// Sets the proof provider
pub fn with_proof_provider(mut self, proof_provider: Box<dyn ProofProvider<D>>) -> Self {
self.proof_provider = Some(proof_provider);
self
}

/// Sets the RNG seed
pub fn with_rng_seed(mut self, seed: [u8; 32]) -> Self {
self.rng_seed = Some(seed);
self
}

/// Sets the entire guaranteed offer
pub fn with_guaranteed_offer(mut self, offer: OfferInfo<D>) -> Self {
self.guaranteed_offer = Some(offer);
self
}

/// Set the intent info
pub fn with_intent_info(mut self, intent: IntentInfo<D>) -> Self {
self.intent_info = Some(intent);
self
}

/// Builds the final transaction
pub async fn build(self) -> Result<Transaction<Proof, D>, TransactionError> {
let context_arc = self
.context
.ok_or_else(|| TransactionError::ValidationError("Context not provided".to_string()))?;
let proof_provider = self.proof_provider.ok_or_else(|| {
TransactionError::ValidationError("Proof provider not provided".to_string())
})?;
let rng_seed = self.rng_seed.ok_or_else(|| {
TransactionError::ValidationError("RNG seed not provided".to_string())
})?;

// Create StandardTransactionInfo with the context
let mut tx_info = StandardTrasactionInfo::new_from_context(
context_arc.clone(),
proof_provider.into(),
Some(rng_seed),
);

// Set the guaranteed offer if present
if let Some(offer) = self.guaranteed_offer {
tx_info.set_guaranteed_coins(offer);
}

// Set the intent info if present to preserve segment information
if let Some(intent) = self.intent_info {
tx_info.set_intents(vec![intent]);
}

// Build transaction and generate proofs
let proven_tx = tx_info.prove().await;

// Get the ledger state from the context
let ledger_state_guard = context_arc.ledger_state.lock().map_err(|e| {
TransactionError::UnexpectedError(format!("Failed to acquire ledger state lock: {}", e))
})?;

let ref_state = &*ledger_state_guard;

// Perform well_formed validation
proven_tx
.well_formed(ref_state, WellFormedStrictness::default())
.map_err(|e| {
TransactionError::ValidationError(format!(
"Transaction failed well_formed validation: {:?}",
e
))
})?;

Ok(proven_tx)
}
}

impl<D: DB> Default for MidnightTransactionBuilder<D> {
fn default() -> Self {
Self::new()
}
}
Loading