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/shadow block tooling #5362

Open
wants to merge 30 commits into
base: fix/5285
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1597209
chore: add nakamoto testnet pox settings
jcnelson Oct 23, 2024
fbec882
chore: expose NakamotoBlockBuilder::get_account()
jcnelson Oct 23, 2024
7e26c01
feat: add `get-account`, `get-nakamoto-tip`, `make-shadow-block`, and…
jcnelson Oct 23, 2024
9bcee64
fix: use correct pox params
jcnelson Oct 24, 2024
850d00b
chore: move shadow block creation and replay into separate functions …
jcnelson Oct 24, 2024
fb391fc
chore: make function public
jcnelson Oct 24, 2024
0f29c68
chore: shadow-chainstate-repair and shadow-chainstate-patch
jcnelson Oct 24, 2024
e8da151
fix: the parent can be a shadow block and thus not have a commit
jcnelson Oct 24, 2024
4851c3c
chore: integration test
jcnelson Oct 24, 2024
8fff6bd
chore: run integration test in CI
jcnelson Oct 24, 2024
05ac928
Merge branch 'fix/5285' into feat/shadow-block-tooling
jcnelson Oct 24, 2024
05a99fa
chore: typo
jcnelson Oct 24, 2024
bfbfbf7
chore: cargo fmt
jcnelson Oct 24, 2024
8743b0b
chore: fix compile issue
jcnelson Oct 25, 2024
84c982c
Adding stacks-signer binary to image
wileyj Oct 25, 2024
c75ed39
chore: read/write
jcnelson Oct 25, 2024
b98239d
fix: load blocks json from file
jcnelson Oct 25, 2024
4153cec
Merge branch 'feat/shadow-block-tooling' of https://github.com/stacks…
jcnelson Oct 25, 2024
ca17ede
chore: don't use docstring
jcnelson Oct 25, 2024
439abdb
fix: accomodate shadow blocks which have no sortition but do have a s…
jcnelson Oct 25, 2024
69eb7ac
fix: use a real signer to verify that we can resume mining atop shado…
jcnelson Oct 25, 2024
3430a81
fix: advance cursor before depth check
jcnelson Oct 28, 2024
1578014
chore: handle shadow block case
jcnelson Oct 28, 2024
0ace927
chore: log HTTP RPC error values at debug level
jcnelson Oct 28, 2024
7bd1ae8
chore: info when post-shadow tenures happen
jcnelson Oct 28, 2024
7341a4b
chore: cargo fmt
jcnelson Oct 28, 2024
b30c0b4
chore: halt block-processing while we process shadow blocks
jcnelson Oct 28, 2024
5970a57
Merge branch 'fix/5285' into feat/shadow-block-tooling
jcnelson Nov 8, 2024
5f83847
chore: search depth only increases with sortitions, not shadow blocks
jcnelson Nov 9, 2024
ad01a76
Merge branch 'fix/5285' into feat/shadow-block-tooling
jcnelson Nov 11, 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
2 changes: 1 addition & 1 deletion .github/actions/dockerfiles/Dockerfile.debian-source
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ RUN --mount=type=tmpfs,target=${BUILD_DIR} cp -R /src/. ${BUILD_DIR}/ \
&& cp -R ${BUILD_DIR}/target/${TARGET}/release/. /out

FROM --platform=${TARGETPLATFORM} debian:bookworm
COPY --from=build /out/stacks-node /out/stacks-signer /bin/
COPY --from=build /out/stacks-node /out/stacks-signer /out/stacks-inspect /bin/
CMD ["stacks-node", "mainnet"]
1 change: 1 addition & 0 deletions .github/workflows/bitcoin-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ jobs:
- tests::nakamoto_integrations::utxo_check_on_startup_panic
- tests::nakamoto_integrations::utxo_check_on_startup_recover
- tests::nakamoto_integrations::v3_signer_api_endpoint
- tests::nakamoto_integrations::test_shadow_recovery
# TODO: enable these once v1 signer is supported by a new nakamoto epoch
# - tests::signer::v1::dkg
# - tests::signer::v1::sign_request_rejected
Expand Down
1 change: 1 addition & 0 deletions stackslib/src/burnchains/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ impl PoxConstants {
)
}

