Skip to content

Commit

Permalink
Merge pull request #54 from SethDusek/updatereservekey
Browse files Browse the repository at this point in the history
Update reserve key
  • Loading branch information
kushti authored Aug 17, 2024
2 parents e507a6a + bb076e7 commit 91bfe9c
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 124 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 29 additions & 18 deletions crates/chaincash_offchain/src/note_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ pub struct OwnershipEntry {
#[cfg_attr(
test,
proptest(
strategy = "proptest::arbitrary::any_with::<TokenId>(ergo_lib::ergotree_ir::chain::token::arbitrary::ArbTokenIdParam::Arbitrary)"
strategy = "proptest::arbitrary::any_with::<TokenId>(ergo_lib::ergotree_ir::chain::token::arbitrary::ArbTokenIdParam::Arbitrary)",
filter = "|&token_id| token_id != TokenId::from_base64(\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\").unwrap()"
)
)]
pub reserve_id: TokenId,
Expand Down Expand Up @@ -173,11 +174,12 @@ impl NoteHistory {
commitment: OwnershipEntry,
) -> Result<SerializedAdProof, NoteHistoryError> {
let mut prover = self.prover()?;
let key = commitment
.reserve_id
.sigma_serialize_bytes()
.unwrap()
.into();
let key = [
&(self.ownership_entries().len() as i64).to_be_bytes()[..],
&commitment.reserve_id.sigma_serialize_bytes().unwrap()[..],
]
.concat()
.into();
let value = commitment.signature.serialize().into();
let insert_op = Operation::Insert(KeyValue { key, value });
prover
Expand All @@ -196,7 +198,7 @@ impl NoteHistory {
AvlTreeData {
digest: self.digest(),
tree_flags,
key_length: 32,
key_length: 40,
value_length_opt: None,
}
}
Expand All @@ -208,21 +210,30 @@ fn build_prover<'a>(
let mut prover = BatchAVLProver::new(
AVLTree::new(
|digest| Node::LabelOnly(NodeHeader::new(Some(*digest), None)),
32,
40,
None,
),
true,
);
signatures
.into_iter()
.enumerate()
.map(
|OwnershipEntry {
reserve_id,
signature,
..
}| {
|(
position,
OwnershipEntry {
reserve_id,
signature,
..
},
)| {
// Unwrap will only fail if OOM
let key: ADKey = reserve_id.sigma_serialize_bytes().unwrap().into();
let key: ADKey = [
(position as i64).to_be_bytes().as_slice(),
reserve_id.sigma_serialize_bytes().unwrap().as_slice(),
]
.concat()
.into();
let value: ADValue = signature.serialize().into();
Operation::Insert(KeyValue { key, value })
},
Expand Down Expand Up @@ -281,25 +292,25 @@ mod test {
assert_eq!(Signature::try_from(&serialized[..]).unwrap(), signature);
}
#[test]
fn test_prover_verifier(commitments in vec(any::<OwnershipEntry>(), 0..100)) {
fn test_prover_verifier(commitments in vec(any::<OwnershipEntry>(), 0..50)) {
let prover = build_prover(commitments.iter()).unwrap();
let mut note_history = NoteHistory::new();
for commitment in commitments {
for (i, commitment) in commitments.into_iter().enumerate() {
let digest = note_history.digest();
let proof = note_history.add_commitment(commitment.clone()).unwrap();
let mut bv = BatchAVLVerifier::new(
&digest.0.to_vec().into(),
&proof,
AVLTree::new(
|digest| Node::LabelOnly(NodeHeader::new(Some(*digest), None)),
32,
40,
None,
),
None,
None,
)
.unwrap();
bv.perform_one_operation(&Operation::Insert(KeyValue { key: commitment.reserve_id.sigma_serialize_bytes().unwrap().into(), value: commitment.signature.serialize().into() })).unwrap();
bv.perform_one_operation(&Operation::Insert(KeyValue { key: [(i as i64).to_be_bytes().as_slice(), commitment.reserve_id.sigma_serialize_bytes().unwrap().as_slice()].concat().into(), value: commitment.signature.serialize().into() })).unwrap();
}
assert_eq!(&note_history.digest().0[..], &prover.digest().unwrap()[..]);
}
Expand Down
5 changes: 3 additions & 2 deletions crates/chaincash_offchain/src/test_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ pub fn force_any_val<T: Arbitrary>() -> T {
.current()
}
// Pre-compiled note address since we need node to compile contract otherwise
pub const NOTE_ADDRESS: &'static str = "ZCrKu5bZniW2nkWfpGE5Tiyqk5VGcmb95ZmSbYAeq2jSt1PnP2LoXZBVBiAWRFt5pM6Eo8AtDMNwM4iesCcguSiiPnfnKKCHfcTh5kUCr3ZGXuMmABgPMjeHtkuthEU1coUkh1CYbw1xuiHC2udWCiLLRrUYJiT9i3hWNmgEm8DR3fgj4udrefshDR5capWKM55yeFYeht4wUs9RjBKGpGby89JWbQAa414wNU3BhYoPdhYHZfLfgddDPPCqwdVrwcEoMrfmtZFPUu1Q1xLqpVL5rbwM9mavDTXKpcvRvACW7J3jmUk8mAmH4PBJa14m2tcdxuntQo8GivAcxmEJpd36WSjrc6cHiXzq4R3e6fSNu7QWxAYyHafxR8LTGkss919iUWyzKZtNVJuAu9wGKP9FeJSXGDAopK1nR4dthbLvRVArRbTkhQSSD3MdT1PAZkjxvRZhfVEzf6FbHPocHkqfJf5fpLqqzB7TyP9utee6vAaw2UZiYAaj94PdYpJYxTUDT61zWQsZhG6Wtx5LDm6nVUN5A9xoBqiLEpSYQeuz5vk4ryv3ErXqMNT1Rp";
pub const RESERVE_ADDRESS: &'static str = "3tqwfXjHWgDQB6vacLK2na6r7QgBftHGzwJorVXCRFdtJthTCjd5FXwvtsPENdcRYnbWeF8F5mUkaBLMzrrGUzK4dhHsgGmwrQCz5zLLmrP4iQqXpuVtNXKDqdGZsQYF6fyLsEzHUTGsCBsju6xmbxz6mg7V6dGd5eJkuCjsU728ts4kQiYByUwG2Rb3UwoV8i8o3SqNeVBC6FZVW5YiZJzN95tNrfPyzMS6hz6jzbN4K83gAFLMcpQPjFpodRj35ymftJPCVoCYXn1Rz1kK5UrxDC62ny5YPu5E8k8ZAiGvnHq4KpMnN43L4fe7jiT3KLkQCsyYtXTAasqWq89un3FFBv4sPaVWEA1JPp7k8cHeiFWZewpytgMYZ9qE5NePV5zMYH6xLSvmcQg16thst3NHyUHbDLN8nqFnTJ4i9UW5YNZbYiF9gZYW7WjQhj8XgnFWxaKhoADgW5g8yWHPa3Cm3HvxxUXNjfiTNYhFZ1LVaaigje2mx32dbzJi8XoUdDFkYSxmnau8s14zbjxbpXdFmwirE2YvhYr1ScygFznkyJ3tL8tqcBsNKbSiybZLXxBxQ39aq7jeftztvgBhr3X4RKq2q3VYHZZBkM3AD8j8rD17YSieXNaKscNB8TLrEUFyz2FUyNxpbN895be2qZ5VJtKxWGT3jJ3JxTaPoUzm3rwKWjo3QohHict8WFwiiivQr55uCAZRJR13J9ie9cGhi3UJqSjuQUzqiYkx4HtRNoHYiToNqLhs8r7azgAWir73HYBiMAwgpp6giNkzwng5YzxrWzm4YpWXCksoGz1ukFqSzYKQNhWBnqsoU9MajhRzXW8wKDGrqWbwVqch2jz39XDSMf7Qc1AGYg2NdRU7vGL94LEWcGyN42ZCkWU3gYVjt6MYKJiUmKsT2ExniKUizfhVCqVm3JZKvJtQu9u3uRyBx4Gkp1Z9ctSm3oBfxrvZHsF1x7Dv";
// This is compiled with reserve/receipt hashes set to "". TODO: change later when testing redeem note
pub const NOTE_ADDRESS: &'static str = "6PS7aYjk1ifeqe4Am4FMbfJcUEpwPvTZeqYTBHTS1CiWhc7RSVQ9UorZtniUwETAgXWYowVSSbD574kiDZhbH2zAuVXzyX5GG6t3gvWJRMgG9BcqGUk7e8c9bQN3AAMwrFzMii8uKBcmRUtBNt1ARAG6ge213msyUaN4vKttkWxyxZdCidUAAdMwbpuMyrUnwKWdkqr1UtHEuAVqLRGHX62rhD5vsBw9GdEvhTCMceeXNiciKhVufRo9FwR7hoQvKWbRpunceF38FHUMMtfa6ik1yPUrn2hFuj5X6xvb9jwBKtqJLqUCmcAcV3uWaLiQRDENLMA6EbPPUNy7W2Pn9G5fBisTknszvs9umrznGnqAUGFdqbXEgGShMYoGzkbPjiFPkzEU3nDNv3W2VNh2fdJ52qPJis2SNyGSP6o8gmuMhXw9QV4jbhrHr3bUJnV1vxUy3NSMXjPiMSpCnfok2pGnfUtTkqzSGtA5anQS1G2gW3ZjSDn81pTWdrbCfjcMuL7aAdFfDpH5WR5eF3xGKgscKCgq67eGG4daHd9x1qh2R74vmJmvMmAqgUZnRyLzPb6kHCBk";
pub const RESERVE_ADDRESS: &'static str = "V5MPcyRpCbWqj3x9wuTCnnDiX4BLX8XunofJYxSN6MD81fGcMePFoiRFUpCBYRQV7BUV7o6cD5Amv3JcxRRSjM12D3JaonJtzaoPEc4R1TS3NTjYysGAtenRuDnJ65sDsUMNMpmW7jtwYqivo8PrwuB93yK83vEBBnAkWqwyFGnsMS44dmkoQZfXprE2cBgG8Wgpv2UN4AN3NfxQ1HZ9gBbfGsLxphnvYdpWLX85XAuZHXZv1rbwZGKaSoZYyYgWpRkVuj3kjNWVbiQLaJP17xw8bv1yXWkR7w26JhhTSfq3JTxp8j1PZJd5n7hfZaf3vJTyYinqM8hwgU9aYcBVRpoSs7HCLQoBBe4QqbEk6txJXYdw75UFpbSYj6t2cBV9Y393c9X7h16DUm8Q9sotJs8kCL1tKqoxpQUP4dXxwTSqqZPGPKzE95mVkRg6UoyRKNpBJsoptash7QK9jro8S8ZebeUMHnTidMwjgGYk91DSPQwfat6FkHLkkEXuwVGaRnwWoqnaN4unqVC4a8B3Zp6iL8UgQFfbqABw5xMrvaZaLMV8aqsnA8H9E2HjSpv1KF73MuNco6geKYm2X6z6QDSBmgxQgeeeSigB4aJNZuATKp8oyg6t2DNWfDvTV8fiwvjqm8dMZ48FpxiXecU5RyRJTgxmmo4fabZN6T7CASFPkAVkgbAbs9RHoFa1b2LAAZUqKQQeAbSfEXZRL8WW5ttv4JYjZhBmg1TBgcdAvvozv22HMKymyYnrTdFp4tAP6KSCoL7joWx6xUvNrWa3txqsEihwXc6a8h7DqkThYerVP9hPjNPnf2eh5PAtBZZRDmo4tJ3zJvutnVHjPbqCjPWeYYtYxYQ4sGoZS25xvc97mCo35xZ9Ld7kynTHsEY6KfYPSM7ivdzxPQjVzQZHPcKTaTY4divEKWeg6jpW4daNBYWgqxUxNJZSo5ErvmVMaiAVhKGHXzrw54fcwSY6s";

pub fn create_box(box_candidate: ErgoBoxCandidate) -> ErgoBox {
let mut rng = thread_rng();
Expand Down
4 changes: 2 additions & 2 deletions crates/chaincash_offchain/src/transactions/notes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub fn mint_note_transaction(
let prover = BatchAVLProver::new(
AVLTree::new(
|digest| Node::LabelOnly(NodeHeader::new(Some(*digest), None)),
32,
40,
None,
),
true,
Expand All @@ -65,7 +65,7 @@ pub fn mint_note_transaction(
let avl_tree = AvlTreeData {
digest,
tree_flags: avl_flags,
key_length: 32,
key_length: 40,
value_length_opt: None,
};
let token_id = inputs
Expand Down
194 changes: 94 additions & 100 deletions crates/chaincash_services/src/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use ergo_lib::{
token::TokenId,
},
ergo_tree::ErgoTree,
mir::constant::TryExtractInto,
serialization::SigmaSerializable,
},
};
Expand All @@ -44,6 +43,81 @@ pub enum ScannerError {
InvalidReserveBox(TokenId, TxId),
}

struct ContractScan<'a> {
scan_type: ScanType,
scan: Scan<'a>,
}

impl<'a> ContractScan<'a> {
async fn new(
state: &ServerState,
scan_type: ScanType,
public_keys: &[EcPoint],
) -> Result<Self, ScannerError> {
let (contract, register) = match scan_type {
ScanType::Reserves => (
state.compiler.reserve_contract().await?,
NonMandatoryRegisterId::R4,
),
ScanType::Notes => (
state.compiler.note_contract().await?,
NonMandatoryRegisterId::R5,
),
ScanType::Receipts => (
state.compiler.receipt_contract().await?,
NonMandatoryRegisterId::R7,
),
};
let scan = Self::pubkey_scan(
format!("Chaincash {} scan", scan_type.to_str()),
contract,
register,
public_keys,
);
Ok(Self { scan_type, scan })
}

fn pubkey_scan(
scan_name: impl Into<std::borrow::Cow<'a, str>>,
contract: &ErgoTree,
pubkey_register: NonMandatoryRegisterId,
public_keys: &[EcPoint],
) -> Scan<'a> {
Scan {
scan_name: scan_name.into(),
wallet_interaction: "off".into(),
tracking_rule: TrackingRule::And {
args: vec![
TrackingRule::Contains {
register: Some(RegisterId::R1),
value: contract.sigma_serialize_bytes().unwrap().into(),
},
TrackingRule::Or {
args: public_keys
.iter()
.map(|pubkey| TrackingRule::Equals {
register: Some(pubkey_register.into()),
value: pubkey.clone().into(),
})
.collect(),
},
],
},
remove_offchain: true,
}
}
async fn register(
&'a self,
state: &ServerState,
) -> Result<chaincash_store::scans::Scan<'a>, ScannerError> {
let scan_id = state.node.endpoints().scan()?.register(&self.scan).await?;
let store_scan =
chaincash_store::scans::Scan::new(scan_id, &*self.scan.scan_name, self.scan_type);
state.store.scans().add(&store_scan)?;
Ok(store_scan)
}
}

// Wait for the next block before re-checking scans
async fn wait_scan_block(state: &ServerState) -> Result<(), ScannerError> {
let wallet = state.node.endpoints().wallet()?;
Expand All @@ -67,83 +141,6 @@ async fn get_transaction(
.find(|tx| tx.id() == *tx_id))
}

fn pubkey_scan<'a>(
scan_name: impl Into<std::borrow::Cow<'a, str>>,
contract: &ErgoTree,
pubkey_register: NonMandatoryRegisterId,
public_keys: &[EcPoint],
) -> Scan<'a> {
Scan {
scan_name: scan_name.into(),
wallet_interaction: "off".into(),
tracking_rule: TrackingRule::And {
args: vec![
TrackingRule::Contains {
register: Some(RegisterId::R1),
value: contract.sigma_serialize_bytes().unwrap().into(),
},
TrackingRule::Or {
args: public_keys
.iter()
.map(|pubkey| TrackingRule::Equals {
register: Some(pubkey_register.into()),
value: pubkey.clone().into(),
})
.collect(),
},
],
},
remove_offchain: true,
}
}

fn pubkeys_from_scan(scan: &RegisteredScan) -> Option<Vec<EcPoint>> {
match &scan.scan.tracking_rule {
TrackingRule::And { args } => match &args[..] {
[.., TrackingRule::Or { args }] => Some(
args.iter()
.filter_map(|arg| {
if let TrackingRule::Equals { register: _, value } = arg {
value.clone().try_extract_into::<EcPoint>().ok()
} else {
None
}
})
.collect(),
),
_ => None,
},
_ => None,
}
}

async fn register_scan<'a>(
state: &ServerState,
scan_type: ScanType,
public_keys: &[EcPoint],
) -> Result<chaincash_store::scans::Scan<'a>, ScannerError> {
let name = format!("Chaincash {} scan", scan_type.to_str());
let (contract, register) = match scan_type {
ScanType::Reserves => (
state.compiler.reserve_contract().await?,
NonMandatoryRegisterId::R4,
),
ScanType::Notes => (
state.compiler.note_contract().await?,
NonMandatoryRegisterId::R5,
),
ScanType::Receipts => (
state.compiler.receipt_contract().await?,
NonMandatoryRegisterId::R7,
),
};
let scan = pubkey_scan(name, contract, register, public_keys);
let scan_id = state.node.endpoints().scan()?.register(&scan).await?;
let store_scan = chaincash_store::scans::Scan::new(scan_id, scan.scan_name, scan_type);
state.store.scans().add(&store_scan)?;
Ok(store_scan)
}

