Skip to content

Commit 3d1e690

Browse files
committed
Merge branch 'develop' into fix/5193-stackerdb-decoherence
2 parents 2140b44 + 4b5e9cf commit 3d1e690

File tree

15 files changed

+617
-352
lines changed

15 files changed

+617
-352
lines changed

.github/workflows/bitcoin-tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ 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
@@ -122,9 +125,6 @@ jobs:
122125
- tests::nakamoto_integrations::follower_bootup_across_multiple_cycles
123126
- tests::nakamoto_integrations::utxo_check_on_startup_panic
124127
- 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
128128
# TODO: enable these once v1 signer is supported by a new nakamoto epoch
129129
# - tests::signer::v1::dkg
130130
# - 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: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::collections::VecDeque;
21
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
32
// Copyright (C) 2020-2024 Stacks Open Internet Foundation
43
//
@@ -14,6 +13,7 @@ use std::collections::VecDeque;
1413
//
1514
// You should have received a copy of the GNU General Public License
1615
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
use std::collections::VecDeque;
1717
use std::net::SocketAddr;
1818

1919
use blockstack_lib::burnchains::Txid;
@@ -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 {
@@ -484,10 +493,10 @@ impl StacksClient {
484493
Ok(tenures)
485494
}
486495

487-
/// Get the sortition information for the latest sortition
488-
pub fn get_latest_sortition(&self) -> Result<SortitionInfo, ClientError> {
489-
debug!("stacks_node_client: Getting latest sortition...");
490-
let path = self.sortition_info_path();
496+
/// Get the current winning sortition and the last winning sortition
497+
pub fn get_current_and_last_sortition(&self) -> Result<CurrentAndLastSortition, ClientError> {
498+
debug!("stacks_node_client: Getting current and prior sortition...");
499+
let path = format!("{}/latest_and_last", self.sortition_info_path());
491500
let timer = crate::monitoring::new_rpc_call_timer(&path, &self.http_origin);
492501
let send_request = || {
493502
self.stacks_node_client.get(&path).send().map_err(|e| {
@@ -500,29 +509,29 @@ impl StacksClient {
500509
if !response.status().is_success() {
501510
return Err(ClientError::RequestFailure(response.status()));
502511
}
503-
let sortition_info = response.json()?;
504-
Ok(sortition_info)
505-
}
506-
507-
/// Get the sortition information for a given sortition
508-
pub fn get_sortition(&self, ch: &ConsensusHash) -> Result<SortitionInfo, ClientError> {
509-
debug!("stacks_node_client: Getting sortition with consensus hash {ch}...");
510-
let path = format!("{}/consensus/{}", self.sortition_info_path(), ch.to_hex());
511-
let timer_label = format!("{}/consensus/:consensus_hash", self.sortition_info_path());
512-
let timer = crate::monitoring::new_rpc_call_timer(&timer_label, &self.http_origin);
513-
let send_request = || {
514-
self.stacks_node_client.get(&path).send().map_err(|e| {
515-
warn!("Signer failed to request sortition"; "consensus_hash" => %ch, "err" => ?e);
516-
e
517-
})
512+
let mut info_list: VecDeque<SortitionInfo> = response.json()?;
513+
let Some(current_sortition) = info_list.pop_front() else {
514+
return Err(ClientError::UnexpectedResponseFormat(
515+
"Empty SortitionInfo returned".into(),
516+
));
518517
};
519-
let response = send_request()?;
520-
timer.stop_and_record();
521-
if !response.status().is_success() {
522-
return Err(ClientError::RequestFailure(response.status()));
518+
if !current_sortition.was_sortition {
519+
return Err(ClientError::UnexpectedResponseFormat(
520+
"'Current' SortitionInfo returned which was not a winning sortition".into(),
521+
));
523522
}
524-
let sortition_info = response.json()?;
525-
Ok(sortition_info)
523+
let last_sortition = if current_sortition.last_sortition_ch.is_some() {
524+
let Some(last_sortition) = info_list.pop_back() else {
525+
return Err(ClientError::UnexpectedResponseFormat("'Current' SortitionInfo has `last_sortition_ch` field, but corresponding data not returned".into()));
526+
};
527+
Some(last_sortition)
528+
} else {
529+
None
530+
};
531+
Ok(CurrentAndLastSortition {
532+
current_sortition,
533+
last_sortition,
534+
})
526535
}
527536

528537
/// 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 {

0 commit comments

Comments
 (0)