Skip to main content

ant_core/node/
mod.rs

1pub mod binary;
2pub mod daemon;
3// `LocalDevnet` wraps `ant_node::devnet::Devnet`. Gated behind `devnet`
4// so default builds of ant-core don't link ant-node at all.
5#[cfg(feature = "devnet")]
6pub mod devnet;
7pub mod events;
8pub mod process;
9pub mod registry;
10pub mod types;
11
12use std::collections::HashMap;
13use std::path::{Path, PathBuf};
14
15use crate::config;
16use crate::error::{Error, Result};
17use crate::node::binary::ProgressReporter;
18use crate::node::registry::NodeRegistry;
19use crate::node::types::{
20    AddNodeOpts, AddNodeResult, NodeConfig, NodeStatus, NodeStatusResult, NodeStatusSummary,
21    RemoveNodeResult, ResetResult,
22};
23
24/// Add one or more nodes to the registry.
25///
26/// This function:
27/// 1. Resolves the binary (download if needed)
28/// 2. Loads the registry (with file lock)
29/// 3. Validates port ranges match count
30/// 4. Creates data and log directories for each node
31/// 5. Assigns IDs and saves the registry
32///
33/// Does NOT start the nodes. Does NOT require the daemon.
34pub async fn add_nodes(
35    opts: AddNodeOpts,
36    registry_path: &Path,
37    progress: &dyn ProgressReporter,
38) -> Result<AddNodeResult> {
39    // Validate and normalize rewards address
40    validate_rewards_address(&opts.rewards_address)?;
41    let rewards_address = opts.rewards_address.trim().to_string();
42
43    // Cap the number of nodes per call to prevent accidental resource exhaustion
44    const MAX_NODES_PER_CALL: u16 = 1000;
45    if opts.count > MAX_NODES_PER_CALL {
46        return Err(Error::InvalidNodeCount {
47            count: opts.count,
48            max: MAX_NODES_PER_CALL,
49        });
50    }
51
52    // Validate port ranges match count
53    if let Some(ref port_range) = opts.node_port {
54        let range_len = port_range.len();
55        if range_len != 1 && range_len != opts.count {
56            return Err(Error::PortRangeMismatch {
57                range_len,
58                count: opts.count,
59            });
60        }
61    }
62    if let Some(ref port_range) = opts.metrics_port {
63        let range_len = port_range.len();
64        if range_len != 1 && range_len != opts.count {
65            return Err(Error::PortRangeMismatch {
66                range_len,
67                count: opts.count,
68            });
69        }
70    }
71
72    // Resolve the binary (downloads to cache if needed)
73    let install_dir = binary::binary_install_dir()?;
74    let resolved = binary::resolve_binary(&opts.binary_source, &install_dir, progress).await?;
75    let cached_binary = resolved.path;
76    let version = resolved.version;
77
78    // Load registry with file lock
79    let (mut registry, _lock) = NodeRegistry::load_locked(registry_path)?;
80
81    // Build node configs
82    let mut nodes_added = Vec::with_capacity(opts.count as usize);
83    let env_map: HashMap<String, String> = opts.env_variables.into_iter().collect();
84
85    // Each node gets its own copy under the plain binary name
86    let binary_file_name = binary::BINARY_NAME;
87
88    for i in 0..opts.count {
89        let node_port = resolve_port(&opts.node_port, i, opts.count);
90        let metrics_port = resolve_port(&opts.metrics_port, i, opts.count);
91
92        // We use a placeholder ID (0) here; the registry will assign the real one
93        let placeholder_id = 0;
94
95        let data_dir = node_data_dir(&opts.data_dir_path, placeholder_id);
96        let log_dir = node_log_dir(&opts.log_dir_path, placeholder_id);
97
98        let config = NodeConfig {
99            id: placeholder_id,
100            service_name: String::new(), // assigned by registry.add()
101            rewards_address: rewards_address.clone(),
102            data_dir,
103            log_dir,
104            node_port,
105            metrics_port,
106            network_id: Some(opts.network_id),
107            binary_path: PathBuf::new(), // placeholder, updated below
108            version: version.clone(),
109            env_variables: env_map.clone(),
110            bootstrap_peers: opts.bootstrap_peers.clone(),
111            upgrade_channel: opts.upgrade_channel,
112        };
113
114        let assigned_id = registry.add(config);
115
116        // Now update paths with the actual assigned ID
117        let node = registry.get_mut(assigned_id)?;
118        node.data_dir = node_data_dir(&opts.data_dir_path, assigned_id);
119        node.log_dir = node_log_dir(&opts.log_dir_path, assigned_id);
120
121        // Create directories
122        std::fs::create_dir_all(&node.data_dir)?;
123        if let Some(ref log_dir) = node.log_dir {
124            std::fs::create_dir_all(log_dir)?;
125        }
126
127        // Copy the binary into this node's data directory so each node
128        // has its own copy. This allows safe per-node upgrades without
129        // affecting running nodes.
130        let node_binary = node.data_dir.join(binary_file_name);
131        std::fs::copy(&cached_binary, &node_binary)?;
132        #[cfg(unix)]
133        {
134            use std::os::unix::fs::PermissionsExt;
135            std::fs::set_permissions(&node_binary, std::fs::Permissions::from_mode(0o755))?;
136        }
137        node.binary_path = node_binary;
138
139        // Copy bootstrap_peers.toml alongside the binary so the node can
140        // discover production network peers on startup.
141        if let Some(ref bp_path) = resolved.bootstrap_peers_path {
142            let dest = node.data_dir.join(binary::BOOTSTRAP_PEERS_FILE);
143            std::fs::copy(bp_path, &dest)?;
144        }
145
146        nodes_added.push(node.clone());
147    }
148
149    registry.save()?;
150
151    Ok(AddNodeResult { nodes_added })
152}
153
154/// Remove a node from the registry.
155///
156/// Does NOT stop the node. Does NOT require the daemon.
157pub fn remove_node(node_id: u32, registry_path: &Path) -> Result<RemoveNodeResult> {
158    let (mut registry, _lock) = NodeRegistry::load_locked(registry_path)?;
159    let removed = registry.remove(node_id)?;
160    registry.save()?;
161    Ok(RemoveNodeResult { removed })
162}
163
164/// Reset all node state: remove all data directories, log directories, and clear the registry.
165///
166/// This function:
167/// 1. Loads the registry (with file lock)
168/// 2. Iterates over all registered nodes
169/// 3. Removes each node's data directory
170/// 4. Removes each node's log directory (if set)
171/// 5. Clears the registry (empties nodes, resets next_id to 1)
172///
173/// Does NOT check if nodes are running — callers must verify that first.
174pub fn reset(registry_path: &Path) -> Result<ResetResult> {
175    let (mut registry, _lock) = NodeRegistry::load_locked(registry_path)?;
176
177    let mut data_dirs_removed = Vec::new();
178    let mut log_dirs_removed = Vec::new();
179    let nodes_cleared = registry.len() as u32;
180
181    for node in registry.list() {
182        if node.data_dir.exists() {
183            std::fs::remove_dir_all(&node.data_dir)?;
184            data_dirs_removed.push(node.data_dir.clone());
185        }
186        if let Some(ref log_dir) = node.log_dir {
187            if log_dir.exists() {
188                std::fs::remove_dir_all(log_dir)?;
189                log_dirs_removed.push(log_dir.clone());
190            }
191        }
192    }
193
194    registry.clear();
195    registry.save()?;
196
197    Ok(ResetResult {
198        nodes_cleared,
199        data_dirs_removed,
200        log_dirs_removed,
201    })
202}
203
204/// Get the status of all registered nodes without the daemon.
205///
206/// Since the daemon is not running, all nodes are reported as `Stopped`.
207pub fn node_status_offline(registry_path: &Path) -> Result<NodeStatusResult> {
208    let registry = NodeRegistry::load(registry_path)?;
209    let nodes: Vec<NodeStatusSummary> = registry
210        .list()
211        .iter()
212        .map(|config| NodeStatusSummary {
213            node_id: config.id,
214            name: config.service_name.clone(),
215            version: config.version.clone(),
216            status: NodeStatus::Stopped,
217            pid: None,
218            uptime_secs: None,
219            pending_version: None,
220        })
221        .collect();
222    let total_stopped = nodes.len() as u32;
223    Ok(NodeStatusResult {
224        nodes,
225        total_running: 0,
226        total_stopped,
227    })
228}
229
230/// Validate that a rewards address is a valid Ethereum-style address.
231///
232/// Must be `0x` followed by exactly 40 hexadecimal characters.
233fn validate_rewards_address(address: &str) -> Result<()> {
234    let address = address.trim();
235    if address.is_empty() {
236        return Err(Error::InvalidRewardsAddress(
237            "rewards address cannot be empty".to_string(),
238        ));
239    }
240    if !address.starts_with("0x") && !address.starts_with("0X") {
241        return Err(Error::InvalidRewardsAddress(format!(
242            "rewards address must start with '0x', got '{address}'"
243        )));
244    }
245    let hex_part = &address[2..];
246    if hex_part.len() != 40 {
247        return Err(Error::InvalidRewardsAddress(format!(
248            "rewards address must be 42 characters (0x + 40 hex), got {} characters",
249            address.len()
250        )));
251    }
252    if !hex_part.chars().all(|c| c.is_ascii_hexdigit()) {
253        return Err(Error::InvalidRewardsAddress(format!(
254            "rewards address contains non-hex characters: '{address}'"
255        )));
256    }
257    Ok(())
258}
259
260/// Determine the data directory for a node.
261fn node_data_dir(custom_prefix: &Option<PathBuf>, node_id: u32) -> PathBuf {
262    match custom_prefix {
263        Some(prefix) => prefix.join(format!("node-{node_id}")),
264        None => config::data_dir()
265            .expect("Could not determine data directory")
266            .join("nodes")
267            .join(format!("node-{node_id}")),
268    }
269}
270
271/// Determine the log directory for a node.
272/// Returns `None` when no custom log dir prefix was provided (no logging by default).
273fn node_log_dir(custom_prefix: &Option<PathBuf>, node_id: u32) -> Option<PathBuf> {
274    custom_prefix
275        .as_ref()
276        .map(|prefix| prefix.join(format!("node-{node_id}")).join("logs"))
277}
278
279/// Resolve a port from a PortRange for a given node index.
280fn resolve_port(range: &Option<types::PortRange>, index: u16, _count: u16) -> Option<u16> {
281    range.as_ref().and_then(|r| r.port_at(index))
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287    use crate::node::binary::NoopProgress;
288    use crate::node::types::{BinarySource, PortRange};
289
290    /// A valid Ethereum address for use in tests.
291    const TEST_ADDR: &str = "0x1234567890abcdef1234567890abcdef12345678";
292
293    fn test_registry_path(dir: &std::path::Path) -> PathBuf {
294        dir.join("node_registry.json")
295    }
296
297    /// Create a fake binary that responds to --version.
298    /// On Windows, uses a .cmd extension so the shell can execute it.
299    fn create_fake_binary(dir: &std::path::Path) -> PathBuf {
300        #[cfg(unix)]
301        {
302            let binary_path = dir.join("fake-antnode");
303            std::fs::write(&binary_path, "#!/bin/sh\necho \"antnode 0.1.0-test\"\n").unwrap();
304            use std::os::unix::fs::PermissionsExt;
305            std::fs::set_permissions(&binary_path, std::fs::Permissions::from_mode(0o755)).unwrap();
306            binary_path
307        }
308        #[cfg(windows)]
309        {
310            let binary_path = dir.join("fake-antnode.cmd");
311            std::fs::write(&binary_path, "@echo off\r\necho antnode 0.1.0-test\r\n").unwrap();
312            binary_path
313        }
314    }
315
316    #[tokio::test]
317    async fn add_single_node_with_local_binary() {
318        let tmp = tempfile::tempdir().unwrap();
319        let binary = create_fake_binary(tmp.path());
320        let reg_path = test_registry_path(tmp.path());
321
322        let opts = AddNodeOpts {
323            count: 1,
324            rewards_address: TEST_ADDR.to_string(),
325            data_dir_path: Some(tmp.path().join("data")),
326            log_dir_path: Some(tmp.path().join("logs")),
327            binary_source: BinarySource::LocalPath(binary),
328            ..Default::default()
329        };
330
331        let result = add_nodes(opts, &reg_path, &NoopProgress).await.unwrap();
332        assert_eq!(result.nodes_added.len(), 1);
333        assert_eq!(result.nodes_added[0].rewards_address, TEST_ADDR);
334        assert_eq!(result.nodes_added[0].id, 1);
335        assert!(result.nodes_added[0].data_dir.exists());
336        assert!(result.nodes_added[0].log_dir.as_ref().unwrap().exists());
337
338        // Verify registry was saved
339        let reg = NodeRegistry::load(&reg_path).unwrap();
340        assert_eq!(reg.len(), 1);
341    }
342
343    #[tokio::test]
344    async fn add_multiple_nodes_with_port_range() {
345        let tmp = tempfile::tempdir().unwrap();
346        let binary = create_fake_binary(tmp.path());
347        let reg_path = test_registry_path(tmp.path());
348
349        let opts = AddNodeOpts {
350            count: 3,
351            rewards_address: TEST_ADDR.to_string(),
352            node_port: Some(PortRange::Range(12000, 12002)),
353            data_dir_path: Some(tmp.path().join("data")),
354            log_dir_path: Some(tmp.path().join("logs")),
355            binary_source: BinarySource::LocalPath(binary),
356            ..Default::default()
357        };
358
359        let result = add_nodes(opts, &reg_path, &NoopProgress).await.unwrap();
360        assert_eq!(result.nodes_added.len(), 3);
361        assert_eq!(result.nodes_added[0].node_port, Some(12000));
362        assert_eq!(result.nodes_added[1].node_port, Some(12001));
363        assert_eq!(result.nodes_added[2].node_port, Some(12002));
364        assert_eq!(result.nodes_added[0].id, 1);
365        assert_eq!(result.nodes_added[1].id, 2);
366        assert_eq!(result.nodes_added[2].id, 3);
367    }
368
369    #[tokio::test]
370    async fn add_nodes_rejects_port_range_mismatch() {
371        let tmp = tempfile::tempdir().unwrap();
372        let binary = create_fake_binary(tmp.path());
373        let reg_path = test_registry_path(tmp.path());
374
375        let opts = AddNodeOpts {
376            count: 3,
377            rewards_address: TEST_ADDR.to_string(),
378            node_port: Some(PortRange::Range(12000, 12001)), // 2 ports, 3 nodes
379            binary_source: BinarySource::LocalPath(binary),
380            ..Default::default()
381        };
382
383        let result = add_nodes(opts, &reg_path, &NoopProgress).await;
384        assert!(result.is_err());
385        assert!(matches!(
386            result.unwrap_err(),
387            Error::PortRangeMismatch { .. }
388        ));
389    }
390
391    #[tokio::test]
392    async fn add_nodes_rejects_empty_rewards_address() {
393        let tmp = tempfile::tempdir().unwrap();
394        let reg_path = test_registry_path(tmp.path());
395
396        let opts = AddNodeOpts {
397            count: 1,
398            rewards_address: "  ".to_string(),
399            ..Default::default()
400        };
401
402        let result = add_nodes(opts, &reg_path, &NoopProgress).await;
403        assert!(result.is_err());
404        assert!(matches!(
405            result.unwrap_err(),
406            Error::InvalidRewardsAddress(_)
407        ));
408    }
409
410    #[test]
411    fn validate_rewards_address_rejects_missing_prefix() {
412        let result = validate_rewards_address("1234567890abcdef1234567890abcdef12345678");
413        assert!(result.is_err());
414    }
415
416    #[test]
417    fn validate_rewards_address_rejects_short_address() {
418        let result = validate_rewards_address("0xabc123");
419        assert!(result.is_err());
420    }
421
422    #[test]
423    fn validate_rewards_address_rejects_non_hex() {
424        let result = validate_rewards_address("0xGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG");
425        assert!(result.is_err());
426    }
427
428    #[test]
429    fn validate_rewards_address_accepts_valid() {
430        let result = validate_rewards_address(TEST_ADDR);
431        assert!(result.is_ok());
432    }
433
434    #[test]
435    fn validate_rewards_address_accepts_uppercase_hex() {
436        let result = validate_rewards_address("0xABCDEF1234567890ABCDEF1234567890ABCDEF12");
437        assert!(result.is_ok());
438    }
439
440    #[tokio::test]
441    async fn add_nodes_with_custom_data_dir() {
442        let tmp = tempfile::tempdir().unwrap();
443        let binary = create_fake_binary(tmp.path());
444        let reg_path = test_registry_path(tmp.path());
445        let custom_data = tmp.path().join("custom-data");
446
447        let opts = AddNodeOpts {
448            count: 1,
449            rewards_address: TEST_ADDR.to_string(),
450            data_dir_path: Some(custom_data.clone()),
451            binary_source: BinarySource::LocalPath(binary),
452            ..Default::default()
453        };
454
455        let result = add_nodes(opts, &reg_path, &NoopProgress).await.unwrap();
456        assert!(result.nodes_added[0].data_dir.starts_with(&custom_data));
457    }
458
459    #[tokio::test]
460    async fn add_nodes_without_log_dir_sets_none() {
461        let tmp = tempfile::tempdir().unwrap();
462        let binary = create_fake_binary(tmp.path());
463        let reg_path = test_registry_path(tmp.path());
464
465        let opts = AddNodeOpts {
466            count: 1,
467            rewards_address: TEST_ADDR.to_string(),
468            data_dir_path: Some(tmp.path().join("data")),
469            // log_dir_path not set — defaults to None
470            binary_source: BinarySource::LocalPath(binary),
471            ..Default::default()
472        };
473
474        let result = add_nodes(opts, &reg_path, &NoopProgress).await.unwrap();
475        assert!(result.nodes_added[0].log_dir.is_none());
476    }
477
478    #[test]
479    fn remove_node_from_registry() {
480        let tmp = tempfile::tempdir().unwrap();
481        let reg_path = test_registry_path(tmp.path());
482
483        // First add a node directly to the registry
484        let (mut registry, _lock) = NodeRegistry::load_locked(&reg_path).unwrap();
485        registry.add(NodeConfig {
486            id: 0,
487            service_name: String::new(),
488            rewards_address: "0xtest".to_string(),
489            data_dir: PathBuf::from("/tmp/test"),
490            log_dir: None,
491            node_port: None,
492            metrics_port: None,
493            network_id: None,
494            binary_path: PathBuf::from("/usr/bin/antnode"),
495            version: "0.1.0".to_string(),
496            env_variables: HashMap::new(),
497            bootstrap_peers: vec![],
498            upgrade_channel: None,
499        });
500        registry.save().unwrap();
501        drop(_lock);
502
503        let result = remove_node(1, &reg_path).unwrap();
504        assert_eq!(result.removed.rewards_address, "0xtest");
505
506        let reg = NodeRegistry::load(&reg_path).unwrap();
507        assert!(reg.is_empty());
508    }
509
510    #[test]
511    fn remove_nonexistent_node_errors() {
512        let tmp = tempfile::tempdir().unwrap();
513        let reg_path = test_registry_path(tmp.path());
514
515        let result = remove_node(999, &reg_path);
516        assert!(result.is_err());
517        assert!(matches!(result.unwrap_err(), Error::NodeNotFound(999)));
518    }
519
520    #[tokio::test]
521    async fn reset_clears_all_nodes_and_directories() {
522        let tmp = tempfile::tempdir().unwrap();
523        let binary = create_fake_binary(tmp.path());
524        let reg_path = test_registry_path(tmp.path());
525
526        // Add 2 nodes
527        let opts = AddNodeOpts {
528            count: 2,
529            rewards_address: TEST_ADDR.to_string(),
530            data_dir_path: Some(tmp.path().join("data")),
531            log_dir_path: Some(tmp.path().join("logs")),
532            binary_source: BinarySource::LocalPath(binary),
533            ..Default::default()
534        };
535
536        let result = add_nodes(opts, &reg_path, &NoopProgress).await.unwrap();
537        assert_eq!(result.nodes_added.len(), 2);
538
539        // Verify directories exist
540        for node in &result.nodes_added {
541            assert!(node.data_dir.exists());
542            assert!(node.log_dir.as_ref().unwrap().exists());
543        }
544
545        // Reset
546        let reset_result = reset(&reg_path).unwrap();
547        assert_eq!(reset_result.nodes_cleared, 2);
548        assert_eq!(reset_result.data_dirs_removed.len(), 2);
549        assert_eq!(reset_result.log_dirs_removed.len(), 2);
550
551        // Verify directories were removed
552        for node in &result.nodes_added {
553            assert!(!node.data_dir.exists());
554            assert!(!node.log_dir.as_ref().unwrap().exists());
555        }
556
557        // Verify registry is empty and next_id reset
558        let reg = NodeRegistry::load(&reg_path).unwrap();
559        assert!(reg.is_empty());
560        assert_eq!(reg.next_id, 1);
561    }
562
563    #[test]
564    fn node_status_offline_shows_all_stopped() {
565        let tmp = tempfile::tempdir().unwrap();
566        let reg_path = test_registry_path(tmp.path());
567
568        // Add two nodes directly to the registry
569        let (mut registry, _lock) = NodeRegistry::load_locked(&reg_path).unwrap();
570        registry.add(NodeConfig {
571            id: 0,
572            service_name: String::new(),
573            rewards_address: "0xtest".to_string(),
574            data_dir: PathBuf::from("/tmp/test1"),
575            log_dir: None,
576            node_port: None,
577            metrics_port: None,
578            network_id: None,
579            binary_path: PathBuf::from("/usr/bin/antnode"),
580            version: "0.110.0".to_string(),
581            env_variables: HashMap::new(),
582            bootstrap_peers: vec![],
583            upgrade_channel: None,
584        });
585        registry.add(NodeConfig {
586            id: 0,
587            service_name: String::new(),
588            rewards_address: "0xtest".to_string(),
589            data_dir: PathBuf::from("/tmp/test2"),
590            log_dir: None,
591            node_port: None,
592            metrics_port: None,
593            network_id: None,
594            binary_path: PathBuf::from("/usr/bin/antnode"),
595            version: "0.110.0".to_string(),
596            env_variables: HashMap::new(),
597            bootstrap_peers: vec![],
598            upgrade_channel: None,
599        });
600        registry.save().unwrap();
601        drop(_lock);
602
603        let result = node_status_offline(&reg_path).unwrap();
604        assert_eq!(result.nodes.len(), 2);
605        assert_eq!(result.total_running, 0);
606        assert_eq!(result.total_stopped, 2);
607        for node in &result.nodes {
608            assert_eq!(node.status, NodeStatus::Stopped);
609        }
610    }
611
612    #[test]
613    fn node_status_offline_empty_registry() {
614        let tmp = tempfile::tempdir().unwrap();
615        let reg_path = test_registry_path(tmp.path());
616
617        let result = node_status_offline(&reg_path).unwrap();
618        assert!(result.nodes.is_empty());
619        assert_eq!(result.total_running, 0);
620        assert_eq!(result.total_stopped, 0);
621    }
622
623    #[test]
624    fn reset_empty_registry_succeeds() {
625        let tmp = tempfile::tempdir().unwrap();
626        let reg_path = test_registry_path(tmp.path());
627
628        let result = reset(&reg_path).unwrap();
629        assert_eq!(result.nodes_cleared, 0);
630        assert!(result.data_dirs_removed.is_empty());
631        assert!(result.log_dirs_removed.is_empty());
632    }
633
634    #[tokio::test]
635    async fn reset_then_add_starts_fresh_ids() {
636        let tmp = tempfile::tempdir().unwrap();
637        let binary = create_fake_binary(tmp.path());
638        let reg_path = test_registry_path(tmp.path());
639
640        // Add 2 nodes
641        let opts = AddNodeOpts {
642            count: 2,
643            rewards_address: TEST_ADDR.to_string(),
644            data_dir_path: Some(tmp.path().join("data")),
645            log_dir_path: Some(tmp.path().join("logs")),
646            binary_source: BinarySource::LocalPath(binary.clone()),
647            ..Default::default()
648        };
649        add_nodes(opts, &reg_path, &NoopProgress).await.unwrap();
650
651        // Reset
652        reset(&reg_path).unwrap();
653
654        // Add again — IDs should restart from 1
655        let opts = AddNodeOpts {
656            count: 1,
657            rewards_address: TEST_ADDR.to_string(),
658            data_dir_path: Some(tmp.path().join("data")),
659            log_dir_path: Some(tmp.path().join("logs")),
660            binary_source: BinarySource::LocalPath(binary),
661            ..Default::default()
662        };
663        let result = add_nodes(opts, &reg_path, &NoopProgress).await.unwrap();
664
665        assert_eq!(result.nodes_added[0].id, 1);
666        assert_eq!(result.nodes_added[0].rewards_address, TEST_ADDR);
667    }
668}