Skip to content

Commit 524b0e1

Browse files
authored
Merge pull request #4250 from stacks-network/feat/miner-improvements
Feat: new miner improvements
2 parents 37ad927 + 8c70e98 commit 524b0e1

34 files changed

+3819
-346
lines changed

.cargo/config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[alias]
22
stacks-node = "run --package stacks-node --"
3+
fmt-stacks = "fmt -- --config group_imports=StdExternalCrate,imports_granularity=Module"
34

45
# Needed by perf to generate flamegraphs.
56
#[target.x86_64-unknown-linux-gnu]

.github/actions/bitcoin-int-tests/Dockerfile.rustfmt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ WORKDIR /src
44

55
COPY ./rust-toolchain .
66
COPY ./Cargo.toml .
7+
COPY ./.cargo .
78

89
RUN rustup component add rustfmt
910

1011
COPY . .
1112

12-
RUN cargo fmt --all -- --check
13+
RUN cargo fmt-stacks --check

.github/workflows/bitcoin-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ jobs:
6868
- tests::neon_integrations::test_problematic_microblocks_are_not_relayed_or_stored
6969
- tests::neon_integrations::test_problematic_txs_are_not_stored
7070
- tests::neon_integrations::use_latest_tip_integration_test
71+
- tests::neon_integrations::min_txs
7172
- tests::should_succeed_handling_malformed_and_valid_txs
7273
steps:
7374
## Setup test environment

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to the versioning scheme outlined in the [README.md](README.md).
77

8+
## [2.4.0.0.5]
9+
10+
This introduces a set of improvements to the Stacks miner behavior. In
11+
particular:
12+
* The VRF public key can be re-used across node restarts.
13+
* Settings that affect mining are hot-reloaded from the config file. They take
14+
effect once the file is updated; there is no longer a need to restart the
15+
node.
16+
* The act of changing the miner settings in the config file automatically
17+
triggers a subsequent block-build attempt, allowing the operator to force the
18+
miner to re-try building blocks.
19+
* This adds a new tip-selection algorithm that minimizes block orphans within a
20+
configurable window of time.
21+
* When configured, the node will automatically stop mining if it is not achieving a
22+
targeted win rate over a configurable window of blocks.
23+
* When configured, the node will selectively mine transactions from only certain
24+
addresses, or only of certain types (STX-transfers, contract-publishes,
25+
contract-calls).
26+
* When configured, the node will optionally only RBF block-commits if it can
27+
produce a block with strictly more transactions.
28+
829
## [2.4.0.0.4]
930

1031
This is a high-priority hotfix that addresses a bug in transaction processing which

CONTRIBUTING.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,24 @@ should reference the issue in the commit message. For example:
9191
fix: incorporate unlocks in mempool admitter, #3623
9292
```
9393

94+
## Recommended developer setup
95+
### Recommended githooks
96+
97+
It is helpful to set up the pre-commit git hook set up, so that Rust formatting issues are caught before
98+
you push your code. Follow these instruction to set it up:
99+
100+
1. Rename `.git/hooks/pre-commit.sample` to `.git/hooks/pre-commit`
101+
2. Change the content of `.git/hooks/pre-commit` to be the following
102+
```sh
103+
#!/bin/sh
104+
git diff --name-only --staged | grep '\.rs$' | xargs -P 8 -I {} rustfmt {} --edition 2021 --check --config group_imports=StdExternalCrate,imports_granularity=Module || (
105+
echo 'rustfmt failed: run "cargo fmt-stacks"';
106+
exit 1
107+
)
108+
```
109+
3. Make it executable by running `chmod +x .git/hooks/pre-commit`
110+
That's it! Now your pre-commit hook should be configured on your local machine.
111+
94112
# Creating and Reviewing PRs
95113

96114
This section describes some best practices on how to create and review PRs in this context. The target audience is people who have commit access to this repository (reviewers), and people who open PRs (submitters). This is a living document -- developers can and should document their own additional guidelines here.
@@ -366,19 +384,20 @@ A test should be marked `#[ignore]` if:
366384

367385
## Formatting
368386

