Skip to content

Commit

Permalink
Merge pull request #274 from nomic-io/fail-on-expired-sigset
Browse files Browse the repository at this point in the history
Fail to generate deposit addresses for soon to expire sigsets
  • Loading branch information
mappum authored Feb 12, 2024
2 parents a84f9a6 + a69770e commit 5bbc01a
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/bin/nomic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1166,7 +1166,7 @@ impl RelayerCmd {
}

let relayer = create_relayer().await;
let deposits = relayer.start_deposit_relay(relayer_dir_path.clone());
let deposits = relayer.start_deposit_relay(relayer_dir_path.clone(), 60 * 60 * 12);

let mut relayer = create_relayer().await;
let recovery_txs = relayer.start_recovery_tx_relay(relayer_dir_path);
Expand Down
7 changes: 2 additions & 5 deletions src/bitcoin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ impl MigrateFrom<ConfigV2> for ConfigV3 {
units_per_sat: value.units_per_sat,
max_offline_checkpoints: value.max_offline_checkpoints,
min_checkpoint_confirmations: 0,
capacity_limit: ConfigV4::default().capacity_limit,
capacity_limit: Config::default().capacity_limit,
})
}
}
Expand All @@ -199,7 +199,7 @@ impl MigrateFrom<ConfigV3> for ConfigV4 {
max_offline_checkpoints: value.max_offline_checkpoints,
min_checkpoint_confirmations: value.min_checkpoint_confirmations,
capacity_limit: value.capacity_limit,
max_deposit_age: Self::default().max_deposit_age,
max_deposit_age: Config::default().max_deposit_age,
fee_pool_target_balance: Config::default().fee_pool_target_balance,
fee_pool_reward_split: Config::default().fee_pool_reward_split,
})
Expand All @@ -226,9 +226,6 @@ impl Config {
capacity_limit: 100 * 100_000_000, // 100 BTC
#[cfg(not(feature = "testnet"))]
capacity_limit: 21 * 100_000_000, // 21 BTC
#[cfg(feature = "testnet")]
max_deposit_age: 3 * 60,
#[cfg(not(feature = "testnet"))]
max_deposit_age: 60 * 60 * 24 * 5,
fee_pool_target_balance: 10_000_000, // 0.1 BTC
fee_pool_reward_split: (1, 10),
Expand Down
39 changes: 31 additions & 8 deletions src/bitcoin/relayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub struct Relayer {
app_client_addr: String,

scripts: Option<WatchedScriptStore>,
deposit_buffer: Option<u64>,
}

impl Relayer {
Expand All @@ -54,6 +55,7 @@ impl Relayer {
btc_client: Arc::new(RwLock::new(btc_client)),
app_client_addr,
scripts: None,
deposit_buffer: None,
}
}

Expand Down Expand Up @@ -111,14 +113,20 @@ impl Relayer {
}
}

