Sometimes a node receives a proposal that references transactions it no longer has — for example, evicted from local storage or never propagated locally.
Instead of rejecting immediately, we could just ask peers for the missing data.
Implementation could be trivial since we can reuse libp2p’s Request-Response protocol.
// --- messages ---
struct TxFetchRequest { tx_hashes: Vec<TxHash> }
struct TxFetchResponse {
transactions: Vec<Transaction>,
missing: Vec<TxHash>,
}
// --- request-response protocol ---
struct TxFetchProtocol;
impl ProtocolName for TxFetchProtocol {
fn protocol_name(&self) -> &[u8] { b"/nomos/tx-fetch/1.0.0" }
}
type TxFetch = RequestResponse<TxFetchCodec>;
struct Behaviour {
tx_fetch: TxFetch,
// ... other protocols
}
Flow
TxFetchRequest
with missing hashes to a few peers.impl ConsensusService {
async fn handle_block_proposal(&mut self, proposal: Proposal) {
let missing: Vec<TxHash> = proposal.transactions.iter()
.filter(|&hash| !self.mempool.has(hash))
.cloned()
.collect();
if missing.is_empty() {
self.validate_proposal(proposal).await;
} else {
// Fetch missing transactions
let request_id = self.network.behaviour_mut().tx_fetch
.send_request(&from_peer, TxFetchRequest { tx_hashes: missing });
self.pending_proposals.insert(request_id, proposal);
}
}
async fn handle_tx_response(&mut self, response: TxFetchResponse, request_id: RequestId) {
// Add transactions to mempool
// Either chain service requests direclty and we call mempool API to store trasnaction afterwards or delegate trasnaction fetching to mempool
for tx in response.transactions {
self.mempool.add(tx);
}
// Validate the proposal now that we have the transactions
if let Some(proposal) = self.pending_proposals.remove(&request_id) {
self.validate_proposal(proposal).await;
}
}
}