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

stacks-network / stacks-core / 23757460166

30 Mar 2026 05:08PM UTC coverage: 46.858% (-38.9%) from 85.712%
23757460166

Pull #7058

github

cb13d9
web-flow
Merge 5b10bfbb9 into 7a9774e50
Pull Request #7058: test: fix flakiness in `check_capitulate_miner_view`

101943 of 217556 relevant lines covered (46.86%)

12736976.58 hits per line

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

77.7
/libstackerdb/src/libstackerdb.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2023 Stacks Open Internet Foundation
3
//
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

17
extern crate clarity;
18
extern crate serde;
19
extern crate sha2;
20
extern crate stacks_common;
21

22
use std::io::{Read, Write};
23
use std::{error, fmt};
24

25
use clarity::vm::types::QualifiedContractIdentifier;
26
use serde::{Deserialize, Serialize};
27
use sha2::{Digest, Sha512_256};
28
use stacks_common::codec::{
29
    read_next, read_next_at_most, write_next, Error as CodecError, StacksMessageCodec,
30
};
31
use stacks_common::types::chainstate::{StacksAddress, StacksPrivateKey, StacksPublicKey};
32
use stacks_common::types::PrivateKey;
33
use stacks_common::util::hash::{hex_bytes, to_hex, Hash160, Sha512Trunc256Sum};
34
use stacks_common::util::secp256k1::MessageSignature;
35

36
/// maximum chunk size (16 MB; same as MAX_PAYLOAD_SIZE)
37
pub const STACKERDB_MAX_CHUNK_SIZE: u32 = 16 * 1024 * 1024;
38
/// CHUNK_SIZE constant for signers StackerDBs (2MB)
39
pub const SIGNERS_STACKERDB_CHUNK_SIZE: usize = 2 * 1024 * 1024; // 2MB
40

41
#[cfg(test)]
42
mod tests;
43

44
#[derive(Debug)]
45
pub enum Error {
46
    /// Error signing a message
47
    SigningError(String),
48
    /// Error verifying a message
49
    VerifyingError(String),
50
}
51

52
impl fmt::Display for Error {
53
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
×
54
        match *self {
×
55
            Error::SigningError(ref s) => fmt::Display::fmt(s, f),
×
56
            Error::VerifyingError(ref s) => fmt::Display::fmt(s, f),
×
57
        }
58
    }
×
59
}
60

61
impl error::Error for Error {
62
    fn cause(&self) -> Option<&dyn error::Error> {
×
63
        match *self {
×
64
            Error::SigningError(ref _s) => None,
×
65
            Error::VerifyingError(ref _s) => None,
×
66
        }
67
    }
×
68
}
69

70
/// Slot metadata from the DB.
71
/// This is derived state from a StackerDBChunkData message.
72
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
73
pub struct SlotMetadata {
74
    /// Slot identifier (unique for each DB instance)
75
    pub slot_id: u32,
76
    /// Slot version (a lamport clock)
77
    pub slot_version: u32,
78
    /// data hash
79
    pub data_hash: Sha512Trunc256Sum,
80
    /// signature over the above
81
    pub signature: MessageSignature,
82
}
83

84
/// Stacker DB chunk (i.e. as a reply to a chunk request)
85
#[derive(Clone, PartialEq, Serialize, Deserialize)]
86
pub struct StackerDBChunkData {
87
    /// slot ID
88
    pub slot_id: u32,
89
    /// slot version (a lamport clock)
90
    pub slot_version: u32,
91
    /// signature from the stacker over (slot id, slot version, chunk sha512/256)
92
    pub sig: MessageSignature,
93
    /// the chunk data
94
    #[serde(
95
        serialize_with = "stackerdb_chunk_hex_serialize",
96
        deserialize_with = "stackerdb_chunk_hex_deserialize"
97
    )]
98
    pub data: Vec<u8>,
99
}
100

101
impl fmt::Debug for StackerDBChunkData {
102
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
745,330✔
103
        if self.data.len() < 128 {
745,330✔
104
            write!(
64,200✔
105
                f,
64,200✔
106
                "StackerDBChunkData({},{},{},{})",
107
                self.slot_id,
108
                self.slot_version,
109
                &self.sig,
64,200✔
110
                &to_hex(&self.data)
64,200✔
111
            )
112
        } else {
113
            write!(
681,130✔
114
                f,
681,130✔
115
                "StackerDBChunkData({},{},{},{}...({}))",
116
                self.slot_id,
117
                self.slot_version,
118
                &self.sig,
681,130✔
119
                &to_hex(&self.data[..128]),
681,130✔
120
                self.data.len()
681,130✔
121
            )
122
        }
123
    }
745,330✔
124
}
125

