Implement calculation of monotonic network times for Bitcoin and Monero

This commit is contained in:
Luke Parker
2023-11-09 05:17:34 -05:00
parent 43ae6794db
commit 19187d2c30
5 changed files with 93 additions and 15 deletions

View File

@@ -256,6 +256,7 @@ impl SignableTransactionTrait for SignableTransaction {
}
}
#[async_trait]
impl BlockTrait<Bitcoin> for Block {
type Id = [u8; 32];
fn id(&self) -> Self::Id {
@@ -270,10 +271,31 @@ impl BlockTrait<Bitcoin> for Block {
hash
}
// TODO: Don't use this block's time, use the network time at this block
// TODO: Confirm network time is monotonic, enabling its usage here
fn time(&self) -> u64 {
self.header.time.into()
async fn time(&self, rpc: &Bitcoin) -> u64 {
// Use the network median time defined in BIP-0113 since the in-block time isn't guaranteed to
// be monotonic
let mut timestamps = vec![u64::from(self.header.time)];
let mut parent = self.parent();
// BIP-0113 uses a median of the prior 11 blocks
while timestamps.len() < 11 {
let mut parent_block;
while {
parent_block = rpc.rpc.get_block(&parent).await;
parent_block.is_err()
} {
log::error!("couldn't get parent block when trying to get block time: {parent_block:?}");
sleep(Duration::from_secs(5)).await;
}
let parent_block = parent_block.unwrap();
timestamps.push(u64::from(parent_block.header.time));
parent = parent_block.parent();
if parent == [0; 32] {
break;
}
}
timestamps.sort();
timestamps[timestamps.len() / 2]
}
}
@@ -325,7 +347,7 @@ impl Bitcoin {
let mut res = Rpc::new(url.clone()).await;
while let Err(e) = res {
log::error!("couldn't connect to Bitcoin node: {e:?}");
tokio::time::sleep(Duration::from_secs(5)).await;
sleep(Duration::from_secs(5)).await;
res = Rpc::new(url.clone()).await;
}
Bitcoin { rpc: res.unwrap() }

View File

@@ -181,13 +181,16 @@ impl<E: Eventuality> Default for EventualitiesTracker<E> {
}
}
#[async_trait]
pub trait Block<N: Network>: Send + Sync + Sized + Clone + Debug {
// This is currently bounded to being 32 bytes.
type Id: 'static + Id;
fn id(&self) -> Self::Id;
fn parent(&self) -> Self::Id;
// The monotonic network time at this block.
fn time(&self) -> u64;
/// The monotonic network time at this block.
///
/// This call is presumed to be expensive and should only be called sparingly.
async fn time(&self, rpc: &N) -> u64;
}
// The post-fee value of an expected branch.

View File

@@ -156,6 +156,7 @@ impl SignableTransactionTrait for SignableTransaction {
}
}
#[async_trait]
impl BlockTrait<Monero> for Block {
type Id = [u8; 32];
fn id(&self) -> Self::Id {
@@ -166,9 +167,48 @@ impl BlockTrait<Monero> for Block {
self.header.previous
}
// TODO: Check Monero enforces this to be monotonic and sane
fn time(&self) -> u64 {
self.header.timestamp
async fn time(&self, rpc: &Monero) -> u64 {
// Constant from Monero
const BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW: u64 = 60;
// If Monero doesn't have enough blocks to build a window, it doesn't define a network time
if (u64::try_from(self.number()).unwrap() + 1) < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW {
// Use the block number as the time
return self.number().try_into().unwrap();
}
let mut timestamps = vec![self.header.timestamp];
let mut parent = self.parent();
while u64::try_from(timestamps.len()).unwrap() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW {
let mut parent_block;
while {
parent_block = rpc.rpc.get_block(parent).await;
parent_block.is_err()
} {
log::error!("couldn't get parent block when trying to get block time: {parent_block:?}");
sleep(Duration::from_secs(5)).await;
}
let parent_block = parent_block.unwrap();
timestamps.push(parent_block.header.timestamp);
parent = parent_block.parent();
if parent_block.number() == 0 {
break;
}
}
timestamps.sort();
// Because 60 has two medians, Monero's epee picks the in-between value, calculated by the
// following formula (from the "get_mid" function)
let n = timestamps.len() / 2;
let a = timestamps[n - 1];
let b = timestamps[n];
#[rustfmt::skip] // Enables Ctrl+F'ing for everything after the `= `
let res = (a/2) + (b/2) + ((a - 2*(a/2)) + (b - 2*(b/2)))/2;
// Techniaslly, res may be 1 if all prior blocks had a timestamp by 0, which would break
// monotonicity with our above definition of height as time
// Ensure monotonicity by increasing this value by the window size
res + BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW
}
}