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

ergoplatform / sigma-rust / 19957094785

05 Dec 2025 08:23AM UTC coverage: 86.918% (+8.5%) from 78.463%
19957094785

Pull #837

github

web-flow
Merge dec08367a into 2f840d387
Pull Request #837: Split TransactionHintsBag hints properly

44 of 53 new or added lines in 13 files covered. (83.02%)

1621 existing lines in 221 files now uncovered.

27453 of 31585 relevant lines covered (86.92%)

253204.4 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> {
1,608✔
26
    #[allow(clippy::unwrap_used)]
27
    let url = node.addr.as_http_url().join("info").unwrap();
1,608✔
28
    let client = build_client()?;
1,608✔
29
    let rb = client.get(url);
1,608✔
30
    Ok(set_req_headers(rb, node)
1,608✔
31
        .send()
1,608✔
32
        .await?
1,608✔
33
        .json::<NodeInfo>()
59✔
34
        .await?)
59✔
35
}
1,608✔
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(
2✔
67
    seeds: NonEmptyVec<Url>,
2✔
68
    max_parallel_tasks: BoundedU16<1, { u16::MAX }>,
2✔
69
    timeout: Duration,
2✔
70
) -> Result<Vec<Url>, PeerDiscoveryError> {
2✔
71
    super::peer_discovery_internals::peer_discovery_inner(seeds, max_parallel_tasks, timeout).await
2✔
72
}
2✔
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(
1✔
122
    node: NodeConf,
1✔
123
    min_chain_length: u32,
1✔
124
    suffix_len: u32,
1✔
125
    header_id: BlockId,
1✔
126
) -> Result<NipopowProof, NodeError> {
1✔
127
    if min_chain_length == 0 || suffix_len == 0 {
1✔
128
        return Err(NodeError::InvalidNumericalUrlSegment);
×
129
    }
1✔
130
    let header_str = String::from(header_id.0);
1✔
131
    let mut path = "nipopow/proof/".to_owned();
1✔
132
    path.push_str(&min_chain_length.to_string());
1✔
133
    path.push('/');
1✔
134
    path.push_str(&suffix_len.to_string());
1✔
135
    path.push('/');
1✔
136
    path.push_str(&header_str);
1✔
137
    #[allow(clippy::unwrap_used)]
138
    let url = node.addr.as_http_url().join(&path).unwrap();
1✔
139
    let client = build_client()?;
1✔
140
    let rb = client.get(url);
1✔
141
    Ok(set_req_headers(rb, node)
1✔
142
        .send()
1✔
143
        .await?
1✔
144
        .json::<NipopowProof>()
1✔
145
        .await?)
1✔
146
}
1✔
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() {
1✔
185
        let runtime_inner = tokio::runtime::Builder::new_multi_thread()
1✔
186
            .enable_all()
1✔
187
            .build()
1✔
188
            .unwrap();
1✔
189
        let node_conf = NodeConf {
1✔
190
            addr: PeerAddr::from_str("213.239.193.208:9053").unwrap(),
1✔
191
            api_key: None,
1✔
192
            timeout: Some(Duration::from_secs(5)),
1✔
193
        };
1✔
194
        let res = runtime_inner.block_on(async { get_info(node_conf).await.unwrap() });
1✔
195
        assert_ne!(res.name, "");
1✔
196
    }
1✔
197

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

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

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

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