Skip to content

Commit 5bf852e

Browse files
committed
Merge remote-tracking branch 'origin/develop' into feat/monitor-signers-cli-command
1 parent 70c8656 commit 5bf852e

File tree

17 files changed

+1175
-407
lines changed

17 files changed

+1175
-407
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,13 @@ jobs:
7575
- tests::neon_integrations::vote_for_aggregate_key_burn_op_test
7676
- tests::neon_integrations::mock_miner_replay
7777
- tests::neon_integrations::listunspent_max_utxos
78+
- tests::neon_integrations::bitcoin_reorg_flap
79+
- tests::neon_integrations::bitcoin_reorg_flap_with_follower
80+
- tests::neon_integrations::start_stop_bitcoind
7881
- tests::epoch_25::microblocks_disabled
7982
- tests::should_succeed_handling_malformed_and_valid_txs
8083
- tests::nakamoto_integrations::simple_neon_integration
84+
- tests::nakamoto_integrations::simple_neon_integration_with_flash_blocks_on_epoch_3
8185
- tests::nakamoto_integrations::mine_multiple_per_tenure_integration
8286
- tests::nakamoto_integrations::block_proposal_api_endpoint
8387
- tests::nakamoto_integrations::miner_writes_proposed_block_to_stackerdb
@@ -122,9 +126,6 @@ jobs:
122126
- tests::nakamoto_integrations::follower_bootup_across_multiple_cycles
123127
- tests::nakamoto_integrations::utxo_check_on_startup_panic
124128
- tests::nakamoto_integrations::utxo_check_on_startup_recover
125-
# Do not run this one until we figure out why it fails in CI
126-
# - tests::neon_integrations::bitcoin_reorg_flap
127-
# - tests::neon_integrations::bitcoin_reorg_flap_with_follower
128129
# TODO: enable these once v1 signer is supported by a new nakamoto epoch
129130
# - tests::signer::v1::dkg
130131
# - tests::signer::v1::sign_request_rejected
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[
2+
{
3+
"burn_block_hash": "0x046f54cd1924a5d80fc3b8186d0334b7521acae90f9e136e2bee680c720d0e83",
4+
"burn_block_height": 231,
5+
"burn_header_timestamp": 1726797570,
6+
"sortition_id": "0x8a5116b7b4306dc4f6db290d1adfff9e1347f3e921bb793fc4c33e2ff05056e2",
7+
"parent_sortition_id": "0xdaf479110cf859e58c56b6ae941f8a14e7c7992c57027183dfbda4a4b820897c",
8+
"consensus_hash": "0x8d2c51db737597a93191f49bcdc9c7bb44b90892",
9+
"was_sortition": true,
10+
"miner_pk_hash160": "0x6bc51b33e9f3626944eb879147e18111581f8f9b",
11+
"stacks_parent_ch": "0x697357c72da55b759b1d6b721676c92c69f0b490",
12+
"last_sortition_ch": "0x697357c72da55b759b1d6b721676c92c69f0b490",
13+
"committed_block_hash": "0xeea47d6d639c565027110e192e308fb11656183d5c077bcd718d830652800183"
14+
}
15+
]
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
[
2+
{
3+
"burn_block_hash": "0x046f54cd1924a5d80fc3b8186d0334b7521acae90f9e136e2bee680c720d0e83",
4+
"burn_block_height": 231,
5+
"burn_header_timestamp": 1726797570,
6+
"sortition_id": "0x8a5116b7b4306dc4f6db290d1adfff9e1347f3e921bb793fc4c33e2ff05056e2",
7+
"parent_sortition_id": "0xdaf479110cf859e58c56b6ae941f8a14e7c7992c57027183dfbda4a4b820897c",
8+
"consensus_hash": "0x8d2c51db737597a93191f49bcdc9c7bb44b90892",
9+
"was_sortition": true,
10+
"miner_pk_hash160": "0x6bc51b33e9f3626944eb879147e18111581f8f9b",
11+
"stacks_parent_ch": "0x697357c72da55b759b1d6b721676c92c69f0b490",
12+
"last_sortition_ch": "0x697357c72da55b759b1d6b721676c92c69f0b490",
13+
"committed_block_hash": "0xeea47d6d639c565027110e192e308fb11656183d5c077bcd718d830652800183"
14+
},
15+
{
16+
"burn_block_hash": "0x496ff02cb63a4850d0bdee5fab69284b6eb0392b4538e1c462f82362c5becfa4",
17+
"burn_block_height": 230,
18+
"burn_header_timestamp": 1726797570,
19+
"sortition_id": "0xdaf479110cf859e58c56b6ae941f8a14e7c7992c57027183dfbda4a4b820897c",
20+
"parent_sortition_id": "0xf9058692055cbd879d7f71e566e44b905a887b2b182407ed596b5d6499ceae2a",
21+
"consensus_hash": "0x697357c72da55b759b1d6b721676c92c69f0b490",
22+
"was_sortition": true,
23+
"miner_pk_hash160": "0x6bc51b33e9f3626944eb879147e18111581f8f9b",
24+
"stacks_parent_ch": "0xf7d1bd7d9d5c5a5c368402b6ef9510bd014d70f7",
25+
"last_sortition_ch": "0xf7d1bd7d9d5c5a5c368402b6ef9510bd014d70f7",
26+
"committed_block_hash": "0x36ee5f7f7271de1c1d4cd830e36320b51e01605547621267ae6e9b4e9b10f95e"
27+
}
28+
]