// NOTE: this is the *old* pre-Nakamoto testnet
pub fn testnet_default() -> PoxConstants {
PoxConstants::new(
POX_REWARD_CYCLE_LENGTH / 2, // 1050
Expand Down
20 changes: 20 additions & 0 deletions stackslib/src/chainstate/nakamoto/coordinator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ use crate::monitoring::increment_stx_blocks_processed_counter;
use crate::net::Error as NetError;
use crate::util_lib::db::Error as DBError;

#[cfg(any(test, feature = "testing"))]
pub static TEST_COORDINATOR_STALL: std::sync::Mutex<Option<bool>> = std::sync::Mutex::new(None);

#[cfg(test)]
pub mod tests;

Expand Down Expand Up @@ -764,6 +767,21 @@ impl<
true
}

#[cfg(any(test, feature = "testing"))]
fn fault_injection_pause_nakamoto_block_processing() {
if *TEST_COORDINATOR_STALL.lock().unwrap() == Some(true) {
// Do an extra check just so we don't log EVERY time.
warn!("Coordinator is stalled due to testing directive");
while *TEST_COORDINATOR_STALL.lock().unwrap() == Some(true) {
std::thread::sleep(std::time::Duration::from_millis(10));
}
warn!("Coordinator is no longer stalled due to testing directive. Continuing...");
}
}

#[cfg(not(any(test, feature = "testing")))]
fn fault_injection_pause_nakamoto_block_processing() {}

/// Handle one or more new Nakamoto Stacks blocks.
/// If we process a PoX anchor block, then return its block hash. This unblocks processing the
/// next reward cycle's burnchain blocks. Subsequent calls to this function will terminate
Expand All @@ -776,6 +794,8 @@ impl<
);

