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

stacks-network / stacks-core / 24140301216

08 Apr 2026 02:18PM UTC coverage: 46.817% (-38.9%) from 85.712%
24140301216

Pull #6959

github

279acf
web-flow
Merge efbee1783 into 882e27245
Pull Request #6959: Perf/cache epoch version in ClarityDatabase

66 of 149 new or added lines in 8 files covered. (44.3%)

85999 existing lines in 334 files now uncovered.

102056 of 217989 relevant lines covered (46.82%)

12315981.16 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 {
735,210✔
103
        if self.data.len() < 128 {
735,210✔
104
            write!(
61,670✔
105
                f,
61,670✔
106
                "StackerDBChunkData({},{},{},{})",
107
                self.slot_id,
108
                self.slot_version,
109
                &self.sig,
61,670✔
110
                &to_hex(&self.data)
61,670✔
111
            )
112
        } else {
113
            write!(
673,540✔
114
                f,
673,540✔
115
                "StackerDBChunkData({},{},{},{}...({}))",
116
                self.slot_id,
117
                self.slot_version,
118
                &self.sig,
673,540✔
119
                &to_hex(&self.data[..128]),
673,540✔
120
                self.data.len()
673,540✔
121
            )
122
        }
123
    }
735,210✔
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
UNCOV
146
    pub fn new_unsigned(
×
UNCOV
147
        slot_id: u32,
×
UNCOV
148
        slot_version: u32,
×
UNCOV
149
        data_hash: Sha512Trunc256Sum,
×
UNCOV
150
    ) -> SlotMetadata {
×
UNCOV
151
        SlotMetadata {
×
UNCOV
152
            slot_id,
×
UNCOV
153
            slot_version,
×
UNCOV
154
            data_hash,
×
UNCOV
155
            signature: MessageSignature::empty(),
×
UNCOV
156
        }
×
UNCOV
157
    }
×
158

159
    /// Get the digest to sign that authenticates this chunk data and metadata
160
    fn auth_digest(&self) -> Sha512Trunc256Sum {
20,788,100✔
161
        let mut hasher = Sha512_256::new();
20,788,100✔
162
        hasher.update(self.slot_id.to_be_bytes());
20,788,100✔
163
        hasher.update(self.slot_version.to_be_bytes());
20,788,100✔
164
        hasher.update(self.data_hash.0);
20,788,100✔
165
        Sha512Trunc256Sum::from_hasher(hasher)
20,788,100✔
166
    }
20,788,100✔
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> {
549,870✔
172
        let auth_digest = self.auth_digest();
549,870✔
173
        let sig = privkey
549,870✔
174
            .sign(&auth_digest.0)
549,870✔
175
            .map_err(|se| Error::SigningError(se.to_string()))?;
549,870✔
176

177
        self.signature = sig;
549,870✔
178
        Ok(())
549,870✔
179
    }
549,870✔
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> {
17,042,470✔
184
        let sigh = self.auth_digest();
17,042,470✔
185
        let pubk = StacksPublicKey::recover_to_pubkey(sigh.as_bytes(), &self.signature)
17,042,470✔
186
            .map_err(|ve| Error::VerifyingError(ve.to_string()))?;
17,042,470✔
187

188
        let pubkh = Hash160::from_node_public_key(&pubk);
17,042,470✔
189
        Ok(pubkh == *principal.bytes())
17,042,470✔
190
    }
17,042,470✔
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 {
549,870✔
197
        StackerDBChunkData {
549,870✔
198
            slot_id,
549,870✔
199
            slot_version,
549,870✔
200
            sig: MessageSignature::empty(),
549,870✔
201
            data,
549,870✔
202
        }
549,870✔
203
    }
549,870✔
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 {
21,522,780✔
207
        Sha512Trunc256Sum::from_data(&self.data)
21,522,780✔
208
    }
21,522,780✔
209

210
    /// Create an owned SlotMetadata describing the metadata of this slot.
211
    pub fn get_slot_metadata(&self) -> SlotMetadata {
20,788,170✔
212
        SlotMetadata {
20,788,170✔
213
            slot_id: self.slot_id,
20,788,170✔
214
            slot_version: self.slot_version,
20,788,170✔
215
            data_hash: self.data_hash(),
20,788,170✔
216
            signature: self.sig.clone(),
20,788,170✔
217
        }
20,788,170✔
218
    }
20,788,170✔
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> {
549,870✔
224
        let mut md = self.get_slot_metadata();
549,870✔
225
        md.sign(privk)?;
549,870✔
226
        self.sig = md.signature;
549,870✔
227
        Ok(())
549,870✔
228
    }
549,870✔
229

230
    pub fn recover_pk(&self) -> Result<StacksPublicKey, Error> {
3,195,760✔
231
        let digest = self.get_slot_metadata().auth_digest();
3,195,760✔
232
        StacksPublicKey::recover_to_pubkey(digest.as_bytes(), &self.sig)
3,195,760✔
233
            .map_err(|ve| Error::VerifyingError(ve.to_string()))
3,195,760✔
234
    }
3,195,760✔
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> {
15,353,540✔
239
        let md = self.get_slot_metadata();
15,353,540✔
240
        md.verify(addr)
15,353,540✔
241
    }
15,353,540✔
242
}
243

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

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

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

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

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

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

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