// Load scans by type. If node changes then wrong scans will be detected and re-registered
// Returns (needs_rescan, scan_type)
async fn load_scan(
Expand All @@ -163,29 +160,20 @@ async fn load_scan(
_ => None,
})
.collect::<Vec<_>>();
let contract_scan = ContractScan::new(state, scan_type, &addresses).await?;
let scan = state.store.scans().scan_by_type(scan_type)?;
if let Some(scan) = scan {
let node_scan = node_scans
.iter()
.find(|node_scan| scan.scan_name == node_scan.scan.scan_name);
if let Some(reserve_scan) = node_scan {
if let Some(scan_pubkeys) = pubkeys_from_scan(reserve_scan) {
if addresses.iter().all(|wallet_pubkey| {
scan_pubkeys
.iter()
.any(|scan_pubkey| wallet_pubkey == scan_pubkey)
}) {
return Ok((false, scan.scan_id));
}
} else {
warn!(
"Scan #{} ({}) invalidated, re-registering",
scan.scan_id, scan.scan_name
);
}
if node_scans.iter().any(|node_scan| {
scan.scan_id as u32 == node_scan.scan_id
&& scan.scan_name == node_scan.scan.scan_name
&& node_scan.scan == contract_scan.scan
}) {
return Ok((false, scan.scan_id));
} else {
warn!("Scan {} invalidated, re-registering", scan.scan_id);
}
}
let scan_id = register_scan(state, scan_type, &addresses).await?.scan_id;
let scan_id = contract_scan.register(state).await?.scan_id;
Ok((true, scan_id))
}

