Electroneum
transactions_flow_test.cpp
Go to the documentation of this file.
1 // Copyright (c) 2014-2019, The Monero Project
2 //
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without modification, are
6 // permitted provided that the following conditions are met:
7 //
8 // 1. Redistributions of source code must retain the above copyright notice, this list of
9 // conditions and the following disclaimer.
10 //
11 // 2. Redistributions in binary form must reproduce the above copyright notice, this list
12 // of conditions and the following disclaimer in the documentation and/or other
13 // materials provided with the distribution.
14 //
15 // 3. Neither the name of the copyright holder nor the names of its contributors may be
16 // used to endorse or promote products derived from this software without specific
17 // prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22 // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27 // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
30 
31 #include <boost/uuid/uuid.hpp>
32 #include <boost/uuid/uuid_io.hpp>
33 #include <boost/uuid/random_generator.hpp>
34 #include <unordered_map>
35 
36 #include "include_base_utils.h"
37 using namespace epee;
38 #include "wallet/wallet2.h"
39 using namespace cryptonote;
40 
41 namespace
42 {
43  uint64_t const TEST_FEE = 5000000000; // 5 * 10^9
44  uint64_t const TEST_DUST_THRESHOLD = 5000000000; // 5 * 10^9
45 }
46 
48 {
49  std::stringstream ss;
50  ss << boost::uuids::random_generator()();
51  return ss.str();
52 }
53 
54 inline uint64_t random(const uint64_t max_value) {
55  return (uint64_t(rand()) ^
56  (uint64_t(rand())<<16) ^
57  (uint64_t(rand())<<32) ^
58  (uint64_t(rand())<<48)) % max_value;
59 }
60 
61 bool do_send_etn(tools::wallet2& w1, tools::wallet2& w2, size_t mix_in_factor, uint64_t amount_to_transfer, transaction& tx, size_t parts=1)
62 {
63  CHECK_AND_ASSERT_MES(parts > 0, false, "parts must be > 0");
64 
65  std::vector<cryptonote::tx_destination_entry> dsts;
66  dsts.reserve(parts);
67  uint64_t amount_used = 0;
68  uint64_t max_part = amount_to_transfer / parts;
69 
70  for (size_t i = 0; i < parts; ++i)
71  {
74 
75  if (i < parts - 1)
76  de.amount = random(max_part);
77  else
78  de.amount = amount_to_transfer - amount_used;
79  amount_used += de.amount;
80 
81  //std::cout << "PARTS (" << amount_to_transfer << ") " << amount_used << " " << de.amount << std::endl;
82 
83  dsts.push_back(de);
84  }
85 
86  try
87  {
88  std::vector<tools::wallet2::pending_tx> ptx;
89  ptx = w1.create_transactions_2(dsts, mix_in_factor, 0, 0, std::vector<uint8_t>(), 0, {});
90  for (auto &p: ptx)
91  w1.commit_tx(p);
92  return true;
93  }
94  catch (const std::exception&)
95  {
96  return false;
97  }
98 }
99 
100 uint64_t get_etn_in_first_transfers(const tools::wallet2::transfer_container& incoming_transfers, size_t n_transfers)
101 {
102  uint64_t summ = 0;
103  size_t count = 0;
104  BOOST_FOREACH(const tools::wallet2::transfer_details& td, incoming_transfers)
105  {
106  summ += td.m_tx.vout[td.m_internal_output_index].amount;
107  if(++count >= n_transfers)
108  return summ;
109  }
110  return summ;
111 }
112 
113 #define FIRST_N_TRANSFERS 10*10
114 
115 bool transactions_flow_test(std::string& working_folder,
116  std::string path_source_wallet,
117  std::string path_target_wallet,
118  std::string& daemon_addr_a,
119  std::string& daemon_addr_b,
120  uint64_t amount_to_transfer, size_t mix_in_factor, size_t transactions_count, size_t transactions_per_second)
121 {
122  LOG_PRINT_L0("-----------------------STARTING TRANSACTIONS FLOW TEST-----------------------");
123  tools::wallet2 w1, w2;
124  if(path_source_wallet.empty())
125  path_source_wallet = generate_random_wallet_name();
126 
127  if(path_target_wallet.empty())
128  path_target_wallet = generate_random_wallet_name();
129 
130 
131  try
132  {
133  w1.generate(working_folder + "/" + path_source_wallet, "");
134  w2.generate(working_folder + "/" + path_target_wallet, "");
135  }
136  catch (const std::exception& e)
137  {
138  LOG_ERROR("failed to generate wallet: " << e.what());
139  return false;
140  }
141 
142  w1.init(daemon_addr_a);
143 
144  uint64_t blocks_fetched = 0;
145  bool received_etn;
146  bool ok;
147  if(!w1.refresh(true, blocks_fetched, received_etn, ok))
148  {
149  LOG_ERROR( "failed to refresh source wallet from " << daemon_addr_a );
150  return false;
151  }
152 
153  w2.init(daemon_addr_b);
154 
155  MGINFO_GREEN("Using wallets: " << ENDL
156  << "Source: " << w1.get_account().get_public_address_str(MAINNET) << ENDL << "Path: " << working_folder + "/" + path_source_wallet << ENDL
157  << "Target: " << w2.get_account().get_public_address_str(MAINNET) << ENDL << "Path: " << working_folder + "/" + path_target_wallet);
158 
159  //lets do some etn
161  COMMAND_RPC_STOP_MINING::request daemon1_req = AUTO_VAL_INIT(daemon1_req);
162  COMMAND_RPC_STOP_MINING::response daemon1_rsp = AUTO_VAL_INIT(daemon1_rsp);
163  bool r = http_client.set_server(daemon_addr_a, boost::none) && net_utils::invoke_http_json("/stop_mine", daemon1_req, daemon1_rsp, http_client, std::chrono::seconds(10));
164  CHECK_AND_ASSERT_MES(r, false, "failed to stop mining");
165 
166  COMMAND_RPC_START_MINING::request daemon_req = AUTO_VAL_INIT(daemon_req);
167  COMMAND_RPC_START_MINING::response daemon_rsp = AUTO_VAL_INIT(daemon_rsp);
168  daemon_req.miner_address = w1.get_account().get_public_address_str(MAINNET);
169  daemon_req.threads_count = 9;
170  r = net_utils::invoke_http_json("/start_mining", daemon_req, daemon_rsp, http_client, std::chrono::seconds(10));
171  CHECK_AND_ASSERT_MES(r, false, "failed to start mining getrandom_outs");
172  CHECK_AND_ASSERT_MES(daemon_rsp.status == CORE_RPC_STATUS_OK, false, "failed to start mining");
173 
174  //wait for etn, until balance will have enough etn
175  w1.refresh(true, blocks_fetched, received_etn, ok);
176  while(w1.unlocked_balance(0, false) < amount_to_transfer)
177  {
179  w1.refresh(true, blocks_fetched, received_etn, ok);
180  }
181 
182  //lets make a lot of small outs to ourselves
183  //since it is not possible to start from transaction that bigger than 20Kb, we gonna make transactions
184  //with 500 outs (about 18kb), and we have to wait appropriate count blocks, mined for test wallet
185  while(true)
186  {
187  tools::wallet2::transfer_container incoming_transfers;
188  w1.get_transfers(incoming_transfers);
189  if(incoming_transfers.size() > FIRST_N_TRANSFERS && get_etn_in_first_transfers(incoming_transfers, FIRST_N_TRANSFERS) < w1.unlocked_balance(0, false) )
190  {
191  //lets go!
192  size_t count = 0;
193  BOOST_FOREACH(tools::wallet2::transfer_details& td, incoming_transfers)
194  {
196  bool r = do_send_etn(w1, w1, 0, td.m_tx.vout[td.m_internal_output_index].amount - TEST_FEE, tx_s, 50);
197  CHECK_AND_ASSERT_MES(r, false, "Failed to send starter tx " << get_transaction_hash(tx_s));
198  MGINFO_GREEN("Starter transaction sent " << get_transaction_hash(tx_s));
199  if(++count >= FIRST_N_TRANSFERS)
200  break;
201  }
202  break;
203  }else
204  {
206  w1.refresh(true, blocks_fetched, received_etn, ok);
207  }
208  }
209  //do actual transfer
210  uint64_t transfered_etn = 0;
211  uint64_t transfer_size = amount_to_transfer/transactions_count;
212  size_t i = 0;
213  struct tx_test_entry
214  {
215  transaction tx;
216  size_t m_received_count;
217  uint64_t amount_transfered;
218  };
219  crypto::key_image lst_sent_ki = AUTO_VAL_INIT(lst_sent_ki);
220  std::unordered_map<crypto::hash, tx_test_entry> txs;
221  for(i = 0; i != transactions_count; i++)
222  {
223  uint64_t amount_to_tx = (amount_to_transfer - transfered_etn) > transfer_size ? transfer_size: (amount_to_transfer - transfered_etn);
224  while(w1.unlocked_balance(0, false) < amount_to_tx + TEST_FEE)
225  {
227  LOG_PRINT_L0("not enough ETN, waiting for cashback or mining");
228  w1.refresh(true, blocks_fetched, received_etn, ok);
229  }
230 
231  transaction tx;
232  /*size_t n_attempts = 0;
233  while (!do_send_etn(w1, w2, mix_in_factor, amount_to_tx, tx)) {
234  n_attempts++;
235  std::cout << "failed to transfer ETN, refresh and try again (attempts=" << n_attempts << ")" << std::endl;
236  w1.refresh();
237  }*/
238 
239 
240  if(!do_send_etn(w1, w2, mix_in_factor, amount_to_tx, tx))
241  {
242  LOG_PRINT_L0("failed to transfer ETN, tx: " << get_transaction_hash(tx) << ", refresh and try again" );
243  w1.refresh(true, blocks_fetched, received_etn, ok);
244  if(!do_send_etn(w1, w2, mix_in_factor, amount_to_tx, tx))
245  {
246  LOG_PRINT_L0( "failed to transfer ETN, second chance. tx: " << get_transaction_hash(tx) << ", exit" );
247  LOCAL_ASSERT(false);
248  return false;
249  }
250  }
251  lst_sent_ki = boost::get<txin_to_key>(tx.vin[0]).k_image;
252 
253  transfered_etn += amount_to_tx;
254 
255  LOG_PRINT_L0("transferred " << amount_to_tx << ", i=" << i );
256  tx_test_entry& ent = txs[get_transaction_hash(tx)] = boost::value_initialized<tx_test_entry>();
257  ent.amount_transfered = amount_to_tx;
258  ent.tx = tx;
259  //if(i % transactions_per_second)
260  // misc_utils::sleep_no_w(1000);
261  }
262 
263 
264  LOG_PRINT_L0( "waiting some new blocks...");
265  misc_utils::sleep_no_w(DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN*20*1000);//wait two blocks before sync on another wallet on another daemon
266  LOG_PRINT_L0( "refreshing...");
267  bool recvd_etn = false;
268  while(w2.refresh(true, blocks_fetched, recvd_etn, ok) && ( (blocks_fetched && recvd_etn) || !blocks_fetched ) )
269  {
270  misc_utils::sleep_no_w(DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN*1000);//wait two blocks before sync on another wallet on another daemon
271  }
272 
273  uint64_t etn_2 = w2.balance(0, false);
274  if(etn_2 == transfered_etn)
275  {
276  MGINFO_GREEN("-----------------------FINISHING TRANSACTIONS FLOW TEST OK-----------------------");
277  MGINFO_GREEN("transferred " << print_etn(transfered_etn) << " via " << i << " transactions" );
278  return true;
279  }else
280  {
282  w2.get_transfers(tc);
283  BOOST_FOREACH(tools::wallet2::transfer_details& td, tc)
284  {
285  auto it = txs.find(td.m_txid);
286  CHECK_AND_ASSERT_MES(it != txs.end(), false, "transaction not found in local cache");
287  it->second.m_received_count += 1;
288  }
289 
290  BOOST_FOREACH(auto& tx_pair, txs)
291  {
292  if(tx_pair.second.m_received_count != 1)
293  {
294  MERROR("Transaction lost: " << get_transaction_hash(tx_pair.second.tx));
295  }
296 
297  }
298 
299  MERROR("-----------------------FINISHING TRANSACTIONS FLOW TEST FAILED-----------------------" );
300  MERROR("income " << print_etn(etn_2) << " via " << i << " transactions, expected ETN = " << print_etn(transfered_etn) );
301  LOCAL_ASSERT(false);
302  return false;
303  }
304 
305  return true;
306 }
307 
#define MERROR(x)
Definition: misc_log_ex.h:73
uint64_t unlocked_balance(uint32_t subaddr_index_major, bool public_blockchain, uint64_t *blocks_to_unlock=NULL) const
Definition: wallet2.cpp:6172
#define LOCAL_ASSERT(expr)
Definition: misc_log_ex.h:122
void commit_tx(pending_tx &ptx_vector)
Definition: wallet2.cpp:6887
::std::string string
Definition: gtest-port.h:1097
std::string print_etn(uint64_t amount, unsigned int decimal_point)
uint64_t balance(uint32_t subaddr_index_major, bool public_blockchain) const
Definition: wallet2.cpp:6162
#define CHECK_AND_ASSERT_MES(expr, fail_ret_val, message)
Definition: misc_log_ex.h:181
bool invoke_http_json(const boost::string_ref uri, const t_request &out_struct, t_response &result_struct, t_transport &transport, std::chrono::milliseconds timeout=std::chrono::seconds(15), const boost::string_ref method="GET")
#define LOG_PRINT_L0(x)
Definition: misc_log_ex.h:99
std::string get_public_address_str(network_type nettype) const
Definition: account.cpp:269
#define FIRST_N_TRANSFERS
#define CORE_RPC_STATUS_OK
const account_keys & get_keys() const
Definition: account.cpp:264
Holds cryptonote related classes and helpers.
Definition: ban.cpp:40
std::vector< wallet2::pending_tx > create_transactions_2(std::vector< cryptonote::tx_destination_entry > dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector< uint8_t > &extra, uint32_t subaddr_account, std::set< uint32_t > subaddr_indices)
Definition: wallet2.cpp:9725
#define MGINFO_GREEN(x)
Definition: misc_log_ex.h:82
mdb_size_t count(MDB_cursor *cur)
void rand(size_t N, uint8_t *bytes)
Definition: crypto.h:209
std::vector< transfer_details > transfer_container
Definition: wallet2.h:449
bool init(std::string daemon_address="http://localhost:8080", boost::optional< epee::net_utils::http::login > daemon_login=boost::none, boost::asio::ip::tcp::endpoint proxy={}, uint64_t upper_transaction_weight_limit=0, bool trusted_daemon=true, epee::net_utils::ssl_options_t ssl_options=epee::net_utils::ssl_support_t::e_ssl_support_autodetect, std::string blockchain_db_path="")
Definition: wallet2.cpp:1282
unsigned __int64 uint64_t
Definition: stdint.h:136
cryptonote::transaction_prefix m_tx
Definition: wallet2.h:304
uint64_t amount
std::string generate_random_wallet_name()
bool sleep_no_w(long ms)
account_public_address addr
account_public_address m_account_address
Definition: account.h:43
bool transactions_flow_test(std::string &working_folder, std::string path_source_wallet, std::string path_target_wallet, std::string &daemon_addr_a, std::string &daemon_addr_b, uint64_t amount_to_transfer, size_t mix_in_factor, size_t transactions_count, size_t transactions_per_second)
#define LOG_ERROR(x)
Definition: misc_log_ex.h:98
#define ENDL
Definition: misc_log_ex.h:149
POD_CLASS key_image
Definition: crypto.h:102
crypto::hash get_transaction_hash(const transaction &t)
uint64_t get_etn_in_first_transfers(const tools::wallet2::transfer_container &incoming_transfers, size_t n_transfers)
#define AUTO_VAL_INIT(v)
Definition: misc_language.h:53
cryptonote::account_base & get_account()
Definition: wallet2.h:734
#define DIFFICULTY_BLOCKS_ESTIMATE_TIMESPAN
bool do_send_etn(tools::wallet2 &w1, tools::wallet2 &w2, size_t mix_in_factor, uint64_t amount_to_transfer, transaction &tx, size_t parts=1)
uint64_t random(const uint64_t max_value)
void refresh(bool trusted_daemon)
Definition: wallet2.cpp:3060
void generate(const std::string &wallet_, const epee::wipeable_string &password, const epee::wipeable_string &multisig_data, bool create_address_file=false)
Generates a wallet or restores one.
Definition: wallet2.cpp:4869
void get_transfers(wallet2::transfer_container &incoming_transfers) const
Definition: wallet2.cpp:6315