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

  1. Proposal validation detects missing transactions.
  2. Send TxFetchRequest with missing hashes to a few peers.
  3. Peers respond with any transactions they still have.
  4. Verify hashes and resume validation.
  5. If still missing after timeout → reject as before.

Consensus integration (example)

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;
        }
    }
}