Skip to content

Commit

Permalink
Merge pull request #4232 from stacks-network/feat/nakamoto-block-inv
Browse files Browse the repository at this point in the history
Nakamoto Tenure Inventories
  • Loading branch information
jcnelson authored Feb 2, 2024
2 parents 074fd76 + 22a4567 commit 86e3c26
Show file tree
Hide file tree
Showing 21 changed files with 4,399 additions and 2,152 deletions.
9 changes: 0 additions & 9 deletions stackslib/src/chainstate/burn/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ pub enum Opcodes {
PreStx = 'p' as u8,
TransferStx = '$' as u8,
DelegateStx = '#' as u8,
PegIn = '<' as u8,
PegOutRequest = '>' as u8,
PegOutFulfill = '!' as u8,
}

// a burnchain block snapshot
Expand Down Expand Up @@ -205,17 +202,11 @@ impl Opcodes {
Opcodes::PreStx => Self::HTTP_PRE_STX,
Opcodes::TransferStx => Self::HTTP_TRANSFER_STX,
Opcodes::DelegateStx => Self::HTTP_DELEGATE_STX,
Opcodes::PegIn => Self::HTTP_PEG_IN,
Opcodes::PegOutRequest => Self::HTTP_PEG_OUT_REQUEST,
Opcodes::PegOutFulfill => Self::HTTP_PEG_OUT_FULFILL,
}
}

pub fn from_http_str(input: &str) -> Option<Opcodes> {
let opcode = match input {
Self::HTTP_PEG_IN => Opcodes::PegIn,
Self::HTTP_PEG_OUT_REQUEST => Opcodes::PegOutRequest,
Self::HTTP_PEG_OUT_FULFILL => Opcodes::PegOutFulfill,
Self::HTTP_BLOCK_COMMIT => Opcodes::LeaderBlockCommit,
Self::HTTP_KEY_REGISTER => Opcodes::LeaderKeyRegister,
Self::HTTP_BURN_SUPPORT => Opcodes::UserBurnSupport,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -682,9 +682,9 @@ impl LeaderBlockCommitOp {
op_error::BlockCommitAnchorCheck})?;
if descended_from_anchor != expect_pox_descendant {
if descended_from_anchor {
warn!("Invalid block commit: descended from PoX anchor, but used burn outputs");
warn!("Invalid block commit: descended from PoX anchor {}, but used burn outputs", &reward_set_info.anchor_block);
} else {
warn!("Invalid block commit: not descended from PoX anchor, but used PoX outputs");
warn!("Invalid block commit: not descended from PoX anchor {}, but used PoX outputs", &reward_set_info.anchor_block);
}
return Err(op_error::BlockCommitBadOutputs);
}
Expand Down
34 changes: 26 additions & 8 deletions stackslib/src/chainstate/nakamoto/coordinator/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ pub fn boot_nakamoto<'a>(
}