loop {
Self::fault_injection_pause_nakamoto_block_processing();

// process at most one block per loop pass
let mut processed_block_receipt = match NakamotoChainState::process_next_nakamoto_block(
&mut self.chain_state_db,
Expand Down
9 changes: 3 additions & 6 deletions stackslib/src/chainstate/nakamoto/miner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,11 +401,11 @@ impl NakamotoBlockBuilder {
burn_dbconn: &'a SortitionHandleConn,
info: &'b mut MinerTenureInfo<'a>,
) -> Result<ClarityTx<'b, 'b>, Error> {
if info.tenure_block_commit_opt.is_none() {
let Some(block_commit) = info.tenure_block_commit_opt.as_ref() else {
return Err(Error::InvalidStacksBlock(
"Block-commit is required; cannot mine a shadow block".into(),
));
}
};

let SetupBlockResult {
clarity_tx,
Expand All @@ -426,10 +426,7 @@ impl NakamotoBlockBuilder {
info.coinbase_height,
info.cause == Some(TenureChangeCause::Extended),
&self.header.pox_treatment,
// safety: checked above
info.tenure_block_commit_opt
.as_ref()
.unwrap_or_else(|| panic!("FATAL: no block-commit for normal Nakamoto block")),
block_commit,
&info.active_reward_set,
)?;
self.matured_miner_rewards_opt = matured_miner_rewards_opt;
Expand Down
146 changes: 141 additions & 5 deletions stackslib/src/chainstate/nakamoto/shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ use crate::chainstate::nakamoto::{
SortitionHandleConn, StacksDBIndexed,
};
use crate::chainstate::stacks::boot::RewardSet;
use crate::chainstate::stacks::db::blocks::DummyEventDispatcher;
use crate::chainstate::stacks::db::{
ChainstateTx, ClarityTx, StacksAccount, StacksChainState, StacksHeaderInfo,
};
Expand All @@ -70,6 +71,7 @@ use crate::chainstate::stacks::{
use crate::clarity::vm::types::StacksAddressExtensions;
use crate::clarity_vm::clarity::ClarityInstance;
use crate::clarity_vm::database::SortitionDBRef;
use crate::net::Error as NetError;
use crate::util_lib::db::{query_row, u64_to_sql, Error as DBError};

impl NakamotoBlockHeader {
Expand Down Expand Up @@ -461,7 +463,7 @@ impl NakamotoBlockBuilder {
}

/// Get an address's account
fn get_account(
pub fn get_account(
chainstate: &mut StacksChainState,
sortdb: &SortitionDB,
addr: &StacksAddress,
Expand Down Expand Up @@ -561,13 +563,17 @@ impl NakamotoBlockBuilder {
}
let block = builder.mine_nakamoto_block(&mut tenure_tx);
let size = builder.bytes_so_far;
let cost = builder.tenure_finish(tenure_tx).unwrap();
let cost = builder.tenure_finish(tenure_tx)?;
Ok((block, size, cost))
}

/// Produce a single-block shadow tenure.
/// Used by tooling to synthesize shadow blocks in case of an emergency.
/// The details and circumstances will be recorded in an accompanying SIP.
/// The details and circumatances will be recorded in an accompanying SIP.
///
/// `naka_tip_id` is the Stacks chain tip on top of which the shadow block will be built.
/// `tenure_id_consensus_hash` is the sortition in which the shadow block will be built.
/// `txs` are transactions to include, beyond a coinbase and tenure-change
pub fn make_shadow_tenure(
chainstate: &mut StacksChainState,
sortdb: &SortitionDB,
Expand Down Expand Up @@ -704,8 +710,7 @@ impl NakamotoBlockBuilder {
Some(&tenure_change_tx),
Some(&coinbase_tx),
1,
)
.unwrap();
)?;

let mut block_txs = vec![tenure_change_tx, coinbase_tx];
block_txs.append(&mut txs);
Expand Down Expand Up @@ -852,3 +857,134 @@ impl<'a> NakamotoStagingBlocksTx<'a> {
Ok(())
}
}

/// DO NOT RUN ON A RUNNING NODE (unless you're testing).
///
/// Insert and process a shadow block into the Stacks chainstate.
pub fn process_shadow_block(
chain_state: &mut StacksChainState,
sort_db: &mut SortitionDB,
shadow_block: NakamotoBlock,
) -> Result<(), ChainstateError> {
let tx = chain_state.staging_db_tx_begin()?;
tx.add_shadow_block(&shadow_block)?;
tx.commit()?;

let no_dispatch: Option<DummyEventDispatcher> = None;
loop {
let sort_tip = SortitionDB::get_canonical_burn_chain_tip(sort_db.conn())?;

// process at most one block per loop pass
let processed_block_receipt = match NakamotoChainState::process_next_nakamoto_block(
chain_state,
sort_db,
&sort_tip.sortition_id,
no_dispatch.as_ref(),
) {
Ok(receipt_opt) => receipt_opt,
Err(ChainstateError::InvalidStacksBlock(msg)) => {
warn!("Encountered invalid block: {}", &msg);
continue;
}
Err(ChainstateError::NetError(NetError::DeserializeError(msg))) => {
// happens if we load a zero-sized block (i.e. an invalid block)
warn!("Encountered invalid block (codec error): {}", &msg);
continue;
}
Err(e) => {
// something else happened
return Err(e.into());
}
};

if processed_block_receipt.is_none() {
// out of blocks
info!("No more blocks to process (no receipts)");
break;
};

let Some((_, processed, orphaned, _)) = chain_state
.nakamoto_blocks_db()
.get_block_processed_and_signed_weight(
&shadow_block.header.consensus_hash,
&shadow_block.header.block_hash(),
)?
else {
return Err(ChainstateError::InvalidStacksBlock(format!(
"Shadow block {} for tenure {} not store",
&shadow_block.block_id(),
&shadow_block.header.consensus_hash
)));
};

if orphaned {
return Err(ChainstateError::InvalidStacksBlock(format!(
"Shadow block {} for tenure {} was orphaned",
&shadow_block.block_id(),
&shadow_block.header.consensus_hash
)));
}

if processed {
break;
}
}
Ok(())
}

/// DO NOT RUN ON A RUNNING NODE (unless you're testing).
///
/// Automatically repair a node that has been stalled due to an empty prepare phase.
/// Works by synthesizing, inserting, and processing shadow tenures in-between the last sortition
/// with a winner and the burnchain tip.
///
/// This is meant to be accessed by the tooling. Once the blocks are synthesized, they would be
/// added into other broken nodes' chainstates by the same tooling. Ultimately, a patched node
/// would be released with these shadow blocks added in as part of the chainstate schema.
///
/// Returns the syntheisized shadow blocks on success.
/// Returns error on failure.
pub fn shadow_chainstate_repair(
chain_state: &mut StacksChainState,
sort_db: &mut SortitionDB,
) -> Result<Vec<NakamotoBlock>, ChainstateError> {
let sort_tip = SortitionDB::get_canonical_burn_chain_tip(sort_db.conn())?;

let header = NakamotoChainState::get_canonical_block_header(chain_state.db(), &sort_db)?
.ok_or_else(|| ChainstateError::NoSuchBlockError)?;

let header_sn =
SortitionDB::get_block_snapshot_consensus(sort_db.conn(), &header.consensus_hash)?
.ok_or_else(|| {
ChainstateError::InvalidStacksBlock(
"Canonical stacks header does not have a sortition".into(),
)
})?;

let mut shadow_blocks = vec![];
for burn_height in (header_sn.block_height + 1)..sort_tip.block_height {
let sort_tip = SortitionDB::get_canonical_burn_chain_tip(sort_db.conn())?;
let sort_handle = sort_db.index_handle(&sort_tip.sortition_id);
let sn = sort_handle
.get_block_snapshot_by_height(burn_height)?
.ok_or_else(|| ChainstateError::InvalidStacksBlock("No sortition at height".into()))?;

let header = NakamotoChainState::get_canonical_block_header(chain_state.db(), &sort_db)?
.ok_or_else(|| ChainstateError::NoSuchBlockError)?;

let chain_tip = header.index_block_hash();
let shadow_block = NakamotoBlockBuilder::make_shadow_tenure(
chain_state,
sort_db,
chain_tip.clone(),
sn.consensus_hash,
vec![],
)?;

shadow_blocks.push(shadow_block.clone());

process_shadow_block(chain_state, sort_db, shadow_block)?;
}

Ok(shadow_blocks)
}
2 changes: 1 addition & 1 deletion stackslib/src/chainstate/nakamoto/staging_blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ impl<'a> NakamotoStagingBlocksConnRef<'a> {
/// There will be at most one such block.
///
/// NOTE: for Nakamoto blocks, the sighash is the same as the block hash.
pub(crate) fn get_block_processed_and_signed_weight(
pub fn get_block_processed_and_signed_weight(
&self,
consensus_hash: &ConsensusHash,
block_hash: &BlockHeaderHash,
Expand Down
Loading
Loading