Skip to content

Commit ce52bb9

Browse files
randomloginfebyeji
andcommitted
Implement process_kyoto_events and ChainOp
Added 4 variants of `ChainOp`: - ConnectFull, - ConnectFiltered, - Disconnect, - Synced Now `process_kyoto_events` reacts to an event from kyoto and sends a `ChainOp` to listener (`BlockApplicator`). Note that on a filter with no match we still need to apply header and we need double check that header in the canonical chain is the relevant one and has not been reorged. Right now it is left as a todo, because of an upstream PR. Co-authored-by: febyeji <yeji.han@sf.snu.ac.kr>
1 parent c4e05f8 commit ce52bb9

1 file changed

Lines changed: 100 additions & 28 deletions

File tree

src/chain/cbf.rs

Lines changed: 100 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use bip157::{
99
HashCheckpoint, Header, IndexedBlock, Info, Node as KyotoNode, Requester, TrustedPeer, Warning,
1010
};
1111
use bitcoin::{BlockHash, FeeRate, Script, ScriptBuf, Txid};
12-
use lightning::chain::{Listen, WatchedOutput};
12+
use lightning::chain::{BlockLocator, Listen, WatchedOutput};
1313

1414
use tokio::sync::{mpsc, oneshot};
1515

@@ -61,9 +61,20 @@ pub struct CbfChainSource {
6161
}
6262

6363
enum ChainOp {
64-
ConnectFull { block_rx: oneshot::Receiver<Result<IndexedBlock, FetchBlockError>> },
65-
ConnectFiltered { header: Header, height: u32 },
66-
//Reorg { /* accepted / reorganized from BlockHeaderChanges */ },
64+
ConnectFull {
65+
block_rx: oneshot::Receiver<Result<IndexedBlock, FetchBlockError>>,
66+
},
67+
ConnectFiltered {
68+
header: Header,
69+
height: u32,
70+
},
71+
Disconnect {
72+
fork_point: BlockLocator,
73+
},
74+
/// Marks reaching the chain tip.
75+
Synced {
76+
tip_height: u32,
77+
},
6778
}
6879

6980
struct BlockApplicator {
@@ -82,9 +93,16 @@ impl BlockApplicator {
8293
Err(_) => log_error!(self.logger, "block oneshot dropped"),
8394
},
8495
ChainOp::ConnectFiltered { header, height } => {
85-
self.chain_listener.filtered_block_connected(&header, &[], height)
96+
self.chain_listener.filtered_block_connected(&header, &[], height);
97+
},
98+
ChainOp::Disconnect { fork_point } => {
99+
self.chain_listener.blocks_disconnected(fork_point);
100+
},
101+
ChainOp::Synced { tip_height } => {
102+
log_info!(self.logger, "CBF caught up to tip {}", tip_height);
103+
// TODO: notify sync-completion waiters (start()/sync_wallets()/tests) once
104+
// a notification primitive is plumbed through.
86105
},
87-
//ChainOp::Reorg { .. } => {},
88106
}
89107
}
90108
}
@@ -239,6 +257,7 @@ impl CbfChainSource {
239257
));
240258

