diff --git a/masq/src/commands/configuration_command.rs b/masq/src/commands/configuration_command.rs index f9e72d5d4..62d2b4650 100644 --- a/masq/src/commands/configuration_command.rs +++ b/masq/src/commands/configuration_command.rs @@ -131,7 +131,10 @@ impl ConfigurationCommand { dump_parameter_line( stream, "Start block:", - &configuration.start_block.to_string(), + &configuration + .start_block_opt + .map(|m| m.separate_with_commas()) + .unwrap_or_else(|| "[Latest]".to_string()), ); Self::dump_value_list(stream, "Past neighbors:", &configuration.past_neighbors); let payment_thresholds = Self::preprocess_combined_parameters({ @@ -333,7 +336,7 @@ mod tests { exit_byte_rate: 129000000, exit_service_rate: 160000000, }, - start_block: 3456, + start_block_opt: None, scan_intervals: UiScanIntervals { pending_payable_sec: 150500, payable_sec: 155000, @@ -378,7 +381,7 @@ mod tests { |Max block count: [Unlimited]\n\ |Neighborhood mode: standard\n\ |Port mapping protocol: PCP\n\ -|Start block: 3456\n\ +|Start block: [Latest]\n\ |Past neighbors: neighbor 1\n\ | neighbor 2\n\ |Payment thresholds: \n\ @@ -433,7 +436,7 @@ mod tests { exit_byte_rate: 20, exit_service_rate: 30, }, - start_block: 3456, + start_block_opt: Some(1234567890u64), scan_intervals: UiScanIntervals { pending_payable_sec: 1000, payable_sec: 1000, @@ -476,7 +479,7 @@ mod tests { |Max block count: 100,000\n\ |Neighborhood mode: zero-hop\n\ |Port mapping protocol: PCP\n\ -|Start block: 3456\n\ +|Start block: 1,234,567,890\n\ |Past neighbors: [?]\n\ |Payment thresholds: \n\ | Debt threshold: 2,500 gwei\n\ diff --git a/masq/src/commands/set_configuration_command.rs b/masq/src/commands/set_configuration_command.rs index 99d979f07..6f822f1f3 100644 --- a/masq/src/commands/set_configuration_command.rs +++ b/masq/src/commands/set_configuration_command.rs @@ -7,6 +7,7 @@ use masq_lib::shared_schema::gas_price_arg; use masq_lib::shared_schema::min_hops_arg; use masq_lib::short_writeln; use masq_lib::utils::ExpectValue; +use std::num::IntErrorKind; #[derive(Debug, PartialEq, Eq)] pub struct SetConfigurationCommand { @@ -35,9 +36,17 @@ impl SetConfigurationCommand { } fn validate_start_block(start_block: String) -> Result<(), String> { - match start_block.parse::() { - Ok(_) => Ok(()), - _ => Err(start_block), + if "latest".eq_ignore_ascii_case(&start_block) || "none".eq_ignore_ascii_case(&start_block) { + Ok(()) + } else { + match start_block.parse::() { + Ok(_) => Ok(()), + Err(e) if e.kind() == &IntErrorKind::PosOverflow => Err( + format!("Unable to parse '{}' into a starting block number or provide 'none' or 'latest' for the latest block number: digits exceed {}.", + start_block, u64::MAX), + ), + Err(e) => Err(format!("Unable to parse '{}' into a starting block number or provide 'none' or 'latest' for the latest block number: {}.", start_block, e)) + } } } @@ -59,7 +68,7 @@ impl Command for SetConfigurationCommand { const SET_CONFIGURATION_ABOUT: &str = "Sets Node configuration parameters being enabled for this operation when the Node is running."; const START_BLOCK_HELP: &str = - "Ordinal number of the Ethereum block where scanning for transactions will start."; + "Ordinal number of the Ethereum block where scanning for transactions will start. Use 'latest' or 'none' for Latest block."; pub fn set_configurationify<'a>(shared_schema_arg: Arg<'a, 'a>) -> Arg<'a, 'a> { shared_schema_arg.takes_value(true).min_values(1) @@ -103,7 +112,7 @@ mod tests { ); assert_eq!( START_BLOCK_HELP, - "Ordinal number of the Ethereum block where scanning for transactions will start." + "Ordinal number of the Ethereum block where scanning for transactions will start. Use 'latest' or 'none' for Latest block." ); } @@ -122,10 +131,28 @@ mod tests { assert!(result.contains("cannot be used with one or more of the other specified arguments")); } + #[test] + fn validate_start_block_catches_invalid_values() { + assert_eq!(validate_start_block("abc".to_string()), Err("Unable to parse 'abc' into a starting block number or provide 'none' or 'latest' for the latest block number: invalid digit found in string.".to_string())); + assert_eq!(validate_start_block("918446744073709551615".to_string()), Err("Unable to parse '918446744073709551615' into a starting block number or provide 'none' or 'latest' for the latest block number: digits exceed 18446744073709551615.".to_string())); + assert_eq!(validate_start_block("123,456,789".to_string()), Err("Unable to parse '123,456,789' into a starting block number or provide 'none' or 'latest' for the latest block number: invalid digit found in string.".to_string())); + assert_eq!(validate_start_block("123'456'789".to_string()), Err("Unable to parse '123'456'789' into a starting block number or provide 'none' or 'latest' for the latest block number: invalid digit found in string.".to_string())); + } #[test] fn validate_start_block_works() { - assert!(validate_start_block("abc".to_string()).is_err()); - assert!(validate_start_block("1566".to_string()).is_ok()); + assert_eq!( + validate_start_block("18446744073709551615".to_string()), + Ok(()) + ); + assert_eq!(validate_start_block("1566".to_string()), Ok(())); + assert_eq!(validate_start_block("none".to_string()), Ok(())); + assert_eq!(validate_start_block("None".to_string()), Ok(())); + assert_eq!(validate_start_block("NONE".to_string()), Ok(())); + assert_eq!(validate_start_block("nOnE".to_string()), Ok(())); + assert_eq!(validate_start_block("latest".to_string()), Ok(())); + assert_eq!(validate_start_block("LATEST".to_string()), Ok(())); + assert_eq!(validate_start_block("LaTeST".to_string()), Ok(())); + assert_eq!(validate_start_block("lATEst".to_string()), Ok(())); } #[test] diff --git a/masq_lib/src/messages.rs b/masq_lib/src/messages.rs index 59522171e..45842e419 100644 --- a/masq_lib/src/messages.rs +++ b/masq_lib/src/messages.rs @@ -492,7 +492,7 @@ pub struct UiConfigurationResponse { #[serde(rename = "portMappingProtocol")] pub port_mapping_protocol_opt: Option, #[serde(rename = "startBlock")] - pub start_block: u64, + pub start_block_opt: Option, #[serde(rename = "consumingWalletPrivateKeyOpt")] pub consuming_wallet_private_key_opt: Option, // This item is calculated from the private key, not stored in the database, so that diff --git a/multinode_integration_tests/docker/blockchain/Dockerfile b/multinode_integration_tests/docker/blockchain/Dockerfile index 027eb7a27..7ff65ea16 100644 --- a/multinode_integration_tests/docker/blockchain/Dockerfile +++ b/multinode_integration_tests/docker/blockchain/Dockerfile @@ -1,8 +1,8 @@ # Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -FROM trufflesuite/ganache-cli:v6.7.0 +FROM trufflesuite/ganache-cli:v6.12.2 ADD ./entrypoint.sh /app/ EXPOSE 18545 -ENTRYPOINT /app/entrypoint.sh +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/multinode_integration_tests/docker/blockchain/entrypoint.sh b/multinode_integration_tests/docker/blockchain/entrypoint.sh index f9d6cc220..c184cbb50 100755 --- a/multinode_integration_tests/docker/blockchain/entrypoint.sh +++ b/multinode_integration_tests/docker/blockchain/entrypoint.sh @@ -1,3 +1,8 @@ #!/bin/sh -node /app/ganache-core.docker.cli.js -p 18545 --networkId 2 --verbose --mnemonic "timber cage wide hawk phone shaft pattern movie army dizzy hen tackle lamp absent write kind term toddler sphere ripple idle dragon curious hold" +node /app/ganache-core.docker.cli.js \ + -h 0.0.0.0 \ + -p 18545 \ + --networkId 2 \ + --verbose \ + --mnemonic "timber cage wide hawk phone shaft pattern movie army dizzy hen tackle lamp absent write kind term toddler sphere ripple idle dragon curious hold" diff --git a/multinode_integration_tests/src/main.rs b/multinode_integration_tests/src/main.rs index d78421672..8f705fed9 100644 --- a/multinode_integration_tests/src/main.rs +++ b/multinode_integration_tests/src/main.rs @@ -1,11 +1,10 @@ // Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved. -use self::sub_lib::utils::indicates_dead_stream; use masq_lib::command::{Command, StdStreams}; use masq_lib::constants::{HIGHEST_USABLE_PORT, LOWEST_USABLE_INSECURE_PORT}; -use node_lib::sub_lib; use node_lib::sub_lib::framer::Framer; use node_lib::sub_lib::node_addr::NodeAddr; +use node_lib::sub_lib::utils::indicates_dead_stream; use node_lib::test_utils::data_hunk::DataHunk; use node_lib::test_utils::data_hunk_framer::DataHunkFramer; use std::borrow::BorrowMut; @@ -14,10 +13,9 @@ use std::env; use std::io; use std::io::Read; use std::io::Write; -use std::net::Shutdown; -use std::net::SocketAddr; use std::net::TcpListener; use std::net::TcpStream; +use std::net::{Shutdown, SocketAddr}; use std::process; use std::str::FromStr; use std::sync::{Arc, Mutex, MutexGuard}; @@ -223,10 +221,10 @@ impl MockNode { } fn usage(stderr: &mut dyn Write) -> u8 { - writeln! (stderr, "Usage: MockNode ://... where is the address MockNode is running on and is between {} and {}", - LOWEST_USABLE_INSECURE_PORT, - HIGHEST_USABLE_PORT, - ).unwrap (); + writeln!(stderr, "Usage: MockNode ://... where is the address MockNode is running on and is between {} and {}", + LOWEST_USABLE_INSECURE_PORT, + HIGHEST_USABLE_PORT, + ).unwrap(); 1 } @@ -369,7 +367,7 @@ mod tests { assert_eq!(result, 1); let stderr = holder.stderr; - assert_eq! (stderr.get_string (), String::from ("Usage: MockNode ://... where is the address MockNode is running on and is between 1025 and 65535\n\n")); + assert_eq!(stderr.get_string(), String::from("Usage: MockNode ://... where is the address MockNode is running on and is between 1025 and 65535\n\n")); } #[test] diff --git a/multinode_integration_tests/src/mock_blockchain_client_server.rs b/multinode_integration_tests/src/mock_blockchain_client_server.rs index 24031b2cc..a40543808 100644 --- a/multinode_integration_tests/src/mock_blockchain_client_server.rs +++ b/multinode_integration_tests/src/mock_blockchain_client_server.rs @@ -241,8 +241,10 @@ impl MockBlockchainClientServer { let mut requests = requests_arc.lock().unwrap(); requests.push(body); } - let response = responses.remove(0); - Self::send_body(conn_state, response); + if !responses.is_empty() { + let response = responses.remove(0); + Self::send_body(conn_state, response); + } let _ = notifier_tx.send(()); // receiver doesn't exist if test didn't set it up } None => (), @@ -437,7 +439,7 @@ mod tests { .response("Thank you and good night", 40) .start(); let mut client = connect(port); - client.write (b"POST /biddle HTTP/1.1\r\nContent-Length: 5\r\n\r\nfirstPOST /biddle HTTP/1.1\r\nContent-Length: 6\r\n\r\nsecond").unwrap(); + client.write(b"POST /biddle HTTP/1.1\r\nContent-Length: 5\r\n\r\nfirstPOST /biddle HTTP/1.1\r\nContent-Length: 6\r\n\r\nsecond").unwrap(); let (_, body) = receive_response(&mut client); assert_eq!( @@ -567,7 +569,7 @@ mod tests { assert_eq!(notified.try_recv().is_err(), true); let requests = subject.requests(); - assert_eq! (requests, vec! [ + assert_eq!(requests, vec![ "POST /biddle HTTP/1.1\r\nContent-Type: application-json\r\nContent-Length: 82\r\n\r\n{\"jsonrpc\": \"2.0\", \"method\": \"first\", \"params\": [\"biddle\", \"de\", \"bee\"], \"id\": 40}".to_string(), "POST /biddle HTTP/1.1\r\nContent-Type: application-json\r\nContent-Length: 48\r\n\r\n{\"jsonrpc\": \"2.0\", \"method\": \"second\", \"id\": 42}".to_string(), "POST /biddle HTTP/1.1\r\nContent-Type: application-json\r\nContent-Length: 47\r\n\r\n{\"jsonrpc\": \"2.0\", \"method\": \"third\", \"id\": 42}".to_string(), @@ -600,7 +602,7 @@ mod tests { r#"{"jsonrpc": "2.0", "result": {"name":"Billy","age":15}, "id": 42}"# ); let requests = subject.requests(); - assert_eq! (requests, vec! [ + assert_eq!(requests, vec![ "POST / HTTP/1.1\r\ncontent-type: application/json\r\nuser-agent: web3.rs\r\nhost: 172.18.0.1:32768\r\ncontent-length: 308\r\n\r\n{\"jsonrpc\":\"2.0\",\"method\":\"eth_getLogs\",\"params\":[{\"address\":\"0x59882e4a8f5d24643d4dda422922a870f1b3e664\",\"fromBlock\":\"0x3e8\",\"toBlock\":\"latest\",\"topics\":[\"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\",null,\"0x00000000000000000000000027d9a2ac83b493f88ce9b4532edcf74e95b9788d\"]}],\"id\":0}".to_string() ]) } @@ -704,6 +706,6 @@ mod tests { body.len(), body ) - .into_bytes() + .into_bytes() } } diff --git a/multinode_integration_tests/tests/verify_bill_payment.rs b/multinode_integration_tests/tests/verify_bill_payment.rs index 5e9b50347..1240deb58 100644 --- a/multinode_integration_tests/tests/verify_bill_payment.rs +++ b/multinode_integration_tests/tests/verify_bill_payment.rs @@ -38,10 +38,7 @@ use web3::Web3; #[test] fn verify_bill_payment() { - let mut cluster = match MASQNodeCluster::start() { - Ok(cluster) => cluster, - Err(e) => panic!("{}", e), - }; + let mut cluster = MASQNodeCluster::start().unwrap(); let blockchain_server = BlockchainServer { name: "ganache-cli", }; @@ -64,7 +61,7 @@ fn verify_bill_payment() { assert_balances( &contract_owner_wallet, &blockchain_interface, - "99998043204000000000", + "99998381140000000000", "472000000000000000000000000", ); let payment_thresholds = PaymentThresholds { @@ -189,7 +186,7 @@ fn verify_bill_payment() { assert_balances( &contract_owner_wallet, &blockchain_interface, - "99998043204000000000", + "99998381140000000000", "472000000000000000000000000", ); @@ -235,7 +232,7 @@ fn verify_bill_payment() { assert_balances( &contract_owner_wallet, &blockchain_interface, - "99997886466000000000", + "99998223682000000000", "471999999700000000000000000", ); @@ -330,7 +327,7 @@ fn assert_balances( assert_eq!( format!("{}", eth_balance), String::from(expected_eth_balance), - "Actual EthBalance {} doesn't much with expected {}", + "Actual EthBalance {} doesn't match with expected {}", eth_balance, expected_eth_balance ); diff --git a/node/src/accountant/mod.rs b/node/src/accountant/mod.rs index cd7381622..e76b15a0d 100644 --- a/node/src/accountant/mod.rs +++ b/node/src/accountant/mod.rs @@ -1056,6 +1056,7 @@ mod tests { use crate::blockchain::test_utils::{make_tx_hash, BlockchainInterfaceMock}; use crate::database::rusqlite_wrappers::TransactionSafeWrapper; use crate::database::test_utils::transaction_wrapper_mock::TransactionInnerWrapperMockBuilder; + use crate::db_config::config_dao::ConfigDaoRecord; use crate::db_config::mocks::ConfigDaoMock; use crate::match_every_type_id; use crate::sub_lib::accountant::{ @@ -1373,7 +1374,11 @@ mod tests { config.suppress_initial_scans = true; let subject = AccountantBuilder::default() .bootstrapper_config(config) - .config_dao(ConfigDaoMock::new().set_result(Ok(()))) + .config_dao( + ConfigDaoMock::new() + .get_result(Ok(ConfigDaoRecord::new("start_block", None, false))) + .set_result(Ok(())), + ) .build(); let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); let subject_addr = subject.start(); @@ -1991,6 +1996,7 @@ mod tests { ) { let more_money_received_params_arc = Arc::new(Mutex::new(vec![])); let commit_params_arc = Arc::new(Mutex::new(vec![])); + let get_params_arc = Arc::new(Mutex::new(vec![])); let set_by_guest_transaction_params_arc = Arc::new(Mutex::new(vec![])); let now = SystemTime::now(); let earning_wallet = make_wallet("earner3000"); @@ -2014,6 +2020,8 @@ mod tests { .more_money_received_params(&more_money_received_params_arc) .more_money_received_result(wrapped_transaction); let config_dao = ConfigDaoMock::new() + .get_params(&get_params_arc) + .get_result(Ok(ConfigDaoRecord::new("start_block", None, false))) .set_by_guest_transaction_params(&set_by_guest_transaction_params_arc) .set_by_guest_transaction_result(Ok(())); let accountant = AccountantBuilder::default() @@ -2028,7 +2036,7 @@ mod tests { .try_send(ReceivedPayments { timestamp: now, payments: vec![expected_receivable_1.clone(), expected_receivable_2.clone()], - new_start_block: 123456789, + new_start_block: 123456789u64, response_skeleton_opt: None, }) .expect("unexpected actix error"); @@ -4774,6 +4782,7 @@ mod tests { let factory = Accountant::dao_factory(data_dir); factory.make(); }; + assert_on_initialization_with_panic_on_migration(&data_dir, &act); } } diff --git a/node/src/accountant/scanners/mod.rs b/node/src/accountant/scanners/mod.rs index f8bc4b163..fc5f5ce91 100644 --- a/node/src/accountant/scanners/mod.rs +++ b/node/src/accountant/scanners/mod.rs @@ -862,7 +862,7 @@ impl Scanner for ReceivableScanner { match self .persistent_configuration - .set_start_block(msg.new_start_block) + .set_start_block(Some(msg.new_start_block)) { Ok(()) => debug!(logger, "Start block updated to {}", msg.new_start_block), Err(e) => panic!( @@ -914,7 +914,7 @@ impl ReceivableScanner { let new_start_block = msg.new_start_block; match self .persistent_configuration - .set_start_block_from_txn(new_start_block, &mut txn) + .set_start_block_from_txn(Some(new_start_block), &mut txn) { Ok(()) => (), Err(e) => panic!( @@ -1243,7 +1243,7 @@ mod tests { assert_eq!(receivable_scanner.common.initiated_at_opt.is_some(), false); receivable_scanner .persistent_configuration - .set_start_block(136890) + .set_start_block(Some(136890)) .unwrap(); let set_params = set_params_arc.lock().unwrap(); assert_eq!( @@ -1631,7 +1631,7 @@ mod tests { .iter() .map(|ppayable| ppayable.hash) .collect::>(); - // Not in an ascending order + // Not in ascending order let rowids_and_hashes_from_fingerprints = vec![(hash_1, 3), (hash_3, 5), (hash_2, 6)] .iter() .map(|(hash, _id)| *hash) @@ -1883,7 +1883,7 @@ mod tests { 00000000000000000000000000000000000000000315 failed due to RecordDeletion(\"Gosh, I overslept \ without an alarm set\")"); let log_handler = TestLogHandler::new(); - // There is a possible situation when we stumble over missing fingerprints and so we log it. + // There is a possible situation when we stumble over missing fingerprints, so we log it. // Here we don't and so any ERROR log shouldn't turn up log_handler.exists_no_log_containing(&format!("ERROR: {}", test_name)) } @@ -3070,6 +3070,7 @@ mod tests { let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); let new_start_block = 4321; let persistent_config = PersistentConfigurationMock::new() + .start_block_result(Ok(None)) .set_start_block_params(&set_start_block_params_arc) .set_start_block_result(Ok(())); let mut subject = ReceivableScannerBuilder::new() @@ -3086,7 +3087,7 @@ mod tests { assert_eq!(message_opt, None); let set_start_block_params = set_start_block_params_arc.lock().unwrap(); - assert_eq!(*set_start_block_params, vec![4321]); + assert_eq!(*set_start_block_params, vec![Some(4321)]); TestLogHandler::new().exists_log_containing(&format!( "INFO: {test_name}: No newly received payments were detected during the scanning process." )); @@ -3099,16 +3100,21 @@ mod tests { init_test_logging(); let test_name = "no_transactions_received_but_start_block_setting_fails"; let now = SystemTime::now(); - let persistent_config = PersistentConfigurationMock::new().set_start_block_result(Err( - PersistentConfigError::UninterpretableValue("Illiterate database manager".to_string()), - )); + let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); + let new_start_block = 6709u64; + let persistent_config = PersistentConfigurationMock::new() + .start_block_result(Ok(None)) + .set_start_block_params(&set_start_block_params_arc) + .set_start_block_result(Err(PersistentConfigError::UninterpretableValue( + "Illiterate database manager".to_string(), + ))); let mut subject = ReceivableScannerBuilder::new() .persistent_configuration(persistent_config) .build(); let msg = ReceivedPayments { timestamp: now, payments: vec![], - new_start_block: 6709, + new_start_block, response_skeleton_opt: None, }; // Not necessary, rather for preciseness @@ -3132,6 +3138,7 @@ mod tests { .set_arbitrary_id_stamp(transaction_id); let transaction = TransactionSafeWrapper::new_with_builder(txn_inner_builder); let persistent_config = PersistentConfigurationMock::new() + .start_block_result(Ok(None)) .set_start_block_from_txn_params(&set_start_block_from_txn_params_arc) .set_start_block_from_txn_result(Ok(())); let receivable_dao = ReceivableDaoMock::new() @@ -3178,7 +3185,7 @@ mod tests { let set_by_guest_transaction_params = set_start_block_from_txn_params_arc.lock().unwrap(); assert_eq!( *set_by_guest_transaction_params, - vec![(7890123, transaction_id)] + vec![(Some(7890123u64), transaction_id)] ); let commit_params = commit_params_arc.lock().unwrap(); assert_eq!(*commit_params, vec![()]); @@ -3196,9 +3203,11 @@ mod tests { let now = SystemTime::now(); let txn_inner_builder = TransactionInnerWrapperMockBuilder::default(); let transaction = TransactionSafeWrapper::new_with_builder(txn_inner_builder); - let persistent_config = PersistentConfigurationMock::new().set_start_block_from_txn_result( - Err(PersistentConfigError::DatabaseError("Fatigue".to_string())), - ); + let persistent_config = PersistentConfigurationMock::new() + .start_block_result(Ok(None)) + .set_start_block_from_txn_result(Err(PersistentConfigError::DatabaseError( + "Fatigue".to_string(), + ))); let receivable_dao = ReceivableDaoMock::new().more_money_received_result(transaction); let mut subject = ReceivableScannerBuilder::new() .receivable_dao(receivable_dao) @@ -3240,8 +3249,9 @@ mod tests { let txn_inner_builder = TransactionInnerWrapperMockBuilder::default().commit_result(commit_err); let transaction = TransactionSafeWrapper::new_with_builder(txn_inner_builder); - let persistent_config = - PersistentConfigurationMock::new().set_start_block_from_txn_result(Ok(())); + let persistent_config = PersistentConfigurationMock::new() + .start_block_result(Ok(None)) + .set_start_block_from_txn_result(Ok(())); let receivable_dao = ReceivableDaoMock::new().more_money_received_result(transaction); let mut subject = ReceivableScannerBuilder::new() .receivable_dao(receivable_dao) diff --git a/node/src/blockchain/blockchain_bridge.rs b/node/src/blockchain/blockchain_bridge.rs index 0bb34fbfd..eadfc40d3 100644 --- a/node/src/blockchain/blockchain_bridge.rs +++ b/node/src/blockchain/blockchain_bridge.rs @@ -281,67 +281,82 @@ impl BlockchainBridge { fn handle_retrieve_transactions(&mut self, msg: RetrieveTransactions) -> Result<(), String> { let start_block_nbr = match self.persistent_config.start_block() { - Ok (sb) => sb, - Err (e) => panic! ("Cannot retrieve start block from database; payments to you may not be processed: {:?}", e) + Ok(Some(sb)) => sb, + Ok(None) => u64::MAX, + Err(e) => panic!("Cannot retrieve start block from database; payments to you may not be processed: {:?}", e) }; let max_block_count = match self.persistent_config.max_block_count() { Ok(Some(mbc)) => mbc, _ => u64::MAX, }; + let use_unlimited_block_count_range = u64::MAX == max_block_count; + let use_latest_block = u64::MAX == start_block_nbr; let end_block = match self .blockchain_interface .lower_interface() .get_block_number() { Ok(eb) => { - if u64::MAX == max_block_count { + if use_unlimited_block_count_range || use_latest_block { BlockNumber::Number(eb) } else { BlockNumber::Number(eb.as_u64().min(start_block_nbr + max_block_count).into()) } } Err(e) => { - info!( - self.logger, - "Using 'latest' block number instead of a literal number. {:?}", e - ); - if max_block_count == u64::MAX { + if use_unlimited_block_count_range || use_latest_block { + debug!( + self.logger, + "Using 'latest' block number instead of a literal number. {:?}", e + ); BlockNumber::Latest } else { + debug!( + self.logger, + "Using '{}' ending block number. {:?}", + start_block_nbr + max_block_count, + e + ); BlockNumber::Number((start_block_nbr + max_block_count).into()) } } }; - let start_block = BlockNumber::Number(start_block_nbr.into()); + let start_block = if use_latest_block { + end_block + } else { + BlockNumber::Number(start_block_nbr.into()) + }; let retrieved_transactions = self.blockchain_interface .retrieve_transactions(start_block, end_block, &msg.recipient); match retrieved_transactions { Ok(transactions) => { - if transactions.transactions.is_empty() { - debug!(self.logger, "No new receivable detected"); + if let BlockNumber::Number(new_start_block_number) = transactions.new_start_block { + if transactions.transactions.is_empty() { + debug!(self.logger, "No new receivable detected"); + } + self.received_payments_subs_opt + .as_ref() + .expect("Accountant is unbound") + .try_send(ReceivedPayments { + timestamp: SystemTime::now(), + payments: transactions.transactions, + new_start_block: new_start_block_number.as_u64(), + response_skeleton_opt: msg.response_skeleton_opt, + }) + .expect("Accountant is dead."); } - self.received_payments_subs_opt - .as_ref() - .expect("Accountant is unbound") - .try_send(ReceivedPayments { - timestamp: SystemTime::now(), - payments: transactions.transactions, - new_start_block: transactions.new_start_block, - response_skeleton_opt: msg.response_skeleton_opt, - }) - .expect("Accountant is dead."); Ok(()) } Err(e) => { if let Some(max_block_count) = self.extract_max_block_count(e.clone()) { - debug!(self.logger, "Writing max_block_count({})", max_block_count); + debug!(self.logger, "Writing max_block_count({})", &max_block_count); self.persistent_config .set_max_block_count(Some(max_block_count)) .map_or_else( |_| { - warning!(self.logger, "{} update max_block_count to {}. Scheduling next scan with that limit.", e, max_block_count); - Err(format!("{} updated max_block_count to {}. Scheduling next scan with that limit.", e, max_block_count)) + warning!(self.logger, "{} update max_block_count to {}. Scheduling next scan with that limit.", e, &max_block_count); + Err(format!("{} updated max_block_count to {}. Scheduling next scan with that limit.", e, &max_block_count)) }, |e| { warning!(self.logger, "Writing max_block_count failed: {:?}", e); @@ -1005,7 +1020,7 @@ mod tests { .lower_interface_results(Box::new(lower_interface)); let persistent_config = PersistentConfigurationMock::new() .max_block_count_result(Ok(Some(100_000))) - .start_block_result(Ok(5)); // no set_start_block_result: set_start_block() must not be called + .start_block_result(Ok(Some(5))); // no set_start_block_result: set_start_block() must not be called let mut subject = BlockchainBridge::new( Box::new(blockchain_interface), Box::new(persistent_config), @@ -1273,7 +1288,7 @@ mod tests { let amount = 42; let amount2 = 55; let expected_transactions = RetrievedBlockchainTransactions { - new_start_block: 8675309u64, + new_start_block: BlockNumber::Number(8675309u64.into()), transactions: vec![ BlockchainTransaction { block_number: 7, @@ -1296,8 +1311,8 @@ mod tests { .retrieve_transactions_result(Ok(expected_transactions.clone())) .lower_interface_results(Box::new(lower_interface)); let persistent_config = PersistentConfigurationMock::new() - .max_block_count_result(Ok(Some(10000u64))) - .start_block_result(Ok(6)); + .max_block_count_result(Ok(None)) + .start_block_result(Ok(Some(6))); let subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), Box::new(persistent_config), @@ -1326,7 +1341,7 @@ mod tests { *retrieve_transactions_params, vec![( BlockNumber::Number(6u64.into()), - BlockNumber::Number(10006u64.into()), + BlockNumber::Latest, earning_wallet )] ); @@ -1346,7 +1361,175 @@ mod tests { }), } ); - TestLogHandler::new().exists_log_containing("INFO: BlockchainBridge: Using 'latest' block number instead of a literal number. QueryFailed(\"Failed to read the latest block number\")"); + TestLogHandler::new().exists_log_containing("DEBUG: BlockchainBridge: Using 'latest' block number instead of a literal number. QueryFailed(\"Failed to read the latest block number\")"); + } + + #[test] + fn handle_retrieve_transactions_when_start_block_number_starts_undefined_in_a_brand_new_database( + ) { + let retrieve_transactions_params_arc = Arc::new(Mutex::new(vec![])); + let system = System::new( + "handle_retrieve_transactions_when_start_block_number_starts_undefined_in_a_brand_new_database", + ); + let (accountant, _, accountant_recording_arc) = make_recorder(); + let earning_wallet = make_wallet("somewallet"); + let amount = 42; + let amount2 = 55; + let expected_transactions = RetrievedBlockchainTransactions { + new_start_block: BlockNumber::Number(8675309u64.into()), + transactions: vec![ + BlockchainTransaction { + block_number: 8675308u64, + from: earning_wallet.clone(), + wei_amount: amount, + }, + BlockchainTransaction { + block_number: 8675309u64, + from: earning_wallet.clone(), + wei_amount: amount2, + }, + ], + }; + let lower_interface = LowBlockchainIntMock::default().get_block_number_result( + LatestBlockNumber::Err(BlockchainError::QueryFailed( + "\"Failed to read the latest block number\"".to_string(), + )), + ); + let blockchain_interface_mock = BlockchainInterfaceMock::default() + .retrieve_transactions_params(&retrieve_transactions_params_arc) + .retrieve_transactions_result(Ok(expected_transactions.clone())) + .lower_interface_results(Box::new(lower_interface)); + let persistent_config = PersistentConfigurationMock::new() + .max_block_count_result(Ok(None)) + .start_block_result(Ok(None)); + let subject = BlockchainBridge::new( + Box::new(blockchain_interface_mock), + Box::new(persistent_config), + false, + ); + let addr = subject.start(); + let subject_subs = BlockchainBridge::make_subs_from(&addr); + let peer_actors = peer_actors_builder().accountant(accountant).build(); + send_bind_message!(subject_subs, peer_actors); + let retrieve_transactions = RetrieveTransactions { + recipient: earning_wallet.clone(), + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321, + }), + }; + let before = SystemTime::now(); + + let _ = addr.try_send(retrieve_transactions).unwrap(); + + System::current().stop(); + system.run(); + let after = SystemTime::now(); + let retrieve_transactions_params = retrieve_transactions_params_arc.lock().unwrap(); + assert_eq!( + *retrieve_transactions_params, + vec![(BlockNumber::Latest, BlockNumber::Latest, earning_wallet)] + ); + let accountant_received_payment = accountant_recording_arc.lock().unwrap(); + assert_eq!(accountant_received_payment.len(), 1); + let received_payments = accountant_received_payment.get_record::(0); + check_timestamp(before, received_payments.timestamp, after); + assert_eq!( + received_payments, + &ReceivedPayments { + timestamp: received_payments.timestamp, + payments: expected_transactions.transactions, + new_start_block: 8675309u64, + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321 + }), + } + ); + } + + #[test] + fn handle_retrieve_transactions_with_latest_for_start_and_end_block_is_supported() { + let retrieve_transactions_params_arc = Arc::new(Mutex::new(vec![])); + let earning_wallet = make_wallet("somewallet"); + let amount = 42; + let amount2 = 55; + let expected_transactions = RetrievedBlockchainTransactions { + new_start_block: BlockNumber::Number(98765u64.into()), + transactions: vec![ + BlockchainTransaction { + block_number: 77, + from: earning_wallet.clone(), + wei_amount: amount, + }, + BlockchainTransaction { + block_number: 99, + from: earning_wallet.clone(), + wei_amount: amount2, + }, + ], + }; + + let system = System::new( + "handle_retrieve_transactions_with_latest_for_start_and_end_block_is_supported", + ); + let (accountant, _, accountant_recording_arc) = make_recorder(); + let persistent_config = PersistentConfigurationMock::new() + .max_block_count_result(Ok(None)) + .start_block_result(Ok(None)); + let latest_block_number = LatestBlockNumber::Err(BlockchainError::QueryFailed( + "Failed to read from block chain service".to_string(), + )); + let lower_interface = + LowBlockchainIntMock::default().get_block_number_result(latest_block_number); + let blockchain_interface = BlockchainInterfaceMock::default() + .retrieve_transactions_params(&retrieve_transactions_params_arc) + .retrieve_transactions_result(Ok(expected_transactions.clone())) + .lower_interface_results(Box::new(lower_interface)); + let subject = BlockchainBridge::new( + Box::new(blockchain_interface), + Box::new(persistent_config), + false, + ); + let addr = subject.start(); + let subject_subs = BlockchainBridge::make_subs_from(&addr); + let peer_actors = peer_actors_builder().accountant(accountant).build(); + send_bind_message!(subject_subs, peer_actors); + let retrieve_transactions = RetrieveTransactions { + recipient: earning_wallet.clone(), + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321, + }), + }; + let before = SystemTime::now(); + + let _ = addr.try_send(retrieve_transactions).unwrap(); + + System::current().stop(); + system.run(); + let after = SystemTime::now(); + let retrieve_transactions_params = retrieve_transactions_params_arc.lock().unwrap(); + assert_eq!( + *retrieve_transactions_params, + vec![(BlockNumber::Latest, BlockNumber::Latest, earning_wallet)] + ); + let accountant_received_payment = accountant_recording_arc.lock().unwrap(); + assert_eq!(accountant_received_payment.len(), 1); + let received_payments = accountant_received_payment.get_record::(0); + check_timestamp(before, received_payments.timestamp, after); + assert_eq!( + received_payments, + &ReceivedPayments { + timestamp: received_payments.timestamp, + payments: expected_transactions.transactions, + new_start_block: 98765, + response_skeleton_opt: Some(ResponseSkeleton { + client_id: 1234, + context_id: 4321 + }), + } + ); } #[test] @@ -1359,7 +1542,7 @@ mod tests { let amount = 42; let amount2 = 55; let expected_transactions = RetrievedBlockchainTransactions { - new_start_block: 9876, + new_start_block: BlockNumber::Number(9876.into()), transactions: vec![ BlockchainTransaction { block_number: 7, @@ -1382,7 +1565,7 @@ mod tests { .lower_interface_results(Box::new(lower_interface)); let persistent_config = PersistentConfigurationMock::new() .max_block_count_result(Ok(Some(10000u64))) - .start_block_result(Ok(6)); + .start_block_result(Ok(Some(6))); let subject = BlockchainBridge::new( Box::new(blockchain_interface_mock), Box::new(persistent_config), @@ -1440,13 +1623,13 @@ mod tests { LowBlockchainIntMock::default().get_block_number_result(Ok(0u64.into())); let blockchain_interface_mock = BlockchainInterfaceMock::default() .retrieve_transactions_result(Ok(RetrievedBlockchainTransactions { - new_start_block: 7, + new_start_block: BlockNumber::Number(7.into()), transactions: vec![], })) .lower_interface_results(Box::new(lower_interface)); let persistent_config = PersistentConfigurationMock::new() .max_block_count_result(Ok(Some(10000u64))) - .start_block_result(Ok(6)); + .start_block_result(Ok(Some(6))); let (accountant, _, accountant_recording_arc) = make_recorder(); let system = System::new( "processing_of_received_payments_continues_even_if_no_payments_are_detected", diff --git a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs index b9bfa37bf..a4425a82f 100644 --- a/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs +++ b/node/src/blockchain/blockchain_interface/blockchain_interface_web3/mod.rs @@ -119,12 +119,12 @@ where .build(); let fallback_start_block_number = match end_block { - BlockNumber::Number(eb) => eb.as_u64(), + BlockNumber::Number(eb) => Some(eb.as_u64()), _ => { if let BlockNumber::Number(start_block_number) = start_block { - start_block_number.as_u64() + 1u64 + Some(start_block_number.as_u64() + 1u64) } else { - panic!("start_block of Latest, Earliest, and Pending are not supported"); + None } } }; @@ -134,15 +134,15 @@ where let logger = self.logger.clone(); match self.web3_batch.transport().submit_batch().wait() { Ok(_) => { - let response_block_number = match block_request.wait() { + let response_block_number_opt = match block_request.wait() { Ok(block_nbr) => { debug!(logger, "Latest block number: {}", block_nbr.as_u64()); - block_nbr.as_u64() + Some(block_nbr.as_u64()) } Err(_) => { debug!( logger, - "Using fallback block number: {}", fallback_start_block_number + "Using fallback block number: {:?}", fallback_start_block_number ); fallback_start_block_number } @@ -178,16 +178,18 @@ where // was not successful. let transaction_max_block_number = self .find_largest_transaction_block_number( - response_block_number, + response_block_number_opt, &transactions, ); debug!( logger, - "Discovered transaction max block nbr: {}", + "Discovered transaction max block nbr: {:?}", transaction_max_block_number ); Ok(RetrievedBlockchainTransactions { - new_start_block: 1u64 + transaction_max_block_number, + new_start_block: transaction_max_block_number + .map(|nsb| BlockNumber::Number((1u64 + nsb).into())) + .unwrap_or(BlockNumber::Latest), transactions, }) } @@ -603,15 +605,18 @@ where fn find_largest_transaction_block_number( &self, - response_block_number: u64, + response_block_number: Option, transactions: &[BlockchainTransaction], - ) -> u64 { + ) -> Option { if transactions.is_empty() { response_block_number } else { transactions .iter() - .fold(response_block_number, |a, b| a.max(b.block_number)) + .fold(response_block_number.unwrap_or(0u64), |a, b| { + a.max(b.block_number) + }) + .into() } } } @@ -833,7 +838,7 @@ mod tests { assert_eq!( result, RetrievedBlockchainTransactions { - new_start_block: 0x4be664, + new_start_block: BlockNumber::Number(0x4be664.into()), transactions: vec![ BlockchainTransaction { block_number: 0x4be663, @@ -895,7 +900,7 @@ mod tests { assert_eq!( result, RetrievedBlockchainTransactions { - new_start_block: 1 + end_block_nbr, + new_start_block: BlockNumber::Number((1 + end_block_nbr).into()), transactions: vec![] } ); @@ -1004,7 +1009,7 @@ mod tests { assert_eq!( result, Ok(RetrievedBlockchainTransactions { - new_start_block: 1 + end_block_nbr, + new_start_block: BlockNumber::Number((1 + end_block_nbr).into()), transactions: vec![] }) ); @@ -1046,7 +1051,39 @@ mod tests { assert_eq!( result, Ok(RetrievedBlockchainTransactions { - new_start_block: 1 + expected_fallback_start_block, + new_start_block: BlockNumber::Number((1 + expected_fallback_start_block).into()), + transactions: vec![] + }) + ); + } + + #[test] + fn blockchain_interface_retrieve_transactions_start_and_end_blocks_can_be_latest() { + let port = find_free_port(); + let contains_error_causing_to_pick_fallback_value = br#"[{"jsonrpc":"2.0","id":1,"result":"error"},{"jsonrpc":"2.0","id":2,"result":[{"address":"0xcd6c588e005032dd882cd43bf53a32129be81302","blockHash":"0x1a24b9169cbaec3f6effa1f600b70c7ab9e8e86db44062b49132a4415d26732a","data":"0x0000000000000000000000000000000000000000000000000010000000000000","logIndex":"0x0","removed":false,"topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003f69f9efd4f2592fd70be8c32ecd9dce71c472fc","0x000000000000000000000000adc1853c7859369639eb414b6342b36288fe6092"],"transactionHash":"0x955cec6ac4f832911ab894ce16aa22c3003f46deff3f7165b32700d2f5ff0681","transactionIndex":"0x0"}]}]"#; + let _test_server = TestServer::start( + port, + vec![contains_error_causing_to_pick_fallback_value.to_vec()], + ); + let (event_loop_handle, transport) = Http::with_max_parallel( + &format!("http://{}:{}", &Ipv4Addr::LOCALHOST, port), + REQUESTS_IN_PARALLEL, + ) + .unwrap(); + let chain = TEST_DEFAULT_CHAIN; + let subject = BlockchainInterfaceWeb3::new(transport, event_loop_handle, chain); + + let result = subject.retrieve_transactions( + BlockNumber::Latest, + BlockNumber::Latest, + &make_wallet("earning-wallet"), + ); + + let expected_new_start_block = BlockNumber::Latest; + assert_eq!( + result, + Ok(RetrievedBlockchainTransactions { + new_start_block: expected_new_start_block, transactions: vec![] }) ); @@ -1221,7 +1258,7 @@ mod tests { //exercising also the layer of web3 functions, but the transport layer is mocked init_test_logging(); let send_batch_params_arc = Arc::new(Mutex::new(vec![])); - //we compute the hashes ourselves during the batch preparation and so we don't care about + //we compute the hashes ourselves during the batch preparation, and so we don't care about //the same ones coming back with the response; we use the returned OKs as indicators of success only. //Any eventual rpc errors brought back are processed as well... let expected_batch_responses = vec![ diff --git a/node/src/blockchain/blockchain_interface/data_structures/mod.rs b/node/src/blockchain/blockchain_interface/data_structures/mod.rs index d1d785aae..d8b86d5d9 100644 --- a/node/src/blockchain/blockchain_interface/data_structures/mod.rs +++ b/node/src/blockchain/blockchain_interface/data_structures/mod.rs @@ -3,7 +3,7 @@ pub mod errors; use crate::accountant::db_access_objects::pending_payable_dao::PendingPayable; use crate::sub_lib::wallet::Wallet; -use web3::types::H256; +use web3::types::{BlockNumber, H256}; use web3::Error; #[derive(Clone, Debug, Eq, PartialEq)] @@ -13,9 +13,9 @@ pub struct BlockchainTransaction { pub wei_amount: u128, } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq)] pub struct RetrievedBlockchainTransactions { - pub new_start_block: u64, + pub new_start_block: BlockNumber, pub transactions: Vec, } diff --git a/node/src/database/config_dumper.rs b/node/src/database/config_dumper.rs index 78f23ade7..17e24899e 100644 --- a/node/src/database/config_dumper.rs +++ b/node/src/database/config_dumper.rs @@ -353,11 +353,7 @@ mod tests { ); assert_value("neighborhoodMode", "zero-hop", &map); assert_value("schemaVersion", &CURRENT_SCHEMA_VERSION.to_string(), &map); - assert_value( - "startBlock", - &Chain::PolyMainnet.rec().contract_creation_block.to_string(), - &map, - ); + assert_null("startBlock", &map); assert_value( "exampleEncrypted", &dao.get("example_encrypted").unwrap().value_opt.unwrap(), @@ -503,11 +499,7 @@ mod tests { assert_value("pastNeighbors", "masq://polygon-mainnet:QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVowMTIzNDU@1.2.3.4:1234,masq://polygon-mainnet:QkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY@2.3.4.5:2345", &map); assert_value("neighborhoodMode", "consume-only", &map); assert_value("schemaVersion", &CURRENT_SCHEMA_VERSION.to_string(), &map); - assert_value( - "startBlock", - &Chain::PolyMainnet.rec().contract_creation_block.to_string(), - &map, - ); + assert_null("startBlock", &map); let expected_ee_entry = dao.get("example_encrypted").unwrap().value_opt.unwrap(); let expected_ee_decrypted = Bip39::decrypt_bytes(&expected_ee_entry, "password").unwrap(); let expected_ee_string = encode_bytes(Some(expected_ee_decrypted)).unwrap().unwrap(); @@ -620,11 +612,7 @@ mod tests { ); assert_value("neighborhoodMode", "standard", &map); assert_value("schemaVersion", &CURRENT_SCHEMA_VERSION.to_string(), &map); - assert_value( - "startBlock", - &Chain::PolyMainnet.rec().contract_creation_block.to_string(), - &map, - ); + assert_null("startBlock", &map); assert_value( "exampleEncrypted", &dao.get("example_encrypted").unwrap().value_opt.unwrap(), @@ -679,6 +667,18 @@ mod tests { assert_eq!(actual_value, expected_value); } + fn assert_null(key: &str, map: &Map) { + assert!(map.contains_key(key)); + let value = map + .get(key) + .unwrap_or_else(|| panic!("record for {} is missing", key)); + assert!( + value.is_null(), + "Expecting {} to be null, but it wasn't", + value + ) + } + fn assert_encrypted_value( key: &str, expected_value: &str, diff --git a/node/src/database/db_initializer.rs b/node/src/database/db_initializer.rs index 8bfb9c1eb..bcb9a3a0a 100644 --- a/node/src/database/db_initializer.rs +++ b/node/src/database/db_initializer.rs @@ -205,13 +205,7 @@ impl DbInitializerReal { Self::set_config_value( conn, "start_block", - Some( - &external_params - .chain - .rec() - .contract_creation_block - .to_string(), - ), + None, false, &format!( "{} start block", @@ -967,15 +961,7 @@ mod tests { Some(&CURRENT_SCHEMA_VERSION.to_string()), false, ); - verify( - &mut config_vec, - "start_block", - Some(&format!( - "{}", - &TEST_DEFAULT_CHAIN.rec().contract_creation_block.to_string() - )), - false, - ); + verify(&mut config_vec, "start_block", None, false); assert_eq!(config_vec, vec![]); } diff --git a/node/src/db_config/config_dao.rs b/node/src/db_config/config_dao.rs index 759440c42..36798dd05 100644 --- a/node/src/db_config/config_dao.rs +++ b/node/src/db_config/config_dao.rs @@ -180,7 +180,7 @@ mod tests { use crate::database::db_initializer::{DbInitializer, DbInitializerReal}; use crate::database::test_utils::ConnectionWrapperMock; use crate::test_utils::assert_contains; - use masq_lib::constants::{CURRENT_SCHEMA_VERSION, ETH_ROPSTEN_CONTRACT_CREATION_BLOCK}; + use masq_lib::constants::CURRENT_SCHEMA_VERSION; use masq_lib::test_utils::utils::ensure_node_home_directory_exists; use rusqlite::Connection; use std::path::Path; @@ -201,14 +201,7 @@ mod tests { false, ), ); - assert_contains( - &result, - &ConfigDaoRecord::new( - "start_block", - Some(Ð_ROPSTEN_CONTRACT_CREATION_BLOCK.to_string()), - false, - ), - ); + assert_contains(&result, &ConfigDaoRecord::new("start_block", None, false)); assert_contains( &result, &ConfigDaoRecord::new("consuming_wallet_private_key", None, true), diff --git a/node/src/db_config/persistent_configuration.rs b/node/src/db_config/persistent_configuration.rs index da3fa1583..532048a34 100644 --- a/node/src/db_config/persistent_configuration.rs +++ b/node/src/db_config/persistent_configuration.rs @@ -113,7 +113,7 @@ pub trait PersistentConfiguration { fn mapping_protocol(&self) -> Result, PersistentConfigError>; fn set_mapping_protocol( &mut self, - value: Option, + value_opt: Option, ) -> Result<(), PersistentConfigError>; fn min_hops(&self) -> Result; fn set_min_hops(&mut self, value: Hops) -> Result<(), PersistentConfigError>; @@ -131,13 +131,13 @@ pub trait PersistentConfiguration { node_descriptors_opt: Option>, db_password: &str, ) -> Result<(), PersistentConfigError>; - fn start_block(&self) -> Result; - fn set_start_block(&mut self, value: u64) -> Result<(), PersistentConfigError>; + fn start_block(&self) -> Result, PersistentConfigError>; + fn set_start_block(&mut self, value_opt: Option) -> Result<(), PersistentConfigError>; fn max_block_count(&self) -> Result, PersistentConfigError>; - fn set_max_block_count(&mut self, value: Option) -> Result<(), PersistentConfigError>; + fn set_max_block_count(&mut self, value_opt: Option) -> Result<(), PersistentConfigError>; fn set_start_block_from_txn( &mut self, - value: u64, + value_opt: Option, transaction: &mut TransactionSafeWrapper, ) -> Result<(), PersistentConfigError>; fn set_wallet_info( @@ -335,9 +335,9 @@ impl PersistentConfiguration for PersistentConfigurationReal { fn set_mapping_protocol( &mut self, - value: Option, + value_opt: Option, ) -> Result<(), PersistentConfigError> { - Ok(self.dao.set("mapping_protocol", value.map(to_string))?) + Ok(self.dao.set("mapping_protocol", value_opt.map(to_string))?) } fn min_hops(&self) -> Result { @@ -406,28 +406,28 @@ impl PersistentConfiguration for PersistentConfigurationReal { )?) } - fn start_block(&self) -> Result { - self.simple_get_method(decode_u64, "start_block") + fn start_block(&self) -> Result, PersistentConfigError> { + Ok(decode_u64(self.get("start_block")?)?) } - fn set_start_block(&mut self, value: u64) -> Result<(), PersistentConfigError> { - self.simple_set_method("start_block", value) + fn set_start_block(&mut self, value_opt: Option) -> Result<(), PersistentConfigError> { + Ok(self.dao.set("start_block", encode_u64(value_opt)?)?) } fn max_block_count(&self) -> Result, PersistentConfigError> { Ok(decode_u64(self.get("max_block_count")?)?) } - fn set_max_block_count(&mut self, value: Option) -> Result<(), PersistentConfigError> { - Ok(self.dao.set("max_block_count", encode_u64(value)?)?) + fn set_max_block_count(&mut self, value_opt: Option) -> Result<(), PersistentConfigError> { + Ok(self.dao.set("max_block_count", encode_u64(value_opt)?)?) } fn set_start_block_from_txn( &mut self, - value: u64, + value_opt: Option, transaction: &mut TransactionSafeWrapper, ) -> Result<(), PersistentConfigError> { - self.simple_set_method_from_provided_txn("start_block", value, transaction) + self.simple_set_method_from_provided_txn("start_block", value_opt, transaction) } fn set_wallet_info( @@ -568,23 +568,14 @@ impl PersistentConfigurationReal { fn simple_set_method_from_provided_txn( &mut self, parameter_name: &str, - value: T, + value_opt: Option, txn: &mut TransactionSafeWrapper, ) -> Result<(), PersistentConfigError> { - Ok(self - .dao - .set_by_guest_transaction(txn, parameter_name, Some(value.to_string()))?) - } - - fn simple_get_method( - &self, - decoder: fn(Option) -> Result, TypedConfigLayerError>, - parameter: &str, - ) -> Result { - match decoder(self.get(parameter)?)? { - None => Self::missing_value_panic(parameter), - Some(value) => Ok(value), - } + Ok(self.dao.set_by_guest_transaction( + txn, + parameter_name, + value_opt.map(|v| v.to_string()), + )?) } fn combined_params_get_method<'a, T, C>( @@ -1503,12 +1494,11 @@ mod tests { let start_block = subject.start_block().unwrap(); - assert_eq!(start_block, 6); + assert_eq!(start_block, Some(6)); } #[test] - #[should_panic(expected = "ever-supplied value missing: start_block; database is corrupt!")] - fn start_block_does_not_tolerate_optional_output() { + fn start_block_can_be_none() { let config_dao = Box::new(ConfigDaoMock::new().get_result(Ok(ConfigDaoRecord::new( "start_block", None, @@ -1516,11 +1506,13 @@ mod tests { )))); let subject = PersistentConfigurationReal::new(config_dao); - let _ = subject.start_block(); + let start_block = subject.start_block(); + + assert_eq!(start_block, Ok(None)); } #[test] - fn set_start_block_success() { + fn set_start_block_success_with_some() { let set_params_arc = Arc::new(Mutex::new(vec![])); let config_dao = Box::new( ConfigDaoMock::new() @@ -1529,7 +1521,7 @@ mod tests { ); let mut subject = PersistentConfigurationReal::new(config_dao); - let result = subject.set_start_block(1234); + let result = subject.set_start_block(Some(1234)); assert_eq!(result, Ok(())); let set_params = set_params_arc.lock().unwrap(); @@ -1553,7 +1545,7 @@ mod tests { let mut txn = TransactionSafeWrapper::new_with_builder(txn_inner_builder); let mut subject = PersistentConfigurationReal::new(config_dao); - let result = subject.set_start_block_from_txn(1234, &mut txn); + let result = subject.set_start_block_from_txn(Some(1234), &mut txn); assert_eq!(result, Ok(())); let set_params = set_params_arc.lock().unwrap(); @@ -1563,6 +1555,23 @@ mod tests { ) } + #[test] + fn set_start_block_success_with_none() { + let set_params_arc = Arc::new(Mutex::new(vec![])); + let config_dao = Box::new( + ConfigDaoMock::new() + .set_params(&set_params_arc) + .set_result(Ok(())), + ); + let mut subject = PersistentConfigurationReal::new(config_dao); + + let result = subject.set_start_block(None); + + assert_eq!(result, Ok(())); + let set_params = set_params_arc.lock().unwrap(); + assert_eq!(*set_params, vec![("start_block".to_string(), None)]) + } + #[test] fn gas_price() { let config_dao = Box::new(ConfigDaoMock::new().get_result(Ok(ConfigDaoRecord::new( diff --git a/node/src/node_configurator/configurator.rs b/node/src/node_configurator/configurator.rs index 30f0eed57..19b0b958a 100644 --- a/node/src/node_configurator/configurator.rs +++ b/node/src/node_configurator/configurator.rs @@ -551,7 +551,8 @@ impl Configurator { persistent_config.earning_wallet_address(), "earningWalletAddressOpt", )?; - let start_block = Self::value_required(persistent_config.start_block(), "startBlock")?; + let start_block_opt = + Self::value_not_required(persistent_config.start_block(), "startBlock")?; let max_block_count_opt = match persistent_config.max_block_count() { Ok(value) => value, Err(e) => panic!( @@ -649,7 +650,7 @@ impl Configurator { exit_byte_rate, exit_service_rate, }, - start_block, + start_block_opt, scan_intervals: UiScanIntervals { pending_payable_sec, payable_sec, @@ -786,11 +787,15 @@ impl Configurator { } fn set_start_block(&mut self, string_number: String) -> Result<(), (u64, String)> { - let block_number = match string_number.parse::() { - Ok(num) => num, - Err(e) => return Err((NON_PARSABLE_VALUE, format!("start block: {:?}", e))), + let block_number_opt = if "none".eq_ignore_ascii_case(&string_number) { + None + } else { + match string_number.parse::() { + Ok(num) => Some(num), + Err(e) => return Err((NON_PARSABLE_VALUE, format!("start block: {:?}", e))), + } }; - match self.persistent_config.set_start_block(block_number) { + match self.persistent_config.set_start_block(block_number_opt) { Ok(_) => Ok(()), Err(e) => Err((CONFIGURATOR_WRITE_ERROR, format!("start block: {:?}", e))), } @@ -2118,11 +2123,54 @@ mod tests { let (_, context_id) = UiSetConfigurationResponse::fmb(response.body.clone()).unwrap(); assert_eq!(context_id, 4444); let check_start_block_params = set_start_block_params_arc.lock().unwrap(); - assert_eq!(*check_start_block_params, vec![166666]); - TestLogHandler::new().exists_log_containing(&format!( - "DEBUG: {}: A request from UI received: {:?} from context id: {}", - test_name, msg, context_id - )); + assert_eq!(*check_start_block_params, vec![Some(166666)]); + } + + #[test] + fn handle_none_cases() { + vec!["none", "None", "nOnE", "NoNe", "NONE"] + .iter() + .for_each(|value| handle_set_configuration_accepts_none_to_unset_start_block(value)); + } + + fn handle_set_configuration_accepts_none_to_unset_start_block(cfg_value: &str) { + init_test_logging(); + let test_name = format!( + "handle_set_configuration_accepts_{}_to_unset_start_block", + &cfg_value + ); + let set_start_block_params_arc = Arc::new(Mutex::new(vec![])); + let (ui_gateway, _, ui_gateway_recording_arc) = make_recorder(); + let persistent_config = PersistentConfigurationMock::new() + .set_start_block_params(&set_start_block_params_arc) + .set_start_block_result(Ok(())); + let mut subject = make_subject(Some(persistent_config)); + subject.logger = Logger::new(test_name.as_str()); + let subject_addr = subject.start(); + let peer_actors = peer_actors_builder().ui_gateway(ui_gateway).build(); + subject_addr.try_send(BindMessage { peer_actors }).unwrap(); + let msg = UiSetConfigurationRequest { + name: "start-block".to_string(), + value: cfg_value.to_string(), + }; + let context_id = 4444; + + subject_addr + .try_send(NodeFromUiMessage { + client_id: 1234, + body: msg.clone().tmb(context_id), + }) + .unwrap(); + + let system = System::new("test"); + System::current().stop(); + system.run(); + let ui_gateway_recording = ui_gateway_recording_arc.lock().unwrap(); + let response = ui_gateway_recording.get_record::(0); + let (_, context_id) = UiSetConfigurationResponse::fmb(response.body.clone()).unwrap(); + assert_eq!(context_id, 4444); + let check_start_block_params = set_start_block_params_arc.lock().unwrap(); + assert_eq!(*check_start_block_params, vec![None]); } #[test] @@ -2498,7 +2546,7 @@ mod tests { .neighborhood_mode_result(Ok(NeighborhoodModeLight::Standard)) .past_neighbors_result(Ok(Some(vec![node_descriptor.clone()]))) .earning_wallet_address_result(Ok(Some(earning_wallet_address.clone()))) - .start_block_result(Ok(3456)); + .start_block_result(Ok(Some(3456))); let persistent_config = payment_thresholds_scan_intervals_rate_pack(persistent_config); let mut subject = make_subject(Some(persistent_config)); @@ -2541,7 +2589,7 @@ mod tests { exit_byte_rate: 10, exit_service_rate: 13 }, - start_block: 3456, + start_block_opt: Some(3456), scan_intervals: UiScanIntervals { pending_payable_sec: 122, payable_sec: 125, @@ -2629,8 +2677,7 @@ mod tests { .past_neighbors_params(&past_neighbors_params_arc) .past_neighbors_result(Ok(Some(vec![node_descriptor.clone()]))) .earning_wallet_address_result(Ok(Some(earning_wallet_address.clone()))) - .start_block_result(Ok(3456)) - .start_block_result(Ok(3456)); + .start_block_result(Ok(Some(3456))); let persistent_config = payment_thresholds_scan_intervals_rate_pack(persistent_config); let mut subject = make_subject(Some(persistent_config)); @@ -2673,7 +2720,7 @@ mod tests { exit_byte_rate: 10, exit_service_rate: 13 }, - start_block: 3456, + start_block_opt: Some(3456), scan_intervals: UiScanIntervals { pending_payable_sec: 122, payable_sec: 125, @@ -2700,7 +2747,7 @@ mod tests { .chain_name_result("ropsten".to_string()) .gas_price_result(Ok(2345)) .earning_wallet_address_result(Ok(None)) - .start_block_result(Ok(3456)) + .start_block_result(Ok(Some(3456))) .max_block_count_result(Ok(None)) .neighborhood_mode_result(Ok(NeighborhoodModeLight::ZeroHop)) .mapping_protocol_result(Ok(None)) @@ -2766,7 +2813,7 @@ mod tests { exit_byte_rate: 0, exit_service_rate: 0 }, - start_block: 3456, + start_block_opt: Some(3456), scan_intervals: UiScanIntervals { pending_payable_sec: 0, payable_sec: 0, @@ -2789,7 +2836,7 @@ mod tests { .chain_name_result("ropsten".to_string()) .gas_price_result(Ok(2345)) .earning_wallet_address_result(Ok(Some("4a5e43b54c6C56Ebf7".to_string()))) - .start_block_result(Ok(3456)) + .start_block_result(Ok(Some(3456))) .max_block_count_result(Err(PersistentConfigError::DatabaseError( "Corruption".to_string(), ))); @@ -2839,7 +2886,7 @@ mod tests { .earning_wallet_address_result(Ok(Some( "0x0123456789012345678901234567890123456789".to_string(), ))) - .start_block_result(Ok(3456)) + .start_block_result(Ok(Some(3456))) .max_block_count_result(Ok(Some(100000))) .neighborhood_mode_result(Ok(NeighborhoodModeLight::ConsumeOnly)) .mapping_protocol_result(Ok(Some(AutomapProtocol::Igdp))) diff --git a/node/src/test_utils/database_utils.rs b/node/src/test_utils/database_utils.rs index 2005166c0..02ba441a4 100644 --- a/node/src/test_utils/database_utils.rs +++ b/node/src/test_utils/database_utils.rs @@ -196,7 +196,7 @@ fn contains_particular_list_of_key_words( found += 1 } }); - assert_eq!(found,1, "We found {} occurrences of the searched line in the tested sql although only a one is considered correct", found) + assert_eq!(found, 1, "We found {} occurrences of the searched line in the tested sql although only a one is considered correct", found) } fn prepare_expected_vectors_of_words_including_sorting( diff --git a/node/src/test_utils/persistent_configuration_mock.rs b/node/src/test_utils/persistent_configuration_mock.rs index 7b7ace61d..d50613392 100644 --- a/node/src/test_utils/persistent_configuration_mock.rs +++ b/node/src/test_utils/persistent_configuration_mock.rs @@ -58,14 +58,14 @@ pub struct PersistentConfigurationMock { set_past_neighbors_params: Arc>, String)>>>, set_past_neighbors_results: RefCell>>, start_block_params: Arc>>, - start_block_results: RefCell>>, - set_start_block_params: Arc>>, + start_block_results: RefCell, PersistentConfigError>>>, + set_start_block_params: Arc>>>, set_start_block_results: RefCell>>, max_block_count_params: Arc>>, max_block_count_results: RefCell, PersistentConfigError>>>, set_max_block_count_params: Arc>>>, set_max_block_count_results: RefCell>>, - set_start_block_from_txn_params: Arc>>, + set_start_block_from_txn_params: Arc, ArbitraryIdStamp)>>>, set_start_block_from_txn_results: RefCell>>, payment_thresholds_results: RefCell>>, set_payment_thresholds_params: Arc>>, @@ -230,12 +230,12 @@ impl PersistentConfiguration for PersistentConfigurationMock { self.set_past_neighbors_results.borrow_mut().remove(0) } - fn start_block(&self) -> Result { + fn start_block(&self) -> Result, PersistentConfigError> { self.start_block_params.lock().unwrap().push(()); Self::result_from(&self.start_block_results) } - fn set_start_block(&mut self, value: u64) -> Result<(), PersistentConfigError> { + fn set_start_block(&mut self, value: Option) -> Result<(), PersistentConfigError> { self.set_start_block_params.lock().unwrap().push(value); Self::result_from(&self.set_start_block_results) } @@ -252,7 +252,7 @@ impl PersistentConfiguration for PersistentConfigurationMock { fn set_start_block_from_txn( &mut self, - value: u64, + value: Option, transaction: &mut TransactionSafeWrapper, ) -> Result<(), PersistentConfigError> { self.set_start_block_from_txn_params @@ -546,12 +546,12 @@ impl PersistentConfigurationMock { self } - pub fn start_block_result(self, result: Result) -> Self { + pub fn start_block_result(self, result: Result, PersistentConfigError>) -> Self { self.start_block_results.borrow_mut().push(result); self } - pub fn set_start_block_params(mut self, params: &Arc>>) -> Self { + pub fn set_start_block_params(mut self, params: &Arc>>>) -> Self { self.set_start_block_params = params.clone(); self } @@ -586,7 +586,7 @@ impl PersistentConfigurationMock { pub fn set_start_block_from_txn_params( mut self, - params: &Arc>>, + params: &Arc, ArbitraryIdStamp)>>>, ) -> Self { self.set_start_block_from_txn_params = params.clone(); self