Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE

- Add `stackerdb_timeout_secs` to miner config for limiting duration of StackerDB HTTP requests.
- When determining a global transaction replay set, the state evaluator now uses a longest-common-prefix algorithm to find a replay set in the case where a single replay set has less than 70% of signer weight.
- New endpoint /v3/tenures/blocks/ allowing retrieving the list of stacks blocks from a burn block
- New endpoints /v3/tenures/blocks/, /v3/tenures/blocks/hash, /v3/tenures/blocks/height allowing retrieving the list of stacks blocks from a burn block
- Creates epoch 3.3 and costs-4 in preparation for a hardfork to activate Clarity 4
- Adds support for new Clarity 4 builtins (not activated until epoch 3.3):
- `contract-hash?`
Expand Down
67 changes: 67 additions & 0 deletions docs/rpc/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,73 @@ paths:
"500":
$ref: "#/components/responses/InternalServerError"

/v3/tenures/blocks/hash/{burnchain_block_hash}:
get:
summary: Get the list of Nakamoto Stacks blocks in a tenure given Bitcoin block hash
tags:
- Blocks
security: []
operationId: getTenureBlocksByHash
description: |
Get the list of Nakamoto blocks in a tenure given the Bitcoin block hash. The blocks will be
shown in order from highest to lowest. This is only for Nakamoto blocks, Epoch2 ones will not be shown.
parameters:
- name: burnchain_block_hash
in: path
description: The hex-encoded Bitcoin block hash of the tenure to query (64 hexadecimal characters, without 0x prefix)
required: true
schema:
type: string
pattern: "^[0-9a-f]{64}$"
responses:
"200":
description: List of Stacks blocks in the tenure
content:
application/json:
schema:
$ref: "#/components/schemas/TenureBlocks"
example:
$ref: "./components/examples/tenure-blocks.example.json"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalServerError"

/v3/tenures/blocks/height/{burnchain_block_height}:
get:
summary: Get the list of Nakamoto Stacks blocks in a tenure given Bitcoin block height
tags:
- Blocks
security: []
operationId: getTenureBlocksByHeight
description: |
Get the list of Nakamoto blocks in a tenure given the Bitcoin block height. The blocks will be
shown in order from highest to lowest. This is only for Nakamoto blocks, Epoch2 ones will not be shown.
parameters:
- name: burnchain_block_height
in: path
description: The Bitcoin block height of the tenure to query
required: true
schema:
type: integer
responses:
"200":
description: List of Stacks blocks in the tenure
content:
application/json:
schema:
$ref: "#/components/schemas/TenureBlocks"
example:
$ref: "./components/examples/tenure-blocks.example.json"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"500":
$ref: "#/components/responses/InternalServerError"

/v3/sortitions:
get:
summary: Get latest sortition information
Expand Down
146 changes: 132 additions & 14 deletions stackslib/src/chainstate/nakamoto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2846,6 +2846,27 @@ impl NakamotoChainState {
Self::get_block_header_nakamoto(chainstate_conn.sqlite(), &block_id)
}

/// Get the first canonical block header in a vector of height-ordered candidates
fn get_highest_canonical_block_header_from_candidates(
sort_db: &SortitionDB,
candidates: Vec<StacksHeaderInfo>,
) -> Result<Option<StacksHeaderInfo>, ChainstateError> {
let canonical_sortition_handle = sort_db.index_handle_at_tip();
for candidate in candidates.into_iter() {
let Some(ref candidate_ch) = candidate.burn_view else {
// this is an epoch 2.x header, no burn view to check
return Ok(Some(candidate));
};
let in_canonical_fork = canonical_sortition_handle.processed_block(&candidate_ch)?;
if in_canonical_fork {
return Ok(Some(candidate));
}
}

// did not find any blocks in candidates
Ok(None)
}

/// Get the highest block in the given tenure on a given fork.
/// Only works on Nakamoto blocks.
/// TODO: unit test
Expand Down Expand Up @@ -2879,20 +2900,7 @@ impl NakamotoChainState {
tenure_id,
)?;

let canonical_sortition_handle = sort_db.index_handle_at_tip();
for candidate in candidates.into_iter() {
let Some(ref candidate_ch) = candidate.burn_view else {
// this is an epoch 2.x header, no burn view to check
return Ok(Some(candidate));
};
let in_canonical_fork = canonical_sortition_handle.processed_block(&candidate_ch)?;
if in_canonical_fork {
return Ok(Some(candidate));
}
}

// did not find any blocks in the tenure
Ok(None)
Self::get_highest_canonical_block_header_from_candidates(sort_db, candidates)
}

/// DO NOT USE IN CONSENSUS CODE. Different nodes can have different blocks for the same
Expand Down Expand Up @@ -2931,6 +2939,116 @@ impl NakamotoChainState {
Ok(Vec::from_iter(epoch2_x))
}

