Skip to content

Commit

Permalink
Merge pull request #272 from nomic-io/checkpoint-backfill
Browse files Browse the repository at this point in the history
Checkpoint backfill
  • Loading branch information
mappum authored Feb 7, 2024
2 parents c83a9e9 + 93f44ce commit 49f959a
Show file tree
Hide file tree
Showing 5 changed files with 568 additions and 4 deletions.
80 changes: 80 additions & 0 deletions src/bin/get-reserve-scripts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use bitcoincore_rpc_async::{Auth, Client, RpcApi};
use clap::Parser;

#[derive(Parser, Debug)]
pub struct Opts {
#[clap(default_value_t = 30)]
lookback_days: u64,

#[clap(short = 'p', long, default_value_t = 8332)]
rpc_port: u16,

#[clap(short = 'u', long)]
rpc_user: Option<String>,

#[clap(short = 'P', long)]
rpc_pass: Option<String>,
}

#[tokio::main]
pub async fn main() {
let opts = Opts::parse();

let rpc_url = format!("http://localhost:{}", opts.rpc_port);
let auth = match (opts.rpc_user, opts.rpc_pass) {
(Some(user), Some(pass)) => Auth::UserPass(user, pass),
_ => Auth::None,
};
let btc_client = Client::new(rpc_url, auth).await.unwrap();

let nomic_client = nomic::app_client("http://localhost:26657");

let (last_conf_index, last_conf_cp) = nomic_client
.query(|app| {
let conf_index = app.bitcoin.checkpoints.confirmed_index.unwrap();
let conf_cp = app.bitcoin.checkpoints.get(conf_index)?;
Ok((conf_index, conf_cp.checkpoint_tx()?.txid()))
})
.await
.unwrap();

let mut index = last_conf_index;
let mut prev_txid = last_conf_cp;
let mut block_hash = btc_client.get_best_block_hash().await.unwrap();
let mut scripts = vec![];

let target_time = now() - 60 * 60 * 24 * opts.lookback_days;

loop {
let block = btc_client.get_block_info(&block_hash).await.unwrap();
let has_tx = block.tx.iter().any(|txid| *txid == prev_txid);
if !has_tx {
block_hash = block.previousblockhash.unwrap();
continue;
}

let tx = btc_client
.get_raw_transaction(&prev_txid, Some(&block_hash))
.await
.unwrap();
prev_txid = tx.input[0].previous_output.txid;
index -= 1;

scripts.push((index, tx.input[0].witness.last().unwrap().to_vec()));

if (block.time as u64) < target_time {
break;
}
}

for (index, script) in scripts.iter() {
println!("{},{}", index, hex::encode(script));
}
}

fn now() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs()
}
154 changes: 151 additions & 3 deletions src/bitcoin/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ use crate::{
app::Dest,
bitcoin::{signatory::derive_pubkey, Nbtc},
};
use bitcoin::hashes::Hash;
use bitcoin::{blockdata::transaction::EcdsaSighashType, Sequence, Transaction, TxIn, TxOut};
use bitcoin::{hashes::Hash, Script};
use derive_more::{Deref, DerefMut};
use log::info;
use orga::coins::{Accounts, Coin};
Expand Down Expand Up @@ -1701,7 +1701,7 @@ impl CheckpointQueue {
Ok(())
}

/// Gets a refernce to the checkpoint at the given index.
/// Gets a reference to the checkpoint at the given index.
///
/// If the index is out of bounds or was pruned, an error is returned.
#[query]
Expand Down Expand Up @@ -1822,6 +1822,11 @@ impl CheckpointQueue {
.ok_or_else(|| Error::Orga(OrgaError::App("No completed checkpoints yet".to_string())))
}

#[query]
pub fn first_index(&self) -> Result<u32> {
Ok(self.index + 1 - self.len()?)
}

/// A reference to the last completed checkpoint.
#[query]
pub fn last_completed(&self) -> Result<Ref<Checkpoint>> {
Expand Down Expand Up @@ -2362,6 +2367,34 @@ impl CheckpointQueue {
let unconf_vbytes = self.unconfirmed_vbytes(config)?;
Ok((unconf_vbytes * fee_rate).saturating_sub(unconf_fees_paid))
}

pub fn backfill(
&mut self,
first_index: u32,
redeem_scripts: impl Iterator<Item = Script>,
) -> Result<()> {
let mut index = first_index + 1;

let create_time = self.queue.get(0)?.unwrap().create_time();

for script in redeem_scripts {
index -= 1;

if index >= self.first_index()? {
continue;
}

let (mut sigset, _) = SignatorySet::from_script(&script)?;
sigset.index = index;
sigset.create_time = create_time;
let mut cp = Checkpoint::new(sigset)?;
cp.status = CheckpointStatus::Complete;

self.queue.push_front(cp)?;
}

Ok(())
}
}

