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

feat: add DKG verification window #1317

Merged
merged 122 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
122 commits
Select commit Hold shift + click to select a range
9306dfa
update for wsts 11
xoloki Jan 15, 2025
c986d2a
use wsts 11.0.0 from crates
xoloki Jan 15, 2025
25ac3be
fix rebase
xoloki Jan 24, 2025
cf13953
use wsts-12.0.0 from crates
xoloki Jan 24, 2025
6cb670a
add FrostCoordinator to wsts_state_machine so we can run signing roun…
xoloki Jan 27, 2025
31726ee
wip
cylewitruk Jan 27, 2025
63731bb
Merge branch 'feat/frost-coordinator' into feat/require-all-signature…
cylewitruk Jan 27, 2025
105d9a7
run a dkg signing before deploying contracts
cylewitruk Jan 28, 2025
4d406ee
new wsts commit
cylewitruk Jan 28, 2025
1218eb6
wip
cylewitruk Jan 28, 2025
4179cd6
Merge branch 'main' into feat/require-all-signatures-for-rotate-keys
cylewitruk Jan 28, 2025
be05f5e
seems to work
cylewitruk Jan 28, 2025
43501cb
working version
cylewitruk Jan 29, 2025
a0ce153
Merge branch 'main' into feat/require-all-signatures-for-rotate-keys
cylewitruk Jan 29, 2025
a75f5a5
remove dead code
cylewitruk Jan 29, 2025
2214427
minor wsts logging tweaks
cylewitruk Jan 29, 2025
1511969
Merge branch 'main' into feat/require-all-signatures-for-rotate-keys
cylewitruk Jan 29, 2025
25d214a
rename wstsmessageid variant
cylewitruk Jan 29, 2025
18b69d2
it works, now need to clean up
cylewitruk Jan 30, 2025
7412f87
downgrade wsts
cylewitruk Jan 30, 2025
5a16608
proto backwards compatability fixes
cylewitruk Jan 30, 2025
0e624e6
Merge branch 'main' into feat/require-all-signatures-for-rotate-keys
cylewitruk Jan 30, 2025
e867b72
some wsts cleanup/refactor
cylewitruk Jan 30, 2025
c742af2
think that's it
cylewitruk Jan 30, 2025
347db71
add message support for a specific dkg wstsmessageid
cylewitruk Jan 30, 2025
df5111f
add a dkg wstsmessageid variant
cylewitruk Jan 30, 2025
e1173f6
merge changes from the refactor branch
cylewitruk Jan 31, 2025
758c6c2
use block hash instead of random data
cylewitruk Jan 31, 2025
8acccaf
fmt
cylewitruk Jan 31, 2025
00537be
remove 100% requirement for stacks signing of rotate keys
cylewitruk Jan 31, 2025
3049863
Merge branch 'main' into feat/require-all-signatures-for-rotate-keys
cylewitruk Jan 31, 2025
7a23839
bump p256k1 to 7.2.2
cylewitruk Jan 31, 2025
b0cf9a2
pop stash
cylewitruk Jan 31, 2025
623bda8
wip
cylewitruk Feb 2, 2025
1526b83
working verifications
cylewitruk Feb 2, 2025
dc4da30
saving
cylewitruk Feb 3, 2025
2c3b83d
fixing tests
cylewitruk Feb 3, 2025
b7bddbf
seems to work
cylewitruk Feb 4, 2025
6a7073e
remove const generic, use 0 amount and add test for random keypair
cylewitruk Feb 4, 2025
3af03bb
vet bitcoinconsensus
cylewitruk Feb 4, 2025
15bc545
working on cleaning up
cylewitruk Feb 4, 2025
1ceb947
audit log
cylewitruk Feb 4, 2025
8f84016
fix merge artifacts
cylewitruk Feb 4, 2025
8cdb739
tracing fields
cylewitruk Feb 4, 2025
328ecb0
Merge branch 'refactor/wsts-cleanup' into feat/require-all-signatures…
cylewitruk Feb 4, 2025
3349fba
Merge branch 'feat/require-all-signatures-for-rotate-keys' into feat/…
cylewitruk Feb 4, 2025
2742370
migration needs to update block hash/height
cylewitruk Feb 4, 2025
267c5a4
storage mut
cylewitruk Feb 4, 2025
716e5c2
lovely cascading changes
cylewitruk Feb 4, 2025
f3587e0
Merge branch 'main' into refactor/wsts-cleanup
cylewitruk Feb 5, 2025
d22ed61
remove wstsmessage.txid infavor of id
cylewitruk Feb 5, 2025
76b9b49
remove save and prefer trait default impls
cylewitruk Feb 5, 2025
cb95417
remove unused trait methods
cylewitruk Feb 5, 2025
a38d1c9
logging stuff
cylewitruk Feb 5, 2025
5f99a29
rename message ids
cylewitruk Feb 5, 2025
b31f6f7
Merge branch 'refactor/wsts-cleanup' into feat/require-all-signatures…
cylewitruk Feb 5, 2025
92dbf17
use tracing constants
cylewitruk Feb 5, 2025
51a39d3
remove stale comment
cylewitruk Feb 5, 2025
2fe82e6
remove box from some types
cylewitruk Feb 5, 2025
6598b11
missed dkg_begin_pause
cylewitruk Feb 5, 2025
576a004
leftover dbg!()
cylewitruk Feb 5, 2025
5c0304b
refactor some validation
cylewitruk Feb 5, 2025
5d2f807
various pr comments
cylewitruk Feb 5, 2025
1e74dbf
mut thing
cylewitruk Feb 5, 2025
68bf8f5
remove unneeded allow(deprecated)
cylewitruk Feb 5, 2025
d30a0c7
Merge branch 'refactor/wsts-cleanup' into feat/require-all-signatures…
cylewitruk Feb 5, 2025
978a83e
Merge branch 'refactor/wsts-cleanup' into feat/mock-signing
cylewitruk Feb 5, 2025
abaf93f
import some changes manually from parent branch to help a confused me…
cylewitruk Feb 5, 2025
2aff467
more diff-reducing imports
cylewitruk Feb 5, 2025
f350a7f
Merge branch 'main' into feat/require-all-signatures-for-rotate-keys
cylewitruk Feb 5, 2025
dea3625
confused merge tool
cylewitruk Feb 5, 2025
15ca645
reduce diff
cylewitruk Feb 5, 2025
13595bf
Merge branch 'feat/require-all-signatures-for-rotate-keys' into feat/…
cylewitruk Feb 5, 2025
2c46ea5
add validation for nonceresponse + signatureshareresponse
cylewitruk Feb 5, 2025
3fd47be
newline
cylewitruk Feb 5, 2025
7e621d7
pr comments
cylewitruk Feb 5, 2025
4642e66
pr comments utxo
cylewitruk Feb 5, 2025
372b55d
utxo comments
cylewitruk Feb 6, 2025
3f7b9ad
remove error conversion method
cylewitruk Feb 6, 2025
5b98412
Change the DKG shares status type.
djordon Feb 6, 2025
377c4f7
Update the migration query
djordon Feb 6, 2025
bd8216c
Fix up remaining queries
djordon Feb 6, 2025
0d24de2
Oops forgot this one
djordon Feb 6, 2025
5838bf6
Forgot this rename
djordon Feb 6, 2025
17bf002
Rename the new status field to
djordon Feb 6, 2025
1f18d30
Fix up the tests and add a new one
djordon Feb 6, 2025
32316d2
Change the return value of some of the
djordon Feb 6, 2025
2af9534
Change it back
djordon Feb 6, 2025
b6a4a28
Update the tests
djordon Feb 6, 2025
696839c
feat: add DKG verification window
matteojug Feb 6, 2025
d8f90e9
fix: less optimistic timeout
matteojug Feb 6, 2025
480b10a
Squashed commit of the following:
cylewitruk Feb 7, 2025
b88100b
Merge branch 'main' into feat/mock-signing
cylewitruk Feb 7, 2025
1fb3ffa
Clean up the comments in the shares enum
djordon Feb 7, 2025
6570d00
rename the rotate keys error variant
djordon Feb 7, 2025
36424ec
Use a better error variant when extracting
djordon Feb 7, 2025
9a66038
Remove some of our unused error variants
djordon Feb 7, 2025
97142e3
Match the behavior in the in memory store
djordon Feb 7, 2025
262b86a
We do not need these errors anymore either
djordon Feb 7, 2025
b71eddc
Merge remote-tracking branch 'origin/1313-validate-rotate-keys-contra…
matteojug Feb 7, 2025
bf615de
chore: CR nits
matteojug Feb 7, 2025
7878be8
Change the DKG shares status type.
djordon Feb 6, 2025
daf4946
Update the migration query
djordon Feb 6, 2025
83b96b0
Fix up remaining queries
djordon Feb 6, 2025
ee46b3e
Oops forgot this one
djordon Feb 6, 2025
aea867b
Forgot this rename
djordon Feb 6, 2025
27a3bfa
Rename the new status field to
djordon Feb 6, 2025
5b7cc5d
Fix up the tests and add a new one
djordon Feb 6, 2025
d290aa8
Change the return value of some of the
djordon Feb 6, 2025
0ce46d3
Change it back
djordon Feb 6, 2025
3c837d8
Update the tests
djordon Feb 6, 2025
76f6754
Clean up the comments in the shares enum
djordon Feb 7, 2025
e487615
rename the rotate keys error variant
djordon Feb 7, 2025
4ad9835
Use a better error variant when extracting
djordon Feb 7, 2025
21cd44b
Remove some of our unused error variants
djordon Feb 7, 2025
102a3d5
Match the behavior in the in memory store
djordon Feb 7, 2025
45c8299
We do not need these errors anymore either
djordon Feb 7, 2025
367491d
Merge remote-tracking branch 'origin/feat/mock-signing' into feat/dkg…
matteojug Feb 7, 2025
6606988
chore: nit comments
matteojug Feb 7, 2025
ea00224
address nits
Jiloc Feb 7, 2025
7d06bff
Merge remote-tracking branch 'origin/feat/mock-signing' into feat/dkg…
matteojug Feb 7, 2025
ba294cb
Merge remote-tracking branch 'origin/main' into feat/dkg-validity-window
matteojug Feb 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docker/sbtc/signer/signer-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,14 @@ bootstrap_signatures_required = 2
# Environment: SIGNER_SIGNER__DKG_TARGET_ROUNDS
# dkg_target_rounds = 1

