Skip to content

Commit bb076e7

Browse files
committed
Update scanner scan invalidation
1 parent c98eb4b commit bb076e7

File tree

3 files changed

+112
-101
lines changed

3 files changed

+112
-101
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/chaincash_services/src/scanner.rs

Lines changed: 94 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use ergo_lib::{
1919
token::TokenId,
2020
},
2121
ergo_tree::ErgoTree,
22-
mir::constant::TryExtractInto,
2322
serialization::SigmaSerializable,
2423
},
2524
};
@@ -44,6 +43,81 @@ pub enum ScannerError {
4443
InvalidReserveBox(TokenId, TxId),
4544
}
4645

46+
struct ContractScan<'a> {
47+
scan_type: ScanType,
48+
scan: Scan<'a>,
49+
}
50+
51+
impl<'a> ContractScan<'a> {
52+
async fn new(
53+
state: &ServerState,
54+
scan_type: ScanType,
55+
public_keys: &[EcPoint],
56+
) -> Result<Self, ScannerError> {
57+
let (contract, register) = match scan_type {
58+
ScanType::Reserves => (
59+
state.compiler.reserve_contract().await?,
60+
NonMandatoryRegisterId::R4,
61+
),
62+
ScanType::Notes => (
63+
state.compiler.note_contract().await?,
64+
NonMandatoryRegisterId::R5,
65+
),
66+
ScanType::Receipts => (
67+
state.compiler.receipt_contract().await?,
68+
NonMandatoryRegisterId::R7,
69+
),
70+
};
71+
let scan = Self::pubkey_scan(
72+
format!("Chaincash {} scan", scan_type.to_str()),
73+
contract,
74+
register,
75+
public_keys,
76+
);
77+
Ok(Self { scan_type, scan })
78+
}
79+
80+
fn pubkey_scan(
81+
scan_name: impl Into<std::borrow::Cow<'a, str>>,
82+
contract: &ErgoTree,
83+
pubkey_register: NonMandatoryRegisterId,
84+
public_keys: &[EcPoint],
85+
) -> Scan<'a> {
86+
Scan {
87+
scan_name: scan_name.into(),
88+
wallet_interaction: "off".into(),
89+
tracking_rule: TrackingRule::And {
90+
args: vec![
91+
TrackingRule::Contains {
92+
register: Some(RegisterId::R1),
93+
value: contract.sigma_serialize_bytes().unwrap().into(),
94+
},
95+
TrackingRule::Or {
96+
args: public_keys
97+
.iter()
98+
.map(|pubkey| TrackingRule::Equals {
99+
register: Some(pubkey_register.into()),
100+
value: pubkey.clone().into(),
101+
})
102+
.collect(),
103+
},
104+
],
105+
},
106+
remove_offchain: true,
107+
}
108+
}
109+
async fn register(
110+
&'a self,
111+
state: &ServerState,
112+
) -> Result<chaincash_store::scans::Scan<'a>, ScannerError> {
113+
let scan_id = state.node.endpoints().scan()?.register(&self.scan).await?;
114+
let store_scan =
115+
chaincash_store::scans::Scan::new(scan_id, &*self.scan.scan_name, self.scan_type);
116+
state.store.scans().add(&store_scan)?;
117+
Ok(store_scan)
118+
}
119+
}
120+
47121
// Wait for the next block before re-checking scans
48122
async fn wait_scan_block(state: &ServerState) -> Result<(), ScannerError> {
49123
let wallet = state.node.endpoints().wallet()?;
@@ -67,83 +141,6 @@ async fn get_transaction(
67141
.find(|tx| tx.id() == *tx_id))
68142
}
69143

