From 12736abcf7c8a63665e45a8d1691bd52bf940e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Mon, 3 Oct 2022 08:15:07 +0200 Subject: [PATCH] WiP: parsing and replay of auction transcripts POST handler of auction_brandt extension now parses JSON transcript and passes JSON via stdin to external program. Reads result via stdout. TODOs: - check signatures in transcript - parse JSON output of replay progam - add configuration to to auction_brandt: - types of auctions - max. concurrent users - max. number of prices - maximum price - fees - add timeout handler for replay program execution - (maybe) sign output of exchange by master-key - (maybe) add GET handler with information re: auction_brandt? --- .../auction_brandt/extension_auction_brandt.c | 162 ++++++++++++++++-- src/testing/test_exchange_auction.conf | 159 +++++++++++++++++ 2 files changed, 302 insertions(+), 19 deletions(-) create mode 100644 src/testing/test_exchange_auction.conf diff --git a/src/extensions/auction_brandt/extension_auction_brandt.c b/src/extensions/auction_brandt/extension_auction_brandt.c index a5f3af8c1..24ac452bc 100644 --- a/src/extensions/auction_brandt/extension_auction_brandt.c +++ b/src/extensions/auction_brandt/extension_auction_brandt.c @@ -27,6 +27,8 @@ #include #define AUCTION_BRANDT "auction_brandt" +#define LOG_PREFIX "[auction_brandt] " +#define MAX_RESULT_SIZE 10 * 1024 /* Path to the replay program. */ static char *replay_program; @@ -77,7 +79,7 @@ json_error (json_t **output, GNUNET_assert (*output); GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "[auction_brandt] got error: %s\n", + LOG_PREFIX "got error: %s\n", error); return GNUNET_SYSERR; @@ -86,6 +88,7 @@ json_error (json_t **output, /** * @brief returns an JSON with the result */ +#if 0 static enum GNUNET_GenericReturnValue json_result (json_t **output, const struct transcript *tr) @@ -119,6 +122,9 @@ json_result (json_t **output, } +#endif + + /* * @brief Parses a given json as transcript. * @@ -126,13 +132,16 @@ json_result (json_t **output, * @param[out] transcript Parsed transcript data * @param[out] jresult JSON output, both, for results or errors * @return GNUNET_OK on succes + * + * TODO: + * - fix leakages + * - parse and verify signatures */ static enum GNUNET_GenericReturnValue parse_transcript (const json_t *jtr, struct transcript *tr, json_t **output) { - // TODO: json_error_t jerror; // TODO: struct GNUNET_CRYPTO_EddsaSignature sig; GNUNET_assert (jtr); @@ -167,7 +176,7 @@ parse_transcript (const json_t *jtr, if (! json_is_array (prices)) - // TODO: leak!? + // TODO: fix leak!? return json_error (output, "no prices found"); tr->k = json_array_size (prices); @@ -176,7 +185,7 @@ parse_transcript (const json_t *jtr, json_array_foreach (prices, idx, val) { if (! json_is_string (val)) - // TODO: leak!_ + // TODO: fix leak!_ return json_error (output, "prices not strings"); tr->prices[idx] = (char *) json_string_value (val); @@ -192,7 +201,7 @@ parse_transcript (const json_t *jtr, bidders = json_object_get (jtr, "bidders"); if (! bidders || ! json_is_array (bidders)) - // TODO: leak!_ + // TODO: fix leak!_ return json_error (output, "bidders not found"); // TODO: parse bidders as pub keys; @@ -207,14 +216,14 @@ parse_transcript (const json_t *jtr, messages = json_object_get (jtr, "transcript"); if (! json_is_array (messages)) - // TODO: leak! + // TODO: fix leak! return json_error (output, "no messages found"); nm = json_array_size (messages); if (nm != (4 * tr->n)) - // TODO: leak! + // TODO: fix leak! return json_error (output, "not the right no. of messages found"); } @@ -229,7 +238,8 @@ parse_transcript (const json_t *jtr, if (! json_is_array (winners)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "winners not provided, continuing without\n"); + LOG_PREFIX "winners not provided, continuing without\n"); + // TODO: fix leakage goto CONT; } @@ -255,13 +265,118 @@ parse_transcript (const json_t *jtr, spec, (const char**) &error, NULL)) - // TODO: leak! + // TODO: fix leak! return json_error (output, "couldn't parse winners"); } CONT: + // TODO: fix leakages } - return json_result (output, tr); + *output = NULL; + return GNUNET_OK; +} + + +/** + * @brief replay an auction using the external program + * + * @param[in] root The original JSON transcript + * @param[in] transcript The transcript object parsed so far + * @param[out] result The JSON result from the program + * @return GNUNET_OK on success + * + * TODO: Make this asynchronous (giving out a replay-ID to poll from)? + */ +static enum GNUNET_GenericReturnValue +replay_transcript (const json_t*root, + struct transcript *tr, + json_t **result) +{ + struct GNUNET_DISK_PipeHandle *pi; + struct GNUNET_DISK_PipeHandle *po; + const struct GNUNET_DISK_FileHandle *fd; + struct GNUNET_OS_Process *proc; + + pi = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE); + po = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ); + proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, + pi, po, NULL, + replay_program, + replay_program, + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + LOG_PREFIX "couldn't create auction replay program '%s'\n", + replay_program); + + return json_error (result, "internal error"); + } + + // Write original transcript JSON to stdin + { + ssize_t sz; + char *str; + size_t str_len; + + + fd = GNUNET_DISK_pipe_handle (pi, GNUNET_DISK_PIPE_END_WRITE); + str = json_dumps (root, JSON_COMPACT); + str_len = strlen (str); + sz = GNUNET_DISK_file_write (fd, + str, + str_len); + free (str); + if (sz != str_len) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + LOG_PREFIX "couldn't write all data to replay_program\n"); + } + } + + // Read output from stdout + { + ssize_t sz; + char buf[MAX_RESULT_SIZE]; + fd = GNUNET_DISK_pipe_handle (po, GNUNET_DISK_PIPE_END_READ); + + sz = GNUNET_DISK_file_read (fd, + buf, + sizeof(buf)); + if (GNUNET_SYSERR == sz) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + LOG_PREFIX "couldn't read data from replay_program\n"); + } + else + { + json_error_t error; + json_t *res; + res = json_loads (buf, + JSON_DECODE_ANY, + &error); + + if (! res) + return json_error (result, error.text); + + *result = json_copy (res); + json_decref (res); + } + } + + if (GNUNET_OK != GNUNET_OS_process_wait (proc)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + LOG_PREFIX "error while launching auction replay program '%s'\n", + replay_program); + + json_object_clear (*result); + return json_error (result, "internal error"); + } + + + // TODO: parse result + return GNUNET_OK; } @@ -338,7 +453,7 @@ auction_http_get_handler ( /* TODO: return some meta-data about supported version, limits, etc.*/ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "auction_http_get_handler not implemented yet\n"); + LOG_PREFIX "auction_http_get_handler not implemented yet\n"); return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_IMPLEMENTED, @@ -359,17 +474,26 @@ auction_http_post_handler ( { struct transcript tr = {}; enum GNUNET_GenericReturnValue ret; - json_t *result; + json_t *result1; + json_t *result2; - ret = parse_transcript (root, &tr, &result); + ret = parse_transcript (root, + &tr, + &result1); + if (GNUNET_OK != ret) + return TALER_MHD_reply_json_steal (connection, + result1, + MHD_HTTP_BAD_REQUEST); + GNUNET_assert (NULL == result1); - /* TODO */ - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "auction_http_post_handler not implemented yet\n"); + ret = replay_transcript (root, + &tr, + &result2); return TALER_MHD_reply_json_steal (connection, - result, - GNUNET_OK == ret? MHD_HTTP_OK : + result2, + GNUNET_OK == ret? + MHD_HTTP_OK : MHD_HTTP_BAD_REQUEST); } @@ -427,7 +551,7 @@ libtaler_extension_auction_brandt_init (void *arg) } /* TODO: check if replay_program is actually executable */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "[auction_brandt] loading... using replay_program '%s'\n", + LOG_PREFIX "loading... using replay_program '%s'\n", replay_program); return &TE_auction_brandt; diff --git a/src/testing/test_exchange_auction.conf b/src/testing/test_exchange_auction.conf new file mode 100644 index 000000000..ee5ef7bc5 --- /dev/null +++ b/src/testing/test_exchange_auction.conf @@ -0,0 +1,159 @@ +# This file is in the public domain. +# + +[PATHS] +# Persistent data storage for the testcase +TALER_TEST_HOME = test_exchange_api_home/ +TALER_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/${USER:-}/taler-system-runtime/ + +[taler-exchange-secmod-rsa] +# Reduce from 1 year to speed up test +LOOKAHEAD_SIGN = 24 days + +[taler-exchange-secmod-eddsa] +# Reduce from 1 year to speed up test +LOOKAHEAD_SIGN = 24 days +# Reduce from 12 weeks to ensure we have multiple +DURATION = 14 days + +[taler] +# Currency supported by the exchange (can only be one) +CURRENCY = EUR +CURRENCY_ROUND_UNIT = EUR:0.01 + +[auditor] +BASE_URL = "http://localhost:8083/" + +# HTTP port the auditor listens to +PORT = 8083 + +[exchange] + +TERMS_ETAG = 0 +PRIVACY_ETAG = 0 + +# HTTP port the exchange listens to +PORT = 8081 + +# Master public key used to sign the exchange's various keys +MASTER_PUBLIC_KEY = 98NJW3CQHZQGQXTY3K85K531XKPAPAVV4Q5V8PYYRR00NJGZWNVG + +# How to access our database +DB = postgres + +# Base URL of the exchange. Must be set to a URL where the +# exchange (or the twister) is actually listening. +BASE_URL = "http://localhost:8081/" + +# How big is an individual shard to be processed +# by taler-exchange-expire (in time). It may take +# this much time for an expired purse to be really +# cleaned up and the coins refunded. +EXPIRE_SHARD_SIZE = 300 ms + +EXPIRE_IDLE_SLEEP_INTERVAL = 1 s + + +[exchangedb-postgres] +CONFIG = "postgres:///talercheck" + +[auditordb-postgres] +CONFIG = "postgres:///talercheck" + +# Sections starting with "exchange-account-" configure the bank accounts +# of the exchange. The "URL" specifies the account in +# payto://-format. +[exchange-account-1] +# What is the URL of our account? +PAYTO_URI = "payto://x-taler-bank/localhost/42?receiver-name=42" +# ENABLE_CREDIT = YES + +[exchange-accountcredentials-1] +WIRE_GATEWAY_URL = "http://localhost:9081/42/" + +[exchange-account-2] +# What is the bank account (with the "Taler Bank" demo system)? +PAYTO_URI = "payto://x-taler-bank/localhost/2?receiver-name=2" +ENABLE_DEBIT = YES +ENABLE_CREDIT = YES + +[exchange-accountcredentials-2] +WIRE_GATEWAY_AUTH_METHOD = basic +USERNAME = Exchange +PASSWORD = x +WIRE_GATEWAY_URL = "http://localhost:9081/2/" + +[bank] +HTTP_PORT = 9081 + +# Enabled extensions +#[exchange-extension-age_restriction] +#ENABLED = YES +# default age groups: +#AGE_GROUPS = "8:10:12:14:16:18:21" + +[exchange-extension-auction_brandt] +ENABLED = YES +REPLAY_PROGRAM = "/usr/local/bin/taler-exchange-auction_brandt-replay" + +# Sections starting with "coin_" specify which denominations +# the exchange should support (and their respective fee structure) +[coin_eur_ct_1] +value = EUR:0.01 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.00 +fee_deposit = EUR:0.00 +fee_refresh = EUR:0.01 +fee_refund = EUR:0.01 +CIPHER = RSA +rsa_keysize = 1024 + +[coin_eur_ct_10] +value = EUR:0.10 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +CIPHER = RSA +rsa_keysize = 1024 + +[coin_eur_1] +value = EUR:1 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +CIPHER = RSA +rsa_keysize = 1024 + +[coin_eur_5] +value = EUR:5 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +CIPHER = RSA +rsa_keysize = 1024 + +[coin_eur_10] +value = EUR:10 +duration_withdraw = 7 days +duration_spend = 2 years +duration_legal = 3 years +fee_withdraw = EUR:0.01 +fee_deposit = EUR:0.01 +fee_refresh = EUR:0.03 +fee_refund = EUR:0.01 +CIPHER = RSA +rsa_keysize = 1024