Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2016-2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU Affero Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file auditor/taler-helper-auditor-aggregation.c
18 : * @brief audits an exchange's aggregations.
19 : * @author Christian Grothoff
20 : */
21 : #include "taler/platform.h"
22 : #include <gnunet/gnunet_util_lib.h>
23 : #include "taler/taler_auditordb_lib.h"
24 : #include "taler/taler_exchangedb_lib.h"
25 : #include "taler/taler_bank_service.h"
26 : #include "taler/taler_signatures.h"
27 : #include "taler/taler_dbevents.h"
28 : #include "report-lib.h"
29 : #include "auditor-database/event_listen.h"
30 : #include "auditor-database/get_auditor_progress.h"
31 : #include "auditor-database/get_balance.h"
32 : #include "auditor-database/insert_amount_arithmetic_inconsistency.h"
33 : #include "auditor-database/insert_auditor_progress.h"
34 : #include "auditor-database/insert_bad_sig_losses.h"
35 : #include "auditor-database/insert_balance.h"
36 : #include "auditor-database/insert_coin_inconsistency.h"
37 : #include "auditor-database/insert_fee_time_inconsistency.h"
38 : #include "auditor-database/insert_row_inconsistency.h"
39 : #include "auditor-database/insert_wire_out_inconsistency.h"
40 : #include "auditor-database/update_auditor_progress.h"
41 : #include "auditor-database/update_balance.h"
42 : #include "exchange-database/get_coin_transactions.h"
43 : #include "exchange-database/get_known_coin.h"
44 : #include "exchange-database/get_wire_fee.h"
45 : #include "exchange-database/lookup_wire_transfer.h"
46 : #include "exchange-database/select_wire_out_above_serial_id.h"
47 :
48 : /**
49 : * Return value from main().
50 : */
51 : static int global_ret;
52 :
53 : /**
54 : * Run in test mode. Exit when idle instead of
55 : * going to sleep and waiting for more work.
56 : */
57 : static int test_mode;
58 :
59 : /**
60 : * Checkpointing our progress for aggregations.
61 : */
62 : static TALER_ARL_DEF_PP (aggregation_last_wire_out_serial_id);
63 :
64 : /**
65 : * Total aggregation fees (wire fees) earned.
66 : */
67 : static TALER_ARL_DEF_AB (aggregation_total_wire_fee_revenue);
68 :
69 : /**
70 : * Total delta between calculated and stored wire out transfers,
71 : * for positive deltas.
72 : */
73 : static TALER_ARL_DEF_AB (aggregation_total_wire_out_delta_plus);
74 :
75 : /**
76 : * Total delta between calculated and stored wire out transfers
77 : * for negative deltas.
78 : */
79 : static TALER_ARL_DEF_AB (aggregation_total_wire_out_delta_minus);
80 :
81 : /**
82 : * Profits the exchange made by bad amount calculations on coins.
83 : */
84 : static TALER_ARL_DEF_AB (aggregation_total_coin_delta_plus);
85 :
86 : /**
87 : * Losses the exchange made by bad amount calculations on coins.
88 : */
89 : static TALER_ARL_DEF_AB (aggregation_total_coin_delta_minus);
90 :
91 : /**
92 : * Profits the exchange made by bad amount calculations.
93 : */
94 : static TALER_ARL_DEF_AB (aggregation_total_arithmetic_delta_plus);
95 :
96 : /**
97 : * Losses the exchange made by bad amount calculations.
98 : */
99 : static TALER_ARL_DEF_AB (aggregation_total_arithmetic_delta_minus);
100 :
101 : /**
102 : * Total amount lost by operations for which signatures were invalid.
103 : */
104 : static TALER_ARL_DEF_AB (aggregation_total_bad_sig_loss);
105 :
106 : /**
107 : * Should we run checks that only work for exchange-internal audits?
108 : */
109 : static int internal_checks;
110 :
111 : static struct GNUNET_DB_EventHandler *eh;
112 :
113 : /**
114 : * The auditors's configuration.
115 : */
116 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
117 :
118 : /**
119 : * Report a (serious) inconsistency in the exchange's database with
120 : * respect to calculations involving amounts.
121 : *
122 : * @param operation what operation had the inconsistency
123 : * @param rowid affected row, 0 if row is missing
124 : * @param exchange amount calculated by exchange
125 : * @param auditor amount calculated by auditor
126 : * @param profitable 1 if @a exchange being larger than @a auditor is
127 : * profitable for the exchange for this operation,
128 : * -1 if @a exchange being smaller than @a auditor is
129 : * profitable for the exchange, and 0 if it is unclear
130 : * @return transaction status
131 : */
132 : static enum GNUNET_DB_QueryStatus
133 0 : report_amount_arithmetic_inconsistency (
134 : const char *operation,
135 : uint64_t rowid,
136 : const struct TALER_Amount *exchange,
137 : const struct TALER_Amount *auditor,
138 : int profitable)
139 : {
140 : struct TALER_Amount delta;
141 : struct TALER_Amount *target;
142 :
143 0 : if (0 < TALER_amount_cmp (exchange,
144 : auditor))
145 : {
146 : /* exchange > auditor */
147 0 : TALER_ARL_amount_subtract (&delta,
148 : exchange,
149 : auditor);
150 : }
151 : else
152 : {
153 : /* auditor < exchange */
154 0 : profitable = -profitable;
155 0 : TALER_ARL_amount_subtract (&delta,
156 : auditor,
157 : exchange);
158 : }
159 :
160 : {
161 0 : struct TALER_AUDITORDB_AmountArithmeticInconsistency aai = {
162 : .problem_row_id = rowid,
163 0 : .profitable = profitable,
164 : .operation = (char *) operation,
165 : .exchange_amount = *exchange,
166 : .auditor_amount = *auditor
167 : };
168 : enum GNUNET_DB_QueryStatus qs;
169 :
170 0 : qs = TALER_AUDITORDB_insert_amount_arithmetic_inconsistency (
171 : TALER_ARL_adb,
172 : &aai);
173 :
174 0 : if (qs < 0)
175 : {
176 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
177 0 : return qs;
178 : }
179 : }
180 0 : if (0 != profitable)
181 : {
182 0 : target = (1 == profitable)
183 : ? &TALER_ARL_USE_AB (aggregation_total_arithmetic_delta_plus)
184 0 : : &TALER_ARL_USE_AB (aggregation_total_arithmetic_delta_minus);
185 0 : TALER_ARL_amount_add (target,
186 : target,
187 : &delta);
188 : }
189 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
190 : }
191 :
192 :
193 : /**
194 : * Report a (serious) inconsistency in the exchange's database with
195 : * respect to calculations involving amounts of a coin.
196 : *
197 : * @param operation what operation had the inconsistency
198 : * @param coin_pub affected coin
199 : * @param exchange amount calculated by exchange
200 : * @param auditor amount calculated by auditor
201 : * @param profitable 1 if @a exchange being larger than @a auditor is
202 : * profitable for the exchange for this operation,
203 : * -1 if @a exchange being smaller than @a auditor is
204 : * profitable for the exchange, and 0 if it is unclear
205 : * @return transaction status
206 : */
207 : static enum GNUNET_DB_QueryStatus
208 0 : report_coin_arithmetic_inconsistency (
209 : const char *operation,
210 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
211 : const struct TALER_Amount *exchange,
212 : const struct TALER_Amount *auditor,
213 : int profitable)
214 : {
215 : struct TALER_Amount delta;
216 : struct TALER_Amount *target;
217 :
218 0 : if (0 < TALER_amount_cmp (exchange,
219 : auditor))
220 : {
221 : /* exchange > auditor */
222 0 : TALER_ARL_amount_subtract (&delta,
223 : exchange,
224 : auditor);
225 : }
226 : else
227 : {
228 : /* auditor < exchange */
229 0 : profitable = -profitable;
230 0 : TALER_ARL_amount_subtract (&delta,
231 : auditor,
232 : exchange);
233 : }
234 :
235 : {
236 : enum GNUNET_DB_QueryStatus qs;
237 0 : struct TALER_AUDITORDB_CoinInconsistency ci = {
238 : .operation = (char *) operation,
239 : .auditor_amount = *auditor,
240 : .exchange_amount = *exchange,
241 0 : .profitable = profitable,
242 : .coin_pub = coin_pub->eddsa_pub
243 : };
244 :
245 0 : qs = TALER_AUDITORDB_insert_coin_inconsistency (
246 : TALER_ARL_adb,
247 : &ci);
248 :
249 0 : if (qs < 0)
250 : {
251 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
252 0 : return qs;
253 : }
254 : }
255 0 : if (0 != profitable)
256 : {
257 0 : target = (1 == profitable)
258 : ? &TALER_ARL_USE_AB (aggregation_total_coin_delta_plus)
259 0 : : &TALER_ARL_USE_AB (aggregation_total_coin_delta_minus);
260 0 : TALER_ARL_amount_add (target,
261 : target,
262 : &delta);
263 : }
264 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
265 : }
266 :
267 :
268 : /**
269 : * Report a (serious) inconsistency in the exchange's database.
270 : *
271 : * @param table affected table
272 : * @param rowid affected row, 0 if row is missing
273 : * @param diagnostic message explaining the problem
274 : * @return transaction status
275 : */
276 : static enum GNUNET_DB_QueryStatus
277 0 : report_row_inconsistency (const char *table,
278 : uint64_t rowid,
279 : const char *diagnostic)
280 : {
281 : enum GNUNET_DB_QueryStatus qs;
282 0 : struct TALER_AUDITORDB_RowInconsistency ri = {
283 : .diagnostic = (char *) diagnostic,
284 : .row_table = (char *) table,
285 : .row_id = rowid
286 : };
287 :
288 0 : qs = TALER_AUDITORDB_insert_row_inconsistency (
289 : TALER_ARL_adb,
290 : &ri);
291 :
292 0 : if (qs < 0)
293 : {
294 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
295 0 : return qs;
296 : }
297 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
298 : }
299 :
300 :
301 : /* *********************** Analyze aggregations ******************** */
302 : /* This logic checks that the aggregator did the right thing
303 : paying each merchant what they were due (and on time). */
304 :
305 :
306 : /**
307 : * Information about wire fees charged by the exchange.
308 : */
309 : struct WireFeeInfo
310 : {
311 :
312 : /**
313 : * Kept in a DLL.
314 : */
315 : struct WireFeeInfo *next;
316 :
317 : /**
318 : * Kept in a DLL.
319 : */
320 : struct WireFeeInfo *prev;
321 :
322 : /**
323 : * When does the fee go into effect (inclusive).
324 : */
325 : struct GNUNET_TIME_Timestamp start_date;
326 :
327 : /**
328 : * When does the fee stop being in effect (exclusive).
329 : */
330 : struct GNUNET_TIME_Timestamp end_date;
331 :
332 : /**
333 : * How high are the wire fees.
334 : */
335 : struct TALER_WireFeeSet fees;
336 :
337 : };
338 :
339 :
340 : /**
341 : * Closure for callbacks during #analyze_merchants().
342 : */
343 : struct AggregationContext
344 : {
345 :
346 : /**
347 : * DLL of wire fees charged by the exchange.
348 : */
349 : struct WireFeeInfo *fee_head;
350 :
351 : /**
352 : * DLL of wire fees charged by the exchange.
353 : */
354 : struct WireFeeInfo *fee_tail;
355 :
356 : /**
357 : * Final result status.
358 : */
359 : enum GNUNET_DB_QueryStatus qs;
360 : };
361 :
362 :
363 : /**
364 : * Closure for #wire_transfer_information_cb.
365 : */
366 : struct WireCheckContext
367 : {
368 :
369 : /**
370 : * Corresponding merchant context.
371 : */
372 : struct AggregationContext *ac;
373 :
374 : /**
375 : * Total deposits claimed by all transactions that were aggregated
376 : * under the given @e wtid.
377 : */
378 : struct TALER_Amount total_deposits;
379 :
380 : /**
381 : * Target account details of the receiver.
382 : */
383 : struct TALER_FullPayto payto_uri;
384 :
385 : /**
386 : * Execution time of the wire transfer.
387 : */
388 : struct GNUNET_TIME_Timestamp date;
389 :
390 : /**
391 : * Database transaction status.
392 : */
393 : enum GNUNET_DB_QueryStatus qs;
394 :
395 : };
396 :
397 :
398 : /**
399 : * Check coin's transaction history for plausibility. Does NOT check
400 : * the signatures (those are checked independently), but does calculate
401 : * the amounts for the aggregation table and checks that the total
402 : * claimed coin value is within the value of the coin's denomination.
403 : *
404 : * @param coin_pub public key of the coin (for reporting)
405 : * @param h_contract_terms hash of the proposal for which we calculate the amount
406 : * @param merchant_pub public key of the merchant (who is allowed to issue refunds)
407 : * @param issue denomination information about the coin
408 : * @param tl_head head of transaction history to verify
409 : * @param[out] merchant_gain amount the coin contributes to the wire transfer to the merchant
410 : * @param[out] deposit_gain amount the coin contributes excluding refunds
411 : * @return database transaction status
412 : */
413 : static enum GNUNET_DB_QueryStatus
414 0 : check_transaction_history_for_deposit (
415 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
416 : const struct TALER_PrivateContractHashP *h_contract_terms,
417 : const struct TALER_MerchantPublicKeyP *merchant_pub,
418 : const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue,
419 : const struct TALER_EXCHANGEDB_TransactionList *tl_head,
420 : struct TALER_Amount *merchant_gain,
421 : struct TALER_Amount *deposit_gain)
422 : {
423 : struct TALER_Amount expenditures;
424 : struct TALER_Amount refunds;
425 : struct TALER_Amount spent;
426 0 : struct TALER_Amount *deposited = NULL;
427 : struct TALER_Amount merchant_loss;
428 : const struct TALER_Amount *deposit_fee;
429 : enum GNUNET_DB_QueryStatus qs;
430 :
431 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
432 : "Checking transaction history of coin %s\n",
433 : TALER_B2S (coin_pub));
434 0 : GNUNET_assert (GNUNET_OK ==
435 : TALER_amount_set_zero (TALER_ARL_currency,
436 : &expenditures));
437 0 : GNUNET_assert (GNUNET_OK ==
438 : TALER_amount_set_zero (TALER_ARL_currency,
439 : &refunds));
440 0 : GNUNET_assert (GNUNET_OK ==
441 : TALER_amount_set_zero (TALER_ARL_currency,
442 : merchant_gain));
443 0 : GNUNET_assert (GNUNET_OK ==
444 : TALER_amount_set_zero (TALER_ARL_currency,
445 : &merchant_loss));
446 : /* Go over transaction history to compute totals; note that we do not bother
447 : to reconstruct the order of the events, so instead of subtracting we
448 : compute positive (deposit, melt) and negative (refund) values separately
449 : here, and then subtract the negative from the positive at the end (after
450 : the loops). */
451 0 : deposit_fee = NULL;
452 0 : for (const struct TALER_EXCHANGEDB_TransactionList *tl = tl_head;
453 0 : NULL != tl;
454 0 : tl = tl->next)
455 : {
456 : const struct TALER_Amount *fee_claimed;
457 :
458 0 : switch (tl->type)
459 : {
460 0 : case TALER_EXCHANGEDB_TT_DEPOSIT:
461 : /* check wire and h_wire are consistent */
462 0 : if (NULL != deposited)
463 : {
464 0 : struct TALER_AUDITORDB_RowInconsistency ri = {
465 0 : .row_id = tl->serial_id,
466 : .diagnostic = (char *)
467 : "multiple deposits of the same coin into the same contract detected",
468 : .row_table = (char *) "deposits"
469 : };
470 :
471 0 : qs = TALER_AUDITORDB_insert_row_inconsistency (
472 : TALER_ARL_adb,
473 : &ri);
474 :
475 0 : if (qs < 0)
476 : {
477 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
478 0 : return qs;
479 : }
480 : }
481 0 : deposited = &tl->details.deposit->amount_with_fee; /* according to exchange*/
482 0 : fee_claimed = &tl->details.deposit->deposit_fee; /* Fee according to exchange DB */
483 0 : TALER_ARL_amount_add (&expenditures,
484 : &expenditures,
485 : deposited);
486 : /* Check if this deposit is within the remit of the aggregation
487 : we are investigating, if so, include it in the totals. */
488 0 : if ((0 == GNUNET_memcmp (merchant_pub,
489 0 : &tl->details.deposit->merchant_pub)) &&
490 0 : (0 == GNUNET_memcmp (h_contract_terms,
491 : &tl->details.deposit->h_contract_terms)))
492 : {
493 : struct TALER_Amount amount_without_fee;
494 :
495 0 : TALER_ARL_amount_subtract (&amount_without_fee,
496 : deposited,
497 : fee_claimed);
498 0 : TALER_ARL_amount_add (merchant_gain,
499 : merchant_gain,
500 : &amount_without_fee);
501 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
502 : "Detected applicable deposit of %s\n",
503 : TALER_amount2s (&amount_without_fee));
504 0 : deposit_fee = fee_claimed; /* We had a deposit, remember the fee, we may need it */
505 : }
506 : /* Check that the fees given in the transaction list and in dki match */
507 0 : if (0 !=
508 0 : TALER_amount_cmp (&issue->fees.deposit,
509 : fee_claimed))
510 : {
511 : /* Disagreement in fee structure between auditor and exchange DB! */
512 0 : qs = report_amount_arithmetic_inconsistency ("deposit fee",
513 : 0,
514 : fee_claimed,
515 : &issue->fees.deposit,
516 : 1);
517 0 : if (0 > qs)
518 0 : return qs;
519 : }
520 0 : break;
521 0 : case TALER_EXCHANGEDB_TT_MELT:
522 : {
523 : const struct TALER_Amount *amount_with_fee;
524 :
525 0 : amount_with_fee = &tl->details.melt->amount_with_fee;
526 0 : fee_claimed = &tl->details.melt->melt_fee;
527 0 : TALER_ARL_amount_add (&expenditures,
528 : &expenditures,
529 : amount_with_fee);
530 : /* Check that the fees given in the transaction list and in dki match */
531 0 : if (0 !=
532 0 : TALER_amount_cmp (&issue->fees.refresh,
533 : fee_claimed))
534 : {
535 : /* Disagreement in fee structure between exchange and auditor */
536 0 : qs = report_amount_arithmetic_inconsistency ("melt fee",
537 : 0,
538 : fee_claimed,
539 : &issue->fees.refresh,
540 : 1);
541 0 : if (0 > qs)
542 0 : return qs;
543 : }
544 0 : break;
545 : }
546 0 : case TALER_EXCHANGEDB_TT_REFUND:
547 : {
548 : const struct TALER_Amount *amount_with_fee;
549 :
550 0 : amount_with_fee = &tl->details.refund->refund_amount;
551 0 : fee_claimed = &tl->details.refund->refund_fee;
552 0 : TALER_ARL_amount_add (&refunds,
553 : &refunds,
554 : amount_with_fee);
555 0 : TALER_ARL_amount_add (&expenditures,
556 : &expenditures,
557 : fee_claimed);
558 : /* Check if this refund is within the remit of the aggregation
559 : we are investigating, if so, include it in the totals. */
560 0 : if ((0 == GNUNET_memcmp (merchant_pub,
561 0 : &tl->details.refund->merchant_pub)) &&
562 0 : (0 == GNUNET_memcmp (h_contract_terms,
563 : &tl->details.refund->h_contract_terms)))
564 : {
565 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
566 : "Detected applicable refund of %s\n",
567 : TALER_amount2s (amount_with_fee));
568 0 : TALER_ARL_amount_add (&merchant_loss,
569 : &merchant_loss,
570 : amount_with_fee);
571 : }
572 : /* Check that the fees given in the transaction list and in dki match */
573 0 : if (0 !=
574 0 : TALER_amount_cmp (&issue->fees.refund,
575 : fee_claimed))
576 : {
577 : /* Disagreement in fee structure between exchange and auditor! */
578 0 : qs = report_amount_arithmetic_inconsistency ("refund fee",
579 : 0,
580 : fee_claimed,
581 : &issue->fees.refund,
582 : 1);
583 0 : if (0 > qs)
584 0 : return qs;
585 : }
586 0 : break;
587 : }
588 0 : case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
589 : {
590 : const struct TALER_Amount *amount_with_fee;
591 :
592 0 : amount_with_fee = &tl->details.old_coin_recoup->value;
593 : /* We count recoups of refreshed coins like refunds for the dirty old
594 : coin, as they equivalently _increase_ the remaining value on the
595 : _old_ coin */
596 0 : TALER_ARL_amount_add (&refunds,
597 : &refunds,
598 : amount_with_fee);
599 0 : break;
600 : }
601 0 : case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW:
602 : {
603 : const struct TALER_Amount *amount_with_fee;
604 :
605 : /* We count recoups of the coin as expenditures, as it
606 : equivalently decreases the remaining value of the recouped coin. */
607 0 : amount_with_fee = &tl->details.recoup->value;
608 0 : TALER_ARL_amount_add (&expenditures,
609 : &expenditures,
610 : amount_with_fee);
611 0 : break;
612 : }
613 0 : case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
614 : {
615 : const struct TALER_Amount *amount_with_fee;
616 :
617 : /* We count recoups of the coin as expenditures, as it
618 : equivalently decreases the remaining value of the recouped coin. */
619 0 : amount_with_fee = &tl->details.recoup_refresh->value;
620 0 : TALER_ARL_amount_add (&expenditures,
621 : &expenditures,
622 : amount_with_fee);
623 0 : break;
624 : }
625 0 : case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
626 : {
627 : const struct TALER_Amount *amount_with_fee;
628 :
629 0 : amount_with_fee = &tl->details.purse_deposit->amount;
630 0 : if (! tl->details.purse_deposit->refunded)
631 0 : TALER_ARL_amount_add (&expenditures,
632 : &expenditures,
633 : amount_with_fee);
634 0 : break;
635 : }
636 :
637 0 : case TALER_EXCHANGEDB_TT_PURSE_REFUND:
638 : {
639 : const struct TALER_Amount *amount_with_fee;
640 :
641 0 : amount_with_fee = &tl->details.purse_refund->refund_amount;
642 0 : fee_claimed = &tl->details.purse_refund->refund_fee;
643 0 : TALER_ARL_amount_add (&refunds,
644 : &refunds,
645 : amount_with_fee);
646 0 : TALER_ARL_amount_add (&expenditures,
647 : &expenditures,
648 : fee_claimed);
649 : /* Check that the fees given in the transaction list and in dki match */
650 0 : if (0 !=
651 0 : TALER_amount_cmp (&issue->fees.refund,
652 : fee_claimed))
653 : {
654 : /* Disagreement in fee structure between exchange and auditor! */
655 0 : qs = report_amount_arithmetic_inconsistency ("refund fee",
656 : 0,
657 : fee_claimed,
658 : &issue->fees.refund,
659 : 1);
660 0 : if (0 > qs)
661 0 : return qs;
662 : }
663 0 : break;
664 : }
665 :
666 0 : case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
667 : {
668 : const struct TALER_Amount *amount_with_fee;
669 :
670 0 : amount_with_fee = &tl->details.reserve_open->coin_contribution;
671 0 : TALER_ARL_amount_add (&expenditures,
672 : &expenditures,
673 : amount_with_fee);
674 0 : break;
675 : }
676 : } /* switch (tl->type) */
677 : } /* for 'tl' */
678 :
679 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
680 : "Deposits for this aggregation (after fees) are %s\n",
681 : TALER_amount2s (merchant_gain));
682 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
683 : "Aggregation loss due to refunds is %s\n",
684 : TALER_amount2s (&merchant_loss));
685 0 : *deposit_gain = *merchant_gain;
686 0 : if ((NULL != deposited) &&
687 0 : (NULL != deposit_fee) &&
688 0 : (0 == TALER_amount_cmp (&refunds,
689 : deposited)))
690 : {
691 : /* We had a /deposit operation AND /refund operations adding up to the
692 : total deposited value including deposit fee. Thus, we should not
693 : subtract the /deposit fee from the merchant gain (as it was also
694 : refunded). */
695 0 : TALER_ARL_amount_add (merchant_gain,
696 : merchant_gain,
697 : deposit_fee);
698 : }
699 : {
700 : struct TALER_Amount final_gain;
701 :
702 0 : if (TALER_ARL_SR_INVALID_NEGATIVE ==
703 0 : TALER_ARL_amount_subtract_neg (&final_gain,
704 : merchant_gain,
705 : &merchant_loss))
706 : {
707 : /* refunds above deposits? Bad! */
708 0 : qs = report_coin_arithmetic_inconsistency ("refund (merchant)",
709 : coin_pub,
710 : merchant_gain,
711 : &merchant_loss,
712 : 1);
713 0 : if (0 > qs)
714 0 : return qs;
715 : /* For the overall aggregation, we should not count this
716 : as a NEGATIVE contribution as that is not allowed; so
717 : let's count it as zero as that's the best we can do. */
718 0 : GNUNET_assert (GNUNET_OK ==
719 : TALER_amount_set_zero (TALER_ARL_currency,
720 : merchant_gain));
721 : }
722 : else
723 : {
724 0 : *merchant_gain = final_gain;
725 : }
726 : }
727 :
728 :
729 : /* Calculate total balance change, i.e. expenditures (recoup, deposit, refresh)
730 : minus refunds (refunds, recoup-to-old) */
731 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
732 : "Subtracting refunds of %s from coin value loss\n",
733 : TALER_amount2s (&refunds));
734 0 : if (TALER_ARL_SR_INVALID_NEGATIVE ==
735 0 : TALER_ARL_amount_subtract_neg (&spent,
736 : &expenditures,
737 : &refunds))
738 : {
739 : /* refunds above expenditures? Bad! */
740 0 : qs = report_coin_arithmetic_inconsistency ("refund (balance)",
741 : coin_pub,
742 : &expenditures,
743 : &refunds,
744 : 1);
745 0 : if (0 > qs)
746 0 : return qs;
747 : }
748 : else
749 : {
750 : /* Now check that 'spent' is less or equal than the total coin value */
751 0 : if (1 == TALER_amount_cmp (&spent,
752 : &issue->value))
753 : {
754 : /* spent > value */
755 0 : qs = report_coin_arithmetic_inconsistency ("spend",
756 : coin_pub,
757 : &spent,
758 : &issue->value,
759 : -1);
760 0 : if (0 > qs)
761 0 : return qs;
762 : }
763 : }
764 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
765 : "Final merchant gain after refunds is %s\n",
766 : TALER_amount2s (deposit_gain));
767 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
768 : "Coin %s contributes %s to contract %s\n",
769 : TALER_B2S (coin_pub),
770 : TALER_amount2s (merchant_gain),
771 : GNUNET_h2s (&h_contract_terms->hash));
772 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
773 : }
774 :
775 :
776 : /**
777 : * Function called with the results of the lookup of the
778 : * transaction data associated with a wire transfer identifier.
779 : *
780 : * @param[in,out] cls a `struct WireCheckContext`
781 : * @param rowid which row in the table is the information from (for diagnostics)
782 : * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls)
783 : * @param account_pay_uri where did we transfer the funds?
784 : * @param h_payto hash over @a account_payto_uri as it is in the DB
785 : * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls)
786 : * @param h_contract_terms which proposal was this payment about
787 : * @param denom_pub denomination of @a coin_pub
788 : * @param coin_pub which public key was this payment about
789 : * @param coin_value amount contributed by this coin in total (with fee),
790 : * but excluding refunds by this coin
791 : * @param deposit_fee applicable deposit fee for this coin, actual
792 : * fees charged may differ if coin was refunded
793 : */
794 : static void
795 0 : wire_transfer_information_cb (
796 : void *cls,
797 : uint64_t rowid,
798 : const struct TALER_MerchantPublicKeyP *merchant_pub,
799 : const struct TALER_FullPayto account_pay_uri,
800 : const struct TALER_FullPaytoHashP *h_payto,
801 : struct GNUNET_TIME_Timestamp exec_time,
802 : const struct TALER_PrivateContractHashP *h_contract_terms,
803 : const struct TALER_DenominationPublicKey *denom_pub,
804 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
805 : const struct TALER_Amount *coin_value,
806 : const struct TALER_Amount *deposit_fee)
807 : {
808 0 : struct WireCheckContext *wcc = cls;
809 : const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
810 : struct TALER_Amount computed_value;
811 : struct TALER_Amount total_deposit_without_refunds;
812 : struct TALER_EXCHANGEDB_TransactionList *tl;
813 : struct TALER_CoinPublicInfo coin;
814 : enum GNUNET_DB_QueryStatus qs;
815 : struct TALER_FullPaytoHashP hpt;
816 : uint64_t etag_out;
817 :
818 0 : if (0 > wcc->qs)
819 0 : return;
820 0 : TALER_full_payto_hash (account_pay_uri,
821 : &hpt);
822 0 : if (0 !=
823 0 : GNUNET_memcmp (&hpt,
824 : h_payto))
825 : {
826 0 : qs = report_row_inconsistency ("wire_targets",
827 : rowid,
828 : "h-payto does not match payto URI");
829 0 : if (0 > qs)
830 : {
831 0 : wcc->qs = qs;
832 0 : return;
833 : }
834 : }
835 : /* Obtain coin's transaction history */
836 : /* FIXME-Optimization: could use 'start' mechanism to only fetch
837 : transactions we did not yet process, instead of going over them again and
838 : again.*/
839 :
840 : {
841 : struct TALER_Amount balance;
842 : struct TALER_DenominationHashP h_denom_pub;
843 :
844 0 : qs = TALER_EXCHANGEDB_get_coin_transactions (TALER_ARL_edb,
845 : false,
846 : coin_pub,
847 : 0,
848 : 0,
849 : &etag_out,
850 : &balance,
851 : &h_denom_pub,
852 : &tl);
853 : }
854 0 : if (0 > qs)
855 : {
856 0 : wcc->qs = qs;
857 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
858 0 : return;
859 : }
860 0 : if (NULL == tl)
861 : {
862 0 : qs = report_row_inconsistency ("aggregation",
863 : rowid,
864 : "no transaction history for coin claimed in aggregation");
865 0 : if (0 > qs)
866 0 : wcc->qs = qs;
867 0 : return;
868 : }
869 0 : qs = TALER_EXCHANGEDB_get_known_coin (TALER_ARL_edb,
870 : coin_pub,
871 : &coin);
872 0 : if (0 > qs)
873 : {
874 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
875 0 : wcc->qs = qs;
876 0 : return;
877 : }
878 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
879 : {
880 : /* this should be a foreign key violation at this point! */
881 0 : qs = report_row_inconsistency ("aggregation",
882 : rowid,
883 : "could not get coin details for coin claimed in aggregation");
884 0 : if (0 > qs)
885 0 : wcc->qs = qs;
886 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
887 0 : return;
888 : }
889 0 : qs = TALER_ARL_get_denomination_info_by_hash (&coin.denom_pub_hash,
890 : &issue);
891 0 : if (0 > qs)
892 : {
893 0 : wcc->qs = qs;
894 0 : TALER_denom_sig_free (&coin.denom_sig);
895 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
896 0 : return;
897 : }
898 0 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
899 : {
900 0 : TALER_denom_sig_free (&coin.denom_sig);
901 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
902 0 : qs = report_row_inconsistency ("aggregation",
903 : rowid,
904 : "could not find denomination key for coin claimed in aggregation");
905 0 : if (0 > qs)
906 0 : wcc->qs = qs;
907 0 : return;
908 : }
909 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
910 : "Testing coin `%s' for validity\n",
911 : TALER_B2S (&coin.coin_pub));
912 0 : if (GNUNET_OK !=
913 0 : TALER_test_coin_valid (&coin,
914 : denom_pub))
915 : {
916 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
917 : .problem_row_id = rowid,
918 : .operation = (char *) "wire",
919 : .loss = *coin_value,
920 : .operation_specific_pub = coin.coin_pub.eddsa_pub
921 : };
922 :
923 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
924 : TALER_ARL_adb,
925 : &bsl);
926 0 : if (qs < 0)
927 : {
928 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
929 0 : wcc->qs = qs;
930 0 : TALER_denom_sig_free (&coin.denom_sig);
931 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
932 0 : return;
933 : }
934 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_bad_sig_loss),
935 : &TALER_ARL_USE_AB (aggregation_total_bad_sig_loss),
936 : coin_value);
937 0 : qs = report_row_inconsistency ("deposit",
938 : rowid,
939 : "coin denomination signature invalid");
940 0 : if (0 > qs)
941 : {
942 0 : wcc->qs = qs;
943 0 : TALER_denom_sig_free (&coin.denom_sig);
944 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
945 0 : return;
946 : }
947 : }
948 0 : TALER_denom_sig_free (&coin.denom_sig);
949 0 : GNUNET_assert (NULL != issue); /* mostly to help static analysis */
950 : /* Check transaction history to see if it supports aggregate
951 : valuation */
952 0 : qs = check_transaction_history_for_deposit (
953 : coin_pub,
954 : h_contract_terms,
955 : merchant_pub,
956 : issue,
957 : tl,
958 : &computed_value,
959 : &total_deposit_without_refunds);
960 0 : if (0 > qs)
961 : {
962 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
963 0 : wcc->qs = qs;
964 0 : return;
965 : }
966 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
967 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
968 : "Coin contributes %s to aggregate (deposits after fees and refunds)\n",
969 : TALER_amount2s (&computed_value));
970 : {
971 : struct TALER_Amount coin_value_without_fee;
972 :
973 0 : if (TALER_ARL_SR_INVALID_NEGATIVE ==
974 0 : TALER_ARL_amount_subtract_neg (&coin_value_without_fee,
975 : coin_value,
976 : deposit_fee))
977 : {
978 0 : qs = report_amount_arithmetic_inconsistency (
979 : "aggregation (fee structure)",
980 : rowid,
981 : coin_value,
982 : deposit_fee,
983 : -1);
984 0 : if (0 > qs)
985 : {
986 0 : wcc->qs = qs;
987 0 : return;
988 : }
989 : }
990 0 : if (0 !=
991 0 : TALER_amount_cmp (&total_deposit_without_refunds,
992 : &coin_value_without_fee))
993 : {
994 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
995 : "Expected coin contribution of %s to aggregate\n",
996 : TALER_amount2s (&coin_value_without_fee));
997 0 : qs = report_amount_arithmetic_inconsistency (
998 : "aggregation (contribution)",
999 : rowid,
1000 : &coin_value_without_fee,
1001 : &total_deposit_without_refunds,
1002 : -1);
1003 0 : if (0 > qs)
1004 : {
1005 0 : wcc->qs = qs;
1006 0 : return;
1007 : }
1008 : }
1009 : }
1010 : /* Check other details of wire transfer match */
1011 0 : if (0 != TALER_full_payto_cmp (account_pay_uri,
1012 : wcc->payto_uri))
1013 : {
1014 0 : qs = report_row_inconsistency ("aggregation",
1015 : rowid,
1016 : "target of outgoing wire transfer do not match hash of wire from deposit");
1017 0 : if (0 > qs)
1018 : {
1019 0 : wcc->qs = qs;
1020 0 : return;
1021 : }
1022 : }
1023 0 : if (GNUNET_TIME_timestamp_cmp (exec_time,
1024 : !=,
1025 : wcc->date))
1026 : {
1027 : /* This should be impossible from database constraints */
1028 0 : GNUNET_break (0);
1029 0 : qs = report_row_inconsistency ("aggregation",
1030 : rowid,
1031 : "date given in aggregate does not match wire transfer date");
1032 0 : if (0 > qs)
1033 : {
1034 0 : wcc->qs = qs;
1035 0 : return;
1036 : }
1037 : }
1038 :
1039 : /* Add coin's contribution to total aggregate value */
1040 : {
1041 : struct TALER_Amount res;
1042 :
1043 0 : TALER_ARL_amount_add (&res,
1044 : &wcc->total_deposits,
1045 : &computed_value);
1046 0 : wcc->total_deposits = res;
1047 : }
1048 : }
1049 :
1050 :
1051 : /**
1052 : * Lookup the wire fee that the exchange charges at @a timestamp.
1053 : *
1054 : * @param ac context for caching the result
1055 : * @param method method of the wire plugin
1056 : * @param timestamp time for which we need the fee
1057 : * @return NULL on error (fee unknown)
1058 : */
1059 : static const struct TALER_Amount *
1060 0 : get_wire_fee (struct AggregationContext *ac,
1061 : const char *method,
1062 : struct GNUNET_TIME_Timestamp timestamp)
1063 : {
1064 : struct WireFeeInfo *wfi;
1065 : struct WireFeeInfo *pos;
1066 : struct TALER_MasterSignatureP master_sig;
1067 : enum GNUNET_DB_QueryStatus qs;
1068 : uint64_t rowid;
1069 :
1070 : /* Check if fee is already loaded in cache */
1071 0 : for (pos = ac->fee_head; NULL != pos; pos = pos->next)
1072 : {
1073 0 : if (GNUNET_TIME_timestamp_cmp (pos->start_date,
1074 : <=,
1075 0 : timestamp) &&
1076 0 : GNUNET_TIME_timestamp_cmp (pos->end_date,
1077 : >,
1078 : timestamp))
1079 0 : return &pos->fees.wire;
1080 0 : if (GNUNET_TIME_timestamp_cmp (pos->start_date,
1081 : >,
1082 : timestamp))
1083 0 : break;
1084 : }
1085 :
1086 : /* Lookup fee in exchange database */
1087 0 : wfi = GNUNET_new (struct WireFeeInfo);
1088 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
1089 0 : TALER_EXCHANGEDB_get_wire_fee (TALER_ARL_edb,
1090 : method,
1091 : timestamp,
1092 : &rowid,
1093 : &wfi->start_date,
1094 : &wfi->end_date,
1095 : &wfi->fees,
1096 : &master_sig))
1097 : {
1098 0 : GNUNET_break (0);
1099 0 : GNUNET_free (wfi);
1100 0 : return NULL;
1101 : }
1102 :
1103 : /* Check signature. (This is not terribly meaningful as the exchange can
1104 : easily make this one up, but it means that we have proof that the master
1105 : key was used for inconsistent wire fees if a merchant complains.) */
1106 0 : if (GNUNET_OK !=
1107 0 : TALER_exchange_offline_wire_fee_verify (
1108 : method,
1109 : wfi->start_date,
1110 : wfi->end_date,
1111 0 : &wfi->fees,
1112 : &TALER_ARL_master_pub,
1113 : &master_sig))
1114 : {
1115 0 : ac->qs = report_row_inconsistency ("wire-fee",
1116 : timestamp.abs_time.abs_value_us,
1117 : "wire fee signature invalid at given time");
1118 : /* Note: continue with the fee despite the signature
1119 : being invalid here; hopefully it is really only the
1120 : signature that is bad ... */
1121 : }
1122 :
1123 : /* Established fee, keep in sorted list */
1124 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1125 : "Wire fee is %s starting at %s\n",
1126 : TALER_amount2s (&wfi->fees.wire),
1127 : GNUNET_TIME_timestamp2s (wfi->start_date));
1128 0 : if ((NULL == pos) ||
1129 0 : (NULL == pos->prev))
1130 0 : GNUNET_CONTAINER_DLL_insert (ac->fee_head,
1131 : ac->fee_tail,
1132 : wfi);
1133 : else
1134 0 : GNUNET_CONTAINER_DLL_insert_after (ac->fee_head,
1135 : ac->fee_tail,
1136 : pos->prev,
1137 : wfi);
1138 : /* Check non-overlaping fee invariant */
1139 0 : if ((NULL != wfi->prev) &&
1140 0 : GNUNET_TIME_timestamp_cmp (wfi->prev->end_date,
1141 : >,
1142 : wfi->start_date))
1143 : {
1144 0 : struct TALER_AUDITORDB_FeeTimeInconsistency ftib = {
1145 : .problem_row_id = rowid,
1146 : .diagnostic = (char *) "start date before previous end date",
1147 : .time = wfi->start_date.abs_time,
1148 : .type = (char *) method
1149 : };
1150 :
1151 0 : qs = TALER_AUDITORDB_insert_fee_time_inconsistency (
1152 : TALER_ARL_adb,
1153 : &ftib);
1154 0 : if (qs < 0)
1155 : {
1156 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1157 0 : ac->qs = qs;
1158 0 : return NULL;
1159 : }
1160 : }
1161 0 : if ((NULL != wfi->next) &&
1162 0 : GNUNET_TIME_timestamp_cmp (wfi->next->start_date,
1163 : >=,
1164 : wfi->end_date))
1165 : {
1166 0 : struct TALER_AUDITORDB_FeeTimeInconsistency ftia = {
1167 : .problem_row_id = rowid,
1168 : .diagnostic = (char *) "end date date after next start date",
1169 : .time = wfi->end_date.abs_time,
1170 : .type = (char *) method
1171 : };
1172 :
1173 0 : qs = TALER_AUDITORDB_insert_fee_time_inconsistency (
1174 : TALER_ARL_adb,
1175 : &ftia);
1176 :
1177 0 : if (qs < 0)
1178 : {
1179 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1180 0 : ac->qs = qs;
1181 0 : return NULL;
1182 : }
1183 : }
1184 0 : return &wfi->fees.wire;
1185 : }
1186 :
1187 :
1188 : /**
1189 : * Check that a wire transfer made by the exchange is valid
1190 : * (has matching deposits).
1191 : *
1192 : * @param cls a `struct AggregationContext`
1193 : * @param rowid identifier of the respective row in the database
1194 : * @param date timestamp of the wire transfer (roughly)
1195 : * @param wtid wire transfer subject
1196 : * @param payto_uri bank account details of the receiver
1197 : * @param amount amount that was wired
1198 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
1199 : */
1200 : static enum GNUNET_GenericReturnValue
1201 0 : check_wire_out_cb (void *cls,
1202 : uint64_t rowid,
1203 : struct GNUNET_TIME_Timestamp date,
1204 : const struct TALER_WireTransferIdentifierRawP *wtid,
1205 : const struct TALER_FullPayto payto_uri,
1206 : const struct TALER_Amount *amount)
1207 : {
1208 0 : struct AggregationContext *ac = cls;
1209 : struct WireCheckContext wcc;
1210 : struct TALER_Amount final_amount;
1211 : struct TALER_Amount exchange_gain;
1212 : enum GNUNET_DB_QueryStatus qs;
1213 : char *method;
1214 :
1215 : /* should be monotonically increasing */
1216 0 : GNUNET_assert (rowid >=
1217 : TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id));
1218 0 : TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id) = rowid + 1;
1219 :
1220 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1221 : "Checking wire transfer %s over %s performed on %s\n",
1222 : TALER_B2S (wtid),
1223 : TALER_amount2s (amount),
1224 : GNUNET_TIME_timestamp2s (date));
1225 0 : if (NULL == (method = TALER_payto_get_method (payto_uri.full_payto)))
1226 : {
1227 0 : qs = report_row_inconsistency ("wire_out",
1228 : rowid,
1229 : "specified wire address lacks method");
1230 0 : if (0 > qs)
1231 0 : ac->qs = qs;
1232 0 : return GNUNET_OK;
1233 : }
1234 :
1235 0 : wcc.ac = ac;
1236 0 : wcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1237 0 : wcc.date = date;
1238 0 : GNUNET_assert (GNUNET_OK ==
1239 : TALER_amount_set_zero (amount->currency,
1240 : &wcc.total_deposits));
1241 0 : wcc.payto_uri = payto_uri;
1242 0 : qs = TALER_EXCHANGEDB_lookup_wire_transfer (TALER_ARL_edb,
1243 : wtid,
1244 : &wire_transfer_information_cb,
1245 : &wcc);
1246 0 : if (0 > qs)
1247 : {
1248 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1249 0 : ac->qs = qs;
1250 0 : GNUNET_free (method);
1251 0 : return GNUNET_SYSERR;
1252 : }
1253 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != wcc.qs)
1254 : {
1255 : /* Note: detailed information was already logged
1256 : in #wire_transfer_information_cb, so here we
1257 : only log for debugging */
1258 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1259 : "Inconsistency for wire_out %llu (WTID %s) detected\n",
1260 : (unsigned long long) rowid,
1261 : TALER_B2S (wtid));
1262 : }
1263 :
1264 :
1265 : /* Subtract aggregation fee from total (if possible) */
1266 : {
1267 : const struct TALER_Amount *wire_fee;
1268 :
1269 0 : wire_fee = get_wire_fee (ac,
1270 : method,
1271 : date);
1272 0 : if (0 > ac->qs)
1273 : {
1274 0 : GNUNET_free (method);
1275 0 : return GNUNET_SYSERR;
1276 : }
1277 0 : if (NULL == wire_fee)
1278 : {
1279 0 : qs = report_row_inconsistency ("wire-fee",
1280 : date.abs_time.abs_value_us,
1281 : "wire fee unavailable for given time");
1282 0 : if (qs < 0)
1283 : {
1284 0 : ac->qs = qs;
1285 0 : GNUNET_free (method);
1286 0 : return GNUNET_SYSERR;
1287 : }
1288 : /* If fee is unknown, we just assume the fee is zero */
1289 0 : final_amount = wcc.total_deposits;
1290 : }
1291 0 : else if (TALER_ARL_SR_INVALID_NEGATIVE ==
1292 0 : TALER_ARL_amount_subtract_neg (&final_amount,
1293 : &wcc.total_deposits,
1294 : wire_fee))
1295 : {
1296 0 : qs = report_amount_arithmetic_inconsistency (
1297 : "wire out (fee structure)",
1298 : rowid,
1299 : &wcc.total_deposits,
1300 : wire_fee,
1301 : -1);
1302 : /* If fee arithmetic fails, we just assume the fee is zero */
1303 0 : if (0 > qs)
1304 : {
1305 0 : ac->qs = qs;
1306 0 : GNUNET_free (method);
1307 0 : return GNUNET_SYSERR;
1308 : }
1309 0 : final_amount = wcc.total_deposits;
1310 : }
1311 : }
1312 0 : GNUNET_free (method);
1313 :
1314 : /* Round down to amount supported by wire method */
1315 0 : GNUNET_break (GNUNET_SYSERR !=
1316 : TALER_amount_round_down (&final_amount,
1317 : &TALER_ARL_currency_round_unit));
1318 :
1319 : /* Calculate the exchange's gain as the fees plus rounding differences! */
1320 0 : TALER_ARL_amount_subtract (&exchange_gain,
1321 : &wcc.total_deposits,
1322 : &final_amount);
1323 :
1324 : /* Sum up aggregation fees (we simply include the rounding gains) */
1325 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue),
1326 : &TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue),
1327 : &exchange_gain);
1328 :
1329 : /* Check that calculated amount matches actual amount */
1330 0 : if (0 != TALER_amount_cmp (amount,
1331 : &final_amount))
1332 : {
1333 : struct TALER_Amount delta;
1334 :
1335 0 : if (0 < TALER_amount_cmp (amount,
1336 : &final_amount))
1337 : {
1338 : /* amount > final_amount */
1339 0 : TALER_ARL_amount_subtract (&delta,
1340 : amount,
1341 : &final_amount);
1342 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (
1343 : aggregation_total_wire_out_delta_plus),
1344 : &TALER_ARL_USE_AB (
1345 : aggregation_total_wire_out_delta_plus),
1346 : &delta);
1347 : }
1348 : else
1349 : {
1350 : /* amount < final_amount */
1351 0 : TALER_ARL_amount_subtract (&delta,
1352 : &final_amount,
1353 : amount);
1354 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (
1355 : aggregation_total_wire_out_delta_minus),
1356 : &TALER_ARL_USE_AB (
1357 : aggregation_total_wire_out_delta_minus),
1358 : &delta);
1359 : }
1360 :
1361 : {
1362 0 : struct TALER_AUDITORDB_WireOutInconsistency woi = {
1363 : .destination_account = payto_uri,
1364 : .diagnostic = (char *) "aggregated amount does not match expectations",
1365 : .wire_out_row_id = rowid,
1366 : .expected = final_amount,
1367 : .claimed = *amount
1368 : };
1369 :
1370 0 : qs = TALER_AUDITORDB_insert_wire_out_inconsistency (
1371 : TALER_ARL_adb,
1372 : &woi);
1373 :
1374 0 : if (qs < 0)
1375 : {
1376 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1377 0 : ac->qs = qs;
1378 0 : return GNUNET_SYSERR;
1379 : }
1380 : }
1381 0 : return GNUNET_OK;
1382 : }
1383 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1384 : "Aggregation unit %s is OK\n",
1385 : TALER_B2S (wtid));
1386 0 : return GNUNET_OK;
1387 : }
1388 :
1389 :
1390 : /**
1391 : * Analyze the exchange aggregator's payment processing.
1392 : *
1393 : * @param cls closure
1394 : * @return transaction status code
1395 : */
1396 : static enum GNUNET_DB_QueryStatus
1397 4 : analyze_aggregations (void *cls)
1398 : {
1399 : struct AggregationContext ac;
1400 : struct WireFeeInfo *wfi;
1401 : enum GNUNET_DB_QueryStatus qs;
1402 :
1403 : (void) cls;
1404 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1405 : "Analyzing aggregations\n");
1406 4 : qs = TALER_AUDITORDB_get_auditor_progress (
1407 : TALER_ARL_adb,
1408 : TALER_ARL_GET_PP (aggregation_last_wire_out_serial_id),
1409 : NULL);
1410 4 : if (0 > qs)
1411 : {
1412 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1413 0 : return qs;
1414 : }
1415 4 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1416 : {
1417 0 : GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
1418 : "First analysis using this auditor, starting audit from scratch\n");
1419 : }
1420 : else
1421 : {
1422 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1423 : "Resuming aggregation audit at %llu\n",
1424 : (unsigned long long) TALER_ARL_USE_PP (
1425 : aggregation_last_wire_out_serial_id));
1426 : }
1427 :
1428 4 : memset (&ac,
1429 : 0,
1430 : sizeof (ac));
1431 4 : qs = TALER_AUDITORDB_get_balance (
1432 : TALER_ARL_adb,
1433 : TALER_ARL_GET_AB (aggregation_total_wire_fee_revenue),
1434 : TALER_ARL_GET_AB (aggregation_total_arithmetic_delta_plus),
1435 : TALER_ARL_GET_AB (aggregation_total_arithmetic_delta_minus),
1436 : TALER_ARL_GET_AB (aggregation_total_bad_sig_loss),
1437 : TALER_ARL_GET_AB (aggregation_total_wire_out_delta_plus),
1438 : TALER_ARL_GET_AB (aggregation_total_wire_out_delta_minus),
1439 : TALER_ARL_GET_AB (aggregation_total_coin_delta_plus),
1440 : TALER_ARL_GET_AB (aggregation_total_coin_delta_minus),
1441 : NULL);
1442 4 : if (0 > qs)
1443 : {
1444 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1445 0 : return qs;
1446 : }
1447 :
1448 4 : ac.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1449 4 : qs = TALER_EXCHANGEDB_select_wire_out_above_serial_id (
1450 : TALER_ARL_edb,
1451 : TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id),
1452 : &check_wire_out_cb,
1453 : &ac);
1454 4 : if (0 > qs)
1455 : {
1456 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1457 0 : ac.qs = qs;
1458 : }
1459 4 : while (NULL != (wfi = ac.fee_head))
1460 : {
1461 0 : GNUNET_CONTAINER_DLL_remove (ac.fee_head,
1462 : ac.fee_tail,
1463 : wfi);
1464 0 : GNUNET_free (wfi);
1465 : }
1466 4 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1467 : {
1468 : /* there were no wire out entries to be looked at, we are done */
1469 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1470 : "No wire out entries found\n");
1471 4 : return qs;
1472 : }
1473 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ac.qs)
1474 : {
1475 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == ac.qs);
1476 0 : return ac.qs;
1477 : }
1478 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1479 : "Finished aggregation audit at %llu\n",
1480 : (unsigned long long) TALER_ARL_USE_PP (
1481 : aggregation_last_wire_out_serial_id));
1482 0 : qs = TALER_AUDITORDB_insert_balance (
1483 : TALER_ARL_adb,
1484 : TALER_ARL_SET_AB (aggregation_total_wire_fee_revenue),
1485 : TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_plus),
1486 : TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_minus),
1487 : TALER_ARL_SET_AB (aggregation_total_bad_sig_loss),
1488 : TALER_ARL_SET_AB (aggregation_total_wire_out_delta_plus),
1489 : TALER_ARL_SET_AB (aggregation_total_wire_out_delta_minus),
1490 : TALER_ARL_SET_AB (aggregation_total_coin_delta_plus),
1491 : TALER_ARL_SET_AB (aggregation_total_coin_delta_minus),
1492 : NULL);
1493 0 : if (0 > qs)
1494 : {
1495 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1496 : "Failed to update auditor DB, not recording progress\n");
1497 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1498 0 : return qs;
1499 : }
1500 0 : qs = TALER_AUDITORDB_update_balance (
1501 : TALER_ARL_adb,
1502 : TALER_ARL_SET_AB (aggregation_total_wire_fee_revenue),
1503 : TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_plus),
1504 : TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_minus),
1505 : TALER_ARL_SET_AB (aggregation_total_bad_sig_loss),
1506 : TALER_ARL_SET_AB (aggregation_total_wire_out_delta_plus),
1507 : TALER_ARL_SET_AB (aggregation_total_wire_out_delta_minus),
1508 : TALER_ARL_SET_AB (aggregation_total_coin_delta_plus),
1509 : TALER_ARL_SET_AB (aggregation_total_coin_delta_minus),
1510 : NULL);
1511 0 : if (0 > qs)
1512 : {
1513 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1514 : "Failed to update auditor DB, not recording progress\n");
1515 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1516 0 : return qs;
1517 : }
1518 :
1519 0 : qs = TALER_AUDITORDB_insert_auditor_progress (
1520 : TALER_ARL_adb,
1521 : TALER_ARL_SET_PP (aggregation_last_wire_out_serial_id),
1522 : NULL);
1523 0 : if (0 > qs)
1524 : {
1525 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1526 : "Failed to update auditor DB, not recording progress\n");
1527 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1528 0 : return qs;
1529 : }
1530 0 : qs = TALER_AUDITORDB_update_auditor_progress (
1531 : TALER_ARL_adb,
1532 : TALER_ARL_SET_PP (aggregation_last_wire_out_serial_id),
1533 : NULL);
1534 0 : if (0 > qs)
1535 : {
1536 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1537 : "Failed to update auditor DB, not recording progress\n");
1538 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1539 0 : return qs;
1540 : }
1541 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1542 : "Concluded aggregation audit step at %llu\n",
1543 : (unsigned long long) TALER_ARL_USE_PP (
1544 : aggregation_last_wire_out_serial_id));
1545 :
1546 0 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1547 : }
1548 :
1549 :
1550 : /**
1551 : * Function called on events received from Postgres.
1552 : *
1553 : * @param cls closure, NULL
1554 : * @param extra additional event data provided
1555 : * @param extra_size number of bytes in @a extra
1556 : */
1557 : static void
1558 0 : db_notify (void *cls,
1559 : const void *extra,
1560 : size_t extra_size)
1561 : {
1562 : (void) cls;
1563 : (void) extra;
1564 : (void) extra_size;
1565 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1566 : "Received notification to wake aggregation helper\n");
1567 0 : if (GNUNET_OK !=
1568 0 : TALER_ARL_setup_sessions_and_run (&analyze_aggregations,
1569 : NULL))
1570 : {
1571 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1572 : "Audit failed\n");
1573 0 : GNUNET_SCHEDULER_shutdown ();
1574 0 : global_ret = EXIT_FAILURE;
1575 0 : return;
1576 : }
1577 : }
1578 :
1579 :
1580 : /**
1581 : * Function called on shutdown.
1582 : */
1583 : static void
1584 4 : do_shutdown (void *cls)
1585 : {
1586 : (void) cls;
1587 4 : if (NULL != eh)
1588 : {
1589 4 : TALER_AUDITORDB_event_listen_cancel (eh);
1590 4 : eh = NULL;
1591 : }
1592 4 : TALER_ARL_done ();
1593 4 : }
1594 :
1595 :
1596 : /**
1597 : * Main function that will be run.
1598 : *
1599 : * @param cls closure
1600 : * @param args remaining command-line arguments
1601 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1602 : * @param c configuration
1603 : */
1604 : static void
1605 4 : run (void *cls,
1606 : char *const *args,
1607 : const char *cfgfile,
1608 : const struct GNUNET_CONFIGURATION_Handle *c)
1609 : {
1610 : (void) cls;
1611 : (void) args;
1612 : (void) cfgfile;
1613 :
1614 4 : cfg = c;
1615 4 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
1616 : NULL);
1617 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1618 : "Launching aggregation auditor\n");
1619 4 : if (GNUNET_OK !=
1620 4 : TALER_ARL_init (c))
1621 : {
1622 0 : global_ret = EXIT_FAILURE;
1623 0 : return;
1624 : }
1625 :
1626 4 : if (test_mode != 1)
1627 : {
1628 4 : struct GNUNET_DB_EventHeaderP es = {
1629 4 : .size = htons (sizeof (es)),
1630 4 : .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_AGGREGATION)
1631 : };
1632 :
1633 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1634 : "Running helper indefinitely\n");
1635 4 : eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
1636 : &es,
1637 4 : GNUNET_TIME_UNIT_FOREVER_REL,
1638 : &db_notify,
1639 : NULL);
1640 : }
1641 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1642 : "Starting audit\n");
1643 4 : if (GNUNET_OK !=
1644 4 : TALER_ARL_setup_sessions_and_run (&analyze_aggregations,
1645 : NULL))
1646 : {
1647 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1648 : "Audit failed\n");
1649 0 : GNUNET_SCHEDULER_shutdown ();
1650 0 : global_ret = EXIT_FAILURE;
1651 0 : return;
1652 : }
1653 : }
1654 :
1655 :
1656 : /**
1657 : * The main function to audit the exchange's aggregation processing.
1658 : *
1659 : * @param argc number of arguments from the command line
1660 : * @param argv command line arguments
1661 : * @return 0 ok, 1 on error
1662 : */
1663 : int
1664 4 : main (int argc,
1665 : char *const *argv)
1666 : {
1667 4 : const struct GNUNET_GETOPT_CommandLineOption options[] = {
1668 4 : GNUNET_GETOPT_option_flag ('i',
1669 : "internal",
1670 : "perform checks only applicable for exchange-internal audits",
1671 : &internal_checks),
1672 4 : GNUNET_GETOPT_option_flag ('t',
1673 : "test",
1674 : "run in test mode and exit when idle",
1675 : &test_mode),
1676 4 : GNUNET_GETOPT_option_timetravel ('T',
1677 : "timetravel"),
1678 : GNUNET_GETOPT_OPTION_END
1679 : };
1680 : enum GNUNET_GenericReturnValue ret;
1681 :
1682 4 : ret = GNUNET_PROGRAM_run (
1683 : TALER_AUDITOR_project_data (),
1684 : argc,
1685 : argv,
1686 : "taler-helper-auditor-aggregation",
1687 : gettext_noop ("Audit Taler exchange aggregation activity"),
1688 : options,
1689 : &run,
1690 : NULL);
1691 4 : if (GNUNET_SYSERR == ret)
1692 0 : return EXIT_INVALIDARGUMENT;
1693 4 : if (GNUNET_NO == ret)
1694 0 : return EXIT_SUCCESS;
1695 4 : return global_ret;
1696 : }
1697 :
1698 :
1699 : /* end of taler-helper-auditor-aggregation.c */
|