/// Make a replay peer, used for replaying the blockchain
fn make_replay_peer<'a>(peer: &'a mut TestPeer<'a>) -> TestPeer<'a> {
fn make_replay_peer<'a>(peer: &mut TestPeer<'a>) -> TestPeer<'a> {
let mut replay_config = peer.config.clone();
replay_config.test_name = format!("{}.replay", &peer.config.test_name);
replay_config.server_port = 0;
Expand Down Expand Up @@ -955,8 +955,7 @@ fn test_nakamoto_chainstate_getters() {

/// Mine a 10 Nakamoto tenures with between 1 and 10 Nakamoto blocks each.
/// Checks the matured mining rewards as well.
#[test]
fn test_simple_nakamoto_coordinator_10_tenures_10_blocks() {
pub fn simple_nakamoto_coordinator_10_tenures_10_sortitions<'a>() -> TestPeer<'a> {
let private_key = StacksPrivateKey::from_seed(&[2]);
let addr = StacksAddress::from_public_keys(
C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
Expand Down Expand Up @@ -1246,6 +1245,7 @@ fn test_simple_nakamoto_coordinator_10_tenures_10_blocks() {
)
}
}

// replay the blocks and sortitions in random order, and verify that we still reach the chain
// tip
let mut replay_peer = make_replay_peer(&mut peer);
Expand All @@ -1272,15 +1272,20 @@ fn test_simple_nakamoto_coordinator_10_tenures_10_blocks() {
tip.anchored_header.as_stacks_nakamoto().unwrap(),
&rc_blocks.last().unwrap().last().unwrap().header
);
return peer;
}

#[test]
fn test_nakamoto_coordinator_10_tenures_10_sortitions() {
simple_nakamoto_coordinator_10_tenures_10_sortitions();
}

/// Mine two tenures across three sortitions, using a tenure-extend to allow the first tenure to
/// cover the time of two sortitions.
///
/// Use a tenure-extend to grant the miner of the first tenure the ability to mine
/// 20 blocks in the first tenure (10 before the second sortiton, and 10 after)
#[test]
fn test_simple_nakamoto_coordinator_2_tenures_3_sortitions() {
pub fn simple_nakamoto_coordinator_2_tenures_3_sortitions<'a>() -> TestPeer<'a> {
let private_key = StacksPrivateKey::from_seed(&[2]);
let addr = StacksAddress::from_public_keys(
C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
Expand Down Expand Up @@ -1606,11 +1611,17 @@ fn test_simple_nakamoto_coordinator_2_tenures_3_sortitions() {
tip.anchored_header.as_stacks_nakamoto().unwrap(),
&blocks.last().unwrap().header
);

return peer;
}

/// Mine a 10 Nakamoto tenures with 10 Nakamoto blocks, but do a tenure-extend in each block
#[test]
fn test_simple_nakamoto_coordinator_10_tenures_and_extensions_10_blocks() {
fn test_nakamoto_coordinator_2_tenures_3_sortitions() {
simple_nakamoto_coordinator_2_tenures_3_sortitions();
}

/// Mine a 10 Nakamoto tenures with 10 Nakamoto blocks, but do a tenure-extend in each block
pub fn simple_nakamoto_coordinator_10_extended_tenures_10_sortitions() -> TestPeer<'static> {
let private_key = StacksPrivateKey::from_seed(&[2]);
let addr = StacksAddress::from_public_keys(
C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
Expand Down Expand Up @@ -1727,7 +1738,7 @@ fn test_simple_nakamoto_coordinator_10_tenures_and_extensions_10_blocks() {
assert!(last_block.header.consensus_hash == sort_tip.consensus_hash);
assert_eq!(highest_tenure.coinbase_height, 12 + i);
assert_eq!(highest_tenure.cause, TenureChangeCause::Extended);
assert_eq!(highest_tenure.tenure_index, 8 * (i + 1));
assert_eq!(highest_tenure.tenure_index, 10 * (i + 1));
assert_eq!(
highest_tenure.num_blocks_confirmed,
(blocks.len() as u32) - 1
Expand Down Expand Up @@ -1865,4 +1876,11 @@ fn test_simple_nakamoto_coordinator_10_tenures_and_extensions_10_blocks() {
tip.anchored_header.as_stacks_nakamoto().unwrap(),
&rc_blocks.last().unwrap().last().unwrap().header
);

return peer;
}

#[test]
fn test_nakamoto_coordinator_10_tenures_and_extensions_10_blocks() {
simple_nakamoto_coordinator_10_extended_tenures_10_sortitions();
}
16 changes: 16 additions & 0 deletions stackslib/src/chainstate/nakamoto/tenure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,22 @@ impl NakamotoChainState {
}
}

/// Get a nakamoto tenure-change by its tenure ID consensus hash.
/// Get the highest such record. It will be the last-processed BlockFound tenure
/// for the given sortition consensus hash.
pub fn get_highest_nakamoto_tenure_change_by_tenure_id(
headers_conn: &Connection,
tenure_id_consensus_hash: &ConsensusHash,
) -> Result<Option<NakamotoTenure>, ChainstateError> {
let sql = "SELECT * FROM nakamoto_tenures WHERE tenure_id_consensus_hash = ?1 AND cause = ?2 ORDER BY tenure_index DESC LIMIT 1";
let args: &[&dyn ToSql] = &[
tenure_id_consensus_hash,
&TenureChangeCause::BlockFound.as_u8(),
];
let tenure_opt: Option<NakamotoTenure> = query_row(headers_conn, sql, args)?;
Ok(tenure_opt)
}

/// Get the highest processed tenure on the canonical sortition history.
pub fn get_highest_nakamoto_tenure(
headers_conn: &Connection,
Expand Down
88 changes: 60 additions & 28 deletions stackslib/src/chainstate/nakamoto/tests/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,6 @@ impl TestMiner {
tx_tenure_change.anchor_mode = TransactionAnchorMode::OnChainOnly;
tx_tenure_change.auth.set_origin_nonce(self.nonce);

// TODO: This needs to be changed to an aggregate signature from the stackers
let mut tx_signer = StacksTransactionSigner::new(&tx_tenure_change);
self.sign_as_origin(&mut tx_signer);
let tx_tenure_change_signed = tx_signer.get_tx().unwrap();
Expand Down Expand Up @@ -289,9 +288,22 @@ impl TestStacksNode {
None => None,
Some(block_commit_op) => {
let last_tenure_id = block_commit_op.last_tenure_id();
debug!(
"Last block commit was for {}: {:?}",
&last_tenure_id, &block_commit_op
);
match self.nakamoto_commit_ops.get(&last_tenure_id) {
None => None,
Some(idx) => self.nakamoto_blocks.get(*idx).cloned(),
None => {
debug!("No Nakamoto index for {}", &last_tenure_id);
None
}
Some(idx) => match self.nakamoto_blocks.get(*idx) {
Some(nakamoto_blocks) => Some(nakamoto_blocks.clone()),
None => {
debug!("Nakamoto block index {} does not correspond to list of mined nakamoto tenures (len {})", idx, self.nakamoto_blocks.len());
None
}
},
}
}
}
Expand Down Expand Up @@ -319,6 +331,7 @@ impl TestStacksNode {
burn_amount: u64,
miner_key: &LeaderKeyRegisterOp,
parent_block_snapshot_opt: Option<&BlockSnapshot>,
expect_success: bool,
) -> LeaderBlockCommitOp {
test_debug!(
"Miner {}: Commit to Nakamoto tenure starting at {}",
Expand Down Expand Up @@ -357,18 +370,25 @@ impl TestStacksNode {
);

test_debug!(
"Miner {}: Nakamoto tenure commit transaction builds on {},{} (parent snapshot is {:?})",
"Miner {}: Nakamoto tenure commit transaction builds on {},{} (parent snapshot is {:?}). Expect success? {}",
miner.id,
block_commit_op.parent_block_ptr,
block_commit_op.parent_vtxindex,
&parent_block_snapshot_opt
&parent_block_snapshot_opt,
expect_success
);

// NOTE: self.nakamoto_commit_ops[block_header_hash] now contains an index into
// self.nakamoto_blocks that doesn't exist. The caller needs to follow this call with a
// call to self.add_nakamoto_tenure_blocks()
self.nakamoto_commit_ops
.insert(last_tenure_id.clone(), self.nakamoto_blocks.len());
if expect_success {
// NOTE: self.nakamoto_commit_ops[block_header_hash] now contains an index into
// self.nakamoto_blocks that doesn't exist. The caller needs to follow this call with a
// call to self.add_nakamoto_tenure_blocks()
self.nakamoto_commit_ops
.insert(last_tenure_id.clone(), self.nakamoto_blocks.len());
} else {
// this extends the last tenure
self.nakamoto_commit_ops
.insert(last_tenure_id.clone(), self.nakamoto_blocks.len() - 1);
}
block_commit_op
}

Expand Down Expand Up @@ -480,6 +500,7 @@ impl TestStacksNode {
&hdr.consensus_hash,
)
.unwrap();
debug!("Tenure length of {} is {}", &hdr.consensus_hash, tenure_len);
(hdr.index_block_hash(), hdr.consensus_hash, tenure_len)
} else {
// building atop epoch2
Expand Down Expand Up @@ -509,6 +530,7 @@ impl TestStacksNode {
burn_amount,
miner_key,
Some(&parent_block_snapshot),
tenure_change_cause == TenureChangeCause::BlockFound,
);

(block_commit_op, tenure_change_payload)
Expand Down Expand Up @@ -544,10 +566,6 @@ impl TestStacksNode {
&[(NakamotoBlock, u64, ExecutionCost)],
) -> Vec<StacksTransaction>,
{
let miner_addr = miner.origin_address().unwrap();
let miner_account = get_account(chainstate, sortdb, &miner_addr);
miner.set_nonce(miner_account.nonce);

let mut blocks = vec![];
let mut block_count = 0;
loop {
Expand Down Expand Up @@ -752,25 +770,38 @@ impl<'a> TestPeer<'a> {
) {
let tip = SortitionDB::get_canonical_burn_chain_tip(sortdb.conn()).unwrap();
if let Some(parent_blocks) = stacks_node.get_last_nakamoto_tenure(miner) {
debug!("Parent will be a Nakamoto block");

// parent is an epoch 3 nakamoto block
let first_parent = parent_blocks.first().unwrap();
let parent_tenure_id = StacksBlockId::new(
debug!("First parent is {:?}", first_parent);

let first_parent_sn = SortitionDB::get_block_snapshot_consensus(
sortdb.conn(),
&first_parent.header.consensus_hash,
&first_parent.header.block_hash(),
);
let ic = sortdb.index_conn();
let parent_sortition_opt = SortitionDB::get_block_snapshot_for_winning_nakamoto_tenure(
&ic,
&tip.sortition_id,
&parent_tenure_id,
)
.unwrap()
.unwrap();
if parent_sortition_opt.is_none() {
warn!(
"No parent sortition: tip.sortition_id = {}, parent_tenure_id = {}",
&tip.sortition_id, &parent_tenure_id
);
}

assert!(first_parent_sn.sortition);

let parent_sortition_id = SortitionDB::get_block_commit_parent_sortition_id(
sortdb.conn(),
&first_parent_sn.winning_block_txid,
&first_parent_sn.sortition_id,
)
.unwrap()
.unwrap();
let parent_sortition =
SortitionDB::get_block_snapshot(sortdb.conn(), &parent_sortition_id)
.unwrap()
.unwrap();

debug!(
"First parent Nakamoto block sortition: {:?}",
&parent_sortition
);
let parent_sortition_opt = Some(parent_sortition);

let last_tenure_id = StacksBlockId::new(
&first_parent.header.consensus_hash,
Expand All @@ -787,6 +818,7 @@ impl<'a> TestPeer<'a> {
let (parent_opt, parent_sortition_opt) = if let Some(parent_block) =
stacks_node.get_last_anchored_block(miner)
{
debug!("Parent will be a Stacks 2.x block");
let ic = sortdb.index_conn();
let sort_opt = SortitionDB::get_block_snapshot_for_winning_stacks_block(
&ic,
Expand Down
5 changes: 4 additions & 1 deletion stackslib/src/chainstate/stacks/db/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1409,7 +1409,10 @@ impl StacksChainState {
TenureChangeCause::Extended => {
// the stackers granted a tenure extension.
// reset the runtime cost
debug!("TenureChange extends block tenure");
debug!(
"TenureChange extends block tenure (confirms {} blocks)",
&payload.previous_tenure_blocks
);
}
}

Expand Down
21 changes: 18 additions & 3 deletions stackslib/src/chainstate/stacks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,10 +768,25 @@ impl TransactionPayload {
match self {
TransactionPayload::TokenTransfer(..) => "TokenTransfer",
TransactionPayload::ContractCall(..) => "ContractCall",
TransactionPayload::SmartContract(..) => "SmartContract",
TransactionPayload::SmartContract(_, version_opt) => {
if version_opt.is_some() {
"SmartContract(Versioned)"
} else {
"SmartContract"
}
}
TransactionPayload::PoisonMicroblock(..) => "PoisonMicroblock",
TransactionPayload::Coinbase(..) => "Coinbase",
TransactionPayload::TenureChange(..) => "TenureChange",
TransactionPayload::Coinbase(_, _, vrf_opt) => {
if vrf_opt.is_some() {
"Coinbase(Nakamoto)"
} else {
"Coinbase"
}
}
TransactionPayload::TenureChange(payload) => match payload.cause {
TenureChangeCause::BlockFound => "TenureChange(BlockFound)",
TenureChangeCause::Extended => "TenureChange(Extension)",
},
}
}
}
Expand Down
1 change: 0 additions & 1 deletion stackslib/src/net/api/tests/liststackerdbreplicas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ fn test_try_make_response() {

let naddr = resp.last().clone().unwrap();
assert_eq!(naddr.addrbytes, PeerAddress::from_ipv4(127, 0, 0, 1));
assert_eq!(naddr.port, 0);
assert_eq!(
naddr.public_key_hash,
Hash160::from_hex("9b92533ccc243e25eb6197bd03c9164642c7c8a8").unwrap()
Expand Down
Loading

0 comments on commit 86e3c26

Please sign in to comment.