pub async fn start_deposit_relay<P: AsRef<Path>>(mut self, store_path: P) -> Result<()> {
pub async fn start_deposit_relay<P: AsRef<Path>>(
mut self,
store_path: P,
deposit_buffer: u64,
) -> Result<()> {
info!("Starting deposit relay...");

let scripts = WatchedScriptStore::open(store_path, &self.app_client_addr).await?;
self.scripts = Some(scripts);

self.deposit_buffer = Some(deposit_buffer);

let index = Arc::new(Mutex::new(DepositIndex::new()));
let (server, mut recv) = self.create_address_server(index.clone());
let (server, mut recv) = self.create_address_server(index.clone())?;

let deposit_relay = async {
loop {
Expand All @@ -137,7 +145,7 @@ impl Relayer {
fn create_address_server(
&self,
index: Arc<Mutex<DepositIndex>>,
) -> (impl Future<Output = ()>, Receiver<(Dest, u32)>) {
) -> Result<(impl Future<Output = ()>, Receiver<(Dest, u32)>)> {
let (send, recv) = tokio::sync::mpsc::channel(1024);

let sigsets = Arc::new(Mutex::new(BTreeMap::new()));
Expand All @@ -146,6 +154,10 @@ impl Relayer {
let app_client_addr: &'static str = self.app_client_addr.clone().leak();

let btc_client = self.btc_client.clone();
let deposit_buffer = match self.deposit_buffer {
Some(deposit_buffer) => deposit_buffer,
None => return Err(Error::Relayer("Deposit buffer not set".to_string())),
};

// TODO: configurable listen address
use bytes::Bytes;
Expand Down Expand Up @@ -203,18 +215,29 @@ impl Relayer {
return Err(warp::reject::custom(Error::InvalidDepositAddress));
}

Ok::<_, warp::Rejection>((dest, query.sigset_index, send))
Ok::<_, warp::Rejection>((dest, sigset.create_time, query.sigset_index, send))
},
)
.then(
async move |(dest, sigset_index, send): (
.and_then(
async move |(dest, create_time, sigset_index, send): (
Dest,
u64,
u32,
tokio::sync::mpsc::Sender<_>,
)| {
debug!("Received deposit commitment: {:?}, {}", dest, sigset_index);
send.send((dest, sigset_index)).await.unwrap();
"OK"
let max_deposit_age = app_client(app_client_addr)
.query(|app| Ok(app.bitcoin.config.max_deposit_age))
.await
.map_err(|e| warp::reject::custom(Error::from(e)))?;
if time_now() + deposit_buffer >= create_time + max_deposit_age {
return Err(warp::reject::custom(Error::Relayer(
"Sigset no longer accepting deposits. Unable to generate deposit address".into(),
)));
}

Ok::<_, warp::Rejection>(warp::reply::json(&"OK"))
},
);

Expand Down Expand Up @@ -291,7 +314,7 @@ impl Relayer {
),
)
.run(([0, 0, 0, 0], 8999));
(server, recv)
Ok((server, recv))
}

async fn relay_deposits(
Expand Down
173 changes: 160 additions & 13 deletions tests/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,10 @@ pub async fn broadcast_deposit_addr(
.send()
.await
.unwrap();

match res.status() {
StatusCode::OK => Ok(()),
_ => Err(Error::Relayer(format!(
"Relayer response returned with error code: {}",
res.status()
))),
_ => Err(Error::Relayer(format!("{}", res.text().await.unwrap()))),
}
}

Expand Down Expand Up @@ -142,8 +140,7 @@ async fn deposit_bitcoin(
"http://localhost:8999".to_string(),
deposit_address.deposit_addr.clone(),
)
.await
.unwrap();
.await?;

wallet
.send_to_address(
Expand Down Expand Up @@ -259,7 +256,7 @@ async fn bitcoin_test() {
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
rpc_addr.clone(),
);
let deposits = relayer.start_deposit_relay(&header_relayer_path);
let deposits = relayer.start_deposit_relay(&header_relayer_path, 60 * 60 * 12);

let mut relayer = Relayer::new(
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
Expand Down Expand Up @@ -724,7 +721,7 @@ async fn signing_completed_checkpoint_test() {
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
rpc_addr.clone(),
);
let deposits = relayer.start_deposit_relay(&header_relayer_path);
let deposits = relayer.start_deposit_relay(&header_relayer_path, 60 * 60 * 12);

let mut relayer = Relayer::new(
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
Expand Down Expand Up @@ -997,7 +994,7 @@ async fn pending_deposits() {
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
rpc_addr.clone(),
);
let deposits = relayer.start_deposit_relay(&header_relayer_path);
let deposits = relayer.start_deposit_relay(&header_relayer_path, 60 * 60 * 12);

let mut relayer = Relayer::new(
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
Expand Down Expand Up @@ -1203,7 +1200,7 @@ async fn signer_key_updating() {
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
rpc_addr.clone(),
);
let deposits = relayer.start_deposit_relay(&header_relayer_path);
let deposits = relayer.start_deposit_relay(&header_relayer_path, 60 * 60 * 12);

let mut relayer = Relayer::new(
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
Expand Down Expand Up @@ -1560,7 +1557,7 @@ async fn recover_expired_deposit() {
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
rpc_addr.clone(),
);
let deposits = relayer.start_deposit_relay(&header_relayer_path);
let deposits = relayer.start_deposit_relay(&header_relayer_path, 60 * 60 * 12);

let mut relayer = Relayer::new(
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
Expand Down Expand Up @@ -1691,8 +1688,7 @@ async fn recover_expired_deposit() {
"http://localhost:8999".to_string(),
expiring_deposit_address.deposit_addr.clone(),
)
.await
.unwrap();
.await;

btc_client
.generate_to_address(1, &async_wallet_address)
Expand Down Expand Up @@ -1738,3 +1734,154 @@ async fn recover_expired_deposit() {
}
}
}

#[tokio::test]
#[serial]
#[ignore]
async fn generate_deposit_expired() {
INIT.call_once(|| {
pretty_env_logger::init();
let genesis_time = Utc.with_ymd_and_hms(2022, 10, 5, 0, 0, 0).unwrap();
let time = Time::from_seconds(genesis_time.timestamp());
set_time(time);
});

let mut conf = Conf::default();
conf.args.push("-txindex");
let bitcoind = BitcoinD::with_conf(bitcoind::downloaded_exe_path().unwrap(), &conf).unwrap();
let rpc_url = bitcoind.rpc_url();
let cookie_file = bitcoind.params.cookie_file.clone();
let btc_client = test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await;

let block_data = populate_bitcoin_block(&btc_client).await;

let home = tempdir().unwrap();
let path = home.into_path();

let node_path = path.clone();
let signer_path = path.clone();
let header_relayer_path = path.clone();

std::env::set_var("NOMIC_HOME_DIR", &path);

let headers_config = HeaderQueueConfig {
encoded_trusted_header: Adapter::new(block_data.block_header)
.encode()
.unwrap()
.try_into()
.unwrap(),
trusted_height: block_data.height,
retargeting: false,
min_difficulty_blocks: true,
max_length: 59,
..Default::default()
};

let bitcoin_config = BitcoinConfig {
max_deposit_age: 60 * 5,
..Default::default()
};

let funded_accounts =
setup_test_app(&path, 4, Some(headers_config), None, Some(bitcoin_config));

let node = Node::<nomic::app::App>::new(node_path, Some("nomic-e2e"), Default::default());
let node_child = node.await.run().await.unwrap();

let rpc_addr = "http://localhost:26657".to_string();

let mut relayer = Relayer::new(
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
rpc_addr.clone(),
);
let headers = relayer.start_header_relay();

let relayer = Relayer::new(
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
rpc_addr.clone(),
);
let deposits = relayer.start_deposit_relay(&header_relayer_path, 5 * 60);

let mut relayer = Relayer::new(
test_bitcoin_client(rpc_url.clone(), cookie_file.clone()).await,
rpc_addr.clone(),
);
let checkpoints = relayer.start_checkpoint_relay();

let xpriv = generate_bitcoin_key(bitcoin::Network::Regtest).unwrap();
fs::create_dir_all(signer_path.join("signer")).unwrap();
fs::write(
signer_path.join("signer/xpriv"),
xpriv.to_string().as_bytes(),
)
.unwrap();
let xpub = ExtendedPubKey::from_priv(&secp256k1::Secp256k1::new(), &xpriv);

let signer = async {
tokio::time::sleep(Duration::from_secs(10)).await;
setup_test_signer(&signer_path, client_provider)
.start()
.await
};

let test = async {
let val_priv_key = load_privkey().unwrap();
let nomic_wallet = DerivedKey::from_secret_key(val_priv_key);
let consensus_key = load_consensus_key(&path)?;
declare_validator(consensus_key, nomic_wallet.clone(), 100_000)
.await
.unwrap();
app_client()
.with_wallet(nomic_wallet.clone())
.call(
|app| build_call!(app.accounts.take_as_funding(MIN_FEE.into())),
|app| build_call!(app.bitcoin.set_signatory_key(xpub.into())),
)
.await?;

let wallet = retry(|| bitcoind.create_wallet("nomic-integration-test"), 10).unwrap();
let wallet_address = wallet.get_new_address(None, None).unwrap();
let async_wallet_address =
bitcoincore_rpc_async::bitcoin::Address::from_str(&wallet_address.to_string()).unwrap();

btc_client
.generate_to_address(120, &async_wallet_address)
.await
.unwrap();

poll_for_bitcoin_header(1120).await.unwrap();

let balance = app_client()
.query(|app| app.bitcoin.accounts.balance(funded_accounts[0].address))
.await
.unwrap();
assert_eq!(balance, Amount::from(0));

poll_for_active_sigset().await;
poll_for_signatory_key(consensus_key).await;

deposit_bitcoin(
&funded_accounts[0].address,
bitcoin::Amount::from_btc(10.0).unwrap(),
&wallet,
)
.await?;

Err::<(), Error>(Error::Test("Test completed successfully".to_string()))
};

poll_for_blocks().await;

match futures::try_join!(headers, deposits, checkpoints, signer, test) {
Err(Error::Test(_)) => panic!("Test failed to fail on deposit address generation"),
Err(Error::Relayer(e)) => {
if !e.to_string().contains("Unable to generate deposit address") {
panic!("Unexpected error: {}", e);
}
}
Ok(_) => panic!("Expected error"),
other => {
other.unwrap();
}
}
}

0 comments on commit 5bbc01a

Please sign in to comment.