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

ergoplatform / sigma-rust / 19909456309

03 Dec 2025 09:29PM UTC coverage: 86.947% (+8.5%) from 78.463%
19909456309

Pull #838

github

web-flow
Merge 717ebc4b7 into 2f840d387
Pull Request #838: Fix CI, bump dependencies and rust toolchain

20 of 24 new or added lines in 12 files covered. (83.33%)

1614 existing lines in 221 files now uncovered.

27478 of 31603 relevant lines covered (86.95%)

506307.07 hits per line

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

79.04
/ergo-rest/src/api/node.rs
1
//! Ergo node REST API endpoints
2

3
use bounded_integer::BoundedU16;
4
use bounded_vec::NonEmptyVec;
5
use ergo_chain_types::BlockId;
6
use ergo_chain_types::Header;
7
use ergo_merkle_tree::MerkleProof;
8
use ergo_nipopow::NipopowProof;
9
use ergotree_ir::chain::tx_id::TxId;
10
use std::time::Duration;
11
use url::Url;
12

13
use crate::error::PeerDiscoveryError;
14
use crate::NodeConf;
15
use crate::NodeError;
16
use crate::NodeInfo;
17

18
use super::build_client;
19
use super::set_req_headers;
20

21
#[cfg(target_arch = "wasm32")]
22
pub use crate::api::peer_discovery_internals::ChromePeerDiscoveryScan;
23

24
/// GET on /info endpoint
25
pub async fn get_info(node: NodeConf) -> Result<NodeInfo, NodeError> {
3,095✔
26
    #[allow(clippy::unwrap_used)]
27
    let url = node.addr.as_http_url().join("info").unwrap();
3,095✔
28
    let client = build_client()?;
3,095✔
29
    let rb = client.get(url);
3,095✔
30
    Ok(set_req_headers(rb, node)
3,095✔
31
        .send()
3,095✔
32
        .await?
3,095✔
33
        .json::<NodeInfo>()
100✔
34
        .await?)
100✔
35
}
3,095✔
36

37
/// GET on /blocks/{header_id}/header endpoint
38
pub async fn get_header(node: NodeConf, header_id: BlockId) -> Result<Header, NodeError> {
×
39
    let header_str = String::from(header_id.0);
×
40
    let mut path = "blocks/".to_owned();
×
41
    path.push_str(&header_str);
×
42
    path.push_str("/header");
×
43
    #[allow(clippy::unwrap_used)]
44
    let url = node.addr.as_http_url().join(&path).unwrap();
×
NEW
45
    let client = build_client()?;
×
46
    let rb = client.get(url);
×
47
    Ok(set_req_headers(rb, node)
×
UNCOV
48
        .send()
×
49
        .await?
×
UNCOV
50
        .json::<Header>()
×
51
        .await?)
×
UNCOV
52
}
×
53

54
/// Given a list of seed nodes, search for peer nodes with an active REST API on port 9053.
55
///  - `seeds` represents a list of ergo node URLs from which to start peer discovery.
56
///  - `max_parallel_tasks` represents the maximum number of tasks to spawn for ergo node HTTP
57
///    requests. Note that the actual number of parallel HTTP requests may well be higher than this
58
///    number.
59
///  - `timeout` represents the amount of time that is spent search for peers. Once the timeout
60
///    value is reached, return with the vec of active peers that have been discovered up to that
61
///    point in time.
62
///
63
/// IMPORTANT: do not call this function on Chromium, as it will likely mess with the browser's
64
/// ability to make HTTP requests. Use `peer_discovery_chrome` instead. For more information why
65
/// please refer to the module documentation for `crate::api::peer_discovery_internals::chrome`.
66
pub async fn peer_discovery(
4✔
67
    seeds: NonEmptyVec<Url>,
4✔
68
    max_parallel_tasks: BoundedU16<1, { u16::MAX }>,
4✔
69
    timeout: Duration,
4✔
70
) -> Result<Vec<Url>, PeerDiscoveryError> {
4✔
71
    super::peer_discovery_internals::peer_discovery_inner(seeds, max_parallel_tasks, timeout).await
4✔
72
}
4✔
73