/// DO NOT USE IN CONSENSUS CODE. Different nodes can have different blocks for the same
/// tenure.
///
/// Get the highest block in a given tenure (identified by burnchain block height) with a canonical
/// burn_view (i.e., burn_view on the canonical sortition fork). This covers only Nakamoto blocks.
/// Epoch2 blocks will not be checked.
pub fn find_highest_known_block_header_in_tenure_by_block_height(
chainstate: &StacksChainState,
sort_db: &SortitionDB,
tenure_height: u64,
) -> Result<Option<StacksHeaderInfo>, ChainstateError> {
let chainstate_db_conn = chainstate.db();

let candidates =
Self::get_highest_known_block_header_in_tenure_by_block_height_at_each_burnview(
chainstate_db_conn,
tenure_height,
)?;

Self::get_highest_canonical_block_header_from_candidates(sort_db, candidates)
}

/// DO NOT USE IN CONSENSUS CODE. Different nodes can have different blocks for the same
/// tenure.
///
/// Get the highest block in a given tenure (identified by burnchain block hash) with a canonical
/// burn_view (i.e., burn_view on the canonical sortition fork). This covers only Nakamoto blocks.
/// Epoch2 blocks will not be checked.
pub fn find_highest_known_block_header_in_tenure_by_block_hash(
chainstate: &StacksChainState,
sort_db: &SortitionDB,
tenure_block_hash: &BurnchainHeaderHash,
) -> Result<Option<StacksHeaderInfo>, ChainstateError> {
let chainstate_db_conn = chainstate.db();

let candidates =
Self::get_highest_known_block_header_in_tenure_by_block_hash_at_each_burnview(
chainstate_db_conn,
tenure_block_hash,
)?;

Self::get_highest_canonical_block_header_from_candidates(sort_db, candidates)
}

/// DO NOT USE IN CONSENSUS CODE. Different nodes can have different blocks for the same
/// tenure.
///
/// Get the highest blocks in a given tenure (identified by burnchain block height) at each burn view
/// active in that tenure. If there are ties at a given burn view, they will both be returned
fn get_highest_known_block_header_in_tenure_by_block_height_at_each_burnview(
db: &Connection,
tenure_height: u64,
) -> Result<Vec<StacksHeaderInfo>, ChainstateError> {
// see if we have a nakamoto block in this tenure
let qry = "
SELECT h.*
FROM nakamoto_block_headers h
JOIN (
SELECT burn_view, MAX(block_height) AS max_height
FROM nakamoto_block_headers
WHERE burn_header_height = ?1
GROUP BY burn_view
) maxed
ON h.burn_view = maxed.burn_view
AND h.block_height = maxed.max_height
WHERE h.burn_header_height = ?1
ORDER BY h.block_height DESC, h.timestamp
";
let args = params![tenure_height];
let out = query_rows(db, qry, args)?;
if !out.is_empty() {
return Ok(out);
}

Err(ChainstateError::NoSuchBlockError)
}

/// DO NOT USE IN CONSENSUS CODE. Different nodes can have different blocks for the same
/// tenure.
///
/// Get the highest blocks in a given tenure (identified by burnchain block hash) at each burn view
/// active in that tenure. If there are ties at a given burn view, they will both be returned
fn get_highest_known_block_header_in_tenure_by_block_hash_at_each_burnview(
db: &Connection,
tenure_block_hash: &BurnchainHeaderHash,
) -> Result<Vec<StacksHeaderInfo>, ChainstateError> {
// see if we have a nakamoto block in this tenure
let qry = "
SELECT h.*
FROM nakamoto_block_headers h
JOIN (
SELECT burn_view, MAX(block_height) AS max_height
FROM nakamoto_block_headers
WHERE burn_header_hash = ?1
GROUP BY burn_view
) maxed
ON h.burn_view = maxed.burn_view
AND h.block_height = maxed.max_height
WHERE h.burn_header_hash = ?1
ORDER BY h.block_height DESC, h.timestamp
";
let args = params![tenure_block_hash];
let out = query_rows(db, qry, args)?;
if !out.is_empty() {
return Ok(out);
}

Err(ChainstateError::NoSuchBlockError)
}

/// Get the VRF proof for a Stacks block.
/// For Nakamoto blocks, this is the VRF proof contained in the coinbase of the tenure-start
/// block of the given tenure identified by the consensus hash.
Expand Down
2 changes: 1 addition & 1 deletion stackslib/src/net/api/gettenureblocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ impl HttpResponse for RPCNakamotoTenureBlocksRequestHandler {
}

impl StacksHttpRequest {
/// Make a new getinfo request to this endpoint
/// Make a new request to this endpoint
pub fn new_get_tenure_blocks(
host: PeerHost,
consensus_hash: &ConsensusHash,
Expand Down
Loading
Loading