Expand All @@ -196,7 +184,7 @@ async fn reserve_scanner(state: Arc<ServerState>, scan_id: i32) -> Result<(), Sc
.extensions()
.get_all_unspent_boxes(scan_id as u32, false)
.await?;
for scan_box in scan_boxes {
for scan_box in &scan_boxes {
match ReserveBoxSpec::try_from(&scan_box.ergo_box) {
Ok(reserve_box) => {
state.store.reserves().add_or_update(&reserve_box)?;
Expand All @@ -207,6 +195,12 @@ async fn reserve_scanner(state: Arc<ServerState>, scan_id: i32) -> Result<(), Sc
),
}
}
state
.store
.reserves()
.delete_not_in(scan_boxes.iter().map(|b| b.ergo_box.box_id()))?
.into_iter()
.for_each(|deleted| info!("Deleting box id: {deleted}"));
wait_scan_block(&state).await?;
}
}
Expand Down Expand Up @@ -337,8 +331,8 @@ pub async fn start_scanner(state: Arc<ServerState>) -> Result<(), ScannerError>
let (rescan, note_scan) = load_scan(&state, ScanType::Notes, &scans).await?;
needs_rescan |= rescan;
if needs_rescan {
//Rescan from block #1,100,000. This height can be increased later when chaincash is deployed
let _ = state.node.endpoints().wallet()?.rescan(1_100_000).await;
//Rescan from block #1,318_639. This height can be increased later when chaincash is deployed
let _ = state.node.endpoints().wallet()?.rescan(1_318_639).await;
}
tokio::spawn(reserve_scanner(state.clone(), reserve_scan));
tokio::spawn(note_scanner(state.clone(), note_scan));
Expand Down
Loading

0 comments on commit 91bfe9c

Please sign in to comment.