feat!: LocalChain with hardwired genesis checkpoint
This ensures that `LocalChain` will always have a tip. The `ChainOracle` trait's `get_chain_tip` method no longer needs to return an option.
This commit is contained in:
@@ -25,7 +25,7 @@ pub struct Emitter<'c, C> {
|
||||
|
||||
/// The checkpoint of the last-emitted block that is in the best chain. If it is later found
|
||||
/// that the block is no longer in the best chain, it will be popped off from here.
|
||||
last_cp: Option<CheckPoint>,
|
||||
last_cp: CheckPoint,
|
||||
|
||||
/// The block result returned from rpc of the last-emitted block. As this result contains the
|
||||
/// next block's block hash (which we use to fetch the next block), we set this to `None`
|
||||
@@ -43,29 +43,12 @@ pub struct Emitter<'c, C> {
|
||||
}
|
||||
|
||||
impl<'c, C: bitcoincore_rpc::RpcApi> Emitter<'c, C> {
|
||||
/// Construct a new [`Emitter`] with the given RPC `client` and `start_height`.
|
||||
///
|
||||
/// `start_height` is the block height to start emitting blocks from.
|
||||
pub fn from_height(client: &'c C, start_height: u32) -> Self {
|
||||
/// TODO
|
||||
pub fn new(client: &'c C, last_cp: CheckPoint, start_height: u32) -> Self {
|
||||
Self {
|
||||
client,
|
||||
start_height,
|
||||
last_cp: None,
|
||||
last_block: None,
|
||||
last_mempool_time: 0,
|
||||
last_mempool_tip: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct a new [`Emitter`] with the given RPC `client` and `checkpoint`.
|
||||
///
|
||||
/// `checkpoint` is used to find the latest block which is still part of the best chain. The
|
||||
/// [`Emitter`] will emit blocks starting right above this block.
|
||||
pub fn from_checkpoint(client: &'c C, checkpoint: CheckPoint) -> Self {
|
||||
Self {
|
||||
client,
|
||||
start_height: 0,
|
||||
last_cp: Some(checkpoint),
|
||||
last_cp,
|
||||
last_block: None,
|
||||
last_mempool_time: 0,
|
||||
last_mempool_tip: None,
|
||||
@@ -134,7 +117,7 @@ impl<'c, C: bitcoincore_rpc::RpcApi> Emitter<'c, C> {
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
self.last_mempool_time = latest_time;
|
||||
self.last_mempool_tip = self.last_cp.as_ref().map(|cp| cp.height());
|
||||
self.last_mempool_tip = Some(self.last_cp.height());
|
||||
|
||||
Ok(txs_to_emit)
|
||||
}
|
||||
@@ -156,7 +139,8 @@ enum PollResponse {
|
||||
/// Fetched block is not in the best chain.
|
||||
BlockNotInBestChain,
|
||||
AgreementFound(bitcoincore_rpc_json::GetBlockResult, CheckPoint),
|
||||
AgreementPointNotFound,
|
||||
/// Force the genesis checkpoint down the receiver's throat.
|
||||
AgreementPointNotFound(BlockHash),
|
||||
}
|
||||
|
||||
fn poll_once<C>(emitter: &Emitter<C>) -> Result<PollResponse, bitcoincore_rpc::Error>
|
||||
@@ -166,45 +150,50 @@ where
|
||||
let client = emitter.client;
|
||||
|
||||
if let Some(last_res) = &emitter.last_block {
|
||||
assert!(
|
||||
emitter.last_cp.is_some(),
|
||||
"must not have block result without last cp"
|
||||
);
|
||||
|
||||
let next_hash = match last_res.nextblockhash {
|
||||
None => return Ok(PollResponse::NoMoreBlocks),
|
||||
Some(next_hash) => next_hash,
|
||||
let next_hash = if last_res.height < emitter.start_height as _ {
|
||||
// enforce start height
|
||||
let next_hash = client.get_block_hash(emitter.start_height as _)?;
|
||||
// make sure last emission is still in best chain
|
||||
if client.get_block_hash(last_res.height as _)? != last_res.hash {
|
||||
return Ok(PollResponse::BlockNotInBestChain);
|
||||
}
|
||||
next_hash
|
||||
} else {
|
||||
match last_res.nextblockhash {
|
||||
None => return Ok(PollResponse::NoMoreBlocks),
|
||||
Some(next_hash) => next_hash,
|
||||
}
|
||||
};
|
||||
|
||||
let res = client.get_block_info(&next_hash)?;
|
||||
if res.confirmations < 0 {
|
||||
return Ok(PollResponse::BlockNotInBestChain);
|
||||
}
|
||||
|
||||
return Ok(PollResponse::Block(res));
|
||||
}
|
||||
|
||||
if emitter.last_cp.is_none() {
|
||||
let hash = client.get_block_hash(emitter.start_height as _)?;
|
||||
|
||||
let res = client.get_block_info(&hash)?;
|
||||
if res.confirmations < 0 {
|
||||
return Ok(PollResponse::BlockNotInBestChain);
|
||||
}
|
||||
return Ok(PollResponse::Block(res));
|
||||
}
|
||||
|
||||
for cp in emitter.last_cp.iter().flat_map(CheckPoint::iter) {
|
||||
let res = client.get_block_info(&cp.hash())?;
|
||||
if res.confirmations < 0 {
|
||||
// block is not in best chain
|
||||
continue;
|
||||
}
|
||||
for cp in emitter.last_cp.iter() {
|
||||
let res = match client.get_block_info(&cp.hash()) {
|
||||
// block not in best chain
|
||||
Ok(res) if res.confirmations < 0 => continue,
|
||||
Ok(res) => res,
|
||||
Err(e) if e.is_not_found_error() => {
|
||||
if cp.height() > 0 {
|
||||
continue;
|
||||
}
|
||||
// if we can't find genesis block, we can't create an update that connects
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
// agreement point found
|
||||
return Ok(PollResponse::AgreementFound(res, cp));
|
||||
}
|
||||
|
||||
Ok(PollResponse::AgreementPointNotFound)
|
||||
let genesis_hash = client.get_block_hash(0)?;
|
||||
Ok(PollResponse::AgreementPointNotFound(genesis_hash))
|
||||
}
|
||||
|
||||
fn poll<C, V, F>(
|
||||
@@ -222,25 +211,12 @@ where
|
||||
let hash = res.hash;
|
||||
let item = get_item(&hash)?;
|
||||
|
||||
let this_id = BlockId { height, hash };
|
||||
let prev_id = res.previousblockhash.map(|prev_hash| BlockId {
|
||||
height: height - 1,
|
||||
hash: prev_hash,
|
||||
});
|
||||
|
||||
match (&mut emitter.last_cp, prev_id) {
|
||||
(Some(cp), _) => *cp = cp.clone().push(this_id).expect("must push"),
|
||||
(last_cp, None) => *last_cp = Some(CheckPoint::new(this_id)),
|
||||
// When the receiver constructs a local_chain update from a block, the previous
|
||||
// checkpoint is also included in the update. We need to reflect this state in
|
||||
// `Emitter::last_cp` as well.
|
||||
(last_cp, Some(prev_id)) => {
|
||||
*last_cp = Some(CheckPoint::new(prev_id).push(this_id).expect("must push"))
|
||||
}
|
||||
}
|
||||
|
||||
emitter.last_cp = emitter
|
||||
.last_cp
|
||||
.clone()
|
||||
.push(BlockId { height, hash })
|
||||
.expect("must push");
|
||||
emitter.last_block = Some(res);
|
||||
|
||||
return Ok(Some((height, item)));
|
||||
}
|
||||
PollResponse::NoMoreBlocks => {
|
||||
@@ -254,9 +230,6 @@ where
|
||||
PollResponse::AgreementFound(res, cp) => {
|
||||
let agreement_h = res.height as u32;
|
||||
|
||||
// get rid of evicted blocks
|
||||
emitter.last_cp = Some(cp);
|
||||
|
||||
// The tip during the last mempool emission needs to in the best chain, we reduce
|
||||
// it if it is not.
|
||||
if let Some(h) = emitter.last_mempool_tip.as_mut() {
|
||||
@@ -264,15 +237,17 @@ where
|
||||
*h = agreement_h;
|
||||
}
|
||||
}
|
||||
|
||||
// get rid of evicted blocks
|
||||
emitter.last_cp = cp;
|
||||
emitter.last_block = Some(res);
|
||||
continue;
|
||||
}
|
||||
PollResponse::AgreementPointNotFound => {
|
||||
// We want to clear `last_cp` and set `start_height` to the first checkpoint's
|
||||
// height. This way, the first checkpoint in `LocalChain` can be replaced.
|
||||
if let Some(last_cp) = emitter.last_cp.take() {
|
||||
emitter.start_height = last_cp.height();
|
||||
}
|
||||
PollResponse::AgreementPointNotFound(genesis_hash) => {
|
||||
emitter.last_cp = CheckPoint::new(BlockId {
|
||||
height: 0,
|
||||
hash: genesis_hash,
|
||||
});
|
||||
emitter.last_block = None;
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user