diff --git a/Cargo.lock b/Cargo.lock --- a/Cargo.lock +++ b/Cargo.lock @@ -353,6 +353,8 @@ "chronik-proto", "chronik-util", "cxx", + "hex", + "itertools", "pretty_assertions", "prost", "prost-build", diff --git a/chronik/chronik-indexer/Cargo.toml b/chronik/chronik-indexer/Cargo.toml --- a/chronik/chronik-indexer/Cargo.toml +++ b/chronik/chronik-indexer/Cargo.toml @@ -25,6 +25,12 @@ # Bridge to C++ cxx = "1.0" +# En-/decode hex +hex = "0.4" + +# Extends iterators with handy helpers +itertools = "0.10" + # Protobuf en-/decoding prost = "0.11" diff --git a/chronik/chronik-indexer/src/query/broadcast.rs b/chronik/chronik-indexer/src/query/broadcast.rs --- a/chronik/chronik-indexer/src/query/broadcast.rs +++ b/chronik/chronik-indexer/src/query/broadcast.rs @@ -12,6 +12,7 @@ use chronik_bridge::ffi; use chronik_db::{db::Db, mem::Mempool}; use chronik_proto::proto; +use itertools::Itertools; use thiserror::Error; use crate::{ @@ -49,6 +50,10 @@ /// Node rejected the tx #[error("400: Broadcast failed: {0}")] BroadcastFailed(String), + + /// Validation failed, inputs not found + #[error("404: Validation failed: input(s) {0} not found or spent")] + ValidationFailed(String), } impl QueryBroadcast<'_> { @@ -139,12 +144,26 @@ let tx = TxMut::deser(&mut raw_tx.into())?; let mut ffi_tx = ffi::Tx::from(tx); let mut coins_to_uncache = Vec::new(); + let mut not_found = Vec::new(); self.node.bridge.lookup_spent_coins( &mut ffi_tx, - &mut vec![], + &mut not_found, &mut coins_to_uncache, )?; self.node.bridge.uncache_coins(&coins_to_uncache)?; + if !not_found.is_empty() { + let not_found_str = not_found + .iter() + .map(|outpoint| { + format!( + "{}:{}", + hex::encode(outpoint.txid), + outpoint.out_idx + ) + }) + .join(", "); + return Err(ValidationFailed(not_found_str).into()); + } let tx = Tx::from(ffi_tx); let token = if self.is_token_index_enabled { TxTokenData::from_unbroadcast_tx(self.db, self.mempool, &tx)?