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