74
#[cfg(target_arch = "wasm32")]
75
/// Given a list of seed nodes, search for peer nodes with an active REST API on port 9053.
76
///  - `seeds` represents a list of ergo node URLs from which to start peer discovery.
77
///  - `max_parallel_requests` represents the maximum number of HTTP requests that can be made in
78
///    parallel
79
///  - `timeout` represents the amount of time that is spent searching for peers PLUS a waiting
80
///    period of 80 seconds to give Chrome the time to relinquish failed preflight requests. Must be
81
///    at least 90 seconds. Once the timeout value is reached, return with the vec of active peers
82
///    that have been discovered up to that point in time.
83
///
84
/// NOTE: intended to be used only on Chromium based browsers. It works on Firefox and Safari, but
85
/// using `peer_discovery` above gives better performance.
86
pub async fn peer_discovery_chrome(
87
    seeds: NonEmptyVec<Url>,
88
    max_parallel_requests: BoundedU16<1, { u16::MAX }>,
89
    timeout: Duration,
90
) -> Result<Vec<Url>, PeerDiscoveryError> {
91
    let scan = super::peer_discovery_internals::ChromePeerDiscoveryScan::new(seeds);
92
    super::peer_discovery_internals::peer_discovery_inner_chrome(
93
        scan,
94
        max_parallel_requests,
95
        timeout,
96
    )
97
    .await
98
    .map(|scan| scan.active_peers())
99
}
100

101
#[cfg(target_arch = "wasm32")]
102
/// An incremental (reusable) version of [`peer_discovery_chrome`] which allows for peer discovery
103
/// to be split into separate sub-tasks.
104
///
105
/// NOTE: intended to be used only on Chromium based browsers. It works on Firefox and Safari, but
106
/// using `peer_discovery` above gives better performance.
107
pub async fn incremental_peer_discovery_chrome(
108
    scan: ChromePeerDiscoveryScan,
109
    max_parallel_requests: BoundedU16<1, { u16::MAX }>,
110
    timeout: Duration,
111
) -> Result<ChromePeerDiscoveryScan, PeerDiscoveryError> {
112
    super::peer_discovery_internals::peer_discovery_inner_chrome(
113
        scan,
114
        max_parallel_requests,
115
        timeout,
116
    )
117
    .await
118
}
119

120
/// GET on /nipopow/proof/{minChainLength}/{suffixLength}/{headerId} endpoint
121
pub async fn get_nipopow_proof_by_header_id(
2✔
122
    node: NodeConf,
2✔
123
    min_chain_length: u32,
2✔
124
    suffix_len: u32,
2✔
125
    header_id: BlockId,
2✔
126
) -> Result<NipopowProof, NodeError> {
2✔
127
    if min_chain_length == 0 || suffix_len == 0 {
2✔
128
        return Err(NodeError::InvalidNumericalUrlSegment);
×
129
    }
2✔
130
    let header_str = String::from(header_id.0);
2✔
131
    let mut path = "nipopow/proof/".to_owned();
2✔
132
    path.push_str(&min_chain_length.to_string());
2✔
133
    path.push('/');
2✔
134
    path.push_str(&suffix_len.to_string());
2✔
135
    path.push('/');
2✔
136
    path.push_str(&header_str);
2✔
137
    #[allow(clippy::unwrap_used)]
138
    let url = node.addr.as_http_url().join(&path).unwrap();
2✔
139
    let client = build_client()?;
2✔
140
    let rb = client.get(url);
2✔
141
    Ok(set_req_headers(rb, node)
2✔
142
        .send()
2✔
143
        .await?
2✔
144
        .json::<NipopowProof>()
2✔
145
        .await?)
2✔
146
}
2✔
147

148
/// GET on /blocks/{header_id}/proofFor/{tx_id} to request the merkle proof for a given transaction
149
/// that belongs to the given header ID.
150
pub async fn get_blocks_header_id_proof_for_tx_id(
×
UNCOV
151
    node: NodeConf,
×
UNCOV
152
    header_id: BlockId,
×
UNCOV
153
    tx_id: TxId,
×
UNCOV
154
) -> Result<Option<MerkleProof>, NodeError> {
×
155
    let header_str = String::from(header_id.0);
×
156
    let mut path = "blocks/".to_owned();
×
157
    path.push_str(&header_str);
×
158
    path.push_str("/proofFor/");
×
159
    let tx_id_str = String::from(tx_id);
×
160
    path.push_str(&tx_id_str);
×
161
    #[allow(clippy::unwrap_used)]
162
    let url = node.addr.as_http_url().join(&path).unwrap();
×
NEW
163
    let client = build_client()?;
×
164
    let rb = client.get(url);
×
165
    Ok(set_req_headers(rb, node)
×
UNCOV
166
        .send()
×
167
        .await?
×
UNCOV
168
        .json::<Option<MerkleProof>>()
×
169
        .await?)
×
UNCOV
170
}
×
171

