diff options
Diffstat (limited to 'src/exchange/taler-exchange-httpd_deposit.c')
-rw-r--r-- | src/exchange/taler-exchange-httpd_deposit.c | 173 |
1 files changed, 95 insertions, 78 deletions
diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c index 65251863a..fe8fdf061 100644 --- a/src/exchange/taler-exchange-httpd_deposit.c +++ b/src/exchange/taler-exchange-httpd_deposit.c | |||
@@ -47,7 +47,7 @@ | |||
47 | * @param coin_pub public key of the coin | 47 | * @param coin_pub public key of the coin |
48 | * @param h_wire hash of wire details | 48 | * @param h_wire hash of wire details |
49 | * @param h_contract_terms hash of contract details | 49 | * @param h_contract_terms hash of contract details |
50 | * @param timestamp client's timestamp | 50 | * @param exchange_timestamp exchange's timestamp |
51 | * @param refund_deadline until when this deposit be refunded | 51 | * @param refund_deadline until when this deposit be refunded |
52 | * @param merchant merchant public key | 52 | * @param merchant merchant public key |
53 | * @param amount_without_fee fraction of coin value to deposit, without the fee | 53 | * @param amount_without_fee fraction of coin value to deposit, without the fee |
@@ -58,7 +58,7 @@ reply_deposit_success (struct MHD_Connection *connection, | |||
58 | const struct TALER_CoinSpendPublicKeyP *coin_pub, | 58 | const struct TALER_CoinSpendPublicKeyP *coin_pub, |
59 | const struct GNUNET_HashCode *h_wire, | 59 | const struct GNUNET_HashCode *h_wire, |
60 | const struct GNUNET_HashCode *h_contract_terms, | 60 | const struct GNUNET_HashCode *h_contract_terms, |
61 | struct GNUNET_TIME_Absolute timestamp, | 61 | struct GNUNET_TIME_Absolute exchange_timestamp, |
62 | struct GNUNET_TIME_Absolute refund_deadline, | 62 | struct GNUNET_TIME_Absolute refund_deadline, |
63 | const struct TALER_MerchantPublicKeyP *merchant, | 63 | const struct TALER_MerchantPublicKeyP *merchant, |
64 | const struct TALER_Amount *amount_without_fee) | 64 | const struct TALER_Amount *amount_without_fee) |
@@ -70,7 +70,7 @@ reply_deposit_success (struct MHD_Connection *connection, | |||
70 | .purpose.size = htonl (sizeof (dc)), | 70 | .purpose.size = htonl (sizeof (dc)), |
71 | .h_contract_terms = *h_contract_terms, | 71 | .h_contract_terms = *h_contract_terms, |
72 | .h_wire = *h_wire, | 72 | .h_wire = *h_wire, |
73 | .timestamp = GNUNET_TIME_absolute_hton (timestamp), | 73 | .exchange_timestamp = GNUNET_TIME_absolute_hton (exchange_timestamp), |
74 | .refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline), | 74 | .refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline), |
75 | .coin_pub = *coin_pub, | 75 | .coin_pub = *coin_pub, |
76 | .merchant = *merchant | 76 | .merchant = *merchant |
@@ -88,13 +88,16 @@ reply_deposit_success (struct MHD_Connection *connection, | |||
88 | TALER_EC_EXCHANGE_BAD_CONFIGURATION, | 88 | TALER_EC_EXCHANGE_BAD_CONFIGURATION, |
89 | "no keys"); | 89 | "no keys"); |
90 | } | 90 | } |
91 | return TALER_MHD_reply_json_pack (connection, | 91 | return TALER_MHD_reply_json_pack ( |
92 | MHD_HTTP_OK, | 92 | connection, |
93 | "{s:o, s:o}", | 93 | MHD_HTTP_OK, |
94 | "exchange_sig", | 94 | "{s:o, s:o, s:o}", |
95 | GNUNET_JSON_from_data_auto (&sig), | 95 | "exchange_timestamp", |
96 | "exchange_pub", | 96 | GNUNET_JSON_from_time_abs (exchange_timestamp), |
97 | GNUNET_JSON_from_data_auto (&pub)); | 97 | "exchange_sig", |
98 | GNUNET_JSON_from_data_auto (&sig), | ||
99 | "exchange_pub", | ||
100 | GNUNET_JSON_from_data_auto (&pub)); | ||
98 | } | 101 | } |
99 | 102 | ||
100 | 103 | ||
@@ -109,6 +112,11 @@ struct DepositContext | |||
109 | const struct TALER_EXCHANGEDB_Deposit *deposit; | 112 | const struct TALER_EXCHANGEDB_Deposit *deposit; |
110 | 113 | ||
111 | /** | 114 | /** |
115 | * Our timestamp (when we received the request). | ||
116 | */ | ||
117 | struct GNUNET_TIME_Absolute exchange_timestamp; | ||
118 | |||
119 | /** | ||
112 | * Value of the coin. | 120 | * Value of the coin. |
113 | */ | 121 | */ |
114 | struct TALER_Amount value; | 122 | struct TALER_Amount value; |
@@ -117,12 +125,11 @@ struct DepositContext | |||
117 | 125 | ||
118 | 126 | ||
119 | /** | 127 | /** |
120 | * Execute database transaction for /deposit. Runs the transaction | 128 | * Check if /deposit is already in the database. IF it returns a non-error |
121 | * logic; IF it returns a non-error code, the transaction logic MUST | 129 | * code, the transaction logic MUST NOT queue a MHD response. IF it returns |
122 | * NOT queue a MHD response. IF it returns an hard error, the | 130 | * an hard error, the transaction logic MUST queue a MHD response and set @a |
123 | * transaction logic MUST queue a MHD response and set @a mhd_ret. IF | 131 | * mhd_ret. We do return a "hard" error also if we found the deposit in the |
124 | * it returns the soft error code, the function MAY be called again to | 132 | * database and generated a regular response. |
125 | * retry and MUST not queue a MHD response. | ||
126 | * | 133 | * |
127 | * @param cls a `struct DepositContext` | 134 | * @param cls a `struct DepositContext` |
128 | * @param connection MHD request context | 135 | * @param connection MHD request context |
@@ -131,20 +138,22 @@ struct DepositContext | |||
131 | * @return transaction status | 138 | * @return transaction status |
132 | */ | 139 | */ |
133 | static enum GNUNET_DB_QueryStatus | 140 | static enum GNUNET_DB_QueryStatus |
134 | deposit_transaction (void *cls, | 141 | deposit_precheck (void *cls, |
135 | struct MHD_Connection *connection, | 142 | struct MHD_Connection *connection, |
136 | struct TALER_EXCHANGEDB_Session *session, | 143 | struct TALER_EXCHANGEDB_Session *session, |
137 | MHD_RESULT *mhd_ret) | 144 | MHD_RESULT *mhd_ret) |
138 | { | 145 | { |
139 | struct DepositContext *dc = cls; | 146 | struct DepositContext *dc = cls; |
140 | const struct TALER_EXCHANGEDB_Deposit *deposit = dc->deposit; | 147 | const struct TALER_EXCHANGEDB_Deposit *deposit = dc->deposit; |
141 | struct TALER_Amount spent; | 148 | struct TALER_Amount deposit_fee; |
142 | enum GNUNET_DB_QueryStatus qs; | 149 | enum GNUNET_DB_QueryStatus qs; |
143 | 150 | ||
144 | qs = TEH_plugin->have_deposit (TEH_plugin->cls, | 151 | qs = TEH_plugin->have_deposit (TEH_plugin->cls, |
145 | session, | 152 | session, |
146 | deposit, | 153 | deposit, |
147 | GNUNET_YES /* check refund deadline */); | 154 | GNUNET_YES /* check refund deadline */, |
155 | &deposit_fee, | ||
156 | &dc->exchange_timestamp); | ||
148 | if (qs < 0) | 157 | if (qs < 0) |
149 | { | 158 | { |
150 | if (GNUNET_DB_STATUS_HARD_ERROR == qs) | 159 | if (GNUNET_DB_STATUS_HARD_ERROR == qs) |
@@ -166,12 +175,12 @@ deposit_transaction (void *cls, | |||
166 | GNUNET_assert (0 <= | 175 | GNUNET_assert (0 <= |
167 | TALER_amount_subtract (&amount_without_fee, | 176 | TALER_amount_subtract (&amount_without_fee, |
168 | &deposit->amount_with_fee, | 177 | &deposit->amount_with_fee, |
169 | &deposit->deposit_fee)); | 178 | &deposit_fee)); |
170 | *mhd_ret = reply_deposit_success (connection, | 179 | *mhd_ret = reply_deposit_success (connection, |
171 | &deposit->coin.coin_pub, | 180 | &deposit->coin.coin_pub, |
172 | &deposit->h_wire, | 181 | &deposit->h_wire, |
173 | &deposit->h_contract_terms, | 182 | &deposit->h_contract_terms, |
174 | deposit->timestamp, | 183 | dc->exchange_timestamp, |
175 | deposit->refund_deadline, | 184 | deposit->refund_deadline, |
176 | &deposit->merchant_pub, | 185 | &deposit->merchant_pub, |
177 | &amount_without_fee); | 186 | &amount_without_fee); |
@@ -179,6 +188,44 @@ deposit_transaction (void *cls, | |||
179 | never try again. */ | 188 | never try again. */ |
180 | return GNUNET_DB_STATUS_HARD_ERROR; | 189 | return GNUNET_DB_STATUS_HARD_ERROR; |
181 | } | 190 | } |
191 | return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; | ||
192 | } | ||
193 | |||
194 | |||
195 | /** | ||
196 | * Execute database transaction for /deposit. Runs the transaction | ||
197 | * logic; IF it returns a non-error code, the transaction logic MUST | ||
198 | * NOT queue a MHD response. IF it returns an hard error, the | ||
199 | * transaction logic MUST queue a MHD response and set @a mhd_ret. IF | ||
200 | * it returns the soft error code, the function MAY be called again to | ||
201 | * retry and MUST not queue a MHD response. | ||
202 | * | ||
203 | * @param cls a `struct DepositContext` | ||
204 | * @param connection MHD request context | ||
205 | * @param session database session and transaction to use | ||
206 | * @param[out] mhd_ret set to MHD status on error | ||
207 | * @return transaction status | ||
208 | */ | ||
209 | static enum GNUNET_DB_QueryStatus | ||
210 | deposit_transaction (void *cls, | ||
211 | struct MHD_Connection *connection, | ||
212 | struct TALER_EXCHANGEDB_Session *session, | ||
213 | MHD_RESULT *mhd_ret) | ||
214 | { | ||
215 | struct DepositContext *dc = cls; | ||
216 | const struct TALER_EXCHANGEDB_Deposit *deposit = dc->deposit; | ||
217 | struct TALER_Amount spent; | ||
218 | enum GNUNET_DB_QueryStatus qs; | ||
219 | |||
220 | /* Theoretically, someone other threat may have received | ||
221 | and committed the deposit in the meantime. Check now | ||
222 | that we are in the transaction scope. */ | ||
223 | qs = deposit_precheck (cls, | ||
224 | connection, | ||
225 | session, | ||
226 | mhd_ret); | ||
227 | if (qs < 0) | ||
228 | return qs; | ||
182 | 229 | ||
183 | /* Start with fee for THIS transaction */ | 230 | /* Start with fee for THIS transaction */ |
184 | spent = deposit->amount_with_fee; | 231 | spent = deposit->amount_with_fee; |
@@ -238,6 +285,7 @@ deposit_transaction (void *cls, | |||
238 | } | 285 | } |
239 | qs = TEH_plugin->insert_deposit (TEH_plugin->cls, | 286 | qs = TEH_plugin->insert_deposit (TEH_plugin->cls, |
240 | session, | 287 | session, |
288 | dc->exchange_timestamp, | ||
241 | deposit); | 289 | deposit); |
242 | if (GNUNET_DB_STATUS_HARD_ERROR == qs) | 290 | if (GNUNET_DB_STATUS_HARD_ERROR == qs) |
243 | { | 291 | { |
@@ -252,45 +300,6 @@ deposit_transaction (void *cls, | |||
252 | 300 | ||
253 | 301 | ||
254 | /** | 302 | /** |
255 | * Check that @a ts is reasonably close to our own RTC. | ||
256 | * | ||
257 | * @param ts timestamp to check | ||
258 | * @return #GNUNET_OK if @a ts is reasonable | ||
259 | */ | ||
260 | static int | ||
261 | check_timestamp_current (struct GNUNET_TIME_Absolute ts) | ||
262 | { | ||
263 | struct GNUNET_TIME_Relative r; | ||
264 | struct GNUNET_TIME_Relative tolerance; | ||
265 | |||
266 | /* Let's be VERY generous (after all, this is basically about | ||
267 | which year the deposit counts for in terms of tax purposes) */ | ||
268 | tolerance = GNUNET_TIME_UNIT_MONTHS; | ||
269 | r = GNUNET_TIME_absolute_get_duration (ts); | ||
270 | if (r.rel_value_us > tolerance.rel_value_us) | ||
271 | { | ||
272 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
273 | "Deposit timestamp too old: %llu vs %llu > %llu\n", | ||
274 | (unsigned long long) ts.abs_value_us, | ||
275 | (unsigned long long) GNUNET_TIME_absolute_get ().abs_value_us, | ||
276 | (unsigned long long) tolerance.rel_value_us); | ||
277 | return GNUNET_SYSERR; | ||
278 | } | ||
279 | r = GNUNET_TIME_absolute_get_remaining (ts); | ||
280 | if (r.rel_value_us > tolerance.rel_value_us) | ||
281 | { | ||
282 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
283 | "Deposit timestamp too new: %llu vs %llu < - %llu\n", | ||
284 | (unsigned long long) ts.abs_value_us, | ||
285 | (unsigned long long) GNUNET_TIME_absolute_get ().abs_value_us, | ||
286 | (unsigned long long) tolerance.rel_value_us); | ||
287 | return GNUNET_SYSERR; | ||
288 | } | ||
289 | return GNUNET_OK; | ||
290 | } | ||
291 | |||
292 | |||
293 | /** | ||
294 | * Handle a "/coins/$COIN_PUB/deposit" request. Parses the JSON, and, if | 303 | * Handle a "/coins/$COIN_PUB/deposit" request. Parses the JSON, and, if |
295 | * successful, passes the JSON data to #deposit_transaction() to | 304 | * successful, passes the JSON data to #deposit_transaction() to |
296 | * further check the details of the operation specified. If everything checks | 305 | * further check the details of the operation specified. If everything checks |
@@ -367,17 +376,6 @@ TEH_handler_deposit (struct MHD_Connection *connection, | |||
367 | TALER_EC_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE, | 376 | TALER_EC_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE, |
368 | "refund_deadline"); | 377 | "refund_deadline"); |
369 | } | 378 | } |
370 | |||
371 | if (GNUNET_OK != | ||
372 | check_timestamp_current (deposit.timestamp)) | ||
373 | { | ||
374 | GNUNET_break_op (0); | ||
375 | GNUNET_JSON_parse_free (spec); | ||
376 | return TALER_MHD_reply_with_error (connection, | ||
377 | MHD_HTTP_BAD_REQUEST, | ||
378 | TALER_EC_DEPOSIT_INVALID_TIMESTAMP, | ||
379 | "timestamp"); | ||
380 | } | ||
381 | if (GNUNET_OK != | 379 | if (GNUNET_OK != |
382 | TALER_JSON_merchant_wire_signature_hash (wire, | 380 | TALER_JSON_merchant_wire_signature_hash (wire, |
383 | &my_h_wire)) | 381 | &my_h_wire)) |
@@ -401,6 +399,26 @@ TEH_handler_deposit (struct MHD_Connection *connection, | |||
401 | "h_wire"); | 399 | "h_wire"); |
402 | } | 400 | } |
403 | 401 | ||
402 | /* Check for idempotency: did we get this request before? */ | ||
403 | dc.deposit = &deposit; | ||
404 | { | ||
405 | MHD_RESULT mhd_ret; | ||
406 | |||
407 | if (GNUNET_OK != | ||
408 | TEH_DB_run_transaction (connection, | ||
409 | "precheck deposit", | ||
410 | &mhd_ret, | ||
411 | &deposit_precheck, | ||
412 | &dc)) | ||
413 | { | ||
414 | GNUNET_JSON_parse_free (spec); | ||
415 | return mhd_ret; | ||
416 | } | ||
417 | } | ||
418 | |||
419 | /* new deposit */ | ||
420 | dc.exchange_timestamp = GNUNET_TIME_absolute_get (); | ||
421 | (void) GNUNET_TIME_round_abs (&dc.exchange_timestamp); | ||
404 | /* check denomination exists and is valid */ | 422 | /* check denomination exists and is valid */ |
405 | { | 423 | { |
406 | struct TEH_KS_StateHandle *key_state; | 424 | struct TEH_KS_StateHandle *key_state; |
@@ -408,7 +426,7 @@ TEH_handler_deposit (struct MHD_Connection *connection, | |||
408 | enum TALER_ErrorCode ec; | 426 | enum TALER_ErrorCode ec; |
409 | unsigned int hc; | 427 | unsigned int hc; |
410 | 428 | ||
411 | key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); | 429 | key_state = TEH_KS_acquire (dc.exchange_timestamp); |
412 | if (NULL == key_state) | 430 | if (NULL == key_state) |
413 | { | 431 | { |
414 | TALER_LOG_ERROR ("Lacking keys to operate\n"); | 432 | TALER_LOG_ERROR ("Lacking keys to operate\n"); |
@@ -502,7 +520,7 @@ TEH_handler_deposit (struct MHD_Connection *connection, | |||
502 | .purpose.size = htonl (sizeof (dr)), | 520 | .purpose.size = htonl (sizeof (dr)), |
503 | .h_contract_terms = deposit.h_contract_terms, | 521 | .h_contract_terms = deposit.h_contract_terms, |
504 | .h_wire = deposit.h_wire, | 522 | .h_wire = deposit.h_wire, |
505 | .timestamp = GNUNET_TIME_absolute_hton (deposit.timestamp), | 523 | .wallet_timestamp = GNUNET_TIME_absolute_hton (deposit.timestamp), |
506 | .refund_deadline = GNUNET_TIME_absolute_hton (deposit.refund_deadline), | 524 | .refund_deadline = GNUNET_TIME_absolute_hton (deposit.refund_deadline), |
507 | .merchant = deposit.merchant_pub, | 525 | .merchant = deposit.merchant_pub, |
508 | .coin_pub = deposit.coin.coin_pub | 526 | .coin_pub = deposit.coin.coin_pub |
@@ -528,7 +546,6 @@ TEH_handler_deposit (struct MHD_Connection *connection, | |||
528 | } | 546 | } |
529 | 547 | ||
530 | /* execute transaction */ | 548 | /* execute transaction */ |
531 | dc.deposit = &deposit; | ||
532 | { | 549 | { |
533 | MHD_RESULT mhd_ret; | 550 | MHD_RESULT mhd_ret; |
534 | 551 | ||
@@ -557,7 +574,7 @@ TEH_handler_deposit (struct MHD_Connection *connection, | |||
557 | &deposit.coin.coin_pub, | 574 | &deposit.coin.coin_pub, |
558 | &deposit.h_wire, | 575 | &deposit.h_wire, |
559 | &deposit.h_contract_terms, | 576 | &deposit.h_contract_terms, |
560 | deposit.timestamp, | 577 | dc.exchange_timestamp, |
561 | deposit.refund_deadline, | 578 | deposit.refund_deadline, |
562 | &deposit.merchant_pub, | 579 | &deposit.merchant_pub, |
563 | &amount_without_fee); | 580 | &amount_without_fee); |