docs/rpc/openapi.yaml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,3 +675,44 @@ paths:
675675
schema:
676676
type: string
677677

678+
/v3/sortitions/{lookup_kind}/{lookup}:
679+
get:
680+
summary: Fetch information about evaluated burnchain blocks (i.e., sortitions).
681+
tags:
682+
- Blocks
683+
operationId: get_sortitions
684+
description:
685+
Fetch sortition information about a burnchain block. If the `lookup_kind` and `lookup` parameters are empty, it will return information about the latest burn block.
686+
responses:
687+
"200":
688+
description: Information for the burn block or in the case of `latest_and_last`, multiple burn blocks
689+
content:
690+
application/json:
691+
examples:
692+
Latest:
693+
description: A single element list is returned when just one sortition is requested
694+
value:
695+
$ref: ./api/core-node/get_sortitions.example.json
696+
LatestAndLast:
697+
description: Sortition information about the latest burn block with a winning miner, and the previous such burn block.
698+
value:
699+
$ref: ./api/core-node/get_sortitions_latest_and_prior.example.json
700+
parameters:
701+
- name: lookup_kind
702+
in: path
703+
description: |-
704+
The style of lookup that should be performed. If not given, the most recent burn block processed will be returned.
705+
Otherwise, the `lookup_kind` should be one of the following strings:
706+
* `consensus` - find the burn block using the consensus hash supplied in the `lookup` field.
707+
* `burn_height` - find the burn block using the burn block height supplied in the `lookup` field.
708+
* `burn` - find the burn block using the burn block hash supplied in the `lookup` field.
709+
* `latest_and_last` - return information about the latest burn block with a winning miner *and* the previous such burn block
710+
required: false
711+
schema:
712+
type: string
713+
- name: lookup
714+
in: path
715+
description: The value to use for the lookup if `lookup_kind` is `consensus`, `burn_height`, or `burn`
716+
required: false
717+
schema:
718+
type: string

stacks-signer/src/chainstate.rs

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use stacks_common::types::chainstate::{ConsensusHash, StacksPublicKey};
2525
use stacks_common::util::hash::Hash160;
2626
use stacks_common::{info, warn};
2727

28-
use crate::client::{ClientError, StacksClient};
28+
use crate::client::{ClientError, CurrentAndLastSortition, StacksClient};
2929
use crate::config::SignerConfig;
3030
use crate::signerdb::{BlockState, SignerDb};
3131