/// Takes a previous fee rate and returns a new fee rate, adjusted up or down by
Expand All @@ -2379,6 +2412,7 @@ pub fn adjust_fee_rate(prev_fee_rate: u64, up: bool, config: &Config) -> u64 {

#[cfg(test)]
mod test {
use crate::bitcoin::threshold_sig::Pubkey;
#[cfg(feature = "full")]
use crate::utils::set_time;

Expand All @@ -2390,7 +2424,11 @@ mod test {
util::bip32::{ChildNumber, ExtendedPrivKey, ExtendedPubKey},
OutPoint, PubkeyHash, Script, Txid,
};
use orga::{collections::EntryMap, context::Context};
use orga::{
collections::EntryMap,
context::Context,
secp256k1::{PublicKey, SecretKey},
};
#[cfg(all(feature = "full"))]
use rand::Rng;

Expand Down Expand Up @@ -2944,4 +2982,114 @@ mod test {

assert_eq!(queue.borrow().len().unwrap(), 5);
}

fn sigset(n: u32) -> SignatorySet {
let mut sigset = SignatorySet::default();
sigset.index = n;
sigset.create_time = n as u64;

let secret = bitcoin::secp256k1::SecretKey::from_slice(&[(n + 1) as u8; 32]).unwrap();
let pubkey: Pubkey = bitcoin::secp256k1::PublicKey::from_secret_key(
&bitcoin::secp256k1::Secp256k1::new(),
&secret,
)
.into();

sigset.signatories.push(Signatory {
pubkey: pubkey.into(),
voting_power: 100,
});

sigset.possible_vp = 100;
sigset.present_vp = 100;

sigset
}

#[test]
fn backfill_basic() {
let mut queue = CheckpointQueue::default();
queue.index = 10;
queue
.queue
.push_back(Checkpoint::new(sigset(7)).unwrap())
.unwrap();
queue
.queue
.push_back(Checkpoint::new(sigset(8)).unwrap())
.unwrap();
queue
.queue
.push_back(Checkpoint::new(sigset(9)).unwrap())
.unwrap();
queue
.queue
.push_back(Checkpoint::new(sigset(10)).unwrap())
.unwrap();

let backfill_data = vec![
sigset(8).redeem_script(&[0], (2, 3)).unwrap(),
sigset(7).redeem_script(&[0], (2, 3)).unwrap(),
sigset(6).redeem_script(&[0], (2, 3)).unwrap(),
sigset(5).redeem_script(&[0], (2, 3)).unwrap(),
sigset(4).redeem_script(&[0], (2, 3)).unwrap(),
sigset(3).redeem_script(&[0], (2, 3)).unwrap(),
];
queue.backfill(8, backfill_data.into_iter()).unwrap();

assert_eq!(queue.len().unwrap(), 8);
assert_eq!(queue.index, 10);
assert_eq!(
queue
.get(3)
.unwrap()
.sigset
.redeem_script(&[0], (2, 3))
.unwrap(),
sigset(3).redeem_script(&[0], (2, 3)).unwrap(),
);
assert_eq!(
queue
.get(10)
.unwrap()
.sigset
.redeem_script(&[0], (2, 3))
.unwrap(),
sigset(10).redeem_script(&[0], (2, 3)).unwrap(),
);
}

#[test]
fn backfill_with_zeroth() {
let mut queue = CheckpointQueue::default();
queue.index = 1;
queue
.queue
.push_back(Checkpoint::new(sigset(1)).unwrap())
.unwrap();

let backfill_data = vec![sigset(0).redeem_script(&[0], (2, 3)).unwrap()];
queue.backfill(0, backfill_data.into_iter()).unwrap();

assert_eq!(queue.len().unwrap(), 2);
assert_eq!(queue.index, 1);
assert_eq!(
queue
.get(0)
.unwrap()
.sigset
.redeem_script(&[0], (2, 3))
.unwrap(),
sigset(0).redeem_script(&[0], (2, 3)).unwrap(),
);
assert_eq!(
queue
.get(1)
.unwrap()
.sigset
.redeem_script(&[0], (2, 3))
.unwrap(),
sigset(1).redeem_script(&[0], (2, 3)).unwrap(),
);
}
}
4 changes: 4 additions & 0 deletions src/bitcoin/relayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,10 @@ impl Relayer {
if relayed.contains(&tx.txid()) {
continue;
}
// skip checkpoints that came from backfill
if tx.input.is_empty() {
continue;
}

let mut tx_bytes = vec![];
tx.consensus_encode(&mut tx_bytes)?;
Expand Down
Loading

0 comments on commit 49f959a

Please sign in to comment.