Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions ergotree-interpreter/src/eval/create_avl_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ impl Evaluable for CreateAvlTree {
};

let tree_flags = AvlTreeFlags::parse(flags_v);
let digest = ADDigest::try_from(digest_v.as_vec_u8()).map_err(map_eval_err)?;
// CreateAvlTree keeps its 33-byte digest validation (behavior unchanged);
// the field is now `Vec<u8>` only so `updateDigest` can hold other lengths.
let digest = ADDigest::try_from(digest_v.as_vec_u8())
.map_err(map_eval_err)?
.0
.to_vec();

Ok(Value::AvlTree(Box::new(AvlTreeData {
tree_flags,
Expand Down Expand Up @@ -84,7 +89,7 @@ mod tests {
.into();

let tree = eval_out_wo_ctx::<AvlTreeData>(&expr);
assert_eq!(tree.digest, initial_digest);
assert_eq!(tree.digest, initial_digest.0.to_vec());
assert_eq!(tree.key_length, 1);
assert_eq!(tree.value_length_opt, None);
assert_eq!(tree.tree_flags, flags);
Expand Down
144 changes: 110 additions & 34 deletions ergotree-interpreter/src/eval/savltree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::convert::TryFrom;
use ergotree_ir::ergo_tree::ErgoTreeVersion;

use bytes::Bytes;
Expand All @@ -13,12 +12,10 @@ use ergo_avltree_rust::batch_node::Node;
use ergo_avltree_rust::batch_node::NodeHeader;
use ergo_avltree_rust::operation::KeyValue;
use ergo_avltree_rust::operation::Operation;
use ergo_chain_types::ADDigest;
use ergotree_ir::mir::avl_tree_data::AvlTreeData;
use ergotree_ir::mir::avl_tree_data::AvlTreeFlags;
use ergotree_ir::mir::constant::TryExtractInto;
use ergotree_ir::mir::value::{CollKind, NativeColl, Value};
use sigma_ser::ScorexSerializable;

use super::EvalError;
use super::EvalFn;
Expand All @@ -27,7 +24,7 @@ use ergotree_ir::types::stype::SType;
pub(crate) static DIGEST_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
let avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
Ok(Value::Coll(CollKind::NativeColl(NativeColl::CollByte(
avl_tree_data.digest.0.iter().map(|&b| b as i8).collect(),
avl_tree_data.digest.iter().map(|&b| b as i8).collect(),
))))
};

Expand Down Expand Up @@ -80,12 +77,16 @@ pub(crate) static UPDATE_OPERATIONS_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, arg

pub(crate) static UPDATE_DIGEST_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {
let mut avl_tree_data = obj.try_extract_into::<AvlTreeData>()?;
// `CAvlTree.updateDigest` stores the new digest with no length check
// (`treeData.copy(digest = newDigest)`); mirror that — accept any-length
// `Coll[Byte]`. A non-33 digest exists only in memory: it cannot round-trip
// through serialization (the parser reads exactly 33 bytes), same as the
// reference impl.
let new_digest = {
let v = args.first().cloned().ok_or_else(|| {
EvalError::AvlTree("eval is missing first arg (new_digest)".to_string())
})?;
let bytes_vec = v.try_extract_into::<Vec<u8>>()?;
ADDigest::try_from(bytes_vec).map_err(map_eval_err)?
v.try_extract_into::<Vec<u8>>()?
};
avl_tree_data.digest = new_digest;
Ok(Value::AvlTree(Box::new(avl_tree_data)))
Expand All @@ -108,7 +109,7 @@ pub(crate) static GET_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {
Bytes::from(v.try_extract_into::<Vec<u8>>()?)
};

let starting_digest = Bytes::from(avl_tree_data.digest.0.to_vec());
let starting_digest = Bytes::from(avl_tree_data.digest.clone());
let mut bv = BatchAVLVerifier::new(
&starting_digest,
&proof,
Expand Down Expand Up @@ -156,7 +157,7 @@ pub(crate) static GET_MANY_EVAL_FN: EvalFn =
Bytes::from(v.try_extract_into::<Vec<u8>>()?)
};

let starting_digest = Bytes::from(avl_tree_data.digest.0.to_vec());
let starting_digest = Bytes::from(avl_tree_data.digest.clone());
let mut bv = BatchAVLVerifier::new(
&starting_digest,
&proof,
Expand Down Expand Up @@ -223,7 +224,7 @@ pub(crate) static INSERT_EVAL_FN: EvalFn =
Bytes::from(v.try_extract_into::<Vec<u8>>()?)
};

let starting_digest = Bytes::from(avl_tree_data.digest.0.to_vec());
let starting_digest = Bytes::from(avl_tree_data.digest.clone());
let mut bv = BatchAVLVerifier::new(
&starting_digest,
&proof,
Expand Down Expand Up @@ -258,8 +259,7 @@ pub(crate) static INSERT_EVAL_FN: EvalFn =
}
}
Ok(if let Some(new_digest) = bv.digest() {
let digest = ADDigest::scorex_parse_bytes(&new_digest)?;
avl_tree_data.digest = digest;
avl_tree_data.digest = new_digest.to_vec();
Value::Opt(Some(Box::new(Value::AvlTree(avl_tree_data.into()))))
} else {
Value::Opt(None)
Expand Down Expand Up @@ -288,7 +288,7 @@ pub(crate) static REMOVE_EVAL_FN: EvalFn =
Bytes::from(v.try_extract_into::<Vec<u8>>()?)
};

let starting_digest = Bytes::from(avl_tree_data.digest.0.to_vec());
let starting_digest = Bytes::from(avl_tree_data.digest.clone());
let mut bv = BatchAVLVerifier::new(
&starting_digest,
&proof,
Expand Down Expand Up @@ -316,8 +316,7 @@ pub(crate) static REMOVE_EVAL_FN: EvalFn =
}
}
if let Some(new_digest) = bv.digest() {
let digest = ADDigest::scorex_parse_bytes(&new_digest)?;
avl_tree_data.digest = digest;
avl_tree_data.digest = new_digest.to_vec();
Ok(Value::Opt(Some(Box::new(Value::AvlTree(
avl_tree_data.into(),
)))))
Expand All @@ -344,7 +343,7 @@ pub(crate) static CONTAINS_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args| {
Bytes::from(v.try_extract_into::<Vec<u8>>()?)
};

let starting_digest = Bytes::from(avl_tree_data.digest.0.to_vec());
let starting_digest = Bytes::from(avl_tree_data.digest.clone());
let mut bv = BatchAVLVerifier::new(
&starting_digest,
&proof,
Expand Down Expand Up @@ -392,7 +391,7 @@ pub(crate) static UPDATE_EVAL_FN: EvalFn =
Bytes::from(v.try_extract_into::<Vec<u8>>()?)
};

let starting_digest = Bytes::from(avl_tree_data.digest.0.to_vec());
let starting_digest = Bytes::from(avl_tree_data.digest.clone());
let mut bv = BatchAVLVerifier::new(
&starting_digest,
&proof,
Expand Down Expand Up @@ -420,8 +419,7 @@ pub(crate) static UPDATE_EVAL_FN: EvalFn =
}
}
Ok(if let Some(new_digest) = bv.digest() {
let digest = ADDigest::scorex_parse_bytes(&new_digest)?;
avl_tree_data.digest = digest;
avl_tree_data.digest = new_digest.to_vec();
Value::Opt(Some(Value::AvlTree(avl_tree_data.into()).into()))
} else {
Value::Opt(None)
Expand Down Expand Up @@ -451,7 +449,7 @@ pub(crate) static INSERT_OR_UPDATE_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args
Bytes::from(v.try_extract_into::<Vec<u8>>()?)
};

let starting_digest = Bytes::from(avl_tree_data.digest.0.to_vec());
let starting_digest = Bytes::from(avl_tree_data.digest.clone());
let mut bv = BatchAVLVerifier::new(
&starting_digest,
&proof,
Expand Down Expand Up @@ -479,8 +477,7 @@ pub(crate) static INSERT_OR_UPDATE_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, args
}
}
Ok(if let Some(new_digest) = bv.digest() {
let digest = ADDigest::scorex_parse_bytes(&new_digest)?;
avl_tree_data.digest = digest;
avl_tree_data.digest = new_digest.to_vec();
Value::Opt(Some(Box::new(Value::AvlTree(avl_tree_data.into()))))
} else {
Value::Opt(None)
Expand Down Expand Up @@ -514,12 +511,17 @@ mod tests {
use crate::eval::test_util::{eval_out_wo_ctx, try_eval_out_with_version};

use super::*;
use ergo_chain_types::ADDigest;
use sigma_ser::ScorexSerializable;
use sigma_util::{AsVecI8, AsVecU8};

#[test]
fn eval_avl_get() {
let mut prover = populate_tree(vec![(vec![1u8], 10u64.to_be_bytes().to_vec())]);
let initial_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap()).unwrap();
let initial_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap())
.unwrap()
.0
.to_vec();

let key1 = Bytes::from(vec![1u8]);
let key2 = Bytes::from(vec![2u8]);
Expand Down Expand Up @@ -589,7 +591,10 @@ mod tests {
(vec![2u8], 20u64.to_be_bytes().to_vec()),
]);

let initial_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap()).unwrap();
let initial_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap())
.unwrap()
.0
.to_vec();

let key1 = Bytes::from(vec![1u8]);
let key2 = Bytes::from(vec![2u8]);
Expand Down Expand Up @@ -672,7 +677,10 @@ mod tests {
),
true,
);
let initial_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap()).unwrap();
let initial_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap())
.unwrap()
.0
.to_vec();
let key1 = Bytes::from(vec![1u8]);
let key2 = Bytes::from(vec![2u8; 1]);
let key3 = Bytes::from(vec![3u8; 1]);
Expand All @@ -691,7 +699,10 @@ mod tests {
prover.perform_one_operation(&op1).unwrap();
prover.perform_one_operation(&op2).unwrap();
prover.perform_one_operation(&op3).unwrap();
let final_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap()).unwrap();
let final_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap())
.unwrap()
.0
.to_vec();
let proof: Constant = prover
.generate_proof()
.into_iter()
Expand Down Expand Up @@ -778,15 +789,21 @@ mod tests {
),
true,
);
let initial_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap()).unwrap();
let initial_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap())
.unwrap()
.0
.to_vec();
let key1 = Bytes::from(vec![1u8]);
let op = Operation::InsertOrUpdate(KeyValue {
key: key1,
value: Bytes::from(10u64.to_be_bytes().to_vec()),
});
prover.perform_one_operation(&op).unwrap();
prover.perform_one_operation(&op).unwrap();
let final_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap()).unwrap();
let final_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap())
.unwrap()
.0
.to_vec();
let proof: Constant = prover
.generate_proof()
.into_iter()
Expand Down Expand Up @@ -839,7 +856,7 @@ mod tests {
proptest! {
#[test]
fn eval_avl_digest(v in any::<AvlTreeData>()) {
let digest: Vec<i8> = v.digest.into();
let digest: Vec<i8> = v.digest.as_vec_i8();
let obj = Expr::Const(v.into());

let expr: Expr = MethodCall::new(
Expand Down Expand Up @@ -1012,7 +1029,51 @@ mod tests {
.into();
let res = eval_out_wo_ctx::<Value>(&expr);
if let Value::AvlTree(a) = res {
assert_eq!(a.digest, new_digest);
assert_eq!(a.digest, new_digest.0.to_vec());
} else {
unreachable!();
}
}
}

#[test]
fn eval_avl_update_digest_any_length() {
// `CAvlTree.updateDigest` stores any-length `Coll[Byte]` with no length
// check (santa-eval `AvlTree.updateDigest_any_length`): the new digest is
// kept verbatim and read back by `.digest`, regardless of length. Such a
// value is in-memory only — it does not round-trip through serialization.
for new_digest in [
vec![1u8, 2, 3],
Vec::<u8>::new(),
(1u8..=40).collect::<Vec<u8>>(),
] {
let tree = AvlTreeData {
digest: vec![0u8; 33],
tree_flags: AvlTreeFlags::new(true, true, true),
key_length: 32,
value_length_opt: None,
};
let updated: Expr = MethodCall::new(
Expr::Const(tree.into()),
savltree::UPDATE_DIGEST_METHOD.clone(),
vec![Constant::from(new_digest.clone()).into()],
)
.unwrap()
.into();
// stored verbatim
assert_eq!(
eval_out_wo_ctx::<AvlTreeData>(&updated.clone()).digest,
new_digest
);
// and read back via `.digest`, whatever the length
let digest_back: Expr =
MethodCall::new(updated, savltree::DIGEST_METHOD.clone(), vec![])
.unwrap()
.into();
if let Value::Coll(CollKind::NativeColl(NativeColl::CollByte(b))) =
eval_out_wo_ctx::<Value>(&digest_back)
{
assert_eq!(b.as_vec_u8(), new_digest);
} else {
unreachable!();
}
Expand All @@ -1026,7 +1087,10 @@ mod tests {
(vec![2u8], 20u64.to_be_bytes().to_vec()),
(vec![3u8], 30u64.to_be_bytes().to_vec()),
]);
let digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap()).unwrap();
let digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap())
.unwrap()
.0
.to_vec();

let op = Operation::Lookup(Bytes::from(vec![2u8]));
prover.perform_one_operation(&op).unwrap();
Expand Down Expand Up @@ -1061,12 +1125,18 @@ mod tests {
#[test]
fn eval_avl_remove() {
let mut prover = populate_tree(vec![(vec![1u8], 10u64.to_be_bytes().to_vec())]);
let initial_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap()).unwrap();
let initial_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap())
.unwrap()
.0
.to_vec();

let key1 = Bytes::from(vec![1u8]);
let op1 = Operation::Remove(key1);
prover.perform_one_operation(&op1).unwrap();
let final_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap()).unwrap();
let final_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap())
.unwrap()
.0
.to_vec();
let proof: Constant = prover
.generate_proof()
.into_iter()
Expand Down Expand Up @@ -1119,7 +1189,10 @@ mod tests {
(vec![2u8], 20u64.to_be_bytes().to_vec()),
(vec![3u8], 30u64.to_be_bytes().to_vec()),
]);
let initial_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap()).unwrap();
let initial_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap())
.unwrap()
.0
.to_vec();