# The number of bitcoin blocks after a DKG start where we attempt to verify the
# shares. After this many blocks, we mark the shares as failed. Please only use
# this parameter when instructed to by the sBTC team.
#
# Required: false
# Environment: SIGNER_SIGNER__DKG_VERIFICATION_WINDOW
# dkg_verification_window = 10

# !! ==============================================================================
# !! Stacks Event Observer Configuration
# !!
Expand Down
38 changes: 38 additions & 0 deletions signer/src/block_observer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use crate::stacks::api::StacksInteract;
use crate::stacks::api::TenureBlocks;
use crate::storage;
use crate::storage::model;
use crate::storage::model::EncryptedDkgShares;
use crate::storage::DbRead;
use crate::storage::DbWrite;
use bitcoin::hashes::Hash as _;
Expand Down Expand Up @@ -159,6 +160,11 @@ where
tracing::warn!(%error, "could not process stacks blocks");
}

if let Err(error) = self.check_pending_dkg_shares(block_hash).await {
tracing::warn!(%error, "could not check pending dkg shares");
continue;
}

tracing::debug!("updating the signer state");
if let Err(error) = self.update_signer_state(block_hash).await {
tracing::warn!(%error, "could not update the signer state");
Expand Down Expand Up @@ -628,6 +634,38 @@ impl<C: Context, B> BlockObserver<C, B> {
tracing::info!("updating the signer state with the current signer set");
self.set_signer_set_and_aggregate_key(chain_tip).await
}

/// Checks if the latest dkg share is pending and is no longer valid
async fn check_pending_dkg_shares(&self, chain_tip: BlockHash) -> Result<(), Error> {
let db = self.context.get_storage_mut();

let last_dkg = db.get_latest_encrypted_dkg_shares().await?;
let Some(
last_dkg @ EncryptedDkgShares {
dkg_shares_status: model::DkgSharesStatus::Unverified,
..
},
) = last_dkg
else {
return Ok(());
};

let chain_tip = db
.get_bitcoin_block(&chain_tip.into())
.await?
.ok_or(Error::NoChainTip)?;
let verification_window = self.context.config().signer.dkg_verification_window;

let max_verification_height = last_dkg
.started_at_bitcoin_block_height
.saturating_add(verification_window as u64);

if max_verification_height < chain_tip.block_height {
db.revoke_dkg_shares(last_dkg.aggregate_key).await?;
}

Ok(())
}
}

