taler-exchange-httpd_get-reserves-RESERVE_PUB.c (8139B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2024 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-httpd_get-reserves-RESERVE_PUB.c 18 * @brief Handle /reserves/$RESERVE_PUB GET requests 19 * @author Florian Dold 20 * @author Benedikt Mueller 21 * @author Christian Grothoff 22 */ 23 #include "platform.h" /* UNNECESSARY? */ 24 #include <gnunet/gnunet_util_lib.h> 25 #include <jansson.h> 26 #include "taler/taler_mhd_lib.h" 27 #include "taler/taler_json_lib.h" 28 #include "taler/taler_dbevents.h" /* UNNECESSARY? */ 29 #include "taler-exchange-httpd_get-keys.h" 30 #include "taler-exchange-httpd_get-reserves-RESERVE_PUB.h" 31 #include "taler-exchange-httpd_responses.h" 32 #include "exchange-database/get_reserve_balance.h" 33 #include "exchange-database/event_listen.h" 34 #include "exchange-database/event_listen_cancel.h" 35 36 37 /** 38 * Reserve GET request that is long-polling. 39 */ 40 struct ReservePoller 41 { 42 /** 43 * Kept in a DLL. 44 */ 45 struct ReservePoller *next; 46 47 /** 48 * Kept in a DLL. 49 */ 50 struct ReservePoller *prev; 51 52 /** 53 * Connection we are handling. 54 */ 55 struct MHD_Connection *connection; 56 57 /** 58 * Our request context. 59 */ 60 struct TEH_RequestContext *rc; 61 62 /** 63 * Subscription for the database event we are waiting for. 64 */ 65 struct GNUNET_DB_EventHandler *eh; 66 67 /** 68 * When will this request time out? 69 */ 70 struct GNUNET_TIME_Absolute timeout; 71 72 /** 73 * Public key of the reserve the inquiry is about. 74 */ 75 struct TALER_ReservePublicKeyP reserve_pub; 76 77 /** 78 * Balance of the reserve, set in the callback. 79 */ 80 struct TALER_Amount balance; 81 82 /** 83 * Last origin account of the reserve, NULL if only 84 * P2P payments were made. 85 */ 86 struct TALER_FullPayto origin_account; 87 88 /** 89 * True if we are still suspended. 90 */ 91 bool suspended; 92 93 }; 94 95 96 /** 97 * Head of list of requests in long polling. 98 */ 99 static struct ReservePoller *rp_head; 100 101 /** 102 * Tail of list of requests in long polling. 103 */ 104 static struct ReservePoller *rp_tail; 105 106 107 void 108 TEH_reserves_get_cleanup () 109 { 110 for (struct ReservePoller *rp = rp_head; 111 NULL != rp; 112 rp = rp->next) 113 { 114 if (rp->suspended) 115 { 116 rp->suspended = false; 117 MHD_resume_connection (rp->connection); 118 } 119 } 120 } 121 122 123 /** 124 * Function called once a connection is done to 125 * clean up the `struct ReservePoller` state. 126 * 127 * @param rc context to clean up for 128 */ 129 static void 130 rp_cleanup (struct TEH_RequestContext *rc) 131 { 132 struct ReservePoller *rp = rc->rh_ctx; 133 134 GNUNET_assert (! rp->suspended); 135 if (NULL != rp->eh) 136 { 137 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 138 "Cancelling DB event listening on cleanup (odd unless during shutdown)\n"); 139 TALER_EXCHANGEDB_event_listen_cancel (TEH_pg, 140 rp->eh); 141 rp->eh = NULL; 142 } 143 GNUNET_CONTAINER_DLL_remove (rp_head, 144 rp_tail, 145 rp); 146 GNUNET_free (rp->origin_account.full_payto); 147 GNUNET_free (rp); 148 } 149 150 151 /** 152 * Function called on events received from Postgres. 153 * Wakes up long pollers. 154 * 155 * @param cls the `struct TEH_RequestContext *` 156 * @param extra additional event data provided 157 * @param extra_size number of bytes in @a extra 158 */ 159 static void 160 db_event_cb (void *cls, 161 const void *extra, 162 size_t extra_size) 163 { 164 struct ReservePoller *rp = cls; 165 struct GNUNET_AsyncScopeSave old_scope; 166 167 (void) extra; 168 (void) extra_size; 169 if (! rp->suspended) 170 return; /* might get multiple wake-up events */ 171 GNUNET_async_scope_enter (&rp->rc->async_scope_id, 172 &old_scope); 173 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 174 "Resuming from long-polling on KYC status\n"); 175 TEH_check_invariants (); 176 rp->suspended = false; 177 MHD_resume_connection (rp->connection); 178 TALER_MHD_daemon_trigger (); 179 TEH_check_invariants (); 180 GNUNET_async_scope_restore (&old_scope); 181 } 182 183 184 enum MHD_Result 185 TEH_handler_reserves_get ( 186 struct TEH_RequestContext *rc, 187 const struct TALER_ReservePublicKeyP *reserve_pub) 188 { 189 struct ReservePoller *rp = rc->rh_ctx; 190 191 if (NULL == rp) 192 { 193 rp = GNUNET_new (struct ReservePoller); 194 rp->connection = rc->connection; 195 rp->rc = rc; 196 rc->rh_ctx = rp; 197 rc->rh_cleaner = &rp_cleanup; 198 GNUNET_CONTAINER_DLL_insert (rp_head, 199 rp_tail, 200 rp); 201 rp->reserve_pub = *reserve_pub; 202 TALER_MHD_parse_request_timeout (rc->connection, 203 &rp->timeout); 204 } 205 206 if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) && 207 (NULL == rp->eh) ) 208 { 209 struct TALER_EXCHANGEDB_ReserveEventP rep = { 210 .header.size = htons (sizeof (rep)), 211 .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING), 212 .reserve_pub = rp->reserve_pub 213 }; 214 215 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 216 "Starting DB event listening until %s\n", 217 GNUNET_TIME_absolute2s (rp->timeout)); 218 rp->eh = TALER_EXCHANGEDB_event_listen ( 219 TEH_pg, 220 GNUNET_TIME_absolute_get_remaining (rp->timeout), 221 &rep.header, 222 &db_event_cb, 223 rp); 224 } 225 { 226 enum GNUNET_DB_QueryStatus qs; 227 228 GNUNET_free (rp->origin_account.full_payto); 229 qs = TALER_EXCHANGEDB_get_reserve_balance (TEH_pg, 230 &rp->reserve_pub, 231 &rp->balance, 232 &rp->origin_account); 233 switch (qs) 234 { 235 case GNUNET_DB_STATUS_SOFT_ERROR: 236 GNUNET_break (0); /* single-shot query should never have soft-errors */ 237 return TALER_MHD_reply_with_error (rc->connection, 238 MHD_HTTP_INTERNAL_SERVER_ERROR, 239 TALER_EC_GENERIC_DB_SOFT_FAILURE, 240 "get_reserve_balance"); 241 case GNUNET_DB_STATUS_HARD_ERROR: 242 GNUNET_break (0); 243 return TALER_MHD_reply_with_error (rc->connection, 244 MHD_HTTP_INTERNAL_SERVER_ERROR, 245 TALER_EC_GENERIC_DB_FETCH_FAILED, 246 "get_reserve_balance"); 247 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 248 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 249 "Got reserve balance of %s\n", 250 TALER_amount2s (&rp->balance)); 251 return TALER_MHD_REPLY_JSON_PACK ( 252 rc->connection, 253 MHD_HTTP_OK, 254 GNUNET_JSON_pack_allow_null ( 255 GNUNET_JSON_pack_string ( 256 "last_origin", 257 rp->origin_account.full_payto)), 258 TALER_JSON_pack_amount ("balance", 259 &rp->balance)); 260 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 261 if (! GNUNET_TIME_absolute_is_future (rp->timeout)) 262 { 263 return TALER_MHD_reply_with_error (rc->connection, 264 MHD_HTTP_NOT_FOUND, 265 TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, 266 NULL); 267 } 268 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 269 "Long-polling on reserve for %s\n", 270 GNUNET_STRINGS_relative_time_to_string ( 271 GNUNET_TIME_absolute_get_remaining (rp->timeout), 272 true)); 273 rp->suspended = true; 274 MHD_suspend_connection (rc->connection); 275 return MHD_YES; 276 } 277 } 278 GNUNET_break (0); 279 return MHD_NO; 280 } 281 282 283 /* end of taler-exchange-httpd_reserves_get.c */