Understanding Rc and how to get past borrow checker

Hello everyone,
Im trying to learn Rust and trying to make a small project on building a basic blockchain, but got some issues with the borrow checker.
My idea is to make a hashmap containing a wallet's adress as key and a vector of references to its unspent outputs, so that I don't need to go through the blockchain to verify/spend it. So I made the spend boolean field a Cell to be able to set it through the reference:

#[derive(Debug, Clone, PartialEq)]
pub struct Output {
    pub sender: String,
    pub receiver: String,
    pub amount: u64,
    pub signature: String,
    pub spent: Cell\<bool>,
}

but adding a new transaction to the pool on my blockchain still got me with the lifetime issue:

// A struct for the Blockchain
#[derive(Debug)]
pub struct Blockchain <'a> {
    pub chain: Vec<Block>,
    pub pool: Vec<Transaction>,
    unspent_outputs: HashMap<String, Vec<&'a Output>>,
}

impl <'a> Blockchain<'a> {
    pub fn new() -> Self {
        let genesis_block = Block::new(Vec::new(), String::new());
        Blockchain {
            chain: vec![genesis_block],
            pool: Vec::new(),
            unspent_outputs: HashMap::new(),
        }
    }

    //adicionar transacao a pool
    pub fn add_to_pool(&mut self, transaction: Transaction) -> Result<bool, String>{

        //validar a transacao
        if !Transaction::validate_transaction(&transaction){
            return Err(String::from("Invalid transaction. Not added"));
        }

        // Gastar os outputs utilizados na transacao
        if self.spend_inputs(&transaction){
            return Err(String::from("Could not spend the inputs"));
        }

        //adicionar a pool
        self.pool.push(transaction);

        //adicionar outputs ao hashmap
        // Iterate over the outputs in the last transaction added to the pool
        let last_idx = self.pool.len() - 1;
        for output in &mut self.pool[last_idx].outputs {
            let output_ref: &Output = output;
            self.add_to_hashmap(output_ref); //error here: argument requires that `'1` must outlive `'a`
        }

        Ok(true)
    }

    //adiciona output ao hashmap
    fn add_to_hashmap(&mut self, output: &'a Output) {
        if output.spent.get() {
            return;
        }
    
        let wallet_address = output.receiver.clone();

        match self.unspent_outputs.entry(wallet_address){
            std::collections::hash_map::Entry::Occupied(mut entry) => {
                //carteira existe, adicionar ao vetor
                entry.get_mut().push(output);
            }
            std::collections::hash_map::Entry::Vacant(entry) => {
                let vec = vec![output];
                entry.insert(vec);
            }
        }
    }

    //Rotina para verificar a integridade do hashmap
    fn check_unspent_map_integrity(&mut self) -> Result<(), &'static str> {
        //verificar se todos os outputs do mapa nao estao gastos
        for (_, outputs) in &self.unspent_outputs {
            for output in outputs {
                if output.spent.get() {
                    return Err("Found spent outputs on hashmap")
                }
            }
        }
        Ok(())
    }

    //tornar todos os outputs utilizados como input em uma transacao em gastos
    pub fn spend_inputs(&mut self, transaction: &Transaction) -> bool {
        for input in &transaction.inputs {
            if let Some(outputs) = self.unspent_outputs.get_mut(&input.receiver) {
                if let Some(output) = outputs.iter_mut().find(|o| *o.receiver == input.receiver && !o.spent.get()) {
                    output.spent.set(true);
                } else {
                    return false; // Input nao encontrado ou ja gasto
                }
            } else {
                return false; // input nao encontrado no hashmap
            }
        }
        true
    }
}

Am I approaching the issue correctly? Also tried using reference counters Rc<> but could't get there. Sorry if its too much or too little information (i'm really new to Rust and programming in general) and any comments on portuguese, its for a project and they need to be there. Theres the project on Github if anything else is needed:

SilvioZec/iefp_coin: Cryptocurrency made from scratch (github.com).

1 Like

What you are trying to build in this case is what is known in Rust jargon as a self-referent struct, and these don't get along with the borrow checker. Using an Rc is one way to circumvent such problems, but you haven't shared the code where you attempted to use it.

Storing a direct reference to the Output is not going to work, because that Output is allocated somewhere in memory, and a reference is simply an address directly at the memory address of an individual Output.

So what happens then when you add a new transaction? The Vec containing the transactions might need to re-allocale, and now your reference points to invalid memory! Rust thankfully prevents that at compile time.

One soltion is indeed to use Rc, but if you don't remove transactions, you could just save the index of the transaction and then fetch it from the transaction list when needed. And if you do need to remove transactions, you can use something like slab to efficiently allocate them while still being able to index them.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.