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).