126
/// StackerDB post chunk acknowledgement
127
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
128
pub struct StackerDBChunkAckData {
129
    pub accepted: bool,
130
    #[serde(skip_serializing_if = "Option::is_none")]
131
    pub reason: Option<String>,
132
    #[serde(skip_serializing_if = "Option::is_none")]
133
    pub metadata: Option<SlotMetadata>,
134
    #[serde(skip_serializing_if = "Option::is_none")]
135
    pub code: Option<u32>,
136
}
137

138
impl fmt::Display for StackerDBChunkAckData {
139
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
×
140
        write!(f, "{self:?}")
×
141
    }
×
142
}
143

144
impl SlotMetadata {
145
    /// Make a new unsigned slot metadata
146
    pub fn new_unsigned(
×
147
        slot_id: u32,
×
148
        slot_version: u32,
×
149
        data_hash: Sha512Trunc256Sum,
×
150
    ) -> SlotMetadata {
×
151
        SlotMetadata {
×
152
            slot_id,
×
153
            slot_version,
×
154
            data_hash,
×
155
            signature: MessageSignature::empty(),
×
156
        }
×
157
    }
×
158

159
    /// Get the digest to sign that authenticates this chunk data and metadata
160
    fn auth_digest(&self) -> Sha512Trunc256Sum {
19,450,420✔
161
        let mut hasher = Sha512_256::new();
19,450,420✔
162
        hasher.update(self.slot_id.to_be_bytes());
19,450,420✔
163
        hasher.update(self.slot_version.to_be_bytes());
19,450,420✔
164
        hasher.update(self.data_hash.0);
19,450,420✔
165
        Sha512Trunc256Sum::from_hasher(hasher)
19,450,420✔
166
    }
19,450,420✔
167

168
    /// Sign this slot metadata, committing to slot_id, slot_version, and
169
    /// data_hash.  Sets self.signature to the signature.
170
    /// Fails if the underlying crypto library fails
171
    pub fn sign(&mut self, privkey: &StacksPrivateKey) -> Result<(), Error> {
553,130✔
172
        let auth_digest = self.auth_digest();
553,130✔
173
        let sig = privkey
553,130✔
174
            .sign(&auth_digest.0)
553,130✔
175
            .map_err(|se| Error::SigningError(se.to_string()))?;
553,130✔
176

177
        self.signature = sig;
553,130✔
178
        Ok(())
553,130✔
179
    }
553,130✔
180

181
    /// Verify that a given principal signed this chunk metadata.
182
    /// Note that the address version is ignored.
183
    pub fn verify(&self, principal: &StacksAddress) -> Result<bool, Error> {
15,697,210✔
184
        let sigh = self.auth_digest();
15,697,210✔
185
        let pubk = StacksPublicKey::recover_to_pubkey(sigh.as_bytes(), &self.signature)
15,697,210✔
186
            .map_err(|ve| Error::VerifyingError(ve.to_string()))?;
15,697,210✔
187

188
        let pubkh = Hash160::from_node_public_key(&pubk);
15,697,210✔
189
        Ok(pubkh == *principal.bytes())
15,697,210✔
190
    }
15,697,210✔
191
}
192

193
/// Helper methods for StackerDBChunkData messages
194
impl StackerDBChunkData {
195
    /// Create a new StackerDBChunkData instance.
196
    pub fn new(slot_id: u32, slot_version: u32, data: Vec<u8>) -> StackerDBChunkData {
553,130✔
197
        StackerDBChunkData {
553,130✔
198
            slot_id,
553,130✔
199
            slot_version,
553,130✔
200
            sig: MessageSignature::empty(),
553,130✔
201
            data,
553,130✔
202
        }
553,130✔
203
    }
553,130✔
204

205
    /// Calculate the hash of the chunk bytes.  This is the SHA512/256 hash of the data.
206
    pub fn data_hash(&self) -> Sha512Trunc256Sum {
20,195,030✔
207
        Sha512Trunc256Sum::from_data(&self.data)
20,195,030✔
208
    }
20,195,030✔
209

210
    /// Create an owned SlotMetadata describing the metadata of this slot.
211
    pub fn get_slot_metadata(&self) -> SlotMetadata {
19,450,420✔
212
        SlotMetadata {
19,450,420✔
213
            slot_id: self.slot_id,
19,450,420✔
214
            slot_version: self.slot_version,
19,450,420✔
215
            data_hash: self.data_hash(),
19,450,420✔
216
            signature: self.sig.clone(),
19,450,420✔
217
        }
19,450,420✔
218
    }
19,450,420✔
219

220
    /// Sign this given chunk data message with the given private key.
221
    /// Sets self.signature to the signature.
222
    /// Fails if the underlying signing library fails.
223
    pub fn sign(&mut self, privk: &StacksPrivateKey) -> Result<(), Error> {
553,130✔
224
        let mut md = self.get_slot_metadata();
553,130✔
225
        md.sign(privk)?;
553,130✔
226
        self.sig = md.signature;
553,130✔
227
        Ok(())
553,130✔
228
    }
553,130✔
229

230
    pub fn recover_pk(&self) -> Result<StacksPublicKey, Error> {
3,200,080✔
231
        let digest = self.get_slot_metadata().auth_digest();
3,200,080✔
232
        StacksPublicKey::recover_to_pubkey(digest.as_bytes(), &self.sig)
3,200,080✔
233
            .map_err(|ve| Error::VerifyingError(ve.to_string()))
3,200,080✔
234
    }
3,200,080✔
235

236
    /// Verify that this chunk was signed by the given
237
    /// public key hash (`addr`).  Only fails if the underlying signing library fails.
238
    pub fn verify(&self, addr: &StacksAddress) -> Result<bool, Error> {
13,961,810✔
239
        let md = self.get_slot_metadata();
13,961,810✔
240
        md.verify(addr)
13,961,810✔
241
    }
13,961,810✔
242
}
243

