diff options
| -rw-r--r-- | src/exchange/taler-exchange-httpd_purses_merge.c | 2 | ||||
| -rw-r--r-- | src/exchange/taler-exchange-httpd_responses.c | 2 | ||||
| -rw-r--r-- | src/exchangedb/plugin_exchangedb_postgres.c | 2 | ||||
| -rw-r--r-- | src/lib/exchange_api_purse_deposit.c | 332 | ||||
| -rw-r--r-- | src/lib/exchange_api_purse_merge.c | 2 | ||||
| -rw-r--r-- | src/testing/test_exchange_p2p.c | 32 | 
6 files changed, 302 insertions, 70 deletions
| diff --git a/src/exchange/taler-exchange-httpd_purses_merge.c b/src/exchange/taler-exchange-httpd_purses_merge.c index 9ca973d1..397492d0 100644 --- a/src/exchange/taler-exchange-httpd_purses_merge.c +++ b/src/exchange/taler-exchange-httpd_purses_merge.c @@ -275,7 +275,7 @@ merge_transaction (void *cls,                                           &merge_timestamp,                                           &partner_url,                                           &reserve_pub); -    if (qs < 0) +    if (qs <= 0)      {        if (GNUNET_DB_STATUS_SOFT_ERROR == qs)          return qs; diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c index 725e08d9..e2b33c79 100644 --- a/src/exchange/taler-exchange-httpd_responses.c +++ b/src/exchange/taler-exchange-httpd_responses.c @@ -566,6 +566,8 @@ TEH_RESPONSE_reply_coin_insufficient_funds (      connection,      TALER_ErrorCode_get_http_status_safe (ec),      TALER_JSON_pack_ec (ec), +    GNUNET_JSON_pack_data_auto ("coin_pub", +                                coin_pub),      GNUNET_JSON_pack_array_steal ("history",                                    history));  } diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index e6b86813..4d5efb9c 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -3620,7 +3620,7 @@ prepare_statements (struct PostgresClosure *pg)        ",merge_timestamp"        ",partner_base_url"        " FROM purse_merges" -      " JOIN partners USING (partner_serial_id)" +      " LEFT JOIN partners USING (partner_serial_id)"        " WHERE purse_pub=$1;",        1),      /* Used in #postgres_do_account_merge() */ diff --git a/src/lib/exchange_api_purse_deposit.c b/src/lib/exchange_api_purse_deposit.c index f71bb389..67f5355d 100644 --- a/src/lib/exchange_api_purse_deposit.c +++ b/src/lib/exchange_api_purse_deposit.c @@ -34,6 +34,28 @@  /** + * Information we track per coin. + */ +struct Coin +{ +  /** +   * Coin's public key. +   */ +  struct TALER_CoinSpendPublicKeyP coin_pub; + +  /** +   * Coin's denomination. +   */ +  struct TALER_DenominationHashP h_denom_pub; + +  /** +   * How much did we say the coin contributed. +   */ +  struct TALER_Amount contribution; +}; + + +/**   * @brief A purse create with deposit handle   */  struct TALER_EXCHANGE_PurseDepositHandle @@ -83,7 +105,7 @@ struct TALER_EXCHANGE_PurseDepositHandle    /**     * Array of @e num_deposits coins we are depositing.     */ -  struct TALER_CoinSpendPublicKeyP *coins; +  struct Coin *coins;    /**     * Number of coins we are depositing. @@ -122,7 +144,6 @@ handle_purse_deposit_finished (void *cls,      break;    case MHD_HTTP_OK:      { -      const struct TALER_EXCHANGE_Keys *key_state;        struct GNUNET_TIME_Timestamp etime;        struct TALER_ExchangeSignatureP exchange_sig;        struct TALER_ExchangePublicKeyP exchange_pub; @@ -158,9 +179,8 @@ handle_purse_deposit_finished (void *cls,          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;          break;        } -      key_state = TALER_EXCHANGE_get_keys (pch->exchange);        if (GNUNET_OK != -          TALER_EXCHANGE_test_signing_key (key_state, +          TALER_EXCHANGE_test_signing_key (keys,                                             &exchange_pub))        {          GNUNET_break_op (0); @@ -191,85 +211,260 @@ handle_purse_deposit_finished (void *cls,      /* This should never happen, either us or the exchange is buggy         (or API version conflict); just pass JSON reply to the application */      dr.hr.ec = TALER_JSON_get_error_code (j); -    dr.hr.hint = TALER_JSON_get_error_hint (j);      break;    case MHD_HTTP_FORBIDDEN:      dr.hr.ec = TALER_JSON_get_error_code (j); -    dr.hr.hint = TALER_JSON_get_error_hint (j);      /* Nothing really to verify, exchange says one of the signatures is         invalid; as we checked them, this should never happen, we         should pass the JSON reply to the application */      break;    case MHD_HTTP_NOT_FOUND:      dr.hr.ec = TALER_JSON_get_error_code (j); -    dr.hr.hint = TALER_JSON_get_error_hint (j);      /* Nothing really to verify, this should never         happen, we should pass the JSON reply to the application */      break;    case MHD_HTTP_CONFLICT: +    dr.hr.ec = TALER_JSON_get_error_code (j); +    switch (dr.hr.ec)      { -      const char *partner_url = NULL; -      struct TALER_CoinSpendPublicKeyP coin_pub; -      struct TALER_CoinSpendSignatureP coin_sig; -      struct TALER_Amount amount; -      struct GNUNET_JSON_Specification spec[] = { -        GNUNET_JSON_spec_fixed_auto ("coin_sig", -                                     &coin_sig), -        GNUNET_JSON_spec_fixed_auto ("coin_pub", -                                     &coin_pub), -        GNUNET_JSON_spec_mark_optional ( -          GNUNET_JSON_spec_string ("partner_url", -                                   &partner_url), -          NULL), -        TALER_JSON_spec_amount ("amount", -                                keys->currency, -                                &amount), -        GNUNET_JSON_spec_end () -      }; -      bool found = false; - -      if (GNUNET_OK != -          GNUNET_JSON_parse (j, -                             spec, -                             NULL, NULL)) +    case TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA:        { -        GNUNET_break_op (0); -        dr.hr.http_status = 0; -        dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +        const char *partner_url = NULL; +        struct TALER_CoinSpendPublicKeyP coin_pub; +        struct TALER_CoinSpendSignatureP coin_sig; +        struct TALER_Amount amount; +        struct GNUNET_JSON_Specification spec[] = { +          GNUNET_JSON_spec_fixed_auto ("coin_sig", +                                       &coin_sig), +          GNUNET_JSON_spec_fixed_auto ("coin_pub", +                                       &coin_pub), +          GNUNET_JSON_spec_mark_optional ( +            GNUNET_JSON_spec_string ("partner_url", +                                     &partner_url), +            NULL), +          TALER_JSON_spec_amount ("amount", +                                  keys->currency, +                                  &amount), +          GNUNET_JSON_spec_end () +        }; +        bool found = false; + +        if (GNUNET_OK != +            GNUNET_JSON_parse (j, +                               spec, +                               NULL, NULL)) +        { +          GNUNET_break_op (0); +          dr.hr.http_status = 0; +          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +          break; +        } +        for (unsigned int i = 0; i<pch->num_deposits; i++) +          if (0 == GNUNET_memcmp (&coin_pub, +                                  &pch->coins[i].coin_pub)) +          { +            found = true; +            break; +          } +        if (! found) +        { +          /* proof is about a coin we did not even deposit */ +          GNUNET_break_op (0); +          dr.hr.http_status = 0; +          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +          break; +        } +        if (NULL == partner_url) +          partner_url = pch->base_url; +        if (GNUNET_OK != +            TALER_wallet_purse_deposit_verify ( +              partner_url, +              &pch->purse_pub, +              &amount, +              &coin_pub, +              &coin_sig)) +        { +          GNUNET_break_op (0); +          dr.hr.http_status = 0; +          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +          break; +        } +        /* meta data conflict is real! */          break;        } -      for (unsigned int i = 0; i<pch->num_deposits; i++) -        if (0 == GNUNET_memcmp (&coin_pub, -                                &pch->coins[i])) +    case TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS: +      { +        json_t *history; +        struct TALER_Amount total; +        struct TALER_DenominationHashP h_denom_pub; +        const struct TALER_EXCHANGE_DenomPublicKey *dki; +        struct TALER_CoinSpendPublicKeyP coin_pub; +        struct GNUNET_JSON_Specification spec[] = { +          GNUNET_JSON_spec_fixed_auto ("coin_pub", +                                       &coin_pub), +          GNUNET_JSON_spec_json ("history", +                                 &history), +          GNUNET_JSON_spec_end () +        }; +        bool found = false; +        const struct Coin *my_coin; + +        if (GNUNET_OK != +            GNUNET_JSON_parse (j, +                               spec, +                               NULL, NULL))          { -          found = true; +          GNUNET_break_op (0); +          dr.hr.http_status = 0; +          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;            break;          } -      if (! found) -      { -        /* proof is about a coin we did not even deposit */ -        GNUNET_break_op (0); -        dr.hr.http_status = 0; -        dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; -        break; +        for (unsigned int i = 0; i<pch->num_deposits; i++) +        { +          if (0 == GNUNET_memcmp (&coin_pub, +                                  &pch->coins[i].coin_pub)) +          { +            found = true; +            my_coin = &pch->coins[i]; +            break; +          } +        } +        if (! found) +        { +          /* proof is about a coin we did not even deposit */ +          GNUNET_break_op (0); +          dr.hr.http_status = 0; +          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +          break; +        } +        dki = TALER_EXCHANGE_get_denomination_key_by_hash ( +          keys, +          &my_coin->h_denom_pub); +        if (NULL == dki) +        { +          dr.hr.http_status = 0; +          dr.hr.ec = TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN; +          GNUNET_break_op (0); +          break; +        } +        if (GNUNET_OK != +            TALER_EXCHANGE_verify_coin_history (dki, +                                                dki->value.currency, +                                                &coin_pub, +                                                history, +                                                &h_denom_pub, +                                                &total)) +        { +          GNUNET_break_op (0); +          dr.hr.http_status = 0; +          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +          json_decref (history); +          break; +        } +        json_decref (history); +        if (0 > +            TALER_amount_add (&total, +                              &total, +                              &my_coin->contribution)) +        { +          /* clearly not OK if our transaction would have caused +             the overflow... */ +          break; +        } +        if (0 >= TALER_amount_cmp (&total, +                                   &dki->value)) +        { +          /* transaction should have still fit */ +          GNUNET_break (0); +          dr.hr.http_status = 0; +          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +          break; +        } +        /* everything OK, proof of double-spending was provided */        } -      if (NULL == partner_url) -        partner_url = pch->base_url; -      if (GNUNET_OK != -          TALER_wallet_purse_deposit_verify ( -            partner_url, -            &pch->purse_pub, -            &amount, -            &coin_pub, -            &coin_sig)) +    case TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY:        { -        GNUNET_break_op (0); -        dr.hr.http_status = 0; -        dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; -        break; +        json_t *history; +        struct TALER_Amount total; +        struct TALER_DenominationHashP h_denom_pub; +        const struct Coin *my_coin; +        const struct TALER_EXCHANGE_DenomPublicKey *dki; +        struct TALER_CoinSpendPublicKeyP coin_pub; +        struct GNUNET_JSON_Specification spec[] = { +          GNUNET_JSON_spec_fixed_auto ("coin_pub", +                                       &coin_pub), +          GNUNET_JSON_spec_json ("history", +                                 &history), +          GNUNET_JSON_spec_end () +        }; +        bool found = false; + +        if (GNUNET_OK != +            GNUNET_JSON_parse (j, +                               spec, +                               NULL, NULL)) +        { +          GNUNET_break_op (0); +          dr.hr.http_status = 0; +          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +          break; +        } +        for (unsigned int i = 0; i<pch->num_deposits; i++) +        { +          if (0 == GNUNET_memcmp (&coin_pub, +                                  &pch->coins[i].coin_pub)) +          { +            found = true; +            my_coin = &pch->coins[i]; +            break; +          } +        } +        if (! found) +        { +          /* proof is about a coin we did not even deposit */ +          GNUNET_break_op (0); +          dr.hr.http_status = 0; +          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +          break; +        } +        dki = TALER_EXCHANGE_get_denomination_key_by_hash ( +          keys, +          &my_coin->h_denom_pub); +        memset (&h_denom_pub, +                0, +                sizeof (h_denom_pub)); +        if (GNUNET_OK != +            TALER_EXCHANGE_verify_coin_history (dki, +                                                dki->value.currency, +                                                &coin_pub, +                                                history, +                                                &h_denom_pub, +                                                &total)) +        { +          GNUNET_break_op (0); +          dr.hr.http_status = 0; +          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +          json_decref (history); +          break; +        } +        json_decref (history); +        if (0 == GNUNET_memcmp (&dki->h_key, +                                &h_denom_pub)) +        { +          /* sorry, this proves nothing */ +          GNUNET_break_op (0); +          dr.hr.http_status = 0; +          dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +          break; +        } +        /* everything OK, proof of conflicting denomination was provided */        } -      /* conflict is real! */ -    } +    default: +      GNUNET_break_op (0); +      dr.hr.http_status = 0; +      dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; +      break; +    } /* ec switch */      break;    case MHD_HTTP_GONE:      /* could happen if denomination was revoked */ @@ -277,18 +472,15 @@ handle_purse_deposit_finished (void *cls,         signature here, alas tricky in case our /keys         is outdated => left to clients */      dr.hr.ec = TALER_JSON_get_error_code (j); -    dr.hr.hint = TALER_JSON_get_error_hint (j);      break;    case MHD_HTTP_INTERNAL_SERVER_ERROR:      dr.hr.ec = TALER_JSON_get_error_code (j); -    dr.hr.hint = TALER_JSON_get_error_hint (j);      /* Server had an internal issue; we should retry, but this API         leaves this to the application */      break;    default:      /* unexpected response code */      dr.hr.ec = TALER_JSON_get_error_code (j); -    dr.hr.hint = TALER_JSON_get_error_hint (j);      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,                  "Unexpected response code %u/%d for exchange deposit\n",                  (unsigned int) response_code, @@ -296,6 +488,10 @@ handle_purse_deposit_finished (void *cls,      GNUNET_break_op (0);      break;    } +  if (TALER_EC_NONE == dr.hr.ec) +    dr.hr.hint = NULL; +  else +    dr.hr.hint = TALER_ErrorCode_get_hint (dr.hr.ec);    pch->cb (pch->cb_cls,             &dr);    TALER_EXCHANGE_purse_deposit_cancel (pch); @@ -361,11 +557,11 @@ TALER_EXCHANGE_purse_deposit (                                      "/");    pch->num_deposits = num_deposits;    pch->coins = GNUNET_new_array (num_deposits, -                                 struct TALER_CoinSpendPublicKeyP); +                                 struct Coin);    for (unsigned int i = 0; i<num_deposits; i++)    {      const struct TALER_EXCHANGE_PurseDeposit *deposit = &deposits[i]; -    struct TALER_CoinSpendPublicKeyP *coin_pub = &pch->coins[i]; +    struct Coin *coin = &pch->coins[i];      json_t *jdeposit;      struct TALER_CoinSpendSignatureP coin_sig;  #if FIXME_OEC @@ -390,7 +586,9 @@ TALER_EXCHANGE_purse_deposit (      }  #endif      GNUNET_CRYPTO_eddsa_key_get_public (&deposit->coin_priv.eddsa_priv, -                                        &coin_pub->eddsa_pub); +                                        &coin->coin_pub.eddsa_pub); +    coin->h_denom_pub = deposit->h_denom_pub; +    coin->contribution = deposit->amount;      TALER_wallet_purse_deposit_sign (        pch->base_url,        &pch->purse_pub, @@ -413,7 +611,7 @@ TALER_EXCHANGE_purse_deposit (        TALER_JSON_pack_denom_sig ("ub_sig",                                   &deposit->denom_sig),        GNUNET_JSON_pack_data_auto ("coin_pub", -                                  coin_pub), +                                  &coin->coin_pub),        GNUNET_JSON_pack_data_auto ("coin_sig",                                    &coin_sig));      GNUNET_assert (0 == diff --git a/src/lib/exchange_api_purse_merge.c b/src/lib/exchange_api_purse_merge.c index 2becd946..a32b44d4 100644 --- a/src/lib/exchange_api_purse_merge.c +++ b/src/lib/exchange_api_purse_merge.c @@ -118,7 +118,7 @@ make_payto (const char *exchange_url,    end = GNUNET_STRINGS_data_to_string (      reserve_pub, -    sizeof (reserve_pub), +    sizeof (*reserve_pub),      pub_str,      sizeof (pub_str));    *end = '\0'; diff --git a/src/testing/test_exchange_p2p.c b/src/testing/test_exchange_p2p.c index e4d6f5b2..b3e98daa 100644 --- a/src/testing/test_exchange_p2p.c +++ b/src/testing/test_exchange_p2p.c @@ -112,6 +112,8 @@ run (void *cls,       */      CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1",                                "EUR:5.01"), +    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-2", +                              "EUR:5.01"),      TALER_TESTING_cmd_reserve_poll ("poll-reserve-1",                                      "create-reserve-1",                                      "EUR:5.01", @@ -122,6 +124,11 @@ run (void *cls,                                                   bc.user42_payto,                                                   bc.exchange_payto,                                                   "create-reserve-1"), +    TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-2", +                                                 "EUR:5.01", +                                                 bc.user42_payto, +                                                 bc.exchange_payto, +                                                 "create-reserve-2"),      /**       * Make a reserve exist, according to the previous       * transfer. @@ -193,6 +200,13 @@ run (void *cls,        "EUR:1",        MHD_HTTP_OK),  #endif +    /* Test conflicting merge */ +    TALER_TESTING_cmd_purse_merge ( +      "purse-merge-into-reserve", +      MHD_HTTP_CONFLICT, +      "push-get-contract", +      "create-reserve-2"), +      TALER_TESTING_cmd_end ()    };    struct TALER_TESTING_Command pull[] = { @@ -242,6 +256,24 @@ run (void *cls,        "EUR:2",        MHD_HTTP_OK),  #endif +    /* create 2nd purse for a deposit conflict */ +    TALER_TESTING_cmd_purse_create_with_reserve ( +      "purse-create-with-reserve-2", +      MHD_HTTP_OK, +      "{\"amount\":\"EUR:4\",\"summary\":\"beer\"}", +      true /* upload contract */, +      GNUNET_TIME_UNIT_MINUTES, /* expiration */ +      "create-reserve-1"), +#if FIXME_RESERVE_HISTORY +    TALER_TESTING_cmd_purse_deposit_coins ( +      "purse-deposit-coins-conflict", +      MHD_HTTP_CONFLICT, +      0 /* min age */, +      "purse-create-with-reserve-2", +      "withdraw-coin-1", +      "EUR:4.01", +      NULL), +#endif      TALER_TESTING_cmd_end ()    }; | 
