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