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 : .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 : .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 : if (TALER_ARL_SR_INVALID_NEGATIVE ==
504 28 : TALER_ARL_amount_subtract_neg (&amount_without_fee,
505 : amount_with_fee,
506 : fee_claimed))
507 : {
508 : /* The exchange claims a deposit fee that exceeds the deposited
509 : amount; report it (the fee check below will also fire) and
510 : continue with a zero contribution instead of dying here. */
511 0 : qs = report_row_inconsistency ("deposits",
512 0 : tl->serial_id,
513 : "deposit fee exceeds deposited amount");
514 0 : if (0 > qs)
515 0 : return qs;
516 0 : GNUNET_assert (GNUNET_OK ==
517 : TALER_amount_set_zero (TALER_ARL_currency,
518 : &amount_without_fee));
519 : }
520 28 : TALER_ARL_amount_add (merchant_gain,
521 : merchant_gain,
522 : &amount_without_fee);
523 28 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
524 : "Detected applicable deposit of %s\n",
525 : TALER_amount2s (&amount_without_fee));
526 28 : deposit_fee = fee_claimed; /* We had a deposit, remember the fee, we may need it */
527 : }
528 : /* Check that the fees given in the transaction list and in dki match */
529 30 : if (0 !=
530 30 : TALER_amount_cmp (&issue->fees.deposit,
531 : fee_claimed))
532 : {
533 : /* Disagreement in fee structure between auditor and exchange DB! */
534 0 : qs = report_amount_arithmetic_inconsistency ("deposit fee",
535 : 0,
536 : fee_claimed,
537 : &issue->fees.deposit,
538 : 1);
539 0 : if (0 > qs)
540 0 : return qs;
541 : }
542 30 : break;
543 : }
544 2 : case TALER_EXCHANGEDB_TT_MELT:
545 : {
546 : const struct TALER_Amount *amount_with_fee;
547 :
548 2 : amount_with_fee = &tl->details.melt->amount_with_fee;
549 2 : fee_claimed = &tl->details.melt->melt_fee;
550 2 : TALER_ARL_amount_add (&expenditures,
551 : &expenditures,
552 : amount_with_fee);
553 : /* Check that the fees given in the transaction list and in dki match */
554 2 : if (0 !=
555 2 : TALER_amount_cmp (&issue->fees.refresh,
556 : fee_claimed))
557 : {
558 : /* Disagreement in fee structure between exchange and auditor */
559 0 : qs = report_amount_arithmetic_inconsistency ("melt fee",
560 : 0,
561 : fee_claimed,
562 : &issue->fees.refresh,
563 : 1);
564 0 : if (0 > qs)
565 0 : return qs;
566 : }
567 2 : break;
568 : }
569 2 : case TALER_EXCHANGEDB_TT_REFUND:
570 : {
571 : const struct TALER_Amount *amount_with_fee;
572 :
573 2 : amount_with_fee = &tl->details.refund->refund_amount;
574 2 : fee_claimed = &tl->details.refund->refund_fee;
575 2 : TALER_ARL_amount_add (&refunds,
576 : &refunds,
577 : amount_with_fee);
578 2 : TALER_ARL_amount_add (&expenditures,
579 : &expenditures,
580 : fee_claimed);
581 : /* Check if this refund is within the remit of the aggregation
582 : we are investigating, if so, include it in the totals. */
583 2 : if ((0 == GNUNET_memcmp (merchant_pub,
584 0 : &tl->details.refund->merchant_pub)) &&
585 0 : (0 == GNUNET_memcmp (h_contract_terms,
586 : &tl->details.refund->h_contract_terms)))
587 : {
588 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
589 : "Detected applicable refund of %s\n",
590 : TALER_amount2s (amount_with_fee));
591 0 : TALER_ARL_amount_add (&merchant_loss,
592 : &merchant_loss,
593 : amount_with_fee);
594 : }
595 : /* Check that the fees given in the transaction list and in dki match */
596 2 : if (0 !=
597 2 : TALER_amount_cmp (&issue->fees.refund,
598 : fee_claimed))
599 : {
600 : /* Disagreement in fee structure between exchange and auditor! */
601 0 : qs = report_amount_arithmetic_inconsistency ("refund fee",
602 : 0,
603 : fee_claimed,
604 : &issue->fees.refund,
605 : 1);
606 0 : if (0 > qs)
607 0 : return qs;
608 : }
609 2 : break;
610 : }
611 0 : case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
612 : {
613 : const struct TALER_Amount *amount_with_fee;
614 :
615 0 : amount_with_fee = &tl->details.old_coin_recoup->value;
616 : /* We count recoups of refreshed coins like refunds for the dirty old
617 : coin, as they equivalently _increase_ the remaining value on the
618 : _old_ coin */
619 0 : TALER_ARL_amount_add (&refunds,
620 : &refunds,
621 : amount_with_fee);
622 0 : break;
623 : }
624 0 : case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW:
625 : {
626 : const struct TALER_Amount *amount_with_fee;
627 :
628 : /* We count recoups of the coin as expenditures, as it
629 : equivalently decreases the remaining value of the recouped coin. */
630 0 : amount_with_fee = &tl->details.recoup->value;
631 0 : TALER_ARL_amount_add (&expenditures,
632 : &expenditures,
633 : amount_with_fee);
634 0 : break;
635 : }
636 0 : case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
637 : {
638 : const struct TALER_Amount *amount_with_fee;
639 :
640 : /* We count recoups of the coin as expenditures, as it
641 : equivalently decreases the remaining value of the recouped coin. */
642 0 : amount_with_fee = &tl->details.recoup_refresh->value;
643 0 : TALER_ARL_amount_add (&expenditures,
644 : &expenditures,
645 : amount_with_fee);
646 0 : break;
647 : }
648 0 : case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
649 : {
650 : const struct TALER_Amount *amount_with_fee;
651 :
652 0 : amount_with_fee = &tl->details.purse_deposit->amount;
653 0 : if (! tl->details.purse_deposit->refunded)
654 0 : TALER_ARL_amount_add (&expenditures,
655 : &expenditures,
656 : amount_with_fee);
657 0 : break;
658 : }
659 :
660 0 : case TALER_EXCHANGEDB_TT_PURSE_REFUND:
661 : {
662 : const struct TALER_Amount *amount_with_fee;
663 :
664 0 : amount_with_fee = &tl->details.purse_refund->refund_amount;
665 0 : fee_claimed = &tl->details.purse_refund->refund_fee;
666 0 : TALER_ARL_amount_add (&refunds,
667 : &refunds,
668 : amount_with_fee);
669 0 : TALER_ARL_amount_add (&expenditures,
670 : &expenditures,
671 : fee_claimed);
672 : /* Check that the fees given in the transaction list and in dki match */
673 0 : if (0 !=
674 0 : TALER_amount_cmp (&issue->fees.refund,
675 : fee_claimed))
676 : {
677 : /* Disagreement in fee structure between exchange and auditor! */
678 0 : qs = report_amount_arithmetic_inconsistency ("refund fee",
679 : 0,
680 : fee_claimed,
681 : &issue->fees.refund,
682 : 1);
683 0 : if (0 > qs)
684 0 : return qs;
685 : }
686 0 : break;
687 : }
688 :
689 0 : case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
690 : {
691 : const struct TALER_Amount *amount_with_fee;
692 :
693 0 : amount_with_fee = &tl->details.reserve_open->coin_contribution;
694 0 : TALER_ARL_amount_add (&expenditures,
695 : &expenditures,
696 : amount_with_fee);
697 0 : break;
698 : }
699 : } /* switch (tl->type) */
700 : } /* for 'tl' */
701 :
702 28 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
703 : "Deposits for this aggregation (after fees) are %s\n",
704 : TALER_amount2s (merchant_gain));
705 28 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
706 : "Aggregation loss due to refunds is %s\n",
707 : TALER_amount2s (&merchant_loss));
708 28 : *deposit_gain = *merchant_gain;
709 28 : if ((NULL != deposited) &&
710 28 : (NULL != deposit_fee) &&
711 28 : (0 == TALER_amount_cmp (&merchant_loss,
712 : deposited)))
713 : {
714 : /* We had a /deposit operation AND /refund operations for the same
715 : merchant and contract adding up to the total deposited value
716 : including deposit fee. Thus, we should not subtract the /deposit fee
717 : from the merchant gain (as it was also refunded). */
718 0 : TALER_ARL_amount_add (merchant_gain,
719 : merchant_gain,
720 : deposit_fee);
721 : }
722 : {
723 : struct TALER_Amount final_gain;
724 :
725 28 : if (TALER_ARL_SR_INVALID_NEGATIVE ==
726 28 : TALER_ARL_amount_subtract_neg (&final_gain,
727 : merchant_gain,
728 : &merchant_loss))
729 : {
730 : /* refunds above deposits? Bad! */
731 0 : qs = report_coin_arithmetic_inconsistency ("refund (merchant)",
732 : coin_pub,
733 : merchant_gain,
734 : &merchant_loss,
735 : 1);
736 0 : if (0 > qs)
737 0 : return qs;
738 : /* For the overall aggregation, we should not count this
739 : as a NEGATIVE contribution as that is not allowed; so
740 : let's count it as zero as that's the best we can do. */
741 0 : GNUNET_assert (GNUNET_OK ==
742 : TALER_amount_set_zero (TALER_ARL_currency,
743 : merchant_gain));
744 : }
745 : else
746 : {
747 28 : *merchant_gain = final_gain;
748 : }
749 : }
750 :
751 :
752 : /* Calculate total balance change, i.e. expenditures (recoup, deposit, refresh)
753 : minus refunds (refunds, recoup-to-old) */
754 28 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
755 : "Subtracting refunds of %s from coin value loss\n",
756 : TALER_amount2s (&refunds));
757 28 : if (TALER_ARL_SR_INVALID_NEGATIVE ==
758 28 : TALER_ARL_amount_subtract_neg (&spent,
759 : &expenditures,
760 : &refunds))
761 : {
762 : /* refunds above expenditures? Bad! */
763 0 : qs = report_coin_arithmetic_inconsistency ("refund (balance)",
764 : coin_pub,
765 : &expenditures,
766 : &refunds,
767 : 1);
768 0 : if (0 > qs)
769 0 : return qs;
770 : }
771 : else
772 : {
773 : /* Now check that 'spent' is less or equal than the total coin value */
774 28 : if (1 == TALER_amount_cmp (&spent,
775 : &issue->value))
776 : {
777 : /* spent > value */
778 0 : qs = report_coin_arithmetic_inconsistency ("spend",
779 : coin_pub,
780 : &spent,
781 : &issue->value,
782 : -1);
783 0 : if (0 > qs)
784 0 : return qs;
785 : }
786 : }
787 28 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
788 : "Final merchant gain after refunds is %s\n",
789 : TALER_amount2s (deposit_gain));
790 28 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
791 : "Coin %s contributes %s to contract %s\n",
792 : TALER_B2S (coin_pub),
793 : TALER_amount2s (merchant_gain),
794 : GNUNET_h2s (&h_contract_terms->hash));
795 28 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
796 : }
797 :
798 :
799 : /**
800 : * Function called with the results of the lookup of the
801 : * transaction data associated with a wire transfer identifier.
802 : *
803 : * @param[in,out] wcc a `struct WireCheckContext`
804 : * @param rowid which row in the table is the information from (for diagnostics)
805 : * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls)
806 : * @param account_pay_uri where did we transfer the funds?
807 : * @param h_payto hash over @a account_payto_uri as it is in the DB
808 : * @param exchange_payto_uri which of the exchange's accounts was debited,
809 : * `full_payto` NULL if not recorded; unused here
810 : * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls)
811 : * @param h_contract_terms which proposal was this payment about
812 : * @param denom_pub denomination of @a coin_pub
813 : * @param coin_pub which public key was this payment about
814 : * @param coin_value amount contributed by this coin in total (with fee),
815 : * but excluding refunds by this coin
816 : * @param deposit_fee applicable deposit fee for this coin, actual
817 : * fees charged may differ if coin was refunded
818 : */
819 : static void
820 28 : wire_transfer_information_cb (
821 : struct WireCheckContext *wcc,
822 : uint64_t rowid,
823 : const struct TALER_MerchantPublicKeyP *merchant_pub,
824 : const struct TALER_FullPayto account_pay_uri,
825 : const struct TALER_FullPaytoHashP *h_payto,
826 : const struct TALER_FullPayto exchange_payto_uri,
827 : struct GNUNET_TIME_Timestamp exec_time,
828 : const struct TALER_PrivateContractHashP *h_contract_terms,
829 : const struct TALER_DenominationPublicKey *denom_pub,
830 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
831 : const struct TALER_Amount *coin_value,
832 : const struct TALER_Amount *deposit_fee)
833 : {
834 : const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue;
835 : struct TALER_Amount computed_value;
836 : struct TALER_Amount total_deposit_without_refunds;
837 : struct TALER_EXCHANGEDB_TransactionList *tl;
838 : struct TALER_CoinPublicInfo coin;
839 : enum GNUNET_DB_QueryStatus qs;
840 : struct TALER_FullPaytoHashP hpt;
841 : uint64_t etag_out;
842 :
843 28 : if (0 > wcc->qs)
844 0 : return;
845 28 : TALER_full_payto_hash (account_pay_uri,
846 : &hpt);
847 28 : if (0 !=
848 28 : GNUNET_memcmp (&hpt,
849 : h_payto))
850 : {
851 0 : qs = report_row_inconsistency ("wire_targets",
852 : rowid,
853 : "h-payto does not match payto URI");
854 0 : if (0 > qs)
855 : {
856 0 : wcc->qs = qs;
857 0 : return;
858 : }
859 : }
860 : /* Obtain coin's transaction history */
861 : /* FIXME-Optimization: could use 'start' mechanism to only fetch
862 : transactions we did not yet process, instead of going over them again and
863 : again.*/
864 :
865 : {
866 : struct TALER_Amount balance;
867 : struct TALER_DenominationHashP h_denom_pub;
868 :
869 28 : qs = TALER_EXCHANGEDB_get_coin_transactions (TALER_ARL_edb,
870 : false,
871 : coin_pub,
872 : 0,
873 : 0,
874 : &etag_out,
875 : &balance,
876 : &h_denom_pub,
877 : &tl);
878 : }
879 28 : if (0 > qs)
880 : {
881 0 : wcc->qs = qs;
882 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
883 0 : return;
884 : }
885 28 : if (NULL == tl)
886 : {
887 0 : qs = report_row_inconsistency ("aggregation",
888 : rowid,
889 : "no transaction history for coin claimed in aggregation");
890 0 : if (0 > qs)
891 0 : wcc->qs = qs;
892 0 : return;
893 : }
894 28 : qs = TALER_EXCHANGEDB_get_known_coin (TALER_ARL_edb,
895 : coin_pub,
896 : &coin);
897 28 : if (0 > qs)
898 : {
899 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
900 0 : wcc->qs = qs;
901 0 : return;
902 : }
903 28 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
904 : {
905 : /* this should be a foreign key violation at this point! */
906 0 : qs = report_row_inconsistency ("aggregation",
907 : rowid,
908 : "could not get coin details for coin claimed in aggregation");
909 0 : if (0 > qs)
910 0 : wcc->qs = qs;
911 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
912 0 : return;
913 : }
914 28 : qs = TALER_ARL_get_denomination_info_by_hash (&coin.denom_pub_hash,
915 : &issue);
916 28 : if (0 > qs)
917 : {
918 0 : wcc->qs = qs;
919 0 : TALER_denom_sig_free (&coin.denom_sig);
920 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
921 0 : return;
922 : }
923 28 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
924 : {
925 0 : TALER_denom_sig_free (&coin.denom_sig);
926 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
927 0 : qs = report_row_inconsistency ("aggregation",
928 : rowid,
929 : "could not find denomination key for coin claimed in aggregation");
930 0 : if (0 > qs)
931 0 : wcc->qs = qs;
932 0 : return;
933 : }
934 28 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
935 : "Testing coin `%s' for validity\n",
936 : TALER_B2S (&coin.coin_pub));
937 28 : if (GNUNET_OK !=
938 28 : TALER_test_coin_valid (&coin,
939 : denom_pub))
940 : {
941 0 : struct TALER_AUDITORDB_BadSigLosses bsl = {
942 : .problem_row_id = rowid,
943 : .operation = (char *) "wire",
944 : .loss = *coin_value,
945 : .operation_specific_pub = coin.coin_pub.eddsa_pub
946 : };
947 :
948 0 : qs = TALER_AUDITORDB_insert_bad_sig_losses (
949 : TALER_ARL_adb,
950 : &bsl);
951 0 : if (qs < 0)
952 : {
953 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
954 0 : wcc->qs = qs;
955 0 : TALER_denom_sig_free (&coin.denom_sig);
956 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
957 0 : return;
958 : }
959 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_bad_sig_loss),
960 : &TALER_ARL_USE_AB (aggregation_total_bad_sig_loss),
961 : coin_value);
962 0 : qs = report_row_inconsistency ("deposit",
963 : rowid,
964 : "coin denomination signature invalid");
965 0 : if (0 > qs)
966 : {
967 0 : wcc->qs = qs;
968 0 : TALER_denom_sig_free (&coin.denom_sig);
969 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
970 0 : return;
971 : }
972 : }
973 28 : TALER_denom_sig_free (&coin.denom_sig);
974 28 : GNUNET_assert (NULL != issue); /* mostly to help static analysis */
975 : /* Check transaction history to see if it supports aggregate
976 : valuation */
977 28 : qs = check_transaction_history_for_deposit (
978 : coin_pub,
979 : h_contract_terms,
980 : merchant_pub,
981 : issue,
982 : tl,
983 : &computed_value,
984 : &total_deposit_without_refunds);
985 28 : if (0 > qs)
986 : {
987 0 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
988 0 : wcc->qs = qs;
989 0 : return;
990 : }
991 28 : TALER_EXCHANGEDB_free_coin_transaction_list (tl);
992 28 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
993 : "Coin contributes %s to aggregate (deposits after fees and refunds)\n",
994 : TALER_amount2s (&computed_value));
995 : {
996 : struct TALER_Amount coin_value_without_fee;
997 :
998 28 : if (TALER_ARL_SR_INVALID_NEGATIVE ==
999 28 : TALER_ARL_amount_subtract_neg (&coin_value_without_fee,
1000 : coin_value,
1001 : deposit_fee))
1002 : {
1003 0 : qs = report_amount_arithmetic_inconsistency (
1004 : "aggregation (fee structure)",
1005 : rowid,
1006 : coin_value,
1007 : deposit_fee,
1008 : -1);
1009 0 : if (0 > qs)
1010 : {
1011 0 : wcc->qs = qs;
1012 0 : return;
1013 : }
1014 : }
1015 28 : else if (0 !=
1016 28 : TALER_amount_cmp (&total_deposit_without_refunds,
1017 : &coin_value_without_fee))
1018 : {
1019 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1020 : "Expected coin contribution of %s to aggregate\n",
1021 : TALER_amount2s (&coin_value_without_fee));
1022 0 : qs = report_amount_arithmetic_inconsistency (
1023 : "aggregation (contribution)",
1024 : rowid,
1025 : &coin_value_without_fee,
1026 : &total_deposit_without_refunds,
1027 : -1);
1028 0 : if (0 > qs)
1029 : {
1030 0 : wcc->qs = qs;
1031 0 : return;
1032 : }
1033 : }
1034 : }
1035 : /* Check other details of wire transfer match */
1036 28 : if (0 != TALER_full_payto_cmp (account_pay_uri,
1037 : wcc->payto_uri))
1038 : {
1039 0 : qs = report_row_inconsistency ("aggregation",
1040 : rowid,
1041 : "target of outgoing wire transfer do not match hash of wire from deposit");
1042 0 : if (0 > qs)
1043 : {
1044 0 : wcc->qs = qs;
1045 0 : return;
1046 : }
1047 : }
1048 28 : if (GNUNET_TIME_timestamp_cmp (exec_time,
1049 : !=,
1050 : wcc->date))
1051 : {
1052 : /* This should be impossible from database constraints */
1053 0 : GNUNET_break (0);
1054 0 : qs = report_row_inconsistency ("aggregation",
1055 : rowid,
1056 : "date given in aggregate does not match wire transfer date");
1057 0 : if (0 > qs)
1058 : {
1059 0 : wcc->qs = qs;
1060 0 : return;
1061 : }
1062 : }
1063 :
1064 : /* Add coin's contribution to total aggregate value */
1065 : {
1066 : struct TALER_Amount res;
1067 :
1068 28 : TALER_ARL_amount_add (&res,
1069 : &wcc->total_deposits,
1070 : &computed_value);
1071 28 : wcc->total_deposits = res;
1072 : }
1073 : }
1074 :
1075 :
1076 : /**
1077 : * Lookup the wire fee that the exchange charges at @a timestamp.
1078 : *
1079 : * @param ac context for caching the result
1080 : * @param method method of the wire plugin
1081 : * @param timestamp time for which we need the fee
1082 : * @return NULL on error (fee unknown)
1083 : */
1084 : static const struct TALER_Amount *
1085 28 : get_wire_fee (struct AggregationContext *ac,
1086 : const char *method,
1087 : struct GNUNET_TIME_Timestamp timestamp)
1088 : {
1089 : struct WireFeeInfo *wfi;
1090 : struct WireFeeInfo *pos;
1091 : struct TALER_MasterSignatureP master_sig;
1092 : enum GNUNET_DB_QueryStatus qs;
1093 : uint64_t rowid;
1094 :
1095 : /* Check if fee is already loaded in cache */
1096 28 : for (pos = ac->fee_head; NULL != pos; pos = pos->next)
1097 : {
1098 26 : if (GNUNET_TIME_timestamp_cmp (pos->start_date,
1099 : <=,
1100 26 : timestamp) &&
1101 26 : GNUNET_TIME_timestamp_cmp (pos->end_date,
1102 : >,
1103 : timestamp))
1104 26 : return &pos->fees.wire;
1105 0 : if (GNUNET_TIME_timestamp_cmp (pos->start_date,
1106 : >,
1107 : timestamp))
1108 0 : break;
1109 : }
1110 :
1111 : /* Lookup fee in exchange database */
1112 2 : wfi = GNUNET_new (struct WireFeeInfo);
1113 2 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
1114 2 : TALER_EXCHANGEDB_get_wire_fee (TALER_ARL_edb,
1115 : method,
1116 : timestamp,
1117 : &rowid,
1118 : &wfi->start_date,
1119 : &wfi->end_date,
1120 : &wfi->fees,
1121 : &master_sig))
1122 : {
1123 0 : GNUNET_break (0);
1124 0 : GNUNET_free (wfi);
1125 0 : return NULL;
1126 : }
1127 :
1128 : /* Check signature. (This is not terribly meaningful as the exchange can
1129 : easily make this one up, but it means that we have proof that the master
1130 : key was used for inconsistent wire fees if a merchant complains.) */
1131 2 : if (GNUNET_OK !=
1132 2 : TALER_exchange_offline_wire_fee_verify (
1133 : method,
1134 : wfi->start_date,
1135 : wfi->end_date,
1136 2 : &wfi->fees,
1137 : &TALER_ARL_master_pub,
1138 : &master_sig))
1139 : {
1140 0 : ac->qs = report_row_inconsistency ("wire-fee",
1141 : timestamp.abs_time.abs_value_us,
1142 : "wire fee signature invalid at given time");
1143 : /* Note: continue with the fee despite the signature
1144 : being invalid here; hopefully it is really only the
1145 : signature that is bad ... */
1146 : }
1147 :
1148 : /* Established fee, keep in sorted list */
1149 2 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1150 : "Wire fee is %s starting at %s\n",
1151 : TALER_amount2s (&wfi->fees.wire),
1152 : GNUNET_TIME_timestamp2s (wfi->start_date));
1153 2 : if ((NULL == pos) ||
1154 0 : (NULL == pos->prev))
1155 2 : GNUNET_CONTAINER_DLL_insert (ac->fee_head,
1156 : ac->fee_tail,
1157 : wfi);
1158 : else
1159 0 : GNUNET_CONTAINER_DLL_insert_after (ac->fee_head,
1160 : ac->fee_tail,
1161 : pos->prev,
1162 : wfi);
1163 : /* Check non-overlaping fee invariant */
1164 2 : if ((NULL != wfi->prev) &&
1165 0 : GNUNET_TIME_timestamp_cmp (wfi->prev->end_date,
1166 : >,
1167 : wfi->start_date))
1168 : {
1169 0 : struct TALER_AUDITORDB_FeeTimeInconsistency ftib = {
1170 : .problem_row_id = rowid,
1171 : .diagnostic = (char *) "start date before previous end date",
1172 : .time = wfi->start_date.abs_time,
1173 : .type = (char *) method
1174 : };
1175 :
1176 0 : qs = TALER_AUDITORDB_insert_fee_time_inconsistency (
1177 : TALER_ARL_adb,
1178 : &ftib);
1179 0 : if (qs < 0)
1180 : {
1181 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1182 0 : ac->qs = qs;
1183 0 : return NULL;
1184 : }
1185 : }
1186 2 : if ((NULL != wfi->next) &&
1187 0 : GNUNET_TIME_timestamp_cmp (wfi->next->start_date,
1188 : <,
1189 : wfi->end_date))
1190 : {
1191 0 : struct TALER_AUDITORDB_FeeTimeInconsistency ftia = {
1192 : .problem_row_id = rowid,
1193 : .diagnostic = (char *) "end date after next start date",
1194 : .time = wfi->end_date.abs_time,
1195 : .type = (char *) method
1196 : };
1197 :
1198 0 : qs = TALER_AUDITORDB_insert_fee_time_inconsistency (
1199 : TALER_ARL_adb,
1200 : &ftia);
1201 :
1202 0 : if (qs < 0)
1203 : {
1204 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1205 0 : ac->qs = qs;
1206 0 : return NULL;
1207 : }
1208 : }
1209 2 : return &wfi->fees.wire;
1210 : }
1211 :
1212 :
1213 : /**
1214 : * Check that a wire transfer made by the exchange is valid
1215 : * (has matching deposits).
1216 : *
1217 : * @param ac a `struct AggregationContext`
1218 : * @param rowid identifier of the respective row in the database
1219 : * @param date timestamp of the wire transfer (roughly)
1220 : * @param wtid wire transfer subject
1221 : * @param payto_uri bank account details of the receiver
1222 : * @param amount amount that was wired
1223 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
1224 : */
1225 : static enum GNUNET_GenericReturnValue
1226 28 : check_wire_out_cb (struct AggregationContext *ac,
1227 : uint64_t rowid,
1228 : struct GNUNET_TIME_Timestamp date,
1229 : const struct TALER_WireTransferIdentifierRawP *wtid,
1230 : const struct TALER_FullPayto payto_uri,
1231 : const struct TALER_Amount *amount)
1232 : {
1233 : struct WireCheckContext wcc;
1234 : struct TALER_Amount final_amount;
1235 : struct TALER_Amount exchange_gain;
1236 : enum GNUNET_DB_QueryStatus qs;
1237 : char *method;
1238 :
1239 : /* should be monotonically increasing */
1240 28 : GNUNET_assert (rowid >=
1241 : TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id));
1242 28 : TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id) = rowid + 1;
1243 :
1244 28 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1245 : "Checking wire transfer %s over %s performed on %s\n",
1246 : TALER_B2S (wtid),
1247 : TALER_amount2s (amount),
1248 : GNUNET_TIME_timestamp2s (date));
1249 28 : if (NULL == (method = TALER_payto_get_method (payto_uri.full_payto)))
1250 : {
1251 0 : qs = report_row_inconsistency ("wire_out",
1252 : rowid,
1253 : "specified wire address lacks method");
1254 0 : if (0 > qs)
1255 0 : ac->qs = qs;
1256 0 : return GNUNET_OK;
1257 : }
1258 :
1259 28 : wcc.ac = ac;
1260 28 : wcc.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1261 28 : wcc.date = date;
1262 28 : GNUNET_assert (GNUNET_OK ==
1263 : TALER_amount_set_zero (amount->currency,
1264 : &wcc.total_deposits));
1265 28 : wcc.payto_uri = payto_uri;
1266 28 : qs = TALER_EXCHANGEDB_iterate_wire_transfers (TALER_ARL_edb,
1267 : wtid,
1268 : &wire_transfer_information_cb,
1269 : &wcc);
1270 28 : if (0 > qs)
1271 : {
1272 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1273 0 : ac->qs = qs;
1274 0 : GNUNET_free (method);
1275 0 : return GNUNET_SYSERR;
1276 : }
1277 28 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != wcc.qs)
1278 : {
1279 : /* Note: detailed information was already logged
1280 : in #wire_transfer_information_cb, so here we
1281 : only log for debugging */
1282 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1283 : "Inconsistency for wire_out %llu (WTID %s) detected\n",
1284 : (unsigned long long) rowid,
1285 : TALER_B2S (wtid));
1286 : }
1287 :
1288 :
1289 : /* Subtract aggregation fee from total (if possible) */
1290 : {
1291 : const struct TALER_Amount *wire_fee;
1292 :
1293 28 : wire_fee = get_wire_fee (ac,
1294 : method,
1295 : date);
1296 28 : if (0 > ac->qs)
1297 : {
1298 0 : GNUNET_free (method);
1299 0 : return GNUNET_SYSERR;
1300 : }
1301 28 : if (NULL == wire_fee)
1302 : {
1303 0 : qs = report_row_inconsistency ("wire-fee",
1304 : date.abs_time.abs_value_us,
1305 : "wire fee unavailable for given time");
1306 0 : if (qs < 0)
1307 : {
1308 0 : ac->qs = qs;
1309 0 : GNUNET_free (method);
1310 0 : return GNUNET_SYSERR;
1311 : }
1312 : /* If fee is unknown, we just assume the fee is zero */
1313 0 : final_amount = wcc.total_deposits;
1314 : }
1315 28 : else if (TALER_ARL_SR_INVALID_NEGATIVE ==
1316 28 : TALER_ARL_amount_subtract_neg (&final_amount,
1317 : &wcc.total_deposits,
1318 : wire_fee))
1319 : {
1320 0 : qs = report_amount_arithmetic_inconsistency (
1321 : "wire out (fee structure)",
1322 : rowid,
1323 : &wcc.total_deposits,
1324 : wire_fee,
1325 : -1);
1326 : /* If fee arithmetic fails, we just assume the fee is zero */
1327 0 : if (0 > qs)
1328 : {
1329 0 : ac->qs = qs;
1330 0 : GNUNET_free (method);
1331 0 : return GNUNET_SYSERR;
1332 : }
1333 0 : final_amount = wcc.total_deposits;
1334 : }
1335 : }
1336 28 : GNUNET_free (method);
1337 :
1338 : /* Round down to amount supported by wire method */
1339 28 : GNUNET_break (GNUNET_SYSERR !=
1340 : TALER_amount_round_down (&final_amount,
1341 : &TALER_ARL_currency_round_unit));
1342 :
1343 : /* Calculate the exchange's gain as the fees plus rounding differences! */
1344 28 : TALER_ARL_amount_subtract (&exchange_gain,
1345 : &wcc.total_deposits,
1346 : &final_amount);
1347 :
1348 : /* Sum up aggregation fees (we simply include the rounding gains) */
1349 28 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue),
1350 : &TALER_ARL_USE_AB (aggregation_total_wire_fee_revenue),
1351 : &exchange_gain);
1352 :
1353 : /* Check that calculated amount matches actual amount */
1354 28 : if (0 != TALER_amount_cmp (amount,
1355 : &final_amount))
1356 : {
1357 : struct TALER_Amount delta;
1358 :
1359 0 : if (0 < TALER_amount_cmp (amount,
1360 : &final_amount))
1361 : {
1362 : /* amount > final_amount */
1363 0 : TALER_ARL_amount_subtract (&delta,
1364 : amount,
1365 : &final_amount);
1366 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (
1367 : aggregation_total_wire_out_delta_plus),
1368 : &TALER_ARL_USE_AB (
1369 : aggregation_total_wire_out_delta_plus),
1370 : &delta);
1371 : }
1372 : else
1373 : {
1374 : /* amount < final_amount */
1375 0 : TALER_ARL_amount_subtract (&delta,
1376 : &final_amount,
1377 : amount);
1378 0 : TALER_ARL_amount_add (&TALER_ARL_USE_AB (
1379 : aggregation_total_wire_out_delta_minus),
1380 : &TALER_ARL_USE_AB (
1381 : aggregation_total_wire_out_delta_minus),
1382 : &delta);
1383 : }
1384 :
1385 : {
1386 0 : struct TALER_AUDITORDB_WireOutInconsistency woi = {
1387 : .destination_account = payto_uri,
1388 : .diagnostic = (char *) "aggregated amount does not match expectations",
1389 : .wire_out_row_id = rowid,
1390 : .expected = final_amount,
1391 : .claimed = *amount
1392 : };
1393 :
1394 0 : qs = TALER_AUDITORDB_insert_wire_out_inconsistency (
1395 : TALER_ARL_adb,
1396 : &woi);
1397 :
1398 0 : if (qs < 0)
1399 : {
1400 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1401 0 : ac->qs = qs;
1402 0 : return GNUNET_SYSERR;
1403 : }
1404 : }
1405 0 : return GNUNET_OK;
1406 : }
1407 28 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1408 : "Aggregation unit %s is OK\n",
1409 : TALER_B2S (wtid));
1410 28 : return GNUNET_OK;
1411 : }
1412 :
1413 :
1414 : /**
1415 : * Analyze the exchange aggregator's payment processing.
1416 : *
1417 : * @param cls closure
1418 : * @return transaction status code
1419 : */
1420 : static enum GNUNET_DB_QueryStatus
1421 10 : analyze_aggregations (void *cls)
1422 : {
1423 : struct AggregationContext ac;
1424 : struct WireFeeInfo *wfi;
1425 : enum GNUNET_DB_QueryStatus qs;
1426 :
1427 : (void) cls;
1428 10 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1429 : "Analyzing aggregations\n");
1430 10 : qs = TALER_AUDITORDB_get_auditor_progress (
1431 : TALER_ARL_adb,
1432 : TALER_ARL_GET_PP (aggregation_last_wire_out_serial_id),
1433 : NULL);
1434 10 : if (0 > qs)
1435 : {
1436 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1437 0 : return qs;
1438 : }
1439 10 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1440 : {
1441 0 : GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
1442 : "First analysis using this auditor, starting audit from scratch\n");
1443 : }
1444 : else
1445 : {
1446 10 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1447 : "Resuming aggregation audit at %llu\n",
1448 : (unsigned long long) TALER_ARL_USE_PP (
1449 : aggregation_last_wire_out_serial_id));
1450 : }
1451 :
1452 10 : memset (&ac,
1453 : 0,
1454 : sizeof (ac));
1455 10 : qs = TALER_AUDITORDB_get_balance (
1456 : TALER_ARL_adb,
1457 : TALER_ARL_GET_AB (aggregation_total_wire_fee_revenue),
1458 : TALER_ARL_GET_AB (aggregation_total_arithmetic_delta_plus),
1459 : TALER_ARL_GET_AB (aggregation_total_arithmetic_delta_minus),
1460 : TALER_ARL_GET_AB (aggregation_total_bad_sig_loss),
1461 : TALER_ARL_GET_AB (aggregation_total_wire_out_delta_plus),
1462 : TALER_ARL_GET_AB (aggregation_total_wire_out_delta_minus),
1463 : TALER_ARL_GET_AB (aggregation_total_coin_delta_plus),
1464 : TALER_ARL_GET_AB (aggregation_total_coin_delta_minus),
1465 : NULL);
1466 10 : if (0 > qs)
1467 : {
1468 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1469 0 : return qs;
1470 : }
1471 :
1472 10 : ac.qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1473 10 : qs = TALER_EXCHANGEDB_iterate_wire_outs_above_serial_id (
1474 : TALER_ARL_edb,
1475 : TALER_ARL_USE_PP (aggregation_last_wire_out_serial_id),
1476 : &check_wire_out_cb,
1477 : &ac);
1478 10 : if (0 > qs)
1479 : {
1480 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1481 0 : ac.qs = qs;
1482 : }
1483 12 : while (NULL != (wfi = ac.fee_head))
1484 : {
1485 2 : GNUNET_CONTAINER_DLL_remove (ac.fee_head,
1486 : ac.fee_tail,
1487 : wfi);
1488 2 : GNUNET_free (wfi);
1489 : }
1490 10 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1491 : {
1492 : /* there were no wire out entries to be looked at, we are done */
1493 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1494 : "No wire out entries found\n");
1495 8 : return qs;
1496 : }
1497 2 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ac.qs)
1498 : {
1499 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == ac.qs);
1500 0 : return ac.qs;
1501 : }
1502 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1503 : "Finished aggregation audit at %llu\n",
1504 : (unsigned long long) TALER_ARL_USE_PP (
1505 : aggregation_last_wire_out_serial_id));
1506 2 : qs = TALER_AUDITORDB_insert_balance (
1507 : TALER_ARL_adb,
1508 : TALER_ARL_SET_AB (aggregation_total_wire_fee_revenue),
1509 : TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_plus),
1510 : TALER_ARL_SET_AB (aggregation_total_arithmetic_delta_minus),
1511 : TALER_ARL_SET_AB (aggregation_total_bad_sig_loss),
1512 : TALER_ARL_SET_AB (aggregation_total_wire_out_delta_plus),
1513 : TALER_ARL_SET_AB (aggregation_total_wire_out_delta_minus),
1514 : TALER_ARL_SET_AB (aggregation_total_coin_delta_plus),
1515 : TALER_ARL_SET_AB (aggregation_total_coin_delta_minus),
1516 : NULL);
1517 2 : if (0 > qs)
1518 : {
1519 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1520 : "Failed to update auditor DB, not recording progress\n");
1521 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1522 0 : return qs;
1523 : }
1524 :
1525 2 : qs = TALER_AUDITORDB_insert_auditor_progress (
1526 : TALER_ARL_adb,
1527 : TALER_ARL_SET_PP (aggregation_last_wire_out_serial_id),
1528 : NULL);
1529 2 : if (0 > qs)
1530 : {
1531 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1532 : "Failed to update auditor DB, not recording progress\n");
1533 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
1534 0 : return qs;
1535 : }
1536 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1537 : "Concluded aggregation audit step at %llu\n",
1538 : (unsigned long long) TALER_ARL_USE_PP (
1539 : aggregation_last_wire_out_serial_id));
1540 :
1541 2 : return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
1542 : }
1543 :
1544 :
1545 : /**
1546 : * Function called on events received from Postgres.
1547 : *
1548 : * @param cls closure, NULL
1549 : * @param extra additional event data provided
1550 : * @param extra_size number of bytes in @a extra
1551 : */
1552 : static void
1553 0 : db_notify (void *cls,
1554 : const void *extra,
1555 : size_t extra_size)
1556 : {
1557 : (void) cls;
1558 : (void) extra;
1559 : (void) extra_size;
1560 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1561 : "Received notification to wake aggregation helper\n");
1562 0 : if (GNUNET_OK !=
1563 0 : TALER_ARL_setup_sessions_and_run (&analyze_aggregations,
1564 : NULL))
1565 : {
1566 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1567 : "Audit failed\n");
1568 0 : GNUNET_SCHEDULER_shutdown ();
1569 0 : global_ret = EXIT_FAILURE;
1570 0 : return;
1571 : }
1572 : }
1573 :
1574 :
1575 : /**
1576 : * Function called on shutdown.
1577 : */
1578 : static void
1579 10 : do_shutdown (void *cls)
1580 : {
1581 : (void) cls;
1582 10 : if (NULL != eh)
1583 : {
1584 4 : TALER_AUDITORDB_event_listen_cancel (eh);
1585 4 : eh = NULL;
1586 : }
1587 10 : TALER_ARL_done ();
1588 10 : }
1589 :
1590 :
1591 : /**
1592 : * Main function that will be run.
1593 : *
1594 : * @param cls closure
1595 : * @param args remaining command-line arguments
1596 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1597 : * @param c configuration
1598 : */
1599 : static void
1600 10 : run (void *cls,
1601 : char *const *args,
1602 : const char *cfgfile,
1603 : const struct GNUNET_CONFIGURATION_Handle *c)
1604 : {
1605 : (void) cls;
1606 : (void) args;
1607 : (void) cfgfile;
1608 :
1609 10 : cfg = c;
1610 10 : GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
1611 : NULL);
1612 10 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1613 : "Launching aggregation auditor\n");
1614 10 : if (EXIT_SUCCESS !=
1615 10 : (global_ret = TALER_ARL_init (c)))
1616 : {
1617 0 : return;
1618 : }
1619 :
1620 10 : if (test_mode != 1)
1621 : {
1622 4 : struct GNUNET_DB_EventHeaderP es = {
1623 4 : .size = htons (sizeof (es)),
1624 4 : .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_AGGREGATION)
1625 : };
1626 :
1627 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1628 : "Running helper indefinitely\n");
1629 4 : eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
1630 : &es,
1631 4 : GNUNET_TIME_UNIT_FOREVER_REL,
1632 : &db_notify,
1633 : NULL);
1634 : }
1635 10 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1636 : "Starting audit\n");
1637 10 : if (GNUNET_OK !=
1638 10 : TALER_ARL_setup_sessions_and_run (&analyze_aggregations,
1639 : NULL))
1640 : {
1641 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1642 : "Audit failed\n");
1643 0 : GNUNET_SCHEDULER_shutdown ();
1644 0 : global_ret = EXIT_FAILURE;
1645 0 : return;
1646 : }
1647 : }
1648 :
1649 :
1650 : /**
1651 : * The main function to audit the exchange's aggregation processing.
1652 : *
1653 : * @param argc number of arguments from the command line
1654 : * @param argv command line arguments
1655 : * @return 0 ok, 1 on error
1656 : */
1657 : int
1658 10 : main (int argc,
1659 : char *const *argv)
1660 : {
1661 10 : const struct GNUNET_GETOPT_CommandLineOption options[] = {
1662 10 : GNUNET_GETOPT_option_flag ('i',
1663 : "internal",
1664 : "perform checks only applicable for exchange-internal audits",
1665 : &internal_checks),
1666 10 : GNUNET_GETOPT_option_flag ('t',
1667 : "test",
1668 : "run in test mode and exit when idle",
1669 : &test_mode),
1670 10 : GNUNET_GETOPT_option_timetravel ('T',
1671 : "timetravel"),
1672 : GNUNET_GETOPT_OPTION_END
1673 : };
1674 : enum GNUNET_GenericReturnValue ret;
1675 :
1676 10 : ret = GNUNET_PROGRAM_run (
1677 : TALER_AUDITOR_project_data (),
1678 : argc,
1679 : argv,
1680 : "taler-helper-auditor-aggregation",
1681 : gettext_noop ("Audit Taler exchange aggregation activity"),
1682 : options,
1683 : &run,
1684 : NULL);
1685 10 : if (GNUNET_SYSERR == ret)
1686 0 : return EXIT_NOTCONFIGURED;
1687 10 : if (GNUNET_NO == ret)
1688 0 : return EXIT_SUCCESS;
1689 10 : return global_ret;
1690 : }
1691 :
1692 :
1693 : /* end of taler-helper-auditor-aggregation.c */
|