Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General 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 General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file src/exchangedb/iterate_aml_history_above_serial_id.c
18 : * @brief Implementation of the iterate_aml_history_above_serial_id function for Postgres
19 : * @author Christian Grothoff
20 : */
21 : #include "taler/taler_pq_lib.h"
22 : #include "helper.h"
23 : #include "exchange-database/iterate_aml_history_above_serial_id.h"
24 :
25 :
26 : /**
27 : * Closure for #aml_decision_cb().
28 : */
29 : struct AmlDecisionContext
30 : {
31 : /**
32 : * Function to call for each decision.
33 : */
34 : TALER_EXCHANGEDB_AmlHistoryDecisionCallback cb;
35 :
36 : /**
37 : * Closure for @e cb.
38 : */
39 : void *cb_cls;
40 :
41 : /**
42 : * Query status to return.
43 : */
44 : enum GNUNET_DB_QueryStatus qs;
45 : };
46 :
47 :
48 : /**
49 : * Helper function for
50 : * #TALER_EXCHANGEDB_iterate_aml_history_above_serial_id().
51 : * To be called with the results of a SELECT statement
52 : * that has returned @a num_results results.
53 : *
54 : * @param cls closure of type `struct AmlDecisionContext *`
55 : * @param result the postgres result
56 : * @param num_results the number of results in @a result
57 : */
58 : static void
59 4 : aml_decision_cb (void *cls,
60 : PGresult *result,
61 : unsigned int num_results)
62 : {
63 4 : struct AmlDecisionContext *adc = cls;
64 :
65 6 : for (unsigned int i = 0; i < num_results; i++)
66 : {
67 : uint64_t rowid;
68 : struct TALER_NormalizedPaytoHashP h_payto;
69 3 : char *justification = NULL;
70 : struct TALER_AmlOfficerPublicKeyP decider_pub;
71 : struct TALER_AmlOfficerSignatureP decider_sig;
72 : struct GNUNET_TIME_Timestamp decision_time;
73 3 : json_t *jproperties = NULL;
74 3 : json_t *jnew_rules = NULL;
75 3 : char *new_measure_name = NULL;
76 : bool to_investigate;
77 3 : struct GNUNET_TIME_Timestamp attributes_expiration
78 : = GNUNET_TIME_UNIT_ZERO_TS;
79 : struct GNUNET_HashCode h_attributes;
80 : bool no_decider_pub;
81 : bool no_decider_sig;
82 : bool no_new_rules;
83 : bool no_measure;
84 : bool no_attributes_expiration;
85 : bool no_h_attributes;
86 3 : struct GNUNET_PQ_ResultSpec rs[] = {
87 3 : GNUNET_PQ_result_spec_uint64 ("aml_history_serial_id",
88 : &rowid),
89 3 : GNUNET_PQ_result_spec_auto_from_type ("h_payto",
90 : &h_payto),
91 3 : GNUNET_PQ_result_spec_string ("justification",
92 : &justification),
93 : /* decider_pub, decider_sig, kyc_attributes_hash and jnew_rules all
94 : lack a NOT NULL constraint. An honest decision has all of them;
95 : reporting the ones that do not is the auditor's job, so they must
96 : not abort the query here. */
97 3 : GNUNET_PQ_result_spec_allow_null (
98 : GNUNET_PQ_result_spec_auto_from_type ("decider_pub",
99 : &decider_pub),
100 : &no_decider_pub),
101 3 : GNUNET_PQ_result_spec_allow_null (
102 : GNUNET_PQ_result_spec_auto_from_type ("decider_sig",
103 : &decider_sig),
104 : &no_decider_sig),
105 3 : GNUNET_PQ_result_spec_timestamp ("decision_time",
106 : &decision_time),
107 3 : GNUNET_PQ_result_spec_allow_null (
108 : TALER_PQ_result_spec_json ("jproperties",
109 : &jproperties),
110 : NULL),
111 3 : GNUNET_PQ_result_spec_allow_null (
112 : TALER_PQ_result_spec_json ("jnew_rules",
113 : &jnew_rules),
114 : &no_new_rules),
115 3 : GNUNET_PQ_result_spec_allow_null (
116 : GNUNET_PQ_result_spec_string ("new_measure_name",
117 : &new_measure_name),
118 : &no_measure),
119 3 : GNUNET_PQ_result_spec_bool ("to_investigate",
120 : &to_investigate),
121 3 : GNUNET_PQ_result_spec_allow_null (
122 : GNUNET_PQ_result_spec_timestamp ("attributes_expiration",
123 : &attributes_expiration),
124 : &no_attributes_expiration),
125 3 : GNUNET_PQ_result_spec_allow_null (
126 : GNUNET_PQ_result_spec_auto_from_type ("kyc_attributes_hash",
127 : &h_attributes),
128 : &no_h_attributes),
129 : GNUNET_PQ_result_spec_end
130 : };
131 : enum GNUNET_GenericReturnValue rval;
132 :
133 3 : if (GNUNET_OK !=
134 3 : GNUNET_PQ_extract_result (result,
135 : rs,
136 : i))
137 : {
138 0 : GNUNET_break (0);
139 0 : adc->qs = GNUNET_DB_STATUS_HARD_ERROR;
140 0 : return;
141 : }
142 3 : if (no_attributes_expiration)
143 2 : attributes_expiration = GNUNET_TIME_UNIT_ZERO_TS;
144 3 : adc->qs = i + 1;
145 3 : rval = adc->cb (adc->cb_cls,
146 : rowid,
147 : &h_payto,
148 : justification,
149 : no_decider_pub ? NULL : &decider_pub,
150 : no_decider_sig ? NULL : &decider_sig,
151 : decision_time,
152 : jproperties,
153 : no_new_rules ? NULL : jnew_rules,
154 : no_measure ? NULL : new_measure_name,
155 : to_investigate,
156 : attributes_expiration,
157 : no_h_attributes ? NULL : &h_attributes);
158 3 : GNUNET_PQ_cleanup_result (rs);
159 3 : if (GNUNET_OK != rval)
160 1 : break;
161 : }
162 : }
163 :
164 :
165 : enum GNUNET_DB_QueryStatus
166 4 : TALER_EXCHANGEDB_iterate_aml_history_above_serial_id (
167 : struct TALER_EXCHANGEDB_PostgresContext *pg,
168 : uint64_t serial_id,
169 : TALER_EXCHANGEDB_AmlHistoryDecisionCallback cb,
170 : TALER_EXCHANGEDB_AML_HISTORY_DECISION_RESULT_CLOSURE *cb_cls)
171 : {
172 4 : struct GNUNET_PQ_QueryParam params[] = {
173 4 : GNUNET_PQ_query_param_uint64 (&serial_id),
174 : GNUNET_PQ_query_param_end
175 : };
176 4 : struct AmlDecisionContext adc = {
177 : .cb = cb,
178 : .cb_cls = cb_cls
179 : };
180 : enum GNUNET_DB_QueryStatus qs;
181 :
182 : /* legitimization_outcomes is joined on a NOT NULL column with a foreign
183 : key, so an inner join here cannot silently drop a decision. The
184 : attributes are optional, hence the LEFT JOIN for their expiration. */
185 4 : PREPARE (pg,
186 : "iterate_aml_history_above_serial_id",
187 : "SELECT"
188 : " ah.aml_history_serial_id"
189 : ",ah.h_payto"
190 : ",ah.justification"
191 : ",ah.decider_pub"
192 : ",ah.decider_sig"
193 : ",ah.kyc_attributes_hash"
194 : ",lo.decision_time"
195 : /* JSONB comes back with a binary version prefix, so it has to
196 : be cast; the cast is what TALER_json_hash() then re-reads. */
197 : ",lo.jproperties::TEXT"
198 : ",lo.jnew_rules::TEXT"
199 : ",lo.new_measure_name"
200 : ",lo.to_investigate"
201 : ",ka.expiration_time AS attributes_expiration"
202 : " FROM aml_history ah"
203 : " JOIN legitimization_outcomes lo"
204 : " USING (outcome_serial_id)"
205 : " LEFT JOIN kyc_attributes ka"
206 : " ON (ah.kyc_attributes_serial_id = ka.kyc_attributes_serial_id)"
207 : " WHERE ah.aml_history_serial_id>=$1"
208 : " ORDER BY ah.aml_history_serial_id ASC;");
209 4 : qs = GNUNET_PQ_eval_prepared_multi_select (
210 : pg->conn,
211 : "iterate_aml_history_above_serial_id",
212 : params,
213 : &aml_decision_cb,
214 : &adc);
215 4 : if (qs > 0)
216 2 : return adc.qs;
217 2 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
218 2 : return qs;
219 : }
|