Skip to content

Commit 9cf8ffd

Browse files
committed
Merge remote-tracking branch 'core/develop' into feat/full-tx-replay
2 parents 30751e6 + 418b0c7 commit 9cf8ffd

File tree

10 files changed

+579
-266
lines changed

10 files changed

+579
-266
lines changed

docs/event-dispatcher.md

Lines changed: 272 additions & 221 deletions
Large diffs are not rendered by default.

stacks-signer/src/v0/signer.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,12 @@ impl Signer {
430430
sortition_state,
431431
),
432432
SignerMessage::StateMachineUpdate(update) => self
433-
.handle_state_machine_update(signer_public_key, update, received_time),
433+
.handle_state_machine_update(
434+
signer_public_key,
435+
update,
436+
received_time,
437+
sortition_state,
438+
),
434439
_ => {}
435440
}
436441
}
@@ -761,6 +766,7 @@ impl Signer {
761766
signer_public_key: &Secp256k1PublicKey,
762767
update: &StateMachineUpdate,
763768
received_time: &SystemTime,
769+
sortition_state: &mut Option<SortitionsView>,
764770
) {
765771
let address = StacksAddress::p2pkh(self.mainnet, signer_public_key);
766772
// Store the state machine update so we can reload it if we crash
@@ -783,6 +789,7 @@ impl Signer {
783789
self.stacks_address,
784790
version,
785791
self.reward_cycle,
792+
sortition_state,
786793
);
787794
}
788795

stacks-signer/src/v0/signer_state.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use stacks_common::util::secp256k1::Secp256k1PublicKey;
4343
use stacks_common::{debug, info, warn};
4444

4545
use crate::chainstate::{
46-
ProposalEvalConfig, SignerChainstateError, SortitionState, SortitionsView,
46+
ProposalEvalConfig, SignerChainstateError, SortitionMinerStatus, SortitionState, SortitionsView,
4747
};
4848
use crate::client::{ClientError, CurrentAndLastSortition, StackerDB, StacksClient};
4949
use crate::signerdb::{BlockValidatedByReplaySet, SignerDb};
@@ -623,6 +623,7 @@ impl LocalStateMachine {
623623
local_address: StacksAddress,
624624
local_supported_signer_protocol_version: u64,
625625
reward_cycle: u64,
626+
sortition_state: &mut Option<SortitionsView>,
626627
) {
627628
// Before we ever access eval...we should make sure to include our own local state machine update message in the evaluation
628629
let Ok(mut local_update) =
@@ -736,6 +737,21 @@ impl LocalStateMachine {
736737
active_signer_protocol_version,
737738
tx_replay_set,
738739
});
740+
741+
match new_miner {
742+
StateMachineUpdateMinerState::ActiveMiner {
743+
current_miner_pkh, ..
744+
} => {
745+
if let Some(sortition_state) = sortition_state {
746+
// if there is a mismatch between the new_miner ad the current sortition view, mark the current miner as invalid
747+
if current_miner_pkh != sortition_state.cur_sortition.miner_pkh {
748+
sortition_state.cur_sortition.miner_status =
749+
SortitionMinerStatus::InvalidatedBeforeFirstBlock
750+
}
751+
}
752+
}
753+
StateMachineUpdateMinerState::NoValidMiner => (),
754+
}
739755
}
740756
}
741757

stackslib/src/config/mod.rs

