Skip to content

Commit 784b55c

Browse files
authored
Merge branch 'develop' into feat/extend-bitcoin-reorg
2 parents 4a7dd42 + 7936329 commit 784b55c

File tree

18 files changed

+566
-244
lines changed

18 files changed

+566
-244
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ 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-
## [Unreleased]
8+
## [3.1.0.0.10]
99

1010
### Added
1111
- Persisted tracking of StackerDB slot versions for mining. This improves miner p2p performance.

libsigner/src/events.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ use blockstack_lib::chainstate::stacks::StacksTransaction;
2929
use blockstack_lib::net::api::postblock_proposal::{
3030
BlockValidateReject, BlockValidateResponse, ValidateRejectCode,
3131
};
32-
use blockstack_lib::net::api::{prefix_hex, prefix_opt_hex};
3332
use blockstack_lib::net::stackerdb::MINER_SLOT_COUNT;
3433
use blockstack_lib::util_lib::boot::boot_code_id;
3534
use blockstack_lib::version_string;
@@ -47,6 +46,7 @@ use stacks_common::types::chainstate::{
4746
BlockHeaderHash, BurnchainHeaderHash, ConsensusHash, SortitionId, StacksPublicKey,
4847
};
4948
use stacks_common::util::hash::{Hash160, Sha512Trunc256Sum};
49+
use stacks_common::util::serde_serializers::{prefix_hex, prefix_opt_hex};
5050
use stacks_common::util::HexError;
5151
use stacks_common::versions::STACKS_NODE_VERSION;
5252
use tiny_http::{

stacks-common/src/util/macros.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2-
// Copyright (C) 2020 Stacks Open Internet Foundation
2+
// Copyright (C) 2020-2025 Stacks Open Internet Foundation
33
//
44
// This program is free software: you can redistribute it and/or modify
55
// it under the terms of the GNU General Public License as published by
@@ -613,6 +613,12 @@ macro_rules! impl_byte_array_newtype {
613613
Self(o)
614614
}
615615
}
616+
617+
impl $crate::util::HexDeser for $thing {
618+
fn try_from_hex(hex_str: &str) -> Result<Self, $crate::util::HexError> {
619+
$thing::from_hex(hex_str)
620+
}
621+
}
616622
};
617623
}
618624

stacks-common/src/util/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub mod pair;
2727
pub mod pipe;
2828
pub mod retry;
2929
pub mod secp256k1;
30+
pub mod serde_serializers;
3031
pub mod uint;
3132
pub mod vrf;
3233

@@ -111,6 +112,10 @@ impl error::Error for HexError {
111112
}
112113
}
113114