/// Return the signing set that can make sBTC related contract calls along
Expand Down
8 changes: 8 additions & 0 deletions signer/src/config/default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,14 @@ sbtc_bitcoin_start_height = 101
# Environment: SIGNER_SIGNER__DKG_TARGET_ROUNDS
# dkg_target_rounds = 1

# The number of bitcoin blocks after a DKG start where we attempt to verify the
# shares. After this many blocks, we mark the shares as failed. Please only use
# this parameter when instructed to by the sBTC team.
#
# Required: false
# Environment: SIGNER_SIGNER__DKG_VERIFICATION_WINDOW
# dkg_verification_window = 10

# !! ==============================================================================
# !! Stacks Event Observer Configuration
# !!
Expand Down
17 changes: 17 additions & 0 deletions signer/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,9 @@ pub struct SignerConfig {
/// assuming the conditions for `dkg_min_bitcoin_block_height` are also met.
/// If DKG has never been run, this configuration has no effect.
pub dkg_target_rounds: NonZeroU32,
/// The number of bitcoin blocks after a DKG start where we attempt to
/// verify the shares. After this many blocks, we mark the shares as failed.
pub dkg_verification_window: u16,
}

impl Validatable for SignerConfig {
Expand Down Expand Up @@ -494,6 +497,7 @@ impl Settings {
DEFAULT_MAX_DEPOSITS_PER_BITCOIN_TX,
)?;
cfg_builder = cfg_builder.set_default("signer.dkg_target_rounds", 1)?;
cfg_builder = cfg_builder.set_default("signer.dkg_verification_window", 10)?;
matteojug marked this conversation as resolved.
Show resolved Hide resolved

if let Some(path) = config_path {
cfg_builder = cfg_builder.add_source(File::from(path.as_ref()));
Expand Down Expand Up @@ -631,6 +635,7 @@ mod tests {
settings.signer.dkg_target_rounds,
NonZeroU32::new(1).unwrap()
);
assert_eq!(settings.signer.dkg_verification_window, 10);
assert_eq!(settings.signer.dkg_min_bitcoin_block_height, None);
}

Expand Down Expand Up @@ -790,6 +795,18 @@ mod tests {
);
}