369-
This repository uses the default rustfmt formatting style. PRs will be checked against `rustfmt` and will _fail_ if not
370-
properly formatted.
387+
PRs will be checked against `rustfmt` and will _fail_ if not properly formatted.
388+
Unfortunately, some config options that we require cannot currently be set in `.rustfmt` files, so arguments must be passed via the command line.
389+
Therefore, we handle `rustfmt` configuration using a Cargo alias: `cargo fmt-stacks`
371390

372391
You can check the formatting locally via:
373392

374393
```bash
375-
cargo fmt --all -- --check --config group_imports=StdExternalCrate
394+
cargo fmt-stacks --check
376395
```
377396

378397
You can automatically reformat your commit via:
379398

380399
```bash
381-
cargo fmt --all -- --config group_imports=StdExternalCrate
400+
cargo fmt-stacks
382401
```
383402

384403
## Comments

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ You can observe the state machine in action locally by running:
6767

6868
```bash
6969
$ cd testnet/stacks-node
70-
$ cargo run --bin stacks-node -- start --config=./conf/testnet-follower-conf.toml
70+
$ cargo run --bin stacks-node -- start --config ./conf/testnet-follower-conf.toml
7171
```
7272

7373
_On Windows, many tests will fail if the line endings aren't `LF`. Please ensure that you are have git's `core.autocrlf` set to `input` when you clone the repository to avoid any potential issues. This is due to the Clarity language currently being sensitive to line endings._
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Usage:
4+
This script is designed to be run from the command line. It takes one or more Bitcoin addresses
5+
and outputs the extracted block commit data for these addresses.
6+
7+
Example command line usage:
8+
python3 get_unconfirmed_block_commits.py [btcAddress1] [btcAddress2] ...
9+
"""
10+
11+
import requests
12+
import json
13+
import sys
14+
15+
def read_api_endpoint(url):
16+
"""
17+
Reads data from the specified API endpoint and returns the response.
18+
19+
Args:
20+
url (str): The API endpoint URL.
21+
22+
Returns:
23+
dict: JSON response from the API if successful, otherwise None.
24+
"""
25+
try:
26+
response = requests.get(url)
27+
response.raise_for_status() # Raise an exception for non-200 status codes
28+
return response.json() # Assuming a JSON response
29+
except requests.exceptions.RequestException as e:
30+
return None
31+
32+
def is_block_commit(txn):
33+
"""
34+
Determines whether a given transaction is a block commit.
35+
36+
Args:
37+
txn (dict): The transaction data.
38+
39+
Returns:
40+
bool: True if the transaction is a block commit, otherwise False.
41+
"""
42+
try:
43+
vout = txn['vout']
44+
45+
# Verify the number of recipients.
46+
assert(3 <= len(vout) <= 4)
47+
block_commit_txn = vout[0]
48+
to_stacker_txns = vout[1::2]
49+
50+
# Verify block commit.
51+
# TODO: Add more verification steps if necessary.
52+
assert(block_commit_txn['scriptpubkey_type'] == "op_return")
53+
54+
# Verify PoX Payouts.
55+
for to_stacker_txn in to_stacker_txns:
56+
# TODO: Add more verification steps if necessary.
57+
assert(to_stacker_txn['scriptpubkey_type'] != "op_return")
58+
59+
except (Exception, AssertionError):
60+
return False
61+
return True
62+
63+
MEMPOOL_TXN_API = "https://mempool.space/api/address/{btcAddress}/txs/mempool"
64+
def unconfirmed_block_commit_from_address(btcAddress):
65+
"""
66+
Fetches the first unconfirmed block commit for a given Bitcoin address.
67+
68+
Args:
69+
btcAddress (str): Bitcoin address.
70+
71+
Returns:
72+
dict: The first transaction that is a block commit.
73+
"""
74+
url = MEMPOOL_TXN_API.format(btcAddress=btcAddress)
75+
txns = read_api_endpoint(url)
76+
77+
# Return only the first block commit transaction. This is good enough for now.
78+
for txn in txns:
79+
if is_block_commit(txn):
80+
return txn
81+
82+
def extracted_block_commit_data(txn):
83+
"""
84+
Extracts data from a block commit transaction.
85+
86+
Args:
87+
txn (dict): Block commit transaction.
88+
89+
Returns:
90+
dict: Extracted data from the transaction, or None if extraction fails.
91+
"""
92+
try:
93+
vout_start = 1
94+
vout_end = len(txn['vout']) - 1
95+
spent_utxo = txn['vin'][0]
96+
return {
97+
'txid': txn['txid'],
98+
'burn': sum(pox_payout['value'] for pox_payout in txn['vout'][vout_start:vout_end]),
99+
'address': spent_utxo['prevout']['scriptpubkey_address'],
100+
'pox_addrs': [txn['vout'][i]['scriptpubkey'] for i in range(vout_start,vout_end)],
101+
'input_txid': spent_utxo['txid'],
102+
'input_index': spent_utxo['vout'],
103+
}
104+
except Exception as e:
105+
return None
106+
107+
def block_commit_data(btcAddresses):
108+
"""
109+
Fetches and extracts block commit data for a list of Bitcoin addresses.
110+
111+
Args:
112+
btcAddresses (list): List of Bitcoin addresses.
113+
114+
Returns:
115+
list: Extracted block commit data for each address.
116+
"""
117+
return [extracted_block_commit_data(unconfirmed_block_commit_from_address(btcAddress)) \
118+
for btcAddress in btcAddresses]
119+
120+
def main():
121+
"""
122+
Main function to run the script. Takes command line arguments as Bitcoin addresses.
123+
"""
124+
btc_addresses = sys.argv[1:]
125+
if not btc_addresses:
126+
print("No Bitcoin addresses provided. Please provide at least one address.")
127+
return
128+
129+
# Return the data by printing it to stdout.
130+
data = block_commit_data(btc_addresses)
131+
print(json.dumps([datum for datum in data if datum is not None], indent=1))
132+
133+
if __name__ == "__main__":
134+
main()

src/burnchains/burnchain.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ impl BurnchainStateTransition {
316316
}
317317

318318
impl BurnchainSigner {
319-
#[cfg(test)]
319+
#[cfg(any(test, feature = "testing"))]
320320
pub fn mock_parts(
321321
hash_mode: AddressHashMode,
322322
num_sigs: usize,
@@ -330,7 +330,7 @@ impl BurnchainSigner {
330330
BurnchainSigner(repr)
331331
}
332332

333-
#[cfg(test)]
333+
#[cfg(any(test, feature = "testing"))]
334334
pub fn new_p2pkh(pubk: &StacksPublicKey) -> BurnchainSigner {
335335
BurnchainSigner::mock_parts(AddressHashMode::SerializeP2PKH, 1, vec![pubk.clone()])
336336
}

src/chainstate/stacks/db/blocks.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7042,6 +7042,28 @@ impl StacksChainState {
70427042
query_row(&self.db(), sql, args).map_err(Error::DBError)
70437043
}
70447044

7045+
/// Get all possible canonical chain tips
7046+
pub fn get_stacks_chain_tips(&self, sortdb: &SortitionDB) -> Result<Vec<StagingBlock>, Error> {
7047+
let (consensus_hash, block_bhh) =
7048+
SortitionDB::get_canonical_stacks_chain_tip_hash(sortdb.conn())?;
7049+
let sql = "SELECT * FROM staging_blocks WHERE processed = 1 AND orphaned = 0 AND consensus_hash = ?1 AND anchored_block_hash = ?2";
7050+
let args: &[&dyn ToSql] = &[&consensus_hash, &block_bhh];
7051+
let Some(staging_block): Option<StagingBlock> =
7052+
query_row(&self.db(), sql, args).map_err(Error::DBError)?
7053+
else {
7054+
return Ok(vec![]);
7055+
};
7056+
self.get_stacks_chain_tips_at_height(staging_block.height)
7057+
}
7058+
7059+
/// Get all Stacks blocks at a given height
7060+
pub fn get_stacks_chain_tips_at_height(&self, height: u64) -> Result<Vec<StagingBlock>, Error> {
7061+
let sql =
7062+
"SELECT * FROM staging_blocks WHERE processed = 1 AND orphaned = 0 AND height = ?1";
7063+
let args: &[&dyn ToSql] = &[&u64_to_sql(height)?];
7064+
query_rows(&self.db(), sql, args).map_err(Error::DBError)
7065+
}
7066+
70457067
/// Get the parent block of `staging_block`.
70467068
pub fn get_stacks_block_parent(
70477069
&self,

0 commit comments

Comments
 (0)