172
#[allow(clippy::unwrap_used)]
173
#[cfg(test)]
174
mod tests {
175
    use std::convert::TryFrom;
176
    use std::str::FromStr;
177
    use std::time::Duration;
178

179
    use ergo_chain_types::PeerAddr;
180

181
    use super::*;
182

183
    #[test]
184
    fn test_get_info() {
2✔
185
        let runtime_inner = tokio::runtime::Builder::new_multi_thread()
2✔
186
            .enable_all()
2✔
187
            .build()
2✔
188
            .unwrap();
2✔
189
        let node_conf = NodeConf {
2✔
190
            addr: PeerAddr::from_str("213.239.193.208:9053").unwrap(),
2✔
191
            api_key: None,
2✔
192
            timeout: Some(Duration::from_secs(5)),
2✔
193
        };
2✔
194
        let res = runtime_inner.block_on(async { get_info(node_conf).await.unwrap() });
2✔
195
        assert_ne!(res.name, "");
2✔
196
    }
2✔
197

198
    #[test]
199
    fn test_get_nipopow_proof_by_header_id() {
2✔
200
        use ergo_chain_types::{BlockId, Digest32};
201
        let header_id = BlockId(
2✔
202
            Digest32::try_from(String::from(
2✔
203
                "9bcb535c2d05fbced6de3d73c63337d6deb64af387438fa748d66ddf3d33ee89",
2✔
204
            ))
2✔
205
            .unwrap(),
2✔
206
        );
2✔
207
        let runtime_inner = tokio::runtime::Builder::new_multi_thread()
2✔
208
            .enable_all()
2✔
209
            .build()
2✔
210
            .unwrap();
2✔
211
        let node_conf = NodeConf {
2✔
212
            addr: PeerAddr::from_str("213.239.193.208:9053").unwrap(),
2✔
213
            api_key: None,
2✔
214
            timeout: Some(Duration::from_secs(5)),
2✔
215
        };
2✔
216
        let m = 7;
2✔
217
        let k = 6;
2✔
218
        let res = runtime_inner.block_on(async {
2✔
219
            get_nipopow_proof_by_header_id(node_conf, m, k, header_id)
2✔
220
                .await
2✔
221
                .unwrap()
2✔
222
        });
2✔
223
        assert_eq!(res.suffix_head.header.id, header_id);
2✔
224
        assert!(!res.prefix.is_empty());
2✔
225
        assert_eq!(res.m, m);
2✔
226
        assert_eq!(res.k, k);
2✔
227
    }
2✔
228

229
    #[test]
230
    fn test_peer_discovery() {
2✔
231
        let seeds: Vec<_> = [
2✔
232
            "http://213.239.193.208:9030",
2✔
233
            "http://159.65.11.55:9030",
2✔
234
            "http://165.227.26.175:9030",
2✔
235
            "http://159.89.116.15:9030",
2✔
236
            "http://136.244.110.145:9030",
2✔
237
            "http://94.130.108.35:9030",
2✔
238
            "http://51.75.147.1:9020",
2✔
239
            "http://221.165.214.185:9030",
2✔
240
            "http://51.81.185.231:9031",
2✔
241
            "http://217.182.197.196:9030",
2✔
242
            "http://62.171.190.193:9030",
2✔
243
            "http://173.212.220.9:9030",
2✔
244
            "http://176.9.65.58:9130",
2✔
245
            "http://213.152.106.56:9030",
2✔
246
        ]
2✔
247
        .iter()
2✔
248
        .map(|s| Url::from_str(s).unwrap())
28✔
249
        .collect();
2✔
250
        let runtime_inner = tokio::runtime::Builder::new_multi_thread()
2✔
251
            .enable_all()
2✔
252
            .build()
2✔
253
            .unwrap();
2✔
254
        let (res_with_quick_timeout, res_with_longer_timeout) = runtime_inner.block_on(async {
2✔
255
            let res_quick = peer_discovery(
2✔
256
                NonEmptyVec::from_vec(seeds.clone()).unwrap(),
2✔
257
                BoundedU16::new(5).unwrap(),
2✔
258
                Duration::from_millis(1000),
2✔
259
            )
2✔
260
            .await
2✔
261
            .unwrap();
2✔
262

263
            tokio::time::sleep(Duration::from_secs(5)).await;
2✔
264

265
            let res_long = peer_discovery(
2✔
266
                NonEmptyVec::from_vec(seeds).unwrap(),
2✔
267
                BoundedU16::new(5).unwrap(),
2✔
268
                Duration::from_millis(10000),
2✔
269
            )
2✔
270
            .await
2✔
271
            .unwrap();
2✔
272
            (res_quick, res_long)
2✔
273
        });
2✔
274
        println!(
2✔
275
            "{} quick peers, {} long peers",
2✔
276
            res_with_quick_timeout.len(),
2✔
277
            res_with_longer_timeout.len()
2✔
278
        );
279
        println!("discovered: {:?}", res_with_longer_timeout);
2✔
280
        assert!(!res_with_longer_timeout.is_empty());
2✔
281
        assert!(res_with_quick_timeout.len() <= res_with_longer_timeout.len());
2✔
282
    }
2✔
283
}
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

© 2025 Coveralls, Inc