taler-exchange-drain.c (12403B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022 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 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 Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-exchange-drain.c 18 * @brief Process that drains exchange profits from the escrow account 19 * and puts them into some regular account of the exchange. 20 * @author Christian Grothoff 21 */ 22 #include "platform.h" 23 #include <gnunet/gnunet_util_lib.h> 24 #include <jansson.h> 25 #include <pthread.h> 26 #include "exchangedb_lib.h" 27 #include "taler/taler_json_lib.h" 28 #include "taler/taler_bank_service.h" 29 #include "exchange-database/start.h" 30 #include "exchange-database/preflight.h" 31 #include "exchange-database/commit.h" 32 #include "exchange-database/rollback.h" 33 #include "exchange-database/profit_drains_get_pending.h" 34 #include "exchange-database/wire_prepare_data_insert.h" 35 #include "exchange-database/profit_drains_set_finished.h" 36 #include "exchange-database/wire_prepare_data_insert.h" 37 38 /** 39 * The exchange's configuration. 40 */ 41 static const struct GNUNET_CONFIGURATION_Handle *cfg; 42 43 /** 44 * Our database plugin. 45 */ 46 static struct TALER_EXCHANGEDB_PostgresContext *pg; 47 48 /** 49 * Our master public key. 50 */ 51 static struct TALER_MasterPublicKeyP master_pub; 52 53 /** 54 * Next task to run, if any. 55 */ 56 static struct GNUNET_SCHEDULER_Task *task; 57 58 /** 59 * Base URL of this exchange. 60 */ 61 static char *exchange_base_url; 62 63 /** 64 * Value to return from main(). 0 on success, non-zero on errors. 65 */ 66 static int global_ret; 67 68 69 /** 70 * We're being aborted with CTRL-C (or SIGTERM). Shut down. 71 * 72 * @param cls closure 73 */ 74 static void 75 shutdown_task (void *cls) 76 { 77 (void) cls; 78 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 79 "Running shutdown\n"); 80 if (NULL != task) 81 { 82 GNUNET_SCHEDULER_cancel (task); 83 task = NULL; 84 } 85 TALER_EXCHANGEDB_rollback (pg); /* just in case */ 86 TALER_EXCHANGEDB_disconnect (pg); 87 pg = NULL; 88 TALER_EXCHANGEDB_unload_accounts (); 89 cfg = NULL; 90 } 91 92 93 /** 94 * Parse the configuration for drain. 95 * 96 * @return #GNUNET_OK on success 97 */ 98 static enum GNUNET_GenericReturnValue 99 parse_drain_config (void) 100 { 101 if (GNUNET_OK != 102 GNUNET_CONFIGURATION_get_value_string (cfg, 103 "exchange", 104 "BASE_URL", 105 &exchange_base_url)) 106 { 107 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 108 "exchange", 109 "BASE_URL"); 110 return GNUNET_SYSERR; 111 } 112 113 { 114 char *master_public_key_str; 115 116 if (GNUNET_OK != 117 GNUNET_CONFIGURATION_get_value_string (cfg, 118 "exchange", 119 "MASTER_PUBLIC_KEY", 120 &master_public_key_str)) 121 { 122 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 123 "exchange", 124 "MASTER_PUBLIC_KEY"); 125 return GNUNET_SYSERR; 126 } 127 if (GNUNET_OK != 128 GNUNET_CRYPTO_eddsa_public_key_from_string (master_public_key_str, 129 strlen ( 130 master_public_key_str), 131 &master_pub.eddsa_pub)) 132 { 133 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 134 "exchange", 135 "MASTER_PUBLIC_KEY", 136 "invalid base32 encoding for a master public key"); 137 GNUNET_free (master_public_key_str); 138 return GNUNET_SYSERR; 139 } 140 GNUNET_free (master_public_key_str); 141 } 142 if (NULL == 143 (pg = TALER_EXCHANGEDB_connect (cfg))) 144 { 145 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 146 "Failed to initialize DB subsystem\n"); 147 return GNUNET_SYSERR; 148 } 149 if (GNUNET_OK != 150 TALER_EXCHANGEDB_load_accounts (cfg, 151 TALER_EXCHANGEDB_ALO_DEBIT 152 | TALER_EXCHANGEDB_ALO_AUTHDATA)) 153 { 154 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 155 "No wire accounts configured for debit!\n"); 156 TALER_EXCHANGEDB_disconnect (pg); 157 pg = NULL; 158 return GNUNET_SYSERR; 159 } 160 return GNUNET_OK; 161 } 162 163 164 /** 165 * Perform a database commit. If it fails, print a warning. 166 * 167 * @return status of commit 168 */ 169 static enum GNUNET_DB_QueryStatus 170 commit_or_warn (void) 171 { 172 enum GNUNET_DB_QueryStatus qs; 173 174 qs = TALER_EXCHANGEDB_commit (pg); 175 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 176 return qs; 177 GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs) 178 ? GNUNET_ERROR_TYPE_INFO 179 : GNUNET_ERROR_TYPE_ERROR, 180 "Failed to commit database transaction!\n"); 181 return qs; 182 } 183 184 185 /** 186 * Execute a wire drain. 187 * 188 * @param cls NULL 189 */ 190 static void 191 run_drain (void *cls) 192 { 193 enum GNUNET_DB_QueryStatus qs; 194 uint64_t serial; 195 struct TALER_WireTransferIdentifierRawP wtid; 196 char *account_section; 197 struct TALER_FullPayto payto_uri; 198 struct GNUNET_TIME_Timestamp request_timestamp; 199 struct TALER_Amount amount; 200 struct TALER_MasterSignatureP master_sig; 201 202 (void) cls; 203 task = NULL; 204 if (GNUNET_OK != 205 TALER_EXCHANGEDB_start (pg, 206 "run drain")) 207 { 208 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 209 "Failed to start database transaction!\n"); 210 global_ret = EXIT_FAILURE; 211 GNUNET_SCHEDULER_shutdown (); 212 return; 213 } 214 qs = TALER_EXCHANGEDB_profit_drains_get_pending (pg, 215 &serial, 216 &wtid, 217 &account_section, 218 &payto_uri, 219 &request_timestamp, 220 &amount, 221 &master_sig); 222 switch (qs) 223 { 224 case GNUNET_DB_STATUS_HARD_ERROR: 225 TALER_EXCHANGEDB_rollback (pg); 226 GNUNET_break (0); 227 global_ret = EXIT_FAILURE; 228 GNUNET_SCHEDULER_shutdown (); 229 return; 230 case GNUNET_DB_STATUS_SOFT_ERROR: 231 TALER_EXCHANGEDB_rollback (pg); 232 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 233 "Serialization failure on simple SELECT!?\n"); 234 global_ret = EXIT_FAILURE; 235 GNUNET_SCHEDULER_shutdown (); 236 return; 237 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 238 /* no profit drains, finished */ 239 TALER_EXCHANGEDB_rollback (pg); 240 GNUNET_assert (NULL == task); 241 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 242 "No profit drains pending. Exiting.\n"); 243 GNUNET_SCHEDULER_shutdown (); 244 return; 245 default: 246 /* continued below */ 247 break; 248 } 249 /* Check signature (again, this is a critical operation!) */ 250 if (GNUNET_OK != 251 TALER_exchange_offline_profit_drain_verify ( 252 &wtid, 253 request_timestamp, 254 &amount, 255 account_section, 256 payto_uri, 257 &master_pub, 258 &master_sig)) 259 { 260 GNUNET_break (0); 261 global_ret = EXIT_FAILURE; 262 TALER_EXCHANGEDB_rollback (pg); 263 GNUNET_assert (NULL == task); 264 GNUNET_SCHEDULER_shutdown (); 265 return; 266 } 267 268 /* Display data for manual human check */ 269 fprintf (stdout, 270 "Critical operation. MANUAL CHECK REQUIRED.\n"); 271 fprintf (stdout, 272 "We will wire %s to `%s'\n based on instructions from %s.\n", 273 TALER_amount2s (&amount), 274 payto_uri.full_payto, 275 GNUNET_TIME_timestamp2s (request_timestamp)); 276 fprintf (stdout, 277 "Press ENTER to confirm, CTRL-D to abort.\n"); 278 while (1) 279 { 280 int key; 281 282 key = getchar (); 283 if (EOF == key) 284 { 285 fprintf (stdout, 286 "Transfer aborted.\n" 287 "Re-run 'taler-exchange-drain' to try it again.\n" 288 "Contact Taler Systems SA to cancel it for good.\n" 289 "Exiting.\n"); 290 TALER_EXCHANGEDB_rollback (pg); 291 GNUNET_free (payto_uri.full_payto); 292 GNUNET_assert (NULL == task); 293 GNUNET_SCHEDULER_shutdown (); 294 global_ret = EXIT_FAILURE; 295 return; 296 } 297 if ('\n' == key) 298 break; 299 } 300 301 /* Note: account_section ignored for now, we 302 might want to use it here in the future... */ 303 (void) account_section; 304 { 305 char *method; 306 void *buf; 307 size_t buf_size; 308 309 TALER_BANK_prepare_transfer (payto_uri, 310 &amount, 311 exchange_base_url, 312 &wtid, 313 NULL, /* no extra metadata */ 314 &buf, 315 &buf_size); 316 method = TALER_payto_get_method (payto_uri.full_payto); 317 qs = TALER_EXCHANGEDB_wire_prepare_data_insert (pg, 318 method, 319 buf, 320 buf_size); 321 GNUNET_free (method); 322 GNUNET_free (buf); 323 } 324 GNUNET_free (payto_uri.full_payto); 325 qs = TALER_EXCHANGEDB_profit_drains_set_finished (pg, 326 serial); 327 switch (qs) 328 { 329 case GNUNET_DB_STATUS_HARD_ERROR: 330 TALER_EXCHANGEDB_rollback (pg); 331 GNUNET_break (0); 332 global_ret = EXIT_FAILURE; 333 GNUNET_SCHEDULER_shutdown (); 334 return; 335 case GNUNET_DB_STATUS_SOFT_ERROR: 336 TALER_EXCHANGEDB_rollback (pg); 337 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 338 "Failed: database serialization issue\n"); 339 global_ret = EXIT_FAILURE; 340 GNUNET_SCHEDULER_shutdown (); 341 return; 342 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 343 TALER_EXCHANGEDB_rollback (pg); 344 GNUNET_assert (NULL == task); 345 GNUNET_break (0); 346 GNUNET_SCHEDULER_shutdown (); 347 return; 348 default: 349 /* continued below */ 350 break; 351 } 352 /* commit transaction + report success + exit */ 353 if (0 >= commit_or_warn ()) 354 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 355 "Profit drain triggered. Exiting.\n"); 356 GNUNET_SCHEDULER_shutdown (); 357 } 358 359 360 /** 361 * First task. 362 * 363 * @param cls closure, NULL 364 * @param args remaining command-line arguments 365 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 366 * @param c configuration 367 */ 368 static void 369 run (void *cls, 370 char *const *args, 371 const char *cfgfile, 372 const struct GNUNET_CONFIGURATION_Handle *c) 373 { 374 (void) cls; 375 (void) args; 376 (void) cfgfile; 377 378 cfg = c; 379 if (GNUNET_OK != parse_drain_config ()) 380 { 381 cfg = NULL; 382 global_ret = EXIT_NOTCONFIGURED; 383 return; 384 } 385 if (GNUNET_SYSERR == 386 TALER_EXCHANGEDB_preflight (pg)) 387 { 388 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 389 "Failed to obtain database connection!\n"); 390 global_ret = EXIT_FAILURE; 391 GNUNET_SCHEDULER_shutdown (); 392 return; 393 } 394 GNUNET_assert (NULL == task); 395 task = GNUNET_SCHEDULER_add_now (&run_drain, 396 NULL); 397 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 398 cls); 399 } 400 401 402 /** 403 * The main function of the taler-exchange-drain. 404 * 405 * @param argc number of arguments from the command line 406 * @param argv command line arguments 407 * @return 0 ok, 1 on error 408 */ 409 int 410 main (int argc, 411 char *const *argv) 412 { 413 struct GNUNET_GETOPT_CommandLineOption options[] = { 414 GNUNET_GETOPT_option_version (VERSION), 415 GNUNET_GETOPT_OPTION_END 416 }; 417 enum GNUNET_GenericReturnValue ret; 418 419 ret = GNUNET_PROGRAM_run ( 420 TALER_EXCHANGE_project_data (), 421 argc, argv, 422 "taler-exchange-drain", 423 gettext_noop ( 424 "process that executes a single profit drain"), 425 options, 426 &run, NULL); 427 if (GNUNET_SYSERR == ret) 428 return EXIT_INVALIDARGUMENT; 429 if (GNUNET_NO == ret) 430 return EXIT_SUCCESS; 431 return global_ret; 432 } 433 434 435 /* end of taler-exchange-drain.c */