Lines changed: 240 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,28 @@ pub struct ConfigFile {
170170
pub __path: Option<String>, // Only used for config file reloads
171171
pub burnchain: Option<BurnchainConfigFile>,
172172
pub node: Option<NodeConfigFile>,
173+
/// Represents an initial STX balance allocation for an address at genesis
174+
/// for testing purposes.
175+
///
176+
/// This struct is used to define pre-allocated STX balances that are credited to
177+
/// specific addresses when the Stacks node first initializes its chainstate. These balances
178+
/// are included in the genesis block and are immediately available for spending.
179+
///
180+
/// **Configuration:**
181+
/// Configured as a list `[[ustx_balance]]` in TOML.
182+
///
183+
/// Example TOML entry:
184+
/// ```toml
185+
/// [[ustx_balance]]
186+
/// address = "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2"
187+
/// amount = 10000000000000000
188+
/// ```
189+
///
190+
/// This is intended strictly for testing purposes.
191+
/// Attempting to specify initial balances if [`BurnchainConfig::mode`] is "mainnet" will
192+
/// result in an invalid config error.
193+
///
194+
/// Default: `None`
173195
pub ustx_balance: Option<Vec<InitialBalanceFile>>,
174196
/// Deprecated: use `ustx_balance` instead
175197
pub mstx_balance: Option<Vec<InitialBalanceFile>>,
@@ -2468,13 +2490,24 @@ impl Default for NodeConfig {
24682490
impl NodeConfig {
24692491
/// Get a SocketAddr for this node's RPC endpoint which uses the loopback address
24702492
pub fn get_rpc_loopback(&self) -> Option<SocketAddr> {
2471-
let rpc_port = SocketAddr::from_str(&self.rpc_bind)
2472-
.map_err(|e| {
2493+
let rpc_port = self.rpc_bind_addr()?.port();
2494+
Some(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), rpc_port))
2495+
}
2496+
2497+
pub fn rpc_bind_addr(&self) -> Option<SocketAddr> {
2498+
SocketAddr::from_str(&self.rpc_bind)
2499+
.inspect_err(|e| {
24732500
error!("Could not parse node.rpc_bind configuration setting as SocketAddr: {e}");
24742501
})
2475-
.ok()?
2476-
.port();
2477-
Some(SocketAddr::new(Ipv4Addr::LOCALHOST.into(), rpc_port))
2502+
.ok()
2503+
}
2504+
2505+
pub fn p2p_bind_addr(&self) -> Option<SocketAddr> {
2506+
SocketAddr::from_str(&self.p2p_bind)
2507+
.inspect_err(|e| {
2508+
error!("Could not parse node.rpc_bind configuration setting as SocketAddr: {e}");
2509+
})
2510+
.ok()
24782511
}
24792512

24802513
pub fn add_signers_stackerdbs(&mut self, is_mainnet: bool) {
@@ -3784,12 +3817,97 @@ impl NodeConfigFile {
37843817
#[derive(Clone, Deserialize, Default, Debug)]
37853818
#[serde(deny_unknown_fields)]
37863819
pub struct FeeEstimationConfigFile {
3820+
/// Specifies the name of the cost estimator to use.
3821+
/// This controls how the node estimates computational costs for transactions.
3822+
///
3823+
/// Accepted values:
3824+
/// - `"NaivePessimistic"`: The only currently supported cost estimator. This estimator
3825+
/// tracks the highest observed costs for each operation type and uses the average
3826+
/// of the top 10 values as its estimate, providing a conservative approach to
3827+
/// cost estimation.
3828+
///
3829+
/// If not specified, or if [`FeeEstimationConfigFile::disabled`] is `true`,
3830+
/// the node will use the default cost estimator.
3831+
///
3832+
/// Default: `"NaivePessimistic"`
37873833
pub cost_estimator: Option<String>,
3834+
/// Specifies the name of the fee estimator to use.
3835+
/// This controls how the node calculates appropriate transaction fees based on costs.
3836+
///
3837+
/// Accepted values:
3838+
/// - `"ScalarFeeRate"`: Simple multiplier-based fee estimation that uses percentiles
3839+
/// (5th, 50th, and 95th) of observed fee rates from recent blocks.
3840+
/// - `"FuzzedWeightedMedianFeeRate"`: Fee estimation that adds controlled randomness
3841+
/// to a weighted median rate calculator. This helps prevent fee optimization attacks
3842+
/// by adding unpredictability to fee estimates while still maintaining accuracy.
3843+
///
3844+
/// If not specified, or if [`FeeEstimationConfigFile::disabled`] is `true`,
3845+
/// the node will use the default fee estimator.
3846+
///
3847+
/// Default: `"ScalarFeeRate"`
37883848
pub fee_estimator: Option<String>,
3849+
/// Specifies the name of the cost metric to use.
3850+
/// This controls how the node measures and compares transaction costs.
3851+
///
3852+
/// Accepted values:
3853+
/// - `"ProportionDotProduct"`: The only currently supported cost metric. This metric
3854+
/// computes a weighted sum of cost dimensions (runtime, read/write counts, etc.)
3855+
/// proportional to how much of the block limit they consume.
3856+
///
3857+
/// If not specified, or if [`FeeEstimationConfigFile::disabled`] is `true`,
3858+
/// the node will use the default cost metric.
3859+
///
3860+
/// Default: `"ProportionDotProduct"`
37893861
pub cost_metric: Option<String>,
3862+
/// If `true`, all fee and cost estimation features are disabled.
3863+
/// The node will use unit estimators and metrics, which effectively
3864+
/// provide no actual estimation capabilities.
3865+
///
3866+
/// When disabled, the node will:
3867+
/// 1. Not track historical transaction costs or fee rates
3868+
/// 2. Return simple unit values for costs for any transaction, regardless of its actual complexity
3869+
/// 3. Be unable to provide meaningful fee estimates for API requests (always returns an error)
3870+
/// 4. Consider only raw transaction fees (not fees per cost unit) when assembling blocks
3871+
///
3872+
/// This setting takes precedence over individual estimator/metric configurations.
3873+
/// When `true`, the values for [`FeeEstimationConfigFile::cost_estimator`],
3874+
/// [`FeeEstimationConfigFile::fee_estimator`], and [`FeeEstimationConfigFile::cost_metric`]
3875+
/// are ignored and treated as `None`.
3876+
///
3877+
/// Default: `false`
37903878
pub disabled: Option<bool>,
3879+
/// If `true`, errors encountered during cost or fee estimation will be logged.
3880+
/// This can help diagnose issues with the fee estimation subsystem.
3881+
///
3882+
/// Default: `false`
37913883
pub log_error: Option<bool>,
3884+
/// Specifies the fraction of random noise to add if using the `FuzzedWeightedMedianFeeRate` fee estimator.
3885+
/// This value should be in the range [0, 1], representing a percentage of the base fee rate.
3886+
///
3887+
/// For example, with a value of 0.1 (10%), fee rate estimates will have random noise added
3888+
/// within the range of ±10% of the original estimate. This randomization makes it difficult
3889+
/// for users to precisely optimize their fees while still providing reasonable estimates.
3890+
///
3891+
/// This setting is only relevant when [`FeeEstimationConfigFile::fee_estimator`] is set to
3892+
/// `"FuzzedWeightedMedianFeeRate"`. It controls how much randomness is introduced in the
3893+
/// fee estimation process to prevent fee optimization attacks.
3894+
///
3895+
/// Default: `0.1` (10%)
37923896
pub fee_rate_fuzzer_fraction: Option<f64>,
3897+
/// Specifies the window size for the [`WeightedMedianFeeRateEstimator`].
3898+
/// This determines how many historical fee rate data points are considered
3899+
/// when calculating the median fee rate.
3900+
///
3901+
/// The window size controls how quickly the fee estimator responds to changing
3902+
/// network conditions. A smaller window size (e.g., 5) makes the estimator more
3903+
/// responsive to recent fee rate changes but potentially more volatile. A larger
3904+
/// window size (e.g., 10) produces more stable estimates but may be slower to
3905+
/// adapt to rapid network changes.
3906+
///
3907+
/// This setting is primarily relevant when [`FeeEstimationConfigFile::fee_estimator`] is set to
3908+
/// `"FuzzedWeightedMedianFeeRate"`, as it's used by the underlying [`WeightedMedianFeeRateEstimator`].
3909+
///
3910+
/// Default: `5`
37933911
pub fee_rate_window_size: Option<u64>,
37943912
}
37953913

@@ -4047,9 +4165,118 @@ impl AtlasConfigFile {
40474165
#[derive(Clone, Deserialize, Default, Debug, Hash, PartialEq, Eq, PartialOrd)]
40484166
#[serde(deny_unknown_fields)]
40494167
pub struct EventObserverConfigFile {
4168+
/// URL endpoint (hostname and port) where event notifications will be sent via HTTP POST requests.
4169+
///
4170+
/// The node will automatically prepend `http://` to this endpoint and append the
4171+
/// specific event path (e.g., `/new_block`, `/new_mempool_tx`).
4172+
/// Therefore, this value should be specified as `hostname:port` (e.g., "localhost:3700").
4173+
///
4174+
/// **Do NOT include the `http://` scheme in this configuration value.**
4175+
///
4176+
/// This should point to a service capable of receiving and processing Stacks event data.
4177+
///
4178+
/// Default: No default. This field is required.
40504179
pub endpoint: String,
4180+
/// List of event types that this observer is configured to receive.
4181+
///
4182+
/// For a more detailed documentation check the event-dispatcher docs in the `/docs` folder.
4183+
///
4184+
/// Each string in the list specifies an event category or a specific event to subscribe to.
4185+
/// For an observer to receive any notifications, this list must contain at least one valid key.
4186+
/// Providing an invalid string that doesn't match any of the valid formats below will cause
4187+
/// the node to panic on startup when parsing the configuration.
4188+
///
4189+
/// All observers, regardless of their `events_keys` configuration, implicitly receive
4190+
/// payloads on the `/attachments/new` endpoint.
4191+
///
4192+
/// **Valid Event Keys:**
4193+
///
4194+
/// - `"*"`: Subscribes to a broad set of common events.
4195+
/// - **Events delivered to:**
4196+
/// - `/new_block`: For blocks containing transactions that generate STX, FT, NFT, or smart contract events.
4197+
/// - `/new_microblocks`: For all new microblock streams. **Note:** Only until epoch 2.5.
4198+
/// - `/new_mempool_tx`: For new mempool transactions.
4199+
/// - `/drop_mempool_tx`: For dropped mempool transactions.
4200+
/// - `/new_burn_block`: For new burnchain blocks.
4201+
/// - **Note:** This key does NOT by itself subscribe to `/stackerdb_chunks` or `/proposal_response`.
4202+
///
4203+
/// - `"stx"`: Subscribes to STX token operation events (transfer, mint, burn, lock).
4204+
/// - **Events delivered to:** `/new_block`, `/new_microblocks`.
4205+
/// - **Payload details:** The "events" array in the delivered payloads will be filtered to include only STX-related events.
4206+
///
4207+
/// - `"memtx"`: Subscribes to new and dropped mempool transaction events.
4208+
/// - **Events delivered to:** `/new_mempool_tx`, `/drop_mempool_tx`.
4209+
///
4210+
/// - `"burn_blocks"`: Subscribes to new burnchain block events.
4211+
/// - **Events delivered to:** `/new_burn_block`.
4212+
///
4213+
/// - `"microblocks"`: Subscribes to new microblock stream events.
4214+
/// - **Events delivered to:** `/new_microblocks`.
4215+
/// - **Payload details:**
4216+
/// - The "transactions" field will contain all transactions from the microblocks.
4217+
/// - The "events" field will contain STX, FT, NFT, or specific smart contract events
4218+
/// *only if* this observer is also subscribed to those more specific event types
4219+
/// (e.g., via `"stx"`, `"*"`, a specific contract event key, or a specific asset identifier key).
4220+
/// - **Note:** Only until epoch 2.5.
4221+
///
4222+
/// - `"stackerdb"`: Subscribes to StackerDB chunk update events.
4223+
/// - **Events delivered to:** `/stackerdb_chunks`.
4224+
///
4225+
/// - `"block_proposal"`: Subscribes to block proposal response events (for Nakamoto consensus).
4226+
/// - **Events delivered to:** `/proposal_response`.
4227+
///
4228+
/// - **Smart Contract Event**: Subscribes to a specific smart contract event.
4229+
/// - **Format:** `"{contract_address}.{contract_name}::{event_name}"`
4230+
/// (e.g., `ST0000000000000000000000000000000000000000.my-contract::my-custom-event`)
4231+
/// - **Events delivered to:** `/new_block`, `/new_microblocks`.
4232+
/// - **Payload details:** The "events" array in the delivered payloads will be filtered for this specific event.
4233+
///
4234+
/// - **Asset Identifier for FT/NFT Events**: Subscribes to events (mint, burn, transfer) for a specific Fungible Token (FT) or Non-Fungible Token (NFT).
4235+
/// - **Format:** `"{contract_address}.{contract_name}.{asset_name}"`
4236+
/// (e.g., for an FT: `ST0000000000000000000000000000000000000000.my-ft-contract.my-fungible-token`)
4237+
/// - **Events delivered to:** `/new_block`, `/new_microblocks`.
4238+
/// - **Payload details:** The "events" array in the delivered payloads will be filtered for events related to the specified asset.
4239+
///
4240+
/// **Configuration:**
4241+
///
4242+
/// ```toml
4243+
/// # Example events_keys in TOML configuration:
4244+
/// events_keys = [
4245+
/// "burn_blocks",
4246+
/// "memtx",
4247+
/// "ST0000000000000000000000000000000000000000.my-contract::my-custom-event", # Smart contract event
4248+
/// "ST0000000000000000000000000000000000000000.token-contract.my-ft" # Fungible token asset event
4249+
/// ]
4250+
/// ```
4251+
///
4252+
/// Default: No default. This field is required.
40514253
pub events_keys: Vec<String>,
4254+
/// Maximum duration (in milliseconds) to wait for the observer endpoint to respond.
4255+
///
4256+
/// When the node sends an event notification to this observer, it will wait at most this long
4257+
/// for a successful HTTP response (status code 200) before considering the request timed out.
4258+
/// If a timeout occurs and retries are enabled (see [`EventObserverConfig::disable_retries`]),
4259+
/// the request will be attempted again according to the retry strategy.
4260+
///
4261+
/// Default: `1_000` ms (1 second).
40524262
pub timeout_ms: Option<u64>,
4263+
/// Controls whether the node should retry sending event notifications if delivery fails or times out.
4264+
///
4265+
/// - If `false` (default): The node will attempt to deliver event notifications persistently.
4266+
/// If an attempt fails (due to network error, timeout, or a non-200 HTTP response), the event
4267+
/// payload is saved and retried indefinitely. This ensures that all events will eventually be
4268+
/// delivered. However, this can cause the node's block processing to stall if an observer is
4269+
/// down, or indefinitely fails to process the event.
4270+
///
4271+
/// - If `true`: The node will make only a single attempt to deliver each event notification.
4272+
/// If this single attempt fails for any reason, the event is discarded, and no further retries
4273+
/// will be made for that specific event.
4274+
///
4275+
/// **Warning:** Setting this to `true` can lead to missed events if the observer endpoint is
4276+
/// temporarily unavailable or experiences issues. This setting should only be used for observers
4277+
/// where completeness of the event history is not critical.
4278+
///
4279+
/// Default: `false` (retries are enabled).
40534280
pub disable_retries: Option<bool>,
40544281
}
40554282

@@ -4152,7 +4379,15 @@ pub struct InitialBalance {
41524379
#[derive(Clone, Deserialize, Default, Debug)]
41534380
#[serde(deny_unknown_fields)]
41544381
pub struct InitialBalanceFile {
4382+
/// The Stacks address to receive the initial STX balance.
4383+
/// Must be a valid "non-mainnet" Stacks address (e.g., "ST2QKZ4FKHAH1NQKYKYAYZPY440FEPK7GZ1R5HBP2").
4384+
///
4385+
/// Default: No default. This field is required.
41554386
pub address: String,
4387+
/// The amount of microSTX to allocate to the address at node startup.
4388+
/// 1 STX = 1_000_000 microSTX.
4389+
///
4390+
/// Default: No default. This field is required.
41564391
pub amount: u64,
41574392
}
41584393

stackslib/src/net/db.rs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -103,29 +103,17 @@ pub struct LocalPeer {
103103

104104
impl fmt::Display for LocalPeer {
105105
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106-
write!(
107-
f,
108-
"local.{:x}://(bind={:?})(pub={:?})",
109-
self.network_id,
110-
&self.addrbytes.to_socketaddr(self.port),
111-
&self
112-
.public_ip_address
113-
.map(|(ref addrbytes, ref port)| addrbytes.to_socketaddr(*port))
114-
)
106+
write!(f, "local::{}", self.port)?;
107+
match &self.public_ip_address {
108+
None => Ok(()),
109+
Some((addr, port)) => write!(f, "::pub={}", addr.to_socketaddr(*port)),
110+
}
115111
}
116112
}
117113

118114
impl fmt::Debug for LocalPeer {
119115
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120-
write!(
121-
f,
122-
"local.{:x}://(bind={:?})(pub={:?})",
123-
self.network_id,
124-
&self.addrbytes.to_socketaddr(self.port),
125-
&self
126-
.public_ip_address
127-
.map(|(ref addrbytes, ref port)| addrbytes.to_socketaddr(*port))
128-
)
116+
write!(f, "{self}")
129117
}
130118
}
131119

0 commit comments

Comments
 (0)