Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checkpoint backfill #272

Merged
merged 13 commits into from
Feb 7, 2024
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
Loading