taler-exchange-drain.c (12152B)
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 &buf, 309 &buf_size); 310 method = TALER_payto_get_method (payto_uri.full_payto); 311 qs = db_plugin->wire_prepare_data_insert (db_plugin->cls, 312 method, 313 buf, 314 buf_size); 315 GNUNET_free (method); 316 GNUNET_free (buf); 317 } 318 GNUNET_free (payto_uri.full_payto); 319 qs = db_plugin->profit_drains_set_finished (db_plugin->cls, 320 serial); 321 switch (qs) 322 { 323 case GNUNET_DB_STATUS_HARD_ERROR: 324 db_plugin->rollback (db_plugin->cls); 325 GNUNET_break (0); 326 global_ret = EXIT_FAILURE; 327 GNUNET_SCHEDULER_shutdown (); 328 return; 329 case GNUNET_DB_STATUS_SOFT_ERROR: 330 db_plugin->rollback (db_plugin->cls); 331 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 332 "Failed: database serialization issue\n"); 333 global_ret = EXIT_FAILURE; 334 GNUNET_SCHEDULER_shutdown (); 335 return; 336 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 337 db_plugin->rollback (db_plugin->cls); 338 GNUNET_assert (NULL == task); 339 GNUNET_break (0); 340 GNUNET_SCHEDULER_shutdown (); 341 return; 342 default: 343 /* continued below */ 344 break; 345 } 346 /* commit transaction + report success + exit */ 347 if (0 >= commit_or_warn ()) 348 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 349 "Profit drain triggered. Exiting.\n"); 350 GNUNET_SCHEDULER_shutdown (); 351 } 352 353 354 /** 355 * First task. 356 * 357 * @param cls closure, NULL 358 * @param args remaining command-line arguments 359 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 360 * @param c configuration 361 */ 362 static void 363 run (void *cls, 364 char *const *args, 365 const char *cfgfile, 366 const struct GNUNET_CONFIGURATION_Handle *c) 367 { 368 (void) cls; 369 (void) args; 370 (void) cfgfile; 371 372 cfg = c; 373 if (GNUNET_OK != parse_drain_config ()) 374 { 375 cfg = NULL; 376 global_ret = EXIT_NOTCONFIGURED; 377 return; 378 } 379 if (GNUNET_SYSERR == 380 db_plugin->preflight (db_plugin->cls)) 381 { 382 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 383 "Failed to obtain database connection!\n"); 384 global_ret = EXIT_FAILURE; 385 GNUNET_SCHEDULER_shutdown (); 386 return; 387 } 388 GNUNET_assert (NULL == task); 389 task = GNUNET_SCHEDULER_add_now (&run_drain, 390 NULL); 391 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 392 cls); 393 } 394 395 396 /** 397 * The main function of the taler-exchange-drain. 398 * 399 * @param argc number of arguments from the command line 400 * @param argv command line arguments 401 * @return 0 ok, 1 on error 402 */ 403 int 404 main (int argc, 405 char *const *argv) 406 { 407 struct GNUNET_GETOPT_CommandLineOption options[] = { 408 GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION), 409 GNUNET_GETOPT_OPTION_END 410 }; 411 enum GNUNET_GenericReturnValue ret; 412 413 ret = GNUNET_PROGRAM_run ( 414 TALER_EXCHANGE_project_data (), 415 argc, argv, 416 "taler-exchange-drain", 417 gettext_noop ( 418 "process that executes a single profit drain"), 419 options, 420 &run, NULL); 421 if (GNUNET_SYSERR == ret) 422 return EXIT_INVALIDARGUMENT; 423 if (GNUNET_NO == ret) 424 return EXIT_SUCCESS; 425 return global_ret; 426 } 427 428 429 /* end of taler-exchange-drain.c */