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

tari-project / tari / 15998966652

01 Jul 2025 12:13PM UTC coverage: 71.633% (-0.05%) from 71.686%
15998966652

push

github

web-flow
fix!: payref migration and indexes, add grpc query via output hash (#7266)

Description
---
- Added gRPC method to retrieve output information via output hash
- Updated the gRPC payref search to return spent and unspent output
information
- Added migration to rebuild payref indexes due to missing payrefs
(_added periodic call to `LMDBStore::resize_if_required`_)
- Fixed error in lmdb migration counter where it recorded a higher
version done than what it should

Fixes #7263 

Motivation and Context
---
- Payref information was deleted when the outputs were spent.
- With migrations, payref information was only created for the current
output set, not all outputs.
- Payref lookups for spent outputs were not possible.
- Output and payref information was not possible via output hash.

How Has This Been Tested?
---
- New lmdb migration tested at the system level for fresh and existing
base nodes.
- New gRPC method system-level testing [_tested with block explorer
upgrade_].
- General system-level testing.

Fresh base node with a restart afterwards
```rust
2025-06-27 09:09:24.013840800 [c::cs::lmdb_db::lmdb_db] INFO  [MIGRATIONS] Blockchain database is at v0 (required version: 5)
2025-06-27 09:09:24.013873800 [c::cs::lmdb_db::lmdb_db] INFO  [MIGRATIONS] v1: No accumulated difficulty found for block height 14999
2025-06-27 09:09:24.013877800 [c::cs::lmdb_db::lmdb_db] INFO  [MIGRATIONS] v1: No migration to perform for version network
2025-06-27 09:09:24.013883100 [c::cs::lmdb_db::lmdb_db] INFO  [MIGRATIONS] v2: Starting PayRef migration
2025-06-27 09:09:24.013899700 [c::cs::lmdb_db::lmdb_db] INFO  [MIGRATIONS] v2: Cleared PayRef index
2025-06-27 09:09:24.038671500 [c::cs::lmdb_db::lmdb_db] INFO  [MIGRATIONS] v2: PayRef index rebuild completed
2025-06-27 09:09:24.038693200 [c::cs::lmdb_db::lmdb_db] INFO  [MIGRATIONS] Migrated database from version 2 to version 3
2025-06-27 09:09:24.039646900 [c::cs::lmdb_db::lmdb_db] INFO  [MIGRATIONS] Migrated database from ver... (continued)

24 of 136 new or added lines in 11 files covered. (17.65%)

68 existing lines in 17 files now uncovered.

82832 of 115634 relevant lines covered (71.63%)

239334.93 hits per line

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

17.42
/base_layer/core/src/base_node/comms_interface/local_interface.rs
1
// Copyright 2019. The Tari Project
2
//
3
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4
// following conditions are met:
5
//
6
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7
// disclaimer.
8
//
9
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
10
// following disclaimer in the documentation and/or other materials provided with the distribution.
11
//
12
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
13
// products derived from this software without specific prior written permission.
14
//
15
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
21
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22

23
use std::{ops::RangeInclusive, sync::Arc};
24

25
use tari_common_types::{
26
    chain_metadata::ChainMetadata,
27
    types::{BlockHash, CompressedCommitment, CompressedPublicKey, FixedHash, HashOutput, Signature},
28
};
29
use tari_service_framework::{reply_channel::SenderService, Service};
30
use tokio::sync::broadcast;
31

32
use crate::{
33
    base_node::comms_interface::{
34
        comms_request::GetNewBlockTemplateRequest,
35
        error::CommsInterfaceError,
36
        BlockEvent,
37
        NodeCommsRequest,
38
        NodeCommsResponse,
39
    },
40
    blocks::{Block, ChainHeader, HistoricalBlock, NewBlockTemplate},
41
    chain_storage::{InputMinedInfo, MinedInfo, OutputMinedInfo, TemplateRegistrationEntry},
42
    proof_of_work::{Difficulty, PowAlgorithm},
43
    transactions::transaction_components::{TransactionKernel, TransactionOutput},
44
};
45

46
pub type BlockEventSender = broadcast::Sender<Arc<BlockEvent>>;
47
pub type BlockEventReceiver = broadcast::Receiver<Arc<BlockEvent>>;
48

49
/// The InboundNodeCommsInterface provides an interface to request information from the current local node by other
50
/// internal services.
51
#[derive(Clone)]
52
pub struct LocalNodeCommsInterface {
53
    request_sender: SenderService<NodeCommsRequest, Result<NodeCommsResponse, CommsInterfaceError>>,
54
    block_sender: SenderService<Block, Result<BlockHash, CommsInterfaceError>>,
55
    block_event_sender: BlockEventSender,
56
}
57

58
impl LocalNodeCommsInterface {
59
    /// Construct a new LocalNodeCommsInterface with the specified SenderService.
60
    pub fn new(
62✔
61
        request_sender: SenderService<NodeCommsRequest, Result<NodeCommsResponse, CommsInterfaceError>>,
62✔
62
        block_sender: SenderService<Block, Result<BlockHash, CommsInterfaceError>>,
62✔
63
        block_event_sender: BlockEventSender,
62✔
64
    ) -> Self {
62✔
65
        Self {
62✔
66
            request_sender,
62✔
67
            block_sender,
62✔
68
            block_event_sender,
62✔
69
        }
62✔
70
    }
62✔
71

72
    pub fn get_block_event_stream(&self) -> BlockEventReceiver {
107✔
73
        self.block_event_sender.subscribe()
107✔
74
    }
107✔
75

76
    /// Request metadata from the current local node.
77
    pub async fn get_metadata(&mut self) -> Result<ChainMetadata, CommsInterfaceError> {
82✔
78
        match self.request_sender.call(NodeCommsRequest::GetChainMetadata).await?? {
82✔
79
            NodeCommsResponse::ChainMetadata(metadata) => Ok(metadata),
82✔
80
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
81
        }
82
    }
82✔
83

84
    pub async fn get_target_difficulty_for_next_block(
×
85
        &mut self,
×
86
        algo: PowAlgorithm,
×
87
    ) -> Result<Difficulty, CommsInterfaceError> {
×
88
        match self
×
89
            .request_sender
×
90
            .call(NodeCommsRequest::GetTargetDifficultyNextBlock(algo))
×
91
            .await??
×
92
        {
93
            NodeCommsResponse::TargetDifficulty(target_difficulty) => Ok(target_difficulty),
×
94
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
95
        }
96
    }
×
97

98
    /// Request the block headers within the given range
99
    pub async fn get_blocks(
×
100
        &mut self,
×
101
        range: RangeInclusive<u64>,
×
102
        compact: bool,
×
103
    ) -> Result<Vec<HistoricalBlock>, CommsInterfaceError> {
×
104
        match self
×
105
            .request_sender
×
106
            .call(NodeCommsRequest::FetchMatchingBlocks { range, compact })
×
107
            .await??
×
108
        {
109
            NodeCommsResponse::HistoricalBlocks(blocks) => Ok(blocks),
×
110
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
111
        }
112
    }
×
113

114
    /// Request the block header at the given height
115
    pub async fn get_block(
×
116
        &mut self,
×
117
        height: u64,
×
118
        compact: bool,
×
119
    ) -> Result<Option<HistoricalBlock>, CommsInterfaceError> {
×
120
        match self
×
121
            .request_sender
×
122
            .call(NodeCommsRequest::FetchMatchingBlocks {
×
123
                range: height..=height,
×
124
                compact,
×
125
            })
×
126
            .await??
×
127
        {
128
            NodeCommsResponse::HistoricalBlocks(mut blocks) => Ok(blocks.pop()),
×
129
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
130
        }
131
    }
×
132

133
    /// Request the block headers with the given range of heights. The returned headers are ordered from lowest to
134
    /// highest block height
135
    pub async fn get_headers(&mut self, range: RangeInclusive<u64>) -> Result<Vec<ChainHeader>, CommsInterfaceError> {
×
136
        match self
×
137
            .request_sender
×
138
            .call(NodeCommsRequest::FetchHeaders(range))
×
139
            .await??
×
140
        {
141
            NodeCommsResponse::BlockHeaders(headers) => Ok(headers),
×
142
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
143
        }
144
    }
×
145

146
    /// Request the block header with the height.
147
    pub async fn get_header(&mut self, height: u64) -> Result<Option<ChainHeader>, CommsInterfaceError> {
×
148
        match self
×
149
            .request_sender
×
150
            .call(NodeCommsRequest::FetchHeaders(height..=height))
×
151
            .await??
×
152
        {
153
            NodeCommsResponse::BlockHeaders(mut headers) => Ok(headers.pop()),
×
154
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
155
        }
156
    }
×
157

158
    /// Request the construction of a new mineable block template from the base node service.
159
    pub async fn get_new_block_template(
3✔
160
        &mut self,
3✔
161
        pow_algorithm: PowAlgorithm,
3✔
162
        max_weight: u64,
3✔
163
    ) -> Result<NewBlockTemplate, CommsInterfaceError> {
3✔
164
        let request = GetNewBlockTemplateRequest {
3✔
165
            algo: pow_algorithm,
3✔
166
            max_weight,
3✔
167
        };
3✔
168
        match self
3✔
169
            .request_sender
3✔
170
            .call(NodeCommsRequest::GetNewBlockTemplate(request))
3✔
171
            .await??
3✔
172
        {
173
            NodeCommsResponse::NewBlockTemplate(new_block_template) => Ok(new_block_template),
3✔
174
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
175
        }
176
    }
3✔
177

178
    /// Request from base node service the construction of a block from a block template.
179
    pub async fn get_new_block(&mut self, block_template: NewBlockTemplate) -> Result<Block, CommsInterfaceError> {
3✔
180
        match self
3✔
181
            .request_sender
3✔
182
            .call(NodeCommsRequest::GetNewBlock(block_template))
3✔
183
            .await??
3✔
184
        {
185
            NodeCommsResponse::NewBlock { success, error, block } => {
3✔
186
                if success {
3✔
187
                    if let Some(block) = block {
3✔
188
                        Ok(block)
3✔
189
                    } else {
190
                        Err(CommsInterfaceError::UnexpectedApiResponse)
×
191
                    }
192
                } else {
193
                    Err(CommsInterfaceError::ApiError(
×
194
                        error.unwrap_or_else(|| "Unspecified error".to_string()),
×
195
                    ))
×
196
                }
197
            },
198
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
199
        }
200
    }
3✔
201

202
    /// Submit a block to the base node service.
203
    pub async fn submit_block(&mut self, block: Block) -> Result<BlockHash, CommsInterfaceError> {
12✔
204
        self.block_sender.call(block).await?
12✔
205
    }
12✔
206

207
    pub fn publish_block_event(&self, event: BlockEvent) -> usize {
×
208
        // If event send fails, that means that there are no receivers (i.e. it was sent to zero receivers)
×
209
        self.block_event_sender.send(Arc::new(event)).unwrap_or(0)
×
210
    }
×
211

212
    pub async fn fetch_matching_utxos(
×
213
        &mut self,
×
214
        hashes: Vec<HashOutput>,
×
215
    ) -> Result<Vec<TransactionOutput>, CommsInterfaceError> {
×
216
        match self
×
217
            .request_sender
×
218
            .call(NodeCommsRequest::FetchMatchingUtxos(hashes))
×
219
            .await??
×
220
        {
221
            NodeCommsResponse::TransactionOutputs(outputs) => Ok(outputs),
×
222
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
223
        }
224
    }
×
225

226
    /// Fetches the blocks with the specified utxo commitments
227
    pub async fn fetch_blocks_with_utxos(
×
228
        &mut self,
×
229
        commitments: Vec<CompressedCommitment>,
×
230
    ) -> Result<Vec<HistoricalBlock>, CommsInterfaceError> {
×
231
        match self
×
232
            .request_sender
×
233
            .call(NodeCommsRequest::FetchBlocksByUtxos(commitments))
×
234
            .await??
×
235
        {
236
            NodeCommsResponse::HistoricalBlocks(blocks) => Ok(blocks),
×
237
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
238
        }
239
    }
×
240

241
    /// Fetches the blocks with the specified kernel signatures commitments
242
    pub async fn get_blocks_with_kernels(
×
243
        &mut self,
×
244
        kernels: Vec<Signature>,
×
245
    ) -> Result<Vec<HistoricalBlock>, CommsInterfaceError> {
×
246
        match self
×
247
            .request_sender
×
248
            .call(NodeCommsRequest::FetchBlocksByKernelExcessSigs(kernels))
×
249
            .await??
×
250
        {
251
            NodeCommsResponse::HistoricalBlocks(blocks) => Ok(blocks),
×
252
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
253
        }
254
    }
×
255

256
    /// Return header matching the given hash. If the header cannot be found `Ok(None)` is returned.
257
    pub async fn get_header_by_hash(&mut self, hash: HashOutput) -> Result<Option<ChainHeader>, CommsInterfaceError> {
×
258
        match self
×
259
            .request_sender
×
260
            .call(NodeCommsRequest::GetHeaderByHash(hash))
×
261
            .await??
×
262
        {
263
            NodeCommsResponse::BlockHeader(header) => Ok(header),
×
264
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
265
        }
266
    }
×
267

268
    /// Return block matching the given hash. If the block cannot be found `Ok(None)` is returned.
269
    pub async fn get_block_by_hash(
×
270
        &mut self,
×
271
        hash: HashOutput,
×
272
    ) -> Result<Option<HistoricalBlock>, CommsInterfaceError> {
×
273
        match self
×
274
            .request_sender
×
275
            .call(NodeCommsRequest::GetBlockByHash(hash))
×
276
            .await??
×
277
        {
278
            NodeCommsResponse::HistoricalBlock(block) => Ok(*block),
×
279
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
280
        }
281
    }
×
282

283
    /// Searches for a kernel via the excess sig
284
    pub async fn get_kernel_by_excess_sig(
×
285
        &mut self,
×
286
        kernel: Signature,
×
287
    ) -> Result<Vec<TransactionKernel>, CommsInterfaceError> {
×
288
        match self
×
289
            .request_sender
×
290
            .call(NodeCommsRequest::FetchKernelByExcessSig(kernel))
×
291
            .await??
×
292
        {
293
            NodeCommsResponse::TransactionKernels(kernels) => Ok(kernels),
×
294
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
295
        }
296
    }
×
297

298
    pub async fn get_active_validator_nodes(
×
299
        &mut self,
×
300
        height: u64,
×
301
    ) -> Result<Vec<(CompressedPublicKey, [u8; 32])>, CommsInterfaceError> {
×
302
        match self
×
303
            .request_sender
×
304
            .call(NodeCommsRequest::FetchValidatorNodesKeys { height })
×
305
            .await??
×
306
        {
307
            NodeCommsResponse::FetchValidatorNodesKeysResponse(validator_node) => Ok(validator_node),
×
308
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
309
        }
310
    }
×
311

312
    pub async fn get_shard_key(
×
313
        &mut self,
×
314
        height: u64,
×
315
        public_key: CompressedPublicKey,
×
316
    ) -> Result<Option<[u8; 32]>, CommsInterfaceError> {
×
317
        match self
×
318
            .request_sender
×
319
            .call(NodeCommsRequest::GetShardKey { height, public_key })
×
320
            .await??
×
321
        {
322
            NodeCommsResponse::GetShardKeyResponse(shard_key) => Ok(shard_key),
×
323
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
324
        }
325
    }
×
326

327
    pub async fn get_template_registrations(
×
328
        &mut self,
×
329
        start_height: u64,
×
330
        end_height: u64,
×
331
    ) -> Result<Vec<TemplateRegistrationEntry>, CommsInterfaceError> {
×
332
        match self
×
333
            .request_sender
×
334
            .call(NodeCommsRequest::FetchTemplateRegistrations {
×
335
                start_height,
×
336
                end_height,
×
337
            })
×
338
            .await??
×
339
        {
340
            NodeCommsResponse::FetchTemplateRegistrationsResponse(template_registrations) => Ok(template_registrations),
×
341
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
342
        }
343
    }
×
344

345
    /// Fetches UTXOs that are not spent for the given block hash up to the current chain tip.
346
    pub async fn fetch_unspent_utxos_in_block(
×
347
        &mut self,
×
348
        block_hash: BlockHash,
×
349
    ) -> Result<Vec<TransactionOutput>, CommsInterfaceError> {
×
350
        match self
×
351
            .request_sender
×
352
            .call(NodeCommsRequest::FetchUnspentUtxosInBlock { block_hash })
×
353
            .await??
×
354
        {
355
            NodeCommsResponse::TransactionOutputs(outputs) => Ok(outputs),
×
356
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
357
        }
358
    }
×
359

360
    /// Fetch mined info by PayRef (Payment Reference)
NEW
361
    pub async fn fetch_mined_info_by_payref(&mut self, payref: &FixedHash) -> Result<MinedInfo, CommsInterfaceError> {
×
NEW
362
        match self
×
NEW
363
            .request_sender
×
NEW
364
            .call(NodeCommsRequest::FetchMinedInfoByPayRef(*payref))
×
NEW
365
            .await??
×
366
        {
NEW
367
            NodeCommsResponse::MinedInfo(mined_info) => Ok(mined_info),
×
NEW
368
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
369
        }
NEW
370
    }
×
371

372
    /// Fetch mined info by output hash
NEW
373
    pub async fn fetch_mined_info_by_output_hash(
×
NEW
374
        &mut self,
×
NEW
375
        output_hash: &HashOutput,
×
NEW
376
    ) -> Result<MinedInfo, CommsInterfaceError> {
×
NEW
377
        match self
×
NEW
378
            .request_sender
×
NEW
379
            .call(NodeCommsRequest::FetchMinedInfoByOutputHash(*output_hash))
×
NEW
380
            .await??
×
381
        {
NEW
382
            NodeCommsResponse::MinedInfo(mined_info) => Ok(mined_info),
×
NEW
383
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
384
        }
NEW
385
    }
×
386

387
    /// Fetch output mined info by output hash
NEW
388
    pub async fn fetch_output_mined_info(
×
389
        &mut self,
×
NEW
390
        output_hash: &HashOutput,
×
391
    ) -> Result<Option<OutputMinedInfo>, CommsInterfaceError> {
×
392
        match self
×
393
            .request_sender
×
NEW
394
            .call(NodeCommsRequest::FetchOutputMinedInfo(*output_hash))
×
395
            .await??
×
396
        {
397
            NodeCommsResponse::OutputMinedInfo(output_info) => Ok(output_info),
×
398
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
399
        }
400
    }
×
401

402
    /// Check if an output is spent and return spent information
403
    pub async fn check_output_spent_status(
×
404
        &mut self,
×
405
        output_hash: HashOutput,
×
406
    ) -> Result<Option<InputMinedInfo>, CommsInterfaceError> {
×
407
        match self
×
408
            .request_sender
×
409
            .call(NodeCommsRequest::CheckOutputSpentStatus(output_hash))
×
410
            .await??
×
411
        {
412
            NodeCommsResponse::InputMinedInfo(input_info) => Ok(input_info),
×
413
            _ => Err(CommsInterfaceError::UnexpectedApiResponse),
×
414
        }
415
    }
×
416
}
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