• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacks-network / stacks-core / 25903914664-1

15 May 2026 06:28AM UTC coverage: 47.122% (-38.8%) from 85.959%
25903914664-1

Pull #7199

github

94e391
web-flow
Merge 109f2828c into 1c7b8e6ac
Pull Request #7199: Feat: L1 and L2 early unlocks, updating signer

103343 of 219309 relevant lines covered (47.12%)

12880462.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.96
/stackslib/src/net/api/getsigner.rs
1
// Copyright (C) 2024 Stacks Open Internet Foundation
2
//
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// This program is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
use clarity::util::secp256k1::Secp256k1PublicKey;
16
use regex::{Captures, Regex};
17
use stacks_common::types::net::PeerHost;
18

19
use crate::chainstate::nakamoto::NakamotoChainState;
20
use crate::net::http::{
21
    parse_json, Error, HttpNotFound, HttpRequest, HttpRequestContents, HttpRequestPreamble,
22
    HttpResponse, HttpResponseContents, HttpResponsePayload, HttpResponsePreamble,
23
};
24
use crate::net::httpcore::{RPCRequestHandler, StacksHttpRequest, StacksHttpResponse};
25
use crate::net::{Error as NetError, StacksNodeState};
26

27
#[derive(Clone, Default)]
28
pub struct GetSignerRequestHandler {
29
    pub signer_pubkey: Option<Secp256k1PublicKey>,
30
    pub reward_cycle: Option<u64>,
31
}
32

33
impl GetSignerRequestHandler {
34
    pub fn new() -> Self {
×
35
        Self {
×
36
            signer_pubkey: None,
×
37
            reward_cycle: None,
×
38
        }
×
39
    }
×
40
}
41

42
#[derive(Debug, Serialize, Deserialize)]
43
pub struct GetSignerResponse {
44
    pub blocks_signed: u64,
45
}
46

47
/// Decode the HTTP request
48
impl HttpRequest for GetSignerRequestHandler {
49
    fn verb(&self) -> &'static str {
1,059,014✔
50
        "GET"
1,059,014✔
51
    }
1,059,014✔
52

53
    fn path_regex(&self) -> Regex {
2,118,028✔
54
        Regex::new(
2,118,028✔
55
            r#"^/v3/signer/(?P<signer_pubkey>0[23][0-9a-f]{64})/(?P<cycle_num>[0-9]{1,10})$"#,
2,118,028✔
56
        )
57
        .unwrap()
2,118,028✔
58
    }
2,118,028✔
59

60
    fn metrics_identifier(&self) -> &str {
126✔
61
        "/v3/signer/:signer_pubkey/:cycle_num"
126✔
62
    }
126✔
63

64
    /// Try to decode this request.
65
    /// There's nothing to load here, so just make sure the request is well-formed.
66
    fn try_parse_request(
126✔
67
        &mut self,
126✔
68
        preamble: &HttpRequestPreamble,
126✔
69
        captures: &Captures,
126✔
70
        query: Option<&str>,
126✔
71
        _body: &[u8],
126✔
72
    ) -> Result<HttpRequestContents, Error> {
126✔
73
        if preamble.get_content_length() != 0 {
126✔
74
            return Err(Error::DecodeError(
×
75
                "Invalid Http request: expected 0-length body".into(),
×
76
            ));
×
77
        }
126✔
78

79
        let Some(cycle_num_str) = captures.name("cycle_num") else {
126✔
80
            return Err(Error::DecodeError(
×
81
                "Missing in request path: `cycle_num`".into(),
×
82
            ));
×
83
        };
84
        let Some(signer_pubkey_str) = captures.name("signer_pubkey") else {
126✔
85
            return Err(Error::DecodeError(
×
86
                "Missing in request path: `signer_pubkey`".into(),
×
87
            ));
×
88
        };
89

90
        let signer_pubkey = Secp256k1PublicKey::from_hex(signer_pubkey_str.into())
126✔
91
            .map_err(|e| Error::DecodeError(format!("Failed to signer public key: {e}")))?;
126✔
92

93
        let cycle_num = u64::from_str_radix(cycle_num_str.into(), 10)
126✔
94
            .map_err(|e| Error::DecodeError(format!("Failed to parse cycle number: {e}")))?;
126✔
95

96
        self.signer_pubkey = Some(signer_pubkey);
126✔
97
        self.reward_cycle = Some(cycle_num);
126✔
98

99
        Ok(HttpRequestContents::new().query_string(query))
126✔
100
    }