115+
pub trait HexDeser: Sized {
116+
fn try_from_hex(hex: &str) -> Result<Self, HexError>;
117+
}
118+
114119
/// Write any `serde_json` object directly to a file
115120
pub fn serialize_json_to_file<J, P>(json: &J, path: P) -> Result<(), std::io::Error>
116121
where
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/// This module serde encodes and decodes optional byte fields in RPC
2+
/// responses as Some(String) where the String is a `0x` prefixed
3+
/// hex string.
4+
pub mod prefix_opt_hex {
5+
pub fn serialize<S: serde::Serializer, T: std::fmt::LowerHex>(
6+
val: &Option<T>,
7+
s: S,
8+
) -> Result<S::Ok, S::Error> {
9+
match val {
10+
Some(ref some_val) => {
11+
let val_str = format!("0x{some_val:x}");
12+
s.serialize_some(&val_str)
13+
}
14+
None => s.serialize_none(),
15+
}
16+
}
17+
18+
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: crate::util::HexDeser>(
19+
d: D,
20+
) -> Result<Option<T>, D::Error> {
21+
let opt_inst_str: Option<String> = serde::Deserialize::deserialize(d)?;
22+
let Some(inst_str) = opt_inst_str else {
23+
return Ok(None);
24+
};
25+
let Some(hex_str) = inst_str.get(2..) else {
26+
return Err(serde::de::Error::invalid_length(
27+
inst_str.len(),
28+
&"at least length 2 string",
29+
));
30+
};
31+
let val = T::try_from_hex(hex_str).map_err(serde::de::Error::custom)?;
32+
Ok(Some(val))
33+
}
34+
}
35+
36+
/// This module serde encodes and decodes byte fields in RPC
37+
/// responses as a String where the String is a `0x` prefixed
38+
/// hex string.
39+
pub mod prefix_hex {
40+
pub fn serialize<S: serde::Serializer, T: std::fmt::LowerHex>(
41+
val: &T,
42+
s: S,
43+
) -> Result<S::Ok, S::Error> {
44+
s.serialize_str(&format!("0x{val:x}"))
45+
}
46+
47+
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: crate::util::HexDeser>(
48+
d: D,
49+
) -> Result<T, D::Error> {
50+
let inst_str: String = serde::Deserialize::deserialize(d)?;
51+
let Some(hex_str) = inst_str.get(2..) else {
52+
return Err(serde::de::Error::invalid_length(
53+
inst_str.len(),
54+
&"at least length 2 string",
55+
));
56+
};
57+
T::try_from_hex(hex_str).map_err(serde::de::Error::custom)
58+
}
59+
}
60+
61+
/// This module serde encode and decodes structs that
62+
/// implement StacksMessageCodec as a 0x-prefixed hex string.
63+
pub mod prefix_hex_codec {
64+
use crate::codec::StacksMessageCodec;
65+
use crate::util::hash::{hex_bytes, to_hex};
66+
67+
pub fn serialize<S: serde::Serializer, T: StacksMessageCodec>(
68+
val: &T,
69+
s: S,
70+
) -> Result<S::Ok, S::Error> {
71+
let mut bytes = vec![];
72+
val.consensus_serialize(&mut bytes)
73+
.map_err(serde::ser::Error::custom)?;
74+
s.serialize_str(&format!("0x{}", to_hex(&bytes)))
75+
}
76+
77+
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: StacksMessageCodec>(
78+
d: D,
79+
) -> Result<T, D::Error> {
80+
let inst_str: String = serde::Deserialize::deserialize(d)?;
81+
let Some(hex_str) = inst_str.get(2..) else {
82+
return Err(serde::de::Error::invalid_length(
83+
inst_str.len(),
84+
&"at least length 2 string",
85+
));
86+
};
87+
let bytes = hex_bytes(hex_str).map_err(serde::de::Error::custom)?;
88+
T::consensus_deserialize(&mut &bytes[..]).map_err(serde::de::Error::custom)
89+
}
90+
}
91+
92+
/// This module serde encode and decodes structs that
93+
/// implement StacksMessageCodec as a 0x-prefixed hex string.
94+
/// This is the same as prefix_hex_codec, but for Option<T>.
95+
pub mod prefix_opt_hex_codec {
96+
use crate::codec::StacksMessageCodec;
97+
use crate::util::hash::{hex_bytes, to_hex};
98+
99+
pub fn serialize<S: serde::Serializer, T: StacksMessageCodec>(
100+
val: &Option<T>,
101+
s: S,
102+
) -> Result<S::Ok, S::Error> {
103+
match val {
104+
Some(ref some_val) => {
105+
let mut bytes = vec![];
106+
some_val
107+
.consensus_serialize(&mut bytes)
108+
.map_err(serde::ser::Error::custom)?;
109+
let hex_string = format!("0x{}", to_hex(&bytes));
110+
s.serialize_some(&hex_string)
111+
}
112+
None => s.serialize_none(),
113+
}
114+
}
115+
116+
pub fn deserialize<'de, D: serde::Deserializer<'de>, T: StacksMessageCodec>(
117+
d: D,
118+
) -> Result<Option<T>, D::Error> {
119+
let opt_inst_str: Option<String> = serde::Deserialize::deserialize(d)?;
120+
let Some(inst_string) = opt_inst_str else {
121+
return Ok(None);
122+
};
123+
let Some(hex_str) = inst_string.get(2..) else {
124+
return Err(serde::de::Error::invalid_length(
125+
inst_string.len(),
126+
&"at least length 2 string",
127+
));
128+
};
129+
let bytes = hex_bytes(hex_str).map_err(serde::de::Error::custom)?;
130+
let val = T::consensus_deserialize(&mut &bytes[..]).map_err(serde::de::Error::custom)?;
131+
Ok(Some(val))
132+
}
133+
}
134+
135+
/// Serialize strings as 0x-prefixed strings.
136+
pub mod prefix_string_0x {
137+
use serde::{Deserialize, Deserializer, Serializer};
138+
139+
pub fn serialize<S: Serializer>(val: &str, s: S) -> Result<S::Ok, S::Error> {
140+
s.serialize_str(&format!("0x{}", val))
141+
}
142+
143+
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<String, D::Error> {
144+
let s: String = Deserialize::deserialize(d)?;
145+
let Some(hex_str) = s.get(2..) else {
146+
return Err(serde::de::Error::invalid_length(
147+
s.len(),
148+
&"at least length 2 string",
149+
));
150+
};
151+
Ok(hex_str.to_string())
152+
}
153+
}

stacks-signer/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ 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-
## [Unreleased]
8+
## [3.1.0.0.10.0]
99

1010
### Added
1111
- Persisted tracking of StackerDB slot versions. This improves signer p2p performance.

stacks-signer/src/client/mod.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use libstackerdb::Error as StackerDBError;
2828
pub use stackerdb::*;
2929
pub use stacks_client::*;
3030
use stacks_common::codec::Error as CodecError;
31-
use stacks_common::debug;
31+
use stacks_common::{debug, warn};
3232

