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

tari-project / tari / 15280118615

27 May 2025 04:01PM UTC coverage: 73.59% (+0.4%) from 73.233%
15280118615

push

github

web-flow
feat: add base node HTTP wallet service (#7061)

Description
---
Added a new HTTP server for base node that exposes some wallet related
query functionality.

Current new endpoints (examples on **esmeralda** network):
 - http://127.0.0.1:9005/get_tip_info
 - http://127.0.0.1:9005/get_header_by_height?height=6994
 - http://127.0.0.1:9005/get_height_at_time?time=1747739959

Default ports for http service (by network):
```
MainNet: 9000,
StageNet: 9001,
NextNet: 9002,
LocalNet: 9003,
Igor: 9004,
Esmeralda: 9005,
```

New configuration needs to be set in base node:
```toml
[base_node.http_wallet_query_service]
port = 9000
external_address = "http://127.0.0.1:9000" # this is optional, but if not set, when someone requests for the external address, just returns a None, so wallets can't contact base node
```

Motivation and Context
---


How Has This Been Tested?
---
### Manually

#### Basic test
1. Build new base node
2. Set base node configuration by adding the following:
```toml
[base_node.http_wallet_query_service]
port = 9000
external_address = "http://127.0.0.1:9000"
```
This way we set the port and external address (which is sent to wallet
client when requesting, so in real world it must be public)
3. Set logging level of base node logs to DEBUG
4. Start base node
5. Build and start console wallet
6. See that it is still able to synchronize
7. Check logs of base node (with `tail -f ...` command for instance) and
see that the HTTP endpoints are used

#### Use RPC fallback test
1. Build new base node
2. Set base node configuration by adding the following:
```toml
[base_node.http_wallet_query_service]
port = 9000
external_address = "http://127.0.0.1:9001"
```
This way we set the port and external address (which is sent to wallet
client when requesting, so in real world it must be public)
3. Set logging level of base node logs to DEBUG
4. Start base node
5. Build and start console wallet
6. See that it is still able to synchronize
7. Check logs of base nod... (continued)

9 of 114 new or added lines in 4 files covered. (7.89%)

1592 existing lines in 62 files now uncovered.

82227 of 111736 relevant lines covered (73.59%)

272070.7 hits per line

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

0.0
/base_layer/core/src/base_node/rpc/query_service.rs
1
// Copyright 2025 The Tari Project
2
// SPDX-License-Identifier: BSD-3-Clause
3

4
use log::trace;
5
use thiserror::Error;
6

7
use crate::{
8
    base_node::{
9
        rpc::{models::TipInfoResponse, BaseNodeWalletQueryService},
10
        state_machine_service::states::StateInfo,
11
        StateMachineHandle,
12
    },
13
    blocks::BlockHeader,
14
    chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend, ChainStorageError},
15
};
16

17
const LOG_TARGET: &str = "c::bn::rpc::query_service";
18

19
#[derive(Debug, Error)]
20
pub enum Error {
21
    #[error("Failed to get chain metadata: {0}")]
22
    FailedToGetChainMetadata(#[from] ChainStorageError),
23
    #[error("Header not found at height: {height}")]
24
    HeaderNotFound { height: u64 },
25
}
26

27
pub struct Service<B> {
28
    db: AsyncBlockchainDb<B>,
29
    state_machine: StateMachineHandle,
30
}
31

32
impl<B: BlockchainBackend + 'static> Service<B> {
NEW
33
    pub fn new(db: AsyncBlockchainDb<B>, state_machine: StateMachineHandle) -> Self {
×
NEW
34
        Self { db, state_machine }
×
NEW
35
    }
×
36

NEW
37
    fn state_machine(&self) -> StateMachineHandle {
×
NEW
38
        self.state_machine.clone()
×
NEW
39
    }
×
40
}
41

42
#[async_trait::async_trait]
43
impl<B: BlockchainBackend + 'static> BaseNodeWalletQueryService for Service<B> {
44
    type Error = Error;
45

