Skip to content

Commit

Permalink
Merge pull request #4781 from stacks-network/feat/header-signer-signa…
Browse files Browse the repository at this point in the history
…tures

modify nakamoto block header to use `Vec<MessageSignature>`
  • Loading branch information
kantai authored Jun 4, 2024
2 parents aede203 + 7c8df31 commit 690701f
Show file tree
Hide file tree
Showing 36 changed files with 2,250 additions and 666 deletions.
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

0 comments on commit 690701f

Please sign in to comment.