Skip to content

Commit

Permalink
Merge pull request #193 from nomic-io/offline-signers
Browse files Browse the repository at this point in the history
Offline signer handling
  • Loading branch information
mappum authored Sep 1, 2023
2 parents 5184121 + 4d0e5ec commit 3a17d8d
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 18 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 1 addition & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ default-run = "nomic"
[dependencies]
bitcoin = { version = "0.29.2", features = ["serde", "rand"] }
bitcoind = { version = "0.27.0", features = ["22_0"], optional = true }
orga = { git = "https://github.com/nomic-io/orga.git", rev = "860052c382b01fa5d5c4b4d954da4aa129adacb9", features = [
"merk-verify",
"compat",
] }
orga = { git = "https://github.com/nomic-io/orga.git", rev = "a48d8df18d230c7e69b8b73631d04330f91e7e2b", features = ["merk-verify"] }
thiserror = "1.0.30"
ed = { git = "https://github.com/nomic-io/ed", rev = "9c0e206ffdb59dacb90f083e004e8080713e6ad8" }
clap = { version = "3.2.16", features = ["derive"], optional = true }
Expand Down
7 changes: 6 additions & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,13 @@ mod abci {
self.incentive_pool.give(ip_reward)?;

let external_outputs: Vec<crate::error::Result<bitcoin::TxOut>> = vec![]; // TODO: remote chain disbursal
self.bitcoin
let offline_signers = self
.bitcoin
.begin_block_step(external_outputs.into_iter())?;
for cons_key in offline_signers {
let address = self.staking.address_by_consensus_key(cons_key)?.unwrap();
self.staking.punish_downtime(address)?;
}

let has_nbtc_rewards = self.bitcoin.reward_pool.amount > 0;
if self.reward_timer.tick(now) && has_stake && has_nbtc_rewards {
Expand Down
12 changes: 6 additions & 6 deletions src/bitcoin/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1190,9 +1190,9 @@ impl CheckpointQueue {
nbtc_accounts: &Accounts<Nbtc>,
recovery_scripts: &Map<orga::coins::Address, Adapter<bitcoin::Script>>,
external_outputs: impl Iterator<Item = Result<bitcoin::TxOut>>,
) -> Result<()> {
) -> Result<bool> {
if self.signing()?.is_some() {
return Ok(());
return Ok(false);
}

if !self.queue.is_empty() {
Expand All @@ -1202,7 +1202,7 @@ impl CheckpointQueue {
.seconds as u64;
let elapsed = now - self.building()?.create_time();
if elapsed < self.config.min_checkpoint_interval {
return Ok(());
return Ok(false);
}

if elapsed < self.config.max_checkpoint_interval || self.index == 0 {
Expand All @@ -1218,13 +1218,13 @@ impl CheckpointQueue {
let has_pending_withdrawal = !checkpoint_tx.output.is_empty();

if !has_pending_deposit && !has_pending_withdrawal {
return Ok(());
return Ok(false);
}
}
}

if self.maybe_push(sig_keys)?.is_none() {
return Ok(());
return Ok(false);
}

#[cfg(feature = "testnet")]
Expand Down Expand Up @@ -1271,7 +1271,7 @@ impl CheckpointQueue {
}
}

Ok(())
Ok(true)
}

#[cfg(feature = "full")]
Expand Down
84 changes: 79 additions & 5 deletions src/bitcoin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ use orga::describe::Describe;
use orga::encoding::{Decode, Encode, Terminated};
use orga::migrate::{Migrate, MigrateFrom};
use orga::orga;
use orga::plugins::Paid;
#[cfg(feature = "full")]
use orga::plugins::Validators;
use orga::plugins::{Paid, ValidatorEntry};
use orga::plugins::{Signer, Time};
use orga::prelude::FieldCall;
use orga::query::FieldQuery;
Expand Down Expand Up @@ -58,7 +58,7 @@ pub const NETWORK: ::bitcoin::Network = ::bitcoin::Network::Testnet;
#[cfg(all(feature = "devnet", feature = "testnet"))]
pub const NETWORK: ::bitcoin::Network = ::bitcoin::Network::Regtest;

#[orga(skip(Default))]
#[orga(skip(Default), version = 1)]
pub struct Config {
min_withdrawal_checkpoints: u32,
min_deposit_amount: u64,
Expand All @@ -71,6 +71,27 @@ pub struct Config {
emergency_disbursal_min_tx_amt: u64,
emergency_disbursal_lock_time_interval: u32,
emergency_disbursal_max_tx_size: u64,
#[orga(version(V1))]
max_offline_checkpoints: u32,
}

impl MigrateFrom<ConfigV0> for ConfigV1 {
fn migrate_from(value: ConfigV0) -> OrgaResult<Self> {
Ok(Self {
min_withdrawal_checkpoints: value.min_withdrawal_checkpoints,
min_deposit_amount: value.min_deposit_amount,
min_withdrawal_amount: value.min_withdrawal_amount,
max_withdrawal_amount: value.max_withdrawal_amount,
max_withdrawal_script_length: value.max_withdrawal_script_length,
transfer_fee: value.transfer_fee,
min_confirmations: value.min_confirmations,
units_per_sat: value.units_per_sat,
emergency_disbursal_min_tx_amt: value.emergency_disbursal_min_tx_amt,
emergency_disbursal_lock_time_interval: value.emergency_disbursal_lock_time_interval,
emergency_disbursal_max_tx_size: value.emergency_disbursal_max_tx_size,
..Default::default()
})
}
}

impl Config {
Expand All @@ -87,6 +108,7 @@ impl Config {
emergency_disbursal_min_tx_amt: 1000,
emergency_disbursal_lock_time_interval: 60 * 60 * 24 * 7, //one week
emergency_disbursal_max_tx_size: 50_000,
max_offline_checkpoints: 20,
}
}

Expand Down Expand Up @@ -600,8 +622,9 @@ impl Bitcoin {
pub fn begin_block_step(
&mut self,
external_outputs: impl Iterator<Item = Result<bitcoin::TxOut>>,
) -> Result<()> {
self.checkpoints
) -> Result<Vec<ConsensusKey>> {
let pushed = self
.checkpoints
.maybe_step(
self.signatory_keys.map(),
&self.accounts,
Expand All @@ -610,7 +633,58 @@ impl Bitcoin {
)
.map_err(|err| OrgaError::App(err.to_string()))?;

Ok(())
if pushed {
self.offline_signers()
} else {
Ok(vec![])
}
}

#[cfg(feature = "full")]
fn offline_signers(&mut self) -> Result<Vec<ConsensusKey>> {
let mut validators = self
.context::<Validators>()
.ok_or_else(|| OrgaError::App("No validator context found".to_string()))?
.entries()?;
validators.sort_by(|a, b| b.power.cmp(&a.power));

let offline_threshold = self.config.max_offline_checkpoints;
let sigset = self.checkpoints.active_sigset()?;
let lowest_power = sigset.signatories.last().unwrap().voting_power;
let completed = self.checkpoints.completed(offline_threshold)?;
if completed.len() < offline_threshold as usize {
return Ok(vec![]);
}
let mut offline_signers = vec![];
for ValidatorEntry {
power,
pubkey: cons_key,
} in validators
{
if power < lowest_power {
break;
}

let xpub = if let Some(xpub) = self.signatory_keys.get(cons_key)? {
xpub
} else {
continue;
};

let mut offline = true;
for checkpoint in completed.iter().rev() {
if checkpoint.to_sign(xpub.clone())?.is_empty() {
offline = false;
break;
}
}

if offline {
offline_signers.push(cons_key);
}
}

Ok(offline_signers)
}
}

Expand Down

0 comments on commit 3a17d8d

Please sign in to comment.