@@ -138,8 +138,6 @@ pub struct SortitionsView {
138138
pub last_sortition: Option<SortitionState>,
139139
/// the current successful sortition (this corresponds to the "current" miner slot)
140140
pub cur_sortition: SortitionState,
141-
/// the hash at which the sortitions view was fetched
142-
pub latest_consensus_hash: ConsensusHash,
143141
/// configuration settings for evaluating proposals
144142
pub config: ProposalEvalConfig,
145143
}
@@ -608,42 +606,21 @@ impl SortitionsView {
608606
config: ProposalEvalConfig,
609607
client: &StacksClient,
610608
) -> Result<Self, ClientError> {
611-
let latest_state = client.get_latest_sortition()?;
612-
let latest_ch = latest_state.consensus_hash;
613-
614-
// figure out what cur_sortition will be set to.
615-
// if the latest sortition wasn't successful, query the last one that was.
616-
let latest_success = if latest_state.was_sortition {
617-
latest_state
618-
} else {
619-
info!("Latest state wasn't a sortition: {latest_state:?}");
620-
let last_sortition_ch = latest_state
621-
.last_sortition_ch
622-
.as_ref()
623-
.ok_or_else(|| ClientError::NoSortitionOnChain)?;
624-
client.get_sortition(last_sortition_ch)?
625-
};
626-
627-
// now, figure out what `last_sortition` will be set to.
628-
let last_sortition = latest_success
629-
.last_sortition_ch
630-
.as_ref()
631-
.map(|ch| client.get_sortition(ch))
632-
.transpose()?;
609+
let CurrentAndLastSortition {
610+
current_sortition,
611+
last_sortition,
612+
} = client.get_current_and_last_sortition()?;
633613

634-
let cur_sortition = SortitionState::try_from(latest_success)?;
614+
let cur_sortition = SortitionState::try_from(current_sortition)?;
635615
let last_sortition = last_sortition
636616
.map(SortitionState::try_from)
637617
.transpose()
638618
.ok()
639619
.flatten();
640620

641-
let latest_consensus_hash = latest_ch;
642-
643621
Ok(Self {
644622
cur_sortition,
645623
last_sortition,
646-
latest_consensus_hash,
647624
config,
648625
})
649626
}

