runtime/merkle & smt: implements DB_roots format documented in the book arch/dao page. We store all merkle roots together with information about exactly when that root occurred. To store when the root occurred, we use an absolute location of (block_height, tx_idx, call_idx). Right now tx_idx and call_idx are hardcoded to 0 since the env doesn't yet have access to this info.

This commit is contained in:
zero
2024-04-01 09:23:07 +02:00
parent bfcd383f3b
commit 2919a595f1
7 changed files with 147 additions and 15 deletions

View File

@@ -35,8 +35,8 @@ use crate::{
},
MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_DB_VERSION, MONEY_CONTRACT_INFO_TREE,
MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_TOKEN_FREEZE_TREE,
MONEY_CONTRACT_TOTAL_FEES_PAID,
MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE,
MONEY_CONTRACT_TOKEN_FREEZE_TREE, MONEY_CONTRACT_TOTAL_FEES_PAID,
};
/// `Money::Fee` functions
@@ -123,12 +123,18 @@ fn init_contract(cid: ContractId, _ix: &[u8]) -> ContractResult {
zkas_db_set(&token_mint_v1_bincode[..])?;
zkas_db_set(&token_frz_v1_bincode[..])?;
// Set up a database tree to hold Merkle roots of all coins
// Set up a database tree to hold Merkle roots of all coin trees
// k=MerkleNode, v=[]
if db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE).is_err() {
db_init(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
}
// Set up a database tree to hold Merkle roots of all nullifier trees
// k=MerkleNode, v=[]
if db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE).is_err() {
db_init(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
}
// Set up a database tree to hold all coins ever seen
// k=Coin, v=[]
if db_lookup(cid, MONEY_CONTRACT_COINS_TREE).is_err() {

View File

@@ -41,7 +41,9 @@ use crate::{
model::{MoneyFeeParamsV1, MoneyFeeUpdateV1, DARK_TOKEN_ID},
MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_LATEST_COIN_ROOT,
MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_TOTAL_FEES_PAID, MONEY_CONTRACT_ZKAS_FEE_NS_V1,
MONEY_CONTRACT_LATEST_NULLIFIER_ROOT, MONEY_CONTRACT_NULLIFIERS_TREE,
MONEY_CONTRACT_NULLIFIER_ROOTS_TREE, MONEY_CONTRACT_TOTAL_FEES_PAID,
MONEY_CONTRACT_ZKAS_FEE_NS_V1,
};
/// `get_metadata` function for `Money::FeeV1`
@@ -205,10 +207,17 @@ pub(crate) fn money_fee_process_update_v1(
let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
let nullifier_roots_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
db_set(info_db, MONEY_CONTRACT_TOTAL_FEES_PAID, &serialize(&update.fee))?;
sparse_merkle_insert_batch(nullifiers_db, &[update.nullifier.inner()])?;
sparse_merkle_insert_batch(
info_db,
nullifiers_db,
nullifier_roots_db,
MONEY_CONTRACT_LATEST_NULLIFIER_ROOT,
&[update.nullifier.inner()],
)?;
db_set(coins_db, &serialize(&update.coin), &[])?;

View File

@@ -40,7 +40,9 @@ use crate::{
model::{MoneyTransferParamsV1, MoneyTransferUpdateV1},
MoneyFunction, MONEY_CONTRACT_COINS_TREE, MONEY_CONTRACT_COIN_MERKLE_TREE,
MONEY_CONTRACT_COIN_ROOTS_TREE, MONEY_CONTRACT_INFO_TREE, MONEY_CONTRACT_LATEST_COIN_ROOT,
MONEY_CONTRACT_NULLIFIERS_TREE, MONEY_CONTRACT_ZKAS_BURN_NS_V1, MONEY_CONTRACT_ZKAS_MINT_NS_V1,
MONEY_CONTRACT_LATEST_NULLIFIER_ROOT, MONEY_CONTRACT_NULLIFIERS_TREE,
MONEY_CONTRACT_NULLIFIER_ROOTS_TREE, MONEY_CONTRACT_ZKAS_BURN_NS_V1,
MONEY_CONTRACT_ZKAS_MINT_NS_V1,
};
/// `get_metadata` function for `Money::TransferV1`
@@ -234,10 +236,14 @@ pub(crate) fn money_transfer_process_update_v1(
let coins_db = db_lookup(cid, MONEY_CONTRACT_COINS_TREE)?;
let nullifiers_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIERS_TREE)?;
let coin_roots_db = db_lookup(cid, MONEY_CONTRACT_COIN_ROOTS_TREE)?;
let nullifier_roots_db = db_lookup(cid, MONEY_CONTRACT_NULLIFIER_ROOTS_TREE)?;
msg!("[TransferV1] Adding new nullifiers to the set");
sparse_merkle_insert_batch(
info_db,
nullifiers_db,
nullifier_roots_db,
MONEY_CONTRACT_LATEST_NULLIFIER_ROOT,
&update.nullifiers.iter().map(|n| n.inner()).collect::<Vec<_>>(),
)?;

View File

@@ -73,12 +73,14 @@ pub const MONEY_CONTRACT_INFO_TREE: &str = "info";
pub const MONEY_CONTRACT_COINS_TREE: &str = "coins";
pub const MONEY_CONTRACT_COIN_ROOTS_TREE: &str = "coin_roots";
pub const MONEY_CONTRACT_NULLIFIERS_TREE: &str = "nullifiers";
pub const MONEY_CONTRACT_NULLIFIER_ROOTS_TREE: &str = "nullifier_roots";
pub const MONEY_CONTRACT_TOKEN_FREEZE_TREE: &str = "token_freezes";
// These are keys inside the info tree
pub const MONEY_CONTRACT_DB_VERSION: &[u8] = b"db_version";
pub const MONEY_CONTRACT_COIN_MERKLE_TREE: &[u8] = b"coin_tree";
pub const MONEY_CONTRACT_LATEST_COIN_ROOT: &[u8] = b"last_root";
pub const MONEY_CONTRACT_COIN_MERKLE_TREE: &[u8] = b"coins_tree";
pub const MONEY_CONTRACT_LATEST_COIN_ROOT: &[u8] = b"last_coins_root";
pub const MONEY_CONTRACT_LATEST_NULLIFIER_ROOT: &[u8] = b"last_nullifiers_root";
pub const MONEY_CONTRACT_TOTAL_FEES_PAID: &[u8] = b"total_fees_paid";
/// zkas fee circuit namespace

View File

@@ -279,10 +279,23 @@ pub(crate) fn merkle_add(mut ctx: FunctionEnvMut<Env>, ptr: WasmPtr<u8>, len: u3
);
let latest_root_data = serialize(latest_root);
assert_eq!(latest_root_data.len(), 32);
let blockheight_data = serialize(&env.verifying_block_height);
assert_eq!(blockheight_data.len(), 8);
if overlay.insert(&db_roots.tree, &latest_root_data, &blockheight_data).is_err() {
let blockheight_data = serialize(&(env.verifying_block_height as u32));
// This is hardcoded but should not be
let tx_idx: u16 = 0;
let call_idx: u16 = 0;
assert_eq!(blockheight_data.len(), 4);
// Little-endian
assert_eq!(blockheight_data[3], 0);
let mut value_data = Vec::with_capacity(7);
value_data.write_slice(&blockheight_data[..3]).expect("Unable to serialize blockheight data");
tx_idx.encode(&mut value_data).expect("Unable to serialize tx_id");
call_idx.encode(&mut value_data).expect("Unable to serialize call_idx");
assert_eq!(value_data.len(), 7);
if overlay.insert(&db_roots.tree, &latest_root_data, &value_data).is_err() {
error!(
target: "runtime::merkle::merkle_add",
"[WASM] [{}] merkle_add(): Couldn't insert to db_roots tree", cid,

View File

@@ -22,9 +22,9 @@ use darkfi_sdk::crypto::{
pasta_prelude::*,
smt::{PoseidonFp, SparseMerkleTree, StorageAdapter, EMPTY_NODES_FP, SMT_FP_DEPTH},
};
use darkfi_serial::Decodable;
use darkfi_serial::{serialize, Decodable, Encodable, WriteExt};
use halo2_proofs::pasta::pallas;
use log::{error, warn};
use log::{debug, error, warn};
use num_bigint::BigUint;
use wasmer::{FunctionEnvMut, WasmPtr};
@@ -112,8 +112,21 @@ pub(crate) fn sparse_merkle_insert_batch(
// The buffer should deserialize into:
// - db_smt
// - db_roots
// - nullifiers (as Vec<pallas::Base>)
let mut buf_reader = Cursor::new(buf);
let db_info_index: u32 = match Decodable::decode(&mut buf_reader) {
Ok(v) => v,
Err(e) => {
error!(
target: "runtime::smt::sparse_merkle_insert_batch",
"[WASM] [{}] sparse_merkle_insert_batch(): Failed to decode db_info DbHandle: {}", cid, e,
);
return darkfi_sdk::error::INTERNAL_ERROR
}
};
let db_info_index = db_info_index as usize;
let db_smt_index: u32 = match Decodable::decode(&mut buf_reader) {
Ok(v) => v,
Err(e) => {
@@ -126,20 +139,37 @@ pub(crate) fn sparse_merkle_insert_batch(
};
let db_smt_index = db_smt_index as usize;
let db_roots_index: u32 = match Decodable::decode(&mut buf_reader) {
Ok(v) => v,
Err(e) => {
error!(
target: "runtime::smt::sparse_merkle_insert_batch",
"[WASM] [{}] sparse_merkle_insert_batch(): Failed to decode db_roots DbHandle: {}", cid, e,
);
return darkfi_sdk::error::INTERNAL_ERROR
}
};
let db_roots_index = db_roots_index as usize;
let db_handles = env.db_handles.borrow();
let n_dbs = db_handles.len();
if n_dbs <= db_smt_index {
if n_dbs <= db_info_index || n_dbs <= db_smt_index || n_dbs <= db_roots_index {
error!(
target: "runtime::smt::sparse_merkle_insert_batch",
"[WASM] [{}] sparse_merkle_insert_batch(): Requested DbHandle that is out of bounds", cid,
);
return darkfi_sdk::error::INTERNAL_ERROR
}
let db_info = &db_handles[db_info_index];
let db_smt = &db_handles[db_smt_index];
let db_roots = &db_handles[db_roots_index];
// Make sure that the contract owns the dbs it wants to write to
if db_smt.contract_id != env.contract_id {
if db_info.contract_id != env.contract_id ||
db_smt.contract_id != env.contract_id ||
db_roots.contract_id != env.contract_id
{
error!(
target: "runtime::smt::sparse_merkle_insert_batch",
"[WASM] [{}] sparse_merkle_insert_batch(): Unauthorized to write to DbHandle", cid,
@@ -147,6 +177,18 @@ pub(crate) fn sparse_merkle_insert_batch(
return darkfi_sdk::error::CALLER_ACCESS_DENIED
}
// This `key` represents the sled key in info where the latest root is
let root_key: Vec<u8> = match Decodable::decode(&mut buf_reader) {
Ok(v) => v,
Err(e) => {
error!(
target: "runtime::smt::sparse_merkle_insert_batch",
"[WASM] [{}] sparse_merkle_insert_batch(): Failed to decode key vec: {}", cid, e,
);
return darkfi_sdk::error::INTERNAL_ERROR
}
};
// This `nullifier` represents the leaf we're adding to the Merkle tree
let nullifiers: Vec<pallas::Base> = match Decodable::decode(&mut buf_reader) {
Ok(v) => v,
@@ -201,6 +243,54 @@ pub(crate) fn sparse_merkle_insert_batch(
return darkfi_sdk::error::INTERNAL_ERROR
};
// Here we add the SMT root to our set of roots
// Since each update to the tree is atomic, we only need to add the last root.
let latest_root = smt.root();
debug!(
target: "runtime::smt::sparse_merkle_insert_batch",
"[WASM] [{}] sparse_merkle_insert_batch(): Appending SMT root to db: {:?}", cid, latest_root,
);
let latest_root_data = serialize(&latest_root);
assert_eq!(latest_root_data.len(), 32);
let blockheight_data = serialize(&(env.verifying_block_height as u32));
// This is hardcoded but should not be
let tx_idx: u16 = 0;
let call_idx: u16 = 0;
assert_eq!(blockheight_data.len(), 4);
// Little-endian
assert_eq!(blockheight_data[3], 0);
let mut value_data = Vec::with_capacity(7);
value_data.write_slice(&blockheight_data[..3]).expect("Unable to serialize blockheight data");
tx_idx.encode(&mut value_data).expect("Unable to serialize tx_id");
call_idx.encode(&mut value_data).expect("Unable to serialize call_idx");
assert_eq!(value_data.len(), 7);
if overlay.insert(&db_roots.tree, &latest_root_data, &value_data).is_err() {
error!(
target: "runtime::smt::sparse_merkle_insert_batch",
"[WASM] [{}] sparse_merkle_insert_batch(): Couldn't insert to db_roots tree", cid,
);
return darkfi_sdk::error::INTERNAL_ERROR
}
// Write a pointer to the latest known root
debug!(
target: "runtime::smt::sparse_merkle_insert_batch",
"[WASM] [{}] sparse_merkle_insert_batch(): Replacing latest SMT root pointer", cid,
);
if overlay.insert(&db_info.tree, &root_key, &latest_root_data).is_err() {
error!(
target: "runtime::smt::sparse_merkle_insert_batch",
"[WASM] [{}] sparse_merkle_insert_batch(): Couldn't insert latest root to db_info tree", cid,
);
return darkfi_sdk::error::INTERNAL_ERROR
}
// Subtract used gas.
// Here we count:
// * The number of nullifiers we inserted into the DB

View File

@@ -71,12 +71,18 @@ pub fn merkle_add(
}
pub fn sparse_merkle_insert_batch(
db_info: DbHandle,
db_smt: DbHandle,
db_roots: DbHandle,
root_key: &[u8],
elements: &[pallas::Base],
) -> GenericResult<()> {
let mut buf = vec![];
let mut len = 0;
len += db_info.encode(&mut buf)?;
len += db_smt.encode(&mut buf)?;
len += db_roots.encode(&mut buf)?;
len += root_key.to_vec().encode(&mut buf)?;
len += elements.to_vec().encode(&mut buf)?;
match unsafe { sparse_merkle_insert_batch_(buf.as_ptr(), len as u32) } {