#[test]
fn default_config_toml_loads_dkg_verification_window() {
clear_env();

let settings = Settings::new_from_default_config().unwrap();
assert_eq!(settings.signer.dkg_verification_window, 10);

std::env::set_var("SIGNER_SIGNER__DKG_VERIFICATION_WINDOW", "42");
let settings = Settings::new_from_default_config().unwrap();
assert_eq!(settings.signer.dkg_verification_window, 42);
}

#[test]
fn default_config_toml_loads_signer_network_with_environment() {
clear_env();
Expand Down
8 changes: 7 additions & 1 deletion signer/src/storage/in_memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,13 @@ impl super::DbRead for SharedStore {
}

async fn get_encrypted_dkg_shares_count(&self) -> Result<u32, Error> {
Ok(self.lock().await.encrypted_dkg_shares.len() as u32)
Ok(self
.lock()
.await
.encrypted_dkg_shares
.values()
.filter(|(_, shares)| shares.dkg_shares_status != DkgSharesStatus::Failed)
.count() as u32)
}

async fn get_last_key_rotation(
Expand Down
2 changes: 1 addition & 1 deletion signer/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ pub trait DbRead {
&self,
) -> impl Future<Output = Result<Option<model::EncryptedDkgShares>, Error>> + Send;

/// Returns the number of DKG shares entries in the database.
/// Returns the number of non-failed DKG shares entries in the database.
fn get_encrypted_dkg_shares_count(&self) -> impl Future<Output = Result<u32, Error>> + Send;

/// Return the latest rotate-keys transaction confirmed by the given `chain-tip`.
Expand Down
12 changes: 7 additions & 5 deletions signer/src/storage/postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1529,12 +1529,14 @@ impl super::DbRead for PgStore {
.map_err(Error::SqlxQuery)
}

/// Returns the number of rows in the `dkg_shares` table.
/// Returns the number of non-failed rows in the `dkg_shares` table.
async fn get_encrypted_dkg_shares_count(&self) -> Result<u32, Error> {
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM sbtc_signer.dkg_shares;")
.fetch_one(&self.0)
.await
.map_err(Error::SqlxQuery)?;
let count: i64 = sqlx::query_scalar(
matteojug marked this conversation as resolved.
Show resolved Hide resolved
"SELECT COUNT(*) FROM sbtc_signer.dkg_shares WHERE dkg_shares_status != 'failed';",
)
.fetch_one(&self.0)
.await
.map_err(Error::SqlxQuery)?;

u32::try_from(count).map_err(Error::ConversionDatabaseInt)
}
Expand Down
7 changes: 6 additions & 1 deletion signer/src/testing/transaction_coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,7 +1020,12 @@ where

let dkg_txid = testing::dummy::txid(&fake::Faker, rng);
let (aggregate_key, all_dkg_shares) = signer_set
.run_dkg(bitcoin_chain_tip, dkg_txid.into(), rng)
.run_dkg(
bitcoin_chain_tip,
dkg_txid.into(),
rng,
model::DkgSharesStatus::Verified,
)
.await;

let encrypted_dkg_shares = all_dkg_shares.first().unwrap();
Expand Down
7 changes: 6 additions & 1 deletion signer/src/testing/transaction_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,12 @@ async fn run_dkg_and_store_results_for_signers<'s: 'r, 'r, S, Rng>(
let dkg_txid = testing::dummy::txid(&fake::Faker, rng);
let bitcoin_chain_tip = *chain_tip;
let (_, all_dkg_shares) = testing_signer_set
.run_dkg(bitcoin_chain_tip, dkg_txid.into(), rng)
.run_dkg(
bitcoin_chain_tip,
dkg_txid.into(),
rng,
model::DkgSharesStatus::Verified,
)
.await;

for (storage, encrypted_dkg_shares) in stores.into_iter().zip(all_dkg_shares) {
Expand Down
14 changes: 11 additions & 3 deletions signer/src/testing/wsts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ impl SignerSet {
bitcoin_chain_tip: model::BitcoinBlockHash,
id: WstsMessageId,
rng: &mut Rng,
dkg_shares_status: model::DkgSharesStatus,
) -> (PublicKey, Vec<model::EncryptedDkgShares>) {
let mut signer_handles = Vec::new();
for signer in self.signers.drain(..) {
Expand All @@ -430,10 +431,12 @@ impl SignerSet {
self.signers
.iter()
.map(|signer| {
signer
let mut shares = signer
.wsts_signer
.get_encrypted_dkg_shares(rng, &started_at)
.expect("failed to get encrypted shares")
.expect("failed to get encrypted shares");
shares.dkg_shares_status = dkg_shares_status;
shares
})
.collect(),
)
Expand Down Expand Up @@ -559,7 +562,12 @@ mod tests {
let mut signer_set = SignerSet::new(&signer_info, threshold, || network.connect());

let (_, dkg_shares) = signer_set
.run_dkg(bitcoin_chain_tip, txid.into(), &mut rng)
.run_dkg(
bitcoin_chain_tip,
txid.into(),
&mut rng,
model::DkgSharesStatus::Unverified,
)
.await;

assert_eq!(dkg_shares.len(), num_signers as usize);
Expand Down
8 changes: 4 additions & 4 deletions signer/src/transaction_coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2000,10 +2000,10 @@ mod tests {
// Write `dkg_shares` entries for the `current` number of rounds, simulating
// the signer having participated in that many successful DKG rounds.
for _ in 0..dkg_rounds_current {
storage
.write_encrypted_dkg_shares(&Faker.fake())
.await
.unwrap();
let mut shares: model::EncryptedDkgShares = Faker.fake();
shares.dkg_shares_status = model::DkgSharesStatus::Verified;

storage.write_encrypted_dkg_shares(&shares).await.unwrap();
}

// Dummy chain tip hash which will be used to fetch the block height
Expand Down
26 changes: 13 additions & 13 deletions signer/src/transaction_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1263,7 +1263,7 @@ where

/// Asserts whether a `DkgBegin` WSTS message should be allowed to proceed
/// based on the current state of the signer and the DKG configuration.
async fn assert_allow_dkg_begin(
pub async fn assert_allow_dkg_begin(
context: &impl Context,
bitcoin_chain_tip: &model::BitcoinBlockRef,
) -> Result<(), Error> {
Expand Down Expand Up @@ -1451,10 +1451,10 @@ mod tests {
// Write `dkg_shares` entries for the `current` number of rounds, simulating
// the signer having participated in that many successful DKG rounds.
for _ in 0..dkg_rounds_current {
storage
.write_encrypted_dkg_shares(&Faker.fake())
.await
.unwrap();
let mut shares: model::EncryptedDkgShares = Faker.fake();
shares.dkg_shares_status = model::DkgSharesStatus::Verified;

storage.write_encrypted_dkg_shares(&shares).await.unwrap();
}

// Dummy chain tip hash which will be used to fetch the block height
Expand Down Expand Up @@ -1495,10 +1495,10 @@ mod tests {

// Write 1 DKG shares entry to the database, simulating that DKG has
// successfully run once.
storage
.write_encrypted_dkg_shares(&Faker.fake())
.await
.unwrap();
let mut shares: model::EncryptedDkgShares = Faker.fake();
shares.dkg_shares_status = model::DkgSharesStatus::Verified;

storage.write_encrypted_dkg_shares(&shares).await.unwrap();

// Dummy chain tip hash which will be used to fetch the block height.
let bitcoin_chain_tip = model::BitcoinBlockRef {
Expand Down Expand Up @@ -1566,10 +1566,10 @@ mod tests {

// Write 1 DKG shares entry to the database, simulating that DKG has
// successfully run once.
storage
.write_encrypted_dkg_shares(&Faker.fake())
.await
.unwrap();
let mut shares: model::EncryptedDkgShares = Faker.fake();
shares.dkg_shares_status = model::DkgSharesStatus::Verified;

storage.write_encrypted_dkg_shares(&shares).await.unwrap();

// Dummy chain tip hash which will be used to fetch the block height.
let bitcoin_chain_tip: model::BitcoinBlockHash = Faker.fake();
Expand Down
Loading