let op1 = Operation::Update(KeyValue {
key: Bytes::from(vec![2u8]),
Expand All @@ -1132,7 +1205,10 @@ mod tests {
prover.perform_one_operation(&op1).unwrap();
prover.perform_one_operation(&op2).unwrap();

let final_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap()).unwrap();
let final_digest = ADDigest::scorex_parse_bytes(&prover.digest().unwrap())
.unwrap()
.0
.to_vec();
let proof: Constant = prover
.generate_proof()
.into_iter()
Expand Down
4 changes: 2 additions & 2 deletions ergotree-interpreter/src/eval/scontext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pub(crate) static LAST_BLOCK_UTXO_ROOT_HASH_EVAL_FN: EvalFn = |_mc, _env, ctx, o
obj
)));
}
let digest = ctx.headers[0].state_root;
let digest = ctx.headers[0].state_root.0.to_vec();
let tree_flags = AvlTreeFlags::new(true, true, true);
Ok(Value::AvlTree(Box::from(AvlTreeData {
digest,
Expand Down Expand Up @@ -203,7 +203,7 @@ mod tests {
.unwrap()
.into();
let ctx = force_any_val::<Context>();
let digest = ctx.headers[0].state_root;
let digest = ctx.headers[0].state_root.0.to_vec();
let tree_flags = AvlTreeFlags::new(true, true, true);
let avl_tree_data = AvlTreeData {
digest,
Expand Down
Loading
Loading