241259
let event_handle = tokio::spawn(Self::process_kyoto_events(
260+
Arc::clone(&restart_logger),
242261
current_event_rx,
243262
Arc::clone(&restart_registered_scripts),
244263
Arc::clone(&restart_cbf_runtime_status),
@@ -351,45 +370,98 @@ impl CbfChainSource {
351370
}
352371

353372
async fn process_kyoto_events(
354-
mut event_rx: mpsc::UnboundedReceiver<Event>,
373+
logger: Arc<Logger>, mut event_rx: mpsc::UnboundedReceiver<Event>,
355374
registered_scripts: Arc<Mutex<HashSet<ScriptBuf>>>,
356375
cbf_runtime_status: Arc<Mutex<CbfRuntimeStatus>>, ops_tx: mpsc::UnboundedSender<ChainOp>,
357376
) {
358377
while let Some(event) = event_rx.recv().await {
359378
match event {
360-
// match download
361379
Event::IndexedFilter(indexed_filter) => {
380+
let requester = match &*cbf_runtime_status.lock().expect("lock") {
381+
CbfRuntimeStatus::Started { requester } => requester.clone(),
382+
CbfRuntimeStatus::Stopped => {
383+
//TODO should we panic here? what do we do if we have no requester?
384+
continue;
385+
},
386+
};
387+
let block_hash = indexed_filter.block_hash();
362388
let matched = indexed_filter
363389
.contains_any(registered_scripts.lock().expect("lock").iter());
364-
if matched {
365-
let rtm = &*cbf_runtime_status.lock().expect("lock");
366-
let requestor = match rtm {
367-
CbfRuntimeStatus::Started { requester } => requester.clone(),
368-
CbfRuntimeStatus::Stopped => {
369-
//panic
370-
// todo!();
390+
391+
let chop: ChainOp = if matched {
392+
let block_rx =
393+
requester.request_block(block_hash).expect("cannot request block");
394+
ChainOp::ConnectFull { block_rx }
395+
} else {
396+
let height = indexed_filter.height();
397+
//TODO we need to recheck that a particular height has not been
398+
//reorganized, and we retrieve indeed the same block header that we
399+
//received `IndexedFilter` event of. right now this would block
400+
//the further sync, as we cannot apply blocks in order.
401+
//Future solution would use something like `get_header_by_hash`.
402+
match requester.get_header(height).await {
403+
Ok(Some(indexed_header)) => {
404+
if indexed_header.block_hash() != block_hash {
405+
log_debug!(
406+
logger,
407+
"Filter for {} reorged; skipping",
408+
block_hash
409+
);
410+
continue;
411+
}
412+
ChainOp::ConnectFiltered {
413+
header: indexed_header.header,
414+
height: indexed_header.height,
415+
}
416+
},
417+
Ok(None) => {
418+
log_error!(logger, "No header at height {}", height,);
371419
continue;
372420
},
373-
};
374-
let block_rx = requestor
375-
.request_block(indexed_filter.block_hash())
376-
.expect("cannot request block");
377-
let chop = ChainOp::ConnectFull { block_rx };
378-
//here we feed evets to the driver
379-
ops_tx.send(chop);
421+
Err(e) => {
422+
log_error!(
423+
logger,
424+
"Failed to fetch header at height {}: {:?}",
425+
height,
426+
e,
427+
);
428+
continue;
429+
},
430+
}
431+
};
432+
if let Err(e) = ops_tx.send(chop) {
433+
log_debug!(logger, "ops_rx gone: {}", e);
380434
}
381435
},
382436
Event::FiltersSynced(sync_update) => {
383-
todo!();
437+
//Because application of blocks is async, the fact that kyoto synced up to the
438+
//tip does NOT mean that we caught everything up, that's why we send a ChainOp,
439+
//only processing of which means we processed all blocks up to the tip.
440+
log_info!(logger, "Kyoto synced up to the tip {}", sync_update.tip().height);
441+
let _ = ops_tx.send(ChainOp::Synced { tip_height: sync_update.tip().height });
384442
},
385-
Event::ChainUpdate(BlockHeaderChanges::Connected(connected_blocks)) => {
386-
todo!();
443+
Event::ChainUpdate(BlockHeaderChanges::Connected(indexed_header)) => {
444+
log_debug!(
445+
logger,
446+
"Kyoto connected header at height {}",
447+
indexed_header.height
448+
);
387449
},
388-
Event::ChainUpdate(BlockHeaderChanges::Reorganized { reorganized, accepted }) => {
389-
todo!();
450+
Event::ChainUpdate(BlockHeaderChanges::Reorganized {
451+
reorganized,
452+
accepted: _,
453+
}) => {
454+
// Rewind to the fork point; kyoto will re-deliver the new chain's filters.
455+
if let Some(lowest) = reorganized.first() {
456+
let fork_point = BlockLocator::new(
457+
lowest.prev_blockhash(),
458+
lowest.height.saturating_sub(1),
459+
);
460+
let _ = ops_tx.send(ChainOp::Disconnect { fork_point });
461+
}
390462
},
391463
Event::ChainUpdate(BlockHeaderChanges::ForkAdded(fork)) => {
392-
todo!();
464+
log_debug!(logger, "Kyoto added fork header at height {}", fork.height);
393465
},
394466
}
395467
}

0 commit comments

Comments
 (0)