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

modify nakamoto block header to use Vec<MessageSignature> #4781

Merged
merged 61 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
8ac1193
wip: modify nakamoto block header to use `Vec<MessageSignature>`
hstove May 13, 2024
fd0407b
feat: verify signatures in nakamoto block header
hstove May 13, 2024
80e6853
feat: refactor test_signers for new signer_signature type
hstove May 15, 2024
57c0db2
feat: update sign_coordinator and blind_signer for new message types
hstove May 15, 2024
da6dad1
fix: code review comments
hstove May 16, 2024
e0f9052
chore: fix compile-time issue
jcnelson May 17, 2024
aaf8b93
Merge branch 'feat/add-v0-signer' into feat/header-signer-signatures
hstove May 20, 2024
401d2d3
crc: docstring, dead_code macro
hstove May 20, 2024
650c86c
feat: gather v0 block signatures from stackerdb
hstove May 20, 2024
f72ecc8
feat: update metrics in v0 signer, add tests
hstove May 21, 2024
aa9baf9
fix: move reward_set loading to top-level BlockMinerThread
hstove May 21, 2024
2416c4b
feat: better API for querying preprocessed reward sets (e.g. by rewar…
jcnelson May 22, 2024
753f9c9
chore: fix 4813 by re-trying to store a reward set if it has 'selecte…
jcnelson May 22, 2024
ad1094b
chore: blow away aggregate key verification code (it won't be used un…
jcnelson May 22, 2024
da60d06
chore: API sync
jcnelson May 22, 2024
67038a2
chore: API sync
jcnelson May 22, 2024
127165a
fix: search for the highest tenure from the block-processor's given s…
jcnelson May 22, 2024
9bc3d55
feat: add a way to synthesize a reward set from a list of signers
jcnelson May 22, 2024
b8e85e3
chore: move unused code for loading the aggregate public key into a t…
jcnelson May 22, 2024
41c50fa
chore: API sync
jcnelson May 22, 2024
f1d4657
chore: fmt
jcnelson May 22, 2024
ffb572e
chore: doc epoch2-specific behavior
jcnelson May 22, 2024
f2fac09
chore: use reward sets instead of aggregate public keys
jcnelson May 22, 2024
3fff20e
chore: use reward set signature verification
jcnelson May 22, 2024
0318fb2
chore: use reward set signature verification
jcnelson May 22, 2024
4f38a04
chore: use reward set signature verification
jcnelson May 22, 2024
b0100bc
chore: document post-nakamoto usage of aggregate public key
jcnelson May 22, 2024
3de7c89
feat: cache reward sets by reward cycle and sortition ID, instead of …
jcnelson May 22, 2024
dc43a27
chore: API sync; delete dead code (it's in the git history now)
jcnelson May 22, 2024
4d0a1a3
chore: use reward sets instead of aggregate public keys
jcnelson May 22, 2024
004f3d6
chore: API sync
jcnelson May 22, 2024
02b1492
Merge branch 'feat/header-signer-signatures' into fix/4793
jcnelson May 22, 2024
6e301e3
fix: load the signer reward cycle for the block based on the fact tha…
jcnelson May 22, 2024
1f1c8dc
fix: check the *ongoing* tenure, not the last-started tenure
jcnelson May 22, 2024
a99c098
feat: add query API for the ongoing tenure in a given sortition (be i…
jcnelson May 22, 2024
f930538
chore: fmt
jcnelson May 22, 2024
480f2fd
chore: document tenure-loading behavior
jcnelson May 22, 2024
d2c2847
chore: fmt
jcnelson May 22, 2024
b24deed
chore: mock aggregate public key until the miner is upgraded to not n…
jcnelson May 22, 2024
bb1aed1
fix: remove unused var
jcnelson May 22, 2024
72b0e2c
chore: address PR feedback
jcnelson May 23, 2024
1091346
Merge branch 'develop' into feat/header-signer-signatures
jcnelson May 23, 2024
91f3545
chore: comment on why we need to continuously re-check the preprocess…
jcnelson May 23, 2024
6b86b95
crc: typecasting and returning errors instead of panic in `coordinate…
hstove May 23, 2024
0658bb9
crc: return err instead of fatal when updating next_bitvec
hstove May 23, 2024
4cb4d15
feat: load reward set in gather_signatures instead of `run_miner` to …
hstove May 23, 2024
4dca885
Merge branch 'feat/header-signer-signatures' into feat/miner-gather-v…
hstove May 23, 2024
2275aa8
feat: remove aggregate key related stuff from sign coordinator
hstove May 23, 2024
3dccf40
chore: address PR feedback
jcnelson May 24, 2024
0e68198
chore: indicate in the docs that the reward set must correspond to th…
jcnelson May 28, 2024
a70bfb5
Merge pull request #4817 from stacks-network/fix/4793
jcnelson May 28, 2024
dbcd10f
Merge branch 'develop' into feat/header-signer-signatures
jcnelson May 28, 2024
faa808a
Merge 'feat/header-signer-signatures' into 'feat/miner-gather-v0-sign…
hstove May 29, 2024
e968530
feat: additional checks for invalid signatures, duplicates, etc
hstove May 30, 2024
f963b35
crc: helper method for RewardSet total weight
hstove May 30, 2024
4c35571
fix: add missing comma to debug metadata
hstove May 30, 2024
ba9df14
Merge pull request #4807 from stacks-network/feat/miner-gather-v0-sig…
hstove May 31, 2024
55567d7
fix: return errors instead of panics in miner thread
hstove Jun 3, 2024
1d7c5bd
Merge branch 'develop' into feat/header-signer-signatures
hstove Jun 3, 2024
0e59295
fix: cargo check --tests errors after merge
hstove Jun 3, 2024
7c8df31
fix: miner should allow prior miner thread to error
kantai Jun 4, 2024
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
4 changes: 3 additions & 1 deletion .github/workflows/bitcoin-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,11 @@ jobs:
- tests::nakamoto_integrations::forked_tenure_is_ignored
- tests::nakamoto_integrations::nakamoto_attempt_time
- tests::signer::v0::block_proposal_rejection
- tests::signer::v0::miner_gather_signatures
- tests::signer::v1::dkg
- tests::signer::v1::sign_request_rejected
- tests::signer::v1::filter_bad_transactions
# TODO: enable these once v1 signer is fixed
# - tests::signer::v1::filter_bad_transactions
- tests::signer::v1::delayed_dkg
# TODO: enable these once v1 signer is fixed
# - tests::signer::v1::mine_2_nakamoto_reward_cycles
Expand Down
7 changes: 6 additions & 1 deletion stacks-signer/src/monitoring/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@ impl MonitoringServer {
public_key,
format!("http://{}", config.node_host),
);
server.update_metrics()?;
if let Err(e) = server.update_metrics() {
warn!(
"Monitoring: Error updating metrics when starting server: {:?}",
e
);
};
server.main_loop()
}

Expand Down
13 changes: 10 additions & 3 deletions stacks-signer/src/v0/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ impl Signer {
"block_id" => %block_proposal.block.block_id(),
);
let block_info = BlockInfo::from(block_proposal.clone());
crate::monitoring::increment_block_proposals_received();
stacks_client
.submit_block_for_validation(block_info.block.clone())
.unwrap_or_else(|e| {
Expand Down Expand Up @@ -312,11 +313,17 @@ impl Signer {
};
// Submit a proposal response to the .signers contract for miners
debug!("{self}: Broadcasting a block response to stacks node: {response:?}");
if let Err(e) = self
match self
.stackerdb
.send_message_with_retry::<SignerMessage>(response.into())
.send_message_with_retry::<SignerMessage>(response.clone().into())
{
warn!("{self}: Failed to send block rejection to stacker-db: {e:?}",);
Ok(_) => {
let accepted = matches!(response, BlockResponse::Accepted(..));
crate::monitoring::increment_block_responses_sent(accepted);
}
Err(e) => {
warn!("{self}: Failed to send block rejection to stacker-db: {e:?}",);
}
}
self.signer_db
.insert_block(&block_info)
Expand Down
187 changes: 142 additions & 45 deletions stackslib/src/chainstate/burn/db/sortdb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ use crate::core::{
FIRST_BURNCHAIN_CONSENSUS_HASH, FIRST_STACKS_BLOCK_HASH, STACKS_EPOCH_MAX,
};
use crate::net::neighbors::MAX_NEIGHBOR_BLOCK_DELAY;
use crate::net::Error as NetError;
use crate::util_lib::db::{
db_mkdirs, get_ancestor_block_hash, opt_u64_to_sql, query_count, query_row, query_row_columns,
query_row_panic, query_rows, sql_pragma, table_exists, tx_begin_immediate, tx_busy_handler,
Expand Down Expand Up @@ -3542,59 +3541,56 @@ impl SortitionDB {
sortition_id: &SortitionId,
rc_info: &RewardCycleInfo,
) -> Result<(), db_error> {
let sql = "INSERT INTO preprocessed_reward_sets (sortition_id,reward_set) VALUES (?1,?2)";
let sql = "REPLACE INTO preprocessed_reward_sets (sortition_id,reward_set) VALUES (?1,?2)";
let rc_json = serde_json::to_string(rc_info).map_err(db_error::SerializationError)?;
let args: &[&dyn ToSql] = &[sortition_id, &rc_json];
sort_tx.execute(sql, args)?;
Ok(())
}

/// Figure out the reward cycle for `tip` and lookup the preprocessed
/// reward set (if it exists) for the active reward cycle during `tip`
pub fn get_preprocessed_reward_set_of(
/// Wrapper around SortitionDBConn::get_prepare_phase_start_sortition_id_for_reward_cycle().
/// See that method for details.
pub fn get_prepare_phase_start_sortition_id_for_reward_cycle(
&self,
tip: &SortitionId,
) -> Result<Option<RewardCycleInfo>, db_error> {
let tip_sn = SortitionDB::get_block_snapshot(self.conn(), tip)?.ok_or_else(|| {
error!(
"Could not find snapshot for sortition while fetching reward set";
"tip_sortition_id" => %tip,
);
db_error::NotFoundError
})?;

let reward_cycle_id = self
.pox_constants
.block_height_to_reward_cycle(self.first_block_height, tip_sn.block_height)
.expect("FATAL: stored snapshot with block height < first_block_height");

let prepare_phase_start = self
.pox_constants
.reward_cycle_to_block_height(self.first_block_height, reward_cycle_id)
.saturating_sub(self.pox_constants.prepare_length.into());

let first_sortition = get_ancestor_sort_id(
&self.index_conn(),
prepare_phase_start,
&tip_sn.sortition_id,
)?
.ok_or_else(|| {
error!(
"Could not find prepare phase start ancestor while fetching reward set";
"tip_sortition_id" => %tip,
"reward_cycle_id" => reward_cycle_id,
"prepare_phase_start_height" => prepare_phase_start
);
db_error::NotFoundError
})?;
reward_cycle_id: u64,
) -> Result<SortitionId, db_error> {
self.index_conn()
.get_prepare_phase_start_sortition_id_for_reward_cycle(
&self.pox_constants,
self.first_block_height,
tip,
reward_cycle_id,
)
}

info!("Fetching preprocessed reward set";
"tip_sortition_id" => %tip,
"reward_cycle_id" => reward_cycle_id,
"prepare_phase_start_sortition_id" => %first_sortition,
);
/// Wrapper around SortitionDBConn::get_preprocessed_reward_set_for_reward_cycle().
/// See that method for details.
pub fn get_preprocessed_reward_set_for_reward_cycle(
&self,
tip: &SortitionId,
reward_cycle_id: u64,
) -> Result<(RewardCycleInfo, SortitionId), db_error> {
self.index_conn()
.get_preprocessed_reward_set_for_reward_cycle(
&self.pox_constants,
self.first_block_height,
tip,
reward_cycle_id,
)
}

Self::get_preprocessed_reward_set(self.conn(), &first_sortition)
/// Wrapper around SortitionDBConn::get_preprocessed_reward_set_of().
/// See that method for details.
pub fn get_preprocessed_reward_set_of(
&self,
tip: &SortitionId,
) -> Result<RewardCycleInfo, db_error> {
Ok(self.index_conn().get_preprocessed_reward_set_of(
&self.pox_constants,
self.first_block_height,
tip,
)?)
}

/// Get a pre-processed reawrd set.
Expand All @@ -3616,8 +3612,10 @@ impl SortitionDB {
Ok(rc_info)
}

/// Get the number of entries in the reward set, given a sortition ID within the reward cycle
/// for which this set is active.
pub fn get_preprocessed_reward_set_size(&self, tip: &SortitionId) -> Option<u16> {
let Ok(Some(reward_info)) = &self.get_preprocessed_reward_set_of(&tip) else {
let Ok(reward_info) = &self.get_preprocessed_reward_set_of(&tip) else {
return None;
};
let Some(reward_set) = reward_info.known_selected_anchor_block() else {
Expand Down Expand Up @@ -3842,6 +3840,100 @@ impl<'a> SortitionDBConn<'a> {
serde_json::from_str(&pox_addrs_json).expect("FATAL: failed to decode pox payout JSON");
Ok(pox_addrs)
}

/// Figure out the reward cycle for `tip` and lookup the preprocessed
/// reward set (if it exists) for the active reward cycle during `tip`.
/// Returns the reward cycle info on success.
/// Returns Error on DB errors, as well as if the reward set is not yet processed.
pub fn get_preprocessed_reward_set_of(
&self,
pox_constants: &PoxConstants,
first_block_height: u64,
tip: &SortitionId,
) -> Result<RewardCycleInfo, db_error> {
let tip_sn = SortitionDB::get_block_snapshot(self, tip)?.ok_or_else(|| {
error!(
"Could not find snapshot for sortition while fetching reward set";
"tip_sortition_id" => %tip,
);
db_error::NotFoundError
})?;

// NOTE: the .saturating_sub(1) is necessary because the reward set is calculated in epoch
// 2.5 and lower at reward cycle index 1, not 0. This correction ensures that the last
// block is checked against the signers who were active just before the new reward set is
// calculated.
let reward_cycle_id = pox_constants
.block_height_to_reward_cycle(first_block_height, tip_sn.block_height.saturating_sub(1))
.expect("FATAL: stored snapshot with block height < first_block_height");

self.get_preprocessed_reward_set_for_reward_cycle(
pox_constants,
first_block_height,
tip,
reward_cycle_id,
)
.and_then(|(reward_cycle_info, _anchor_sortition_id)| Ok(reward_cycle_info))
}

/// Get the prepare phase start sortition ID of a reward cycle. This is the first prepare
/// phase sortition for the prepare phase that began this reward cycle (i.e. the returned
/// sortition will be in the preceding reward cycle)
pub fn get_prepare_phase_start_sortition_id_for_reward_cycle(
&self,
pox_constants: &PoxConstants,
first_block_height: u64,
tip: &SortitionId,
reward_cycle_id: u64,
) -> Result<SortitionId, db_error> {
let prepare_phase_start = pox_constants
.reward_cycle_to_block_height(first_block_height, reward_cycle_id)
.saturating_sub(pox_constants.prepare_length.into());

let first_sortition =
get_ancestor_sort_id(self, prepare_phase_start, tip)?.ok_or_else(|| {
error!(
"Could not find prepare phase start ancestor while fetching reward set";
"tip_sortition_id" => %tip,
"reward_cycle_id" => reward_cycle_id,
"prepare_phase_start_height" => prepare_phase_start
);
db_error::NotFoundError
})?;
Ok(first_sortition)
}

/// Get the reward set for a reward cycle, given the reward cycle tip. The reward cycle info
/// will be returned for the reward set in which `tip` belongs (i.e. the reward set calculated
/// in the preceding reward cycle).
/// Return the reward cycle info for this reward cycle, as well as the first prepare-phase
/// sortition ID under which this reward cycle info is stored.
/// Returns Error on DB Error, or if the reward cycle info is not processed yet.
pub fn get_preprocessed_reward_set_for_reward_cycle(
&self,
pox_constants: &PoxConstants,
first_block_height: u64,
tip: &SortitionId,
reward_cycle_id: u64,
) -> Result<(RewardCycleInfo, SortitionId), db_error> {
let first_sortition = self.get_prepare_phase_start_sortition_id_for_reward_cycle(
pox_constants,
first_block_height,
tip,
reward_cycle_id,
)?;
info!("Fetching preprocessed reward set";
"tip_sortition_id" => %tip,
"reward_cycle_id" => reward_cycle_id,
"prepare_phase_start_sortition_id" => %first_sortition,
);

Ok((
SortitionDB::get_preprocessed_reward_set(self, &first_sortition)?
.ok_or(db_error::NotFoundError)?,
first_sortition,
))
}
}

// High-level functions used by ChainsCoordinator
Expand Down Expand Up @@ -4559,12 +4651,14 @@ impl SortitionDB {
Ok(ret)
}

/// DO NOT CALL during Stacks block processing (including during Clarity VM evaluation). This function returns the latest data known to the node, which may not have been at the time of original block assembly.
pub fn index_handle_at_tip<'a>(&'a self) -> SortitionHandleConn<'a> {
let sortition_id = SortitionDB::get_canonical_sortition_tip(self.conn()).unwrap();
self.index_handle(&sortition_id)
}

/// Open a tx handle at the burn chain tip
/// DO NOT CALL during Stacks block processing (including during Clarity VM evaluation). This function returns the latest data known to the node, which may not have been at the time of original block assembly.
pub fn tx_begin_at_tip<'a>(&'a mut self) -> SortitionHandleTx<'a> {
let sortition_id = SortitionDB::get_canonical_sortition_tip(self.conn()).unwrap();
self.tx_handle_begin(&sortition_id).unwrap()
Expand All @@ -4574,6 +4668,7 @@ impl SortitionDB {
/// Returns Ok(Some(tip info)) on success
/// Returns Ok(None) if there are no Nakamoto blocks in this tip
/// Returns Err(..) on other DB error
/// DO NOT CALL during Stacks block processing (including during Clarity VM evaluation). This function returns the latest data known to the node, which may not have been at the time of original block assembly.
pub fn get_canonical_nakamoto_tip_hash_and_height(
conn: &Connection,
tip: &BlockSnapshot,
Expand All @@ -4598,6 +4693,7 @@ impl SortitionDB {
}

/// Get the canonical Stacks chain tip -- this gets memoized on the canonical burn chain tip.
/// DO NOT CALL during Stacks block processing (including during Clarity VM evaluation). This function returns the latest data known to the node, which may not have been at the time of original block assembly.
pub fn get_canonical_stacks_chain_tip_hash_and_height(
conn: &Connection,
) -> Result<(ConsensusHash, BlockHeaderHash, u64), db_error> {
Expand Down Expand Up @@ -4625,6 +4721,7 @@ impl SortitionDB {
}

/// Get the canonical Stacks chain tip -- this gets memoized on the canonical burn chain tip.
/// DO NOT CALL during Stacks block processing (including during Clarity VM evaluation). This function returns the latest data known to the node, which may not have been at the time of original block assembly.
pub fn get_canonical_stacks_chain_tip_hash(
conn: &Connection,
) -> Result<(ConsensusHash, BlockHeaderHash), db_error> {
Expand Down
28 changes: 24 additions & 4 deletions stackslib/src/chainstate/coordinator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ impl NewBurnchainBlockStatus {
}
}

#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct RewardCycleInfo {
pub reward_cycle: u64,
pub anchor_status: PoxAnchorBlockStatus,
Expand Down Expand Up @@ -845,9 +845,29 @@ pub fn get_reward_cycle_info<U: RewardSetProvider>(
.expect("FATAL: no start-of-prepare-phase sortition");

let mut tx = sort_db.tx_begin()?;
if SortitionDB::get_preprocessed_reward_set(&mut tx, &first_prepare_sn.sortition_id)?
.is_none()
{
let preprocessed_reward_set =
SortitionDB::get_preprocessed_reward_set(&mut tx, &first_prepare_sn.sortition_id)?;

// It's possible that we haven't processed the PoX anchor block at the time we have
// processed the burnchain block which commits to it. In this case, the PoX anchor block
// status would be SelectedAndUnknown. However, it's overwhelmingly likely (and in
// Nakamoto, _required_) that the PoX anchor block will be processed shortly thereafter.
// When this happens, we need to _update_ the sortition DB with the newly-processed reward
// set. This code performs this check to determine whether or not we need to store this
// calculated reward set.
let need_to_store = if let Some(reward_cycle_info) = preprocessed_reward_set {
// overwrite if we have an unknown anchor block
!reward_cycle_info.is_reward_info_known()
} else {
true
};
if need_to_store {
debug!(
"Store preprocessed reward set for cycle";
"reward_cycle" => prev_reward_cycle,
"prepare-start sortition" => %first_prepare_sn.sortition_id,
"reward_cycle_info" => format!("{:?}", &reward_cycle_info)
);
SortitionDB::store_preprocessed_reward_set(
&mut tx,
&first_prepare_sn.sortition_id,
Expand Down
Loading