-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathverify_bill_payment.rs
More file actions
319 lines (302 loc) · 12.9 KB
/
verify_bill_payment.rs
File metadata and controls
319 lines (302 loc) · 12.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// Copyright (c) 2019, MASQ (https://masq.ai) and/or its affiliates. All rights reserved.
use crate::verify_bill_payment_utils::utils::{
test_body, to_wei, AssertionsValues, Debt, DebtsSpecs, FinalServiceFeeBalancesByServingNodes,
NodeProfile, TestInputBuilder, UiPorts, WholesomeConfig,
};
use itertools::Itertools;
use masq_lib::blockchains::chains::Chain;
use masq_lib::messages::FromMessageBody;
use masq_lib::messages::ToMessageBody;
use masq_lib::messages::{ScanType, UiScanRequest, UiScanResponse};
use masq_lib::percentage::PurePercentage;
use masq_lib::utils::find_free_port;
use multinode_integration_tests_lib::masq_node::MASQNode;
use multinode_integration_tests_lib::masq_node_cluster::MASQNodeCluster;
use multinode_integration_tests_lib::masq_real_node::{MASQRealNode, NodeStartupConfigBuilder};
use node_lib::sub_lib::accountant::PaymentThresholds;
use node_lib::sub_lib::blockchain_interface_web3::{compute_gas_limit, web3_gas_limit_const_part};
use std::convert::TryFrom;
use std::time::Duration;
use std::u128;
mod verify_bill_payment_utils;
#[test]
fn payments_processed_fully_as_balances_were_sufficient() {
// Note: besides the main objectives of this test, it relies on (and so it proves) the premise
// that each Node, after it achieves an effective connectivity as making a route is enabled,
// activates the accountancy module whereas the first cycle of scanners is unleashed. That's
// an excuse hopefully good enough not to take out the passage in this test with the intense
// startup of a bunch of real Nodes, with the only purpose of fulfilling the conditions required
// for going through that above depicted sequence of events. That said, this test could've been
// written simpler with an emulated UI and its `scans` command, lowering the CPU burden.
// (You may be pleased to know that such an approach is implemented for another test in this
// file.)
let payment_thresholds = PaymentThresholds {
threshold_interval_sec: 2_500_000,
debt_threshold_gwei: 1_000_000_000,
payment_grace_period_sec: 85_000,
maturity_threshold_sec: 85_000,
permanent_debt_allowed_gwei: 10_000_000,
unban_below_gwei: 10_000_000,
};
let debt_threshold_wei = to_wei(payment_thresholds.debt_threshold_gwei);
let owed_to_serving_node_1_minor = debt_threshold_wei + 123_456;
let owed_to_serving_node_2_minor = debt_threshold_wei + 456_789;
let owed_to_serving_node_3_minor = debt_threshold_wei + 789_012;
let consuming_node_initial_service_fee_balance_minor = debt_threshold_wei * 4;
let test_input = TestInputBuilder::default()
.consuming_node_initial_service_fee_balance_minor(
consuming_node_initial_service_fee_balance_minor,
)
.debts_config(set_old_debts(
[
owed_to_serving_node_1_minor,
owed_to_serving_node_2_minor,
owed_to_serving_node_3_minor,
],
&payment_thresholds,
))
.payment_thresholds_all_nodes(payment_thresholds)
.build();
let debts_total =
owed_to_serving_node_1_minor + owed_to_serving_node_2_minor + owed_to_serving_node_3_minor;
let final_consuming_node_service_fee_balance_minor =
consuming_node_initial_service_fee_balance_minor - debts_total;
let assertions_values = AssertionsValues {
final_consuming_node_transaction_fee_balance_minor: to_wei(999_842_470),
final_consuming_node_service_fee_balance_minor,
final_service_fee_balances_by_serving_nodes: FinalServiceFeeBalancesByServingNodes::new(
owed_to_serving_node_1_minor,
owed_to_serving_node_2_minor,
owed_to_serving_node_3_minor,
),
};
test_body(
test_input,
assertions_values,
stimulate_consuming_node_to_pay_for_test_with_sufficient_funds,
activating_serving_nodes_for_test_with_sufficient_funds,
);
}
fn stimulate_consuming_node_to_pay_for_test_with_sufficient_funds(
cluster: &mut MASQNodeCluster,
real_consuming_node: &MASQRealNode,
_wholesome_config: &WholesomeConfig,
) {
// 1 + 4 Nodes should be enough to compose a route, right?
for _ in 0..4 {
cluster.start_real_node(
NodeStartupConfigBuilder::standard()
.chain(Chain::Dev)
.neighbor(real_consuming_node.node_reference())
.build(),
);
}
}
fn activating_serving_nodes_for_test_with_sufficient_funds(
cluster: &mut MASQNodeCluster,
wholesome_values: &WholesomeConfig,
) -> [MASQRealNode; 3] {
let (node_references, serving_nodes): (Vec<_>, Vec<_>) = wholesome_values
.serving_nodes
.iter()
.map(|attributes| {
let node_id = &attributes.common.prepared_node;
cluster.start_named_real_node(
&node_id.node_docker_name,
node_id.index,
attributes
.common
.startup_config_opt
.borrow_mut()
.take()
.unwrap(),
)
})
.map(|node| (node.node_reference(), node))
.unzip();
let auxiliary_node_config = node_references
.into_iter()
.fold(
NodeStartupConfigBuilder::standard().chain(Chain::Dev),
|builder, serving_node_reference| builder.neighbor(serving_node_reference),
)
.build();
// Should be enough additional Nodes to provide the full connectivity
for _ in 0..3 {
let _ = cluster.start_real_node(auxiliary_node_config.clone());
}
serving_nodes.try_into().unwrap()
}
fn set_old_debts(
owed_money_to_serving_nodes: [u128; 3],
payment_thresholds: &PaymentThresholds,
) -> DebtsSpecs {
let quite_long_ago =
payment_thresholds.maturity_threshold_sec + payment_thresholds.threshold_interval_sec + 1;
let debts = owed_money_to_serving_nodes
.into_iter()
.map(|balance_minor| Debt::new(balance_minor, quite_long_ago))
.collect_vec();
let debt_array = debts.try_into().unwrap();
DebtsSpecs::new(debt_array)
}
#[test]
fn payments_were_adjusted_due_to_insufficient_balances() {
let payment_thresholds = PaymentThresholds {
threshold_interval_sec: 2_500_000,
debt_threshold_gwei: 100_000_000,
payment_grace_period_sec: 85_000,
maturity_threshold_sec: 85_000,
permanent_debt_allowed_gwei: 10_000_000,
unban_below_gwei: 1_000_000,
};
// Assuming all Nodes rely on the same set of payment thresholds
let owed_to_serv_node_1_minor = to_wei(payment_thresholds.debt_threshold_gwei + 5_000_000);
let owed_to_serv_node_2_minor = to_wei(payment_thresholds.debt_threshold_gwei + 20_000_000);
// Account of Node 3 will be a victim of tx fee insufficiency and will drop out, as its debt
// is the heaviest, implying the smallest weight evaluated and the smallest priority compared to
// the two others.
let owed_to_serv_node_3_minor = to_wei(payment_thresholds.debt_threshold_gwei + 60_000_000);
let enough_balance_for_serving_node_1_and_2 =
owed_to_serv_node_1_minor + owed_to_serv_node_2_minor;
let missing_portion_to_the_full_amount = to_wei(2_345_678);
let consuming_node_initial_service_fee_balance_minor =
enough_balance_for_serving_node_1_and_2 - missing_portion_to_the_full_amount;
let gas_price_major = 60;
let tx_fee_needed_to_pay_for_one_payment_major = {
// We'll need littler funds, but we can stand mild inaccuracy from assuming the use of
// all nonzero bytes in the data in both txs, which represents maximized costs
let txn_data_with_maximized_costs = [0xff; 68];
let gas_limit_dev_chain = {
let const_part = web3_gas_limit_const_part(Chain::Dev);
u64::try_from(compute_gas_limit(
const_part,
txn_data_with_maximized_costs.as_slice(),
))
.unwrap()
};
let transaction_fee_margin = PurePercentage::try_from(15).unwrap();
transaction_fee_margin.increase_by_percent_for(gas_limit_dev_chain * gas_price_major)
};
let affordable_payments_count_by_tx_fee = 2;
let tx_fee_needed_to_pay_for_one_payment_minor: u128 =
to_wei(tx_fee_needed_to_pay_for_one_payment_major);
let consuming_node_transaction_fee_balance_minor =
affordable_payments_count_by_tx_fee * tx_fee_needed_to_pay_for_one_payment_minor;
let test_input = TestInputBuilder::default()
.ui_ports(UiPorts::new(
find_free_port(),
find_free_port(),
find_free_port(),
find_free_port(),
))
// Should be enough only for two payments, the least significant one will fall out
.consuming_node_initial_tx_fee_balance_minor(consuming_node_transaction_fee_balance_minor)
.consuming_node_initial_service_fee_balance_minor(
consuming_node_initial_service_fee_balance_minor,
)
.debts_config(DebtsSpecs::new([
// This account will be the most significant and will deserve the full balance
Debt::new(
owed_to_serv_node_1_minor,
payment_thresholds.maturity_threshold_sec + 1000,
),
// This balance is of a middle size it will be reduced as there won't be enough
// after the first one is filled up.
Debt::new(
owed_to_serv_node_2_minor,
payment_thresholds.maturity_threshold_sec + 100_000,
),
// This account will be the least significant, therefore eliminated due to tx fee
Debt::new(
owed_to_serv_node_3_minor,
payment_thresholds.maturity_threshold_sec + 30_000,
),
]))
.payment_thresholds_all_nodes(payment_thresholds)
.consuming_node_gas_price_major(gas_price_major)
.build();
let assertions_values = AssertionsValues {
// How much is left after the smart contract was successfully executed, those three payments
final_consuming_node_transaction_fee_balance_minor: to_wei(2_828_352),
// Zero reached, because the algorithm is designed to exhaust the wallet completely
final_consuming_node_service_fee_balance_minor: 0,
// This account was granted with the full size as its lowest balance from the set makes
// it weight the most
final_service_fee_balances_by_serving_nodes: FinalServiceFeeBalancesByServingNodes::new(
owed_to_serv_node_1_minor,
owed_to_serv_node_2_minor - to_wei(2_345_678),
// This account dropped out from the payment, so received no money
0,
),
};
test_body(
test_input,
assertions_values,
stimulate_consuming_node_to_pay_for_test_with_insufficient_funds,
activating_serving_nodes_for_test_with_insufficient_funds,
);
}
fn stimulate_consuming_node_to_pay_for_test_with_insufficient_funds(
_cluster: &mut MASQNodeCluster,
real_consuming_node: &MASQRealNode,
wholesome_config: &WholesomeConfig,
) {
process_scan_request_to_node(
&real_consuming_node,
wholesome_config
.consuming_node
.node_profile
.ui_port()
.expect("UI port missing"),
ScanType::Payables,
1111,
)
}
fn activating_serving_nodes_for_test_with_insufficient_funds(
cluster: &mut MASQNodeCluster,
wholesome_config: &WholesomeConfig,
) -> [MASQRealNode; 3] {
let real_nodes: Vec<_> = wholesome_config
.serving_nodes
.iter()
.enumerate()
.map(|(idx, serving_node_attributes)| {
let node_config = serving_node_attributes
.common
.startup_config_opt
.borrow_mut()
.take()
.unwrap();
let common = &serving_node_attributes.common;
let serving_node = cluster.start_named_real_node(
&common.prepared_node.node_docker_name,
common.prepared_node.index,
node_config,
);
let ui_port = serving_node_attributes
.node_profile
.ui_port()
.expect("ui port missing");
process_scan_request_to_node(
&serving_node,
ui_port,
ScanType::Receivables,
(idx * 111) as u64,
);
serving_node
})
.collect();
real_nodes.try_into().unwrap()
}
fn process_scan_request_to_node(
real_node: &MASQRealNode,
ui_port: u16,
scan_type: ScanType,
context_id: u64,
) {
let ui_client = real_node.make_ui(ui_port);
ui_client.send_request(UiScanRequest { scan_type }.tmb(context_id));
let response = ui_client.wait_for_response(context_id, Duration::from_secs(10));
UiScanResponse::fmb(response).expect("Scan request went wrong");
}