stacks-signer/src/client/stacks_client.rs

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::collections::{HashMap, VecDeque};
2-
31
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
42
// Copyright (C) 2020-2024 Stacks Open Internet Foundation
53
//
@@ -15,6 +13,8 @@ use std::collections::{HashMap, VecDeque};
1513
//
1614
// You should have received a copy of the GNU General Public License
1715
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
use std::collections::{HashMap, VecDeque};
17+
1818
use blockstack_lib::burnchains::Txid;
1919
use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
2020
use blockstack_lib::chainstate::stacks::boot::{
@@ -88,6 +88,15 @@ struct GetStackersErrorResp {
8888
err_msg: String,
8989
}
9090

91+
/// Result from fetching current and last sortition:
92+
/// two sortition infos
93+
pub struct CurrentAndLastSortition {
94+
/// the latest winning sortition in the current burnchain fork
95+
pub current_sortition: SortitionInfo,
96+
/// the last winning sortition prior to `current_sortition`, if there was one
97+
pub last_sortition: Option<SortitionInfo>,
98+
}
99+
91100
impl From<&GlobalConfig> for StacksClient {
92101
fn from(config: &GlobalConfig) -> Self {
93102
Self {
@@ -531,10 +540,10 @@ impl StacksClient {
531540
Ok(tenures)
532541
}
533542

534-
/// Get the sortition information for the latest sortition
535-
pub fn get_latest_sortition(&self) -> Result<SortitionInfo, ClientError> {
536-
debug!("stacks_node_client: Getting latest sortition...");
537-
let path = self.sortition_info_path();
543+
/// Get the current winning sortition and the last winning sortition
544+
pub fn get_current_and_last_sortition(&self) -> Result<CurrentAndLastSortition, ClientError> {
545+
debug!("stacks_node_client: Getting current and prior sortition...");
546+
let path = format!("{}/latest_and_last", self.sortition_info_path());
538547
let timer = crate::monitoring::new_rpc_call_timer(&path, &self.http_origin);
539548
let send_request = || {
540549
self.stacks_node_client.get(&path).send().map_err(|e| {
@@ -547,29 +556,29 @@ impl StacksClient {
547556
if !response.status().is_success() {
548557
return Err(ClientError::RequestFailure(response.status()));
549558
}
550-
let sortition_info = response.json()?;
551-
Ok(sortition_info)
552-
}
553-
554-
/// Get the sortition information for a given sortition
555-
pub fn get_sortition(&self, ch: &ConsensusHash) -> Result<SortitionInfo, ClientError> {
556-
debug!("stacks_node_client: Getting sortition with consensus hash {ch}...");
557-
let path = format!("{}/consensus/{}", self.sortition_info_path(), ch.to_hex());
558-
let timer_label = format!("{}/consensus/:consensus_hash", self.sortition_info_path());
559-
let timer = crate::monitoring::new_rpc_call_timer(&timer_label, &self.http_origin);
560-
let send_request = || {
561-
self.stacks_node_client.get(&path).send().map_err(|e| {
562-
warn!("Signer failed to request sortition"; "consensus_hash" => %ch, "err" => ?e);
563-
e
564-
})
559+
let mut info_list: VecDeque<SortitionInfo> = response.json()?;
560+
let Some(current_sortition) = info_list.pop_front() else {
561+
return Err(ClientError::UnexpectedResponseFormat(
562+
"Empty SortitionInfo returned".into(),
563+
));
565564
};
566-
let response = send_request()?;
567-
timer.stop_and_record();
568-
if !response.status().is_success() {
569-
return Err(ClientError::RequestFailure(response.status()));
565+
if !current_sortition.was_sortition {
566+
return Err(ClientError::UnexpectedResponseFormat(
567+
"'Current' SortitionInfo returned which was not a winning sortition".into(),
568+
));
570569
}
571-
let sortition_info = response.json()?;
572-
Ok(sortition_info)
570+
let last_sortition = if current_sortition.last_sortition_ch.is_some() {
571+
let Some(last_sortition) = info_list.pop_back() else {
572+
return Err(ClientError::UnexpectedResponseFormat("'Current' SortitionInfo has `last_sortition_ch` field, but corresponding data not returned".into()));
573+
};
574+
Some(last_sortition)
575+
} else {
576+
None
577+
};
578+
Ok(CurrentAndLastSortition {
579+
current_sortition,
580+
last_sortition,
581+
})
573582
}
574583

575584
/// Get the current peer info data from the stacks node

stacks-signer/src/tests/chainstate.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ fn setup_test_environment(
8282
});
8383

8484
let view = SortitionsView {
85-
latest_consensus_hash: cur_sortition.consensus_hash,
8685
cur_sortition,
8786
last_sortition,
8887
config: ProposalEvalConfig {

stackslib/src/chainstate/nakamoto/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,7 @@ impl NakamotoBlockHeader {
734734
write_next(fd, &self.tx_merkle_root)?;
735735
write_next(fd, &self.state_index_root)?;
736736
write_next(fd, &self.timestamp)?;
737+
write_next(fd, &self.pox_treatment)?;
737738
Ok(Sha512Trunc256Sum::from_hasher(hasher))
738739
}
739740

@@ -1876,7 +1877,7 @@ impl NakamotoChainState {
18761877
"stacks_block_id" => %next_ready_block.header.block_id(),
18771878
"parent_block_id" => %next_ready_block.header.parent_block_id
18781879
);
1879-
ChainstateError::InvalidStacksBlock("Failed to load burn view of parent block ID".into())
1880+
ChainstateError::InvalidStacksBlock("Failed to load burn view of parent block ID".into())
18801881
})?;
18811882
let handle = sort_db.index_handle_at_ch(&tenure_change.burn_view_consensus_hash)?;
18821883
let connected_sort_id = get_ancestor_sort_id(&handle, parent_burn_view_sn.block_height, &handle.context.chain_tip)?
@@ -1888,7 +1889,7 @@ impl NakamotoChainState {
18881889
"stacks_block_id" => %next_ready_block.header.block_id(),
18891890
"parent_block_id" => %next_ready_block.header.parent_block_id
18901891
);
1891-
ChainstateError::InvalidStacksBlock("Failed to load burn view of parent block ID".into())
1892+
ChainstateError::InvalidStacksBlock("Failed to load burn view of parent block ID".into())
18921893
})?;
18931894
if connected_sort_id != parent_burn_view_sn.sortition_id {
18941895
warn!(

0 commit comments

Comments
 (0)