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