70-
fn pubkey_scan<'a>(
71-
scan_name: impl Into<std::borrow::Cow<'a, str>>,
72-
contract: &ErgoTree,
73-
pubkey_register: NonMandatoryRegisterId,
74-
public_keys: &[EcPoint],
75-
) -> Scan<'a> {
76-
Scan {
77-
scan_name: scan_name.into(),
78-
wallet_interaction: "off".into(),
79-
tracking_rule: TrackingRule::And {
80-
args: vec![
81-
TrackingRule::Contains {
82-
register: Some(RegisterId::R1),
83-
value: contract.sigma_serialize_bytes().unwrap().into(),
84-
},
85-
TrackingRule::Or {
86-
args: public_keys
87-
.iter()
88-
.map(|pubkey| TrackingRule::Equals {
89-
register: Some(pubkey_register.into()),
90-
value: pubkey.clone().into(),
91-
})
92-
.collect(),
93-
},
94-
],
95-
},
96-
remove_offchain: true,
97-
}
98-
}
99-
100-
fn pubkeys_from_scan(scan: &RegisteredScan) -> Option<Vec<EcPoint>> {
101-
match &scan.scan.tracking_rule {
102-
TrackingRule::And { args } => match &args[..] {
103-
[.., TrackingRule::Or { args }] => Some(
104-
args.iter()
105-
.filter_map(|arg| {
106-
if let TrackingRule::Equals { register: _, value } = arg {
107-
value.clone().try_extract_into::<EcPoint>().ok()
108-
} else {
109-
None
110-
}
111-
})
112-
.collect(),
113-
),
114-
_ => None,
115-
},
116-
_ => None,
117-
}
118-
}
119-
120-
async fn register_scan<'a>(
121-
state: &ServerState,
122-
scan_type: ScanType,
123-
public_keys: &[EcPoint],
124-
) -> Result<chaincash_store::scans::Scan<'a>, ScannerError> {
125-
let name = format!("Chaincash {} scan", scan_type.to_str());
126-
let (contract, register) = match scan_type {
127-
ScanType::Reserves => (
128-
state.compiler.reserve_contract().await?,
129-
NonMandatoryRegisterId::R4,
130-
),
131-
ScanType::Notes => (
132-
state.compiler.note_contract().await?,
133-
NonMandatoryRegisterId::R5,
134-
),
135-
ScanType::Receipts => (
136-
state.compiler.receipt_contract().await?,
137-
NonMandatoryRegisterId::R7,
138-
),
139-
};
140-
let scan = pubkey_scan(name, contract, register, public_keys);
141-
let scan_id = state.node.endpoints().scan()?.register(&scan).await?;
142-
let store_scan = chaincash_store::scans::Scan::new(scan_id, scan.scan_name, scan_type);
143-
state.store.scans().add(&store_scan)?;
144-
Ok(store_scan)
145-
}
146-
147144
// Load scans by type. If node changes then wrong scans will be detected and re-registered
148145
// Returns (needs_rescan, scan_type)
149146
async fn load_scan(
@@ -163,29 +160,20 @@ async fn load_scan(
163160
_ => None,
164161
})
165162
.collect::<Vec<_>>();
163+
let contract_scan = ContractScan::new(state, scan_type, &addresses).await?;
166164
let scan = state.store.scans().scan_by_type(scan_type)?;
167165
if let Some(scan) = scan {
168-
let node_scan = node_scans
169-
.iter()
170-
.find(|node_scan| scan.scan_name == node_scan.scan.scan_name);
171-
if let Some(reserve_scan) = node_scan {
172-
if let Some(scan_pubkeys) = pubkeys_from_scan(reserve_scan) {
173-
if addresses.iter().all(|wallet_pubkey| {
174-
scan_pubkeys
175-
.iter()
176-
.any(|scan_pubkey| wallet_pubkey == scan_pubkey)
177-
}) {
178-
return Ok((false, scan.scan_id));
179-
}
180-
} else {
181-
warn!(
182-
"Scan #{} ({}) invalidated, re-registering",
183-
scan.scan_id, scan.scan_name
184-
);
185-
}
166+
if node_scans.iter().any(|node_scan| {
167+
scan.scan_id as u32 == node_scan.scan_id
168+
&& scan.scan_name == node_scan.scan.scan_name
169+
&& node_scan.scan == contract_scan.scan
170+
}) {
171+
return Ok((false, scan.scan_id));
172+
} else {
173+
warn!("Scan {} invalidated, re-registering", scan.scan_id);
186174
}
187175
}
188-
let scan_id = register_scan(state, scan_type, &addresses).await?.scan_id;
176+
let scan_id = contract_scan.register(state).await?.scan_id;
189177
Ok((true, scan_id))
190178
}
191179