126✔
101
}
102

103
impl RPCRequestHandler for GetSignerRequestHandler {
104
    /// Reset internal state
105
    fn restart(&mut self) {
126✔
106
        self.signer_pubkey = None;
126✔
107
        self.reward_cycle = None;
126✔
108
    }
126✔
109

110
    /// Make the response
111
    fn try_handle_request(
126✔
112
        &mut self,
126✔
113
        preamble: HttpRequestPreamble,
126✔
114
        _contents: HttpRequestContents,
126✔
115
        node: &mut StacksNodeState,
126✔
116
    ) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
126✔
117
        let signer_pubkey = self
126✔
118
            .signer_pubkey
126✔
119
            .take()
126✔
120
            .ok_or(NetError::SendError("Missing `signer_pubkey`".into()))?;
126✔
121

122
        let reward_cycle = self
126✔
123
            .reward_cycle
126✔
124
            .take()
126✔
125
            .ok_or(NetError::SendError("Missing `reward_cycle`".into()))?;
126✔
126

127
        let result = node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| {
126✔
128
            NakamotoChainState::get_signer_block_count(
126✔
129
                &chainstate.index_conn(),
126✔
130
                &signer_pubkey,
126✔
131
                reward_cycle,
126✔
132
            )
133
        });
126✔
134

135
        let blocks_signed = match result {
126✔
136
            Ok(response) => response,
126✔
137
            Err(error) => {
×
138
                return StacksHttpResponse::new_error(
×
139
                    &preamble,
×
140
                    &HttpNotFound::new(error.to_string()),
×
141
                )
142
                .try_into_contents()
×
143
                .map_err(NetError::from);
×
144
            }
145
        };
146

147
        let response = GetSignerResponse { blocks_signed };
126✔
148

149
        let preamble = HttpResponsePreamble::ok_json(&preamble);
126✔
150
        let body = HttpResponseContents::try_from_json(&response)?;
126✔
151
        Ok((preamble, body))
126✔
152
    }
126✔
153
}
154

155
impl HttpResponse for GetSignerRequestHandler {
156
    fn try_parse_response(
×
157
        &self,
×
158
        preamble: &HttpResponsePreamble,
×
159
        body: &[u8],
×
160
    ) -> Result<HttpResponsePayload, Error> {
×
161
        let response: GetSignerResponse = parse_json(preamble, body)?;
×
162
        Ok(HttpResponsePayload::try_from_json(response)?)
×
163
    }
×
164
}
165

166
impl StacksHttpRequest {
167
    /// Make a new getinfo request to this endpoint
168
    pub fn new_getsigner(
×
169
        host: PeerHost,
×
170
        signer_pubkey: &Secp256k1PublicKey,
×
171
        cycle_num: u64,
×
172
    ) -> StacksHttpRequest {
×
173
        StacksHttpRequest::new_for_peer(
×
174
            host,
×
175
            "GET".into(),
×
176
            format!("/v3/signer/{}/{cycle_num}", signer_pubkey.to_hex()),
×
177
            HttpRequestContents::new(),
×
178
        )
179
        .expect("FATAL: failed to construct request from infallible data")
×
180
    }
×
181
}
182

183
impl StacksHttpResponse {
184
    pub fn decode_signer(self) -> Result<GetSignerResponse, NetError> {
×
185
        let contents = self.get_http_payload_ok()?;
×
186
        let response_json: serde_json::Value = contents.try_into()?;
×
187
        let response: GetSignerResponse = serde_json::from_value(response_json)
×
188
            .map_err(|_e| Error::DecodeError("Failed to decode JSON".to_string()))?;
×
189
        Ok(response)
×
190
    }
×
191
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc