taler-exchange-drain.c (12464B)
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 false))) 145 { 146 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 147 "Failed to initialize DB subsystem\n"); 148 return GNUNET_SYSERR; 149 } 150 if (GNUNET_OK != 151 TALER_EXCHANGEDB_load_accounts (cfg, 152 TALER_EXCHANGEDB_ALO_DEBIT 153 | TALER_EXCHANGEDB_ALO_AUTHDATA)) 154 { 155 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 156 "No wire accounts configured for debit!\n"); 157 TALER_EXCHANGEDB_disconnect (pg); 158 pg = NULL; 159 return GNUNET_SYSERR; 160 } 161 return GNUNET_OK; 162 } 163 164 165 /** 166 * Perform a database commit. If it fails, print a warning. 167 * 168 * @return status of commit 169 */ 170 static enum GNUNET_DB_QueryStatus 171 commit_or_warn (void) 172 { 173 enum GNUNET_DB_QueryStatus qs; 174 175 qs = TALER_EXCHANGEDB_commit (pg); 176 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 177 return qs; 178 GNUNET_log ((GNUNET_DB_STATUS_SOFT_ERROR == qs) 179 ? GNUNET_ERROR_TYPE_INFO 180 : GNUNET_ERROR_TYPE_ERROR, 181 "Failed to commit database transaction!\n"); 182 return qs; 183 } 184 185 186 /** 187 * Execute a wire drain. 188 * 189 * @param cls NULL 190 */ 191 static void 192 run_drain (void *cls) 193 { 194 enum GNUNET_DB_QueryStatus qs; 195 uint64_t serial; 196 struct TALER_WireTransferIdentifierRawP wtid; 197 char *account_section; 198 struct TALER_FullPayto payto_uri; 199 struct GNUNET_TIME_Timestamp request_timestamp; 200 struct TALER_Amount amount; 201 struct TALER_MasterSignatureP master_sig; 202 203 (void) cls; 204 task = NULL; 205 if (GNUNET_OK != 206 TALER_EXCHANGEDB_start (pg, 207 "run drain")) 208 { 209 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 210 "Failed to start database transaction!\n"); 211 global_ret = EXIT_FAILURE; 212 GNUNET_SCHEDULER_shutdown (); 213 return; 214 } 215 qs = TALER_EXCHANGEDB_profit_drains_get_pending (pg, 216 &serial, 217 &wtid, 218 &account_section, 219 &payto_uri, 220 &request_timestamp, 221 &amount, 222 &master_sig); 223 switch (qs) 224 { 225 case GNUNET_DB_STATUS_HARD_ERROR: 226 TALER_EXCHANGEDB_rollback (pg); 227 GNUNET_break (0); 228 global_ret = EXIT_FAILURE; 229 GNUNET_SCHEDULER_shutdown (); 230 return; 231 case GNUNET_DB_STATUS_SOFT_ERROR: 232 TALER_EXCHANGEDB_rollback (pg); 233 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 234 "Serialization failure on simple SELECT!?\n"); 235 global_ret = EXIT_FAILURE; 236 GNUNET_SCHEDULER_shutdown (); 237 return; 238 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 239 /* no profit drains, finished */ 240 TALER_EXCHANGEDB_rollback (pg); 241 GNUNET_assert (NULL == task); 242 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 243 "No profit drains pending. Exiting.\n"); 244 GNUNET_SCHEDULER_shutdown (); 245 return; 246 default: 247 /* continued below */ 248 break; 249 } 250 /* Check signature (again, this is a critical operation!) */ 251 if (GNUNET_OK != 252 TALER_exchange_offline_profit_drain_verify ( 253 &wtid, 254 request_timestamp, 255 &amount, 256 account_section, 257 payto_uri, 258 &master_pub, 259 &master_sig)) 260 { 261 GNUNET_break (0); 262 global_ret = EXIT_FAILURE; 263 TALER_EXCHANGEDB_rollback (pg); 264 GNUNET_assert (NULL == task); 265 GNUNET_SCHEDULER_shutdown (); 266 return; 267 } 268 269 /* Display data for manual human check */ 270 fprintf (stdout, 271 "Critical operation. MANUAL CHECK REQUIRED.\n"); 272 fprintf (stdout, 273 "We will wire %s to `%s'\n based on instructions from %s.\n", 274 TALER_amount2s (&amount), 275 payto_uri.full_payto, 276 GNUNET_TIME_timestamp2s (request_timestamp)); 277 fprintf (stdout, 278 "Press ENTER to confirm, CTRL-D to abort.\n"); 279 while (1) 280 { 281 int key; 282 283 key = getchar (); 284 if (EOF == key) 285 { 286 fprintf (stdout, 287 "Transfer aborted.\n" 288 "Re-run 'taler-exchange-drain' to try it again.\n" 289 "Contact Taler Systems SA to cancel it for good.\n" 290 "Exiting.\n"); 291 TALER_EXCHANGEDB_rollback (pg); 292 GNUNET_free (payto_uri.full_payto); 293 GNUNET_assert (NULL == task); 294 GNUNET_SCHEDULER_shutdown (); 295 global_ret = EXIT_FAILURE; 296 return; 297 } 298 if ('\n' == key) 299 break; 300 } 301 302 /* Note: account_section ignored for now, we 303 might want to use it here in the future... */ 304 (void) account_section; 305 { 306 char *method; 307 void *buf; 308 size_t buf_size; 309 310 TALER_BANK_prepare_transfer (payto_uri, 311 &amount, 312 exchange_base_url, 313 &wtid, 314 NULL, /* no extra metadata */ 315 &buf, 316 &buf_size); 317 method = TALER_payto_get_method (payto_uri.full_payto); 318 qs = TALER_EXCHANGEDB_wire_prepare_data_insert (pg, 319 method, 320 buf, 321 buf_size); 322 GNUNET_free (method); 323 GNUNET_free (buf); 324 } 325 GNUNET_free (payto_uri.full_payto); 326 qs = TALER_EXCHANGEDB_profit_drains_set_finished (pg, 327 serial); 328 switch (qs) 329 { 330 case GNUNET_DB_STATUS_HARD_ERROR: 331 TALER_EXCHANGEDB_rollback (pg); 332 GNUNET_break (0); 333 global_ret = EXIT_FAILURE; 334 GNUNET_SCHEDULER_shutdown (); 335 return; 336 case GNUNET_DB_STATUS_SOFT_ERROR: 337 TALER_EXCHANGEDB_rollback (pg); 338 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 339 "Failed: database serialization issue\n"); 340 global_ret = EXIT_FAILURE; 341 GNUNET_SCHEDULER_shutdown (); 342 return; 343 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 344 TALER_EXCHANGEDB_rollback (pg); 345 GNUNET_assert (NULL == task); 346 GNUNET_break (0); 347 GNUNET_SCHEDULER_shutdown (); 348 return; 349 default: 350 /* continued below */ 351 break; 352 } 353 /* commit transaction + report success + exit */ 354 if (0 >= commit_or_warn ()) 355 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 356 "Profit drain triggered. Exiting.\n"); 357 GNUNET_SCHEDULER_shutdown (); 358 } 359 360 361 /** 362 * First task. 363 * 364 * @param cls closure, NULL 365 * @param args remaining command-line arguments 366 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 367 * @param c configuration 368 */ 369 static void 370 run (void *cls, 371 char *const *args, 372 const char *cfgfile, 373 const struct GNUNET_CONFIGURATION_Handle *c) 374 { 375 (void) cls; 376 (void) args; 377 (void) cfgfile; 378 379 cfg = c; 380 if (GNUNET_OK != parse_drain_config ()) 381 { 382 cfg = NULL; 383 global_ret = EXIT_NOTCONFIGURED; 384 return; 385 } 386 if (GNUNET_SYSERR == 387 TALER_EXCHANGEDB_preflight (pg)) 388 { 389 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 390 "Failed to obtain database connection!\n"); 391 global_ret = EXIT_FAILURE; 392 GNUNET_SCHEDULER_shutdown (); 393 return; 394 } 395 GNUNET_assert (NULL == task); 396 task = GNUNET_SCHEDULER_add_now (&run_drain, 397 NULL); 398 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 399 cls); 400 } 401 402 403 /** 404 * The main function of the taler-exchange-drain. 405 * 406 * @param argc number of arguments from the command line 407 * @param argv command line arguments 408 * @return 0 ok, 1 on error 409 */ 410 int 411 main (int argc, 412 char *const *argv) 413 { 414 struct GNUNET_GETOPT_CommandLineOption options[] = { 415 GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION), 416 GNUNET_GETOPT_OPTION_END 417 }; 418 enum GNUNET_GenericReturnValue ret; 419 420 ret = GNUNET_PROGRAM_run ( 421 TALER_EXCHANGE_project_data (), 422 argc, argv, 423 "taler-exchange-drain", 424 gettext_noop ( 425 "process that executes a single profit drain"), 426 options, 427 &run, NULL); 428 if (GNUNET_SYSERR == ret) 429 return EXIT_INVALIDARGUMENT; 430 if (GNUNET_NO == ret) 431 return EXIT_SUCCESS; 432 return global_ret; 433 } 434 435 436 /* end of taler-exchange-drain.c */