3333
/// Backoff timer initial interval in milliseconds
3434
const BACKOFF_INITIAL_INTERVAL: u64 = 128;
@@ -103,7 +103,7 @@ pub enum ClientError {
103103
pub fn retry_with_exponential_backoff<F, E, T>(request_fn: F) -> Result<T, ClientError>
104104
where
105105
F: FnMut() -> Result<T, backoff::Error<E>>,
106-
E: std::fmt::Debug,
106+
E: std::fmt::Debug + Into<ClientError>,
107107
{
108108
let notify = |err, dur| {
109109
debug!(
@@ -117,7 +117,16 @@ where
117117
.with_max_elapsed_time(Some(Duration::from_secs(BACKOFF_MAX_ELAPSED)))
118118
.build();
119119

120-
backoff::retry_notify(backoff_timer, request_fn, notify).map_err(|_| ClientError::RetryTimeout)
120+
backoff::retry_notify(backoff_timer, request_fn, notify).map_err(|e| match e {
121+
backoff::Error::Permanent(err) => {
122+
warn!("Non-retry error during request: {err:?}");
123+
err.into()
124+
}
125+
backoff::Error::Transient { err, .. } => {
126+
warn!("Exceeded max retries during request: {err:?}");
127+
err.into()
128+
}
129+
})
121130
}
122131

123132
#[cfg(test)]

stacks-signer/src/client/stacks_client.rs

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
use std::collections::{HashMap, VecDeque};
17-
use std::fmt::Display;
18-
use std::time::{Duration, Instant};
1917

2018
use blockstack_lib::chainstate::nakamoto::NakamotoBlock;
2119
use blockstack_lib::chainstate::stacks::boot::{NakamotoSignerEntry, SIGNERS_NAME};
@@ -602,36 +600,6 @@ impl StacksClient {
602600
Ok(account_entry)
603601
}
604602

605-
/// Post a block to the stacks-node, retry forever on errors.
606-
///
607-
/// In tests, this panics if the retry takes longer than 30 seconds.
608-
pub fn post_block_until_ok<F: Display>(&self, log_fmt: &F, block: &NakamotoBlock) -> bool {
609-
debug!("StacksClient: Posting block to stacks node";
610-
"signer_signature_hash" => %block.header.signer_signature_hash(),
611-
"block_id" => %block.header.block_id(),
612-
"block_height" => %block.header.chain_length,
613-
);
614-
let start_time = Instant::now();
615-
loop {
616-
match self.post_block(block) {
617-
Ok(block_push_result) => {
618-
debug!("{log_fmt}: Block pushed to stacks node: {block_push_result:?}");
619-
return block_push_result;
620-
}
621-
Err(e) => {
622-
if cfg!(any(test, feature = "testing"))
623-
&& start_time.elapsed() > Duration::from_secs(30)
624-
{
625-
panic!(
626-
"{log_fmt}: Timed out in test while pushing block to stacks node: {e}"
627-
);
628-
}
629-
warn!("{log_fmt}: Failed to push block to stacks node: {e}. Retrying...");
630-
}
631-
};
632-
}
633-
}
634-
635603
/// Try to post a completed nakamoto block to our connected stacks-node
636604
/// Returns `true` if the block was accepted or `false` if the block
637605
/// was rejected.
@@ -644,22 +612,30 @@ impl StacksClient {
644612
let path = format!("{}{}?broadcast=1", self.http_origin, postblock_v3::PATH);
645613
let timer = crate::monitoring::actions::new_rpc_call_timer(&path, &self.http_origin);
646614
let send_request = || {
647-
self.stacks_node_client
615+
let response = self
616+
.stacks_node_client
648617
.post(&path)
649618
.header("Content-Type", "application/octet-stream")
650619
.header(AUTHORIZATION, self.auth_password.clone())
651620
.body(block.serialize_to_vec())
652621
.send()
653622
.map_err(|e| {
654623
debug!("Failed to submit block to the Stacks node: {e:?}");
655-
backoff::Error::transient(e)
656-
})
624+
backoff::Error::transient(ClientError::from(e))
625+
})?;
626+
if !response.status().is_success() {
627+
warn!(
628+
"Failed to post block to stacks-node, will retry until limit reached";
629+
"http_status" => %response.status(),
630+
);
631+
return Err(backoff::Error::transient(ClientError::RequestFailure(
632+
response.status(),
633+
)));
634+
}
635+
Ok(response)
657636
};
658637
let response = retry_with_exponential_backoff(send_request)?;
659638
timer.stop_and_record();
660-
if !response.status().is_success() {
661-
return Err(ClientError::RequestFailure(response.status()));
662-
}
663639
let post_block_resp = response.json::<StacksBlockAcceptedData>()?;
664640
Ok(post_block_resp.accepted)
665641
}

0 commit comments

Comments
 (0)