/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2023 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
r* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use darkfi_sdk::crypto::ContractId;
use darkfi_serial::{deserialize, serialize};
use log::{debug, error};
use crate::{Error, Result};
const SLED_CONTRACTS_TREE: &[u8] = b"_contracts";
const SLED_BINCODE_TREE: &[u8] = b"_wasm_bincode";
/// The `WasmStore` is a `sled` tree that stores the wasm bincode for deployed
/// contracts.
#[derive(Clone)]
pub struct WasmStore(sled::Tree);
impl WasmStore {
/// Opens or creates a `WasmStore`. This tree holds the wasm bincode.
/// The layout looks like this:
/// ```plaintext
/// tree: "_wasm_bincode"
/// key: ContractId
/// value: Vec
pub fn new(db: &sled::Db) -> Result {
let tree = db.open_tree(SLED_BINCODE_TREE)?;
Ok(Self(tree))
}
/// Fetches the bincode for a given ContractId
/// Returns an error if the bincode is not found.
pub fn get(&self, contract_id: ContractId) -> Result> {
if let Some(bincode) = self.0.get(&serialize(&contract_id))? {
return Ok(bincode.to_vec())
}
Err(Error::WasmBincodeNotFound)
}
/// Inserts or replaces the bincode for a given ContractId
pub fn insert(&self, contract_id: ContractId, bincode: &[u8]) -> Result<()> {
if let Err(e) = self.0.insert(&serialize(&contract_id), bincode) {
error!(target: "blockchain::contractstore", "Failed to insert bincode to WasmStore: {}", e);
return Err(e.into())
}
Ok(())
}
}
/// The `ContractStateStore` is a `sled` tree that stores pointers to contracts'
/// databases. See the rustdoc for the impl functions for more info.
#[derive(Clone)]
pub struct ContractStateStore(sled::Tree);
impl ContractStateStore {
/// Opens or creates a `ContractStateStore`. This main tree holds the links
/// of contracts' states.
/// The layout looks like this:
/// ```plaintext
/// tree: "_contracts"
/// key: ContractId
/// value: Vec
/// ```
/// These values get mutated with `init()` and `remove()`.
pub fn new(db: &sled::Db) -> Result {
let tree = db.open_tree(SLED_CONTRACTS_TREE)?;
Ok(Self(tree))
}
/// Try to initialize a new contract state. Contracts can create a number
/// of trees, separated by `tree_name`, which they can then use from the
/// smart contract API. `init()` will look into the main `ContractStateStore`
/// tree to check if the smart contract was already deployed, and if so
/// it will fetch a vector of these states that were initialized. If the
/// state was already found, this function will return an error, because
/// in this case the handle should be fetched using `lookup()`.
/// If the tree was not initialized previously, it will be appended to
/// the main `ContractStateStore` tree and a `sled::Tree` handle will be
/// returned.
pub fn init(
&self,
db: &sled::Db,
contract_id: &ContractId,
tree_name: &str,
) -> Result {
debug!(target: "blockchain::contractstore", "Initializing state tree for {}:{}", contract_id, tree_name);
let contract_id_bytes = serialize(contract_id);
let ptr = contract_id.hash_state_id(tree_name);
// See if there are existing state trees. If not, just start with an
// empty vector.
let mut state_pointers: Vec<[u8; 32]> = if self.0.contains_key(&contract_id_bytes)? {
let bytes = self.0.get(&contract_id_bytes)?.unwrap();
deserialize(&bytes)?
} else {
vec![]
};
// If the db was never initialized, it should not be in here.
if state_pointers.contains(&ptr) {
return Err(Error::ContractAlreadyInitialized)
}
// Now we add it so it's marked as initialized
state_pointers.push(ptr);
// We do this as a batch so in case of not being able to open the tree
// we don't write that it's initialized.
let mut batch = sled::Batch::default();
batch.insert(contract_id_bytes, serialize(&state_pointers));
// We open the tree and return its handle
let tree = db.open_tree(ptr)?;
// On success, apply the batch
self.0.apply_batch(batch)?;
Ok(tree)
}
/// Do a lookup of an existing contract state. In order to succeed, the
/// state must have been previously initialized with `init()`. If the
/// state has been found, a handle to it will be returned. Otherwise, we
/// return an error.
pub fn lookup(
&self,
db: &sled::Db,
contract_id: &ContractId,
tree_name: &str,
) -> Result {
debug!(target: "blockchain::contractstore", "Looking up state tree for {}:{}", contract_id, tree_name);
let contract_id_bytes = serialize(contract_id);
let ptr = contract_id.hash_state_id(tree_name);
// A guard to make sure we went through init()
if !self.0.contains_key(&contract_id_bytes)? {
return Err(Error::ContractNotFound(contract_id.to_string()))
}
let state_pointers = self.0.get(&contract_id_bytes)?.unwrap();
let state_pointers: Vec<[u8; 32]> = deserialize(&state_pointers)?;
// We assume the tree has been created already, so it should be listed
// in this array. If not, that's an error.
if !state_pointers.contains(&ptr) {
return Err(Error::ContractStateNotFound)
}
// We open the tree and return its handle
let tree = db.open_tree(ptr)?;
Ok(tree)
}
/// Attempt to remove an existing contract state. In order to succeed, the
/// state must have been previously initialized with `init()`. If the state
/// has been found, its contents in the tree will be cleared, and the pointer
/// will be removed from the main `ContractStateStore`. If anything is not
/// found as initialized, an error is returned.
pub fn remove(&self, db: &sled::Db, contract_id: &ContractId, tree_name: &str) -> Result<()> {
debug!(target: "blockchain::contractstore", "Removing state tree for {}:{}", contract_id, tree_name);
let contract_id_bytes = serialize(contract_id);
let ptr = contract_id.hash_state_id(tree_name);
// A guard to make sure we went through init()
if !self.0.contains_key(&contract_id_bytes)? {
return Err(Error::ContractNotFound(contract_id.to_string()))
}
let state_pointers = self.0.get(&contract_id_bytes)?.unwrap();
let mut state_pointers: Vec<[u8; 32]> = deserialize(&state_pointers)?;
// We assume the tree has been created already, so it should be listed
// in this array. If not, that's an error.
if !state_pointers.contains(&ptr) {
return Err(Error::ContractStateNotFound)
}
// We open the tree and clear it. This is unfortunately not atomic.
let tree = db.open_tree(ptr)?;
tree.clear()?;
state_pointers.retain(|x| *x != ptr);
self.0.insert(contract_id_bytes, serialize(&state_pointers))?;
Ok(())
}
}