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