NEW
46
    async fn get_tip_info(&self) -> Result<TipInfoResponse, Self::Error> {
×
NEW
47
        let state_machine = self.state_machine();
×
NEW
48
        let status_watch = state_machine.get_status_info_watch();
×
NEW
49
        let is_synced = match status_watch.borrow().state_info {
×
NEW
50
            StateInfo::Listening(li) => li.is_synced(),
×
NEW
51
            _ => false,
×
52
        };
53

NEW
54
        let metadata = self.db.get_chain_metadata().await?;
×
55

NEW
56
        Ok(TipInfoResponse {
×
NEW
57
            metadata: Some(metadata),
×
NEW
58
            is_synced,
×
NEW
59
        })
×
NEW
60
    }
×
61

NEW
62
    async fn get_header_by_height(&self, height: u64) -> Result<BlockHeader, Self::Error> {
×
NEW
63
        Ok(self
×
NEW
64
            .db
×
NEW
65
            .fetch_header(height)
×
NEW
66
            .await?
×
NEW
67
            .ok_or(Error::HeaderNotFound { height })?)
×
NEW
68
    }
×
69

NEW
70
    async fn get_height_at_time(&self, epoch_time: u64) -> Result<u64, Self::Error> {
×
NEW
71
        trace!(target: LOG_TARGET, "requested_epoch_time: {}", epoch_time);
×
NEW
72
        let tip_header = self.db.fetch_tip_header().await?;
×
73

NEW
74
        let mut left_height = 0u64;
×
NEW
75
        let mut right_height = tip_header.height();
×
76

NEW
77
        while left_height <= right_height {
×
NEW
78
            let mut mid_height = (left_height + right_height) / 2;
×
NEW
79

×
NEW
80
            if mid_height == 0 {
×
NEW
81
                return Ok(0u64);
×
NEW
82
            }
×
NEW
83
            // If the two bounds are adjacent then perform the test between the right and left sides
×
NEW
84
            if left_height == mid_height {
×
NEW
85
                mid_height = right_height;
×
NEW
86
            }
×
87

NEW
88
            let mid_header = self
×
NEW
89
                .db
×
NEW
90
                .fetch_header(mid_height)
×
NEW
91
                .await?
×
NEW
92
                .ok_or_else(|| Error::HeaderNotFound { height: mid_height })?;
×
NEW
93
            let before_mid_header = self
×
NEW
94
                .db
×
NEW
95
                .fetch_header(mid_height - 1)
×
NEW
96
                .await?
×
NEW
97
                .ok_or_else(|| Error::HeaderNotFound { height: mid_height - 1 })?;
×
NEW
98
            trace!(
×
NEW
99
                target: LOG_TARGET,
×
NEW
100
                "requested_epoch_time: {}, left: {}, mid: {}/{} ({}/{}), right: {}",
×
NEW
101
                epoch_time,
×
NEW
102
                left_height,
×
NEW
103
                mid_height,
×
NEW
104
                mid_height-1,
×
NEW
105
                mid_header.timestamp.as_u64(),
×
NEW
106
                before_mid_header.timestamp.as_u64(),
×
107
                right_height
108
            );
NEW
109
            if epoch_time < mid_header.timestamp.as_u64() && epoch_time >= before_mid_header.timestamp.as_u64() {
×
NEW
110
                trace!(
×
NEW
111
                    target: LOG_TARGET,
×
NEW
112
                    "requested_epoch_time: {}, selected height: {}",
×
113
                    epoch_time, before_mid_header.height
114
                );
NEW
115
                return Ok(before_mid_header.height);
×
NEW
116
            } else if mid_height == right_height {
×
NEW
117
                trace!(
×
NEW
118
                    target: LOG_TARGET,
×
NEW
119
                    "requested_epoch_time: {}, selected height: {}",
×
120
                    epoch_time, right_height
121
                );
NEW
122
                return Ok(right_height);
×
NEW
123
            } else if epoch_time <= mid_header.timestamp.as_u64() {
×
NEW
124
                right_height = mid_height;
×
NEW
125
            } else {
×
NEW
126
                left_height = mid_height;
×
NEW
127
            }
×
128
        }
129

NEW
130
        Ok(0u64)
×
NEW
131
    }
×
132
}
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