244
impl StacksMessageCodec for StackerDBChunkData {
245
    fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), CodecError> {
1,085,706✔
246
        write_next(fd, &self.slot_id)?;
1,085,706✔
247
        write_next(fd, &self.slot_version)?;
1,085,706✔
248
        write_next(fd, &self.sig)?;
1,085,706✔
249
        write_next(fd, &self.data)?;
1,085,706✔
250
        Ok(())
1,085,706✔
251
    }
1,085,706✔
252

253
    fn consensus_deserialize<R: Read>(fd: &mut R) -> Result<StackerDBChunkData, CodecError> {
542,457✔
254
        let slot_id: u32 = read_next(fd)?;
542,457✔
255
        let slot_version: u32 = read_next(fd)?;
542,457✔
256
        let sig: MessageSignature = read_next(fd)?;
542,457✔
257
        let data: Vec<u8> = read_next_at_most(fd, STACKERDB_MAX_CHUNK_SIZE)?;
542,457✔
258
        Ok(StackerDBChunkData {
542,457✔
259
            slot_id,
542,457✔
260
            slot_version,
542,457✔
261
            sig,
542,457✔
262
            data,
542,457✔
263
        })
542,457✔
264
    }
542,457✔
265
}
266

267
fn stackerdb_chunk_hex_serialize<S: serde::Serializer>(
858,789✔
268
    chunk: &[u8],
858,789✔
269
    s: S,
858,789✔
270
) -> Result<S::Ok, S::Error> {
858,789✔
271
    let inst = to_hex(chunk);
858,789✔
272
    s.serialize_str(inst.as_str())
858,789✔
273
}
858,789✔
274

275
fn stackerdb_chunk_hex_deserialize<'de, D: serde::Deserializer<'de>>(
3,148,119✔
276
    d: D,
3,148,119✔
277
) -> Result<Vec<u8>, D::Error> {
3,148,119✔
278
    let inst_str = String::deserialize(d)?;
3,148,119✔
279
    hex_bytes(&inst_str).map_err(serde::de::Error::custom)
3,148,119✔
280
}
3,148,119✔
281

282
/// Calculate the GET path for a stacker DB metadata listing
283
pub fn stackerdb_get_metadata_path(contract_id: QualifiedContractIdentifier) -> String {
×
284
    format!(
×
285
        "/v2/stackerdb/{}/{}",
286
        &StacksAddress::from(contract_id.issuer),
×
287
        &contract_id.name
×
288
    )
289
}
×
290

291
/// Calculate the GET path for a stacker DB chunk
292
pub fn stackerdb_get_chunk_path(
147,000✔
293
    contract_id: QualifiedContractIdentifier,
147,000✔
294
    slot_id: u32,
147,000✔
295
    slot_version: Option<u32>,
147,000✔
296
) -> String {
147,000✔
297
    if let Some(version) = slot_version {
147,000✔
298
        format!(
×
299
            "/v2/stackerdb/{}/{}/{}/{}",
300
            &StacksAddress::from(contract_id.issuer),
×
301
            &contract_id.name,
×
302
            slot_id,
303
            version
304
        )
305
    } else {
306
        format!(
147,000✔
307
            "/v2/stackerdb/{}/{}/{}",
308
            &StacksAddress::from(contract_id.issuer),
147,000✔
309
            &contract_id.name,
147,000✔
310
            slot_id
311
        )
312
    }
313
}
147,000✔
314

315
/// Calculate POST path for a stacker DB chunk
316
pub fn stackerdb_post_chunk_path(contract_id: QualifiedContractIdentifier) -> String {
555,930✔
317
    format!(
555,930✔
318
        "/v2/stackerdb/{}/{}/chunks",
319
        &StacksAddress::from(contract_id.issuer),
555,930✔
320
        &contract_id.name
555,930✔
321
    )
322
}
555,930✔
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