@@ -196,7 +184,7 @@ async fn reserve_scanner(state: Arc<ServerState>, scan_id: i32) -> Result<(), Sc
196184
.extensions()
197185
.get_all_unspent_boxes(scan_id as u32, false)
198186
.await?;
199-
for scan_box in scan_boxes {
187+
for scan_box in &scan_boxes {
200188
match ReserveBoxSpec::try_from(&scan_box.ergo_box) {
201189
Ok(reserve_box) => {
202190
state.store.reserves().add_or_update(&reserve_box)?;
@@ -207,6 +195,12 @@ async fn reserve_scanner(state: Arc<ServerState>, scan_id: i32) -> Result<(), Sc
207195
),
208196
}
209197
}
198+
state
199+
.store
200+
.reserves()
201+
.delete_not_in(scan_boxes.iter().map(|b| b.ergo_box.box_id()))?
202+
.into_iter()
203+
.for_each(|deleted| info!("Deleting box id: {deleted}"));
210204
wait_scan_block(&state).await?;
211205
}
212206
}
@@ -337,8 +331,8 @@ pub async fn start_scanner(state: Arc<ServerState>) -> Result<(), ScannerError>
337331
let (rescan, note_scan) = load_scan(&state, ScanType::Notes, &scans).await?;
338332
needs_rescan |= rescan;
339333
if needs_rescan {
340-
//Rescan from block #1,100,000. This height can be increased later when chaincash is deployed
341-
let _ = state.node.endpoints().wallet()?.rescan(1_100_000).await;
334+
//Rescan from block #1,318_639. This height can be increased later when chaincash is deployed
335+
let _ = state.node.endpoints().wallet()?.rescan(1_318_639).await;
342336
}
343337
tokio::spawn(reserve_scanner(state.clone(), reserve_scan));
344338
tokio::spawn(note_scanner(state.clone(), note_scan));

crates/chaincash_store/src/reserves.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ use crate::schema;
44
use crate::ConnectionPool;
55
use crate::Error;
66
use chaincash_offchain::boxes::ReserveBoxSpec;
7+
use diesel::dsl::delete;
78
use diesel::prelude::*;
89
use ergo_lib::ergotree_ir::chain;
10+
use ergo_lib::ergotree_ir::chain::ergo_box::BoxId;
911
use ergo_lib::ergotree_ir::chain::token::TokenId;
1012
use std::borrow::BorrowMut;
1113

@@ -96,4 +98,19 @@ impl ReserveRepository {
9698
.collect::<Result<Vec<_>, Box<dyn std::error::Error>>>()
9799
.expect("Failed to parse ReserveBoxSpec from database"))
98100
}
101+
102+
/// Delete boxes that are not in latest scan (spent)
103+
pub fn delete_not_in(&self, ids: impl Iterator<Item = BoxId>) -> Result<Vec<String>, Error> {
104+
let mut conn = self.pool.get()?;
105+
let ids = ids.map(|id| id.to_string());
106+
let spent_boxes = schema::reserves::table
107+
.inner_join(schema::ergo_boxes::table)
108+
.filter(diesel::dsl::not(schema::ergo_boxes::ergo_id.eq_any(ids)))
109+
.select(schema::ergo_boxes::id)
110+
.into_boxed();
111+
let query = delete(schema::ergo_boxes::table)
112+
.filter(schema::ergo_boxes::id.eq_any(spent_boxes))
113+
.returning(schema::ergo_boxes::ergo_id);
114+
query.load(&mut conn).map_err(Into::into)
115+
}
99116
}

0 commit comments

Comments
 (0)