summaryrefslogtreecommitdiff
path: root/src/exchangedb
diff options
context:
space:
mode:
Diffstat (limited to 'src/exchangedb')
-rw-r--r--src/exchangedb/.gitignore20
-rw-r--r--src/exchangedb/0002-account_merges.sql139
-rw-r--r--src/exchangedb/0002-age_withdraw.sql157
-rw-r--r--src/exchangedb/0002-aggregation_tracking.sql118
-rw-r--r--src/exchangedb/0002-aggregation_transient.sql71
-rw-r--r--src/exchangedb/0002-aml_history.sql147
-rw-r--r--src/exchangedb/0002-aml_staff.sql40
-rw-r--r--src/exchangedb/0002-aml_status.sql101
-rw-r--r--src/exchangedb/0002-auditor_denom_sigs.sql32
-rw-r--r--src/exchangedb/0002-auditors.sql35
-rw-r--r--src/exchangedb/0002-batch_deposits.sql172
-rw-r--r--src/exchangedb/0002-close_requests.sql178
-rw-r--r--src/exchangedb/0002-coin_deposits.sql157
-rw-r--r--src/exchangedb/0002-coin_history.sql138
-rw-r--r--src/exchangedb/0002-contracts.sql109
-rw-r--r--src/exchangedb/0002-cs_nonce_locks.sql97
-rw-r--r--src/exchangedb/0002-denomination_revocations.sql23
-rw-r--r--src/exchangedb/0002-denominations.sql45
-rw-r--r--src/exchangedb/0002-exchange_sign_keys.sql36
-rw-r--r--src/exchangedb/0002-extensions.sql27
-rw-r--r--src/exchangedb/0002-global_fee.sql37
-rw-r--r--src/exchangedb/0002-history_requests.sql159
-rw-r--r--src/exchangedb/0002-known_coins.sql136
-rw-r--r--src/exchangedb/0002-kyc_alerts.sql27
-rw-r--r--src/exchangedb/0002-kyc_attributes.sql162
-rw-r--r--src/exchangedb/0002-legitimization_processes.sql149
-rw-r--r--src/exchangedb/0002-legitimization_requirements.sql104
-rw-r--r--src/exchangedb/0002-partner_accounts.sql33
-rw-r--r--src/exchangedb/0002-partners.sql49
-rw-r--r--src/exchangedb/0002-policy_details.sql175
-rw-r--r--src/exchangedb/0002-policy_fulfillments.sql101
-rw-r--r--src/exchangedb/0002-prewire.sql116
-rw-r--r--src/exchangedb/0002-profit_drains.sql42
-rw-r--r--src/exchangedb/0002-purse_actions.sql121
-rw-r--r--src/exchangedb/0002-purse_decision.sql143
-rw-r--r--src/exchangedb/0002-purse_deletion.sql110
-rw-r--r--src/exchangedb/0002-purse_deposits.sql176
-rw-r--r--src/exchangedb/0002-purse_merges.sql140
-rw-r--r--src/exchangedb/0002-purse_requests.sql163
-rw-r--r--src/exchangedb/0002-recoup.sql267
-rw-r--r--src/exchangedb/0002-recoup_refresh.sql203
-rw-r--r--src/exchangedb/0002-refresh_commitments.sql166
-rw-r--r--src/exchangedb/0002-refresh_revealed_coins.sql169
-rw-r--r--src/exchangedb/0002-refresh_transfer_keys.sql127
-rw-r--r--src/exchangedb/0002-refunds.sql162
-rw-r--r--src/exchangedb/0002-reserve_history.sql138
-rw-r--r--src/exchangedb/0002-reserves.sql152
-rw-r--r--src/exchangedb/0002-reserves_close.sql151
-rw-r--r--src/exchangedb/0002-reserves_in.sql175
-rw-r--r--src/exchangedb/0002-reserves_open_deposits.sql135
-rw-r--r--src/exchangedb/0002-reserves_open_requests.sql150
-rw-r--r--src/exchangedb/0002-reserves_out.sql173
-rw-r--r--src/exchangedb/0002-revolving_work_shards.sql (renamed from src/exchangedb/exchange-0003.sql)49
-rw-r--r--src/exchangedb/0002-signkey_revocations.sql23
-rw-r--r--src/exchangedb/0002-wad_in_entries.sql175
-rw-r--r--src/exchangedb/0002-wad_out_entries.sql179
-rw-r--r--src/exchangedb/0002-wads_in.sql107
-rw-r--r--src/exchangedb/0002-wads_out.sql128
-rw-r--r--src/exchangedb/0002-wire_accounts.sql45
-rw-r--r--src/exchangedb/0002-wire_fee.sql34
-rw-r--r--src/exchangedb/0002-wire_out.sql130
-rw-r--r--src/exchangedb/0002-wire_targets.sql89
-rw-r--r--src/exchangedb/0002-work_shards.sql56
-rw-r--r--src/exchangedb/0003-purse_deletion.sql52
-rw-r--r--src/exchangedb/0003-wire_accounts.sql25
-rw-r--r--src/exchangedb/0004-refunds.sql35
-rw-r--r--src/exchangedb/Makefile.am333
-rw-r--r--src/exchangedb/auditor-triggers-0001.sql41
-rw-r--r--src/exchangedb/bench-db-postgres.conf14
-rw-r--r--src/exchangedb/bench_db.c531
-rw-r--r--src/exchangedb/benchmark-0001.sql55
-rw-r--r--src/exchangedb/drop.sql39
-rw-r--r--src/exchangedb/drop0001.sql69
-rw-r--r--src/exchangedb/exchange-0001.sql699
-rw-r--r--src/exchangedb/exchange-0002.sql473
-rw-r--r--src/exchangedb/exchange-0002.sql.in118
-rw-r--r--src/exchangedb/exchange-0003.sql.in25
-rw-r--r--src/exchangedb/exchange-0004.sql.in24
-rw-r--r--src/exchangedb/exchange_do_account_merge.sql15
-rw-r--r--src/exchangedb/exchange_do_age_withdraw.sql165
-rw-r--r--src/exchangedb/exchange_do_amount_specific.sql92
-rw-r--r--src/exchangedb/exchange_do_batch_coin_known.sql469
-rw-r--r--src/exchangedb/exchange_do_batch_reserves_update.sql72
-rw-r--r--src/exchangedb/exchange_do_batch_withdraw.sql126
-rw-r--r--src/exchangedb/exchange_do_batch_withdraw_insert.sql120
-rw-r--r--src/exchangedb/exchange_do_deposit.sql206
-rw-r--r--src/exchangedb/exchange_do_expire_purse.sql98
-rw-r--r--src/exchangedb/exchange_do_gc.sql140
-rw-r--r--src/exchangedb/exchange_do_get_link_data.sql59
-rw-r--r--src/exchangedb/exchange_do_insert_aml_decision.sql127
-rw-r--r--src/exchangedb/exchange_do_insert_aml_officer.sql74
-rw-r--r--src/exchangedb/exchange_do_insert_kyc_attributes.sql114
-rw-r--r--src/exchangedb/exchange_do_insert_or_update_policy_details.sql114
-rw-r--r--src/exchangedb/exchange_do_melt.sql182
-rw-r--r--src/exchangedb/exchange_do_purse_delete.sql118
-rw-r--r--src/exchangedb/exchange_do_purse_deposit.sql267
-rw-r--r--src/exchangedb/exchange_do_purse_merge.sql237
-rw-r--r--src/exchangedb/exchange_do_recoup_by_reserve.sql87
-rw-r--r--src/exchangedb/exchange_do_recoup_to_coin.sql135
-rw-r--r--src/exchangedb/exchange_do_recoup_to_reserve.sql150
-rw-r--r--src/exchangedb/exchange_do_refund.sql205
-rw-r--r--src/exchangedb/exchange_do_reserve_open.sql194
-rw-r--r--src/exchangedb/exchange_do_reserve_open_deposit.sql84
-rw-r--r--src/exchangedb/exchange_do_reserve_purse.sql162
-rw-r--r--src/exchangedb/exchange_do_reserves_in_insert.sql122
-rw-r--r--src/exchangedb/exchange_do_select_deposits_missing_wire.sql73
-rw-r--r--src/exchangedb/exchange_do_select_justification_for_missing_wire.sql102
-rw-r--r--src/exchangedb/exchangedb-postgres.conf7
-rw-r--r--src/exchangedb/exchangedb.conf8
-rw-r--r--src/exchangedb/exchangedb_accounts.c1
-rw-r--r--src/exchangedb/exchangedb_plugin.c11
-rw-r--r--src/exchangedb/exchangedb_transactions.c55
-rw-r--r--src/exchangedb/irbt_callbacks.c653
-rw-r--r--src/exchangedb/lrbt_callbacks.c1201
-rwxr-xr-xsrc/exchangedb/perf-exchangedb-reserves-in-insert-postgres210
-rw-r--r--src/exchangedb/perf_deposits_get_ready.c565
-rw-r--r--src/exchangedb/perf_get_link_data.c543
-rw-r--r--src/exchangedb/perf_reserves_in_insert.c232
-rw-r--r--src/exchangedb/perf_select_refunds_by_coin.c619
-rw-r--r--src/exchangedb/pg_abort_shard.c53
-rw-r--r--src/exchangedb/pg_abort_shard.h43
-rw-r--r--src/exchangedb/pg_activate_signing_key.c58
-rw-r--r--src/exchangedb/pg_activate_signing_key.h44
-rw-r--r--src/exchangedb/pg_add_denomination_key.c86
-rw-r--r--src/exchangedb/pg_add_denomination_key.h46
-rw-r--r--src/exchangedb/pg_add_policy_fulfillment_proof.c159
-rw-r--r--src/exchangedb/pg_add_policy_fulfillment_proof.h39
-rw-r--r--src/exchangedb/pg_aggregate.c205
-rw-r--r--src/exchangedb/pg_aggregate.h46
-rw-r--r--src/exchangedb/pg_batch_ensure_coin_known.c462
-rw-r--r--src/exchangedb/pg_batch_ensure_coin_known.h47
-rw-r--r--src/exchangedb/pg_begin_revolving_shard.c263
-rw-r--r--src/exchangedb/pg_begin_revolving_shard.h49
-rw-r--r--src/exchangedb/pg_begin_shard.c266
-rw-r--r--src/exchangedb/pg_begin_shard.h47
-rw-r--r--src/exchangedb/pg_commit.c58
-rw-r--r--src/exchangedb/pg_commit.h37
-rw-r--r--src/exchangedb/pg_complete_shard.c56
-rw-r--r--src/exchangedb/pg_complete_shard.h43
-rw-r--r--src/exchangedb/pg_compute_shard.c49
-rw-r--r--src/exchangedb/pg_compute_shard.h39
-rw-r--r--src/exchangedb/pg_count_known_coins.c63
-rw-r--r--src/exchangedb/pg_count_known_coins.h39
-rw-r--r--src/exchangedb/pg_create_aggregation_transient.c64
-rw-r--r--src/exchangedb/pg_create_aggregation_transient.h49
-rw-r--r--src/exchangedb/pg_create_tables.c72
-rw-r--r--src/exchangedb/pg_create_tables.h44
-rw-r--r--src/exchangedb/pg_delete_aggregation_transient.c50
-rw-r--r--src/exchangedb/pg_delete_aggregation_transient.h43
-rw-r--r--src/exchangedb/pg_delete_shard_locks.c41
-rw-r--r--src/exchangedb/pg_delete_shard_locks.h38
-rw-r--r--src/exchangedb/pg_do_age_withdraw.c108
-rw-r--r--src/exchangedb/pg_do_age_withdraw.h57
-rw-r--r--src/exchangedb/pg_do_batch_withdraw.c89
-rw-r--r--src/exchangedb/pg_do_batch_withdraw.h59
-rw-r--r--src/exchangedb/pg_do_batch_withdraw_insert.c77
-rw-r--r--src/exchangedb/pg_do_batch_withdraw_insert.h52
-rw-r--r--src/exchangedb/pg_do_deposit.c119
-rw-r--r--src/exchangedb/pg_do_deposit.h51
-rw-r--r--src/exchangedb/pg_do_melt.c82
-rw-r--r--src/exchangedb/pg_do_melt.h49
-rw-r--r--src/exchangedb/pg_do_purse_delete.c64
-rw-r--r--src/exchangedb/pg_do_purse_delete.h49
-rw-r--r--src/exchangedb/pg_do_purse_deposit.c90
-rw-r--r--src/exchangedb/pg_do_purse_deposit.h63
-rw-r--r--src/exchangedb/pg_do_purse_merge.c91
-rw-r--r--src/exchangedb/pg_do_purse_merge.h57
-rw-r--r--src/exchangedb/pg_do_recoup.c85
-rw-r--r--src/exchangedb/pg_do_recoup.h56
-rw-r--r--src/exchangedb/pg_do_recoup_refresh.c79
-rw-r--r--src/exchangedb/pg_do_recoup_refresh.h57
-rw-r--r--src/exchangedb/pg_do_refund.c93
-rw-r--r--src/exchangedb/pg_do_refund.h52
-rw-r--r--src/exchangedb/pg_do_reserve_open.c101
-rw-r--r--src/exchangedb/pg_do_reserve_open.h64
-rw-r--r--src/exchangedb/pg_do_reserve_purse.c121
-rw-r--r--src/exchangedb/pg_do_reserve_purse.h57
-rw-r--r--src/exchangedb/pg_drain_kyc_alert.c59
-rw-r--r--src/exchangedb/pg_drain_kyc_alert.h40
-rw-r--r--src/exchangedb/pg_drop_tables.c58
-rw-r--r--src/exchangedb/pg_drop_tables.h38
-rw-r--r--src/exchangedb/pg_ensure_coin_known.c169
-rw-r--r--src/exchangedb/pg_ensure_coin_known.h45
-rw-r--r--src/exchangedb/pg_event_listen.c53
-rw-r--r--src/exchangedb/pg_event_listen.h45
-rw-r--r--src/exchangedb/pg_event_listen_cancel.c36
-rw-r--r--src/exchangedb/pg_event_listen_cancel.h38
-rw-r--r--src/exchangedb/pg_event_notify.c41
-rw-r--r--src/exchangedb/pg_event_notify.h42
-rw-r--r--src/exchangedb/pg_expire_purse.c69
-rw-r--r--src/exchangedb/pg_expire_purse.h41
-rw-r--r--src/exchangedb/pg_find_aggregation_transient.c150
-rw-r--r--src/exchangedb/pg_find_aggregation_transient.h43
-rw-r--r--src/exchangedb/pg_gc.c80
-rw-r--r--src/exchangedb/pg_gc.h39
-rw-r--r--src/exchangedb/pg_get_age_withdraw.c119
-rw-r--r--src/exchangedb/pg_get_age_withdraw.h45
-rw-r--r--src/exchangedb/pg_get_coin_denomination.c69
-rw-r--r--src/exchangedb/pg_get_coin_denomination.h43
-rw-r--r--src/exchangedb/pg_get_coin_transactions.c1144
-rw-r--r--src/exchangedb/pg_get_coin_transactions.h61
-rw-r--r--src/exchangedb/pg_get_denomination_info.c91
-rw-r--r--src/exchangedb/pg_get_denomination_info.h41
-rw-r--r--src/exchangedb/pg_get_denomination_revocation.c63
-rw-r--r--src/exchangedb/pg_get_denomination_revocation.h45
-rw-r--r--src/exchangedb/pg_get_drain_profit.c76
-rw-r--r--src/exchangedb/pg_get_drain_profit.h52
-rw-r--r--src/exchangedb/pg_get_expired_reserves.c174
-rw-r--r--src/exchangedb/pg_get_expired_reserves.h45
-rw-r--r--src/exchangedb/pg_get_extension_manifest.c67
-rw-r--r--src/exchangedb/pg_get_extension_manifest.h42
-rw-r--r--src/exchangedb/pg_get_global_fee.c86
-rw-r--r--src/exchangedb/pg_get_global_fee.h52
-rw-r--r--src/exchangedb/pg_get_global_fees.c165
-rw-r--r--src/exchangedb/pg_get_global_fees.h41
-rw-r--r--src/exchangedb/pg_get_known_coin.c67
-rw-r--r--src/exchangedb/pg_get_known_coin.h40
-rw-r--r--src/exchangedb/pg_get_link_data.c367
-rw-r--r--src/exchangedb/pg_get_link_data.h45
-rw-r--r--src/exchangedb/pg_get_melt.c124
-rw-r--r--src/exchangedb/pg_get_melt.h44
-rw-r--r--src/exchangedb/pg_get_old_coin_by_h_blind.c64
-rw-r--r--src/exchangedb/pg_get_old_coin_by_h_blind.h45
-rw-r--r--src/exchangedb/pg_get_pending_kyc_requirement_process.c66
-rw-r--r--src/exchangedb/pg_get_pending_kyc_requirement_process.h45
-rw-r--r--src/exchangedb/pg_get_policy_details.c64
-rw-r--r--src/exchangedb/pg_get_policy_details.h40
-rw-r--r--src/exchangedb/pg_get_purse_deposit.c84
-rw-r--r--src/exchangedb/pg_get_purse_deposit.h53
-rw-r--r--src/exchangedb/pg_get_purse_request.c79
-rw-r--r--src/exchangedb/pg_get_purse_request.h57
-rw-r--r--src/exchangedb/pg_get_ready_deposit.c74
-rw-r--r--src/exchangedb/pg_get_ready_deposit.h46
-rw-r--r--src/exchangedb/pg_get_refresh_reveal.c213
-rw-r--r--src/exchangedb/pg_get_refresh_reveal.h44
-rw-r--r--src/exchangedb/pg_get_reserve_balance.c55
-rw-r--r--src/exchangedb/pg_get_reserve_balance.h40
-rw-r--r--src/exchangedb/pg_get_reserve_by_h_blind.c63
-rw-r--r--src/exchangedb/pg_get_reserve_by_h_blind.h44
-rw-r--r--src/exchangedb/pg_get_reserve_history.c936
-rw-r--r--src/exchangedb/pg_get_reserve_history.h57
-rw-r--r--src/exchangedb/pg_get_signature_for_known_coin.c63
-rw-r--r--src/exchangedb/pg_get_signature_for_known_coin.h43
-rw-r--r--src/exchangedb/pg_get_unfinished_close_requests.c166
-rw-r--r--src/exchangedb/pg_get_unfinished_close_requests.h46
-rw-r--r--src/exchangedb/pg_get_wire_accounts.c169
-rw-r--r--src/exchangedb/pg_get_wire_accounts.h42
-rw-r--r--src/exchangedb/pg_get_wire_fee.c73
-rw-r--r--src/exchangedb/pg_get_wire_fee.h49
-rw-r--r--src/exchangedb/pg_get_wire_fees.c147
-rw-r--r--src/exchangedb/pg_get_wire_fees.h44
-rw-r--r--src/exchangedb/pg_get_wire_hash_for_contract.c81
-rw-r--r--src/exchangedb/pg_get_wire_hash_for_contract.h46
-rw-r--r--src/exchangedb/pg_get_withdraw_info.c79
-rw-r--r--src/exchangedb/pg_get_withdraw_info.h43
-rw-r--r--src/exchangedb/pg_have_deposit2.c117
-rw-r--r--src/exchangedb/pg_have_deposit2.h53
-rw-r--r--src/exchangedb/pg_helper.h149
-rw-r--r--src/exchangedb/pg_inject_auditor_triggers.c57
-rw-r--r--src/exchangedb/pg_inject_auditor_triggers.h41
-rw-r--r--src/exchangedb/pg_insert_aml_decision.c97
-rw-r--r--src/exchangedb/pg_insert_aml_decision.h64
-rw-r--r--src/exchangedb/pg_insert_aml_officer.c66
-rw-r--r--src/exchangedb/pg_insert_aml_officer.h56
-rw-r--r--src/exchangedb/pg_insert_auditor.c58
-rw-r--r--src/exchangedb/pg_insert_auditor.h45
-rw-r--r--src/exchangedb/pg_insert_auditor_denom_sig.c61
-rw-r--r--src/exchangedb/pg_insert_auditor_denom_sig.h43
-rw-r--r--src/exchangedb/pg_insert_close_request.c68
-rw-r--r--src/exchangedb/pg_insert_close_request.h52
-rw-r--r--src/exchangedb/pg_insert_contract.c93
-rw-r--r--src/exchangedb/pg_insert_contract.h47
-rw-r--r--src/exchangedb/pg_insert_denomination_info.c101
-rw-r--r--src/exchangedb/pg_insert_denomination_info.h42
-rw-r--r--src/exchangedb/pg_insert_denomination_revocation.c54
-rw-r--r--src/exchangedb/pg_insert_denomination_revocation.h42
-rw-r--r--src/exchangedb/pg_insert_drain_profit.c63
-rw-r--r--src/exchangedb/pg_insert_drain_profit.h50
-rw-r--r--src/exchangedb/pg_insert_global_fee.c137
-rw-r--r--src/exchangedb/pg_insert_global_fee.h50
-rw-r--r--src/exchangedb/pg_insert_kyc_attributes.c110
-rw-r--r--src/exchangedb/pg_insert_kyc_attributes.h69
-rw-r--r--src/exchangedb/pg_insert_kyc_failure.c82
-rw-r--r--src/exchangedb/pg_insert_kyc_failure.h50
-rw-r--r--src/exchangedb/pg_insert_kyc_requirement_for_account.c67
-rw-r--r--src/exchangedb/pg_insert_kyc_requirement_for_account.h47
-rw-r--r--src/exchangedb/pg_insert_kyc_requirement_process.c75
-rw-r--r--src/exchangedb/pg_insert_kyc_requirement_process.h49
-rw-r--r--src/exchangedb/pg_insert_partner.c69
-rw-r--r--src/exchangedb/pg_insert_partner.h51
-rw-r--r--src/exchangedb/pg_insert_purse_request.c126
-rw-r--r--src/exchangedb/pg_insert_purse_request.h61
-rw-r--r--src/exchangedb/pg_insert_records_by_table.c2334
-rw-r--r--src/exchangedb/pg_insert_records_by_table.h43
-rw-r--r--src/exchangedb/pg_insert_refresh_reveal.c109
-rw-r--r--src/exchangedb/pg_insert_refresh_reveal.h51
-rw-r--r--src/exchangedb/pg_insert_refund.c65
-rw-r--r--src/exchangedb/pg_insert_refund.h38
-rw-r--r--src/exchangedb/pg_insert_reserve_closed.c113
-rw-r--r--src/exchangedb/pg_insert_reserve_closed.h51
-rw-r--r--src/exchangedb/pg_insert_reserve_open_deposit.c67
-rw-r--r--src/exchangedb/pg_insert_reserve_open_deposit.h54
-rw-r--r--src/exchangedb/pg_insert_signkey_revocation.c53
-rw-r--r--src/exchangedb/pg_insert_signkey_revocation.h41
-rw-r--r--src/exchangedb/pg_insert_wire.c74
-rw-r--r--src/exchangedb/pg_insert_wire.h55
-rw-r--r--src/exchangedb/pg_insert_wire_fee.c108
-rw-r--r--src/exchangedb/pg_insert_wire_fee.h46
-rw-r--r--src/exchangedb/pg_iterate_active_auditors.c123
-rw-r--r--src/exchangedb/pg_iterate_active_auditors.h42
-rw-r--r--src/exchangedb/pg_iterate_active_signkeys.c144
-rw-r--r--src/exchangedb/pg_iterate_active_signkeys.h43
-rw-r--r--src/exchangedb/pg_iterate_auditor_denominations.c121
-rw-r--r--src/exchangedb/pg_iterate_auditor_denominations.h45
-rw-r--r--src/exchangedb/pg_iterate_denomination_info.c180
-rw-r--r--src/exchangedb/pg_iterate_denomination_info.h41
-rw-r--r--src/exchangedb/pg_iterate_denominations.c172
-rw-r--r--src/exchangedb/pg_iterate_denominations.h44
-rw-r--r--src/exchangedb/pg_iterate_kyc_reference.c129
-rw-r--r--src/exchangedb/pg_iterate_kyc_reference.h46
-rw-r--r--src/exchangedb/pg_iterate_reserve_close_info.c128
-rw-r--r--src/exchangedb/pg_iterate_reserve_close_info.h49
-rw-r--r--src/exchangedb/pg_kyc_provider_account_lookup.c64
-rw-r--r--src/exchangedb/pg_kyc_provider_account_lookup.h48
-rw-r--r--src/exchangedb/pg_lookup_aml_officer.c71
-rw-r--r--src/exchangedb/pg_lookup_aml_officer.h51
-rw-r--r--src/exchangedb/pg_lookup_auditor_status.c61
-rw-r--r--src/exchangedb/pg_lookup_auditor_status.h44
-rw-r--r--src/exchangedb/pg_lookup_auditor_timestamp.c57
-rw-r--r--src/exchangedb/pg_lookup_auditor_timestamp.h41
-rw-r--r--src/exchangedb/pg_lookup_denomination_key.c82
-rw-r--r--src/exchangedb/pg_lookup_denomination_key.h41
-rw-r--r--src/exchangedb/pg_lookup_global_fee_by_time.c182
-rw-r--r--src/exchangedb/pg_lookup_global_fee_by_time.h52
-rw-r--r--src/exchangedb/pg_lookup_kyc_process_by_account.c83
-rw-r--r--src/exchangedb/pg_lookup_kyc_process_by_account.h51
-rw-r--r--src/exchangedb/pg_lookup_kyc_requirement_by_row.c71
-rw-r--r--src/exchangedb/pg_lookup_kyc_requirement_by_row.h47
-rw-r--r--src/exchangedb/pg_lookup_records_by_table.c3607
-rw-r--r--src/exchangedb/pg_lookup_records_by_table.h49
-rw-r--r--src/exchangedb/pg_lookup_serial_by_table.c459
-rw-r--r--src/exchangedb/pg_lookup_serial_by_table.h45
-rw-r--r--src/exchangedb/pg_lookup_signing_key.c64
-rw-r--r--src/exchangedb/pg_lookup_signing_key.h42
-rw-r--r--src/exchangedb/pg_lookup_signkey_revocation.c59
-rw-r--r--src/exchangedb/pg_lookup_signkey_revocation.h42
-rw-r--r--src/exchangedb/pg_lookup_transfer_by_deposit.c239
-rw-r--r--src/exchangedb/pg_lookup_transfer_by_deposit.h63
-rw-r--r--src/exchangedb/pg_lookup_wire_fee_by_time.c156
-rw-r--r--src/exchangedb/pg_lookup_wire_fee_by_time.h76
-rw-r--r--src/exchangedb/pg_lookup_wire_timestamp.c56
-rw-r--r--src/exchangedb/pg_lookup_wire_timestamp.h40
-rw-r--r--src/exchangedb/pg_lookup_wire_transfer.c188
-rw-r--r--src/exchangedb/pg_lookup_wire_transfer.h45
-rw-r--r--src/exchangedb/pg_persist_policy_details.c78
-rw-r--r--src/exchangedb/pg_persist_policy_details.h47
-rw-r--r--src/exchangedb/pg_preflight.c69
-rw-r--r--src/exchangedb/pg_preflight.h44
-rw-r--r--src/exchangedb/pg_profit_drains_get_pending.c78
-rw-r--r--src/exchangedb/pg_profit_drains_get_pending.h52
-rw-r--r--src/exchangedb/pg_profit_drains_set_finished.c49
-rw-r--r--src/exchangedb/pg_profit_drains_set_finished.h40
-rw-r--r--src/exchangedb/pg_release_revolving_shard.c59
-rw-r--r--src/exchangedb/pg_release_revolving_shard.h44
-rw-r--r--src/exchangedb/pg_reserves_get.c61
-rw-r--r--src/exchangedb/pg_reserves_get.h40
-rw-r--r--src/exchangedb/pg_reserves_get_origin.c57
-rw-r--r--src/exchangedb/pg_reserves_get_origin.h41
-rw-r--r--src/exchangedb/pg_reserves_in_insert.c373
-rw-r--r--src/exchangedb/pg_reserves_in_insert.h47
-rw-r--r--src/exchangedb/pg_reserves_update.c53
-rw-r--r--src/exchangedb/pg_reserves_update.h40
-rw-r--r--src/exchangedb/pg_rollback.c50
-rw-r--r--src/exchangedb/pg_rollback.h36
-rw-r--r--src/exchangedb/pg_select_account_merges_above_serial_id.c192
-rw-r--r--src/exchangedb/pg_select_account_merges_above_serial_id.h46
-rw-r--r--src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.c154
-rw-r--r--src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.h48
-rw-r--r--src/exchangedb/pg_select_aggregation_transient.c66
-rw-r--r--src/exchangedb/pg_select_aggregation_transient.h47
-rw-r--r--src/exchangedb/pg_select_aggregations_above_serial.c137
-rw-r--r--src/exchangedb/pg_select_aggregations_above_serial.h47
-rw-r--r--src/exchangedb/pg_select_all_purse_decisions_above_serial_id.c146
-rw-r--r--src/exchangedb/pg_select_all_purse_decisions_above_serial_id.h47
-rw-r--r--src/exchangedb/pg_select_aml_history.c157
-rw-r--r--src/exchangedb/pg_select_aml_history.h46
-rw-r--r--src/exchangedb/pg_select_aml_process.c170
-rw-r--r--src/exchangedb/pg_select_aml_process.h52
-rw-r--r--src/exchangedb/pg_select_aml_threshold.c70
-rw-r--r--src/exchangedb/pg_select_aml_threshold.h48
-rw-r--r--src/exchangedb/pg_select_auditor_denom_sig.c66
-rw-r--r--src/exchangedb/pg_select_auditor_denom_sig.h43
-rw-r--r--src/exchangedb/pg_select_batch_deposits_missing_wire.c144
-rw-r--r--src/exchangedb/pg_select_batch_deposits_missing_wire.h44
-rw-r--r--src/exchangedb/pg_select_coin_deposits_above_serial_id.c204
-rw-r--r--src/exchangedb/pg_select_coin_deposits_above_serial_id.h44
-rw-r--r--src/exchangedb/pg_select_contract.c66
-rw-r--r--src/exchangedb/pg_select_contract.h47
-rw-r--r--src/exchangedb/pg_select_contract_by_purse.c63
-rw-r--r--src/exchangedb/pg_select_contract_by_purse.h42
-rw-r--r--src/exchangedb/pg_select_justification_for_missing_wire.c89
-rw-r--r--src/exchangedb/pg_select_justification_for_missing_wire.h49
-rw-r--r--src/exchangedb/pg_select_kyc_attributes.c156
-rw-r--r--src/exchangedb/pg_select_kyc_attributes.h45
-rw-r--r--src/exchangedb/pg_select_merge_amounts_for_kyc_check.c157
-rw-r--r--src/exchangedb/pg_select_merge_amounts_for_kyc_check.h47
-rw-r--r--src/exchangedb/pg_select_purse.c94
-rw-r--r--src/exchangedb/pg_select_purse.h57
-rw-r--r--src/exchangedb/pg_select_purse_by_merge_pub.c79
-rw-r--r--src/exchangedb/pg_select_purse_by_merge_pub.h54
-rw-r--r--src/exchangedb/pg_select_purse_decisions_above_serial_id.c162
-rw-r--r--src/exchangedb/pg_select_purse_decisions_above_serial_id.h47
-rw-r--r--src/exchangedb/pg_select_purse_deposits_above_serial_id.c201
-rw-r--r--src/exchangedb/pg_select_purse_deposits_above_serial_id.h47
-rw-r--r--src/exchangedb/pg_select_purse_deposits_by_purse.c153
-rw-r--r--src/exchangedb/pg_select_purse_deposits_by_purse.h44
-rw-r--r--src/exchangedb/pg_select_purse_merge.c80
-rw-r--r--src/exchangedb/pg_select_purse_merge.h51
-rw-r--r--src/exchangedb/pg_select_purse_merges_above_serial_id.c190
-rw-r--r--src/exchangedb/pg_select_purse_merges_above_serial_id.h46
-rw-r--r--src/exchangedb/pg_select_purse_requests_above_serial_id.c178
-rw-r--r--src/exchangedb/pg_select_purse_requests_above_serial_id.h47
-rw-r--r--src/exchangedb/pg_select_recoup_above_serial_id.c194
-rw-r--r--src/exchangedb/pg_select_recoup_above_serial_id.h44
-rw-r--r--src/exchangedb/pg_select_recoup_refresh_above_serial_id.c203
-rw-r--r--src/exchangedb/pg_select_recoup_refresh_above_serial_id.h45
-rw-r--r--src/exchangedb/pg_select_refreshes_above_serial_id.c179
-rw-r--r--src/exchangedb/pg_select_refreshes_above_serial_id.h45
-rw-r--r--src/exchangedb/pg_select_refunds_above_serial_id.c223
-rw-r--r--src/exchangedb/pg_select_refunds_above_serial_id.h45
-rw-r--r--src/exchangedb/pg_select_refunds_by_coin.c143
-rw-r--r--src/exchangedb/pg_select_refunds_by_coin.h48
-rw-r--r--src/exchangedb/pg_select_reserve_close_info.c63
-rw-r--r--src/exchangedb/pg_select_reserve_close_info.h49
-rw-r--r--src/exchangedb/pg_select_reserve_closed_above_serial_id.c178
-rw-r--r--src/exchangedb/pg_select_reserve_closed_above_serial_id.h47
-rw-r--r--src/exchangedb/pg_select_reserve_open_above_serial_id.c168
-rw-r--r--src/exchangedb/pg_select_reserve_open_above_serial_id.h47
-rw-r--r--src/exchangedb/pg_select_reserves_in_above_serial_id.c164
-rw-r--r--src/exchangedb/pg_select_reserves_in_above_serial_id.h44
-rw-r--r--src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.c168
-rw-r--r--src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.h46
-rw-r--r--src/exchangedb/pg_select_satisfied_kyc_processes.c141
-rw-r--r--src/exchangedb/pg_select_satisfied_kyc_processes.h47
-rw-r--r--src/exchangedb/pg_select_similar_kyc_attributes.c154
-rw-r--r--src/exchangedb/pg_select_similar_kyc_attributes.h45
-rw-r--r--src/exchangedb/pg_select_wire_out_above_serial_id.c157
-rw-r--r--src/exchangedb/pg_select_wire_out_above_serial_id.h45
-rw-r--r--src/exchangedb/pg_select_wire_out_above_serial_id_by_account.c161
-rw-r--r--src/exchangedb/pg_select_wire_out_above_serial_id_by_account.h47
-rw-r--r--src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.c158
-rw-r--r--src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.h48
-rw-r--r--src/exchangedb/pg_select_withdrawals_above_serial_id.c170
-rw-r--r--src/exchangedb/pg_select_withdrawals_above_serial_id.h45
-rw-r--r--src/exchangedb/pg_set_extension_manifest.c56
-rw-r--r--src/exchangedb/pg_set_extension_manifest.h43
-rw-r--r--src/exchangedb/pg_set_purse_balance.c52
-rw-r--r--src/exchangedb/pg_set_purse_balance.h43
-rw-r--r--src/exchangedb/pg_setup_wire_target.c54
-rw-r--r--src/exchangedb/pg_setup_wire_target.h43
-rw-r--r--src/exchangedb/pg_start.c56
-rw-r--r--src/exchangedb/pg_start.h40
-rw-r--r--src/exchangedb/pg_start_deferred_wire_out.c59
-rw-r--r--src/exchangedb/pg_start_deferred_wire_out.h39
-rw-r--r--src/exchangedb/pg_start_read_committed.c56
-rw-r--r--src/exchangedb/pg_start_read_committed.h39
-rw-r--r--src/exchangedb/pg_start_read_only.c57
-rw-r--r--src/exchangedb/pg_start_read_only.h40
-rw-r--r--src/exchangedb/pg_store_wire_transfer_out.c61
-rw-r--r--src/exchangedb/pg_store_wire_transfer_out.h48
-rw-r--r--src/exchangedb/pg_template.c26
-rw-r--r--src/exchangedb/pg_template.h29
-rwxr-xr-xsrc/exchangedb/pg_template.sh21
-rw-r--r--src/exchangedb/pg_test_aml_officer.c48
-rw-r--r--src/exchangedb/pg_test_aml_officer.h43
-rw-r--r--src/exchangedb/pg_trigger_aml_process.c58
-rw-r--r--src/exchangedb/pg_trigger_aml_process.h45
-rw-r--r--src/exchangedb/pg_update_aggregation_transient.c57
-rw-r--r--src/exchangedb/pg_update_aggregation_transient.h46
-rw-r--r--src/exchangedb/pg_update_auditor.c59
-rw-r--r--src/exchangedb/pg_update_auditor.h47
-rw-r--r--src/exchangedb/pg_update_kyc_process_by_row.c122
-rw-r--r--src/exchangedb/pg_update_kyc_process_by_row.h53
-rw-r--r--src/exchangedb/pg_update_wire.c81
-rw-r--r--src/exchangedb/pg_update_wire.h57
-rw-r--r--src/exchangedb/pg_wire_prepare_data_get.c140
-rw-r--r--src/exchangedb/pg_wire_prepare_data_get.h46
-rw-r--r--src/exchangedb/pg_wire_prepare_data_insert.c54
-rw-r--r--src/exchangedb/pg_wire_prepare_data_insert.h43
-rw-r--r--src/exchangedb/pg_wire_prepare_data_mark_failed.c48
-rw-r--r--src/exchangedb/pg_wire_prepare_data_mark_failed.h40
-rw-r--r--src/exchangedb/pg_wire_prepare_data_mark_finished.c47
-rw-r--r--src/exchangedb/pg_wire_prepare_data_mark_finished.h40
-rw-r--r--src/exchangedb/plugin_exchangedb_common.c99
-rw-r--r--src/exchangedb/plugin_exchangedb_common.h51
-rw-r--r--src/exchangedb/plugin_exchangedb_postgres.c11890
-rw-r--r--src/exchangedb/procedures.sql.in49
-rw-r--r--src/exchangedb/spi/Makefile9
-rw-r--r--src/exchangedb/spi/README.md37
-rw-r--r--src/exchangedb/spi/own_test.c873
-rw-r--r--src/exchangedb/spi/own_test.control4
-rw-r--r--src/exchangedb/spi/own_test.sql201
-rw-r--r--src/exchangedb/spi/perf_own_test.c25
-rw-r--r--src/exchangedb/spi/pg_aggregate.c411
-rw-r--r--src/exchangedb/test-exchange-db-postgres.conf8
-rwxr-xr-xsrc/exchangedb/test-exchangedb-batch-reserves-in-insert-postgres210
-rwxr-xr-xsrc/exchangedb/test-exchangedb-by-j-postgres210
-rwxr-xr-xsrc/exchangedb/test-exchangedb-populate-link-data-postgres210
-rwxr-xr-xsrc/exchangedb/test-exchangedb-populate-ready-deposit-postgres210
-rwxr-xr-xsrc/exchangedb/test-exchangedb-populate-select-refunds-by-coin-postgres210
-rw-r--r--src/exchangedb/test_exchangedb.c2324
-rw-r--r--src/exchangedb/test_exchangedb_by_j.c232
-rwxr-xr-xsrc/exchangedb/test_idempotency.sh12
-rw-r--r--src/exchangedb/versioning.sql (renamed from src/exchangedb/exchange-0000.sql)3
514 files changed, 56653 insertions, 15225 deletions
diff --git a/src/exchangedb/.gitignore b/src/exchangedb/.gitignore
index 830cf10cc..6e67fadb1 100644
--- a/src/exchangedb/.gitignore
+++ b/src/exchangedb/.gitignore
@@ -1,6 +1,16 @@
-test-exchangedb-auditors
-test-exchangedb-denomkeys
-test-exchangedb-fees
test-exchangedb-postgres
-test-exchangedb-signkeys
-test-perf-taler-exchangedb
+bench-db-postgres
+perf_deposits_get_ready-postgres
+perf_get_link_data-postgres
+perf_reserves_in_insert-postgres
+perf_select_refunds_by_coin-postgres
+exchange-0002.sql
+procedures.sql
+exchange-0003.sql
+perf-exchangedb-reserves-in-insert-postgres
+test-exchangedb-batch-reserves-in-insert-postgres
+test-exchangedb-by-j-postgres
+test-exchangedb-populate-link-data-postgres
+test-exchangedb-populate-ready-deposit-postgres
+test-exchangedb-populate-select-refunds-by-coin-postgres
+exchange-0004.sql
diff --git a/src/exchangedb/0002-account_merges.sql b/src/exchangedb/0002-account_merges.sql
new file mode 100644
index 000000000..1dd7e5bf0
--- /dev/null
+++ b/src/exchangedb/0002-account_merges.sql
@@ -0,0 +1,139 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_account_merges(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'account_merges';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I '
+ '(account_merge_request_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ',wallet_h_payto BYTEA NOT NULL CHECK (LENGTH(wallet_h_payto)=32)'
+ ',PRIMARY KEY (purse_pub)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Merge requests where a purse- and account-owner requested merging the purse into the account'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public key of the target reserve'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'signature by the reserve private key affirming the merge, of type TALER_SIGNATURE_WALLET_ACCOUNT_MERGE'
+ ,'reserve_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_account_merges(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'account_merges';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ -- Note: this index *may* be useful in
+ -- pg_get_reserve_history depending on how
+ -- smart the DB is when computing the JOIN.
+ -- Removing it MAY boost performance slightly, at
+ -- the expense of trouble if the "merge_by_reserve"
+ -- query planner goes off the rails. Needs benchmarking
+ -- to be sure.
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_reserve_pub '
+ 'ON ' || table_name || ' '
+ '(reserve_pub);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_account_merge_request_serial_id_key'
+ ' UNIQUE (account_merge_request_serial_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_account_merges()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'account_merges';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub'
+ ' FOREIGN KEY (reserve_pub) '
+ ' REFERENCES reserves (reserve_pub) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_purse_pub'
+ ' FOREIGN KEY (purse_pub) '
+ ' REFERENCES purse_requests (purse_pub)'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('account_merges'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('account_merges'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('account_merges'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-age_withdraw.sql b/src/exchangedb/0002-age_withdraw.sql
new file mode 100644
index 000000000..87cac7816
--- /dev/null
+++ b/src/exchangedb/0002-age_withdraw.sql
@@ -0,0 +1,157 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+-- @author Özgür Kesim
+
+CREATE FUNCTION create_table_age_withdraw(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'age_withdraw';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(age_withdraw_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_commitment BYTEA NOT NULL CONSTRAINT h_commitment_length CHECK(LENGTH(h_commitment)=64)'
+ ',max_age SMALLINT NOT NULL CONSTRAINT max_age_positive CHECK(max_age>=0)'
+ ',amount_with_fee taler_amount NOT NULL'
+ ',reserve_pub BYTEA NOT NULL CONSTRAINT reserve_pub_length CHECK(LENGTH(reserve_pub)=32)'
+ ',reserve_sig BYTEA NOT NULL CONSTRAINT reserve_sig_length CHECK(LENGTH(reserve_sig)=64)'
+ ',noreveal_index SMALLINT NOT NULL CONSTRAINT noreveal_index_positive CHECK(noreveal_index>=0)'
+ ',h_blind_evs BYTEA[] NOT NULL CONSTRAINT h_blind_evs_length CHECK(cardinality(h_blind_evs)=cardinality(denom_serials))'
+ ',denom_serials INT8[] NOT NULL CONSTRAINT denom_serials_array_length CHECK(cardinality(denom_serials)=cardinality(denom_sigs))'
+ ',denom_sigs BYTEA[] NOT NULL CONSTRAINT denom_sigs_array_length CHECK(cardinality(denom_sigs)=cardinality(denom_serials))'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Commitments made when withdrawing coins with age restriction and the gamma value chosen by the exchange. '
+ 'It also contains the blindly signed coins, their signatures and denominations.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The gamma value chosen by the exchange in the cut-and-choose protocol'
+ ,'noreveal_index'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The maximum age (in years) that the client commits to with this request'
+ ,'max_age'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Commitment made by the client, hash over the various client inputs in the cut-and-choose protocol'
+ ,'h_commitment'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Reference to the public key of the reserve from which the coins are going to be withdrawn'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature of the reserve''s private key over the age-withdraw request'
+ ,'reserve_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Array of references to the denominations'
+ ,'denom_serials'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Array of the blinded envelopes of the chosen fresh coins, with value as given by the denomination in the corresponding slot in denom_serials'
+ ,'h_blind_evs'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Array of signatures over each blinded envelope'
+ ,'denom_sigs'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_age_withdraw(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'age_withdraw';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD PRIMARY KEY (h_commitment);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_h_commitment_reserve_pub_key'
+ ' UNIQUE (h_commitment, reserve_pub);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_age_withdraw_id_key'
+ ' UNIQUE (age_withdraw_id);'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_age_withdraw()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'age_withdraw';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub'
+ ' FOREIGN KEY (reserve_pub)'
+ ' REFERENCES reserves(reserve_pub) ON DELETE CASCADE;'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+VALUES
+ ('age_withdraw', 'exchange-0002', 'create', TRUE ,FALSE),
+ ('age_withdraw', 'exchange-0002', 'constrain',TRUE ,FALSE),
+ ('age_withdraw', 'exchange-0002', 'foreign', TRUE ,FALSE);
+
diff --git a/src/exchangedb/0002-aggregation_tracking.sql b/src/exchangedb/0002-aggregation_tracking.sql
new file mode 100644
index 000000000..d07960247
--- /dev/null
+++ b/src/exchangedb/0002-aggregation_tracking.sql
@@ -0,0 +1,118 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_aggregation_tracking(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aggregation_tracking';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(aggregation_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',batch_deposit_serial_id INT8 PRIMARY KEY'
+ ',wtid_raw BYTEA NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (batch_deposit_serial_id)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'mapping from wire transfer identifiers (WTID) to deposits (and back)'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'identifier of the wire transfer'
+ ,'wtid_raw'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_aggregation_tracking(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aggregation_tracking';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_wtid_raw_index '
+ 'ON ' || table_name || ' '
+ '(wtid_raw);'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_wtid_raw_index '
+ 'IS ' || quote_literal('for lookup_transactions') || ';'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_aggregation_serial_id_key'
+ ' UNIQUE (aggregation_serial_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_aggregation_tracking()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aggregation_tracking';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_deposit'
+ ' FOREIGN KEY (batch_deposit_serial_id)'
+ ' REFERENCES batch_deposits (batch_deposit_serial_id)'
+ ' ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('aggregation_tracking'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('aggregation_tracking'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('aggregation_tracking'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-aggregation_transient.sql b/src/exchangedb/0002-aggregation_transient.sql
new file mode 100644
index 000000000..8e46450f0
--- /dev/null
+++ b/src/exchangedb/0002-aggregation_transient.sql
@@ -0,0 +1,71 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_aggregation_transient(
+ IN shard_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aggregation_transient';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(amount taler_amount NOT NULL'
+ ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
+ ',merchant_pub BYTEA CHECK (LENGTH(merchant_pub)=32)'
+ ',exchange_account_section TEXT NOT NULL'
+ ',legitimization_requirement_serial_id INT8 NOT NULL DEFAULT(0)'
+ ',wtid_raw BYTEA NOT NULL CHECK (LENGTH(wtid_raw)=32)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (wire_target_h_payto)'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'aggregations currently happening (lacking wire_out, usually because the amount is too low); this table is not replicated'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Sum of all of the aggregated deposits (without deposit fees)'
+ ,'amount'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'identifier of the wire transfer'
+ ,'wtid_raw'
+ ,table_name
+ ,shard_suffix
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('aggregation_transient'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-aml_history.sql b/src/exchangedb/0002-aml_history.sql
new file mode 100644
index 000000000..af81be9d8
--- /dev/null
+++ b/src/exchangedb/0002-aml_history.sql
@@ -0,0 +1,147 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION create_table_aml_history(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aml_history';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I'
+ '(aml_history_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_payto BYTEA CHECK (LENGTH(h_payto)=32)'
+ ',new_threshold taler_amount NOT NULL DEFAULT(0,0)'
+ ',new_status INT4 NOT NULL DEFAULT(0)'
+ ',decision_time INT8 NOT NULL DEFAULT(0)'
+ ',justification TEXT NOT NULL'
+ ',kyc_requirements TEXT'
+ ',kyc_req_row INT8 NOT NULL DEFAULT(0)'
+ ',decider_pub BYTEA CHECK (LENGTH(decider_pub)=32)'
+ ',decider_sig BYTEA CHECK (LENGTH(decider_sig)=64)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (h_payto)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'AML decision history for a particular payment destination'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash of the payto://-URI this AML history is about'
+ ,'h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'new monthly inbound transaction limit below which we are OK'
+ ,'new_threshold'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ '0 for all OK, 1 for AML decision required, 2 for account is frozen (prevents further transactions)'
+ ,'new_status'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'when was the status changed'
+ ,'decision_time'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'human-readable justification for the status change'
+ ,'justification'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the staff member who made the AML decision'
+ ,'decider_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Additional KYC requirements imposed by the AML staff member. Serialized JSON array of strings.'
+ ,'kyc_requirements'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Row in the KYC table for this KYC requirement, 0 for none.'
+ ,'kyc_req_row'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature key of the staff member affirming the AML decision; of type AML_DECISION'
+ ,'decider_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+COMMENT ON FUNCTION create_table_aml_history
+ IS 'Creates the aml_history table';
+
+
+CREATE OR REPLACE FUNCTION constrain_table_aml_history(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aml_history';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_serial_key '
+ 'UNIQUE (aml_history_serial_id)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_main_index '
+ 'ON ' || table_name || ' '
+ '(h_payto, decision_time DESC);'
+ );
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('aml_history'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('aml_history'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-aml_staff.sql b/src/exchangedb/0002-aml_staff.sql
new file mode 100644
index 000000000..cec18c62b
--- /dev/null
+++ b/src/exchangedb/0002-aml_staff.sql
@@ -0,0 +1,40 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE TABLE aml_staff
+ (aml_staff_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,decider_pub BYTEA PRIMARY KEY CHECK (LENGTH(decider_pub)=32)
+ ,master_sig BYTEA CHECK (LENGTH(master_sig)=64)
+ ,decider_name TEXT NOT NULL
+ ,is_active BOOLEAN NOT NULL
+ ,read_only BOOLEAN NOT NULL
+ ,last_change INT8 NOT NULL
+ );
+COMMENT ON TABLE aml_staff
+ IS 'Table with AML staff members the exchange uses or has used in the past. Entries never expire as we need to remember the last_change column indefinitely.';
+COMMENT ON COLUMN aml_staff.decider_pub
+ IS 'Public key of the AML staff member.';
+COMMENT ON COLUMN aml_staff.master_sig
+ IS 'The master public key signature on the AML staff member status, of type TALER_SIGNATURE_MASTER_AML_KEY.';
+COMMENT ON COLUMN aml_staff.decider_name
+ IS 'Name of the staff member.';
+COMMENT ON COLUMN aml_staff.is_active
+ IS 'true if we are currently supporting the use of this AML staff member.';
+COMMENT ON COLUMN aml_staff.is_active
+ IS 'true if the member has read-only access.';
+COMMENT ON COLUMN aml_staff.last_change
+ IS 'Latest time when active status changed. Used to detect replays of old messages.';
diff --git a/src/exchangedb/0002-aml_status.sql b/src/exchangedb/0002-aml_status.sql
new file mode 100644
index 000000000..a8b567a82
--- /dev/null
+++ b/src/exchangedb/0002-aml_status.sql
@@ -0,0 +1,101 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION create_table_aml_status(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aml_status';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I'
+ '(aml_status_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=32)'
+ ',threshold taler_amount NOT NULL DEFAULT(0,0)'
+ ',status INT4 NOT NULL DEFAULT(0)'
+ ',kyc_requirement INT8 NOT NULL DEFAULT(0)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (h_payto)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'AML status for a particular payment destination'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash of the payto://-URI this AML status is about'
+ ,'h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'monthly inbound transaction limit below which we are OK (if status is 1)'
+ ,'threshold'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ '0 for all OK, 1 for AML decision required, 2 for account is frozen (prevents further transactions)'
+ ,'status'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+COMMENT ON FUNCTION create_table_aml_status
+ IS 'Creates the aml_status table';
+
+
+CREATE OR REPLACE FUNCTION constrain_table_aml_status(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'aml_status';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_serial_key '
+ 'UNIQUE (aml_status_serial_id)'
+ );
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('aml_status'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('aml_status'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-auditor_denom_sigs.sql b/src/exchangedb/0002-auditor_denom_sigs.sql
new file mode 100644
index 000000000..3ed645af5
--- /dev/null
+++ b/src/exchangedb/0002-auditor_denom_sigs.sql
@@ -0,0 +1,32 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE TABLE auditor_denom_sigs
+ (auditor_denom_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,auditor_uuid INT8 NOT NULL REFERENCES auditors (auditor_uuid) ON DELETE CASCADE
+ ,denominations_serial INT8 NOT NULL REFERENCES denominations (denominations_serial) ON DELETE CASCADE
+ ,auditor_sig BYTEA CHECK (LENGTH(auditor_sig)=64)
+ ,PRIMARY KEY (denominations_serial, auditor_uuid)
+ );
+COMMENT ON TABLE auditor_denom_sigs
+ IS 'Table with auditor signatures on exchange denomination keys.';
+COMMENT ON COLUMN auditor_denom_sigs.auditor_uuid
+ IS 'Identifies the auditor.';
+COMMENT ON COLUMN auditor_denom_sigs.denominations_serial
+ IS 'Denomination the signature is for.';
+COMMENT ON COLUMN auditor_denom_sigs.auditor_sig
+ IS 'Signature of the auditor, of purpose TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS.';
diff --git a/src/exchangedb/0002-auditors.sql b/src/exchangedb/0002-auditors.sql
new file mode 100644
index 000000000..76e43b183
--- /dev/null
+++ b/src/exchangedb/0002-auditors.sql
@@ -0,0 +1,35 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE TABLE auditors
+ (auditor_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,auditor_pub BYTEA PRIMARY KEY CHECK (LENGTH(auditor_pub)=32)
+ ,auditor_name TEXT NOT NULL
+ ,auditor_url TEXT NOT NULL
+ ,is_active BOOLEAN NOT NULL
+ ,last_change INT8 NOT NULL
+ );
+COMMENT ON TABLE auditors
+ IS 'Table with auditors the exchange uses or has used in the past. Entries never expire as we need to remember the last_change column indefinitely.';
+COMMENT ON COLUMN auditors.auditor_pub
+ IS 'Public key of the auditor.';
+COMMENT ON COLUMN auditors.auditor_url
+ IS 'The base URL of the auditor.';
+COMMENT ON COLUMN auditors.is_active
+ IS 'true if we are currently supporting the use of this auditor.';
+COMMENT ON COLUMN auditors.last_change
+ IS 'Latest time when active status changed. Used to detect replays of old messages.';
diff --git a/src/exchangedb/0002-batch_deposits.sql b/src/exchangedb/0002-batch_deposits.sql
new file mode 100644
index 000000000..71a4b4205
--- /dev/null
+++ b/src/exchangedb/0002-batch_deposits.sql
@@ -0,0 +1,172 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_batch_deposits(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'batch_deposits';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(batch_deposit_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY'
+ ',shard INT8 NOT NULL'
+ ',merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)'
+ ',wallet_timestamp INT8 NOT NULL'
+ ',exchange_timestamp INT8 NOT NULL'
+ ',refund_deadline INT8 NOT NULL'
+ ',wire_deadline INT8 NOT NULL'
+ ',h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)'
+ ',wallet_data_hash BYTEA CHECK (LENGTH(wallet_data_hash)=64) DEFAULT NULL'
+ ',wire_salt BYTEA NOT NULL CHECK (LENGTH(wire_salt)=16)'
+ ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
+ ',policy_details_serial_id INT8'
+ ',policy_blocked BOOLEAN NOT NULL DEFAULT FALSE'
+ ',done BOOLEAN NOT NULL DEFAULT FALSE'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (shard)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Information about the contracts for which we have received (batch) deposits.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Used for load sharding in the materialized indices. Should be set based on merchant_pub. 64-bit value because we need an *unsigned* 32-bit value.'
+ ,'shard'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Unsalted hash of the target bank account; also used to lookup the KYC status'
+ ,'wire_target_h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash over data provided by the wallet upon payment to select a more specific contract'
+ ,'wallet_data_hash'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Salt used when hashing the payto://-URI to get the h_wire that was used by the coin deposit signatures; not used to calculate wire_target_h_payto (as that one is unsalted)'
+ ,'wire_salt'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Set to TRUE once we have included this (batch) deposit (and all associated coins) in some aggregate wire transfer to the merchant'
+ ,'done'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'True if the aggregation of the (batch) deposit is currently blocked by some policy extension mechanism. Used to filter out deposits that must not be processed by the canonical deposit logic.'
+ ,'policy_blocked'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'References policy extensions table, NULL if extensions are not used'
+ ,'policy_details_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_batch_deposits(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'batch_deposits';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_batch_deposit_serial_id_pkey'
+ ' PRIMARY KEY (batch_deposit_serial_id) '
+ ',ADD CONSTRAINT ' || table_name || '_merchant_pub_h_contract_terms'
+ ' UNIQUE (shard, merchant_pub, h_contract_terms)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_ready '
+ 'ON ' || table_name || ' '
+ '(shard ASC'
+ ',wire_deadline ASC'
+ ') WHERE NOT (done OR policy_blocked);'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_for_matching '
+ 'ON ' || table_name || ' '
+ '(shard ASC'
+ ',refund_deadline ASC'
+ ',wire_target_h_payto'
+ ') WHERE NOT (done OR policy_blocked);'
+ );
+END
+$$;
+
+CREATE OR REPLACE FUNCTION foreign_table_batch_deposits()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'batch_deposits';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_policy_details'
+ ' FOREIGN KEY (policy_details_serial_id) '
+ ' REFERENCES policy_details (policy_details_serial_id) ON DELETE RESTRICT'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('batch_deposits'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('batch_deposits'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('batch_deposits'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE)
+ ;
diff --git a/src/exchangedb/0002-close_requests.sql b/src/exchangedb/0002-close_requests.sql
new file mode 100644
index 000000000..6a7028095
--- /dev/null
+++ b/src/exchangedb/0002-close_requests.sql
@@ -0,0 +1,178 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_close_requests(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'close_requests';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(close_request_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
+ ',close_timestamp INT8 NOT NULL'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',close taler_amount NOT NULL'
+ ',close_fee taler_amount NOT NULL'
+ ',payto_uri TEXT NOT NULL'
+ ',done BOOL NOT NULL DEFAULT(FALSE)'
+ ',PRIMARY KEY (reserve_pub,close_timestamp)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Explicit requests by a reserve owner to close a reserve immediately'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'When the request was created by the client'
+ ,'close_timestamp'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature affirming that the reserve is to be closed'
+ ,'reserve_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Balance of the reserve at the time of closing, to be wired to the associated bank account (minus the closing fee)'
+ ,'close'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the credited bank account. Optional.'
+ ,'payto_uri'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+
+CREATE FUNCTION constrain_table_close_requests(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'close_requests';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_close_request_uuid_index '
+ 'ON ' || table_name || ' '
+ '(close_request_serial_id);'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_close_request_done_index '
+ 'ON ' || table_name || ' '
+ '(done);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_close_request_uuid_pkey'
+ ' UNIQUE (close_request_serial_id)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_close_requests()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'close_requests';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub'
+ ' FOREIGN KEY (reserve_pub) '
+ ' REFERENCES reserves(reserve_pub) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION close_requests_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.reserve_pub
+ ,'close_requests'
+ ,NEW.close_request_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION close_requests_insert_trigger()
+ IS 'Automatically generate reserve history entry.';
+
+
+CREATE FUNCTION master_table_close_requests()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER close_requests_on_insert
+ AFTER INSERT
+ ON close_requests
+ FOR EACH ROW EXECUTE FUNCTION close_requests_insert_trigger();
+END $$;
+
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('close_requests'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('close_requests'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('close_requests'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('close_requests'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-coin_deposits.sql b/src/exchangedb/0002-coin_deposits.sql
new file mode 100644
index 000000000..c3eef6e5a
--- /dev/null
+++ b/src/exchangedb/0002-coin_deposits.sql
@@ -0,0 +1,157 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_coin_deposits(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'coin_deposits';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(coin_deposit_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY'
+ ',batch_deposit_serial_id INT8 NOT NULL'
+ ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+ ',coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)'
+ ',amount_with_fee taler_amount NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Coins which have been deposited with the respective per-coin signatures.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Link to information about the batch deposit this coin was used for'
+ ,'batch_deposit_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_coin_deposits(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'coin_deposits';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_coin_deposit_serial_id_pkey'
+ ' PRIMARY KEY (coin_deposit_serial_id) '
+ ',ADD CONSTRAINT ' || table_name || '_unique_coin_sig'
+ ' UNIQUE (coin_pub, coin_sig)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_batch '
+ 'ON ' || table_name || ' '
+ '(batch_deposit_serial_id);'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_coin_deposits()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'coin_deposits';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (coin_pub) '
+ ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_batch_deposits_id'
+ ' FOREIGN KEY (batch_deposit_serial_id) '
+ ' REFERENCES batch_deposits (batch_deposit_serial_id) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION coin_deposits_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.coin_pub
+ ,'coin_deposits'
+ ,NEW.coin_deposit_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION coin_deposits_insert_trigger()
+ IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_coin_deposits()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER coin_deposits_on_insert
+ AFTER INSERT
+ ON coin_deposits
+ FOR EACH ROW EXECUTE FUNCTION coin_deposits_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('coin_deposits'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('coin_deposits'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('coin_deposits'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('coin_deposits'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE)
+ ;
diff --git a/src/exchangedb/0002-coin_history.sql b/src/exchangedb/0002-coin_history.sql
new file mode 100644
index 000000000..9b5efdcb6
--- /dev/null
+++ b/src/exchangedb/0002-coin_history.sql
@@ -0,0 +1,138 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_coin_history (
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'coin_history';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(coin_history_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY'
+ ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+ ',table_name TEXT NOT NULL'
+ ',serial_id INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Links to tables with entries that affected the transaction history of a coin.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'For which coin is this a history entry'
+ ,'coin_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'In which table is the history entry'
+ ,'table_name'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Which is the generated serial ID of the entry in the table'
+ ,'serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Monotonic counter, used to generate Etags for caching'
+ ,'coin_history_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_coin_history(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'coin_history';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_coin_history_serial_id_pkey'
+ ' PRIMARY KEY (coin_history_serial_id) '
+ ',ADD CONSTRAINT ' || table_name || '_coin_entry_key'
+ ' UNIQUE (coin_pub, table_name, serial_id)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_coin_by_time'
+ ' ON ' || table_name || ' '
+ '(coin_pub'
+ ',coin_history_serial_id DESC'
+ ');'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_coin_history()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'coin_history';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (coin_pub) '
+ ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('coin_history'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('coin_history'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('coin_history'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE)
+ ;
diff --git a/src/exchangedb/0002-contracts.sql b/src/exchangedb/0002-contracts.sql
new file mode 100644
index 000000000..c1f92c9aa
--- /dev/null
+++ b/src/exchangedb/0002-contracts.sql
@@ -0,0 +1,109 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE FUNCTION create_table_contracts(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'contracts';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(contract_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ',pub_ckey BYTEA NOT NULL CHECK (LENGTH(pub_ckey)=32)'
+ ',contract_sig BYTEA NOT NULL CHECK (LENGTH(contract_sig)=64)'
+ ',e_contract BYTEA NOT NULL'
+ ',purse_expiration INT8 NOT NULL'
+ ',PRIMARY KEY (purse_pub)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'encrypted contracts associated with purses'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public key of the purse that the contract is associated with'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'signature over the encrypted contract by the purse contract key'
+ ,'contract_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public ECDH key used to encrypt the contract, to be used with the purse private key for decryption'
+ ,'pub_ckey'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'AES-GCM encrypted contract terms (contains gzip compressed JSON after decryption)'
+ ,'e_contract'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_contracts(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'contracts';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_contract_serial_id_key'
+ ' UNIQUE (contract_serial_id) '
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('contracts'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('contracts'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-cs_nonce_locks.sql b/src/exchangedb/0002-cs_nonce_locks.sql
new file mode 100644
index 000000000..36c0c7a5f
--- /dev/null
+++ b/src/exchangedb/0002-cs_nonce_locks.sql
@@ -0,0 +1,97 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_cs_nonce_locks(
+ partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(cs_nonce_lock_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',nonce BYTEA PRIMARY KEY CHECK (LENGTH(nonce)=32)'
+ ',op_hash BYTEA NOT NULL CHECK (LENGTH(op_hash)=64)'
+ ',max_denomination_serial INT8 NOT NULL'
+ ') %s ;'
+ ,'cs_nonce_locks'
+ ,'PARTITION BY HASH (nonce)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'ensures a Clause Schnorr client nonce is locked for use with an operation identified by a hash'
+ ,'cs_nonce_locks'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'actual nonce submitted by the client'
+ ,'nonce'
+ ,'cs_nonce_locks'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash (RC for refresh, blind coin hash for withdraw) the nonce may be used with'
+ ,'op_hash'
+ ,'cs_nonce_locks'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Maximum number of a CS denomination serial the nonce could be used with, for GC'
+ ,'max_denomination_serial'
+ ,'cs_nonce_locks'
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_cs_nonce_locks(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'cs_nonce_locks';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_cs_nonce_lock_serial_id_key'
+ ' UNIQUE (cs_nonce_lock_serial_id)'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('cs_nonce_locks'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('cs_nonce_locks'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-denomination_revocations.sql b/src/exchangedb/0002-denomination_revocations.sql
new file mode 100644
index 000000000..96e13cd15
--- /dev/null
+++ b/src/exchangedb/0002-denomination_revocations.sql
@@ -0,0 +1,23 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE TABLE IF NOT EXISTS denomination_revocations
+ (denom_revocations_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,denominations_serial INT8 PRIMARY KEY REFERENCES denominations (denominations_serial) ON DELETE CASCADE
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ );
+COMMENT ON TABLE denomination_revocations
+ IS 'remembering which denomination keys have been revoked';
diff --git a/src/exchangedb/0002-denominations.sql b/src/exchangedb/0002-denominations.sql
new file mode 100644
index 000000000..a3de2b149
--- /dev/null
+++ b/src/exchangedb/0002-denominations.sql
@@ -0,0 +1,45 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE TABLE denominations
+ (denominations_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)
+ ,denom_type INT4 NOT NULL DEFAULT (1) -- 1 == RSA (for now, remove default later!)
+ ,age_mask INT4 NOT NULL DEFAULT (0)
+ ,denom_pub BYTEA NOT NULL
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,valid_from INT8 NOT NULL
+ ,expire_withdraw INT8 NOT NULL
+ ,expire_deposit INT8 NOT NULL
+ ,expire_legal INT8 NOT NULL
+ ,coin taler_amount NOT NULL
+ ,fee_withdraw taler_amount NOT NULL
+ ,fee_deposit taler_amount NOT NULL
+ ,fee_refresh taler_amount NOT NULL
+ ,fee_refund taler_amount NOT NULL
+ );
+COMMENT ON TABLE denominations
+ IS 'Main denominations table. All the valid denominations the exchange knows about.';
+COMMENT ON COLUMN denominations.denom_type
+ IS 'determines cipher type for blind signatures used with this denomination; 0 is for RSA';
+COMMENT ON COLUMN denominations.age_mask
+ IS 'bitmask with the age restrictions that are being used for this denomination; 0 if denomination does not support the use of age restrictions';
+COMMENT ON COLUMN denominations.denominations_serial
+ IS 'needed for exchange-auditor replication logic';
+
+CREATE INDEX denominations_by_expire_legal_index
+ ON denominations
+ (expire_legal);
diff --git a/src/exchangedb/0002-exchange_sign_keys.sql b/src/exchangedb/0002-exchange_sign_keys.sql
new file mode 100644
index 000000000..d6acc6bb0
--- /dev/null
+++ b/src/exchangedb/0002-exchange_sign_keys.sql
@@ -0,0 +1,36 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE TABLE exchange_sign_keys
+ (esk_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,exchange_pub BYTEA PRIMARY KEY CHECK (LENGTH(exchange_pub)=32)
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,valid_from INT8 NOT NULL
+ ,expire_sign INT8 NOT NULL
+ ,expire_legal INT8 NOT NULL
+ );
+COMMENT ON TABLE exchange_sign_keys
+ IS 'Table with master public key signatures on exchange online signing keys.';
+COMMENT ON COLUMN exchange_sign_keys.exchange_pub
+ IS 'Public online signing key of the exchange.';
+COMMENT ON COLUMN exchange_sign_keys.master_sig
+ IS 'Signature affirming the validity of the signing key of purpose TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY.';
+COMMENT ON COLUMN exchange_sign_keys.valid_from
+ IS 'Time when this online signing key will first be used to sign messages.';
+COMMENT ON COLUMN exchange_sign_keys.expire_sign
+ IS 'Time when this online signing key will no longer be used to sign.';
+COMMENT ON COLUMN exchange_sign_keys.expire_legal
+ IS 'Time when this online signing key legally expires.';
diff --git a/src/exchangedb/0002-extensions.sql b/src/exchangedb/0002-extensions.sql
new file mode 100644
index 000000000..df9e50090
--- /dev/null
+++ b/src/exchangedb/0002-extensions.sql
@@ -0,0 +1,27 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE TABLE extensions
+ (extension_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,name TEXT NOT NULL UNIQUE
+ ,manifest BYTEA
+ );
+COMMENT ON TABLE extensions
+ IS 'Configurations of the activated extensions';
+COMMENT ON COLUMN extensions.name
+ IS 'Name of the extension';
+COMMENT ON COLUMN extensions.manifest
+ IS 'Manifest of the extension as JSON-blob, maybe NULL. It contains common meta-information and extension-specific configuration.';
diff --git a/src/exchangedb/0002-global_fee.sql b/src/exchangedb/0002-global_fee.sql
new file mode 100644
index 000000000..f6526798d
--- /dev/null
+++ b/src/exchangedb/0002-global_fee.sql
@@ -0,0 +1,37 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE TABLE global_fee
+ (global_fee_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,start_date INT8 NOT NULL
+ ,end_date INT8 NOT NULL
+ ,history_fee taler_amount NOT NULL
+ ,account_fee taler_amount NOT NULL
+ ,purse_fee taler_amount NOT NULL
+ ,purse_timeout INT8 NOT NULL
+ ,history_expiration INT8 NOT NULL
+ ,purse_account_limit INT4 NOT NULL
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,PRIMARY KEY (start_date)
+ );
+COMMENT ON TABLE global_fee
+ IS 'list of the global fees of this exchange, by date';
+COMMENT ON COLUMN global_fee.global_fee_serial
+ IS 'needed for exchange-auditor replication logic';
+
+CREATE INDEX global_fee_by_end_date_index
+ ON global_fee
+ (end_date);
diff --git a/src/exchangedb/0002-history_requests.sql b/src/exchangedb/0002-history_requests.sql
new file mode 100644
index 000000000..0714e1bea
--- /dev/null
+++ b/src/exchangedb/0002-history_requests.sql
@@ -0,0 +1,159 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE FUNCTION create_table_history_requests(
+ IN shard_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'history_requests';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(history_request_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
+ ',request_timestamp INT8 NOT NULL'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',history_fee taler_amount NOT NULL'
+ ',PRIMARY KEY (reserve_pub,request_timestamp)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Paid history requests issued by a client against a reserve'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'When was the history request made'
+ ,'request_timestamp'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature approving payment for the history request'
+ ,'reserve_sig'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'History fee approved by the signature'
+ ,'history_fee'
+ ,table_name
+ ,shard_suffix
+ );
+END $$;
+
+
+CREATE FUNCTION constrain_table_history_requests(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ partition_name TEXT;
+BEGIN
+ partition_name = concat_ws('_', 'history_requests', partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || partition_name ||
+ ' ADD CONSTRAINT ' || partition_name || '_serial_id'
+ ' UNIQUE (history_request_serial_id)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_history_requests()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'history_requests';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub'
+ ' FOREIGN KEY (reserve_pub) '
+ ' REFERENCES reserves(reserve_pub) ON DELETE CASCADE'
+ );
+END $$;
+
+
+CREATE OR REPLACE FUNCTION history_requests_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.reserve_pub
+ ,'history_requests'
+ ,NEW.history_request_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION history_requests_insert_trigger()
+ IS 'Automatically generate reserve history entry.';
+
+
+CREATE FUNCTION master_table_history_requests()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER history_requests_on_insert
+ AFTER INSERT
+ ON history_requests
+ FOR EACH ROW EXECUTE FUNCTION history_requests_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('history_requests'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('history_requests'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('history_requests'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('history_requests'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-known_coins.sql b/src/exchangedb/0002-known_coins.sql
new file mode 100644
index 000000000..a13beff6f
--- /dev/null
+++ b/src/exchangedb/0002-known_coins.sql
@@ -0,0 +1,136 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE FUNCTION create_table_known_coins(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'known_coins';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(known_coin_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',denominations_serial INT8 NOT NULL'
+ ',coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32)'
+ ',age_commitment_hash BYTEA CHECK (LENGTH(age_commitment_hash)=32)'
+ ',denom_sig BYTEA NOT NULL'
+ ',remaining taler_amount NOT NULL DEFAULT(0,0)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'information about coins and their signatures, so we do not have to store the signatures more than once if a coin is involved in multiple operations'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Denomination of the coin, determines the value of the original coin and applicable fees for coin-specific operations.'
+ ,'denominations_serial'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'EdDSA public key of the coin'
+ ,'coin_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Value of the coin that remains to be spent'
+ ,'remaining'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Optional hash of the age commitment for age restrictions as per DD 24 (active if denom_type has the respective bit set)'
+ ,'age_commitment_hash'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'This is the signature of the exchange that affirms that the coin is a valid coin. The specific signature type depends on denom_type of the denomination.'
+ ,'denom_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_known_coins(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'known_coins';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_known_coin_id_key'
+ ' UNIQUE (known_coin_id)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_known_coins()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'known_coins';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_denominations'
+ ' FOREIGN KEY (denominations_serial) '
+ ' REFERENCES denominations (denominations_serial) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('known_coins'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('known_coins'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('known_coins'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-kyc_alerts.sql b/src/exchangedb/0002-kyc_alerts.sql
new file mode 100644
index 000000000..8e54846cf
--- /dev/null
+++ b/src/exchangedb/0002-kyc_alerts.sql
@@ -0,0 +1,27 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE TABLE kyc_alerts
+ (h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=32)
+ ,trigger_type INT4 NOT NULL
+ ,UNIQUE(trigger_type,h_payto)
+ );
+COMMENT ON TABLE kyc_alerts
+ IS 'alerts about completed KYC events reliably notifying other components (even if they are not running)';
+COMMENT ON COLUMN kyc_alerts.h_payto
+ IS 'hash of the payto://-URI for which the KYC status changed';
+COMMENT ON COLUMN kyc_alerts.trigger_type
+ IS 'identifies the receiver of the alert, as the same h_payto may require multiple components to be notified';
diff --git a/src/exchangedb/0002-kyc_attributes.sql b/src/exchangedb/0002-kyc_attributes.sql
new file mode 100644
index 000000000..66f3fc315
--- /dev/null
+++ b/src/exchangedb/0002-kyc_attributes.sql
@@ -0,0 +1,162 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION create_table_kyc_attributes(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'kyc_attributes';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I'
+ '(kyc_attributes_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_payto BYTEA PRIMARY KEY CHECK (LENGTH(h_payto)=32)'
+ ',kyc_prox BYTEA NOT NULL CHECK (LENGTH(kyc_prox)=32)'
+ ',provider TEXT NOT NULL'
+ ',satisfied_checks TEXT[] NOT NULL'
+ ',collection_time INT8 NOT NULL'
+ ',expiration_time INT8 NOT NULL'
+ ',encrypted_attributes BYTEA NOT NULL'
+ ',legitimization_serial INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (h_payto)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'KYC data about particular payment addresses'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash of payto://-URI the attributes are about'
+ ,'h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'short hash of normalized full name and birthdate; used to efficiently find likely duplicate users'
+ ,'kyc_prox'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'time when the attributes were collected by the provider'
+ ,'collection_time'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'time when the attributes should no longer be considered validated'
+ ,'expiration_time'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'configuration section name of the provider that affirmed the attributes'
+ ,'provider'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ '(encrypted) JSON object (as string) with the attributes'
+ ,'encrypted_attributes'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Reference the legitimization process for which these attributes are gathered for.'
+ ,'legitimization_serial'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+COMMENT ON FUNCTION create_table_kyc_attributes
+ IS 'Creates the kyc_attributes table';
+
+
+CREATE OR REPLACE FUNCTION constrain_table_kyc_attributes(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'kyc_attributes';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_serial_key '
+ 'UNIQUE (kyc_attributes_serial_id)'
+ );
+ -- To search similar users (e.g. during AML checks)
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_similarity_index '
+ 'ON ' || table_name || ' '
+ '(kyc_prox);'
+ );
+ -- For garbage collection
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_expiration_time '
+ 'ON ' || table_name || ' '
+ '(expiration_time ASC);'
+ );
+END $$;
+
+
+CREATE OR REPLACE FUNCTION foreign_table_kyc_attributes()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'kyc_attributes';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_legitimization_processes'
+ ' FOREIGN KEY (legitimization_serial) '
+ ' REFERENCES legitimization_processes (legitimization_process_serial_id)' -- ON DELETE CASCADE
+ );
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('kyc_attributes'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('kyc_attributes'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('kyc_attributes'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-legitimization_processes.sql b/src/exchangedb/0002-legitimization_processes.sql
new file mode 100644
index 000000000..3212b1c06
--- /dev/null
+++ b/src/exchangedb/0002-legitimization_processes.sql
@@ -0,0 +1,149 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_legitimization_processes(
+ IN shard_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(legitimization_process_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=32)'
+ ',start_time INT8 NOT NULL'
+ ',expiration_time INT8 NOT NULL DEFAULT (0)'
+ ',provider_section TEXT NOT NULL'
+ ',provider_user_id TEXT DEFAULT NULL'
+ ',provider_legitimization_id TEXT DEFAULT NULL'
+ ',redirect_url TEXT DEFAULT NULL'
+ ',finished BOOLEAN DEFAULT (FALSE)'
+ ') %s ;'
+ ,'legitimization_processes'
+ ,'PARTITION BY HASH (h_payto)'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'List of legitimization processes (ongoing and completed) by account and provider'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'unique ID for this legitimization process at the exchange'
+ ,'legitimization_process_serial_id'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'foreign key linking the entry to the wire_targets table, NOT a primary key (multiple legitimizations are possible per wire target)'
+ ,'h_payto'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'time when the KYC check was initiated, useful for garbage collection'
+ ,'expiration_time'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'URL where the user should go to begin the KYC process'
+ ,'redirect_url'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'in the future if the respective KYC check was passed successfully'
+ ,'expiration_time'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Configuration file section with details about this provider'
+ ,'provider_section'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifier for the user at the provider that was used for the legitimization. NULL if provider is unaware.'
+ ,'provider_user_id'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifier for the specific legitimization process at the provider. NULL if legitimization was not started.'
+ ,'provider_legitimization_id'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Set to TRUE when the specific legitimization process is finished.'
+ ,'finished'
+ ,'legitimization_processes'
+ ,shard_suffix
+ );
+END
+$$;
+
+-- We need a separate function for this, as we call create_table only once but need to add
+-- those constraints to each partition which gets created
+CREATE FUNCTION constrain_table_legitimization_processes(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ partition_name TEXT;
+BEGIN
+ partition_name = concat_ws('_', 'legitimization_processes', partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || partition_name
+ || ' '
+ 'ADD CONSTRAINT ' || partition_name || '_serial_key '
+ 'UNIQUE (legitimization_process_serial_id)');
+ EXECUTE FORMAT (
+ 'CREATE INDEX IF NOT EXISTS ' || partition_name || '_by_provider_and_legi_index '
+ 'ON '|| partition_name || ' '
+ '(provider_section,provider_legitimization_id)'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || partition_name || '_by_provider_and_legi_index '
+ 'IS ' || quote_literal('used (rarely) in kyc_provider_account_lookup') || ';'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('legitimization_processes'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('legitimization_processes'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-legitimization_requirements.sql b/src/exchangedb/0002-legitimization_requirements.sql
new file mode 100644
index 000000000..d806eb424
--- /dev/null
+++ b/src/exchangedb/0002-legitimization_requirements.sql
@@ -0,0 +1,104 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_legitimization_requirements(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(legitimization_requirement_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_payto BYTEA NOT NULL CHECK (LENGTH(h_payto)=32)'
+ ',reserve_pub BYTEA'
+ ',required_checks TEXT NOT NULL'
+ ',UNIQUE (h_payto, required_checks)'
+ ') %s ;'
+ ,'legitimization_requirements'
+ ,'PARTITION BY HASH (h_payto)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'List of required legitimizations by account'
+ ,'legitimization_requirements'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'unique ID for this legitimization requirement at the exchange'
+ ,'legitimization_requirement_serial_id'
+ ,'legitimization_requirements'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'foreign key linking the entry to the wire_targets table, NOT a primary key (multiple legitimizations are possible per wire target)'
+ ,'h_payto'
+ ,'legitimization_requirements'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'if h_payto refers to a reserve, this is its public key, NULL otherwise. It allows to lookup the corresponding reserve when the KYC process is done.'
+ ,'reserve_pub'
+ ,'legitimization_requirements'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'space-separated list of required checks'
+ ,'required_checks'
+ ,'legitimization_requirements'
+ ,partition_suffix
+ );
+END
+$$;
+
+-- We need a separate function for this, as we call create_table only once but need to add
+-- those constraints to each partition which gets created
+CREATE FUNCTION constrain_table_legitimization_requirements(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ partition_name TEXT;
+BEGIN
+ partition_name = concat_ws('_', 'legitimization_requirements', partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || partition_name || ' '
+ 'ADD CONSTRAINT ' || partition_name || '_serial_id_key '
+ 'UNIQUE (legitimization_requirement_serial_id)');
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('legitimization_requirements'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('legitimization_requirements'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-partner_accounts.sql b/src/exchangedb/0002-partner_accounts.sql
new file mode 100644
index 000000000..b12dc06e6
--- /dev/null
+++ b/src/exchangedb/0002-partner_accounts.sql
@@ -0,0 +1,33 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE TABLE partner_accounts
+ (payto_uri TEXT PRIMARY KEY
+ ,partner_serial_id INT8 REFERENCES partners(partner_serial_id) ON DELETE CASCADE
+ ,partner_master_sig BYTEA CHECK (LENGTH(partner_master_sig)=64)
+ ,last_seen INT8 NOT NULL
+ );
+CREATE INDEX IF NOT EXISTS partner_accounts_index_by_partner_and_time
+ ON partner_accounts (partner_serial_id,last_seen);
+COMMENT ON TABLE partner_accounts
+ IS 'Table with bank accounts of the partner exchange. Entries never expire as we need to remember the signature for the auditor.';
+COMMENT ON COLUMN partner_accounts.payto_uri
+ IS 'payto URI (RFC 8905) with the bank account of the partner exchange.';
+COMMENT ON COLUMN partner_accounts.partner_master_sig
+ IS 'Signature of purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS by the partner master public key';
+COMMENT ON COLUMN partner_accounts.last_seen
+ IS 'Last time we saw this account as being active at the partner exchange. Used to select the most recent entry, and to detect when we should check again.';
diff --git a/src/exchangedb/0002-partners.sql b/src/exchangedb/0002-partners.sql
new file mode 100644
index 000000000..ed09378d0
--- /dev/null
+++ b/src/exchangedb/0002-partners.sql
@@ -0,0 +1,49 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE TABLE partners
+ (partner_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,partner_master_pub BYTEA NOT NULL CHECK(LENGTH(partner_master_pub)=32)
+ ,start_date INT8 NOT NULL
+ ,end_date INT8 NOT NULL
+ ,next_wad INT8 NOT NULL DEFAULT (0)
+ ,wad_frequency INT8 NOT NULL
+ ,wad_fee taler_amount NOT NULL
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,partner_base_url TEXT NOT NULL
+ ,PRIMARY KEY (partner_master_pub, start_date)
+ );
+COMMENT ON TABLE partners
+ IS 'exchanges we do wad transfers to';
+COMMENT ON COLUMN partners.partner_master_pub
+ IS 'offline master public key of the partner';
+COMMENT ON COLUMN partners.start_date
+ IS 'starting date of the partnership';
+COMMENT ON COLUMN partners.end_date
+ IS 'end date of the partnership';
+COMMENT ON COLUMN partners.next_wad
+ IS 'at what time should we do the next wad transfer to this partner (frequently updated); set to forever after the end_date';
+COMMENT ON COLUMN partners.wad_frequency
+ IS 'how often do we promise to do wad transfers';
+COMMENT ON COLUMN partners.wad_fee
+ IS 'how high is the fee for a wallet to be added to a wad to this partner';
+COMMENT ON COLUMN partners.partner_base_url
+ IS 'base URL of the REST API for this partner';
+COMMENT ON COLUMN partners.master_sig
+ IS 'signature of our master public key affirming the partnership, of purpose TALER_SIGNATURE_MASTER_PARTNER_DETAILS';
+
+CREATE INDEX IF NOT EXISTS partner_by_wad_time
+ ON partners (next_wad ASC);
diff --git a/src/exchangedb/0002-policy_details.sql b/src/exchangedb/0002-policy_details.sql
new file mode 100644
index 000000000..3acbb5c10
--- /dev/null
+++ b/src/exchangedb/0002-policy_details.sql
@@ -0,0 +1,175 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+-- @author: Özgür Kesim
+
+CREATE FUNCTION create_table_policy_details(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'policy_details';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(policy_details_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',policy_hash_code gnunet_hashcode NOT NULL'
+ ',policy_json TEXT NOT NULL'
+ ',deadline INT8 NOT NULL'
+ ',commitment taler_amount NOT NULL'
+ ',accumulated_total taler_amount NOT NULL'
+ ',fee taler_amount NOT NULL'
+ ',transferable taler_amount NOT NULL'
+ ',fulfillment_state SMALLINT NOT NULL CHECK(fulfillment_state between 0 and 5)'
+ ',h_fulfillment_proof gnunet_hashcode'
+ ') %s;'
+ ,table_name
+ ,'PARTITION BY HASH (h_fulfillment_proof)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Policies that were provided with deposits via policy extensions.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'ID (GNUNET_HashCode) that identifies a policy. Will be calculated by the policy extension based on the content'
+ ,'policy_hash_code'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'JSON object with options set that the exchange needs to consider when executing a deposit. Supported details depend on the policy extensions supported by the exchange.'
+ ,'policy_json'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Deadline until the policy must be marked as fulfilled (maybe "forever")'
+ ,'deadline'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The amount that this policy commits to. Invariant: commitment >= fee'
+ ,'commitment'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The sum of all contributions of all deposit that reference this policy. Invariant: The fulfilment_state must be Insufficient as long as accumulated_total < commitment'
+ ,'accumulated_total'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The fee for this policy, due when the policy is fulfilled or timed out'
+ ,'fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The amount that on fulfillment or timeout will be transferred to the payto-URI''s of the corresponding deposit''s. The policy fees must have been already deducted from it. Invariant: fee+transferable <= accumulated_total. The remaining amount (accumulated_total - fee - transferable) can be refreshed by the owner of the coins when the state is Timeout or Success.'
+ ,'transferable'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'State of the fulfillment:
+ - 0 (Failure)
+ - 1 (Insufficient)
+ - 2 (Ready)
+ - 4 (Success)
+ - 5 (Timeout)'
+ ,'fulfillment_state'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Reference to the proof of the fulfillment of this policy, if it exists. Invariant: If not NULL, this entry''s .hash_code MUST be part of the corresponding policy_fulfillments.policy_hash_codes array.'
+ ,'h_fulfillment_proof'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+COMMENT ON FUNCTION create_table_policy_details
+ IS 'Creates the policy_details table';
+
+
+
+
+CREATE FUNCTION constrain_table_policy_details(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ partition_name TEXT;
+BEGIN
+ partition_name = concat_ws('_', 'policy_details', partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || partition_name ||
+ ' ADD CONSTRAINT ' || partition_name || '_unique_serial_id '
+ ' UNIQUE (policy_details_serial_id)'
+ );
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || partition_name ||
+ ' ADD CONSTRAINT ' || partition_name || '_unique_hash_fulfillment_proof '
+ ' UNIQUE (policy_hash_code, h_fulfillment_proof)'
+ );
+
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || partition_name || '_policy_hash_code'
+ ' ON ' || partition_name ||
+ ' (policy_hash_code);'
+ );
+END
+$$;
+
+CREATE OR REPLACE FUNCTION foreign_table_policy_details()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'policy_details';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_policy_fulfillments'
+ ' FOREIGN KEY (h_fulfillment_proof) '
+ ' REFERENCES policy_fulfillments (h_fulfillment_proof) ON DELETE RESTRICT'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+VALUES
+ ('policy_details', 'exchange-0002', 'create', TRUE ,FALSE),
+ ('policy_details', 'exchange-0002', 'constrain', TRUE ,FALSE),
+ ('policy_details', 'exchange-0002', 'foreign', TRUE ,FALSE);
diff --git a/src/exchangedb/0002-policy_fulfillments.sql b/src/exchangedb/0002-policy_fulfillments.sql
new file mode 100644
index 000000000..c00947019
--- /dev/null
+++ b/src/exchangedb/0002-policy_fulfillments.sql
@@ -0,0 +1,101 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+-- @author: Özgür Kesim
+
+CREATE FUNCTION create_table_policy_fulfillments(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'policy_fulfillments';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(h_fulfillment_proof gnunet_hashcode PRIMARY KEY'
+ ',fulfillment_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',fulfillment_timestamp INT8 NOT NULL'
+ ',fulfillment_proof TEXT'
+ ',policy_hash_codes gnunet_hashcode[] NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (h_fulfillment_proof)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Proofs of fulfillment of policies that were set in deposits'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Timestamp of the arrival of a proof of fulfillment'
+ ,'fulfillment_timestamp'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'JSON object with a proof of the fulfillment of a policy. Supported details depend on the policy extensions supported by the exchange.'
+ ,'fulfillment_proof'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Hash of the fulfillment_proof'
+ ,'h_fulfillment_proof'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Array of the policy_hash_code''s of all policy_details that are fulfilled by this proof'
+ ,'policy_hash_codes'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+COMMENT ON FUNCTION create_table_policy_fulfillments
+ IS 'Creates the policy_fulfillments table';
+
+CREATE FUNCTION constrain_table_policy_fulfillments(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ partition_name TEXT;
+BEGIN
+ partition_name = concat_ws('_', 'policy_fulfillments', partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || partition_name ||
+ ' ADD CONSTRAINT ' || partition_name || '_serial_id '
+ ' UNIQUE (h_fulfillment_proof, fulfillment_id)'
+ );
+END
+$$;
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+VALUES
+ ('policy_fulfillments', 'exchange-0002', 'create', TRUE ,FALSE),
+ ('policy_fulfillments', 'exchange-0002', 'constrain', TRUE ,FALSE);
diff --git a/src/exchangedb/0002-prewire.sql b/src/exchangedb/0002-prewire.sql
new file mode 100644
index 000000000..396a27608
--- /dev/null
+++ b/src/exchangedb/0002-prewire.sql
@@ -0,0 +1,116 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_prewire(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'prewire';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(prewire_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY'
+ ',wire_method TEXT NOT NULL'
+ ',finished BOOLEAN NOT NULL DEFAULT FALSE'
+ ',failed BOOLEAN NOT NULL DEFAULT FALSE'
+ ',buf BYTEA NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (prewire_uuid)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'pre-commit data for wire transfers we are about to execute'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'set to TRUE if the bank responded with a non-transient failure to our transfer request'
+ ,'failed'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'set to TRUE once bank confirmed receiving the wire transfer request'
+ ,'finished'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'serialized data to send to the bank to execute the wire transfer'
+ ,'buf'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_prewire(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'prewire';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_finished_index '
+ 'ON ' || table_name || ' '
+ '(finished)'
+ ' WHERE finished;'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_finished_index '
+ 'IS ' || quote_literal('for do_gc') || ';'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_failed_finished_index '
+ 'ON ' || table_name || ' '
+ '(prewire_uuid)'
+ ' WHERE finished=FALSE'
+ ' AND failed=FALSE;'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_failed_finished_index '
+ 'IS ' || quote_literal('for wire_prepare_data_get') || ';'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('prewire'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('prewire'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-profit_drains.sql b/src/exchangedb/0002-profit_drains.sql
new file mode 100644
index 000000000..c4f3a7bd0
--- /dev/null
+++ b/src/exchangedb/0002-profit_drains.sql
@@ -0,0 +1,42 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE TABLE profit_drains
+ (profit_drain_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,wtid BYTEA PRIMARY KEY CHECK (LENGTH(wtid)=32)
+ ,account_section TEXT NOT NULL
+ ,payto_uri TEXT NOT NULL
+ ,trigger_date INT8 NOT NULL
+ ,amount taler_amount NOT NULL
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,executed BOOLEAN NOT NULL DEFAULT FALSE
+ );
+COMMENT ON TABLE profit_drains
+ IS 'transactions to be performed to move profits from the escrow account of the exchange to a regular account';
+COMMENT ON COLUMN profit_drains.wtid
+ IS 'randomly chosen nonce, unique to prevent double-submission';
+COMMENT ON COLUMN profit_drains.account_section
+ IS 'specifies the configuration section in the taler-exchange-drain configuration with the wire account to drain';
+COMMENT ON COLUMN profit_drains.payto_uri
+ IS 'specifies the account to be credited';
+COMMENT ON COLUMN profit_drains.trigger_date
+ IS 'set by taler-exchange-offline at the time of making the signature; not necessarily the exact date of execution of the wire transfer, just for orientation';
+COMMENT ON COLUMN profit_drains.amount
+ IS 'amount to be transferred';
+COMMENT ON COLUMN profit_drains.master_sig
+ IS 'EdDSA signature of type TALER_SIGNATURE_MASTER_DRAIN_PROFIT';
+COMMENT ON COLUMN profit_drains.executed
+ IS 'set to TRUE by taler-exchange-drain on execution of the transaction, not replicated to auditor';
diff --git a/src/exchangedb/0002-purse_actions.sql b/src/exchangedb/0002-purse_actions.sql
new file mode 100644
index 000000000..0dd6cfc4d
--- /dev/null
+++ b/src/exchangedb/0002-purse_actions.sql
@@ -0,0 +1,121 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE OR REPLACE FUNCTION create_table_purse_actions(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_actions';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I'
+ '(purse_pub BYTEA NOT NULL PRIMARY KEY CHECK(LENGTH(purse_pub)=32)'
+ ',action_date INT8 NOT NULL'
+ ',partner_serial_id INT8'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'purses awaiting some action by the router'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public (contract) key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'when is the purse ready for action'
+ ,'action_date'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'wad target of an outgoing wire transfer, 0 for local, NULL if the purse is unmerged and thus the target is still unknown'
+ ,'partner_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+
+CREATE OR REPLACE FUNCTION purse_requests_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO
+ purse_actions
+ (purse_pub
+ ,action_date)
+ VALUES
+ (NEW.purse_pub
+ ,NEW.purse_expiration);
+ RETURN NEW;
+END $$;
+
+COMMENT ON FUNCTION purse_requests_insert_trigger()
+ IS 'When a purse is created, insert it into the purse_action table to take action when the purse expires.';
+
+
+CREATE OR REPLACE FUNCTION master_table_purse_actions()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_actions';
+BEGIN
+ -- Create global index
+ CREATE INDEX IF NOT EXISTS purse_action_by_target
+ ON purse_actions
+ (partner_serial_id,action_date);
+
+ -- Setup trigger
+ CREATE TRIGGER purse_requests_on_insert
+ AFTER INSERT
+ ON purse_requests
+ FOR EACH ROW EXECUTE FUNCTION purse_requests_insert_trigger();
+ COMMENT ON TRIGGER purse_requests_on_insert
+ ON purse_requests
+ IS 'Here we install an entry for the purse expiration.';
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_actions'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('purse_actions'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-purse_decision.sql b/src/exchangedb/0002-purse_decision.sql
new file mode 100644
index 000000000..091bd468b
--- /dev/null
+++ b/src/exchangedb/0002-purse_decision.sql
@@ -0,0 +1,143 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE FUNCTION create_table_purse_decision(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_decision';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(purse_decision_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ',action_timestamp INT8 NOT NULL'
+ ',refunded BOOL NOT NULL'
+ ',PRIMARY KEY (purse_pub)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Purses that were decided upon (refund or merge)'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+CREATE FUNCTION constrain_table_purse_decision(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_decision';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_purse_action_serial_id_key'
+ ' UNIQUE (purse_decision_serial_id) '
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION purse_decision_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ UPDATE purse_requests
+ SET was_decided=TRUE
+ WHERE purse_pub=NEW.purse_pub;
+ IF NEW.refunded
+ THEN
+ INSERT INTO coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ SELECT
+ pd.coin_pub
+ ,'purse_decision'
+ ,NEW.purse_decision_serial_id
+ FROM purse_deposits pd
+ WHERE purse_pub = NEW.purse_pub;
+ ELSE
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ SELECT
+ reserve_pub
+ ,'purse_decision'
+ ,NEW.purse_decision_serial_id
+ FROM purse_merges
+ WHERE purse_pub=NEW.purse_pub;
+ END IF;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION purse_decision_insert_trigger()
+ IS 'Automatically generate coin history entry and update decision status for the purse.';
+
+
+CREATE FUNCTION master_table_purse_decision()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER purse_decision_on_insert
+ AFTER INSERT
+ ON purse_decision
+ FOR EACH ROW EXECUTE FUNCTION purse_decision_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_decision'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('purse_decision'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('purse_decision'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-purse_deletion.sql b/src/exchangedb/0002-purse_deletion.sql
new file mode 100644
index 000000000..45b2e85a9
--- /dev/null
+++ b/src/exchangedb/0002-purse_deletion.sql
@@ -0,0 +1,110 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION create_table_purse_deletion(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_deletion';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I'
+ '(purse_deletion_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',purse_sig BYTEA CHECK (LENGTH(purse_sig)=64)'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'signatures affirming explicit purse deletions'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'signature of type WALLET_PURSE_DELETE'
+ ,'purse_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+COMMENT ON FUNCTION create_table_purse_deletion
+ IS 'Creates the purse_deletion table';
+
+
+CREATE OR REPLACE FUNCTION constrain_table_purse_deletion(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_deletion';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_delete_serial_key '
+ 'UNIQUE (purse_deletion_serial_id)'
+ );
+END $$;
+
+
+CREATE OR REPLACE FUNCTION master_table_purse_requests_was_deleted (
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_requests';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE exchange.' || table_name ||
+ ' ADD COLUMN'
+ ' was_deleted BOOLEAN NOT NULL DEFAULT(FALSE)'
+ );
+ COMMENT ON COLUMN purse_requests.was_deleted
+ IS 'TRUE if the purse was explicitly deleted (purse must have an entry in the purse_deletion table)';
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_deletion'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('purse_deletion'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('purse_requests_was_deleted'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-purse_deposits.sql b/src/exchangedb/0002-purse_deposits.sql
new file mode 100644
index 000000000..6a07c4b62
--- /dev/null
+++ b/src/exchangedb/0002-purse_deposits.sql
@@ -0,0 +1,176 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_purse_deposits(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_deposits';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(purse_deposit_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',partner_serial_id INT8'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ',coin_pub BYTEA NOT NULL'
+ ',amount_with_fee taler_amount NOT NULL'
+ ',coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)'
+ ',PRIMARY KEY (purse_pub,coin_pub)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Requests depositing coins into a purse'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'identifies the partner exchange, NULL in case the target purse lives at this exchange'
+ ,'partner_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the coin being deposited'
+ ,'coin_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total amount being deposited'
+ ,'amount_with_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature of the coin affirming the deposit into the purse, of type TALER_SIGNATURE_PURSE_DEPOSIT'
+ ,'coin_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_purse_deposits(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_deposits';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_purse_deposit_serial_id_key'
+ ' UNIQUE (purse_deposit_serial_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_purse_deposits()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_deposits';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_partner'
+ ' FOREIGN KEY (partner_serial_id) '
+ ' REFERENCES partners(partner_serial_id) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (coin_pub) '
+ ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION purse_deposits_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.coin_pub
+ ,'purse_deposits'
+ ,NEW.purse_deposit_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION purse_deposits_insert_trigger()
+ IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_purse_deposits()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER purse_deposits_on_insert
+ AFTER INSERT
+ ON purse_deposits
+ FOR EACH ROW EXECUTE FUNCTION purse_deposits_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_deposits'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('purse_deposits'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('purse_deposits'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('purse_deposits'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-purse_merges.sql b/src/exchangedb/0002-purse_merges.sql
new file mode 100644
index 000000000..0b4d230b3
--- /dev/null
+++ b/src/exchangedb/0002-purse_merges.sql
@@ -0,0 +1,140 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_purse_merges(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_merges';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(purse_merge_request_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',partner_serial_id INT8'
+ ',reserve_pub BYTEA NOT NULL CHECK(length(reserve_pub)=32)'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ',merge_sig BYTEA NOT NULL CHECK (LENGTH(merge_sig)=64)'
+ ',merge_timestamp INT8 NOT NULL'
+ ',PRIMARY KEY (purse_pub)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Merge requests where a purse-owner requested merging the purse into the account'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'identifies the partner exchange, NULL in case the target reserve lives at this exchange'
+ ,'partner_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public key of the target reserve'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'signature by the purse private key affirming the merge, of type TALER_SIGNATURE_WALLET_PURSE_MERGE'
+ ,'merge_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'when was the merge message signed'
+ ,'merge_timestamp'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_purse_merges(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_merges';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_purse_merge_request_serial_id_key'
+ ' UNIQUE (purse_merge_request_serial_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_purse_merges()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_merges';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_partner_serial_id'
+ ' FOREIGN KEY (partner_serial_id) '
+ ' REFERENCES partners(partner_serial_id) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_purse_pub'
+ ' FOREIGN KEY (purse_pub) '
+ ' REFERENCES purse_requests (purse_pub) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_merges'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('purse_merges'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('purse_merges'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-purse_requests.sql b/src/exchangedb/0002-purse_requests.sql
new file mode 100644
index 000000000..0fa076338
--- /dev/null
+++ b/src/exchangedb/0002-purse_requests.sql
@@ -0,0 +1,163 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_purse_requests(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_requests';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(purse_requests_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',purse_pub BYTEA NOT NULL CHECK (LENGTH(purse_pub)=32)'
+ ',merge_pub BYTEA NOT NULL CHECK (LENGTH(merge_pub)=32)'
+ ',purse_creation INT8 NOT NULL'
+ ',purse_expiration INT8 NOT NULL'
+ ',h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)'
+ ',age_limit INT4 NOT NULL'
+ ',flags INT4 NOT NULL'
+ ',in_reserve_quota BOOLEAN NOT NULL DEFAULT(FALSE)'
+ ',was_decided BOOLEAN NOT NULL DEFAULT(FALSE)'
+ ',amount_with_fee taler_amount NOT NULL'
+ ',purse_fee taler_amount NOT NULL'
+ ',balance taler_amount NOT NULL DEFAULT (0,0)'
+ ',purse_sig BYTEA NOT NULL CHECK(LENGTH(purse_sig)=64)'
+ ',PRIMARY KEY (purse_pub)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Requests establishing purses, associating them with a contract but without a target reserve'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Local time when the purse was created. Determines applicable purse fees.'
+ ,'purse_creation'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'When the purse is set to expire'
+ ,'purse_expiration'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Hash of the contract the parties are to agree to'
+ ,'h_contract_terms'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'see the enum TALER_WalletAccountMergeFlags'
+ ,'flags'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'set to TRUE if this purse currently counts against the number of free purses in the respective reserve'
+ ,'in_reserve_quota'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total amount expected to be in the purse'
+ ,'amount_with_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Purse fee the client agreed to pay from the reserve (accepted by the exchange at the time the purse was created). Zero if in_reserve_quota is TRUE.'
+ ,'purse_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total amount actually in the purse (updated)'
+ ,'balance'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature of the purse affirming the purse parameters, of type TALER_SIGNATURE_PURSE_REQUEST'
+ ,'purse_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+CREATE FUNCTION constrain_table_purse_requests(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_requests';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_merge_pub '
+ 'ON ' || table_name || ' '
+ '(merge_pub);'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_purse_expiration '
+ 'ON ' || table_name || ' '
+ '(purse_expiration) ' ||
+ 'WHERE NOT was_decided;'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_purse_requests_serial_id_key'
+ ' UNIQUE (purse_requests_serial_id) '
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_requests'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('purse_requests'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-recoup.sql b/src/exchangedb/0002-recoup.sql
new file mode 100644
index 000000000..4b3452498
--- /dev/null
+++ b/src/exchangedb/0002-recoup.sql
@@ -0,0 +1,267 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_recoup(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(recoup_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+ ',coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)'
+ ',coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)'
+ ',amount taler_amount NOT NULL'
+ ',recoup_timestamp INT8 NOT NULL'
+ ',reserve_out_serial_id INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub);'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Information about recoups that were executed between a coin and a reserve. In this type of recoup, the amount is credited back to the reserve from which the coin originated.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Coin that is being debited in the recoup. Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!'
+ ,'coin_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the h_blind_ev of the recouped coin and provides the link to the credited reserve.'
+ ,'reserve_out_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature by the coin affirming the recoup, of type TALER_SIGNATURE_WALLET_COIN_RECOUP'
+ ,'coin_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Denomination blinding key used when creating the blinded coin from the planchet. Secret revealed during the recoup to provide the linkage between the coin and the withdraw operation.'
+ ,'coin_blind'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_recoup(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_coin_pub_index '
+ 'ON ' || table_name || ' '
+ '(coin_pub);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_recoup_uuid_key'
+ ' UNIQUE (recoup_uuid) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_recoup()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_reserves_out'
+ ' FOREIGN KEY (reserve_out_serial_id) '
+ ' REFERENCES reserves_out (reserve_out_serial_id) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (coin_pub) '
+ ' REFERENCES known_coins (coin_pub)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION create_table_recoup_by_reserve(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup_by_reserve';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(reserve_out_serial_id INT8 NOT NULL' -- REFERENCES reserves (reserve_out_serial_id) ON DELETE CASCADE
+ ',coin_pub BYTEA CHECK (LENGTH(coin_pub)=32)' -- REFERENCES known_coins (coin_pub)
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_out_serial_id)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Information in this table is strictly redundant with that of recoup, but saved by a different primary key for fast lookups by reserve_out_serial_id.'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_recoup_by_reserve(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup_by_reserve';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_main_index '
+ 'ON ' || table_name || ' '
+ '(reserve_out_serial_id);'
+ );
+END
+$$;
+
+
+CREATE FUNCTION recoup_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO recoup_by_reserve
+ (reserve_out_serial_id
+ ,coin_pub)
+ VALUES
+ (NEW.reserve_out_serial_id
+ ,NEW.coin_pub);
+ INSERT INTO coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.coin_pub
+ ,'recoup'
+ ,NEW.recoup_uuid);
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ SELECT
+ res.reserve_pub
+ ,'recoup'
+ ,NEW.recoup_uuid
+ FROM reserves_out rout
+ JOIN reserves res
+ USING (reserve_uuid)
+ WHERE rout.reserve_out_serial_id = NEW.reserve_out_serial_id;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION recoup_insert_trigger()
+ IS 'Replicates recoup inserts into recoup_by_reserve table and updates the coin_history table.';
+
+
+CREATE FUNCTION recoup_delete_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ DELETE FROM recoup_by_reserve
+ WHERE reserve_out_serial_id = OLD.reserve_out_serial_id
+ AND coin_pub = OLD.coin_pub;
+ RETURN OLD;
+END $$;
+COMMENT ON FUNCTION recoup_delete_trigger()
+ IS 'Replicate recoup deletions into recoup_by_reserve table.';
+
+
+CREATE FUNCTION master_table_recoup()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER recoup_on_insert
+ AFTER INSERT
+ ON recoup
+ FOR EACH ROW EXECUTE FUNCTION recoup_insert_trigger();
+ CREATE TRIGGER recoup_on_delete
+ AFTER DELETE
+ ON recoup
+ FOR EACH ROW EXECUTE FUNCTION recoup_delete_trigger();
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('recoup'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('recoup'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('recoup'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('recoup_by_reserve'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('recoup_by_reserve'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('recoup'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-recoup_refresh.sql b/src/exchangedb/0002-recoup_refresh.sql
new file mode 100644
index 000000000..8b979a49f
--- /dev/null
+++ b/src/exchangedb/0002-recoup_refresh.sql
@@ -0,0 +1,203 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE FUNCTION create_table_recoup_refresh(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup_refresh';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(recoup_refresh_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+ ',known_coin_id BIGINT NOT NULL'
+ ',coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)'
+ ',coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)'
+ ',amount taler_amount NOT NULL'
+ ',recoup_timestamp INT8 NOT NULL'
+ ',rrc_serial INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Table of coins that originated from a refresh operation and that were recouped. Links the (fresh) coin to the melted operation (and thus the old coin). A recoup on a refreshed coin credits the old coin and debits the fresh coin.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Refreshed coin of a revoked denomination where the residual value is credited to the old coin. Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!'
+ ,'coin_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Used for garbage collection (in the absence of foreign constraints, in the future)'
+ ,'known_coin_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Link to the refresh operation. Also identifies the h_blind_ev of the recouped coin (as h_coin_ev).'
+ ,'rrc_serial'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Denomination blinding key used when creating the blinded coin from the planchet. Secret revealed during the recoup to provide the linkage between the coin and the refresh operation.'
+ ,'coin_blind'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_recoup_refresh(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup_refresh';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_rrc_serial_index'
+ ' ON ' || table_name || ' '
+ '(rrc_serial);'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_rrc_serial_index '
+ 'IS ' || quote_literal('used in exchange_do_melt for zombie coins (rare)') || ';'
+ );
+
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_coin_pub_index'
+ ' ON ' || table_name || ' '
+ '(coin_pub);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_recoup_refresh_uuid_key'
+ ' UNIQUE (recoup_refresh_uuid) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_recoup_refresh()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'recoup_refresh';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (coin_pub) '
+ ' REFERENCES known_coins (coin_pub)'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_known_coin_id'
+ ' FOREIGN KEY (known_coin_id) '
+ ' REFERENCES known_coins (known_coin_id) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_rrc_serial'
+ ' FOREIGN KEY (rrc_serial) '
+ ' REFERENCES refresh_revealed_coins (rrc_serial) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION recoup_refresh_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.coin_pub
+ ,'recoup_refresh::NEW'
+ ,NEW.recoup_refresh_uuid);
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ SELECT
+ melt.old_coin_pub
+ ,'recoup_refresh::OLD'
+ ,NEW.recoup_refresh_uuid
+ FROM refresh_revealed_coins rrc
+ JOIN refresh_commitments melt
+ USING (melt_serial_id)
+ WHERE rrc.rrc_serial = NEW.rrc_serial;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION coin_deposits_insert_trigger()
+ IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_recoup_refresh()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER recoup_refresh_on_insert
+ AFTER INSERT
+ ON recoup_refresh
+ FOR EACH ROW EXECUTE FUNCTION recoup_refresh_insert_trigger();
+END $$;
+
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('recoup_refresh'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('recoup_refresh'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('recoup_refresh'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('recoup_refresh'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-refresh_commitments.sql b/src/exchangedb/0002-refresh_commitments.sql
new file mode 100644
index 000000000..e577f1e1c
--- /dev/null
+++ b/src/exchangedb/0002-refresh_commitments.sql
@@ -0,0 +1,166 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_refresh_commitments(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_commitments';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(melt_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64)'
+ ',old_coin_pub BYTEA NOT NULL'
+ ',old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64)'
+ ',amount_with_fee taler_amount NOT NULL'
+ ',noreveal_index INT4 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (rc)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Commitments made when melting coins and the gamma value chosen by the exchange.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'The gamma value chosen by the exchange in the cut-and-choose protocol'
+ ,'noreveal_index'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Commitment made by the client, hash over the various client inputs in the cut-and-choose protocol'
+ ,'rc'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Coin being melted in the refresh process.'
+ ,'old_coin_pub'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_refresh_commitments(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_commitments';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ -- Note: index spans partitions, may need to be materialized.
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_old_coin_pub_index '
+ 'ON ' || table_name || ' '
+ '(old_coin_pub);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_melt_serial_id_key'
+ ' UNIQUE (melt_serial_id)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_refresh_commitments()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_commitments';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (old_coin_pub) '
+ ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION refresh_commitments_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.old_coin_pub
+ ,'refresh_commitments'
+ ,NEW.melt_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION refresh_commitments_insert_trigger()
+ IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_refresh_commitments()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER refresh_commitments_on_insert
+ AFTER INSERT
+ ON refresh_commitments
+ FOR EACH ROW EXECUTE FUNCTION refresh_commitments_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('refresh_commitments'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('refresh_commitments'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('refresh_commitments'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('refresh_commitments'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-refresh_revealed_coins.sql b/src/exchangedb/0002-refresh_revealed_coins.sql
new file mode 100644
index 000000000..ad65c9942
--- /dev/null
+++ b/src/exchangedb/0002-refresh_revealed_coins.sql
@@ -0,0 +1,169 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_refresh_revealed_coins(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_revealed_coins';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(rrc_serial BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',melt_serial_id INT8 NOT NULL'
+ ',freshcoin_index INT4 NOT NULL'
+ ',link_sig BYTEA NOT NULL CHECK(LENGTH(link_sig)=64)'
+ ',denominations_serial INT8 NOT NULL'
+ ',coin_ev BYTEA NOT NULL'
+ ',h_coin_ev BYTEA NOT NULL CHECK(LENGTH(h_coin_ev)=64)'
+ ',ev_sig BYTEA NOT NULL'
+ ',ewv BYTEA NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (melt_serial_id)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Revelations about the new coins that are to be created during a melting session.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'needed for exchange-auditor replication logic'
+ ,'rrc_serial'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the refresh commitment (rc) of the melt operation.'
+ ,'melt_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'index of the fresh coin being created (one melt operation may result in multiple fresh coins)'
+ ,'freshcoin_index'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature of type WALLET_COIN_LINK, proves exchange did not tamper with the link data'
+ ,'link_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'envelope of the new coin to be signed'
+ ,'coin_ev'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'exchange contributed values in the creation of the fresh coin (see /csr)'
+ ,'ewv'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash of the envelope of the new coin to be signed (for lookups)'
+ ,'h_coin_ev'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'exchange signature over the envelope'
+ ,'ev_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_refresh_revealed_coins(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_revealed_coins';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_coins_by_melt_serial_id_index '
+ 'ON ' || table_name || ' '
+ '(melt_serial_id);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_rrc_serial_key'
+ ' UNIQUE (rrc_serial) '
+ ',ADD CONSTRAINT ' || table_name || '_coin_ev_key'
+ ' UNIQUE (coin_ev) '
+ ',ADD CONSTRAINT ' || table_name || '_h_coin_ev_key'
+ ' UNIQUE (h_coin_ev) '
+ ',ADD PRIMARY KEY (melt_serial_id, freshcoin_index)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_refresh_revealed_coins()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_revealed_coins';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_melt'
+ ' FOREIGN KEY (melt_serial_id)'
+ ' REFERENCES refresh_commitments (melt_serial_id) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_denom'
+ ' FOREIGN KEY (denominations_serial)'
+ ' REFERENCES denominations (denominations_serial) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('refresh_revealed_coins'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('refresh_revealed_coins'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('refresh_revealed_coins'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-refresh_transfer_keys.sql b/src/exchangedb/0002-refresh_transfer_keys.sql
new file mode 100644
index 000000000..9bcb912da
--- /dev/null
+++ b/src/exchangedb/0002-refresh_transfer_keys.sql
@@ -0,0 +1,127 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_refresh_transfer_keys(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_transfer_keys';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(rtc_serial BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',melt_serial_id INT8 PRIMARY KEY'
+ ',transfer_pub BYTEA NOT NULL CHECK(LENGTH(transfer_pub)=32)'
+ ',transfer_privs BYTEA NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (melt_serial_id)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Transfer keys of a refresh operation (the data revealed to the exchange).'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'needed for exchange-auditor replication logic'
+ ,'rtc_serial'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the refresh commitment (rc) of the operation.'
+ ,'melt_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'transfer public key for the gamma index'
+ ,'transfer_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'array of TALER_CNC_KAPPA-1 transfer private keys that have been revealed, with the gamma entry being skipped'
+ ,'transfer_privs'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_refresh_transfer_keys(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_transfer_keys';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_rtc_serial_key'
+ ' UNIQUE (rtc_serial)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_refresh_transfer_keys()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refresh_transfer_keys';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || 'foreign_melt_serial_id'
+ ' FOREIGN KEY (melt_serial_id)'
+ ' REFERENCES refresh_commitments (melt_serial_id) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('refresh_transfer_keys'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('refresh_transfer_keys'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('refresh_transfer_keys'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-refunds.sql b/src/exchangedb/0002-refunds.sql
new file mode 100644
index 000000000..2a40bc192
--- /dev/null
+++ b/src/exchangedb/0002-refunds.sql
@@ -0,0 +1,162 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_refunds(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refunds';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(refund_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+ ',batch_deposit_serial_id INT8 NOT NULL'
+ ',merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64)'
+ ',rtransaction_id INT8 NOT NULL'
+ ',amount_with_fee taler_amount NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Data on coins that were refunded. Technically, refunds always apply against specific deposit operations involving a coin. The combination of coin_pub, merchant_pub, h_contract_terms and rtransaction_id MUST be unique, and we usually select by coin_pub so that one goes first.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies ONLY the merchant_pub, h_contract_terms and coin_pub. Multiple deposits may match a refund, this only identifies one of them.'
+ ,'batch_deposit_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'used by the merchant to make refunds unique in case the same coin for the same deposit gets a subsequent (higher) refund'
+ ,'rtransaction_id'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_refunds (
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refunds';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_coin_pub_index '
+ 'ON ' || table_name || ' '
+ '(coin_pub);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_refund_serial_id_key'
+ ' UNIQUE (refund_serial_id) '
+ ',ADD PRIMARY KEY (batch_deposit_serial_id, rtransaction_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_refunds ()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refunds';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_coin_pub'
+ ' FOREIGN KEY (coin_pub) '
+ ' REFERENCES known_coins (coin_pub) ON DELETE CASCADE'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_deposit'
+ ' FOREIGN KEY (batch_deposit_serial_id) '
+ ' REFERENCES batch_deposits (batch_deposit_serial_id) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION refunds_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.coin_pub
+ ,'refunds'
+ ,NEW.refund_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION refunds_insert_trigger()
+ IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_refunds()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER refunds_on_insert
+ AFTER INSERT
+ ON refunds
+ FOR EACH ROW EXECUTE FUNCTION refunds_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('refunds'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('refunds'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('refunds'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('refunds'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-reserve_history.sql b/src/exchangedb/0002-reserve_history.sql
new file mode 100644
index 000000000..b0c764306
--- /dev/null
+++ b/src/exchangedb/0002-reserve_history.sql
@@ -0,0 +1,138 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_reserve_history (
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'reserve_history';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(reserve_history_serial_id INT8 GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
+ ',table_name TEXT NOT NULL'
+ ',serial_id INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Links to tables with entries that affected the transaction history of a reserve.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'For which reserve is this a history entry'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'In which table is the history entry'
+ ,'table_name'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Which is the generated serial ID of the entry in the table'
+ ,'serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Monotonic counter, used to generate Etags for caching'
+ ,'reserve_history_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserve_history(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'reserve_history';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_reserve_history_serial_id_pkey'
+ ' PRIMARY KEY (reserve_history_serial_id) '
+ ',ADD CONSTRAINT ' || table_name || '_reserve_entry_key'
+ ' UNIQUE (reserve_pub, table_name, serial_id)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_reserve_by_time'
+ ' ON ' || table_name || ' '
+ '(reserve_pub'
+ ',reserve_history_serial_id DESC'
+ ');'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_reserve_history()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'reserve_history';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub'
+ ' FOREIGN KEY (reserve_pub) '
+ ' REFERENCES reserves (reserve_pub) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserve_history'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserve_history'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('reserve_history'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE)
+ ;
diff --git a/src/exchangedb/0002-reserves.sql b/src/exchangedb/0002-reserves.sql
new file mode 100644
index 000000000..d710dd01b
--- /dev/null
+++ b/src/exchangedb/0002-reserves.sql
@@ -0,0 +1,152 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_reserves(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'reserves';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(reserve_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32)'
+ ',current_balance taler_amount NOT NULL DEFAULT (0, 0)'
+ ',purses_active INT8 NOT NULL DEFAULT(0)'
+ ',purses_allowed INT8 NOT NULL DEFAULT(0)'
+ ',birthday INT4 NOT NULL DEFAULT(0)'
+ ',expiration_date INT8 NOT NULL'
+ ',gc_date INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Summarizes the balance of a reserve. Updated when new funds are added or withdrawn.'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'EdDSA public key of the reserve. Knowledge of the private key implies ownership over the balance.'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Current balance remaining with the reserve.'
+ ,'current_balance'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Number of purses that were created by this reserve that are not expired and not fully paid.'
+ ,'purses_active'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Number of purses that this reserve is allowed to have active at most.'
+ ,'purses_allowed'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Used to trigger closing of reserves that have not been drained after some time'
+ ,'expiration_date'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Used to forget all information about a reserve during garbage collection'
+ ,'gc_date'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Birthday of the user in days after 1970, or 0 if user is an adult and is not subject to age restrictions'
+ ,'birthday'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserves(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'reserves';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_unique_uuid'
+ ' UNIQUE (reserve_uuid)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_expiration_index '
+ 'ON ' || table_name || ' '
+ '(expiration_date'
+ ',current_balance'
+ ');'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_expiration_index '
+ 'IS ' || quote_literal('used in get_expired_reserves') || ';'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_reserve_uuid_index '
+ 'ON ' || table_name || ' '
+ '(reserve_uuid);'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_gc_date_index '
+ 'ON ' || table_name || ' '
+ '(gc_date);'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_gc_date_index '
+ 'IS ' || quote_literal('for reserve garbage collection') || ';'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserves'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserves'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-reserves_close.sql b/src/exchangedb/0002-reserves_close.sql
new file mode 100644
index 000000000..16669768d
--- /dev/null
+++ b/src/exchangedb/0002-reserves_close.sql
@@ -0,0 +1,151 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_reserves_close(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_close';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(close_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA NOT NULL'
+ ',execution_date INT8 NOT NULL'
+ ',wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32)'
+ ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
+ ',amount taler_amount NOT NULL'
+ ',closing_fee taler_amount NOT NULL'
+ ',close_request_row INT8 NOT NULL DEFAULT(0)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'wire transfers executed by the reserve to close reserves'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the credited bank account (and KYC status). Note that closing does not depend on KYC.'
+ ,'wire_target_h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserves_close(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_close';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_close_uuid_pkey'
+ ' PRIMARY KEY (close_uuid)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_reserve_pub_index '
+ 'ON ' || table_name || ' (reserve_pub);'
+ );
+END $$;
+
+
+CREATE FUNCTION foreign_table_reserves_close()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_close';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub'
+ ' FOREIGN KEY (reserve_pub)'
+ ' REFERENCES reserves (reserve_pub) ON DELETE CASCADE'
+ );
+END $$;
+
+
+CREATE OR REPLACE FUNCTION reserves_close_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.reserve_pub
+ ,'reserves_close'
+ ,NEW.close_uuid);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION reserves_close_insert_trigger()
+ IS 'Automatically generate reserve history entry.';
+
+
+CREATE FUNCTION master_table_reserves_close()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER reserves_close_on_insert
+ AFTER INSERT
+ ON reserves_close
+ FOR EACH ROW EXECUTE FUNCTION reserves_close_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserves_close'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserves_close'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('reserves_close'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('reserves_close'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-reserves_in.sql b/src/exchangedb/0002-reserves_in.sql
new file mode 100644
index 000000000..197a815b3
--- /dev/null
+++ b/src/exchangedb/0002-reserves_in.sql
@@ -0,0 +1,175 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_reserves_in(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_in';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(reserve_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA PRIMARY KEY'
+ ',wire_reference INT8 NOT NULL'
+ ',credit taler_amount NOT NULL'
+ ',wire_source_h_payto BYTEA CHECK (LENGTH(wire_source_h_payto)=32)'
+ ',exchange_account_section TEXT NOT NULL'
+ ',execution_date INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'list of transfers of funds into the reserves, one per incoming wire transfer'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the debited bank account and KYC status'
+ ,'wire_source_h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the reserve. Private key signifies ownership of the remaining balance.'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Amount that was transferred into the reserve'
+ ,'credit'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+
+CREATE FUNCTION constrain_table_reserves_in(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_in';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_reserve_in_serial_id_key'
+ ' UNIQUE (reserve_in_serial_id)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_reserve_in_serial_id_index '
+ 'ON ' || table_name || ' '
+ '(reserve_in_serial_id);'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_exch_accnt_reserve_in_serial_id_idx '
+ 'ON ' || table_name || ' '
+ '(exchange_account_section'
+ ',reserve_in_serial_id ASC'
+ ');'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_exch_accnt_reserve_in_serial_id_idx '
+ 'IS ' || quote_literal ('for pg_select_reserves_in_above_serial_id_by_account') || ';'
+ );
+
+END
+$$;
+
+CREATE FUNCTION foreign_table_reserves_in()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'reserves_in';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub'
+ ' FOREIGN KEY (reserve_pub) '
+ ' REFERENCES reserves(reserve_pub) ON DELETE CASCADE'
+ );
+END $$;
+
+
+
+CREATE OR REPLACE FUNCTION reserves_in_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.reserve_pub
+ ,'reserves_in'
+ ,NEW.reserve_in_serial_id);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION reserves_in_insert_trigger()
+ IS 'Automatically generate reserve history entry.';
+
+
+CREATE FUNCTION master_table_reserves_in()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER reserves_in_on_insert
+ AFTER INSERT
+ ON reserves_in
+ FOR EACH ROW EXECUTE FUNCTION reserves_in_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserves_in'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserves_in'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('reserves_in'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('reserves_in'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-reserves_open_deposits.sql b/src/exchangedb/0002-reserves_open_deposits.sql
new file mode 100644
index 000000000..776859df8
--- /dev/null
+++ b/src/exchangedb/0002-reserves_open_deposits.sql
@@ -0,0 +1,135 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_reserves_open_deposits(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_open_deposits';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(reserve_open_deposit_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',reserve_pub BYTEA NOT NULL CHECK (LENGTH(reserve_pub)=32)'
+ ',coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)'
+ ',coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)'
+ ',contribution taler_amount NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (coin_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'coin contributions paying for a reserve to remain open'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the specific reserve being paid for (possibly together with reserve_sig).'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserves_open_deposits(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_open_deposits';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name || ' '
+ 'ADD CONSTRAINT ' || table_name || '_coin_unique '
+ 'PRIMARY KEY (coin_pub,coin_sig)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_uuid '
+ 'ON ' || table_name || ' '
+ '(reserve_open_deposit_uuid);'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_reserve '
+ 'ON ' || table_name || ' '
+ '(reserve_pub);'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION reserves_open_deposits_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO exchange.coin_history
+ (coin_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.coin_pub
+ ,'reserves_open_deposits'
+ ,NEW.reserve_open_deposit_uuid);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION reserves_open_deposits_insert_trigger()
+ IS 'Automatically generate coin history entry.';
+
+
+CREATE FUNCTION master_table_reserves_open_deposits()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER reserves_open_deposits_on_insert
+ AFTER INSERT
+ ON reserves_open_deposits
+ FOR EACH ROW EXECUTE FUNCTION reserves_open_deposits_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserves_open_deposits'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserves_open_deposits'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('reserves_open_deposits'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-reserves_open_requests.sql b/src/exchangedb/0002-reserves_open_requests.sql
new file mode 100644
index 000000000..b51168dc0
--- /dev/null
+++ b/src/exchangedb/0002-reserves_open_requests.sql
@@ -0,0 +1,150 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_reserves_open_requests(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_open_requests';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(open_request_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',reserve_pub BYTEA NOT NULL'
+ ',request_timestamp INT8 NOT NULL'
+ ',expiration_date INT8 NOT NULL'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',reserve_payment taler_amount NOT NULL'
+ ',requested_purse_limit INT4 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (reserve_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table (
+ 'requests to keep a reserve open'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column (
+ 'Fee to pay for the request from the reserve balance itself.'
+ ,'reserve_payment'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserves_open_requests(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_open_requests';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_by_uuid'
+ ' PRIMARY KEY (open_request_uuid)'
+ ',ADD CONSTRAINT ' || table_name || '_by_time'
+ ' UNIQUE (reserve_pub,request_timestamp)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_reserves_open_requests()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_open_requests';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_reserve_pub '
+ ' FOREIGN KEY (reserve_pub)'
+ ' REFERENCES reserves (reserve_pub) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE OR REPLACE FUNCTION reserves_open_requests_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ VALUES
+ (NEW.reserve_pub
+ ,'reserves_open_requests'
+ ,NEW.open_request_uuid);
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION reserves_open_requests_insert_trigger()
+ IS 'Automatically generate reserve history entry.';
+
+
+CREATE FUNCTION master_table_reserves_open_requests()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER reserves_open_requests_on_insert
+ AFTER INSERT
+ ON reserves_open_requests
+ FOR EACH ROW EXECUTE FUNCTION reserves_open_requests_insert_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserves_open_requests'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserves_open_requests'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('reserves_open_requests'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('reserves_open_requests'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-reserves_out.sql b/src/exchangedb/0002-reserves_out.sql
new file mode 100644
index 000000000..f0965d222
--- /dev/null
+++ b/src/exchangedb/0002-reserves_out.sql
@@ -0,0 +1,173 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_reserves_out(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_out';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(reserve_out_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',h_blind_ev BYTEA CHECK (LENGTH(h_blind_ev)=64) UNIQUE'
+ ',denominations_serial INT8 NOT NULL'
+ ',denom_sig BYTEA NOT NULL'
+ ',reserve_uuid INT8 NOT NULL'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',execution_date INT8 NOT NULL'
+ ',amount_with_fee taler_amount NOT NULL'
+ ') %s ;'
+ ,'reserves_out'
+ ,'PARTITION BY HASH (h_blind_ev)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table (
+ 'Withdraw operations performed on reserves.'
+ ,'reserves_out'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column (
+ 'Hash of the blinded coin, used as primary key here so that broken clients that use a non-random coin or blinding factor fail to withdraw (otherwise they would fail on deposit when the coin is not unique there).'
+ ,'h_blind_ev'
+ ,'reserves_out'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column (
+ 'We do not CASCADE ON DELETE for the foreign constrain here, as we may keep the denomination data alive'
+ ,'denominations_serial'
+ ,'reserves_out'
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_reserves_out(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_out';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_reserve_out_serial_id_key'
+ ' UNIQUE (reserve_out_serial_id)'
+ );
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_reserve_uuid_and_execution_date_index '
+ 'ON ' || table_name || ' '
+ '(reserve_uuid, execution_date);'
+ );
+ EXECUTE FORMAT (
+ 'COMMENT ON INDEX ' || table_name || '_by_reserve_uuid_and_execution_date_index '
+ 'IS ' || quote_literal('for do_gc, do_recoup_by_reserve, select_kyc_relevant_withdraw_events and a few others') || ';'
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_reserves_out()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT default 'reserves_out';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_denom'
+ ' FOREIGN KEY (denominations_serial)'
+ ' REFERENCES denominations (denominations_serial)'
+ ',ADD CONSTRAINT ' || table_name || '_foreign_reserve '
+ ' FOREIGN KEY (reserve_uuid)'
+ ' REFERENCES reserves (reserve_uuid) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+CREATE FUNCTION reserves_out_insert_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ INSERT INTO reserve_history
+ (reserve_pub
+ ,table_name
+ ,serial_id)
+ SELECT
+ res.reserve_pub
+ ,'reserves_out'
+ ,NEW.reserve_out_serial_id
+ FROM
+ reserves res
+ WHERE res.reserve_uuid = NEW.reserve_uuid;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION reserves_out_insert_trigger()
+ IS 'Replicate reserve_out inserts into reserve_history table.';
+
+
+CREATE FUNCTION master_table_reserves_out()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER reserves_out_on_insert
+ AFTER INSERT
+ ON reserves_out
+ FOR EACH ROW EXECUTE FUNCTION reserves_out_insert_trigger();
+END $$;
+COMMENT ON FUNCTION master_table_reserves_out()
+ IS 'Setup triggers to replicate reserve_out into reserve_history.';
+
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('reserves_out'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('reserves_out'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('reserves_out'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE),
+ ('reserves_out'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/exchange-0003.sql b/src/exchangedb/0002-revolving_work_shards.sql
index 5a33e0fcc..8cfff09b4 100644
--- a/src/exchangedb/exchange-0003.sql
+++ b/src/exchangedb/0002-revolving_work_shards.sql
@@ -1,6 +1,6 @@
--
-- This file is part of TALER
--- Copyright (C) 2021 Taler Systems SA
+-- Copyright (C) 2014--2022 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
@@ -14,48 +14,15 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
--- Everything in one big transaction
-BEGIN;
-
--- Check patch versioning is in place.
-SELECT _v.register_patch('exchange-0003', NULL, NULL);
-
-
-
-ALTER TABLE deposits
- ADD COLUMN shard INT4 NOT NULL DEFAULT 0;
-COMMENT ON COLUMN deposits.shard
- IS 'Used for load sharding. Should be set based on h_wire, merchant_pub and a service salt. Default of 0 onlyapplies for columns migrated from a previous version without sharding support. 64-bit value because we need an *unsigned* 32-bit value.';
-
-DROP INDEX deposits_get_ready_index;
-CREATE INDEX deposits_get_ready_index
- ON deposits
- (shard
- ,tiny
- ,done
- ,wire_deadline
- ,refund_deadline
- );
-COMMENT ON INDEX deposits_get_ready_index
- IS 'for deposits_get_ready';
-
-
-
-CREATE UNLOGGED TABLE IF NOT EXISTS revolving_work_shards
- (shard_serial_id BIGSERIAL UNIQUE
+CREATE UNLOGGED TABLE revolving_work_shards
+ (shard_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
,last_attempt INT8 NOT NULL
,start_row INT4 NOT NULL
,end_row INT4 NOT NULL
,active BOOLEAN NOT NULL DEFAULT FALSE
- ,job_name VARCHAR NOT NULL
+ ,job_name TEXT NOT NULL
,PRIMARY KEY (job_name, start_row)
);
-CREATE INDEX IF NOT EXISTS revolving_work_shards_index
- ON revolving_work_shards
- (job_name
- ,active
- ,last_attempt
- );
COMMENT ON TABLE revolving_work_shards
IS 'coordinates work between multiple processes working on the same job with partitions that need to be repeatedly processed; unlogged because on system crashes the locks represented by this table will have to be cleared anyway, typically using "taler-exchange-dbinit -s"';
COMMENT ON COLUMN revolving_work_shards.shard_serial_id
@@ -71,5 +38,9 @@ COMMENT ON COLUMN revolving_work_shards.end_row
COMMENT ON COLUMN revolving_work_shards.job_name
IS 'unique name of the job the workers on this shard are performing';
--- Complete transaction
-COMMIT;
+CREATE INDEX revolving_work_shards_by_job_name_active_last_attempt_index
+ ON revolving_work_shards
+ (job_name
+ ,active
+ ,last_attempt
+ );
diff --git a/src/exchangedb/0002-signkey_revocations.sql b/src/exchangedb/0002-signkey_revocations.sql
new file mode 100644
index 000000000..37ab32c67
--- /dev/null
+++ b/src/exchangedb/0002-signkey_revocations.sql
@@ -0,0 +1,23 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE TABLE signkey_revocations
+ (signkey_revocations_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,esk_serial INT8 PRIMARY KEY REFERENCES exchange_sign_keys (esk_serial) ON DELETE CASCADE
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ );
+COMMENT ON TABLE signkey_revocations
+ IS 'Table storing which online signing keys have been revoked';
diff --git a/src/exchangedb/0002-wad_in_entries.sql b/src/exchangedb/0002-wad_in_entries.sql
new file mode 100644
index 000000000..3ef1f1b8e
--- /dev/null
+++ b/src/exchangedb/0002-wad_in_entries.sql
@@ -0,0 +1,175 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_wad_in_entries(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wad_in_entries';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(wad_in_entry_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',wad_in_serial_id INT8'
+ ',reserve_pub BYTEA NOT NULL CHECK(LENGTH(reserve_pub)=32)'
+ ',purse_pub BYTEA PRIMARY KEY CHECK(LENGTH(purse_pub)=32)'
+ ',h_contract BYTEA NOT NULL CHECK(LENGTH(h_contract)=64)'
+ ',purse_expiration INT8 NOT NULL'
+ ',merge_timestamp INT8 NOT NULL'
+ ',amount_with_fee taler_amount NOT NULL'
+ ',wad_fee taler_amount NOT NULL'
+ ',deposit_fees taler_amount NOT NULL'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',purse_sig BYTEA NOT NULL CHECK (LENGTH(purse_sig)=64)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'list of purses aggregated in a wad according to the sending exchange'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'wad for which the given purse was included in the aggregation'
+ ,'wad_in_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'target account of the purse (must be at the local exchange)'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'public key of the purse that was merged'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'hash of the contract terms of the purse'
+ ,'h_contract'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Time when the purse was set to expire'
+ ,'purse_expiration'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Time when the merge was approved'
+ ,'merge_timestamp'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total amount in the purse'
+ ,'amount_with_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total wad fees paid by the purse'
+ ,'wad_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total deposit fees paid when depositing coins into the purse'
+ ,'deposit_fees'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature by the receiving reserve, of purpose TALER_SIGNATURE_ACCOUNT_MERGE'
+ ,'reserve_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature by the purse of purpose TALER_SIGNATURE_PURSE_MERGE'
+ ,'purse_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+
+CREATE FUNCTION constrain_table_wad_in_entries(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wad_in_entries';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_wad_in_entry_serial_id_key'
+ ' UNIQUE (wad_in_entry_serial_id) '
+ );
+END $$;
+
+
+CREATE FUNCTION foreign_table_wad_in_entries()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wad_in_entries';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_wad_in'
+ ' FOREIGN KEY(wad_in_serial_id)'
+ ' REFERENCES wads_in (wad_in_serial_id) ON DELETE CASCADE'
+ );
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('wad_in_entries'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('wad_in_entries'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('wad_in_entries'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-wad_out_entries.sql b/src/exchangedb/0002-wad_out_entries.sql
new file mode 100644
index 000000000..de921637b
--- /dev/null
+++ b/src/exchangedb/0002-wad_out_entries.sql
@@ -0,0 +1,179 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE FUNCTION create_table_wad_out_entries(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wad_out_entries';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(wad_out_entry_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',wad_out_serial_id INT8'
+ ',reserve_pub BYTEA NOT NULL CHECK(LENGTH(reserve_pub)=32)'
+ ',purse_pub BYTEA PRIMARY KEY CHECK(LENGTH(purse_pub)=32)'
+ ',h_contract BYTEA NOT NULL CHECK(LENGTH(h_contract)=64)'
+ ',purse_expiration INT8 NOT NULL'
+ ',merge_timestamp INT8 NOT NULL'
+ ',amount_with_fee taler_amount NOT NULL'
+ ',wad_fee taler_amount NOT NULL'
+ ',deposit_fees taler_amount NOT NULL'
+ ',reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)'
+ ',purse_sig BYTEA NOT NULL CHECK (LENGTH(purse_sig)=64)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (purse_pub)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Purses combined into a wad'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Wad the purse was part of'
+ ,'wad_out_serial_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Target reserve for the purse'
+ ,'reserve_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Public key of the purse'
+ ,'purse_pub'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Hash of the contract associated with the purse'
+ ,'h_contract'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Time when the purse expires'
+ ,'purse_expiration'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Time when the merge was approved'
+ ,'merge_timestamp'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total amount in the purse'
+ ,'amount_with_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Wad fee charged to the purse'
+ ,'wad_fee'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Total deposit fees charged to the purse'
+ ,'deposit_fees'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature by the receiving reserve, of purpose TALER_SIGNATURE_ACCOUNT_MERGE'
+ ,'reserve_sig'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Signature by the purse of purpose TALER_SIGNATURE_PURSE_MERGE'
+ ,'purse_sig'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_wad_out_entries(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wad_out_entries';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_wad_out_entry_serial_id_key'
+ ' UNIQUE (wad_out_entry_serial_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_wad_out_entries()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wad_out_entries';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_wad_out'
+ ' FOREIGN KEY(wad_out_serial_id)'
+ ' REFERENCES wads_out (wad_out_serial_id) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('wad_out_entries'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('wad_out_entries'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('wad_out_entries'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-wads_in.sql b/src/exchangedb/0002-wads_in.sql
new file mode 100644
index 000000000..479589ba4
--- /dev/null
+++ b/src/exchangedb/0002-wads_in.sql
@@ -0,0 +1,107 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_wads_in(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wads_in';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(wad_in_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',wad_id BYTEA PRIMARY KEY CHECK (LENGTH(wad_id)=24)'
+ ',origin_exchange_url TEXT NOT NULL'
+ ',amount taler_amount NOT NULL'
+ ',arrival_time INT8 NOT NULL'
+ ',UNIQUE (wad_id, origin_exchange_url)'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (wad_id)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Incoming exchange-to-exchange wad wire transfers'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Unique identifier of the wad, part of the wire transfer subject'
+ ,'wad_id'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Base URL of the originating URL, also part of the wire transfer subject'
+ ,'origin_exchange_url'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Actual amount that was received by our exchange'
+ ,'amount'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Time when the wad was received'
+ ,'arrival_time'
+ ,table_name
+ ,partition_suffix
+ );
+END $$;
+
+
+CREATE FUNCTION constrain_table_wads_in(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wads_in';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_wad_in_serial_id_key'
+ ' UNIQUE (wad_in_serial_id) '
+ ',ADD CONSTRAINT ' || table_name || '_wad_is_origin_exchange_url_key'
+ ' UNIQUE (wad_id, origin_exchange_url) '
+ );
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('wads_in'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('wads_in'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-wads_out.sql b/src/exchangedb/0002-wads_out.sql
new file mode 100644
index 000000000..e52010e96
--- /dev/null
+++ b/src/exchangedb/0002-wads_out.sql
@@ -0,0 +1,128 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_wads_out(
+ IN shard_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wads_out';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I '
+ '(wad_out_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',wad_id BYTEA PRIMARY KEY CHECK (LENGTH(wad_id)=24)'
+ ',partner_serial_id INT8 NOT NULL'
+ ',amount taler_amount NOT NULL'
+ ',execution_time INT8 NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (wad_id)'
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'Wire transfers made to another exchange to transfer purse funds'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Unique identifier of the wad, part of the wire transfer subject'
+ ,'wad_id'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'target exchange of the wad'
+ ,'partner_serial_id'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Amount that was wired'
+ ,'amount'
+ ,table_name
+ ,shard_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Time when the wire transfer was scheduled'
+ ,'execution_time'
+ ,table_name
+ ,shard_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_wads_out(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wads_out';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_wad_out_serial_id_key'
+ ' UNIQUE (wad_out_serial_id) '
+ );
+END
+$$;
+
+
+CREATE FUNCTION foreign_table_wads_out()
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wads_out';
+BEGIN
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_foreign_partner'
+ ' FOREIGN KEY(partner_serial_id)'
+ ' REFERENCES partners(partner_serial_id) ON DELETE CASCADE'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('wads_out'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('wads_out'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('wads_out'
+ ,'exchange-0002'
+ ,'foreign'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-wire_accounts.sql b/src/exchangedb/0002-wire_accounts.sql
new file mode 100644
index 000000000..dba522d7b
--- /dev/null
+++ b/src/exchangedb/0002-wire_accounts.sql
@@ -0,0 +1,45 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE TABLE wire_accounts
+ (payto_uri TEXT PRIMARY KEY
+ ,master_sig BYTEA CHECK (LENGTH(master_sig)=64)
+ ,is_active BOOLEAN NOT NULL
+ ,last_change INT8 NOT NULL
+ ,conversion_url TEXT DEFAULT (NULL)
+ ,debit_restrictions TEXT DEFAULT (NULL)
+ ,credit_restrictions TEXT DEFAULT (NULL)
+ );
+COMMENT ON TABLE wire_accounts
+ IS 'Table with current and historic bank accounts of the exchange. Entries never expire as we need to remember the last_change column indefinitely.';
+COMMENT ON COLUMN wire_accounts.payto_uri
+ IS 'payto URI (RFC 8905) with the bank account of the exchange.';
+COMMENT ON COLUMN wire_accounts.master_sig
+ IS 'Signature of purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS';
+COMMENT ON COLUMN wire_accounts.is_active
+ IS 'true if we are currently supporting the use of this account.';
+COMMENT ON COLUMN wire_accounts.last_change
+ IS 'Latest time when active status changed. Used to detect replays of old messages.';
+COMMENT ON COLUMN wire_accounts.conversion_url
+ IS 'URL of a currency conversion service if conversion is needed when this account is used; NULL if there is no conversion.';
+COMMENT ON COLUMN wire_accounts.debit_restrictions
+ IS 'JSON array describing restrictions imposed when debiting this account. Empty for no restrictions, NULL if account was migrated from previous database revision or account is disabled.';
+COMMENT ON COLUMN wire_accounts.credit_restrictions
+ IS 'JSON array describing restrictions imposed when crediting this account. Empty for no restrictions, NULL if account was migrated from previous database revision or account is disabled.';
+
+
+-- "wire_accounts" has no sequence because it is a 'mutable' table
+-- and is of no concern to the auditor
diff --git a/src/exchangedb/0002-wire_fee.sql b/src/exchangedb/0002-wire_fee.sql
new file mode 100644
index 000000000..12cb91b98
--- /dev/null
+++ b/src/exchangedb/0002-wire_fee.sql
@@ -0,0 +1,34 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE TABLE wire_fee
+ (wire_fee_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,wire_method TEXT NOT NULL
+ ,start_date INT8 NOT NULL
+ ,end_date INT8 NOT NULL
+ ,wire_fee taler_amount NOT NULL
+ ,closing_fee taler_amount NOT NULL
+ ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
+ ,PRIMARY KEY (wire_method, start_date)
+ );
+COMMENT ON TABLE wire_fee
+ IS 'list of the wire fees of this exchange, by date';
+COMMENT ON COLUMN wire_fee.wire_fee_serial
+ IS 'needed for exchange-auditor replication logic';
+
+CREATE INDEX wire_fee_by_end_date_index
+ ON wire_fee
+ (end_date);
diff --git a/src/exchangedb/0002-wire_out.sql b/src/exchangedb/0002-wire_out.sql
new file mode 100644
index 000000000..c0f471b56
--- /dev/null
+++ b/src/exchangedb/0002-wire_out.sql
@@ -0,0 +1,130 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_wire_out(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wire_out';
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE IF NOT EXISTS %I'
+ '(wireout_uuid BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',execution_date INT8 NOT NULL'
+ ',wtid_raw BYTEA UNIQUE NOT NULL CHECK (LENGTH(wtid_raw)=32)'
+ ',wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)'
+ ',exchange_account_section TEXT NOT NULL'
+ ',amount taler_amount NOT NULL'
+ ') %s ;'
+ ,table_name
+ ,'PARTITION BY HASH (wtid_raw)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'wire transfers the exchange has executed'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'identifies the configuration section with the debit account of this payment'
+ ,'exchange_account_section'
+ ,table_name
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Identifies the credited bank account and KYC status'
+ ,'wire_target_h_payto'
+ ,table_name
+ ,partition_suffix
+ );
+END
+$$;
+
+
+CREATE FUNCTION constrain_table_wire_out(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wire_out';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'CREATE INDEX ' || table_name || '_by_wire_target_h_payto_index '
+ 'ON ' || table_name || ' '
+ '(wire_target_h_payto);'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_wireout_uuid_pkey'
+ ' PRIMARY KEY (wireout_uuid)'
+ );
+END
+$$;
+
+
+CREATE FUNCTION wire_out_delete_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+ DELETE FROM exchange.aggregation_tracking
+ WHERE wtid_raw = OLD.wtid_raw;
+ RETURN OLD;
+END $$;
+COMMENT ON FUNCTION wire_out_delete_trigger()
+ IS 'Replicate reserve_out deletions into aggregation_tracking. This replaces an earlier use of an ON DELETE CASCADE that required a DEFERRABLE constraint and conflicted with nice partitioning.';
+
+
+CREATE FUNCTION master_table_wire_out()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CREATE TRIGGER wire_out_on_delete
+ AFTER DELETE
+ ON wire_out
+ FOR EACH ROW EXECUTE FUNCTION wire_out_delete_trigger();
+END $$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('wire_out'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('wire_out'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE),
+ ('wire_out'
+ ,'exchange-0002'
+ ,'master'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-wire_targets.sql b/src/exchangedb/0002-wire_targets.sql
new file mode 100644
index 000000000..88d67d9a5
--- /dev/null
+++ b/src/exchangedb/0002-wire_targets.sql
@@ -0,0 +1,89 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE FUNCTION create_table_wire_targets(
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ PERFORM create_partitioned_table(
+ 'CREATE TABLE %I'
+ '(wire_target_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY'
+ ',wire_target_h_payto BYTEA PRIMARY KEY CHECK (LENGTH(wire_target_h_payto)=32)'
+ ',payto_uri TEXT NOT NULL'
+ ') %s ;'
+ ,'wire_targets'
+ ,'PARTITION BY HASH (wire_target_h_payto)'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_table(
+ 'All senders and recipients of money via the exchange'
+ ,'wire_targets'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Can be a regular bank account, or also be a URI identifying a reserve-account (for P2P payments)'
+ ,'payto_uri'
+ ,'wire_targets'
+ ,partition_suffix
+ );
+ PERFORM comment_partitioned_column(
+ 'Unsalted hash of payto_uri'
+ ,'wire_target_h_payto'
+ ,'wire_targets'
+ ,partition_suffix
+ );
+END $$;
+
+
+CREATE FUNCTION constrain_table_wire_targets(
+ IN partition_suffix TEXT
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'wire_targets';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_wire_target_serial_id_key'
+ ' UNIQUE (wire_target_serial_id)'
+ );
+END
+$$;
+
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('wire_targets'
+ ,'exchange-0002'
+ ,'create'
+ ,TRUE
+ ,FALSE),
+ ('wire_targets'
+ ,'exchange-0002'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0002-work_shards.sql b/src/exchangedb/0002-work_shards.sql
new file mode 100644
index 000000000..6347d42c5
--- /dev/null
+++ b/src/exchangedb/0002-work_shards.sql
@@ -0,0 +1,56 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE TABLE work_shards
+ (shard_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,last_attempt INT8 NOT NULL
+ ,start_row INT8 NOT NULL
+ ,end_row INT8 NOT NULL
+ ,completed BOOLEAN NOT NULL DEFAULT FALSE
+ ,job_name TEXT NOT NULL
+ ,PRIMARY KEY (job_name, start_row)
+ );
+COMMENT ON TABLE work_shards
+ IS 'coordinates work between multiple processes working on the same job';
+COMMENT ON COLUMN work_shards.shard_serial_id
+ IS 'unique serial number identifying the shard';
+COMMENT ON COLUMN work_shards.last_attempt
+ IS 'last time a worker attempted to work on the shard';
+COMMENT ON COLUMN work_shards.completed
+ IS 'set to TRUE once the shard is finished by a worker';
+COMMENT ON COLUMN work_shards.start_row
+ IS 'row at which the shard scope starts, inclusive';
+COMMENT ON COLUMN work_shards.end_row
+ IS 'row at which the shard scope ends, exclusive';
+COMMENT ON COLUMN work_shards.job_name
+ IS 'unique name of the job the workers on this shard are performing';
+
+CREATE INDEX work_shards_by_job_name_completed_last_attempt_index
+ ON work_shards
+ (job_name
+ ,completed
+ ,last_attempt ASC
+ );
+
+CREATE INDEX work_shards_by_end_row_index
+ ON work_shards
+ (end_row DESC);
+
+CREATE INDEX work_shards_by_rows
+ ON work_shards
+ (job_name
+ ,start_row
+ ,end_row);
diff --git a/src/exchangedb/0003-purse_deletion.sql b/src/exchangedb/0003-purse_deletion.sql
new file mode 100644
index 000000000..66a95ff03
--- /dev/null
+++ b/src/exchangedb/0003-purse_deletion.sql
@@ -0,0 +1,52 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+-- Adds a 'unique' constraint to the 'purse_pub'.
+-- This is not only semantically correct, but also
+-- creates a dramatic speed-up on the
+-- pg_select_purse query (which otherwise fails to
+-- use indices correctly).
+
+CREATE FUNCTION constrain_table_purse_decision3(
+ IN partition_suffix TEXT
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'purse_decision';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD CONSTRAINT ' || table_name || '_purse_decision_purse_pub'
+ ' UNIQUE (purse_pub) '
+ );
+END
+$$;
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('purse_decision3'
+ ,'exchange-0003'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/0003-wire_accounts.sql b/src/exchangedb/0003-wire_accounts.sql
new file mode 100644
index 000000000..51fc86c80
--- /dev/null
+++ b/src/exchangedb/0003-wire_accounts.sql
@@ -0,0 +1,25 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+-- new columns for #8000
+ALTER TABLE wire_accounts
+ ADD COLUMN priority INT8 NOT NULL DEFAULT (0),
+ ADD COLUMN bank_label TEXT DEFAULT (NULL);
+
+COMMENT ON COLUMN wire_accounts.priority
+ IS 'priority determines the order in which wallets should display wire accounts';
+COMMENT ON COLUMN wire_accounts.bank_label
+ IS 'label to show in the selector for this bank account in the wallet UI';
diff --git a/src/exchangedb/0004-refunds.sql b/src/exchangedb/0004-refunds.sql
new file mode 100644
index 000000000..eb9e7ad6e
--- /dev/null
+++ b/src/exchangedb/0004-refunds.sql
@@ -0,0 +1,35 @@
+
+CREATE FUNCTION constrain_table_refunds4 (
+ IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ table_name TEXT DEFAULT 'refunds';
+BEGIN
+ table_name = concat_ws('_', table_name, partition_suffix);
+
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' DROP CONSTRAINT ' || table_name || '_pkey'
+ );
+ EXECUTE FORMAT (
+ 'ALTER TABLE ' || table_name ||
+ ' ADD PRIMARY KEY (batch_deposit_serial_id, coin_pub, rtransaction_id) '
+ );
+END
+$$;
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('refunds4'
+ ,'exchange-0004'
+ ,'constrain'
+ ,TRUE
+ ,FALSE);
diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
index 37b5f015a..fd993f968 100644
--- a/src/exchangedb/Makefile.am
+++ b/src/exchangedb/Makefile.am
@@ -14,21 +14,71 @@ pkgcfg_DATA = \
sqldir = $(prefix)/share/taler/sql/exchange/
+sqlinputs = \
+ exchange_do_*.sql \
+ procedures.sql.in \
+ 0002-*.sql \
+ 0003-*.sql \
+ 0004-*.sql \
+ exchange-0002.sql.in \
+ exchange-0003.sql.in \
+ exchange-0004.sql.in
+
sql_DATA = \
- exchange-0000.sql \
+ benchmark-0001.sql \
+ versioning.sql \
+ auditor-triggers-0001.sql \
+ exchange-0001.sql \
+ exchange-0002.sql \
+ exchange-0003.sql \
+ exchange-0004.sql \
+ drop.sql \
+ procedures.sql
+
+BUILT_SOURCES = \
+ benchmark-0001.sql \
+ drop.sql \
exchange-0001.sql \
+ procedures.sql
+
+CLEANFILES = \
exchange-0002.sql \
exchange-0003.sql \
- drop0001.sql
+ procedures.sql
+
+procedures.sql: procedures.sql.in exchange_do_*.sql
+ chmod +w $@ 2> /dev/null || true
+ gcc -E -P -undef - < procedures.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
+ chmod ugo-w $@
+
+exchange-0002.sql: exchange-0002.sql.in 0002-*.sql
+ chmod +w $@ 2> /dev/null || true
+ gcc -E -P -undef - < exchange-0002.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
+ chmod ugo-w $@
+
+exchange-0003.sql: exchange-0003.sql.in 0003-*.sql
+ chmod +w $@ 2> /dev/null || true
+ gcc -E -P -undef - < exchange-0003.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
+ chmod ugo-w $@
+
+exchange-0004.sql: exchange-0004.sql.in 0004-*.sql
+ chmod +w $@ 2> /dev/null || true
+ gcc -E -P -undef - < exchange-0004.sql.in 2>/dev/null | sed -e "s/--.*//" | awk 'NF' - >$@
+ chmod ugo-w $@
+
+check_SCRIPTS = \
+ test_idempotency.sh
EXTRA_DIST = \
exchangedb.conf \
exchangedb-postgres.conf \
- plugin_exchangedb_common.c \
- irbt_callbacks.c \
- lrbt_callbacks.c \
+ bench-db-postgres.conf \
test-exchange-db-postgres.conf \
- $(sql_DATA)
+ $(sqlinputs) \
+ $(sql_DATA) \
+ $(check_SCRIPTS) \
+ pg_template.h pg_template.c \
+ pg_template.sh
plugindir = $(libdir)/taler
@@ -38,19 +88,203 @@ plugin_LTLIBRARIES = \
endif
libtaler_plugin_exchangedb_postgres_la_SOURCES = \
- plugin_exchangedb_postgres.c
-libtaler_plugin_exchangedb_postgres_la_LIBADD = \
- $(LTLIBINTL)
+ plugin_exchangedb_common.c plugin_exchangedb_common.h \
+ pg_setup_wire_target.h pg_setup_wire_target.c \
+ pg_compute_shard.h pg_compute_shard.c \
+ plugin_exchangedb_postgres.c pg_helper.h \
+ pg_reserves_update.h pg_reserves_update.c \
+ pg_select_aggregation_amounts_for_kyc_check.h pg_select_aggregation_amounts_for_kyc_check.c \
+ pg_lookup_wire_fee_by_time.h pg_lookup_wire_fee_by_time.c \
+ pg_select_satisfied_kyc_processes.h pg_select_satisfied_kyc_processes.c \
+ pg_get_pending_kyc_requirement_process.h pg_get_pending_kyc_requirement_process.c \
+ pg_kyc_provider_account_lookup.h pg_kyc_provider_account_lookup.c \
+ pg_lookup_kyc_requirement_by_row.h pg_lookup_kyc_requirement_by_row.c \
+ pg_insert_kyc_requirement_for_account.h pg_insert_kyc_requirement_for_account.c \
+ pg_lookup_kyc_process_by_account.h pg_lookup_kyc_process_by_account.c \
+ pg_update_kyc_process_by_row.h pg_update_kyc_process_by_row.c \
+ pg_insert_kyc_requirement_process.h pg_insert_kyc_requirement_process.c \
+ pg_select_withdraw_amounts_for_kyc_check.h pg_select_withdraw_amounts_for_kyc_check.c \
+ pg_select_merge_amounts_for_kyc_check.h pg_select_merge_amounts_for_kyc_check.c \
+ pg_profit_drains_set_finished.h pg_profit_drains_set_finished.c \
+ pg_profit_drains_get_pending.h pg_profit_drains_get_pending.c \
+ pg_get_drain_profit.h pg_get_drain_profit.c \
+ pg_get_purse_deposit.h pg_get_purse_deposit.c \
+ pg_insert_contract.h pg_insert_contract.c \
+ pg_select_aml_threshold.h pg_select_aml_threshold.c \
+ pg_select_contract.h pg_select_contract.c \
+ pg_select_purse_merge.h pg_select_purse_merge.c \
+ pg_select_contract_by_purse.h pg_select_contract_by_purse.c \
+ pg_insert_drain_profit.h pg_insert_drain_profit.c \
+ pg_insert_kyc_failure.h pg_insert_kyc_failure.c \
+ pg_inject_auditor_triggers.h pg_inject_auditor_triggers.c \
+ pg_create_tables.h pg_create_tables.c \
+ pg_event_listen.h pg_event_listen.c \
+ pg_event_listen_cancel.h pg_event_listen_cancel.c \
+ pg_event_notify.h pg_event_notify.c \
+ pg_get_denomination_info.h pg_get_denomination_info.c \
+ pg_iterate_denomination_info.h pg_iterate_denomination_info.c \
+ pg_iterate_denominations.h pg_iterate_denominations.c \
+ pg_iterate_active_auditors.h pg_iterate_active_auditors.c \
+ pg_iterate_auditor_denominations.h pg_iterate_auditor_denominations.c \
+ pg_reserves_get.h pg_reserves_get.c \
+ pg_reserves_get_origin.h pg_reserves_get_origin.c \
+ pg_drain_kyc_alert.h pg_drain_kyc_alert.c \
+ pg_reserves_in_insert.h pg_reserves_in_insert.c \
+ pg_get_withdraw_info.h pg_get_withdraw_info.c \
+ pg_do_age_withdraw.h pg_do_age_withdraw.c \
+ pg_get_age_withdraw.h pg_get_age_withdraw.c \
+ pg_batch_ensure_coin_known.h pg_batch_ensure_coin_known.c \
+ pg_do_batch_withdraw.h pg_do_batch_withdraw.c \
+ pg_get_policy_details.h pg_get_policy_details.c \
+ pg_persist_policy_details.h pg_persist_policy_details.c \
+ pg_do_deposit.h pg_do_deposit.c \
+ pg_get_wire_hash_for_contract.h pg_get_wire_hash_for_contract.c \
+ pg_add_policy_fulfillment_proof.h pg_add_policy_fulfillment_proof.c \
+ pg_do_melt.h pg_do_melt.c \
+ pg_do_refund.h pg_do_refund.c \
+ pg_do_recoup.h pg_do_recoup.c \
+ pg_do_recoup_refresh.h pg_do_recoup_refresh.c \
+ pg_get_reserve_balance.h pg_get_reserve_balance.c \
+ pg_count_known_coins.h pg_count_known_coins.c \
+ pg_ensure_coin_known.h pg_ensure_coin_known.c \
+ pg_get_known_coin.h pg_get_known_coin.c \
+ pg_get_signature_for_known_coin.h pg_get_signature_for_known_coin.c \
+ pg_get_coin_denomination.h pg_get_coin_denomination.c \
+ pg_have_deposit2.h pg_have_deposit2.c \
+ pg_aggregate.h pg_aggregate.c \
+ pg_create_aggregation_transient.h pg_create_aggregation_transient.c \
+ pg_insert_kyc_attributes.h pg_insert_kyc_attributes.c \
+ pg_select_similar_kyc_attributes.h pg_select_similar_kyc_attributes.c \
+ pg_select_kyc_attributes.h pg_select_kyc_attributes.c \
+ pg_insert_aml_officer.h pg_insert_aml_officer.c \
+ pg_test_aml_officer.h pg_test_aml_officer.c \
+ pg_lookup_aml_officer.h pg_lookup_aml_officer.c \
+ pg_trigger_aml_process.h pg_trigger_aml_process.c \
+ pg_select_aml_process.h pg_select_aml_process.c \
+ pg_select_aml_history.h pg_select_aml_history.c \
+ pg_insert_aml_decision.h pg_insert_aml_decision.c \
+ pg_select_aggregation_transient.h pg_select_aggregation_transient.c \
+ pg_find_aggregation_transient.h pg_find_aggregation_transient.c \
+ pg_update_aggregation_transient.h pg_update_aggregation_transient.c \
+ pg_get_ready_deposit.h pg_get_ready_deposit.c \
+ pg_insert_refund.h pg_insert_refund.c \
+ pg_select_refunds_by_coin.h pg_select_refunds_by_coin.c \
+ pg_get_melt.h pg_get_melt.c \
+ pg_insert_refresh_reveal.h pg_insert_refresh_reveal.c \
+ pg_get_refresh_reveal.h pg_get_refresh_reveal.c \
+ pg_lookup_wire_transfer.h pg_lookup_wire_transfer.c \
+ pg_lookup_transfer_by_deposit.h pg_lookup_transfer_by_deposit.c \
+ pg_insert_wire_fee.h pg_insert_wire_fee.c \
+ pg_insert_global_fee.h pg_insert_global_fee.c \
+ pg_get_wire_fee.h pg_get_wire_fee.c \
+ pg_get_global_fee.h pg_get_global_fee.c \
+ pg_get_global_fees.h pg_get_global_fees.c \
+ pg_insert_reserve_closed.h pg_insert_reserve_closed.c \
+ pg_wire_prepare_data_insert.h pg_wire_prepare_data_insert.c \
+ pg_wire_prepare_data_mark_finished.h pg_wire_prepare_data_mark_finished.c \
+ pg_wire_prepare_data_mark_failed.h pg_wire_prepare_data_mark_failed.c \
+ pg_wire_prepare_data_get.h pg_wire_prepare_data_get.c \
+ pg_start_deferred_wire_out.h pg_start_deferred_wire_out.c \
+ pg_store_wire_transfer_out.h pg_store_wire_transfer_out.c \
+ pg_gc.h pg_gc.c \
+ pg_select_coin_deposits_above_serial_id.h pg_select_coin_deposits_above_serial_id.c \
+ pg_select_purse_decisions_above_serial_id.h pg_select_purse_decisions_above_serial_id.c \
+ pg_select_purse_deposits_by_purse.h pg_select_purse_deposits_by_purse.c \
+ pg_select_refreshes_above_serial_id.h pg_select_refreshes_above_serial_id.c \
+ pg_select_refunds_above_serial_id.h pg_select_refunds_above_serial_id.c \
+ pg_select_reserves_in_above_serial_id.h pg_select_reserves_in_above_serial_id.c \
+ pg_select_reserves_in_above_serial_id_by_account.h pg_select_reserves_in_above_serial_id_by_account.c \
+ pg_select_withdrawals_above_serial_id.h pg_select_withdrawals_above_serial_id.c \
+ pg_select_wire_out_above_serial_id.h pg_select_wire_out_above_serial_id.c \
+ pg_select_wire_out_above_serial_id_by_account.h pg_select_wire_out_above_serial_id_by_account.c \
+ pg_select_recoup_above_serial_id.h pg_select_recoup_above_serial_id.c \
+ pg_select_recoup_refresh_above_serial_id.h pg_select_recoup_refresh_above_serial_id.c \
+ pg_get_reserve_by_h_blind.h pg_get_reserve_by_h_blind.c \
+ pg_get_old_coin_by_h_blind.h pg_get_old_coin_by_h_blind.c \
+ pg_insert_denomination_revocation.h pg_insert_denomination_revocation.c \
+ pg_get_denomination_revocation.h pg_get_denomination_revocation.c \
+ pg_select_batch_deposits_missing_wire.h pg_select_batch_deposits_missing_wire.c \
+ pg_select_justification_for_missing_wire.h pg_select_justification_for_missing_wire.c \
+ pg_select_aggregations_above_serial.h pg_select_aggregations_above_serial.c \
+ pg_lookup_auditor_timestamp.h pg_lookup_auditor_timestamp.c \
+ pg_lookup_auditor_status.h pg_lookup_auditor_status.c \
+ pg_insert_auditor.h pg_insert_auditor.c \
+ pg_lookup_wire_timestamp.h pg_lookup_wire_timestamp.c \
+ pg_insert_wire.h pg_insert_wire.c \
+ pg_update_wire.h pg_update_wire.c \
+ pg_get_wire_accounts.h pg_get_wire_accounts.c \
+ pg_get_wire_fees.h pg_get_wire_fees.c \
+ pg_insert_signkey_revocation.h pg_insert_signkey_revocation.c \
+ pg_lookup_signkey_revocation.h pg_lookup_signkey_revocation.c \
+ pg_lookup_denomination_key.h pg_lookup_denomination_key.c \
+ pg_insert_auditor_denom_sig.h pg_insert_auditor_denom_sig.c \
+ pg_select_auditor_denom_sig.h pg_select_auditor_denom_sig.c \
+ pg_add_denomination_key.h pg_add_denomination_key.c \
+ pg_lookup_signing_key.h pg_lookup_signing_key.c \
+ pg_begin_shard.h pg_begin_shard.c \
+ pg_abort_shard.h pg_abort_shard.c \
+ pg_complete_shard.h pg_complete_shard.c \
+ pg_release_revolving_shard.h pg_release_revolving_shard.c \
+ pg_delete_shard_locks.h pg_delete_shard_locks.c \
+ pg_set_extension_manifest.h pg_set_extension_manifest.c \
+ pg_insert_partner.h pg_insert_partner.c \
+ pg_expire_purse.h pg_expire_purse.c \
+ pg_select_purse_by_merge_pub.h pg_select_purse_by_merge_pub.c \
+ pg_set_purse_balance.h pg_set_purse_balance.c \
+ pg_do_reserve_purse.h pg_do_reserve_purse.c \
+ pg_lookup_global_fee_by_time.h pg_lookup_global_fee_by_time.c \
+ pg_do_purse_deposit.h pg_do_purse_deposit.c \
+ pg_activate_signing_key.h pg_activate_signing_key.c \
+ pg_update_auditor.h pg_update_auditor.c \
+ pg_begin_revolving_shard.h pg_begin_revolving_shard.c \
+ pg_get_extension_manifest.h pg_get_extension_manifest.c \
+ pg_do_purse_merge.h pg_do_purse_merge.c \
+ pg_start_read_committed.h pg_start_read_committed.c \
+ pg_start_read_only.h pg_start_read_only.c \
+ pg_insert_denomination_info.h pg_insert_denomination_info.c \
+ pg_do_batch_withdraw_insert.h pg_do_batch_withdraw_insert.c \
+ pg_do_reserve_open.c pg_do_reserve_open.h \
+ pg_do_purse_delete.c pg_do_purse_delete.h \
+ pg_preflight.h pg_preflight.c \
+ pg_iterate_active_signkeys.h pg_iterate_active_signkeys.c \
+ pg_commit.h pg_commit.c \
+ pg_get_coin_transactions.c pg_get_coin_transactions.h \
+ pg_get_expired_reserves.c pg_get_expired_reserves.h \
+ pg_start.h pg_start.c \
+ pg_rollback.h pg_rollback.c \
+ pg_get_purse_request.c pg_get_purse_request.h \
+ pg_get_reserve_history.c pg_get_reserve_history.h \
+ pg_get_unfinished_close_requests.c pg_get_unfinished_close_requests.h \
+ pg_insert_close_request.c pg_insert_close_request.h \
+ pg_delete_aggregation_transient.h pg_delete_aggregation_transient.c \
+ pg_get_link_data.h pg_get_link_data.c \
+ pg_drop_tables.h pg_drop_tables.c \
+ pg_insert_purse_request.h pg_insert_purse_request.c \
+ pg_insert_records_by_table.c pg_insert_records_by_table.h \
+ pg_insert_reserve_open_deposit.c pg_insert_reserve_open_deposit.h \
+ pg_iterate_kyc_reference.c pg_iterate_kyc_reference.h \
+ pg_iterate_reserve_close_info.c pg_iterate_reserve_close_info.h \
+ pg_lookup_records_by_table.c pg_lookup_records_by_table.h \
+ pg_lookup_serial_by_table.c pg_lookup_serial_by_table.h \
+ pg_select_reserve_close_info.c pg_select_reserve_close_info.h \
+ pg_select_reserve_closed_above_serial_id.c pg_select_reserve_closed_above_serial_id.h \
+ pg_select_purse.h pg_select_purse.c \
+ pg_select_purse_requests_above_serial_id.h pg_select_purse_requests_above_serial_id.c \
+ pg_select_purse_merges_above_serial_id.h pg_select_purse_merges_above_serial_id.c \
+ pg_select_purse_deposits_above_serial_id.h pg_select_purse_deposits_above_serial_id.c \
+ pg_select_account_merges_above_serial_id.h pg_select_account_merges_above_serial_id.c \
+ pg_select_all_purse_decisions_above_serial_id.h pg_select_all_purse_decisions_above_serial_id.c \
+ pg_select_reserve_open_above_serial_id.c pg_select_reserve_open_above_serial_id.h
libtaler_plugin_exchangedb_postgres_la_LDFLAGS = \
- $(TALER_PLUGIN_LDFLAGS) \
+ $(TALER_PLUGIN_LDFLAGS)
+libtaler_plugin_exchangedb_postgres_la_LIBADD = \
+ $(LTLIBINTL) \
$(top_builddir)/src/pq/libtalerpq.la \
- $(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
- -lpq \
- -lpthread \
-lgnunetpq \
-lgnunetutil \
-ljansson \
+ -lpq \
$(XLIB)
lib_LTLIBRARIES = \
@@ -74,9 +308,17 @@ libtalerexchangedb_la_LDFLAGS = \
check_PROGRAMS = \
test-exchangedb-postgres
+noinst_PROGRAMS = \
+ bench-db-postgres\
+ perf_get_link_data-postgres\
+ perf_select_refunds_by_coin-postgres\
+ perf_reserves_in_insert-postgres \
+ perf_deposits_get_ready-postgres
+
AM_TESTS_ENVIRONMENT=export TALER_PREFIX=$${TALER_PREFIX:-@libdir@};export PATH=$${TALER_PREFIX:-@prefix@}/bin:$$PATH;
TESTS = \
- test-exchangedb-postgres
+ $(check_SCRIPTS) \
+ $(check_PROGRAMS)
test_exchangedb_postgres_SOURCES = \
test_exchangedb.c
@@ -90,5 +332,68 @@ test_exchangedb_postgres_LDADD = \
-lgnunetutil \
$(XLIB)
+bench_db_postgres_SOURCES = \
+ bench_db.c
+bench_db_postgres_LDADD = \
+ libtalerexchangedb.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -lgnunetpq \
+ -lgnunetutil \
+ $(XLIB)
+
+perf_reserves_in_insert_postgres_SOURCES = \
+ perf_reserves_in_insert.c
+perf_reserves_in_insert_postgres_LDADD = \
+ libtalerexchangedb.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lm \
+ $(XLIB)
+
+perf_select_refunds_by_coin_postgres_SOURCES = \
+ perf_select_refunds_by_coin.c
+perf_select_refunds_by_coin_postgres_LDADD = \
+ libtalerexchangedb.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lm \
+ $(XLIB)
+
+perf_get_link_data_postgres_SOURCES = \
+ perf_get_link_data.c
+perf_get_link_data_postgres_LDADD = \
+ libtalerexchangedb.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lm \
+ $(XLIB)
+
+perf_deposits_get_ready_postgres_SOURCES = \
+ perf_deposits_get_ready.c
+perf_deposits_get_ready_postgres_LDADD = \
+ libtalerexchangedb.la \
+ $(top_builddir)/src/json/libtalerjson.la \
+ $(top_builddir)/src/util/libtalerutil.la \
+ $(top_builddir)/src/pq/libtalerpq.la \
+ -ljansson \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lm \
+ $(XLIB)
+
+
EXTRA_test_exchangedb_postgres_DEPENDENCIES = \
libtaler_plugin_exchangedb_postgres.la
diff --git a/src/exchangedb/auditor-triggers-0001.sql b/src/exchangedb/auditor-triggers-0001.sql
new file mode 100644
index 000000000..4e2ea66ce
--- /dev/null
+++ b/src/exchangedb/auditor-triggers-0001.sql
@@ -0,0 +1,41 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+SELECT _v.register_patch('auditor-triggers-0001');
+
+SET search_path TO exchange;
+
+CREATE OR REPLACE FUNCTION auditor_new_deposits_trigger()
+ RETURNS trigger
+ LANGUAGE plpgsql
+AS $$
+BEGIN
+ NOTIFY XFIXME;
+ RETURN NEW;
+END $$;
+COMMENT ON FUNCTION auditor_new_deposits_trigger()
+ IS 'Call XXX on new entry';
+
+CREATE TRIGGER auditor_notify_helper_insert_deposits
+ AFTER INSERT
+ ON exchange.batch_deposits
+EXECUTE PROCEDURE auditor_new_deposits_trigger();
+
+
+COMMIT;
diff --git a/src/exchangedb/bench-db-postgres.conf b/src/exchangedb/bench-db-postgres.conf
new file mode 100644
index 000000000..d51cf9175
--- /dev/null
+++ b/src/exchangedb/bench-db-postgres.conf
@@ -0,0 +1,14 @@
+# This file is in the public domain.
+#
+# Database-backend independent specification for the exchangedb module.
+#
+[bench-db-postgres]
+CONFIG = postgres:///talercheck
+
+# Where are the SQL files to setup our tables?
+# Important: this MUST end with a "/"!
+SQL_DIR = $DATADIR/sql/exchange/
+
+[exchangedb]
+# Number of purses per account by default.
+DEFAULT_PURSE_LIMIT = 1 \ No newline at end of file
diff --git a/src/exchangedb/bench_db.c b/src/exchangedb/bench_db.c
new file mode 100644
index 000000000..302d23062
--- /dev/null
+++ b/src/exchangedb/bench_db.c
@@ -0,0 +1,531 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2021 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchangedb/bench_db.c
+ * @brief test cases for DB interaction functions
+ * @author Sree Harsha Totakura
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include <gnunet/gnunet_pq_lib.h>
+#include "taler_util.h"
+
+/**
+ * How many elements should we insert?
+ */
+#define TOTAL (1024 * 16)
+
+/**
+ * Global result from the testcase.
+ */
+static int result;
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+
+
+static bool
+prepare (struct GNUNET_PQ_Context *conn)
+{
+ struct GNUNET_PQ_PreparedStatement ps[] = {
+ GNUNET_PQ_make_prepare (
+ "bm_insert",
+ "INSERT INTO benchmap "
+ "(hc"
+ ",expiration_date"
+ ") VALUES "
+ "($1, $2);"),
+ /* Used in #postgres_iterate_denomination_info() */
+ GNUNET_PQ_make_prepare (
+ "bm_select",
+ "SELECT"
+ " expiration_date"
+ " FROM benchmap"
+ " WHERE hc=$1;"),
+ GNUNET_PQ_make_prepare (
+ "bhm_insert",
+ "INSERT INTO benchhmap "
+ "(hc"
+ ",expiration_date"
+ ") VALUES "
+ "($1, $2);"),
+ /* Used in #postgres_iterate_denomination_info() */
+ GNUNET_PQ_make_prepare (
+ "bhm_select",
+ "SELECT"
+ " expiration_date"
+ " FROM benchhmap"
+ " WHERE hc=$1;"),
+ GNUNET_PQ_make_prepare (
+ "bem_insert",
+ "INSERT INTO benchemap "
+ "(hc"
+ ",ihc"
+ ",expiration_date"
+ ") VALUES "
+ "($1, $2, $3);"),
+ /* Used in #postgres_iterate_denomination_info() */
+ GNUNET_PQ_make_prepare (
+ "bem_select",
+ "SELECT"
+ " expiration_date"
+ " FROM benchemap"
+ " WHERE ihc=$1 AND hc=$2;"),
+ GNUNET_PQ_PREPARED_STATEMENT_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_PQ_prepare_statements (conn,
+ ps);
+ if (GNUNET_OK != ret)
+ return false;
+ return true;
+}
+
+
+static bool
+bm_insert (struct GNUNET_PQ_Context *conn,
+ unsigned int i)
+{
+ uint32_t b = htonl ((uint32_t) i);
+ struct GNUNET_HashCode hc;
+ struct GNUNET_TIME_Absolute now;
+
+ now = GNUNET_TIME_absolute_get ();
+ GNUNET_CRYPTO_hash (&b,
+ sizeof (b),
+ &hc);
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&hc),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_non_select (conn,
+ "bm_insert",
+ params);
+ return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ }
+}
+
+
+static bool
+bhm_insert (struct GNUNET_PQ_Context *conn,
+ unsigned int i)
+{
+ uint32_t b = htonl ((uint32_t) i);
+ struct GNUNET_HashCode hc;
+ struct GNUNET_TIME_Absolute now;
+
+ now = GNUNET_TIME_absolute_get ();
+ GNUNET_CRYPTO_hash (&b,
+ sizeof (b),
+ &hc);
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&hc),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_non_select (conn,
+ "bhm_insert",
+ params);
+ return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ }
+}
+
+
+static bool
+bem_insert (struct GNUNET_PQ_Context *conn,
+ unsigned int i)
+{
+ uint32_t b = htonl ((uint32_t) i);
+ struct GNUNET_HashCode hc;
+ struct GNUNET_TIME_Absolute now;
+ uint32_t ihc;
+
+ now = GNUNET_TIME_absolute_get ();
+ GNUNET_CRYPTO_hash (&b,
+ sizeof (b),
+ &hc);
+ GNUNET_memcpy (&ihc,
+ &hc,
+ sizeof (ihc));
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&hc),
+ GNUNET_PQ_query_param_uint32 (&ihc),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_non_select (conn,
+ "bem_insert",
+ params);
+ return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ }
+}
+
+
+static bool
+bm_select (struct GNUNET_PQ_Context *conn,
+ unsigned int i)
+{
+ uint32_t b = htonl ((uint32_t) i);
+ struct GNUNET_HashCode hc;
+ struct GNUNET_TIME_Absolute now;
+
+ GNUNET_CRYPTO_hash (&b,
+ sizeof (b),
+ &hc);
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&hc),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ &now),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (conn,
+ "bm_select",
+ params,
+ rs);
+ return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ }
+}
+
+
+static bool
+bhm_select (struct GNUNET_PQ_Context *conn,
+ unsigned int i)
+{
+ uint32_t b = htonl ((uint32_t) i);
+ struct GNUNET_HashCode hc;
+ struct GNUNET_TIME_Absolute now;
+
+ GNUNET_CRYPTO_hash (&b,
+ sizeof (b),
+ &hc);
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&hc),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ &now),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (conn,
+ "bhm_select",
+ params,
+ rs);
+ return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ }
+}
+
+
+static bool
+bem_select (struct GNUNET_PQ_Context *conn,
+ unsigned int i)
+{
+ uint32_t b = htonl ((uint32_t) i);
+ struct GNUNET_HashCode hc;
+ struct GNUNET_TIME_Absolute now;
+ uint32_t ihc;
+
+ GNUNET_CRYPTO_hash (&b,
+ sizeof (b),
+ &hc);
+ GNUNET_memcpy (&ihc,
+ &hc,
+ sizeof (ihc));
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint32 (&ihc),
+ GNUNET_PQ_query_param_auto_from_type (&hc),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ &now),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (conn,
+ "bem_select",
+ params,
+ rs);
+ return (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ }
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+static void
+run (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct GNUNET_PQ_Context *conn;
+ struct GNUNET_PQ_Context *conn2;
+ struct GNUNET_TIME_Absolute now;
+ pid_t f;
+ int status;
+
+ conn = GNUNET_PQ_connect_with_cfg (cfg,
+ "bench-db-postgres",
+ "benchmark-",
+ NULL,
+ NULL);
+ if (NULL == conn)
+ {
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ conn2 = GNUNET_PQ_connect_with_cfg (cfg,
+ "bench-db-postgres",
+ NULL,
+ NULL,
+ NULL);
+ if (! prepare (conn))
+ {
+ GNUNET_PQ_disconnect (conn);
+ GNUNET_PQ_disconnect (conn2);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ if (! prepare (conn2))
+ {
+ GNUNET_PQ_disconnect (conn);
+ GNUNET_PQ_disconnect (conn2);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ {
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute ("DELETE FROM benchmap;"),
+ GNUNET_PQ_make_try_execute ("DELETE FROM benchemap;"),
+ GNUNET_PQ_make_try_execute ("DELETE FROM benchhmap;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_PQ_exec_statements (conn,
+ es));
+ }
+ now = GNUNET_TIME_absolute_get ();
+ for (unsigned int i = 0; i<TOTAL; i++)
+ if (! bm_insert (conn,
+ i))
+ {
+ GNUNET_PQ_disconnect (conn);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Insertion of %u elements took %s\n",
+ (unsigned int) TOTAL,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+ now = GNUNET_TIME_absolute_get ();
+ f = fork ();
+ for (unsigned int i = 0; i<TOTAL; i++)
+ {
+ uint32_t j;
+
+ j = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
+ TOTAL);
+ if (! bm_select ((0 == f) ? conn2 : conn,
+ j))
+ {
+ GNUNET_PQ_disconnect (conn);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ }
+ if (0 == f)
+ exit (0);
+ waitpid (f, &status, 0);
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Selection of 2x%u elements took %s\n",
+ (unsigned int) TOTAL,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+
+ now = GNUNET_TIME_absolute_get ();
+ for (unsigned int i = 0; i<TOTAL; i++)
+ if (! bhm_insert (conn,
+ i))
+ {
+ GNUNET_PQ_disconnect (conn);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Insertion of %u elements with hash index took %s\n",
+ (unsigned int) TOTAL,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+ now = GNUNET_TIME_absolute_get ();
+ f = fork ();
+ for (unsigned int i = 0; i<TOTAL; i++)
+ {
+ uint32_t j;
+
+ j = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
+ TOTAL);
+ if (! bhm_select ((0 == f) ? conn2 : conn,
+ j))
+ {
+ GNUNET_PQ_disconnect (conn);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ }
+ if (0 == f)
+ exit (0);
+ waitpid (f, &status, 0);
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Selection of 2x%u elements with hash index took %s\n",
+ (unsigned int) TOTAL,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+
+ now = GNUNET_TIME_absolute_get ();
+ for (unsigned int i = 0; i<TOTAL; i++)
+ if (! bem_insert (conn,
+ i))
+ {
+ GNUNET_PQ_disconnect (conn);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Insertion of %u elements with short element took %s\n",
+ (unsigned int) TOTAL,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+ now = GNUNET_TIME_absolute_get ();
+ f = fork ();
+ for (unsigned int i = 0; i<TOTAL; i++)
+ {
+ uint32_t j;
+
+ j = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
+ TOTAL);
+ if (! bem_select ((0 == f) ? conn2 : conn,
+ j))
+ {
+ GNUNET_PQ_disconnect (conn);
+ result = EXIT_FAILURE;
+ GNUNET_break (0);
+ return;
+ }
+ }
+ if (0 == f)
+ exit (0);
+ waitpid (f, &status, 0);
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "Selection of 2x%u elements with short element took %s\n",
+ (unsigned int) TOTAL,
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_duration (now),
+ GNUNET_YES));
+
+ GNUNET_PQ_disconnect (conn);
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ char *testname;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ (void) argc;
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ GNUNET_log_setup (argv[0],
+ "INFO",
+ NULL);
+ plugin_name++;
+ (void) GNUNET_asprintf (&testname,
+ "bench-db-%s",
+ plugin_name);
+ (void) GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ TALER_OS_init ();
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return result;
+}
+
+
+/* end of bench_db.c */
diff --git a/src/exchangedb/benchmark-0001.sql b/src/exchangedb/benchmark-0001.sql
new file mode 100644
index 000000000..34fed6a55
--- /dev/null
+++ b/src/exchangedb/benchmark-0001.sql
@@ -0,0 +1,55 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2021 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+-- Check patch versioning is in place.
+SELECT _v.register_patch('benchmark-0001', NULL, NULL);
+
+-- Naive, btree version
+CREATE TABLE IF NOT EXISTS benchmap
+ (uuid BIGSERIAL PRIMARY KEY
+ ,hc BYTEA UNIQUE CHECK(LENGTH(hc)=64)
+ ,expiration_date INT8 NOT NULL
+ );
+
+-- Replace btree with hash-based index
+CREATE TABLE IF NOT EXISTS benchhmap
+ (uuid BIGSERIAL PRIMARY KEY
+ ,hc BYTEA NOT NULL CHECK(LENGTH(hc)=64)
+ ,expiration_date INT8 NOT NULL
+ );
+CREATE INDEX IF NOT EXISTS benchhmap_index
+ ON benchhmap
+ USING HASH (hc);
+ALTER TABLE benchhmap
+ ADD CONSTRAINT pk
+ EXCLUDE USING HASH (hc with =);
+
+-- Keep btree, also add 32-bit hash-based index on top
+CREATE TABLE IF NOT EXISTS benchemap
+ (uuid BIGSERIAL PRIMARY KEY
+ ,ihc INT4 NOT NULL
+ ,hc BYTEA UNIQUE CHECK(LENGTH(hc)=64)
+ ,expiration_date INT8 NOT NULL
+ );
+CREATE INDEX IF NOT EXISTS benchemap_index
+ ON benchemap
+ USING HASH (ihc);
+
+-- Complete transaction
+COMMIT;
diff --git a/src/exchangedb/drop.sql b/src/exchangedb/drop.sql
new file mode 100644
index 000000000..b7583f794
--- /dev/null
+++ b/src/exchangedb/drop.sql
@@ -0,0 +1,39 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+WITH xpatches AS (
+ SELECT patch_name
+ FROM _v.patches
+ WHERE starts_with(patch_name,'exchange-')
+)
+ SELECT _v.unregister_patch(xpatches.patch_name)
+ FROM xpatches;
+
+
+WITH xpatches AS (
+ SELECT patch_name
+ FROM _v.patches
+ WHERE starts_with(patch_name,'auditor-triggers-')
+)
+ SELECT _v.unregister_patch(xpatches.patch_name)
+ FROM xpatches;
+
+DROP SCHEMA exchange CASCADE;
+
+COMMIT;
diff --git a/src/exchangedb/drop0001.sql b/src/exchangedb/drop0001.sql
deleted file mode 100644
index 507393e57..000000000
--- a/src/exchangedb/drop0001.sql
+++ /dev/null
@@ -1,69 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2014--2020 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU General Public License as published by the Free Software
--- Foundation; either version 3, or (at your option) any later version.
---
--- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
--- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
--- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
---
--- You should have received a copy of the GNU General Public License along with
--- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
---
-
--- Everything in one big transaction
-BEGIN;
-
--- This script DROPs all of the tables we create.
---
--- Unlike the other SQL files, it SHOULD be updated to reflect the
--- latest requirements for dropping tables.
-
-
--- Drops for exchange-0003.sql
-DROP TABLE IF EXISTS revolving_work_shards CASCADE;
-
-
--- Drops for exchange-0002.sql
-DROP TABLE IF EXISTS auditors CASCADE;
-DROP TABLE IF EXISTS auditor_denom_sigs CASCADE;
-DROP TABLE IF EXISTS exchange_sign_keys CASCADE;
-DROP TABLE IF EXISTS wire_accounts CASCADE;
-DROP TABLE IF EXISTS signkey_revocations CASCADE;
-DROP TABLE IF EXISTS work_shards CASCADE;
-
--- Drops for 0001.sql
-DROP TABLE IF EXISTS prewire CASCADE;
-DROP TABLE IF EXISTS recoup CASCADE;
-DROP TABLE IF EXISTS recoup_refresh CASCADE;
-DROP TABLE IF EXISTS aggregation_tracking CASCADE;
-DROP TABLE IF EXISTS wire_out CASCADE;
-DROP TABLE IF EXISTS wire_fee CASCADE;
-DROP TABLE IF EXISTS deposits CASCADE;
-DROP TABLE IF EXISTS refunds CASCADE;
-DROP TABLE IF EXISTS refresh_commitments CASCADE;
-DROP TABLE IF EXISTS refresh_revealed_coins CASCADE;
-DROP TABLE IF EXISTS refresh_transfer_keys CASCADE;
-DROP TABLE IF EXISTS known_coins CASCADE;
-DROP TABLE IF EXISTS reserves_close CASCADE;
-DROP TABLE IF EXISTS reserves_out CASCADE;
-DROP TABLE IF EXISTS reserves_in CASCADE;
-DROP TABLE IF EXISTS reserves CASCADE;
-DROP TABLE IF EXISTS denomination_revocations CASCADE;
-DROP TABLE IF EXISTS denominations CASCADE;
-
-
--- Unregister patch (exchange-0001.sql)
-SELECT _v.unregister_patch('exchange-0001');
-
--- Unregister patch (exchange-0002.sql)
-SELECT _v.unregister_patch('exchange-0002');
-
--- Unregister patch (exchange-0003.sql)
-SELECT _v.unregister_patch('exchange-0003');
-
--- And we're out of here...
-COMMIT;
diff --git a/src/exchangedb/exchange-0001.sql b/src/exchangedb/exchange-0001.sql
index c8438d5c5..a4b1c8b9f 100644
--- a/src/exchangedb/exchange-0001.sql
+++ b/src/exchangedb/exchange-0001.sql
@@ -1,6 +1,6 @@
--
-- This file is part of TALER
--- Copyright (C) 2014--2020 Taler Systems SA
+-- Copyright (C) 2014--2022 Taler Systems SA
--
-- TALER is free software; you can redistribute it and/or modify it under the
-- terms of the GNU General Public License as published by the Free Software
@@ -14,440 +14,283 @@
-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
--
--- Everything in one big transaction
BEGIN;
--- Check patch versioning is in place.
SELECT _v.register_patch('exchange-0001', NULL, NULL);
-
-CREATE TABLE IF NOT EXISTS denominations
- (denom_pub_hash BYTEA PRIMARY KEY CHECK (LENGTH(denom_pub_hash)=64)
- ,denom_pub BYTEA NOT NULL
- ,master_pub BYTEA NOT NULL CHECK (LENGTH(master_pub)=32)
- ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
- ,valid_from INT8 NOT NULL
- ,expire_withdraw INT8 NOT NULL
- ,expire_deposit INT8 NOT NULL
- ,expire_legal INT8 NOT NULL
- ,coin_val INT8 NOT NULL
- ,coin_frac INT4 NOT NULL
- ,fee_withdraw_val INT8 NOT NULL
- ,fee_withdraw_frac INT4 NOT NULL
- ,fee_deposit_val INT8 NOT NULL
- ,fee_deposit_frac INT4 NOT NULL
- ,fee_refresh_val INT8 NOT NULL
- ,fee_refresh_frac INT4 NOT NULL
- ,fee_refund_val INT8 NOT NULL
- ,fee_refund_frac INT4 NOT NULL
- );
-COMMENT ON TABLE denominations
- IS 'Main denominations table. All the valid denominations the exchange knows about.';
-
-CREATE INDEX IF NOT EXISTS denominations_expire_legal_index
- ON denominations
- (expire_legal);
-
-
-CREATE TABLE IF NOT EXISTS denomination_revocations
- (denom_revocations_serial_id BIGSERIAL UNIQUE
- ,denom_pub_hash BYTEA PRIMARY KEY REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE
- ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
- );
-COMMENT ON TABLE denomination_revocations
- IS 'remembering which denomination keys have been revoked';
-
-
-CREATE TABLE IF NOT EXISTS reserves
- (reserve_pub BYTEA PRIMARY KEY CHECK(LENGTH(reserve_pub)=32)
- ,account_details TEXT NOT NULL
- ,current_balance_val INT8 NOT NULL
- ,current_balance_frac INT4 NOT NULL
- ,expiration_date INT8 NOT NULL
- ,gc_date INT8 NOT NULL
- );
-COMMENT ON TABLE reserves
- IS 'Summarizes the balance of a reserve. Updated when new funds are added or withdrawn.';
-COMMENT ON COLUMN reserves.expiration_date
- IS 'Used to trigger closing of reserves that have not been drained after some time';
-COMMENT ON COLUMN reserves.gc_date
- IS 'Used to forget all information about a reserve during garbage collection';
-
-
-CREATE INDEX IF NOT EXISTS reserves_expiration_index
- ON reserves
- (expiration_date
- ,current_balance_val
- ,current_balance_frac
- );
-COMMENT ON INDEX reserves_expiration_index
- IS 'used in get_expired_reserves';
-
-CREATE INDEX IF NOT EXISTS reserves_gc_index
- ON reserves
- (gc_date);
-COMMENT ON INDEX reserves_gc_index
- IS 'for reserve garbage collection';
-
-
-CREATE TABLE IF NOT EXISTS reserves_in
- (reserve_in_serial_id BIGSERIAL UNIQUE
- ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE
- ,wire_reference INT8 NOT NULL
- ,credit_val INT8 NOT NULL
- ,credit_frac INT4 NOT NULL
- ,sender_account_details TEXT NOT NULL
- ,exchange_account_section TEXT NOT NULL
- ,execution_date INT8 NOT NULL
- ,PRIMARY KEY (reserve_pub, wire_reference)
- );
-COMMENT ON TABLE reserves_in
- IS 'list of transfers of funds into the reserves, one per incoming wire transfer';
--- FIXME: explain 'wire_reference'!
-CREATE INDEX IF NOT EXISTS reserves_in_execution_index
- ON reserves_in
- (exchange_account_section
- ,execution_date
- );
-CREATE INDEX IF NOT EXISTS reserves_in_exchange_account_serial
- ON reserves_in
- (exchange_account_section,
- reserve_in_serial_id DESC
- );
-
-
-CREATE TABLE IF NOT EXISTS reserves_close
- (close_uuid BIGSERIAL PRIMARY KEY
- ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE
- ,execution_date INT8 NOT NULL
- ,wtid BYTEA NOT NULL CHECK (LENGTH(wtid)=32)
- ,receiver_account TEXT NOT NULL
- ,amount_val INT8 NOT NULL
- ,amount_frac INT4 NOT NULL
- ,closing_fee_val INT8 NOT NULL
- ,closing_fee_frac INT4 NOT NULL);
-COMMENT ON TABLE reserves_close
- IS 'wire transfers executed by the reserve to close reserves';
-
-CREATE INDEX IF NOT EXISTS reserves_close_by_reserve
- ON reserves_close
- (reserve_pub);
-
-
-CREATE TABLE IF NOT EXISTS reserves_out
- (reserve_out_serial_id BIGSERIAL UNIQUE
- ,h_blind_ev BYTEA PRIMARY KEY CHECK (LENGTH(h_blind_ev)=64)
- ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash)
- ,denom_sig BYTEA NOT NULL
- ,reserve_pub BYTEA NOT NULL REFERENCES reserves (reserve_pub) ON DELETE CASCADE
- ,reserve_sig BYTEA NOT NULL CHECK (LENGTH(reserve_sig)=64)
- ,execution_date INT8 NOT NULL
- ,amount_with_fee_val INT8 NOT NULL
- ,amount_with_fee_frac INT4 NOT NULL
- );
-COMMENT ON TABLE reserves_out
- IS 'Withdraw operations performed on reserves.';
-COMMENT ON COLUMN reserves_out.h_blind_ev
- IS 'Hash of the blinded coin, used as primary key here so that broken clients that use a non-random coin or blinding factor fail to withdraw (otherwise they would fail on deposit when the coin is not unique there).';
-COMMENT ON COLUMN reserves_out.denom_pub_hash
- IS 'We do not CASCADE ON DELETE here, we may keep the denomination data alive';
-
-CREATE INDEX IF NOT EXISTS reserves_out_reserve_pub_index
- ON reserves_out
- (reserve_pub);
-COMMENT ON INDEX reserves_out_reserve_pub_index
- IS 'for get_reserves_out';
-CREATE INDEX IF NOT EXISTS reserves_out_execution_date
- ON reserves_out
- (execution_date);
-CREATE INDEX IF NOT EXISTS reserves_out_for_get_withdraw_info
- ON reserves_out
- (denom_pub_hash
- ,h_blind_ev
+CREATE SCHEMA exchange;
+COMMENT ON SCHEMA exchange IS 'taler-exchange data';
+
+SET search_path TO exchange;
+
+---------------------------------------------------------------------------
+-- General procedures for DB setup
+---------------------------------------------------------------------------
+
+CREATE TABLE exchange_tables
+ (table_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY
+ ,name TEXT NOT NULL
+ ,version TEXT NOT NULL
+ ,action TEXT NOT NULL
+ ,partitioned BOOL NOT NULL
+ ,by_range BOOL NOT NULL
+ ,finished BOOL NOT NULL DEFAULT(FALSE));
+COMMENT ON TABLE exchange_tables
+ IS 'Tables of the exchange and their status';
+COMMENT ON COLUMN exchange_tables.name
+ IS 'Base name of the table (without partition/shard)';
+COMMENT ON COLUMN exchange_tables.version
+ IS 'Version of the DB in which the given action happened';
+COMMENT ON COLUMN exchange_tables.action
+ IS 'Action to take on the table (e.g. create, constrain, or foreign). Create is done for the master table and each partition; constrain is only for partitions or for master if there are no partitions; master only on master (takes no argument); foreign only on master if there are no partitions.';
+COMMENT ON COLUMN exchange_tables.partitioned
+ IS 'TRUE if the table is partitioned';
+COMMENT ON COLUMN exchange_tables.by_range
+ IS 'TRUE if the table is partitioned by range';
+COMMENT ON COLUMN exchange_tables.finished
+ IS 'TRUE if the respective migration has been run';
+
+
+CREATE FUNCTION create_partitioned_table(
+ IN table_definition TEXT -- SQL template for table creation
+ ,IN table_name TEXT -- base name of the table
+ ,IN main_table_partition_str TEXT -- declaration for how to partition the table
+ ,IN partition_suffix TEXT DEFAULT NULL -- NULL: no partitioning, 0: yes partitioning, no sharding, >0: sharding
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ IF (partition_suffix IS NULL)
+ THEN
+ -- no partitioning, disable option
+ main_table_partition_str = '';
+ ELSE
+ IF (partition_suffix::int > 0)
+ THEN
+ -- sharding, add shard name
+ table_name=table_name || '_' || partition_suffix;
+ END IF;
+ END IF;
+ EXECUTE FORMAT(
+ table_definition,
+ table_name,
+ main_table_partition_str
);
-
-
-CREATE TABLE IF NOT EXISTS known_coins
- (known_coin_id BIGSERIAL UNIQUE
- ,coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (LENGTH(coin_pub)=32)
- ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE
- ,denom_sig BYTEA NOT NULL
+END $$;
+
+COMMENT ON FUNCTION create_partitioned_table
+ IS 'Generic function to create a table that is partitioned or sharded.';
+
+
+CREATE FUNCTION comment_partitioned_table(
+ IN table_comment TEXT
+ ,IN table_name TEXT
+ ,IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ IF ( (partition_suffix IS NOT NULL) AND
+ (partition_suffix::int > 0) )
+ THEN
+ -- sharding, add shard name
+ table_name=table_name || '_' || partition_suffix;
+ END IF;
+ EXECUTE FORMAT(
+ 'COMMENT ON TABLE %s IS %s'
+ ,table_name
+ ,quote_literal(table_comment)
);
-COMMENT ON TABLE known_coins
- IS 'information about coins and their signatures, so we do not have to store the signatures more than once if a coin is involved in multiple operations';
-
-CREATE INDEX IF NOT EXISTS known_coins_by_denomination
- ON known_coins
- (denom_pub_hash);
-
-
-CREATE TABLE IF NOT EXISTS refresh_commitments
- (melt_serial_id BIGSERIAL UNIQUE
- ,rc BYTEA PRIMARY KEY CHECK (LENGTH(rc)=64)
- ,old_coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE
- ,old_coin_sig BYTEA NOT NULL CHECK(LENGTH(old_coin_sig)=64)
- ,amount_with_fee_val INT8 NOT NULL
- ,amount_with_fee_frac INT4 NOT NULL
- ,noreveal_index INT4 NOT NULL
- );
-COMMENT ON TABLE refresh_commitments
- IS 'Commitments made when melting coins and the gamma value chosen by the exchange.';
-
-CREATE INDEX IF NOT EXISTS refresh_commitments_old_coin_pub_index
- ON refresh_commitments
- (old_coin_pub);
-
-
-CREATE TABLE IF NOT EXISTS refresh_revealed_coins
- (rc BYTEA NOT NULL REFERENCES refresh_commitments (rc) ON DELETE CASCADE
- ,freshcoin_index INT4 NOT NULL
- ,link_sig BYTEA NOT NULL CHECK(LENGTH(link_sig)=64)
- ,denom_pub_hash BYTEA NOT NULL REFERENCES denominations (denom_pub_hash) ON DELETE CASCADE
- ,coin_ev BYTEA UNIQUE NOT NULL
- ,h_coin_ev BYTEA NOT NULL CHECK(LENGTH(h_coin_ev)=64)
- ,ev_sig BYTEA NOT NULL
- ,PRIMARY KEY (rc, freshcoin_index)
- ,UNIQUE (h_coin_ev)
- );
-COMMENT ON TABLE refresh_revealed_coins
- IS 'Revelations about the new coins that are to be created during a melting session.';
-COMMENT ON COLUMN refresh_revealed_coins.rc
- IS 'refresh commitment identifying the melt operation';
-COMMENT ON COLUMN refresh_revealed_coins.freshcoin_index
- IS 'index of the fresh coin being created (one melt operation may result in multiple fresh coins)';
-COMMENT ON COLUMN refresh_revealed_coins.coin_ev
- IS 'envelope of the new coin to be signed';
-COMMENT ON COLUMN refresh_revealed_coins.h_coin_ev
- IS 'hash of the envelope of the new coin to be signed (for lookups)';
-COMMENT ON COLUMN refresh_revealed_coins.ev_sig
- IS 'exchange signature over the envelope';
-
-CREATE INDEX IF NOT EXISTS refresh_revealed_coins_coin_pub_index
- ON refresh_revealed_coins
- (denom_pub_hash);
-
-
-CREATE TABLE IF NOT EXISTS refresh_transfer_keys
- (rc BYTEA NOT NULL PRIMARY KEY REFERENCES refresh_commitments (rc) ON DELETE CASCADE
- ,transfer_pub BYTEA NOT NULL CHECK(LENGTH(transfer_pub)=32)
- ,transfer_privs BYTEA NOT NULL
+END $$;
+
+COMMENT ON FUNCTION comment_partitioned_table
+ IS 'Generic function to create a comment on table that is partitioned.';
+
+
+CREATE FUNCTION comment_partitioned_column(
+ IN table_comment TEXT
+ ,IN column_name TEXT
+ ,IN table_name TEXT
+ ,IN partition_suffix TEXT DEFAULT NULL
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ IF ( (partition_suffix IS NOT NULL) AND
+ (partition_suffix::int > 0) )
+ THEN
+ -- sharding, add shard name
+ table_name=table_name || '_' || partition_suffix;
+ END IF;
+ EXECUTE FORMAT(
+ 'COMMENT ON COLUMN %s.%s IS %s'
+ ,table_name
+ ,column_name
+ ,quote_literal(table_comment)
);
-COMMENT ON TABLE refresh_transfer_keys
- IS 'Transfer keys of a refresh operation (the data revealed to the exchange).';
-COMMENT ON COLUMN refresh_transfer_keys.rc
- IS 'refresh commitment identifying the melt operation';
-COMMENT ON COLUMN refresh_transfer_keys.transfer_pub
- IS 'transfer public key for the gamma index';
-COMMENT ON COLUMN refresh_transfer_keys.transfer_privs
- IS 'array of TALER_CNC_KAPPA - 1 transfer private keys that have been revealed, with the gamma entry being skipped';
-
-CREATE INDEX IF NOT EXISTS refresh_transfer_keys_coin_tpub
- ON refresh_transfer_keys
- (rc
- ,transfer_pub
- );
-COMMENT ON INDEX refresh_transfer_keys_coin_tpub
- IS 'for get_link (unsure if this helps or hurts for performance as there should be very few transfer public keys per rc, but at least in theory this helps the ORDER BY clause)';
-
-
-CREATE TABLE IF NOT EXISTS deposits
- (deposit_serial_id BIGSERIAL PRIMARY KEY
- ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE
- ,amount_with_fee_val INT8 NOT NULL
- ,amount_with_fee_frac INT4 NOT NULL
- ,wallet_timestamp INT8 NOT NULL
- ,exchange_timestamp INT8 NOT NULL
- ,refund_deadline INT8 NOT NULL
- ,wire_deadline INT8 NOT NULL
- ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
- ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
- ,h_wire BYTEA NOT NULL CHECK (LENGTH(h_wire)=64)
- ,coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)
- ,wire TEXT NOT NULL
- ,tiny BOOLEAN NOT NULL DEFAULT FALSE
- ,done BOOLEAN NOT NULL DEFAULT FALSE
- ,UNIQUE (coin_pub, merchant_pub, h_contract_terms)
- );
-COMMENT ON TABLE deposits
- IS 'Deposits we have received and for which we need to make (aggregate) wire transfers (and manage refunds).';
-COMMENT ON COLUMN deposits.done
- IS 'Set to TRUE once we have included this deposit in some aggregate wire transfer to the merchant';
-COMMENT ON COLUMN deposits.tiny
- IS 'Set to TRUE if we decided that the amount is too small to ever trigger a wire transfer by itself (requires real aggregation)';
-
-CREATE INDEX IF NOT EXISTS deposits_coin_pub_merchant_contract_index
- ON deposits
- (coin_pub
- ,merchant_pub
- ,h_contract_terms
- );
-COMMENT ON INDEX deposits_coin_pub_merchant_contract_index
- IS 'for get_deposit_for_wtid and test_deposit_done';
-CREATE INDEX IF NOT EXISTS deposits_get_ready_index
- ON deposits
- (tiny
- ,done
- ,wire_deadline
- ,refund_deadline
- );
-COMMENT ON INDEX deposits_coin_pub_merchant_contract_index
- IS 'for deposits_get_ready';
-CREATE INDEX IF NOT EXISTS deposits_iterate_matching_index
- ON deposits
- (merchant_pub
- ,h_wire
- ,done
- ,wire_deadline
- );
-COMMENT ON INDEX deposits_iterate_matching_index
- IS 'for deposits_iterate_matching';
-
-
-CREATE TABLE IF NOT EXISTS refunds
- (refund_serial_id BIGSERIAL UNIQUE
- ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) ON DELETE CASCADE
- ,merchant_pub BYTEA NOT NULL CHECK(LENGTH(merchant_pub)=32)
- ,merchant_sig BYTEA NOT NULL CHECK(LENGTH(merchant_sig)=64)
- ,h_contract_terms BYTEA NOT NULL CHECK(LENGTH(h_contract_terms)=64)
- ,rtransaction_id INT8 NOT NULL
- ,amount_with_fee_val INT8 NOT NULL
- ,amount_with_fee_frac INT4 NOT NULL
- ,PRIMARY KEY (coin_pub, merchant_pub, h_contract_terms, rtransaction_id)
- );
-COMMENT ON TABLE refunds
- IS 'Data on coins that were refunded. Technically, refunds always apply against specific deposit operations involving a coin. The combination of coin_pub, merchant_pub, h_contract_terms and rtransaction_id MUST be unique, and we usually select by coin_pub so that one goes first.';
-COMMENT ON COLUMN refunds.rtransaction_id
- IS 'used by the merchant to make refunds unique in case the same coin for the same deposit gets a subsequent (higher) refund';
-
-CREATE INDEX IF NOT EXISTS refunds_coin_pub_index
- ON refunds
- (coin_pub);
-
-
-CREATE TABLE IF NOT EXISTS wire_out
- (wireout_uuid BIGSERIAL PRIMARY KEY
- ,execution_date INT8 NOT NULL
- ,wtid_raw BYTEA UNIQUE NOT NULL CHECK (LENGTH(wtid_raw)=32)
- ,wire_target TEXT NOT NULL
- ,exchange_account_section TEXT NOT NULL
- ,amount_val INT8 NOT NULL
- ,amount_frac INT4 NOT NULL
- );
-COMMENT ON TABLE wire_out
- IS 'wire transfers the exchange has executed';
-COMMENT ON COLUMN wire_out.exchange_account_section
- IS 'identifies the configuration section with the debit account of this payment';
-
-CREATE TABLE IF NOT EXISTS aggregation_tracking
- (aggregation_serial_id BIGSERIAL UNIQUE
- ,deposit_serial_id INT8 PRIMARY KEY REFERENCES deposits (deposit_serial_id) ON DELETE CASCADE
- ,wtid_raw BYTEA CONSTRAINT wire_out_ref REFERENCES wire_out(wtid_raw) ON DELETE CASCADE DEFERRABLE
- );
-COMMENT ON TABLE aggregation_tracking
- IS 'mapping from wire transfer identifiers (WTID) to deposits (and back)';
-COMMENT ON COLUMN aggregation_tracking.wtid_raw
- IS 'We first create entries in the aggregation_tracking table and then finally the wire_out entry once we know the total amount. Hence the constraint must be deferrable and we cannot use a wireout_uuid here, because we do not have it when these rows are created. Changing the logic to first INSERT a dummy row into wire_out and then UPDATEing that row in the same transaction would theoretically reduce per-deposit storage costs by 5 percent (24/~460 bytes).';
-
-CREATE INDEX IF NOT EXISTS aggregation_tracking_wtid_index
- ON aggregation_tracking
- (wtid_raw);
-COMMENT ON INDEX aggregation_tracking_wtid_index
- IS 'for lookup_transactions';
-
-
-CREATE TABLE IF NOT EXISTS wire_fee
- (wire_method VARCHAR NOT NULL
- ,start_date INT8 NOT NULL
- ,end_date INT8 NOT NULL
- ,wire_fee_val INT8 NOT NULL
- ,wire_fee_frac INT4 NOT NULL
- ,closing_fee_val INT8 NOT NULL
- ,closing_fee_frac INT4 NOT NULL
- ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
- ,PRIMARY KEY (wire_method, start_date)
- );
-COMMENT ON TABLE wire_fee
- IS 'list of the wire fees of this exchange, by date';
-
-CREATE INDEX IF NOT EXISTS wire_fee_gc_index
- ON wire_fee
- (end_date);
-
-
-CREATE TABLE IF NOT EXISTS recoup
- (recoup_uuid BIGSERIAL UNIQUE
- ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub)
- ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)
- ,coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)
- ,amount_val INT8 NOT NULL
- ,amount_frac INT4 NOT NULL
- ,timestamp INT8 NOT NULL
- ,h_blind_ev BYTEA NOT NULL REFERENCES reserves_out (h_blind_ev) ON DELETE CASCADE
- );
-COMMENT ON TABLE recoup
- IS 'Information about recoups that were executed';
-COMMENT ON COLUMN recoup.coin_pub
- IS 'Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!';
-
--- Note: this first index is redundant;
--- It is implicitly removed by the exchange-0002.sql
--- schema changes.
-CREATE INDEX IF NOT EXISTS recoup_by_coin_index
- ON recoup
- (coin_pub);
-CREATE INDEX IF NOT EXISTS recoup_by_h_blind_ev
- ON recoup
- (h_blind_ev);
-CREATE INDEX IF NOT EXISTS recoup_for_by_reserve
- ON recoup
- (coin_pub
- ,h_blind_ev
- );
-
-
-CREATE TABLE IF NOT EXISTS recoup_refresh
- (recoup_refresh_uuid BIGSERIAL UNIQUE
- ,coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub)
- ,coin_sig BYTEA NOT NULL CHECK(LENGTH(coin_sig)=64)
- ,coin_blind BYTEA NOT NULL CHECK(LENGTH(coin_blind)=32)
- ,amount_val INT8 NOT NULL
- ,amount_frac INT4 NOT NULL
- ,timestamp INT8 NOT NULL
- ,h_blind_ev BYTEA NOT NULL REFERENCES refresh_revealed_coins (h_coin_ev) ON DELETE CASCADE
- );
-COMMENT ON COLUMN recoup_refresh.coin_pub
- IS 'Do not CASCADE ON DROP on the coin_pub, as we may keep the coin alive!';
-
--- Note: this index is redundant; implicitly removed
--- by the exchange-0002.sql update!
-CREATE INDEX IF NOT EXISTS recoup_refresh_by_coin_index
- ON recoup_refresh
- (coin_pub);
-CREATE INDEX IF NOT EXISTS recoup_refresh_by_h_blind_ev
- ON recoup_refresh
- (h_blind_ev);
-CREATE INDEX IF NOT EXISTS recoup_refresh_for_by_reserve
- ON recoup_refresh
- (coin_pub
- ,h_blind_ev
- );
-
-
-CREATE TABLE IF NOT EXISTS prewire
- (prewire_uuid BIGSERIAL PRIMARY KEY
- ,type TEXT NOT NULL
- ,finished BOOLEAN NOT NULL DEFAULT false
- ,buf BYTEA NOT NULL
- );
-COMMENT ON TABLE prewire
- IS 'pre-commit data for wire transfers we are about to execute';
-
-CREATE INDEX IF NOT EXISTS prepare_iteration_index
- ON prewire
- (finished);
-COMMENT ON INDEX prepare_iteration_index
- IS 'for wire_prepare_data_get and gc_prewire';
+END $$;
+
+COMMENT ON FUNCTION comment_partitioned_column
+ IS 'Generic function to create a comment on column of a table that is partitioned.';
+
+
+---------------------------------------------------------------------------
+-- Main DB setup loop
+---------------------------------------------------------------------------
+
+CREATE FUNCTION do_create_tables(
+ num_partitions INTEGER
+-- NULL: no partitions, add foreign constraints
+-- 0: no partitions, no foreign constraints
+-- 1: only 1 default partition
+-- > 1: normal partitions
+)
+ RETURNS VOID
+ LANGUAGE plpgsql
+AS $$
+DECLARE
+ tc CURSOR FOR
+ SELECT table_serial_id
+ ,name
+ ,action
+ ,partitioned
+ ,by_range
+ FROM exchange.exchange_tables
+ WHERE NOT finished
+ ORDER BY table_serial_id ASC;
+BEGIN
+ FOR rec IN tc
+ LOOP
+ CASE rec.action
+ -- "create" actions apply to master and partitions
+ WHEN 'create'
+ THEN
+ IF (rec.partitioned AND
+ (num_partitions IS NOT NULL))
+ THEN
+ -- Create master table with partitioning.
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s (%s)'::text
+ ,rec.action
+ ,rec.name
+ ,quote_literal('0')
+ );
+ IF (rec.by_range OR
+ (num_partitions = 0))
+ THEN
+ -- Create default partition.
+ IF (rec.by_range)
+ THEN
+ -- Range partition
+ EXECUTE FORMAT(
+ 'CREATE TABLE exchange.%s_default'
+ ' PARTITION OF %s'
+ ' DEFAULT'
+ ,rec.name
+ ,rec.name
+ );
+ ELSE
+ -- Hash partition
+ EXECUTE FORMAT(
+ 'CREATE TABLE exchange.%s_default'
+ ' PARTITION OF %s'
+ ' FOR VALUES WITH (MODULUS 1, REMAINDER 0)'
+ ,rec.name
+ ,rec.name
+ );
+ END IF;
+ ELSE
+ FOR i IN 1..num_partitions LOOP
+ -- Create num_partitions
+ EXECUTE FORMAT(
+ 'CREATE TABLE exchange.%I'
+ ' PARTITION OF %I'
+ ' FOR VALUES WITH (MODULUS %s, REMAINDER %s)'
+ ,rec.name || '_' || i
+ ,rec.name
+ ,num_partitions
+ ,i-1
+ );
+ END LOOP;
+ END IF;
+ ELSE
+ -- Only create master table. No partitions.
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s ()'::text
+ ,rec.action
+ ,rec.name
+ );
+ END IF;
+ -- Constrain action apply to master OR each partition
+ WHEN 'constrain'
+ THEN
+ ASSERT rec.partitioned, 'constrain action only applies to partitioned tables';
+ IF (num_partitions IS NULL)
+ THEN
+ -- Constrain master table
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s (NULL)'::text
+ ,rec.action
+ ,rec.name
+ );
+ ELSE
+ IF ( (num_partitions = 0) OR
+ (rec.by_range) )
+ THEN
+ -- Constrain default table
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s (%s)'::text
+ ,rec.action
+ ,rec.name
+ ,quote_literal('default')
+ );
+ ELSE
+ -- Constrain each partition
+ FOR i IN 1..num_partitions LOOP
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s (%s)'::text
+ ,rec.action
+ ,rec.name
+ ,quote_literal(i)
+ );
+ END LOOP;
+ END IF;
+ END IF;
+ -- Foreign actions only apply if partitioning is off
+ WHEN 'foreign'
+ THEN
+ IF (num_partitions IS NULL)
+ THEN
+ -- Add foreign constraints
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s (%s)'::text
+ ,rec.action
+ ,rec.name
+ ,NULL
+ );
+ END IF;
+ WHEN 'master'
+ THEN
+ EXECUTE FORMAT(
+ 'SELECT exchange.%s_table_%s ()'::text
+ ,rec.action
+ ,rec.name
+ );
+ ELSE
+ ASSERT FALSE, 'unsupported action type: ' || rec.action;
+ END CASE; -- END CASE (rec.action)
+ -- Mark as finished
+ UPDATE exchange.exchange_tables
+ SET finished=TRUE
+ WHERE table_serial_id=rec.table_serial_id;
+ END LOOP; -- create/alter/drop actions
+END $$;
+
+COMMENT ON FUNCTION do_create_tables
+ IS 'Creates all tables for the given number of partitions that need creating. Does NOT support sharding.';
--- Complete transaction
COMMIT;
diff --git a/src/exchangedb/exchange-0002.sql b/src/exchangedb/exchange-0002.sql
deleted file mode 100644
index 175ffb392..000000000
--- a/src/exchangedb/exchange-0002.sql
+++ /dev/null
@@ -1,473 +0,0 @@
---
--- This file is part of TALER
--- Copyright (C) 2020-2021 Taler Systems SA
---
--- TALER is free software; you can redistribute it and/or modify it under the
--- terms of the GNU General Public License as published by the Free Software
--- Foundation; either version 3, or (at your option) any later version.
---
--- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
--- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
--- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
---
--- You should have received a copy of the GNU General Public License along with
--- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
---
-
--- Everything in one big transaction
-BEGIN;
-
--- Check patch versioning is in place.
-SELECT _v.register_patch('exchange-0002', NULL, NULL);
-
--- Need 'failed' bit to prevent hanging transfer tool in case
--- bank API fails.
-ALTER TABLE prewire
- ADD failed BOOLEAN NOT NULL DEFAULT false;
-
-COMMENT ON COLUMN prewire.failed
- IS 'set to TRUE if the bank responded with a non-transient failure to our transfer request';
-COMMENT ON COLUMN prewire.finished
- IS 'set to TRUE once bank confirmed receiving the wire transfer request';
-COMMENT ON COLUMN prewire.buf
- IS 'serialized data to send to the bank to execute the wire transfer';
-
--- change comment, existing index is still useful, but only for gc_prewire.
-COMMENT ON INDEX prepare_iteration_index
- IS 'for gc_prewire';
-
--- need a new index for updated wire_prepare_data_get statement:
-CREATE INDEX IF NOT EXISTS prepare_get_index
- ON prewire
- (failed,finished);
-COMMENT ON INDEX prepare_get_index
- IS 'for wire_prepare_data_get';
-
-
--- we do not actually need the master public key, it is always the same
-ALTER TABLE denominations
- DROP COLUMN master_pub;
-
--- need serial IDs on various tables for exchange-auditor replication
-ALTER TABLE denominations
- ADD COLUMN denominations_serial BIGSERIAL UNIQUE;
-COMMENT ON COLUMN denominations.denominations_serial
- IS 'needed for exchange-auditor replication logic';
-ALTER TABLE refresh_revealed_coins
- ADD COLUMN rrc_serial BIGSERIAL UNIQUE;
-COMMENT ON COLUMN refresh_revealed_coins.rrc_serial
- IS 'needed for exchange-auditor replication logic';
-ALTER TABLE refresh_transfer_keys
- ADD COLUMN rtc_serial BIGSERIAL UNIQUE;
-COMMENT ON COLUMN refresh_transfer_keys.rtc_serial
- IS 'needed for exchange-auditor replication logic';
-ALTER TABLE wire_fee
- ADD COLUMN wire_fee_serial BIGSERIAL UNIQUE;
-COMMENT ON COLUMN wire_fee.wire_fee_serial
- IS 'needed for exchange-auditor replication logic';
-
--- for the reserves, we add the new reserve_uuid, and also
--- change the foreign keys to use the new BIGSERIAL instead
--- of the public key to reference the entry
-ALTER TABLE reserves
- ADD COLUMN reserve_uuid BIGSERIAL UNIQUE;
-ALTER TABLE reserves_in
- ADD COLUMN reserve_uuid INT8 REFERENCES reserves (reserve_uuid) ON DELETE CASCADE;
-UPDATE reserves_in
- SET reserve_uuid=r.reserve_uuid
- FROM reserves_in rin
- INNER JOIN reserves r USING(reserve_pub);
-ALTER TABLE reserves_in
- ALTER COLUMN reserve_uuid SET NOT NULL;
-ALTER TABLE reserves_in
- DROP COLUMN reserve_pub,
- ADD CONSTRAINT unique_in PRIMARY KEY (reserve_uuid, wire_reference);
-
-ALTER TABLE reserves_out
- ADD COLUMN reserve_uuid INT8 REFERENCES reserves (reserve_uuid) ON DELETE CASCADE;
-UPDATE reserves_out
- SET reserve_uuid=r.reserve_uuid
- FROM reserves_out rout
- INNER JOIN reserves r USING(reserve_pub);
-ALTER TABLE reserves_out
- ALTER COLUMN reserve_uuid SET NOT NULL;
-ALTER TABLE reserves_out
- DROP COLUMN reserve_pub;
-ALTER TABLE reserves_close
- ADD COLUMN reserve_uuid INT8 REFERENCES reserves (reserve_uuid) ON DELETE CASCADE;
-CREATE INDEX IF NOT EXISTS reserves_out_reserve_uuid_index
- ON reserves_out
- (reserve_uuid);
-COMMENT ON INDEX reserves_out_reserve_uuid_index
- IS 'for get_reserves_out';
-
-UPDATE reserves_close
- SET reserve_uuid=r.reserve_uuid
- FROM reserves_close rclose
- INNER JOIN reserves r USING(reserve_pub);
-ALTER TABLE reserves_close
- ALTER COLUMN reserve_uuid SET NOT NULL;
-ALTER TABLE reserves_close
- DROP COLUMN reserve_pub;
-CREATE INDEX IF NOT EXISTS reserves_close_by_uuid
- ON reserves_close
- (reserve_uuid);
-
-
-
--- change all foreign keys using 'denom_pub_hash' to using 'denominations_serial' instead
-ALTER TABLE reserves_out
- ADD COLUMN denominations_serial INT8 REFERENCES denominations (denominations_serial) ON DELETE CASCADE;
-UPDATE reserves_out
- SET denominations_serial=d.denominations_serial
- FROM reserves_out o
- INNER JOIN denominations d USING(denom_pub_hash);
-ALTER TABLE reserves_out
- ALTER COLUMN denominations_serial SET NOT NULL;
-ALTER TABLE reserves_out
- DROP COLUMN denom_pub_hash;
-CREATE INDEX IF NOT EXISTS reserves_out_for_get_withdraw_info
- ON reserves_out
- (denominations_serial
- ,h_blind_ev
- );
-
-ALTER TABLE known_coins
- ADD COLUMN denominations_serial INT8 REFERENCES denominations (denominations_serial) ON DELETE CASCADE;
-UPDATE known_coins
- SET denominations_serial=d.denominations_serial
- FROM known_coins o
- INNER JOIN denominations d USING(denom_pub_hash);
-ALTER TABLE known_coins
- ALTER COLUMN denominations_serial SET NOT NULL;
-ALTER TABLE known_coins
- DROP COLUMN denom_pub_hash;
-CREATE INDEX IF NOT EXISTS known_coins_by_denomination
- ON known_coins
- (denominations_serial);
-
-ALTER TABLE denomination_revocations
- ADD COLUMN denominations_serial INT8 REFERENCES denominations (denominations_serial) ON DELETE CASCADE;
-UPDATE denomination_revocations
- SET denominations_serial=d.denominations_serial
- FROM denomination_revocations o
- INNER JOIN denominations d USING(denom_pub_hash);
-ALTER TABLE denomination_revocations
- ALTER COLUMN denominations_serial SET NOT NULL;
-ALTER TABLE denomination_revocations
- DROP COLUMN denom_pub_hash;
-ALTER TABLE denomination_revocations
- ADD CONSTRAINT denominations_serial_pk PRIMARY KEY (denominations_serial);
-CREATE INDEX IF NOT EXISTS denomination_revocations_by_denomination
- ON denomination_revocations
- (denominations_serial);
-
-ALTER TABLE refresh_revealed_coins
- ADD COLUMN denominations_serial INT8 REFERENCES denominations (denominations_serial) ON DELETE CASCADE;
-UPDATE refresh_revealed_coins
- SET denominations_serial=d.denominations_serial
- FROM refresh_revealed_coins o
- INNER JOIN denominations d USING(denom_pub_hash);
-ALTER TABLE refresh_revealed_coins
- ALTER COLUMN denominations_serial SET NOT NULL;
-ALTER TABLE refresh_revealed_coins
- DROP COLUMN denom_pub_hash;
-CREATE INDEX IF NOT EXISTS refresh_revealed_coins_denominations_index
- ON refresh_revealed_coins
- (denominations_serial);
-
--- Change all foreign keys involving 'coin_pub' to use known_coin_id instead.
-ALTER TABLE recoup_refresh
- ADD COLUMN known_coin_id INT8 REFERENCES known_coins (known_coin_id) ON DELETE CASCADE;
-UPDATE recoup_refresh
- SET known_coin_id=d.known_coin_id
- FROM recoup_refresh o
- INNER JOIN known_coins d USING(coin_pub);
-ALTER TABLE recoup_refresh
- ALTER COLUMN known_coin_id SET NOT NULL;
-ALTER TABLE recoup_refresh
- DROP COLUMN coin_pub;
-
-
-ALTER TABLE recoup
- ADD COLUMN known_coin_id INT8 REFERENCES known_coins (known_coin_id) ON DELETE CASCADE;
-UPDATE recoup
- SET known_coin_id=d.known_coin_id
- FROM recoup o
- INNER JOIN known_coins d USING(coin_pub);
-ALTER TABLE recoup
- ALTER COLUMN known_coin_id SET NOT NULL;
-ALTER TABLE recoup
- DROP COLUMN coin_pub;
-
-
-ALTER TABLE refresh_commitments
- ADD COLUMN old_known_coin_id INT8 REFERENCES known_coins (known_coin_id) ON DELETE CASCADE;
-UPDATE refresh_commitments
- SET old_known_coin_id=d.known_coin_id
- FROM refresh_commitments o
- INNER JOIN known_coins d ON(o.old_coin_pub=d.coin_pub);
-ALTER TABLE refresh_commitments
- ALTER COLUMN old_known_coin_id SET NOT NULL;
-ALTER TABLE refresh_commitments
- DROP COLUMN old_coin_pub;
-CREATE INDEX IF NOT EXISTS refresh_commitments_old_coin_pub_index
- ON refresh_commitments
- (old_known_coin_id);
-
-
-ALTER TABLE deposits
- ADD COLUMN known_coin_id INT8 REFERENCES known_coins (known_coin_id) ON DELETE CASCADE;
-UPDATE deposits
- SET known_coin_id=d.known_coin_id
- FROM deposits o
- INNER JOIN known_coins d USING(coin_pub);
-ALTER TABLE deposits
- ALTER COLUMN known_coin_id SET NOT NULL,
- ADD CONSTRAINT deposit_unique UNIQUE (known_coin_id, merchant_pub, h_contract_terms);
-ALTER TABLE deposits
- DROP COLUMN coin_pub;
-
-ALTER TABLE refunds
- ADD COLUMN known_coin_id INT8 REFERENCES known_coins (known_coin_id) ON DELETE CASCADE;
-UPDATE refunds
- SET known_coin_id=d.known_coin_id
- FROM refunds o
- INNER JOIN known_coins d USING(coin_pub);
-ALTER TABLE refunds
- ALTER COLUMN known_coin_id SET NOT NULL;
-ALTER TABLE refunds
- DROP COLUMN coin_pub;
-
--- Change 'h_blind_ev' in recoup table to 'reserve_out_serial_id'
-ALTER TABLE recoup
- ADD COLUMN reserve_out_serial_id INT8 REFERENCES reserves_out (reserve_out_serial_id) ON DELETE CASCADE;
-UPDATE recoup
- SET reserve_out_serial_id=d.reserve_out_serial_id
- FROM recoup o
- INNER JOIN reserves_out d USING(h_blind_ev);
-ALTER TABLE recoup
- ALTER COLUMN reserve_out_serial_id SET NOT NULL;
-ALTER TABLE recoup
- DROP COLUMN h_blind_ev;
-CREATE INDEX IF NOT EXISTS recoup_by_h_blind_ev
- ON recoup
- (reserve_out_serial_id);
-CREATE INDEX IF NOT EXISTS recoup_for_by_reserve
- ON recoup
- (known_coin_id
- ,reserve_out_serial_id
- );
-
-
-COMMENT ON COLUMN recoup.reserve_out_serial_id
- IS 'Identifies the h_blind_ev of the recouped coin.';
-
-
--- Change 'h_blind_ev' in recoup_refresh table to 'rrc_serial'
-ALTER TABLE recoup_refresh
- ADD COLUMN rrc_serial INT8 REFERENCES refresh_revealed_coins (rrc_serial) ON DELETE CASCADE;
-UPDATE recoup_refresh
- SET rrc_serial=d.rrc_serial
- FROM recoup_refresh o
- INNER JOIN refresh_revealed_coins d ON (d.h_coin_ev = o.h_blind_ev);
-ALTER TABLE recoup_refresh
- ALTER COLUMN rrc_serial SET NOT NULL,
- ADD CONSTRAINT recoup_unique UNIQUE (rrc_serial);
-ALTER TABLE recoup_refresh
- DROP COLUMN h_blind_ev;
-COMMENT ON COLUMN recoup_refresh.rrc_serial
- IS 'Identifies the h_blind_ev of the recouped coin (as h_coin_ev).';
-CREATE INDEX IF NOT EXISTS recoup_refresh_by_h_blind_ev
- ON recoup_refresh
- (rrc_serial);
-CREATE INDEX IF NOT EXISTS recoup_refresh_for_by_reserve
- ON recoup_refresh
- (known_coin_id
- ,rrc_serial
- );
-
-
--- Change 'rc' in refresh_transfer_keys and refresh_revealed_coins tables to 'melt_serial_id'
-ALTER TABLE refresh_transfer_keys
- ADD COLUMN melt_serial_id INT8 REFERENCES refresh_commitments (melt_serial_id) ON DELETE CASCADE;
-UPDATE refresh_transfer_keys
- SET melt_serial_id=d.melt_serial_id
- FROM refresh_transfer_keys o
- INNER JOIN refresh_commitments d ON (d.rc = o.rc);
-ALTER TABLE refresh_transfer_keys
- ALTER COLUMN melt_serial_id SET NOT NULL;
-ALTER TABLE refresh_transfer_keys
- DROP COLUMN rc;
-COMMENT ON COLUMN refresh_transfer_keys.melt_serial_id
- IS 'Identifies the refresh commitment (rc) of the operation.';
-CREATE INDEX IF NOT EXISTS refresh_transfer_keys_coin_tpub
- ON refresh_transfer_keys
- (melt_serial_id
- ,transfer_pub
- );
-COMMENT ON INDEX refresh_transfer_keys_coin_tpub
- IS 'for get_link (unsure if this helps or hurts for performance as there should be very few transfer public keys per rc, but at least in theory this helps the ORDER BY clause)';
-
-
-ALTER TABLE refresh_revealed_coins
- ADD COLUMN melt_serial_id INT8 REFERENCES refresh_commitments (melt_serial_id) ON DELETE CASCADE;
-UPDATE refresh_revealed_coins
- SET melt_serial_id=d.melt_serial_id
- FROM refresh_revealed_coins o
- INNER JOIN refresh_commitments d ON (d.rc = o.rc);
-ALTER TABLE refresh_revealed_coins
- ALTER COLUMN melt_serial_id SET NOT NULL;
-ALTER TABLE refresh_revealed_coins
- DROP COLUMN rc;
-COMMENT ON COLUMN refresh_revealed_coins.melt_serial_id
- IS 'Identifies the refresh commitment (rc) of the operation.';
-
-
--- Change 'merchant_pub' and 'h_contract_terms' and 'known_coin_id' in 'refunds' table
--- to 'deposit_serial_id' instead!
-ALTER TABLE refunds
- ADD COLUMN deposit_serial_id INT8 REFERENCES deposits (deposit_serial_id) ON DELETE CASCADE;
-UPDATE refunds
- SET deposit_serial_id=d.deposit_serial_id
- FROM refunds o
- INNER JOIN deposits d
- ON ( (d.known_coin_id = o.known_coin_id) AND
- (d.h_contract_terms = o.h_contract_terms) AND
- (d.merchant_pub = o.merchant_pub) );
-ALTER TABLE refunds
- ALTER COLUMN deposit_serial_id SET NOT NULL;
-ALTER TABLE refunds
- DROP COLUMN merchant_pub,
- DROP COLUMN h_contract_terms,
- DROP COLUMN known_coin_id;
-ALTER TABLE refunds
- ADD CONSTRAINT refunds_primary_key PRIMARY KEY (deposit_serial_id, rtransaction_id);
-COMMENT ON COLUMN refunds.deposit_serial_id
- IS 'Identifies ONLY the merchant_pub, h_contract_terms and known_coin_id. Multiple deposits may match a refund, this only identifies one of them.';
-
-
--- Create additional tables...
-
-CREATE TABLE IF NOT EXISTS auditors
- (auditor_uuid BIGSERIAL UNIQUE
- ,auditor_pub BYTEA PRIMARY KEY CHECK (LENGTH(auditor_pub)=32)
- ,auditor_name VARCHAR NOT NULL
- ,auditor_url VARCHAR NOT NULL
- ,is_active BOOLEAN NOT NULL
- ,last_change INT8 NOT NULL
- );
-COMMENT ON TABLE auditors
- IS 'Table with auditors the exchange uses or has used in the past. Entries never expire as we need to remember the last_change column indefinitely.';
-COMMENT ON COLUMN auditors.auditor_pub
- IS 'Public key of the auditor.';
-COMMENT ON COLUMN auditors.auditor_url
- IS 'The base URL of the auditor.';
-COMMENT ON COLUMN auditors.is_active
- IS 'true if we are currently supporting the use of this auditor.';
-COMMENT ON COLUMN auditors.last_change
- IS 'Latest time when active status changed. Used to detect replays of old messages.';
-
-
-CREATE TABLE IF NOT EXISTS auditor_denom_sigs
- (auditor_denom_serial BIGSERIAL UNIQUE
- ,auditor_uuid INT8 NOT NULL REFERENCES auditors (auditor_uuid) ON DELETE CASCADE
- ,denominations_serial INT8 NOT NULL REFERENCES denominations (denominations_serial) ON DELETE CASCADE
- ,auditor_sig BYTEA CHECK (LENGTH(auditor_sig)=64)
- ,PRIMARY KEY (denominations_serial, auditor_uuid)
- );
-COMMENT ON TABLE auditor_denom_sigs
- IS 'Table with auditor signatures on exchange denomination keys.';
-COMMENT ON COLUMN auditor_denom_sigs.auditor_uuid
- IS 'Identifies the auditor.';
-COMMENT ON COLUMN auditor_denom_sigs.denominations_serial
- IS 'Denomination the signature is for.';
-COMMENT ON COLUMN auditor_denom_sigs.auditor_sig
- IS 'Signature of the auditor, of purpose TALER_SIGNATURE_AUDITOR_EXCHANGE_KEYS.';
-
-
-CREATE TABLE IF NOT EXISTS exchange_sign_keys
- (esk_serial BIGSERIAL UNIQUE
- ,exchange_pub BYTEA PRIMARY KEY CHECK (LENGTH(exchange_pub)=32)
- ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
- ,valid_from INT8 NOT NULL
- ,expire_sign INT8 NOT NULL
- ,expire_legal INT8 NOT NULL
- );
-COMMENT ON TABLE exchange_sign_keys
- IS 'Table with master public key signatures on exchange online signing keys.';
-COMMENT ON COLUMN exchange_sign_keys.exchange_pub
- IS 'Public online signing key of the exchange.';
-COMMENT ON COLUMN exchange_sign_keys.master_sig
- IS 'Signature affirming the validity of the signing key of purpose TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY.';
-COMMENT ON COLUMN exchange_sign_keys.valid_from
- IS 'Time when this online signing key will first be used to sign messages.';
-COMMENT ON COLUMN exchange_sign_keys.expire_sign
- IS 'Time when this online signing key will no longer be used to sign.';
-COMMENT ON COLUMN exchange_sign_keys.expire_legal
- IS 'Time when this online signing key legally expires.';
-
-
-CREATE TABLE IF NOT EXISTS wire_accounts
- (payto_uri VARCHAR PRIMARY KEY
- ,master_sig BYTEA CHECK (LENGTH(master_sig)=64)
- ,is_active BOOLEAN NOT NULL
- ,last_change INT8 NOT NULL
- );
-COMMENT ON TABLE wire_accounts
- IS 'Table with current and historic bank accounts of the exchange. Entries never expire as we need to remember the last_change column indefinitely.';
-COMMENT ON COLUMN wire_accounts.payto_uri
- IS 'payto URI (RFC 8905) with the bank account of the exchange.';
-COMMENT ON COLUMN wire_accounts.master_sig
- IS 'Signature of purpose TALER_SIGNATURE_MASTER_WIRE_DETAILS';
-COMMENT ON COLUMN wire_accounts.is_active
- IS 'true if we are currently supporting the use of this account.';
-COMMENT ON COLUMN wire_accounts.last_change
- IS 'Latest time when active status changed. Used to detect replays of old messages.';
--- "wire_accounts" has no BIGSERIAL because it is a 'mutable' table
--- and is of no concern to the auditor
-
-
-CREATE TABLE IF NOT EXISTS signkey_revocations
- (signkey_revocations_serial_id BIGSERIAL UNIQUE
- ,esk_serial INT8 PRIMARY KEY REFERENCES exchange_sign_keys (esk_serial) ON DELETE CASCADE
- ,master_sig BYTEA NOT NULL CHECK (LENGTH(master_sig)=64)
- );
-COMMENT ON TABLE signkey_revocations
- IS 'remembering which online signing keys have been revoked';
-
-
-
-CREATE TABLE IF NOT EXISTS work_shards
- (shard_serial_id BIGSERIAL UNIQUE
- ,last_attempt INT8 NOT NULL
- ,start_row INT8 NOT NULL
- ,end_row INT8 NOT NULL
- ,completed BOOLEAN NOT NULL DEFAULT FALSE
- ,job_name VARCHAR NOT NULL
- ,PRIMARY KEY (job_name, start_row)
- );
-CREATE INDEX IF NOT EXISTS work_shards_index
- ON work_shards
- (job_name
- ,completed
- ,last_attempt
- );
-COMMENT ON TABLE work_shards
- IS 'coordinates work between multiple processes working on the same job';
-COMMENT ON COLUMN work_shards.shard_serial_id
- IS 'unique serial number identifying the shard';
-COMMENT ON COLUMN work_shards.last_attempt
- IS 'last time a worker attempted to work on the shard';
-COMMENT ON COLUMN work_shards.completed
- IS 'set to TRUE once the shard is finished by a worker';
-COMMENT ON COLUMN work_shards.start_row
- IS 'row at which the shard scope starts, inclusive';
-COMMENT ON COLUMN work_shards.end_row
- IS 'row at which the shard scope ends, exclusive';
-COMMENT ON COLUMN work_shards.job_name
- IS 'unique name of the job the workers on this shard are performing';
-
-
--- Complete transaction
-COMMIT;
diff --git a/src/exchangedb/exchange-0002.sql.in b/src/exchangedb/exchange-0002.sql.in
new file mode 100644
index 000000000..ab13b28af
--- /dev/null
+++ b/src/exchangedb/exchange-0002.sql.in
@@ -0,0 +1,118 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+BEGIN;
+
+SELECT _v.register_patch('exchange-0002', NULL, NULL);
+SET search_path TO exchange;
+
+CREATE DOMAIN gnunet_hashcode
+ AS BYTEA
+ CHECK(LENGTH(VALUE) = 32);
+
+CREATE TYPE taler_amount
+ AS
+ (val INT8
+ ,frac INT4
+ );
+COMMENT ON TYPE taler_amount
+ IS 'Stores an amount, fraction is in units of 1/100000000 of the base value';
+
+CREATE TYPE exchange_do_array_reserve_insert_return_type
+ AS
+ (transaction_duplicate BOOLEAN
+ ,ruuid INT8
+ );
+COMMENT ON TYPE exchange_do_array_reserve_insert_return_type
+ IS 'Return type for exchange_do_array_reserves_insert() stored procedure';
+
+CREATE TYPE exchange_do_select_deposits_missing_wire_return_type
+ AS
+ (
+ batch_deposit_serial_id INT8,
+ total_amount taler_amount,
+ wire_target_h_payto BYTEA,
+ deadline INT8
+ );
+COMMENT ON TYPE exchange_do_select_deposits_missing_wire_return_type
+ IS 'Return type for exchange_do_select_deposits_missing_wire';
+
+
+#include "0002-denominations.sql"
+#include "0002-denomination_revocations.sql"
+#include "0002-wire_targets.sql"
+#include "0002-kyc_alerts.sql"
+#include "0002-wire_fee.sql"
+#include "0002-global_fee.sql"
+#include "0002-wire_accounts.sql"
+#include "0002-auditors.sql"
+#include "0002-auditor_denom_sigs.sql"
+#include "0002-exchange_sign_keys.sql"
+#include "0002-signkey_revocations.sql"
+#include "0002-extensions.sql"
+#include "0002-policy_fulfillments.sql"
+#include "0002-policy_details.sql"
+#include "0002-profit_drains.sql"
+#include "0002-legitimization_processes.sql"
+#include "0002-legitimization_requirements.sql"
+#include "0002-reserves.sql"
+#include "0002-reserve_history.sql"
+#include "0002-reserves_in.sql"
+#include "0002-reserves_close.sql"
+#include "0002-close_requests.sql"
+#include "0002-reserves_open_deposits.sql"
+#include "0002-reserves_open_requests.sql"
+#include "0002-reserves_out.sql"
+#include "0002-known_coins.sql"
+#include "0002-coin_history.sql"
+#include "0002-refresh_commitments.sql"
+#include "0002-refresh_revealed_coins.sql"
+#include "0002-refresh_transfer_keys.sql"
+#include "0002-batch_deposits.sql"
+#include "0002-coin_deposits.sql"
+#include "0002-refunds.sql"
+#include "0002-wire_out.sql"
+#include "0002-aggregation_transient.sql"
+#include "0002-aggregation_tracking.sql"
+#include "0002-recoup.sql"
+#include "0002-recoup_refresh.sql"
+#include "0002-prewire.sql"
+#include "0002-cs_nonce_locks.sql"
+#include "0002-purse_requests.sql"
+#include "0002-purse_merges.sql"
+#include "0002-account_merges.sql"
+#include "0002-purse_decision.sql"
+#include "0002-contracts.sql"
+#include "0002-history_requests.sql"
+#include "0002-purse_deposits.sql"
+#include "0002-wads_in.sql"
+#include "0002-wad_in_entries.sql"
+#include "0002-wads_out.sql"
+#include "0002-wad_out_entries.sql"
+#include "0002-work_shards.sql"
+#include "0002-revolving_work_shards.sql"
+#include "0002-partners.sql"
+#include "0002-partner_accounts.sql"
+#include "0002-purse_actions.sql"
+#include "0002-purse_deletion.sql"
+#include "0002-kyc_attributes.sql"
+#include "0002-aml_status.sql"
+#include "0002-aml_staff.sql"
+#include "0002-aml_history.sql"
+#include "0002-age_withdraw.sql"
+
+
+COMMIT;
diff --git a/src/exchangedb/exchange-0003.sql.in b/src/exchangedb/exchange-0003.sql.in
new file mode 100644
index 000000000..c94497531
--- /dev/null
+++ b/src/exchangedb/exchange-0003.sql.in
@@ -0,0 +1,25 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+BEGIN;
+
+SELECT _v.register_patch('exchange-0003', NULL, NULL);
+SET search_path TO exchange;
+
+#include "0003-wire_accounts.sql"
+#include "0003-purse_deletion.sql"
+
+COMMIT;
diff --git a/src/exchangedb/exchange-0004.sql.in b/src/exchangedb/exchange-0004.sql.in
new file mode 100644
index 000000000..c966aedc5
--- /dev/null
+++ b/src/exchangedb/exchange-0004.sql.in
@@ -0,0 +1,24 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+BEGIN;
+
+SELECT _v.register_patch('exchange-0004', NULL, NULL);
+SET search_path TO exchange;
+
+#include "0004-refunds.sql"
+
+COMMIT;
diff --git a/src/exchangedb/exchange_do_account_merge.sql b/src/exchangedb/exchange_do_account_merge.sql
new file mode 100644
index 000000000..723154f1d
--- /dev/null
+++ b/src/exchangedb/exchange_do_account_merge.sql
@@ -0,0 +1,15 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
diff --git a/src/exchangedb/exchange_do_age_withdraw.sql b/src/exchangedb/exchange_do_age_withdraw.sql
new file mode 100644
index 000000000..89a291445
--- /dev/null
+++ b/src/exchangedb/exchange_do_age_withdraw.sql
@@ -0,0 +1,165 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+-- @author Özgür Kesim
+
+CREATE OR REPLACE FUNCTION exchange_do_age_withdraw(
+ IN amount_with_fee taler_amount,
+ IN rpub BYTEA,
+ IN rsig BYTEA,
+ IN now INT8,
+ IN min_reserve_gc INT8,
+ IN h_commitment BYTEA,
+ IN maximum_age_committed INT2, -- in years ϵ [0,1..)
+ IN noreveal_index INT2,
+ IN blinded_evs BYTEA[],
+ IN denom_serials INT8[],
+ IN denom_sigs BYTEA[],
+ OUT reserve_found BOOLEAN,
+ OUT balance_ok BOOLEAN,
+ OUT reserve_balance taler_amount,
+ OUT age_ok BOOLEAN,
+ OUT required_age INT2, -- in years ϵ [0,1..)
+ OUT reserve_birthday INT4,
+ OUT conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ reserve RECORD;
+ difference RECORD;
+ balance taler_amount;
+ not_before date;
+ earliest_date date;
+BEGIN
+-- Shards: reserves by reserve_pub (SELECT)
+-- reserves_out (INSERT, with CONFLICT detection) by wih
+-- reserves by reserve_pub (UPDATE)
+-- reserves_in by reserve_pub (SELECT)
+-- wire_targets by wire_target_h_payto
+
+SELECT current_balance
+ ,birthday
+ ,gc_date
+ INTO reserve
+ FROM exchange.reserves
+ WHERE reserves.reserve_pub=rpub;
+
+IF NOT FOUND
+THEN
+ reserve_found=FALSE;
+ age_ok = FALSE;
+ required_age=-1;
+ conflict=FALSE;
+ reserve_balance.val = 0;
+ reserve_balance.frac = 0;
+ balance_ok=FALSE;
+ RETURN;
+END IF;
+
+reserve_found = TRUE;
+conflict=FALSE; -- not really yet determined
+
+reserve_balance = reserve.current_balance;
+reserve_birthday = reserve.birthday;
+
+-- Check age requirements
+IF (reserve.birthday <> 0)
+THEN
+ not_before=date '1970-01-01' + reserve.birthday;
+ earliest_date = current_date - make_interval(maximum_age_committed);
+ --
+ -- 1970-01-01 + birthday == not_before now
+ -- | | |
+ -- <.......not allowed......>[<.....allowed range......>]
+ -- | | |
+ -- ____*_____________________*_________*________________* timeline
+ -- |
+ -- earliest_date ==
+ -- now - maximum_age_committed*year
+ --
+ IF (earliest_date < not_before)
+ THEN
+ required_age = extract(year from age(current_date, not_before));
+ age_ok = FALSE;
+ balance_ok=TRUE; -- NOT REALLY
+ RETURN;
+ END IF;
+END IF;
+
+age_ok = TRUE;
+required_age=0;
+
+-- Check reserve balance is sufficient.
+SELECT *
+INTO difference
+FROM amount_left_minus_right(reserve_balance
+ ,amount_with_fee);
+
+balance_ok = difference.ok;
+
+IF NOT balance_ok
+THEN
+ RETURN;
+END IF;
+
+balance = difference.diff;
+
+-- Calculate new expiration dates.
+min_reserve_gc=GREATEST(min_reserve_gc,reserve.gc_date);
+
+-- Update reserve balance.
+UPDATE reserves SET
+ gc_date=min_reserve_gc
+ ,current_balance=balance
+WHERE
+ reserves.reserve_pub=rpub;
+
+-- Write the commitment into the age-withdraw table
+INSERT INTO exchange.age_withdraw
+ (h_commitment
+ ,max_age
+ ,amount_with_fee
+ ,reserve_pub
+ ,reserve_sig
+ ,noreveal_index
+ ,denom_serials
+ ,h_blind_evs
+ ,denom_sigs)
+VALUES
+ (h_commitment
+ ,maximum_age_committed
+ ,amount_with_fee
+ ,rpub
+ ,rsig
+ ,noreveal_index
+ ,denom_serials
+ ,blinded_evs
+ ,denom_sigs)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Signal a conflict so that the caller
+ -- can fetch the actual data from the DB.
+ conflict=TRUE;
+ RETURN;
+ELSE
+ conflict=FALSE;
+END IF;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_age_withdraw(taler_amount, BYTEA, BYTEA, INT8, INT8, BYTEA, INT2, INT2, BYTEA[], INT8[], BYTEA[])
+ IS 'Checks whether the reserve has sufficient balance for an age-withdraw operation (or the request is repeated and was previously approved) and that age requirements are met. If so updates the database with the result. Includes storing the blinded planchets and denomination signatures, or signaling conflict';
diff --git a/src/exchangedb/exchange_do_amount_specific.sql b/src/exchangedb/exchange_do_amount_specific.sql
new file mode 100644
index 000000000..9b305a3ec
--- /dev/null
+++ b/src/exchangedb/exchange_do_amount_specific.sql
@@ -0,0 +1,92 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+--------------------------------------------------------------
+-- Taler amounts and helper functions
+-------------------------------------------------------------
+
+CREATE OR REPLACE FUNCTION amount_normalize(
+ IN amount taler_amount
+ ,OUT normalized taler_amount
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ normalized.val = amount.val + amount.frac / 100000000;
+ normalized.frac = amount.frac % 100000000;
+END $$;
+
+COMMENT ON FUNCTION amount_normalize
+ IS 'Returns the normalized amount by adding to the .val the value of (.frac / 100000000) and removing the modulus 100000000 from .frac.';
+
+CREATE OR REPLACE FUNCTION amount_add(
+ IN a taler_amount
+ ,IN b taler_amount
+ ,OUT sum taler_amount
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ sum = (a.val + b.val, a.frac + b.frac);
+ CALL amount_normalize(sum ,sum);
+
+ IF (sum.val > (1<<52))
+ THEN
+ RAISE EXCEPTION 'addition overflow';
+ END IF;
+END $$;
+
+COMMENT ON FUNCTION amount_add
+ IS 'Returns the normalized sum of two amounts. It raises an exception when the resulting .val is larger than 2^52';
+
+CREATE OR REPLACE FUNCTION amount_left_minus_right(
+ IN l taler_amount
+ ,IN r taler_amount
+ ,OUT diff taler_amount
+ ,OUT ok BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+IF (l.val > r.val)
+THEN
+ ok = TRUE;
+ IF (l.frac >= r.frac)
+ THEN
+ diff.val = l.val - r.val;
+ diff.frac = l.frac - r.frac;
+ ELSE
+ diff.val = l.val - r.val - 1;
+ diff.frac = l.frac + 100000000 - r.frac;
+ END IF;
+ELSE
+ IF (l.val = r.val) AND (l.frac >= r.frac)
+ THEN
+ diff.val = 0;
+ diff.frac = l.frac - r.frac;
+ ok = TRUE;
+ ELSE
+ diff = (-1, -1);
+ ok = FALSE;
+ END IF;
+END IF;
+
+RETURN;
+END $$;
+
+COMMENT ON FUNCTION amount_left_minus_right
+ IS 'Subtracts the right amount from the left and returns the difference and TRUE, if the left amount is larger than the right, or an invalid amount and FALSE otherwise.';
diff --git a/src/exchangedb/exchange_do_batch_coin_known.sql b/src/exchangedb/exchange_do_batch_coin_known.sql
new file mode 100644
index 000000000..db96cb08c
--- /dev/null
+++ b/src/exchangedb/exchange_do_batch_coin_known.sql
@@ -0,0 +1,469 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_batch4_known_coin(
+ IN in_coin_pub1 BYTEA,
+ IN in_denom_pub_hash1 BYTEA,
+ IN in_h_age_commitment1 BYTEA,
+ IN in_denom_sig1 BYTEA,
+ IN in_coin_pub2 BYTEA,
+ IN in_denom_pub_hash2 BYTEA,
+ IN in_h_age_commitment2 BYTEA,
+ IN in_denom_sig2 BYTEA,
+ IN in_coin_pub3 BYTEA,
+ IN in_denom_pub_hash3 BYTEA,
+ IN in_h_age_commitment3 BYTEA,
+ IN in_denom_sig3 BYTEA,
+ IN in_coin_pub4 BYTEA,
+ IN in_denom_pub_hash4 BYTEA,
+ IN in_h_age_commitment4 BYTEA,
+ IN in_denom_sig4 BYTEA,
+ OUT existed1 BOOLEAN,
+ OUT existed2 BOOLEAN,
+ OUT existed3 BOOLEAN,
+ OUT existed4 BOOLEAN,
+ OUT known_coin_id1 INT8,
+ OUT known_coin_id2 INT8,
+ OUT known_coin_id3 INT8,
+ OUT known_coin_id4 INT8,
+ OUT denom_pub_hash1 BYTEA,
+ OUT denom_pub_hash2 BYTEA,
+ OUT denom_pub_hash3 BYTEA,
+ OUT denom_pub_hash4 BYTEA,
+ OUT age_commitment_hash1 BYTEA,
+ OUT age_commitment_hash2 BYTEA,
+ OUT age_commitment_hash3 BYTEA,
+ OUT age_commitment_hash4 BYTEA)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+WITH dd AS (
+SELECT
+ denominations_serial,
+ coin
+ FROM denominations
+ WHERE denom_pub_hash
+ IN
+ (in_denom_pub_hash1,
+ in_denom_pub_hash2,
+ in_denom_pub_hash3,
+ in_denom_pub_hash4)
+ ),--dd
+ input_rows AS (
+ VALUES
+ (in_coin_pub1,
+ in_denom_pub_hash1,
+ in_h_age_commitment1,
+ in_denom_sig1),
+ (in_coin_pub2,
+ in_denom_pub_hash2,
+ in_h_age_commitment2,
+ in_denom_sig2),
+ (in_coin_pub3,
+ in_denom_pub_hash3,
+ in_h_age_commitment3,
+ in_denom_sig3),
+ (in_coin_pub4,
+ in_denom_pub_hash4,
+ in_h_age_commitment4,
+ in_denom_sig4)
+ ),--ir
+ ins AS (
+ INSERT INTO known_coins (
+ coin_pub,
+ denominations_serial,
+ age_commitment_hash,
+ denom_sig,
+ remaining
+ )
+ SELECT
+ ir.coin_pub,
+ dd.denominations_serial,
+ ir.age_commitment_hash,
+ ir.denom_sig,
+ dd.coin
+ FROM input_rows ir
+ JOIN dd
+ ON dd.denom_pub_hash = ir.denom_pub_hash
+ ON CONFLICT DO NOTHING
+ RETURNING known_coin_id
+ ),--kc
+ exists AS (
+ SELECT
+ CASE
+ WHEN
+ ins.known_coin_id IS NOT NULL
+ THEN
+ FALSE
+ ELSE
+ TRUE
+ END AS existed,
+ ins.known_coin_id,
+ dd.denom_pub_hash,
+ kc.age_commitment_hash
+ FROM input_rows ir
+ LEFT JOIN ins
+ ON ins.coin_pub = ir.coin_pub
+ LEFT JOIN known_coins kc
+ ON kc.coin_pub = ir.coin_pub
+ LEFT JOIN dd
+ ON dd.denom_pub_hash = ir.denom_pub_hash
+ )--exists
+SELECT
+ exists.existed AS existed1,
+ exists.known_coin_id AS known_coin_id1,
+ exists.denom_pub_hash AS denom_pub_hash1,
+ exists.age_commitment_hash AS age_commitment_hash1,
+ (
+ SELECT exists.existed
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ ) AS existed2,
+ (
+ SELECT exists.known_coin_id
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ ) AS known_coin_id2,
+ (
+ SELECT exists.denom_pub_hash
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ ) AS denom_pub_hash2,
+ (
+ SELECT exists.age_commitment_hash
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ )AS age_commitment_hash2,
+ (
+ SELECT exists.existed
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash3
+ ) AS existed3,
+ (
+ SELECT exists.known_coin_id
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash3
+ ) AS known_coin_id3,
+ (
+ SELECT exists.denom_pub_hash
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash3
+ ) AS denom_pub_hash3,
+ (
+ SELECT exists.age_commitment_hash
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash3
+ )AS age_commitment_hash3,
+ (
+ SELECT exists.existed
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash4
+ ) AS existed4,
+ (
+ SELECT exists.known_coin_id
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash4
+ ) AS known_coin_id4,
+ (
+ SELECT exists.denom_pub_hash
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash4
+ ) AS denom_pub_hash4,
+ (
+ SELECT exists.age_commitment_hash
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash4
+ )AS age_commitment_hash4
+FROM exists;
+
+RETURN;
+END $$;
+
+
+CREATE OR REPLACE FUNCTION exchange_do_batch2_known_coin(
+ IN in_coin_pub1 BYTEA,
+ IN in_denom_pub_hash1 BYTEA,
+ IN in_h_age_commitment1 BYTEA,
+ IN in_denom_sig1 BYTEA,
+ IN in_coin_pub2 BYTEA,
+ IN in_denom_pub_hash2 BYTEA,
+ IN in_h_age_commitment2 BYTEA,
+ IN in_denom_sig2 BYTEA,
+ OUT existed1 BOOLEAN,
+ OUT existed2 BOOLEAN,
+ OUT known_coin_id1 INT8,
+ OUT known_coin_id2 INT8,
+ OUT denom_pub_hash1 BYTEA,
+ OUT denom_pub_hash2 BYTEA,
+ OUT age_commitment_hash1 BYTEA,
+ OUT age_commitment_hash2 BYTEA)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+WITH dd AS (
+SELECT
+ denominations_serial,
+ coin
+ FROM denominations
+ WHERE denom_pub_hash
+ IN
+ (in_denom_pub_hash1,
+ in_denom_pub_hash2)
+ ),--dd
+ input_rows AS (
+ VALUES
+ (in_coin_pub1,
+ in_denom_pub_hash1,
+ in_h_age_commitment1,
+ in_denom_sig1),
+ (in_coin_pub2,
+ in_denom_pub_hash2,
+ in_h_age_commitment2,
+ in_denom_sig2)
+ ),--ir
+ ins AS (
+ INSERT INTO known_coins (
+ coin_pub,
+ denominations_serial,
+ age_commitment_hash,
+ denom_sig,
+ remaining
+ )
+ SELECT
+ ir.coin_pub,
+ dd.denominations_serial,
+ ir.age_commitment_hash,
+ ir.denom_sig,
+ dd.coin
+ FROM input_rows ir
+ JOIN dd
+ ON dd.denom_pub_hash = ir.denom_pub_hash
+ ON CONFLICT DO NOTHING
+ RETURNING known_coin_id
+ ),--kc
+ exists AS (
+ SELECT
+ CASE
+ WHEN ins.known_coin_id IS NOT NULL
+ THEN
+ FALSE
+ ELSE
+ TRUE
+ END AS existed,
+ ins.known_coin_id,
+ dd.denom_pub_hash,
+ kc.age_commitment_hash
+ FROM input_rows ir
+ LEFT JOIN ins
+ ON ins.coin_pub = ir.coin_pub
+ LEFT JOIN known_coins kc
+ ON kc.coin_pub = ir.coin_pub
+ LEFT JOIN dd
+ ON dd.denom_pub_hash = ir.denom_pub_hash
+ )--exists
+SELECT
+ exists.existed AS existed1,
+ exists.known_coin_id AS known_coin_id1,
+ exists.denom_pub_hash AS denom_pub_hash1,
+ exists.age_commitment_hash AS age_commitment_hash1,
+ (
+ SELECT exists.existed
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ ) AS existed2,
+ (
+ SELECT exists.known_coin_id
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ ) AS known_coin_id2,
+ (
+ SELECT exists.denom_pub_hash
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ ) AS denom_pub_hash2,
+ (
+ SELECT exists.age_commitment_hash
+ FROM exists
+ WHERE exists.denom_pub_hash = in_denom_pub_hash2
+ )AS age_commitment_hash2
+FROM exists;
+
+RETURN;
+END $$;
+
+
+CREATE OR REPLACE FUNCTION exchange_do_batch1_known_coin(
+ IN in_coin_pub1 BYTEA,
+ IN in_denom_pub_hash1 BYTEA,
+ IN in_h_age_commitment1 BYTEA,
+ IN in_denom_sig1 BYTEA,
+ OUT existed1 BOOLEAN,
+ OUT known_coin_id1 INT8,
+ OUT denom_pub_hash1 BYTEA,
+ OUT age_commitment_hash1 BYTEA)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+WITH dd AS (
+SELECT
+ denominations_serial,
+ coin
+ FROM denominations
+ WHERE denom_pub_hash
+ IN
+ (in_denom_pub_hash1,
+ in_denom_pub_hash2)
+ ),--dd
+ input_rows AS (
+ VALUES
+ (in_coin_pub1,
+ in_denom_pub_hash1,
+ in_h_age_commitment1,
+ in_denom_sig1)
+ ),--ir
+ ins AS (
+ INSERT INTO known_coins (
+ coin_pub,
+ denominations_serial,
+ age_commitment_hash,
+ denom_sig,
+ remaining
+ )
+ SELECT
+ ir.coin_pub,
+ dd.denominations_serial,
+ ir.age_commitment_hash,
+ ir.denom_sig,
+ dd.coin
+ FROM input_rows ir
+ JOIN dd
+ ON dd.denom_pub_hash = ir.denom_pub_hash
+ ON CONFLICT DO NOTHING
+ RETURNING known_coin_id
+ ),--kc
+ exists AS (
+ SELECT
+ CASE
+ WHEN ins.known_coin_id IS NOT NULL
+ THEN
+ FALSE
+ ELSE
+ TRUE
+ END AS existed,
+ ins.known_coin_id,
+ dd.denom_pub_hash,
+ kc.age_commitment_hash
+ FROM input_rows ir
+ LEFT JOIN ins
+ ON ins.coin_pub = ir.coin_pub
+ LEFT JOIN known_coins kc
+ ON kc.coin_pub = ir.coin_pub
+ LEFT JOIN dd
+ ON dd.denom_pub_hash = ir.denom_pub_hash
+ )--exists
+SELECT
+ exists.existed AS existed1,
+ exists.known_coin_id AS known_coin_id1,
+ exists.denom_pub_hash AS denom_pub_hash1,
+ exists.age_commitment_hash AS age_commitment_hash1
+FROM exists;
+
+RETURN;
+END $$;
+
+/*** Experiment using a loop ***/
+/*
+CREATE OR REPLACE FUNCTION exchange_do_batch2_known_coin(
+ IN in_coin_pub1 BYTEA,
+ IN in_denom_pub_hash1 TEXT,
+ IN in_h_age_commitment1 TEXT,
+ IN in_denom_sig1 TEXT,
+ IN in_coin_pub2 BYTEA,
+ IN in_denom_pub_hash2 TEXT,
+ IN in_h_age_commitment2 TEXT,
+ IN in_denom_sig2 TEXT,
+ OUT existed1 BOOLEAN,
+ OUT existed2 BOOLEAN,
+ OUT known_coin_id1 INT8,
+ OUT known_coin_id2 INT8,
+ OUT denom_pub_hash1 TEXT,
+ OUT denom_pub_hash2 TEXT,
+ OUT age_commitment_hash1 TEXT,
+ OUT age_commitment_hash2 TEXT)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ ins_values RECORD;
+BEGIN
+ FOR i IN 1..2 LOOP
+ ins_values := (
+ SELECT
+ in_coin_pub1 AS coin_pub,
+ in_denom_pub_hash1 AS denom_pub_hash,
+ in_h_age_commitment1 AS age_commitment_hash,
+ in_denom_sig1 AS denom_sig
+ WHERE i = 1
+ UNION
+ SELECT
+ in_coin_pub2 AS coin_pub,
+ in_denom_pub_hash2 AS denom_pub_hash,
+ in_h_age_commitment2 AS age_commitment_hash,
+ in_denom_sig2 AS denom_sig
+ WHERE i = 2
+ );
+ WITH dd (denominations_serial, coin) AS (
+ SELECT denominations_serial, coin
+ FROM denominations
+ WHERE denom_pub_hash = ins_values.denom_pub_hash
+ ),
+ input_rows(coin_pub) AS (
+ VALUES (ins_values.coin_pub)
+ ),
+ ins AS (
+ INSERT INTO known_coins (
+ coin_pub,
+ denominations_serial,
+ age_commitment_hash,
+ denom_sig,
+ remaining
+ ) SELECT
+ input_rows.coin_pub,
+ dd.denominations_serial,
+ ins_values.age_commitment_hash,
+ ins_values.denom_sig,
+ coin
+ FROM dd
+ CROSS JOIN input_rows
+ ON CONFLICT DO NOTHING
+ RETURNING known_coin_id, denom_pub_hash
+ )
+ SELECT
+ CASE i
+ WHEN 1 THEN
+ COALESCE(ins.known_coin_id, 0) <> 0 AS existed1,
+ ins.known_coin_id AS known_coin_id1,
+ ins.denom_pub_hash AS denom_pub_hash1,
+ ins.age_commitment_hash AS age_commitment_hash1
+ WHEN 2 THEN
+ COALESCE(ins.known_coin_id, 0) <> 0 AS existed2,
+ ins.known_coin_id AS known_coin_id2,
+ ins.denom_pub_hash AS denom_pub_hash2,
+ ins.age_commitment_hash AS age_commitment_hash2
+ END
+ FROM ins;
+ END LOOP;
+END;
+$$;*/
diff --git a/src/exchangedb/exchange_do_batch_reserves_update.sql b/src/exchangedb/exchange_do_batch_reserves_update.sql
new file mode 100644
index 000000000..ebb58a149
--- /dev/null
+++ b/src/exchangedb/exchange_do_batch_reserves_update.sql
@@ -0,0 +1,72 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_batch_reserves_update(
+ IN in_reserve_pub BYTEA,
+ IN in_expiration_date INT8,
+ IN in_wire_ref INT8,
+ IN in_credit taler_amount,
+ IN in_exchange_account_name TEXT,
+ IN in_wire_source_h_payto BYTEA,
+ IN in_notify text,
+ OUT out_duplicate BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ INSERT INTO reserves_in
+ (reserve_pub
+ ,wire_reference
+ ,credit
+ ,exchange_account_section
+ ,wire_source_h_payto
+ ,execution_date)
+ VALUES
+ (in_reserve_pub
+ ,in_wire_ref
+ ,in_credit
+ ,in_exchange_account_name
+ ,in_wire_source_h_payto
+ ,in_expiration_date)
+ ON CONFLICT DO NOTHING;
+ IF FOUND
+ THEN
+ --IF THE INSERTION WAS A SUCCESS IT MEANS NO DUPLICATED TRANSACTION
+ out_duplicate = FALSE;
+ UPDATE reserves rs
+ SET
+ current_balance.frac = (rs.current_balance).frac+in_credit.frac
+ - CASE
+ WHEN (rs.current_balance).frac + in_credit.frac >= 100000000
+ THEN 100000000
+ ELSE 1
+ END
+ ,current_balance.val = (rs.current_balance).val+in_credit.val
+ + CASE
+ WHEN (rs.current_balance).frac + in_credit.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ ,expiration_date=GREATEST(expiration_date,in_expiration_date)
+ ,gc_date=GREATEST(gc_date,in_expiration_date)
+ WHERE reserve_pub=in_reserve_pub;
+ EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,in_notify);
+ ELSE
+ out_duplicate = TRUE;
+ END IF;
+ RETURN;
+END $$;
diff --git a/src/exchangedb/exchange_do_batch_withdraw.sql b/src/exchangedb/exchange_do_batch_withdraw.sql
new file mode 100644
index 000000000..a48561a9a
--- /dev/null
+++ b/src/exchangedb/exchange_do_batch_withdraw.sql
@@ -0,0 +1,126 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+-- @author Christian Grothoff
+-- @author Özgür Kesim
+
+CREATE OR REPLACE FUNCTION exchange_do_batch_withdraw(
+ IN amount taler_amount,
+ IN rpub BYTEA,
+ IN now INT8,
+ IN min_reserve_gc INT8,
+ IN do_age_check BOOLEAN,
+ OUT reserve_found BOOLEAN,
+ OUT balance_ok BOOLEAN,
+ OUT reserve_balance taler_amount,
+ OUT age_ok BOOLEAN,
+ OUT allowed_maximum_age INT2, -- in years
+ OUT ruuid INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ reserve RECORD;
+ balance taler_amount;
+ not_before date;
+BEGIN
+-- Shards: reserves by reserve_pub (SELECT)
+-- reserves_out (INSERT, with CONFLICT detection) by wih
+-- reserves by reserve_pub (UPDATE)
+-- reserves_in by reserve_pub (SELECT)
+-- wire_targets by wire_target_h_payto
+
+SELECT current_balance
+ ,reserve_uuid
+ ,birthday
+ ,gc_date
+ INTO reserve
+ FROM exchange.reserves
+ WHERE reserves.reserve_pub=rpub;
+
+IF NOT FOUND
+THEN
+ -- reserve unknown
+ reserve_found=FALSE;
+ balance_ok=FALSE;
+ reserve_balance.frac = 0;
+ reserve_balance.val = 0;
+ age_ok=FALSE;
+ allowed_maximum_age=0;
+ ruuid=2;
+ RETURN;
+END IF;
+reserve_found=TRUE;
+reserve_balance = reserve.current_balance;
+ruuid = reserve.reserve_uuid;
+
+-- Check if age requirements are present
+IF ((NOT do_age_check) OR (reserve.birthday = 0))
+THEN
+ age_ok = TRUE;
+ allowed_maximum_age = -1;
+ELSE
+ -- Age requirements are formally not met: The exchange is setup to support
+ -- age restrictions (do_age_check == TRUE) and the reserve has a
+ -- birthday set (reserve_birthday != 0), but the client called the
+ -- batch-withdraw endpoint instead of the age-withdraw endpoint, which it
+ -- should have.
+ not_before=date '1970-01-01' + reserve.birthday;
+ allowed_maximum_age = extract(year from age(current_date, not_before));
+
+ balance_ok=FALSE;
+ age_ok = FALSE;
+ RETURN;
+END IF;
+
+
+-- Check reserve balance is sufficient.
+IF (reserve_balance.val > amount.val)
+THEN
+ IF (reserve_balance.frac >= amount.frac)
+ THEN
+ balance.val=reserve_balance.val - amount.val;
+ balance.frac=reserve_balance.frac - amount.frac;
+ ELSE
+ balance.val=reserve_balance.val - amount.val - 1;
+ balance.frac=reserve_balance.frac + 100000000 - amount.frac;
+ END IF;
+ELSE
+ IF (reserve_balance.val = amount.val) AND (reserve_balance.frac >= amount.frac)
+ THEN
+ balance.val=0;
+ balance.frac=reserve_balance.frac - amount.frac;
+ ELSE
+ balance_ok=FALSE;
+ RETURN;
+ END IF;
+END IF;
+
+-- Calculate new expiration dates.
+min_reserve_gc=GREATEST(min_reserve_gc,reserve.gc_date);
+
+-- Update reserve balance.
+UPDATE reserves SET
+ gc_date=min_reserve_gc
+ ,current_balance=balance
+WHERE
+ reserves.reserve_pub=rpub;
+
+balance_ok=TRUE;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_batch_withdraw(taler_amount, BYTEA, INT8, INT8, BOOLEAN)
+ IS 'Checks whether the reserve has sufficient balance for a withdraw operation (or the request is repeated and was previously approved) and that age requirements are formally met. If so updates the database with the result. Excludes storing the planchets.';
+
diff --git a/src/exchangedb/exchange_do_batch_withdraw_insert.sql b/src/exchangedb/exchange_do_batch_withdraw_insert.sql
new file mode 100644
index 000000000..d36181a6b
--- /dev/null
+++ b/src/exchangedb/exchange_do_batch_withdraw_insert.sql
@@ -0,0 +1,120 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_batch_withdraw_insert(
+ IN cs_nonce BYTEA,
+ IN amount taler_amount,
+ IN h_denom_pub BYTEA, -- FIXME: denom_serials should really be a parameter to this FUNCTION.
+ IN ruuid INT8,
+ IN reserve_sig BYTEA,
+ IN h_coin_envelope BYTEA,
+ IN denom_sig BYTEA,
+ IN now INT8,
+ OUT out_denom_unknown BOOLEAN,
+ OUT out_nonce_reuse BOOLEAN,
+ OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ denom_serial INT8;
+BEGIN
+-- Shards: reserves by reserve_pub (SELECT)
+-- reserves_out (INSERT, with CONFLICT detection) by wih
+-- reserves by reserve_pub (UPDATE)
+-- reserves_in by reserve_pub (SELECT)
+-- wire_targets by wire_target_h_payto
+
+out_denom_unknown=TRUE;
+out_conflict=TRUE;
+out_nonce_reuse=TRUE;
+
+-- FIXME: denom_serials should really be a parameter to this FUNCTION.
+
+SELECT denominations_serial
+ INTO denom_serial
+ FROM exchange.denominations
+ WHERE denom_pub_hash=h_denom_pub;
+
+IF NOT FOUND
+THEN
+ -- denomination unknown, should be impossible!
+ out_denom_unknown=TRUE;
+ ASSERT false, 'denomination unknown';
+ RETURN;
+END IF;
+out_denom_unknown=FALSE;
+
+INSERT INTO exchange.reserves_out
+ (h_blind_ev
+ ,denominations_serial
+ ,denom_sig
+ ,reserve_uuid
+ ,reserve_sig
+ ,execution_date
+ ,amount_with_fee)
+VALUES
+ (h_coin_envelope
+ ,denom_serial
+ ,denom_sig
+ ,ruuid
+ ,reserve_sig
+ ,now
+ ,amount)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ out_conflict=TRUE;
+ RETURN;
+END IF;
+out_conflict=FALSE;
+
+-- Special actions needed for a CS withdraw?
+out_nonce_reuse=FALSE;
+IF NOT NULL cs_nonce
+THEN
+ -- Cache CS signature to prevent replays in the future
+ -- (and check if cached signature exists at the same time).
+ INSERT INTO exchange.cs_nonce_locks
+ (nonce
+ ,max_denomination_serial
+ ,op_hash)
+ VALUES
+ (cs_nonce
+ ,denom_serial
+ ,h_coin_envelope)
+ ON CONFLICT DO NOTHING;
+
+ IF NOT FOUND
+ THEN
+ -- See if the existing entry is identical.
+ SELECT 1
+ FROM exchange.cs_nonce_locks
+ WHERE nonce=cs_nonce
+ AND op_hash=h_coin_envelope;
+ IF NOT FOUND
+ THEN
+ out_nonce_reuse=TRUE;
+ ASSERT false, 'nonce reuse attempted by client';
+ RETURN;
+ END IF;
+ END IF;
+END IF;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_batch_withdraw_insert(BYTEA, taler_amount, BYTEA, INT8, BYTEA, BYTEA, BYTEA, INT8)
+ IS 'Stores information about a planchet for a batch withdraw operation. Checks if the planchet already exists, and in that case indicates a conflict';
diff --git a/src/exchangedb/exchange_do_deposit.sql b/src/exchangedb/exchange_do_deposit.sql
new file mode 100644
index 000000000..c89e9e470
--- /dev/null
+++ b/src/exchangedb/exchange_do_deposit.sql
@@ -0,0 +1,206 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+CREATE OR REPLACE FUNCTION exchange_do_deposit(
+ -- For batch_deposits
+ IN in_shard INT8,
+ IN in_merchant_pub BYTEA,
+ IN in_wallet_timestamp INT8,
+ IN in_exchange_timestamp INT8,
+ IN in_refund_deadline INT8,
+ IN in_wire_deadline INT8,
+ IN in_h_contract_terms BYTEA,
+ IN in_wallet_data_hash BYTEA, -- can be NULL
+ IN in_wire_salt BYTEA,
+ IN in_wire_target_h_payto BYTEA,
+ IN in_policy_details_serial_id INT8, -- can be NULL
+ IN in_policy_blocked BOOLEAN,
+ -- For wire_targets
+ IN in_receiver_wire_account TEXT,
+ -- For coin_deposits
+ IN ina_coin_pub BYTEA[],
+ IN ina_coin_sig BYTEA[],
+ IN ina_amount_with_fee taler_amount[],
+ OUT out_exchange_timestamp INT8,
+ OUT out_insufficient_balance_coin_index INT4, -- index of coin with bad balance, NULL if none
+ OUT out_conflict BOOL
+ )
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ wtsi INT8; -- wire target serial id
+ bdsi INT8; -- batch_deposits serial id
+ i INT4;
+ ini_amount_with_fee taler_amount;
+ ini_coin_pub BYTEA;
+ ini_coin_sig BYTEA;
+BEGIN
+-- Shards:
+-- INSERT wire_targets (by h_payto), ON CONFLICT DO NOTHING;
+-- INSERT batch_deposits (by shard, merchant_pub), ON CONFLICT idempotency check;
+-- INSERT[] coin_deposits (by coin_pub), ON CONFLICT idempotency check;
+-- UPDATE[] known_coins (by coin_pub)
+
+
+-- First, get or create the 'wtsi'
+INSERT INTO wire_targets
+ (wire_target_h_payto
+ ,payto_uri)
+ VALUES
+ (in_wire_target_h_payto
+ ,in_receiver_wire_account)
+ ON CONFLICT DO NOTHING -- for CONFLICT ON (wire_target_h_payto)
+ RETURNING
+ wire_target_serial_id
+ INTO
+ wtsi;
+
+IF NOT FOUND
+THEN
+ SELECT
+ wire_target_serial_id
+ INTO
+ wtsi
+ FROM wire_targets
+ WHERE
+ wire_target_h_payto=in_wire_target_h_payto;
+END IF;
+
+
+-- Second, create the batch_deposits entry
+INSERT INTO batch_deposits
+ (shard
+ ,merchant_pub
+ ,wallet_timestamp
+ ,exchange_timestamp
+ ,refund_deadline
+ ,wire_deadline
+ ,h_contract_terms
+ ,wallet_data_hash
+ ,wire_salt
+ ,wire_target_h_payto
+ ,policy_details_serial_id
+ ,policy_blocked
+ )
+ VALUES
+ (in_shard
+ ,in_merchant_pub
+ ,in_wallet_timestamp
+ ,in_exchange_timestamp
+ ,in_refund_deadline
+ ,in_wire_deadline
+ ,in_h_contract_terms
+ ,in_wallet_data_hash
+ ,in_wire_salt
+ ,in_wire_target_h_payto
+ ,in_policy_details_serial_id
+ ,in_policy_blocked)
+ ON CONFLICT DO NOTHING -- for CONFLICT ON (merchant_pub, h_contract_terms)
+ RETURNING
+ batch_deposit_serial_id
+ INTO
+ bdsi;
+
+IF NOT FOUND
+THEN
+ -- Idempotency check: see if an identical record exists.
+ -- We do select over merchant_pub, h_contract_terms and wire_target_h_payto
+ -- first to maximally increase the chance of using the existing index.
+ SELECT
+ exchange_timestamp
+ ,batch_deposit_serial_id
+ INTO
+ out_exchange_timestamp
+ ,bdsi
+ FROM batch_deposits
+ WHERE shard=in_shard
+ AND merchant_pub=in_merchant_pub
+ AND h_contract_terms=in_h_contract_terms
+ AND wire_target_h_payto=in_wire_target_h_payto
+ -- now check the rest, too
+ AND ( (wallet_data_hash=in_wallet_data_hash) OR
+ (wallet_data_hash IS NULL AND in_wallet_data_hash IS NULL) )
+ AND wire_salt=in_wire_salt
+ AND wallet_timestamp=in_wallet_timestamp
+ AND refund_deadline=in_refund_deadline
+ AND wire_deadline=in_wire_deadline
+ AND ( (policy_details_serial_id=in_policy_details_serial_id) OR
+ (policy_details_serial_id IS NULL AND in_policy_details_serial_id IS NULL) );
+ IF NOT FOUND
+ THEN
+ -- Deposit exists, but with *strange* differences. Not allowed.
+ out_conflict=TRUE;
+ RETURN;
+ END IF;
+END IF;
+
+out_conflict=FALSE;
+
+-- Deposit each coin
+
+FOR i IN 1..array_length(ina_coin_pub,1)
+LOOP
+ ini_coin_pub = ina_coin_pub[i];
+ ini_coin_sig = ina_coin_sig[i];
+ ini_amount_with_fee = ina_amount_with_fee[i];
+
+ INSERT INTO coin_deposits
+ (batch_deposit_serial_id
+ ,coin_pub
+ ,coin_sig
+ ,amount_with_fee
+ )
+ VALUES
+ (bdsi
+ ,ini_coin_pub
+ ,ini_coin_sig
+ ,ini_amount_with_fee
+ )
+ ON CONFLICT DO NOTHING;
+
+ IF FOUND
+ THEN
+ -- Insert did happen, update balance in known_coins!
+
+ UPDATE known_coins kc
+ SET
+ remaining.frac=(kc.remaining).frac-ini_amount_with_fee.frac
+ + CASE
+ WHEN (kc.remaining).frac < ini_amount_with_fee.frac
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val-ini_amount_with_fee.val
+ - CASE
+ WHEN (kc.remaining).frac < ini_amount_with_fee.frac
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub=ini_coin_pub
+ AND ( ((kc.remaining).val > ini_amount_with_fee.val) OR
+ ( ((kc.remaining).frac >= ini_amount_with_fee.frac) AND
+ ((kc.remaining).val >= ini_amount_with_fee.val) ) );
+
+ IF NOT FOUND
+ THEN
+ -- Insufficient balance.
+ -- Note: C arrays are 0 indexed, but i started at 1
+ out_insufficient_balance_coin_index=i-1;
+ RETURN;
+ END IF;
+ END IF;
+END LOOP; -- end FOR all coins
+
+END $$;
diff --git a/src/exchangedb/exchange_do_expire_purse.sql b/src/exchangedb/exchange_do_expire_purse.sql
new file mode 100644
index 000000000..ee9757f03
--- /dev/null
+++ b/src/exchangedb/exchange_do_expire_purse.sql
@@ -0,0 +1,98 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_expire_purse(
+ IN in_start_time INT8,
+ IN in_end_time INT8,
+ IN in_now INT8,
+ OUT out_found BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_purse_pub BYTEA;
+DECLARE
+ my_deposit record;
+DECLARE
+ my_in_reserve_quota BOOLEAN;
+BEGIN
+
+-- FIXME: we should probably do this in a loop
+-- and expire all at once, instead of one per query
+SELECT purse_pub
+ ,in_reserve_quota
+ INTO my_purse_pub
+ ,my_in_reserve_quota
+ FROM purse_requests
+ WHERE (purse_expiration >= in_start_time) AND
+ (purse_expiration < in_end_time) AND
+ NOT was_decided
+ ORDER BY purse_expiration ASC
+ LIMIT 1;
+out_found = FOUND;
+IF NOT FOUND
+THEN
+ RETURN;
+END IF;
+
+INSERT INTO purse_decision
+ (purse_pub
+ ,action_timestamp
+ ,refunded)
+VALUES
+ (my_purse_pub
+ ,in_now
+ ,TRUE);
+
+-- Code for 'TALER_DBEVENT_EXCHANGE_PURSE_REFUNDED'
+NOTIFY X8DJSPNYJMNZDAP7GN6YQ4EZVSQXMF3HRP4VAR347WP9SZYP1C200;
+
+IF (my_in_reserve_quota)
+THEN
+ UPDATE reserves
+ SET purses_active=purses_active-1
+ WHERE reserve_pub IN
+ (SELECT reserve_pub
+ FROM exchange.purse_merges
+ WHERE purse_pub=my_purse_pub
+ LIMIT 1);
+END IF;
+
+-- restore balance to each coin deposited into the purse
+FOR my_deposit IN
+ SELECT coin_pub
+ ,amount_with_fee
+ FROM purse_deposits
+ WHERE purse_pub = my_purse_pub
+LOOP
+ UPDATE known_coins kc SET
+ remaining.frac=(kc.remaining).frac+(my_deposit.amount_with_fee).frac
+ - CASE
+ WHEN (kc.remaining).frac+(my_deposit.amount_with_fee).frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val+(my_deposit.amount_with_fee).val
+ + CASE
+ WHEN (kc.remaining).frac+(my_deposit.amount_with_fee).frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub = my_deposit.coin_pub;
+ END LOOP;
+END $$;
+
+COMMENT ON FUNCTION exchange_do_expire_purse(INT8,INT8,INT8)
+ IS 'Finds an expired purse in the given time range and refunds the coins (if any).';
diff --git a/src/exchangedb/exchange_do_gc.sql b/src/exchangedb/exchange_do_gc.sql
new file mode 100644
index 000000000..d4ecb3024
--- /dev/null
+++ b/src/exchangedb/exchange_do_gc.sql
@@ -0,0 +1,140 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE PROCEDURE exchange_do_gc(
+ IN in_ancient_date INT8,
+ IN in_now INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ reserve_uuid_min INT8; -- minimum reserve UUID still alive
+ melt_min INT8; -- minimum melt still alive
+ coin_min INT8; -- minimum known_coin still alive
+ batch_deposit_min INT8; -- minimum deposit still alive
+ reserve_out_min INT8; -- minimum reserve_out still alive
+ denom_min INT8; -- minimum denomination still alive
+BEGIN
+
+DELETE FROM prewire
+ WHERE finished=TRUE;
+
+DELETE FROM wire_fee
+ WHERE end_date < in_ancient_date;
+
+-- FIXME: use closing fee as threshold?
+DELETE FROM reserves
+ WHERE gc_date < in_now
+ AND current_balance = (0, 0);
+
+SELECT
+ reserve_out_serial_id
+ INTO
+ reserve_out_min
+ FROM reserves_out
+ ORDER BY reserve_out_serial_id ASC
+ LIMIT 1;
+
+DELETE FROM recoup
+ WHERE reserve_out_serial_id < reserve_out_min;
+
+SELECT
+ reserve_uuid
+ INTO
+ reserve_uuid_min
+ FROM reserves
+ ORDER BY reserve_uuid ASC
+ LIMIT 1;
+
+DELETE FROM reserves_out
+ WHERE reserve_uuid < reserve_uuid_min;
+
+-- FIXME: this query will be horribly slow;
+-- need to find another way to formulate it...
+DELETE FROM denominations
+ WHERE expire_legal < in_now
+ AND denominations_serial NOT IN
+ (SELECT DISTINCT denominations_serial
+ FROM reserves_out)
+ AND denominations_serial NOT IN
+ (SELECT DISTINCT denominations_serial
+ FROM known_coins
+ WHERE coin_pub IN
+ (SELECT DISTINCT coin_pub
+ FROM recoup))
+ AND denominations_serial NOT IN
+ (SELECT DISTINCT denominations_serial
+ FROM known_coins
+ WHERE coin_pub IN
+ (SELECT DISTINCT coin_pub
+ FROM recoup_refresh));
+
+SELECT
+ melt_serial_id
+ INTO
+ melt_min
+ FROM refresh_commitments
+ ORDER BY melt_serial_id ASC
+ LIMIT 1;
+
+DELETE FROM refresh_revealed_coins
+ WHERE melt_serial_id < melt_min;
+
+DELETE FROM refresh_transfer_keys
+ WHERE melt_serial_id < melt_min;
+
+SELECT
+ known_coin_id
+ INTO
+ coin_min
+ FROM known_coins
+ ORDER BY known_coin_id ASC
+ LIMIT 1;
+
+DELETE FROM recoup_refresh
+ WHERE known_coin_id < coin_min;
+
+DELETE FROM batch_deposits
+ WHERE wire_deadline < in_ancient_date;
+
+SELECT
+ batch_deposit_serial_id
+ INTO
+ batch_deposit_min
+ FROM coin_deposits
+ ORDER BY batch_deposit_serial_id ASC
+ LIMIT 1;
+
+DELETE FROM refunds
+ WHERE batch_deposit_serial_id < batch_deposit_min;
+DELETE FROM aggregation_tracking
+ WHERE batch_deposit_serial_id < batch_deposit_min;
+DELETE FROM coin_deposits
+ WHERE batch_deposit_serial_id < batch_deposit_min;
+
+
+
+SELECT
+ denominations_serial
+ INTO
+ denom_min
+ FROM denominations
+ ORDER BY denominations_serial ASC
+ LIMIT 1;
+
+DELETE FROM cs_nonce_locks
+ WHERE max_denomination_serial <= denom_min;
+
+END $$;
diff --git a/src/exchangedb/exchange_do_get_link_data.sql b/src/exchangedb/exchange_do_get_link_data.sql
new file mode 100644
index 000000000..08611a5ea
--- /dev/null
+++ b/src/exchangedb/exchange_do_get_link_data.sql
@@ -0,0 +1,59 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_get_link_data(
+ IN in_coin_pub BYTEA
+)
+RETURNS SETOF record
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ curs CURSOR
+ FOR
+ SELECT
+ melt_serial_id
+ FROM refresh_commitments
+ WHERE old_coin_pub=in_coin_pub;
+
+DECLARE
+ i RECORD;
+BEGIN
+OPEN curs;
+LOOP
+ FETCH NEXT FROM curs INTO i;
+ EXIT WHEN NOT FOUND;
+ RETURN QUERY
+ SELECT
+ tp.transfer_pub
+ ,denoms.denom_pub
+ ,rrc.ev_sig
+ ,rrc.ewv
+ ,rrc.link_sig
+ ,rrc.freshcoin_index
+ ,rrc.coin_ev
+ FROM refresh_revealed_coins rrc
+ JOIN refresh_transfer_keys tp
+ ON (tp.melt_serial_id=rrc.melt_serial_id)
+ JOIN denominations denoms
+ ON (rrc.denominations_serial=denoms.denominations_serial)
+ WHERE rrc.melt_serial_id =i.melt_serial_id
+/* GROUP BY tp.transfer_pub, denoms.denom_pub, rrc.ev_sig,rrc.ewv,rrc.link_sig,rrc.freshcoin_index, rrc.coin_ev*/
+ ORDER BY tp.transfer_pub,
+ rrc.freshcoin_index ASC
+ ;
+END LOOP;
+CLOSE curs;
+END $$;
diff --git a/src/exchangedb/exchange_do_insert_aml_decision.sql b/src/exchangedb/exchange_do_insert_aml_decision.sql
new file mode 100644
index 000000000..c8ed7e928
--- /dev/null
+++ b/src/exchangedb/exchange_do_insert_aml_decision.sql
@@ -0,0 +1,127 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_insert_aml_decision(
+ IN in_h_payto BYTEA,
+ IN in_new_threshold taler_amount,
+ IN in_new_status INT4,
+ IN in_decision_time INT8,
+ IN in_justification TEXT,
+ IN in_decider_pub BYTEA,
+ IN in_decider_sig BYTEA,
+ IN in_notify_s TEXT,
+ IN in_kyc_requirements TEXT,
+ IN in_requirement_row INT8,
+ OUT out_invalid_officer BOOLEAN,
+ OUT out_last_date INT8)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+-- Check officer is eligible to make decisions.
+PERFORM
+ FROM aml_staff
+ WHERE decider_pub=in_decider_pub
+ AND is_active
+ AND NOT read_only;
+IF NOT FOUND
+THEN
+ out_invalid_officer=TRUE;
+ out_last_date=0;
+ RETURN;
+END IF;
+out_invalid_officer=FALSE;
+
+-- Check no more recent decision exists.
+SELECT decision_time
+ INTO out_last_date
+ FROM aml_history
+ WHERE h_payto=in_h_payto
+ ORDER BY decision_time DESC;
+IF FOUND
+THEN
+ IF out_last_date >= in_decision_time
+ THEN
+ -- Refuse to insert older decision.
+ RETURN;
+ END IF;
+ UPDATE aml_status
+ SET threshold=in_new_threshold
+ ,status=in_new_status
+ ,kyc_requirement=in_requirement_row
+ WHERE h_payto=in_h_payto;
+ ASSERT FOUND, 'cannot have AML decision history but no AML status';
+ELSE
+ out_last_date = 0;
+ INSERT INTO aml_status
+ (h_payto
+ ,threshold
+ ,status
+ ,kyc_requirement)
+ VALUES
+ (in_h_payto
+ ,in_new_threshold
+ ,in_new_status
+ ,in_requirement_row)
+ ON CONFLICT (h_payto) DO
+ UPDATE SET
+ threshold=in_new_threshold
+ ,status=in_new_status;
+END IF;
+
+
+INSERT INTO aml_history
+ (h_payto
+ ,new_threshold
+ ,new_status
+ ,decision_time
+ ,justification
+ ,kyc_requirements
+ ,kyc_req_row
+ ,decider_pub
+ ,decider_sig
+ ) VALUES
+ (in_h_payto
+ ,in_new_threshold
+ ,in_new_status
+ ,in_decision_time
+ ,in_justification
+ ,in_kyc_requirements
+ ,in_requirement_row
+ ,in_decider_pub
+ ,in_decider_sig);
+
+
+-- wake up taler-exchange-aggregator
+IF 0 = in_new_status
+THEN
+ INSERT INTO kyc_alerts
+ (h_payto
+ ,trigger_type)
+ VALUES
+ (in_h_payto,1);
+
+ EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,in_notify_s);
+
+END IF;
+
+
+END $$;
+
+
+COMMENT ON FUNCTION exchange_do_insert_aml_decision(BYTEA, taler_amount, INT4, INT8, TEXT, BYTEA, BYTEA, TEXT, TEXT, INT8)
+ IS 'Checks whether the AML officer is eligible to make AML decisions and if so inserts the decision into the table';
diff --git a/src/exchangedb/exchange_do_insert_aml_officer.sql b/src/exchangedb/exchange_do_insert_aml_officer.sql
new file mode 100644
index 000000000..429ba11c7
--- /dev/null
+++ b/src/exchangedb/exchange_do_insert_aml_officer.sql
@@ -0,0 +1,74 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_insert_aml_officer(
+ IN in_decider_pub BYTEA,
+ IN in_master_sig BYTEA,
+ IN in_decider_name TEXT,
+ IN in_is_active BOOLEAN,
+ IN in_read_only BOOLEAN,
+ IN in_last_change INT8,
+ OUT out_last_change INT8)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+INSERT INTO exchange.aml_staff
+ (decider_pub
+ ,master_sig
+ ,decider_name
+ ,is_active
+ ,read_only
+ ,last_change
+ ) VALUES
+ (in_decider_pub
+ ,in_master_sig
+ ,in_decider_name
+ ,in_is_active
+ ,in_read_only
+ ,in_last_change)
+ ON CONFLICT DO NOTHING;
+IF FOUND
+THEN
+ out_last_change=0;
+ RETURN;
+END IF;
+
+-- Check update is most recent...
+SELECT last_change
+ INTO out_last_change
+ FROM exchange.aml_staff
+ WHERE decider_pub=in_decider_pub;
+ASSERT FOUND, 'cannot have INSERT conflict but no AML staff record';
+
+IF out_last_change >= in_last_change
+THEN
+ -- Refuse to insert older status
+ RETURN;
+END IF;
+
+-- We are more recent, update existing record.
+UPDATE exchange.aml_staff
+ SET master_sig=in_master_sig
+ ,decider_name=in_decider_name
+ ,is_active=in_is_active
+ ,read_only=in_read_only
+ ,last_change=in_last_change
+ WHERE decider_pub=in_decider_pub;
+END $$;
+
+
+COMMENT ON FUNCTION exchange_do_insert_aml_officer(BYTEA, BYTEA, TEXT, BOOL, BOOL, INT8)
+ IS 'Inserts or updates AML staff record, making sure the update is more recent than the previous change';
diff --git a/src/exchangedb/exchange_do_insert_kyc_attributes.sql b/src/exchangedb/exchange_do_insert_kyc_attributes.sql
new file mode 100644
index 000000000..7db4d80c0
--- /dev/null
+++ b/src/exchangedb/exchange_do_insert_kyc_attributes.sql
@@ -0,0 +1,114 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_insert_kyc_attributes(
+ IN in_process_row INT8,
+ IN in_h_payto BYTEA,
+ IN in_kyc_prox BYTEA,
+ IN in_provider_section TEXT,
+ IN in_satisfied_checks TEXT[],
+ IN in_birthday INT4,
+ IN in_provider_account_id TEXT,
+ IN in_provider_legitimization_id TEXT,
+ IN in_collection_time_ts INT8,
+ IN in_expiration_time INT8,
+ IN in_expiration_time_ts INT8,
+ IN in_enc_attributes BYTEA,
+ IN in_require_aml BOOLEAN,
+ IN in_kyc_completed_notify_s TEXT,
+ OUT out_ok BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ orig_reserve_pub BYTEA;
+ orig_reserve_found BOOLEAN;
+BEGIN
+
+INSERT INTO exchange.kyc_attributes
+ (h_payto
+ ,kyc_prox
+ ,provider
+ ,satisfied_checks
+ ,collection_time
+ ,expiration_time
+ ,encrypted_attributes
+ ,legitimization_serial
+ ) VALUES
+ (in_h_payto
+ ,in_kyc_prox
+ ,in_provider_section
+ ,in_satisfied_checks
+ ,in_collection_time_ts
+ ,in_expiration_time_ts
+ ,in_enc_attributes
+ ,in_process_row);
+
+UPDATE legitimization_processes
+ SET provider_user_id=in_provider_account_id
+ ,provider_legitimization_id=in_provider_legitimization_id
+ ,expiration_time=GREATEST(expiration_time,in_expiration_time)
+ ,finished=TRUE
+ WHERE h_payto=in_h_payto
+ AND legitimization_process_serial_id=in_process_row
+ AND provider_section=in_provider_section;
+out_ok = FOUND;
+
+
+-- If the h_payto refers to a reserve in the original requirements
+-- update the originating reserve's birthday.
+SELECT reserve_pub
+ INTO orig_reserve_pub
+ FROM exchange.legitimization_requirements
+ WHERE h_payto=in_h_payto
+ AND NOT reserve_pub IS NULL;
+orig_reserve_found = FOUND;
+
+IF orig_reserve_found
+THEN
+ UPDATE exchange.reserves
+ SET birthday=in_birthday
+ WHERE reserve_pub=orig_reserve_pub;
+END IF;
+
+IF in_require_aml
+THEN
+ INSERT INTO exchange.aml_status
+ (h_payto
+ ,status)
+ VALUES
+ (in_h_payto
+ ,1)
+ ON CONFLICT (h_payto) DO
+ UPDATE SET status=EXCLUDED.status | 1;
+END IF;
+
+EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,in_kyc_completed_notify_s);
+
+
+INSERT INTO kyc_alerts
+ (h_payto
+ ,trigger_type)
+ VALUES
+ (in_h_payto,1);
+
+
+END $$;
+
+
+COMMENT ON FUNCTION exchange_do_insert_kyc_attributes(INT8, BYTEA, BYTEA, TEXT, TEXT[], INT4, TEXT, TEXT, INT8, INT8, INT8, BYTEA, BOOL, TEXT)
+ IS 'Inserts new KYC attributes and updates the status of the legitimization process and the AML status for the account';
diff --git a/src/exchangedb/exchange_do_insert_or_update_policy_details.sql b/src/exchangedb/exchange_do_insert_or_update_policy_details.sql
new file mode 100644
index 000000000..85e52d3d3
--- /dev/null
+++ b/src/exchangedb/exchange_do_insert_or_update_policy_details.sql
@@ -0,0 +1,114 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_insert_or_update_policy_details(
+ IN in_policy_hash_code BYTEA,
+ IN in_policy_json TEXT,
+ IN in_deadline INT8,
+ IN in_commitment taler_amount,
+ IN in_accumulated_total taler_amount,
+ IN in_fee taler_amount,
+ IN in_transferable taler_amount,
+ IN in_fulfillment_state SMALLINT,
+ OUT out_policy_details_serial_id INT8,
+ OUT out_accumulated_total taler_amount,
+ OUT out_fulfillment_state SMALLINT)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ cur_commitment taler_amount;
+DECLARE
+ cur_accumulated_total taler_amount;
+DECLARE
+ rval RECORD;
+BEGIN
+ -- First, try to create a new entry.
+ INSERT INTO policy_details
+ (policy_hash_code,
+ policy_json,
+ deadline,
+ commitment,
+ accumulated_total,
+ fee,
+ transferable,
+ fulfillment_state)
+ VALUES (in_policy_hash_code,
+ in_policy_json,
+ in_deadline,
+ in_commitment,
+ in_accumulated_total,
+ in_fee,
+ in_transferable,
+ in_fulfillment_state)
+ ON CONFLICT (policy_hash_code) DO NOTHING
+ RETURNING policy_details_serial_id INTO out_policy_details_serial_id;
+
+ -- If the insert was successful, return
+ -- We assume that the fullfilment_state was correct in first place.
+ IF FOUND THEN
+ out_accumulated_total = in_accumulated_total;
+ out_fulfillment_state = in_fulfillment_state;
+ RETURN;
+ END IF;
+
+ -- We had a conflict, grab the parts we need to update.
+ SELECT policy_details_serial_id
+ ,commitment
+ ,accumulated_total
+ INTO rval
+ FROM policy_details
+ WHERE policy_hash_code = in_policy_hash_code;
+
+ -- We use rval as workaround as we cannot select
+ -- directly into the amount due to Postgres limitations.
+ out_policy_details_serial_id := rval.policy_details_serial_id;
+ cur_commitment := rval.commitment;
+ cur_accumulated_total := rval.accumulated_total;
+
+ -- calculate the new values (overflows throws exception)
+ out_accumulated_total.val = cur_accumulated_total.val + in_accumulated_total.val;
+ out_accumulated_total.frac = cur_accumulated_total.frac + in_accumulated_total.frac;
+ -- normalize
+ out_accumulated_total.val = out_accumulated_total.val + out_accumulated_total.frac / 100000000;
+ out_accumulated_total.frac = out_accumulated_total.frac % 100000000;
+
+ IF (out_accumulated_total.val > (1 << 52))
+ THEN
+ RAISE EXCEPTION 'accumulation overflow';
+ END IF;
+
+
+ -- Set the fulfillment_state according to the values.
+ -- For now, we only update the state when it was INSUFFICIENT.
+ -- FIXME[oec] #7999: What to do in case of Failure or other state?
+ IF (out_fullfillment_state = 2) -- INSUFFICIENT
+ THEN
+ IF (out_accumulated_total.val >= cur_commitment.val OR
+ (out_accumulated_total.val = cur_commitment.val AND
+ out_accumulated_total.frac >= cur_commitment.frac))
+ THEN
+ out_fulfillment_state = 3; -- READY
+ END IF;
+ END IF;
+
+ -- Now, update the record
+ UPDATE exchange.policy_details
+ SET
+ accumulated = out_accumulated_total,
+ fulfillment_state = out_fulfillment_state
+ WHERE
+ policy_details_serial_id = out_policy_details_serial_id;
+END $$;
diff --git a/src/exchangedb/exchange_do_melt.sql b/src/exchangedb/exchange_do_melt.sql
new file mode 100644
index 000000000..0200986fa
--- /dev/null
+++ b/src/exchangedb/exchange_do_melt.sql
@@ -0,0 +1,182 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+
+
+CREATE OR REPLACE FUNCTION exchange_do_melt(
+ IN in_cs_rms BYTEA,
+ IN in_amount_with_fee taler_amount,
+ IN in_rc BYTEA,
+ IN in_old_coin_pub BYTEA,
+ IN in_old_coin_sig BYTEA,
+ IN in_known_coin_id INT8, -- not used, but that's OK
+ IN in_noreveal_index INT4,
+ IN in_zombie_required BOOLEAN,
+ OUT out_balance_ok BOOLEAN,
+ OUT out_zombie_bad BOOLEAN,
+ OUT out_noreveal_index INT4)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ denom_max INT8;
+BEGIN
+-- Shards: INSERT refresh_commitments (by rc)
+-- (rare:) SELECT refresh_commitments (by old_coin_pub) -- crosses shards!
+-- (rare:) SEELCT refresh_revealed_coins (by melt_serial_id)
+-- (rare:) PERFORM recoup_refresh (by rrc_serial) -- crosses shards!
+-- UPDATE known_coins (by coin_pub)
+
+INSERT INTO exchange.refresh_commitments
+ (rc
+ ,old_coin_pub
+ ,old_coin_sig
+ ,amount_with_fee
+ ,noreveal_index
+ )
+ VALUES
+ (in_rc
+ ,in_old_coin_pub
+ ,in_old_coin_sig
+ ,in_amount_with_fee
+ ,in_noreveal_index)
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Idempotency check: see if an identical record exists.
+ out_noreveal_index=-1;
+ SELECT
+ noreveal_index
+ INTO
+ out_noreveal_index
+ FROM exchange.refresh_commitments
+ WHERE rc=in_rc;
+ out_balance_ok=FOUND;
+ out_zombie_bad=FALSE; -- zombie is OK
+ RETURN;
+END IF;
+
+
+IF in_zombie_required
+THEN
+ -- Check if this coin was part of a refresh
+ -- operation that was subsequently involved
+ -- in a recoup operation. We begin by all
+ -- refresh operations our coin was involved
+ -- with, then find all associated reveal
+ -- operations, and then see if any of these
+ -- reveal operations was involved in a recoup.
+ PERFORM
+ FROM recoup_refresh
+ WHERE rrc_serial IN
+ (SELECT rrc_serial
+ FROM refresh_revealed_coins
+ WHERE melt_serial_id IN
+ (SELECT melt_serial_id
+ FROM refresh_commitments
+ WHERE old_coin_pub=in_old_coin_pub));
+ IF NOT FOUND
+ THEN
+ out_zombie_bad=TRUE;
+ out_balance_ok=FALSE;
+ RETURN;
+ END IF;
+END IF;
+
+out_zombie_bad=FALSE; -- zombie is OK
+
+
+-- Check and update balance of the coin.
+UPDATE known_coins kc
+ SET
+ remaining.frac=(kc.remaining).frac-in_amount_with_fee.frac
+ + CASE
+ WHEN (kc.remaining).frac < in_amount_with_fee.frac
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val-in_amount_with_fee.val
+ - CASE
+ WHEN (kc.remaining).frac < in_amount_with_fee.frac
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub=in_old_coin_pub
+ AND ( ((kc.remaining).val > in_amount_with_fee.val) OR
+ ( ((kc.remaining).frac >= in_amount_with_fee.frac) AND
+ ((kc.remaining).val >= in_amount_with_fee.val) ) );
+
+IF NOT FOUND
+THEN
+ -- Insufficient balance.
+ out_noreveal_index=-1;
+ out_balance_ok=FALSE;
+ RETURN;
+END IF;
+
+
+
+-- Special actions needed for a CS melt?
+IF NOT NULL in_cs_rms
+THEN
+ -- Get maximum denominations serial value in
+ -- existence, this will determine how long the
+ -- nonce will be locked.
+ SELECT
+ denominations_serial
+ INTO
+ denom_max
+ FROM exchange.denominations
+ ORDER BY denominations_serial DESC
+ LIMIT 1;
+
+ -- Cache CS signature to prevent replays in the future
+ -- (and check if cached signature exists at the same time).
+ INSERT INTO exchange.cs_nonce_locks
+ (nonce
+ ,max_denomination_serial
+ ,op_hash)
+ VALUES
+ (cs_rms
+ ,denom_serial
+ ,in_rc)
+ ON CONFLICT DO NOTHING;
+
+ IF NOT FOUND
+ THEN
+ -- Record exists, make sure it is the same
+ SELECT 1
+ FROM exchange.cs_nonce_locks
+ WHERE nonce=cs_rms
+ AND op_hash=in_rc;
+
+ IF NOT FOUND
+ THEN
+ -- Nonce reuse detected
+ out_balance_ok=FALSE;
+ out_zombie_bad=FALSE;
+ out_noreveal_index=42; -- FIXME: return error message more nicely!
+ ASSERT false, 'nonce reuse attempted by client';
+ END IF;
+ END IF;
+END IF;
+
+-- Everything fine, return success!
+out_balance_ok=TRUE;
+out_noreveal_index=in_noreveal_index;
+
+END $$;
diff --git a/src/exchangedb/exchange_do_purse_delete.sql b/src/exchangedb/exchange_do_purse_delete.sql
new file mode 100644
index 000000000..5668f7bec
--- /dev/null
+++ b/src/exchangedb/exchange_do_purse_delete.sql
@@ -0,0 +1,118 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_purse_delete(
+ IN in_purse_pub BYTEA,
+ IN in_purse_sig BYTEA,
+ IN in_now INT8,
+ OUT out_decided BOOLEAN,
+ OUT out_found BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_deposit record;
+DECLARE
+ my_in_reserve_quota BOOLEAN;
+BEGIN
+
+PERFORM refunded FROM purse_decision
+ WHERE purse_pub=in_purse_pub;
+IF FOUND
+THEN
+ out_found=TRUE;
+ out_decided=TRUE;
+ RETURN;
+END IF;
+out_decided=FALSE;
+
+SELECT in_reserve_quota
+ INTO my_in_reserve_quota
+ FROM exchange.purse_requests
+ WHERE purse_pub=in_purse_pub;
+out_found=FOUND;
+IF NOT FOUND
+THEN
+ RETURN;
+END IF;
+
+-- store reserve deletion
+INSERT INTO exchange.purse_deletion
+ (purse_pub
+ ,purse_sig)
+VALUES
+ (in_purse_pub
+ ,in_purse_sig)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ RETURN;
+END IF;
+
+-- Delete contract associated with purse, if it exists.
+DELETE FROM contracts
+ WHERE purse_pub=in_purse_pub;
+
+-- store purse decision
+INSERT INTO purse_decision
+ (purse_pub
+ ,action_timestamp
+ ,refunded)
+VALUES
+ (in_purse_pub
+ ,in_now
+ ,TRUE);
+
+-- update purse quota at reserve
+IF (my_in_reserve_quota)
+THEN
+ UPDATE reserves
+ SET purses_active=purses_active-1
+ WHERE reserve_pub IN
+ (SELECT reserve_pub
+ FROM exchange.purse_merges
+ WHERE purse_pub=in_purse_pub
+ LIMIT 1);
+END IF;
+
+-- restore balance to each coin deposited into the purse
+FOR my_deposit IN
+ SELECT coin_pub
+ ,amount_with_fee
+ FROM exchange.purse_deposits
+ WHERE purse_pub = in_purse_pub
+LOOP
+ UPDATE known_coins kc SET
+ remaining.frac=(kc.remaining).frac+(my_deposit.amount_with_fee).frac
+ - CASE
+ WHEN (kc.remaining).frac+(my_deposit.amount_with_fee).frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val+(my_deposit.amount_with_fee).val
+ + CASE
+ WHEN (kc.remaining).frac+(my_deposit.amount_with_fee).frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub = my_deposit.coin_pub;
+END LOOP;
+
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_purse_delete(BYTEA,BYTEA,INT8)
+ IS 'Delete a previously undecided purse and refund the coins (if any).';
diff --git a/src/exchangedb/exchange_do_purse_deposit.sql b/src/exchangedb/exchange_do_purse_deposit.sql
new file mode 100644
index 000000000..49d3c919b
--- /dev/null
+++ b/src/exchangedb/exchange_do_purse_deposit.sql
@@ -0,0 +1,267 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_purse_deposit(
+ IN in_partner_id INT8,
+ IN in_purse_pub BYTEA,
+ IN in_amount_with_fee taler_amount,
+ IN in_coin_pub BYTEA,
+ IN in_coin_sig BYTEA,
+ IN in_amount_without_fee taler_amount,
+ IN in_reserve_expiration INT8,
+ IN in_now INT8,
+ OUT out_balance_ok BOOLEAN,
+ OUT out_late BOOLEAN,
+ OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ was_merged BOOLEAN;
+DECLARE
+ psi INT8; -- partner's serial ID (set if merged)
+DECLARE
+ my_amount taler_amount; -- total in purse
+DECLARE
+ was_paid BOOLEAN;
+DECLARE
+ my_in_reserve_quota BOOLEAN;
+DECLARE
+ my_reserve_pub BYTEA;
+DECLARE
+ rval RECORD;
+BEGIN
+
+-- Store the deposit request.
+INSERT INTO purse_deposits
+ (partner_serial_id
+ ,purse_pub
+ ,coin_pub
+ ,amount_with_fee
+ ,coin_sig)
+ VALUES
+ (in_partner_id
+ ,in_purse_pub
+ ,in_coin_pub
+ ,in_amount_with_fee
+ ,in_coin_sig)
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Idempotency check: check if coin_sig is the same,
+ -- if so, success, otherwise conflict!
+
+ PERFORM
+ FROM purse_deposits
+ WHERE purse_pub = in_purse_pub
+ AND coin_pub = in_coin_pub
+ AND coin_sig = in_coin_sig;
+ IF NOT FOUND
+ THEN
+ -- Deposit exists, but with differences. Not allowed.
+ out_balance_ok=FALSE;
+ out_late=FALSE;
+ out_conflict=TRUE;
+ RETURN;
+ ELSE
+ -- Deposit exists, do not count for balance. Allow.
+ out_late=FALSE;
+ out_balance_ok=TRUE;
+ out_conflict=FALSE;
+ RETURN;
+ END IF;
+END IF;
+
+
+-- Check if purse was deleted, if so, abort and prevent deposit.
+PERFORM
+ FROM exchange.purse_deletion
+ WHERE purse_pub = in_purse_pub;
+IF FOUND
+THEN
+ out_late=TRUE;
+ out_balance_ok=FALSE;
+ out_conflict=FALSE;
+ RETURN;
+END IF;
+
+
+-- Debit the coin
+-- Check and update balance of the coin.
+UPDATE known_coins kc
+ SET
+ remaining.frac=(kc.remaining).frac-in_amount_with_fee.frac
+ + CASE
+ WHEN (kc.remaining).frac < in_amount_with_fee.frac
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val-in_amount_with_fee.val
+ - CASE
+ WHEN (kc.remaining).frac < in_amount_with_fee.frac
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub=in_coin_pub
+ AND ( ((kc.remaining).val > in_amount_with_fee.val) OR
+ ( ((kc.remaining).frac >= in_amount_with_fee.frac) AND
+ ((kc.remaining).val >= in_amount_with_fee.val) ) );
+
+IF NOT FOUND
+THEN
+ -- Insufficient balance.
+ out_balance_ok=FALSE;
+ out_late=FALSE;
+ out_conflict=FALSE;
+ RETURN;
+END IF;
+
+
+-- Credit the purse.
+UPDATE purse_requests pr
+ SET
+ balance.frac=(pr.balance).frac+in_amount_without_fee.frac
+ - CASE
+ WHEN (pr.balance).frac+in_amount_without_fee.frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END,
+ balance.val=(pr.balance).val+in_amount_without_fee.val
+ + CASE
+ WHEN (pr.balance).frac+in_amount_without_fee.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ WHERE purse_pub=in_purse_pub;
+
+out_conflict=FALSE;
+out_balance_ok=TRUE;
+
+-- See if we can finish the merge or need to update the trigger time and partner.
+SELECT COALESCE(partner_serial_id,0)
+ ,reserve_pub
+ INTO psi
+ ,my_reserve_pub
+ FROM purse_merges
+ WHERE purse_pub=in_purse_pub;
+
+IF NOT FOUND
+THEN
+ -- Purse was not yet merged. We are done.
+ out_late=FALSE;
+ RETURN;
+END IF;
+
+SELECT
+ amount_with_fee
+ ,in_reserve_quota
+ INTO
+ rval
+ FROM exchange.purse_requests preq
+ WHERE (purse_pub=in_purse_pub)
+ AND ( ( ( ((preq.amount_with_fee).val <= (preq.balance).val)
+ AND ((preq.amount_with_fee).frac <= (preq.balance).frac) )
+ OR ((preq.amount_with_fee).val < (preq.balance).val) ) );
+IF NOT FOUND
+THEN
+ out_late=FALSE;
+ RETURN;
+END IF;
+
+-- We use rval as workaround as we cannot select
+-- directly into the amount due to Postgres limitations.
+my_amount := rval.amount_with_fee;
+my_in_reserve_quota := rval.in_reserve_quota;
+
+-- Remember how this purse was finished.
+INSERT INTO purse_decision
+ (purse_pub
+ ,action_timestamp
+ ,refunded)
+VALUES
+ (in_purse_pub
+ ,in_now
+ ,FALSE)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Purse already decided, likely expired.
+ out_late=TRUE;
+ RETURN;
+END IF;
+
+out_late=FALSE;
+
+IF (my_in_reserve_quota)
+THEN
+ UPDATE reserves
+ SET purses_active=purses_active-1
+ WHERE reserve_pub IN
+ (SELECT reserve_pub
+ FROM purse_merges
+ WHERE purse_pub=my_purse_pub
+ LIMIT 1);
+END IF;
+
+
+IF (0 != psi)
+THEN
+ -- The taler-exchange-router will take care of this.
+ UPDATE purse_actions
+ SET action_date=0 --- "immediately"
+ ,partner_serial_id=psi
+ WHERE purse_pub=in_purse_pub;
+ELSE
+ -- This is a local reserve, update balance immediately.
+ INSERT INTO reserves
+ (reserve_pub
+ ,current_balance
+ ,expiration_date
+ ,gc_date)
+ VALUES
+ (my_reserve_pub
+ ,my_amount
+ ,in_reserve_expiration
+ ,in_reserve_expiration)
+ ON CONFLICT DO NOTHING;
+
+ IF NOT FOUND
+ THEN
+ -- Reserve existed, thus UPDATE instead of INSERT.
+ UPDATE reserves
+ SET
+ current_balance.frac=(current_balance).frac+my_amount.frac
+ - CASE
+ WHEN (current_balance).frac + my_amount.frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END
+ ,current_balance.val=(current_balance).val+my_amount.val
+ + CASE
+ WHEN (current_balance).frac + my_amount.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ ,expiration_date=GREATEST(expiration_date,in_reserve_expiration)
+ ,gc_date=GREATEST(gc_date,in_reserve_expiration)
+ WHERE reserve_pub=my_reserve_pub;
+ END IF;
+
+END IF;
+
+
+END $$;
diff --git a/src/exchangedb/exchange_do_purse_merge.sql b/src/exchangedb/exchange_do_purse_merge.sql
new file mode 100644
index 000000000..946fd7e97
--- /dev/null
+++ b/src/exchangedb/exchange_do_purse_merge.sql
@@ -0,0 +1,237 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_purse_merge(
+ IN in_purse_pub BYTEA,
+ IN in_merge_sig BYTEA,
+ IN in_merge_timestamp INT8,
+ IN in_reserve_sig BYTEA,
+ IN in_partner_url TEXT,
+ IN in_reserve_pub BYTEA,
+ IN in_wallet_h_payto BYTEA,
+ IN in_expiration_date INT8,
+ OUT out_no_partner BOOLEAN,
+ OUT out_no_balance BOOLEAN,
+ OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_amount taler_amount;
+DECLARE
+ my_purse_fee taler_amount;
+DECLARE
+ my_partner_serial_id INT8;
+DECLARE
+ my_in_reserve_quota BOOLEAN;
+DECLARE
+ rval RECORD;
+DECLARE
+ reserve RECORD;
+DECLARE
+ balance taler_amount;
+BEGIN
+
+-- Initialize reserve, if not yet exists.
+INSERT INTO reserves
+ (reserve_pub
+ ,expiration_date
+ ,gc_date)
+ VALUES
+ (in_reserve_pub
+ ,in_expiration_date
+ ,in_expiration_date)
+ ON CONFLICT DO NOTHING;
+
+
+IF in_partner_url IS NULL
+THEN
+ my_partner_serial_id=NULL;
+ELSE
+ SELECT
+ partner_serial_id
+ INTO
+ my_partner_serial_id
+ FROM exchange.partners
+ WHERE partner_base_url=in_partner_url
+ AND start_date <= in_merge_timestamp
+ AND end_date > in_merge_timestamp;
+ IF NOT FOUND
+ THEN
+ out_no_partner=TRUE;
+ out_conflict=FALSE;
+ RETURN;
+ END IF;
+END IF;
+
+out_no_partner=FALSE;
+
+-- Check purse is 'full'.
+SELECT amount_with_fee
+ ,purse_fee
+ ,in_reserve_quota
+ INTO rval
+ FROM purse_requests pr
+ WHERE purse_pub=in_purse_pub
+ AND (pr.balance).val >= (pr.amount_with_fee).val
+ AND ( (pr.balance).frac >= (pr.amount_with_fee).frac OR
+ (pr.balance).val > (pr.amount_with_fee).val );
+IF NOT FOUND
+THEN
+ out_no_balance=TRUE;
+ out_conflict=FALSE;
+ RETURN;
+END IF;
+
+-- We use rval as workaround as we cannot select
+-- directly into the amount due to Postgres limitations.
+my_amount := rval.amount_with_fee;
+my_purse_fee := rval.purse_fee;
+my_in_reserve_quota := rval.in_reserve_quota;
+
+out_no_balance=FALSE;
+
+-- Store purse merge signature, checks for purse_pub uniqueness
+INSERT INTO purse_merges
+ (partner_serial_id
+ ,reserve_pub
+ ,purse_pub
+ ,merge_sig
+ ,merge_timestamp)
+ VALUES
+ (my_partner_serial_id
+ ,in_reserve_pub
+ ,in_purse_pub
+ ,in_merge_sig
+ ,in_merge_timestamp)
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Idempotency check: see if an identical record exists.
+ -- Note that by checking 'merge_sig', we implicitly check
+ -- identity over everything that the signature covers.
+ PERFORM
+ FROM purse_merges
+ WHERE purse_pub=in_purse_pub
+ AND merge_sig=in_merge_sig;
+ IF NOT FOUND
+ THEN
+ -- Purse was merged, but to some other reserve. Not allowed.
+ out_conflict=TRUE;
+ RETURN;
+ END IF;
+
+ -- "success"
+ out_conflict=FALSE;
+ RETURN;
+END IF;
+
+
+-- Remember how this purse was finished. This will conflict
+-- if the purse was already decided previously.
+INSERT INTO purse_decision
+ (purse_pub
+ ,action_timestamp
+ ,refunded)
+VALUES
+ (in_purse_pub
+ ,in_merge_timestamp
+ ,FALSE)
+ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Purse was already decided (possibly deleted or merged differently).
+ out_conflict=TRUE;
+ RETURN;
+END IF;
+
+out_conflict=FALSE;
+
+
+
+IF (my_in_reserve_quota)
+THEN
+ UPDATE reserves
+ SET purses_active=purses_active-1
+ WHERE reserve_pub IN
+ (SELECT reserve_pub
+ FROM purse_merges
+ WHERE purse_pub=my_purse_pub
+ LIMIT 1);
+END IF;
+
+-- Store account merge signature.
+INSERT INTO account_merges
+ (reserve_pub
+ ,reserve_sig
+ ,purse_pub
+ ,wallet_h_payto)
+ VALUES
+ (in_reserve_pub
+ ,in_reserve_sig
+ ,in_purse_pub
+ ,in_wallet_h_payto);
+
+-- If we need a wad transfer, mark purse ready for it.
+IF (0 != my_partner_serial_id)
+THEN
+ -- The taler-exchange-router will take care of this.
+ UPDATE purse_actions
+ SET action_date=0 --- "immediately"
+ ,partner_serial_id=my_partner_serial_id
+ WHERE purse_pub=in_purse_pub;
+ELSE
+ -- This is a local reserve, update reserve balance immediately.
+
+ -- Refund the purse fee, by adding it to the purse value:
+ my_amount.val = my_amount.val + my_purse_fee.val;
+ my_amount.frac = my_amount.frac + my_purse_fee.frac;
+ -- normalize result
+ my_amount.val = my_amount.val + my_amount.frac / 100000000;
+ my_amount.frac = my_amount.frac % 100000000;
+
+ SELECT *
+ INTO reserve
+ FROM exchange.reserves
+ WHERE reserve_pub=in_reserve_pub;
+
+ balance = reserve.current_balance;
+ balance.frac=balance.frac+my_amount.frac
+ - CASE
+ WHEN balance.frac + my_amount.frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END;
+ balance.val=balance.val+my_amount.val
+ + CASE
+ WHEN balance.frac + my_amount.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END;
+
+ UPDATE exchange.reserves
+ SET current_balance=balance
+ WHERE reserve_pub=in_reserve_pub;
+
+END IF;
+
+RETURN;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_purse_merge(BYTEA, BYTEA, INT8, BYTEA, TEXT, BYTEA, BYTEA, INT8)
+ IS 'Checks that the partner exists, the purse has not been merged with a different reserve and that the purse is full. If so, persists the merge data and either merges the purse with the reserve or marks it as ready for the taler-exchange-router. Caller MUST abort the transaction on failures so as to not persist data by accident.';
diff --git a/src/exchangedb/exchange_do_recoup_by_reserve.sql b/src/exchangedb/exchange_do_recoup_by_reserve.sql
new file mode 100644
index 000000000..80f953c4a
--- /dev/null
+++ b/src/exchangedb/exchange_do_recoup_by_reserve.sql
@@ -0,0 +1,87 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE OR REPLACE FUNCTION exchange_do_recoup_by_reserve(
+ IN res_pub BYTEA
+)
+RETURNS TABLE
+(
+ denom_sig BYTEA,
+ denominations_serial BIGINT,
+ coin_pub BYTEA,
+ coin_sig BYTEA,
+ coin_blind BYTEA,
+ amount taler_amount,
+ recoup_timestamp BIGINT
+)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ res_uuid BIGINT;
+ blind_ev BYTEA;
+ c_pub BYTEA;
+BEGIN
+ SELECT reserve_uuid
+ INTO res_uuid
+ FROM reserves
+ WHERE reserve_pub = res_pub;
+
+ FOR blind_ev IN
+ SELECT h_blind_ev
+ FROM reserves_out ro
+ JOIN reserve_history rh
+ ON (rh.serial_id = ro.reserve_out_serial_id)
+ WHERE rh.reserve_pub = res_pub
+ AND rh.table_name='reserves_out'
+ LOOP
+ SELECT robr.coin_pub
+ INTO c_pub
+ FROM exchange.recoup_by_reserve robr
+ WHERE robr.reserve_out_serial_id = (
+ SELECT reserve_out_serial_id
+ FROM reserves_out
+ WHERE h_blind_ev = blind_ev
+ );
+ RETURN QUERY
+ SELECT kc.denom_sig,
+ kc.denominations_serial,
+ rc.coin_pub,
+ rc.coin_sig,
+ rc.coin_blind,
+ rc.amount,
+ rc.recoup_timestamp
+ FROM (
+ SELECT denom_sig
+ ,denominations_serial
+ FROM exchange.known_coins
+ WHERE known_coins.coin_pub = c_pub
+ ) kc
+ JOIN (
+ SELECT coin_pub
+ ,coin_sig
+ ,coin_blind
+ ,amount
+ ,recoup_timestamp
+ FROM exchange.recoup
+ WHERE recoup.coin_pub = c_pub
+ ) rc USING (coin_pub);
+ END LOOP;
+END;
+$$;
+
+COMMENT ON FUNCTION exchange_do_recoup_by_reserve
+ IS 'Recoup by reserve as a function to make sure we hit only the needed partition and not all when joining as joins on distributed tables fetch ALL rows from the shards';
diff --git a/src/exchangedb/exchange_do_recoup_to_coin.sql b/src/exchangedb/exchange_do_recoup_to_coin.sql
new file mode 100644
index 000000000..6cecfb7f8
--- /dev/null
+++ b/src/exchangedb/exchange_do_recoup_to_coin.sql
@@ -0,0 +1,135 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+
+
+CREATE OR REPLACE FUNCTION exchange_do_recoup_to_coin(
+ IN in_old_coin_pub BYTEA,
+ IN in_rrc_serial INT8,
+ IN in_coin_blind BYTEA,
+ IN in_coin_pub BYTEA,
+ IN in_known_coin_id INT8,
+ IN in_coin_sig BYTEA,
+ IN in_recoup_timestamp INT8,
+ OUT out_recoup_ok BOOLEAN,
+ OUT out_internal_failure BOOLEAN,
+ OUT out_recoup_timestamp INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ rval RECORD;
+DECLARE
+ tmp taler_amount; -- amount recouped
+BEGIN
+
+-- Shards: UPDATE known_coins (by coin_pub)
+-- SELECT recoup_refresh (by coin_pub)
+-- UPDATE known_coins (by coin_pub)
+-- INSERT recoup_refresh (by coin_pub)
+
+out_internal_failure=FALSE;
+
+-- Check remaining balance of the coin.
+SELECT
+ remaining
+ INTO
+ rval
+FROM exchange.known_coins
+ WHERE coin_pub=in_coin_pub;
+
+IF NOT FOUND
+THEN
+ out_internal_failure=TRUE;
+ out_recoup_ok=FALSE;
+ RETURN;
+END IF;
+
+tmp := rval.remaining;
+
+IF tmp.val + tmp.frac = 0
+THEN
+ -- Check for idempotency
+ SELECT
+ recoup_timestamp
+ INTO
+ out_recoup_timestamp
+ FROM recoup_refresh
+ WHERE coin_pub=in_coin_pub;
+ out_recoup_ok=FOUND;
+ RETURN;
+END IF;
+
+-- Update balance of the coin.
+UPDATE known_coins
+ SET
+ remaining.val = 0
+ ,remaining.frac = 0
+ WHERE coin_pub=in_coin_pub;
+
+-- Credit the old coin.
+UPDATE known_coins kc
+ SET
+ remaining.frac=(kc.remaining).frac+tmp.frac
+ - CASE
+ WHEN (kc.remaining).frac+tmp.frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val+tmp.val
+ + CASE
+ WHEN (kc.remaining).frac+tmp.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub=in_old_coin_pub;
+
+IF NOT FOUND
+THEN
+ RAISE NOTICE 'failed to increase old coin balance from recoup';
+ out_recoup_ok=TRUE;
+ out_internal_failure=TRUE;
+ RETURN;
+END IF;
+
+
+INSERT INTO recoup_refresh
+ (coin_pub
+ ,known_coin_id
+ ,coin_sig
+ ,coin_blind
+ ,amount
+ ,recoup_timestamp
+ ,rrc_serial
+ )
+VALUES
+ (in_coin_pub
+ ,in_known_coin_id
+ ,in_coin_sig
+ ,in_coin_blind
+ ,tmp
+ ,in_recoup_timestamp
+ ,in_rrc_serial);
+
+-- Normal end, everything is fine.
+out_recoup_ok=TRUE;
+out_recoup_timestamp=in_recoup_timestamp;
+
+END $$;
+
+
+-- COMMENT ON FUNCTION exchange_do_recoup_to_coin(INT8, INT4, BYTEA, BOOLEAN, BOOLEAN)
+-- IS 'Executes a recoup-refresh of a coin that was obtained from a refresh-reveal process';
diff --git a/src/exchangedb/exchange_do_recoup_to_reserve.sql b/src/exchangedb/exchange_do_recoup_to_reserve.sql
new file mode 100644
index 000000000..10ae063bd
--- /dev/null
+++ b/src/exchangedb/exchange_do_recoup_to_reserve.sql
@@ -0,0 +1,150 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE OR REPLACE FUNCTION exchange_do_recoup_to_reserve(
+ IN in_reserve_pub BYTEA,
+ IN in_reserve_out_serial_id INT8,
+ IN in_coin_blind BYTEA,
+ IN in_coin_pub BYTEA,
+ IN in_known_coin_id INT8,
+ IN in_coin_sig BYTEA,
+ IN in_reserve_gc INT8,
+ IN in_reserve_expiration INT8,
+ IN in_recoup_timestamp INT8,
+ OUT out_recoup_ok BOOLEAN,
+ OUT out_internal_failure BOOLEAN,
+ OUT out_recoup_timestamp INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ tmp taler_amount; -- amount recouped
+ balance taler_amount; -- current balance of the reserve
+ new_balance taler_amount; -- new balance of the reserve
+ reserve RECORD;
+ rval RECORD;
+BEGIN
+-- Shards: SELECT known_coins (by coin_pub)
+-- SELECT recoup (by coin_pub)
+-- UPDATE known_coins (by coin_pub)
+-- UPDATE reserves (by reserve_pub)
+-- INSERT recoup (by coin_pub)
+
+out_internal_failure=FALSE;
+
+
+-- Check remaining balance of the coin.
+SELECT
+ remaining
+ INTO
+ rval
+FROM exchange.known_coins
+ WHERE coin_pub=in_coin_pub;
+
+IF NOT FOUND
+THEN
+ out_internal_failure=TRUE;
+ out_recoup_ok=FALSE;
+ RETURN;
+END IF;
+
+tmp := rval.remaining;
+
+IF tmp.val + tmp.frac = 0
+THEN
+ -- Check for idempotency
+ SELECT
+ recoup_timestamp
+ INTO
+ out_recoup_timestamp
+ FROM exchange.recoup
+ WHERE coin_pub=in_coin_pub;
+
+ out_recoup_ok=FOUND;
+ RETURN;
+END IF;
+
+
+-- Update balance of the coin.
+UPDATE known_coins
+ SET
+ remaining.val = 0
+ ,remaining.frac = 0
+ WHERE coin_pub=in_coin_pub;
+
+-- Get current balance
+SELECT current_balance
+ INTO reserve
+ FROM reserves
+ WHERE reserve_pub=in_reserve_pub;
+
+balance = reserve.current_balance;
+new_balance.frac=balance.frac+tmp.frac
+ - CASE
+ WHEN balance.frac+tmp.frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END;
+
+new_balance.val=balance.val+tmp.val
+ + CASE
+ WHEN balance.frac+tmp.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END;
+
+-- Credit the reserve and update reserve timers.
+UPDATE reserves
+ SET
+ current_balance = new_balance,
+ gc_date=GREATEST(gc_date, in_reserve_gc),
+ expiration_date=GREATEST(expiration_date, in_reserve_expiration)
+ WHERE reserve_pub=in_reserve_pub;
+
+
+IF NOT FOUND
+THEN
+ RAISE NOTICE 'failed to increase reserve balance from recoup';
+ out_recoup_ok=TRUE;
+ out_internal_failure=TRUE;
+ RETURN;
+END IF;
+
+
+INSERT INTO exchange.recoup
+ (coin_pub
+ ,coin_sig
+ ,coin_blind
+ ,amount
+ ,recoup_timestamp
+ ,reserve_out_serial_id
+ )
+VALUES
+ (in_coin_pub
+ ,in_coin_sig
+ ,in_coin_blind
+ ,tmp
+ ,in_recoup_timestamp
+ ,in_reserve_out_serial_id);
+
+-- Normal end, everything is fine.
+out_recoup_ok=TRUE;
+out_recoup_timestamp=in_recoup_timestamp;
+
+END $$;
+
+-- COMMENT ON FUNCTION exchange_do_recoup_to_reserve(INT8, INT4, BYTEA, BOOLEAN, BOOLEAN)
+-- IS 'Executes a recoup of a coin that was withdrawn from a reserve';
diff --git a/src/exchangedb/exchange_do_refund.sql b/src/exchangedb/exchange_do_refund.sql
new file mode 100644
index 000000000..a95746127
--- /dev/null
+++ b/src/exchangedb/exchange_do_refund.sql
@@ -0,0 +1,205 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_refund(
+ IN in_amount_with_fee taler_amount,
+ IN in_amount taler_amount,
+ IN in_deposit_fee taler_amount,
+ IN in_h_contract_terms BYTEA,
+ IN in_rtransaction_id INT8,
+ IN in_deposit_shard INT8,
+ IN in_known_coin_id INT8,
+ IN in_coin_pub BYTEA,
+ IN in_merchant_pub BYTEA,
+ IN in_merchant_sig BYTEA,
+ OUT out_not_found BOOLEAN,
+ OUT out_refund_ok BOOLEAN,
+ OUT out_gone BOOLEAN,
+ OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ bdsi INT8; -- ID of deposit being refunded
+DECLARE
+ tmp_val INT8; -- total amount refunded
+DECLARE
+ tmp_frac INT8; -- total amount refunded, large fraction to deal with overflows!
+DECLARE
+ tmp taler_amount; -- total amount refunded, normalized
+DECLARE
+ deposit taler_amount; -- amount that was originally deposited
+BEGIN
+-- Shards: SELECT deposits (coin_pub, shard, h_contract_terms, merchant_pub)
+-- INSERT refunds (by coin_pub, rtransaction_id) ON CONFLICT DO NOTHING
+-- SELECT refunds (by coin_pub)
+-- UPDATE known_coins (by coin_pub)
+
+SELECT
+ bdep.batch_deposit_serial_id
+ ,(cdep.amount_with_fee).val
+ ,(cdep.amount_with_fee).frac
+ ,bdep.done
+ INTO
+ bdsi
+ ,deposit.val
+ ,deposit.frac
+ ,out_gone
+ FROM batch_deposits bdep
+ JOIN coin_deposits cdep
+ USING (batch_deposit_serial_id)
+ WHERE cdep.coin_pub=in_coin_pub
+ AND shard=in_deposit_shard
+ AND merchant_pub=in_merchant_pub
+ AND h_contract_terms=in_h_contract_terms;
+
+IF NOT FOUND
+THEN
+ -- No matching deposit found!
+ out_refund_ok=FALSE;
+ out_conflict=FALSE;
+ out_not_found=TRUE;
+ out_gone=FALSE;
+ RETURN;
+END IF;
+
+INSERT INTO refunds
+ (batch_deposit_serial_id
+ ,coin_pub
+ ,merchant_sig
+ ,rtransaction_id
+ ,amount_with_fee
+ )
+ VALUES
+ (bdsi
+ ,in_coin_pub
+ ,in_merchant_sig
+ ,in_rtransaction_id
+ ,in_amount_with_fee
+ )
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Idempotency check: see if an identical record exists.
+ -- Note that by checking 'coin_sig', we implicitly check
+ -- identity over everything that the signature covers.
+ -- We do select over merchant_pub and h_contract_terms
+ -- primarily here to maximally use the existing index.
+ PERFORM
+ FROM exchange.refunds
+ WHERE coin_pub=in_coin_pub
+ AND batch_deposit_serial_id=bdsi
+ AND rtransaction_id=in_rtransaction_id
+ AND amount_with_fee=in_amount_with_fee;
+
+ IF NOT FOUND
+ THEN
+ -- Deposit exists, but have conflicting refund.
+ out_refund_ok=FALSE;
+ out_conflict=TRUE;
+ out_not_found=FALSE;
+ RETURN;
+ END IF;
+
+ -- Idempotent request known, return success.
+ out_refund_ok=TRUE;
+ out_conflict=FALSE;
+ out_not_found=FALSE;
+ out_gone=FALSE;
+ RETURN;
+END IF;
+
+IF out_gone
+THEN
+ -- money already sent to the merchant. Tough luck.
+ out_refund_ok=FALSE;
+ out_conflict=FALSE;
+ out_not_found=FALSE;
+ RETURN;
+END IF;
+
+-- Check refund balance invariant.
+SELECT
+ SUM((refs.amount_with_fee).val) -- overflow here is not plausible
+ ,SUM(CAST((refs.amount_with_fee).frac AS INT8)) -- compute using 64 bits
+ INTO
+ tmp_val
+ ,tmp_frac
+ FROM refunds refs
+ WHERE coin_pub=in_coin_pub
+ AND batch_deposit_serial_id=bdsi;
+IF tmp_val IS NULL
+THEN
+ RAISE NOTICE 'failed to sum up existing refunds';
+ out_refund_ok=FALSE;
+ out_conflict=FALSE;
+ out_not_found=FALSE;
+ RETURN;
+END IF;
+
+-- Normalize result before continuing
+tmp.val = tmp_val + tmp_frac / 100000000;
+tmp.frac = tmp_frac % 100000000;
+
+-- Actually check if the deposits are sufficient for the refund. Verbosely. ;-)
+IF (tmp.val < deposit.val)
+THEN
+ out_refund_ok=TRUE;
+ELSE
+ IF (tmp.val = deposit.val) AND (tmp.frac <= deposit.frac)
+ THEN
+ out_refund_ok=TRUE;
+ ELSE
+ out_refund_ok=FALSE;
+ END IF;
+END IF;
+
+IF (tmp.val = deposit.val) AND (tmp.frac = deposit.frac)
+THEN
+ -- Refunds have reached the full value of the original
+ -- deposit. Also refund the deposit fee.
+ in_amount.frac = in_amount.frac + in_deposit_fee.frac;
+ in_amount.val = in_amount.val + in_deposit_fee.val;
+
+ -- Normalize result before continuing
+ in_amount.val = in_amount.val + in_amount.frac / 100000000;
+ in_amount.frac = in_amount.frac % 100000000;
+END IF;
+
+-- Update balance of the coin.
+UPDATE known_coins kc
+ SET
+ remaining.frac=(kc.remaining).frac+in_amount.frac
+ - CASE
+ WHEN (kc.remaining).frac+in_amount.frac >= 100000000
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val+in_amount.val
+ + CASE
+ WHEN (kc.remaining).frac+in_amount.frac >= 100000000
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub=in_coin_pub;
+
+out_conflict=FALSE;
+out_not_found=FALSE;
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_refund(taler_amount, taler_amount, taler_amount, BYTEA, INT8, INT8, INT8, BYTEA, BYTEA, BYTEA)
+ IS 'Executes a refund operation, checking that the corresponding deposit was sufficient to cover the refunded amount';
diff --git a/src/exchangedb/exchange_do_reserve_open.sql b/src/exchangedb/exchange_do_reserve_open.sql
new file mode 100644
index 000000000..dd7a578ee
--- /dev/null
+++ b/src/exchangedb/exchange_do_reserve_open.sql
@@ -0,0 +1,194 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_reserve_open(
+ IN in_reserve_pub BYTEA,
+ IN in_total_paid taler_amount,
+ IN in_reserve_payment taler_amount,
+ IN in_min_purse_limit INT4,
+ IN in_default_purse_limit INT4,
+ IN in_reserve_sig BYTEA,
+ IN in_desired_expiration INT8,
+ IN in_reserve_gc_delay INT8,
+ IN in_now INT8,
+ IN in_open_fee taler_amount,
+ OUT out_open_cost taler_amount,
+ OUT out_final_expiration INT8,
+ OUT out_no_reserve BOOLEAN,
+ OUT out_no_funds BOOLEAN,
+ OUT out_reserve_balance taler_amount)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_balance taler_amount;
+ my_cost taler_amount;
+ my_cost_tmp INT8;
+ my_years_tmp INT4;
+ my_years INT4;
+ my_needs_update BOOL;
+ my_expiration_date INT8;
+ reserve RECORD;
+BEGIN
+
+SELECT current_balance
+ ,expiration_date
+ ,purses_allowed
+ INTO reserve
+ FROM reserves
+ WHERE reserve_pub=in_reserve_pub;
+
+IF NOT FOUND
+THEN
+ RAISE NOTICE 'reserve not found';
+ out_no_reserve = TRUE;
+ out_no_funds = TRUE;
+ out_reserve_balance.val = 0;
+ out_reserve_balance.frac = 0;
+ out_open_cost.val = 0;
+ out_open_cost.frac = 0;
+ out_final_expiration = 0;
+ RETURN;
+END IF;
+
+out_no_reserve = FALSE;
+out_reserve_balance = reserve.current_balance;
+
+-- Do not allow expiration time to start in the past already
+IF (reserve.expiration_date < in_now)
+THEN
+ my_expiration_date = in_now;
+ELSE
+ my_expiration_date = reserve.expiration_date;
+END IF;
+
+my_cost.val = 0;
+my_cost.frac = 0;
+my_needs_update = FALSE;
+my_years = 0;
+
+-- Compute years based on desired expiration time
+IF (my_expiration_date < in_desired_expiration)
+THEN
+ my_years = (31535999999999 + in_desired_expiration - my_expiration_date) / 31536000000000;
+ reserve.purses_allowed = in_default_purse_limit;
+ my_expiration_date = my_expiration_date + 31536000000000 * my_years;
+END IF;
+
+-- Increase years based on purses requested
+IF (reserve.purses_allowed < in_min_purse_limit)
+THEN
+ my_years = (31535999999999 + in_desired_expiration - in_now) / 31536000000000;
+ my_expiration_date = in_now + 31536000000000 * my_years;
+ my_years_tmp = (in_min_purse_limit + in_default_purse_limit - reserve.purses_allowed - 1) / in_default_purse_limit;
+ my_years = my_years + my_years_tmp;
+ reserve.purses_allowed = reserve.purses_allowed + (in_default_purse_limit * my_years_tmp);
+END IF;
+
+
+-- Compute cost based on annual fees
+IF (my_years > 0)
+THEN
+ my_cost.val = my_years * in_open_fee.val;
+ my_cost_tmp = my_years * in_open_fee.frac / 100000000;
+ IF (CAST (my_cost.val + my_cost_tmp AS INT8) < my_cost.val)
+ THEN
+ out_open_cost.val=9223372036854775807;
+ out_open_cost.frac=2147483647;
+ out_final_expiration=my_expiration_date;
+ out_no_funds=FALSE;
+ RAISE NOTICE 'arithmetic issue computing amount';
+ RETURN;
+ END IF;
+ my_cost.val = CAST (my_cost.val + my_cost_tmp AS INT8);
+ my_cost.frac = my_years * in_open_fee.frac % 100000000;
+ my_needs_update = TRUE;
+END IF;
+
+-- check if we actually have something to do
+IF NOT my_needs_update
+THEN
+ out_final_expiration = reserve.expiration_date;
+ out_open_cost.val = 0;
+ out_open_cost.frac = 0;
+ out_no_funds=FALSE;
+ RAISE NOTICE 'no change required';
+ RETURN;
+END IF;
+
+-- Check payment (coins and reserve) would be sufficient.
+IF ( (in_total_paid.val < my_cost.val) OR
+ ( (in_total_paid.val = my_cost.val) AND
+ (in_total_paid.frac < my_cost.frac) ) )
+THEN
+ out_open_cost.val = my_cost.val;
+ out_open_cost.frac = my_cost.frac;
+ out_no_funds=FALSE;
+ -- We must return a failure, which is indicated by
+ -- the expiration being below the desired expiration.
+ IF (reserve.expiration_date >= in_desired_expiration)
+ THEN
+ -- This case is relevant especially if the purse
+ -- count was to be increased and the payment was
+ -- insufficient to cover this for the full period.
+ RAISE NOTICE 'forcing low expiration time';
+ out_final_expiration = 0;
+ ELSE
+ out_final_expiration = reserve.expiration_date;
+ END IF;
+ RAISE NOTICE 'amount paid too low';
+ RETURN;
+END IF;
+
+-- Check reserve balance is sufficient.
+IF (out_reserve_balance.val > in_reserve_payment.val)
+THEN
+ IF (out_reserve_balance.frac >= in_reserve_payment.frac)
+ THEN
+ my_balance.val=out_reserve_balance.val - in_reserve_payment.val;
+ my_balance.frac=out_reserve_balance.frac - in_reserve_payment.frac;
+ ELSE
+ my_balance.val=out_reserve_balance.val - in_reserve_payment.val - 1;
+ my_balance.frac=out_reserve_balance.frac + 100000000 - in_reserve_payment.frac;
+ END IF;
+ELSE
+ IF (out_reserve_balance.val = in_reserve_payment.val) AND (out_reserve_balance.frac >= in_reserve_payment.frac)
+ THEN
+ my_balance.val=0;
+ my_balance.frac=out_reserve_balance.frac - in_reserve_payment.frac;
+ ELSE
+ out_final_expiration = reserve.expiration_date;
+ out_open_cost.val = my_cost.val;
+ out_open_cost.frac = my_cost.frac;
+ out_no_funds=TRUE;
+ RAISE NOTICE 'reserve balance too low';
+ RETURN;
+ END IF;
+END IF;
+
+UPDATE reserves SET
+ current_balance=my_balance
+ ,gc_date=reserve.expiration_date + in_reserve_gc_delay
+ ,expiration_date=my_expiration_date
+ ,purses_allowed=reserve.purses_allowed
+WHERE
+ reserve_pub=in_reserve_pub;
+
+out_final_expiration=my_expiration_date;
+out_open_cost = my_cost;
+out_no_funds=FALSE;
+RETURN;
+
+END $$;
diff --git a/src/exchangedb/exchange_do_reserve_open_deposit.sql b/src/exchangedb/exchange_do_reserve_open_deposit.sql
new file mode 100644
index 000000000..aa6f86a9b
--- /dev/null
+++ b/src/exchangedb/exchange_do_reserve_open_deposit.sql
@@ -0,0 +1,84 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE OR REPLACE FUNCTION exchange_do_reserve_open_deposit(
+ IN in_coin_pub BYTEA,
+ IN in_known_coin_id INT8,
+ IN in_coin_sig BYTEA,
+ IN in_reserve_sig BYTEA,
+ IN in_reserve_pub BYTEA,
+ IN in_coin_total taler_amount,
+ OUT out_insufficient_funds BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+INSERT INTO exchange.reserves_open_deposits
+ (reserve_sig
+ ,reserve_pub
+ ,coin_pub
+ ,coin_sig
+ ,contribution
+ )
+ VALUES
+ (in_reserve_sig
+ ,in_reserve_pub
+ ,in_coin_pub
+ ,in_coin_sig
+ ,in_coin_total
+ )
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Idempotent request known, return success.
+ out_insufficient_funds=FALSE;
+ RETURN;
+END IF;
+
+
+-- Check and update balance of the coin.
+UPDATE exchange.known_coins kc
+ SET
+ remaining.frac=(kc.remaining).frac-in_coin_total.frac
+ + CASE
+ WHEN (kc.remaining).frac < in_coin_total.frac
+ THEN 100000000
+ ELSE 0
+ END,
+ remaining.val=(kc.remaining).val-in_coin_total.val
+ - CASE
+ WHEN (kc.remaining).frac < in_coin_total.frac
+ THEN 1
+ ELSE 0
+ END
+ WHERE coin_pub=in_coin_pub
+ AND ( ((kc.remaining).val > in_coin_total.val) OR
+ ( ((kc.remaining).frac >= in_coin_total.frac) AND
+ ((kc.remaining).val >= in_coin_total.val) ) );
+
+IF NOT FOUND
+THEN
+ -- Insufficient balance.
+ out_insufficient_funds=TRUE;
+ RETURN;
+END IF;
+
+-- Everything fine, return success!
+out_insufficient_funds=FALSE;
+
+END $$;
diff --git a/src/exchangedb/exchange_do_reserve_purse.sql b/src/exchangedb/exchange_do_reserve_purse.sql
new file mode 100644
index 000000000..8ae652e69
--- /dev/null
+++ b/src/exchangedb/exchange_do_reserve_purse.sql
@@ -0,0 +1,162 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+CREATE OR REPLACE FUNCTION exchange_do_reserve_purse(
+ IN in_purse_pub BYTEA,
+ IN in_merge_sig BYTEA,
+ IN in_merge_timestamp INT8,
+ IN in_reserve_expiration INT8,
+ IN in_reserve_gc INT8,
+ IN in_reserve_sig BYTEA,
+ IN in_reserve_quota BOOLEAN,
+ IN in_purse_fee taler_amount,
+ IN in_reserve_pub BYTEA,
+ IN in_wallet_h_payto BYTEA,
+ OUT out_no_funds BOOLEAN,
+ OUT out_no_reserve BOOLEAN,
+ OUT out_conflict BOOLEAN)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+
+-- Store purse merge signature, checks for purse_pub uniqueness
+INSERT INTO purse_merges
+ (partner_serial_id
+ ,reserve_pub
+ ,purse_pub
+ ,merge_sig
+ ,merge_timestamp)
+ VALUES
+ (NULL
+ ,in_reserve_pub
+ ,in_purse_pub
+ ,in_merge_sig
+ ,in_merge_timestamp)
+ ON CONFLICT DO NOTHING;
+
+IF NOT FOUND
+THEN
+ -- Idempotency check: see if an identical record exists.
+ -- Note that by checking 'merge_sig', we implicitly check
+ -- identity over everything that the signature covers.
+ PERFORM
+ FROM purse_merges
+ WHERE purse_pub=in_purse_pub
+ AND merge_sig=in_merge_sig;
+ IF NOT FOUND
+ THEN
+ -- Purse was merged, but to some other reserve. Not allowed.
+ out_conflict=TRUE;
+ out_no_reserve=FALSE;
+ out_no_funds=FALSE;
+ RETURN;
+ END IF;
+
+ -- "success"
+ out_conflict=FALSE;
+ out_no_funds=FALSE;
+ out_no_reserve=FALSE;
+ RETURN;
+END IF;
+out_conflict=FALSE;
+
+PERFORM
+ FROM exchange.reserves
+ WHERE reserve_pub=in_reserve_pub;
+
+out_no_reserve = NOT FOUND;
+
+IF (in_reserve_quota)
+THEN
+ -- Increment active purses per reserve (and check this is allowed)
+ IF (out_no_reserve)
+ THEN
+ out_no_funds=TRUE;
+ RETURN;
+ END IF;
+ UPDATE exchange.reserves
+ SET purses_active=purses_active+1
+ WHERE reserve_pub=in_reserve_pub
+ AND purses_active < purses_allowed;
+ IF NOT FOUND
+ THEN
+ out_no_funds=TRUE;
+ RETURN;
+ END IF;
+ELSE
+ -- UPDATE reserves balance (and check if balance is enough to pay the fee)
+ IF (out_no_reserve)
+ THEN
+ IF ( (0 != in_purse_fee.val) OR
+ (0 != in_purse_fee.frac) )
+ THEN
+ out_no_funds=TRUE;
+ RETURN;
+ END IF;
+ INSERT INTO exchange.reserves
+ (reserve_pub
+ ,expiration_date
+ ,gc_date)
+ VALUES
+ (in_reserve_pub
+ ,in_reserve_expiration
+ ,in_reserve_gc);
+ ELSE
+ UPDATE exchange.reserves
+ SET
+ current_balance.frac=(current_balance).frac-in_purse_fee.frac
+ + CASE
+ WHEN (current_balance).frac < in_purse_fee.frac
+ THEN 100000000
+ ELSE 0
+ END,
+ current_balance.val=(current_balance).val-in_purse_fee.val
+ - CASE
+ WHEN (current_balance).frac < in_purse_fee.frac
+ THEN 1
+ ELSE 0
+ END
+ WHERE reserve_pub=in_reserve_pub
+ AND ( ((current_balance).val > in_purse_fee.val) OR
+ ( ((current_balance).frac >= in_purse_fee.frac) AND
+ ((current_balance).val >= in_purse_fee.val) ) );
+ IF NOT FOUND
+ THEN
+ out_no_funds=TRUE;
+ RETURN;
+ END IF;
+ END IF;
+END IF;
+
+out_no_funds=FALSE;
+
+
+-- Store account merge signature.
+INSERT INTO account_merges
+ (reserve_pub
+ ,reserve_sig
+ ,purse_pub
+ ,wallet_h_payto)
+ VALUES
+ (in_reserve_pub
+ ,in_reserve_sig
+ ,in_purse_pub
+ ,in_wallet_h_payto);
+
+END $$;
+
+COMMENT ON FUNCTION exchange_do_reserve_purse(BYTEA, BYTEA, INT8, INT8, INT8, BYTEA, BOOLEAN, taler_amount, BYTEA, BYTEA)
+ IS 'Create a purse for a reserve.';
diff --git a/src/exchangedb/exchange_do_reserves_in_insert.sql b/src/exchangedb/exchange_do_reserves_in_insert.sql
new file mode 100644
index 000000000..1be06f063
--- /dev/null
+++ b/src/exchangedb/exchange_do_reserves_in_insert.sql
@@ -0,0 +1,122 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+
+CREATE OR REPLACE FUNCTION exchange_do_array_reserves_insert(
+ IN in_gc_date INT8,
+ IN in_reserve_expiration INT8,
+ IN ina_reserve_pub BYTEA[],
+ IN ina_wire_ref INT8[],
+ IN ina_credit taler_amount[],
+ IN ina_exchange_account_name TEXT[],
+ IN ina_execution_date INT8[],
+ IN ina_wire_source_h_payto BYTEA[],
+ IN ina_payto_uri TEXT[],
+ IN ina_notify TEXT[])
+RETURNS SETOF exchange_do_array_reserve_insert_return_type
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ conflict BOOL;
+ dup BOOL;
+ uuid INT8;
+ i INT4;
+ ini_reserve_pub BYTEA;
+ ini_wire_ref INT8;
+ ini_credit taler_amount;
+ ini_exchange_account_name TEXT;
+ ini_execution_date INT8;
+ ini_wire_source_h_payto BYTEA;
+ ini_payto_uri TEXT;
+ ini_notify TEXT;
+BEGIN
+
+ FOR i IN 1..array_length(ina_reserve_pub,1)
+ LOOP
+ ini_reserve_pub = ina_reserve_pub[i];
+ ini_wire_ref = ina_wire_ref[i];
+ ini_credit = ina_credit[i];
+ ini_exchange_account_name = ina_exchange_account_name[i];
+ ini_execution_date = ina_execution_date[i];
+ ini_wire_source_h_payto = ina_wire_source_h_payto[i];
+ ini_payto_uri = ina_payto_uri[i];
+ ini_notify = ina_notify[i];
+
+-- RAISE WARNING 'Starting loop on %', ini_notify;
+
+ INSERT INTO wire_targets
+ (wire_target_h_payto
+ ,payto_uri
+ ) VALUES (
+ ini_wire_source_h_payto
+ ,ini_payto_uri
+ )
+ ON CONFLICT DO NOTHING;
+
+ INSERT INTO reserves
+ (reserve_pub
+ ,current_balance
+ ,expiration_date
+ ,gc_date
+ ) VALUES (
+ ini_reserve_pub
+ ,ini_credit
+ ,in_reserve_expiration
+ ,in_gc_date
+ )
+ ON CONFLICT DO NOTHING
+ RETURNING reserve_uuid
+ INTO uuid;
+ conflict = NOT FOUND;
+
+ INSERT INTO reserves_in
+ (reserve_pub
+ ,wire_reference
+ ,credit
+ ,exchange_account_section
+ ,wire_source_h_payto
+ ,execution_date
+ ) VALUES (
+ ini_reserve_pub
+ ,ini_wire_ref
+ ,ini_credit
+ ,ini_exchange_account_name
+ ,ini_wire_source_h_payto
+ ,ini_execution_date
+ )
+ ON CONFLICT DO NOTHING;
+
+ IF NOT FOUND
+ THEN
+ IF conflict
+ THEN
+ dup = TRUE;
+ else
+ dup = FALSE;
+ END IF;
+ ELSE
+ IF NOT conflict
+ THEN
+ EXECUTE FORMAT (
+ 'NOTIFY %s'
+ ,ini_notify);
+ END IF;
+ dup = FALSE;
+ END IF;
+ RETURN NEXT (dup,uuid);
+ END LOOP;
+ RETURN;
+END $$;
diff --git a/src/exchangedb/exchange_do_select_deposits_missing_wire.sql b/src/exchangedb/exchange_do_select_deposits_missing_wire.sql
new file mode 100644
index 000000000..3d44a58c9
--- /dev/null
+++ b/src/exchangedb/exchange_do_select_deposits_missing_wire.sql
@@ -0,0 +1,73 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+-- @author: Christian Grothoff
+
+CREATE OR REPLACE FUNCTION exchange_do_select_deposits_missing_wire(
+ IN in_min_serial_id INT8)
+RETURNS SETOF exchange_do_select_deposits_missing_wire_return_type
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ missing CURSOR
+ FOR
+ SELECT
+ batch_deposit_serial_id
+ ,wire_target_h_payto
+ ,wire_deadline
+ FROM batch_deposits
+ WHERE batch_deposit_serial_id > in_min_serial_id
+ ORDER BY batch_deposit_serial_id ASC;
+DECLARE
+ my_total_val INT8; -- all deposits without wire
+DECLARE
+ my_total_frac INT8; -- all deposits without wire (fraction, not normalized)
+DECLARE
+ my_total taler_amount; -- amount that was originally deposited
+DECLARE
+ my_batch_record RECORD;
+DECLARE
+ i RECORD;
+BEGIN
+
+OPEN missing;
+LOOP
+ FETCH NEXT FROM missing INTO i;
+ EXIT WHEN NOT FOUND;
+
+ SELECT
+ SUM((cdep.amount_with_fee).val) AS total_val
+ ,SUM((cdep.amount_with_fee).frac::INT8) AS total_frac
+ INTO
+ my_batch_record
+ FROM coin_deposits cdep
+ WHERE cdep.batch_deposit_serial_id = i.batch_deposit_serial_id;
+
+ my_total_val=my_batch_record.total_val;
+ my_total_frac=my_batch_record.total_frac;
+
+ -- Normalize total amount
+ my_total.val = my_total_val + my_total_frac / 100000000;
+ my_total.frac = my_total_frac % 100000000;
+ RETURN NEXT (
+ i.batch_deposit_serial_id
+ ,my_total
+ ,i.wire_target_h_payto
+ ,i.wire_deadline);
+
+END LOOP;
+CLOSE missing;
+RETURN;
+END $$;
diff --git a/src/exchangedb/exchange_do_select_justification_for_missing_wire.sql b/src/exchangedb/exchange_do_select_justification_for_missing_wire.sql
new file mode 100644
index 000000000..f02a51d3d
--- /dev/null
+++ b/src/exchangedb/exchange_do_select_justification_for_missing_wire.sql
@@ -0,0 +1,102 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2023 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+-- @author: Christian Grothoff
+
+CREATE OR REPLACE FUNCTION exchange_do_select_justification_missing_wire(
+ IN in_wire_target_h_payto BYTEA,
+ IN in_current_time INT8,
+ OUT out_payto_uri TEXT, -- NULL allowed
+ OUT out_kyc_pending TEXT, -- NULL allowed
+ OUT out_aml_status INT4, -- NULL allowed
+ OUT out_aml_limit taler_amount) -- NULL allowed!
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_required_checks TEXT[];
+DECLARE
+ my_aml_data RECORD;
+DECLARE
+ satisfied CURSOR FOR
+ SELECT satisfied_checks
+ FROM kyc_attributes
+ WHERE h_payto=in_wire_target_h_payto
+ AND expiration_time < in_current_time;
+DECLARE
+ i RECORD;
+BEGIN
+
+ -- Fetch payto URI
+ out_payto_uri = NULL;
+ SELECT payto_uri
+ INTO out_payto_uri
+ FROM wire_targets
+ WHERE wire_target_h_payto=my_wire_target_h_payto;
+
+ -- Check KYC status
+ my_required_checks = NULL;
+ SELECT string_to_array (required_checks, ' ')
+ INTO my_required_checks
+ FROM legitimization_requirements
+ WHERE h_payto=my_wire_target_h_payto;
+
+ -- Get last AML decision
+ SELECT
+ new_threshold
+ ,kyc_requirements
+ ,new_status
+ INTO
+ my_aml_data
+ FROM aml_history
+ WHERE h_payto=in_wire_target_h_payto
+ ORDER BY aml_history_serial_id -- get last decision
+ DESC LIMIT 1;
+ IF FOUND
+ THEN
+ out_aml_limit=my_aml_data.new_threshold;
+ out_aml_status=my_aml_data.kyc_status;
+ -- Combine KYC requirements
+ my_required_checks
+ = array_cat (my_required_checks,
+ my_aml_data.kyc_requirements);
+ ELSE
+ out_aml_limit=NULL;
+ out_aml_status=0; -- or NULL? Style question!
+ END IF;
+
+ OPEN satisfied;
+ LOOP
+ FETCH NEXT FROM satisfied INTO i;
+ EXIT WHEN NOT FOUND;
+
+ -- remove all satisfied checks from the list
+ FOR i in 1..array_length(i.satisfied_checks)
+ LOOP
+ my_required_checks
+ = array_remove (my_required_checks,
+ i.satisfied_checks[i]);
+ END LOOP;
+ END LOOP;
+
+ -- Return remaining required checks as one string
+ IF ( (my_required_checks IS NOT NULL) AND
+ (0 < array_length(my_satisfied_checks)) )
+ THEN
+ out_kyc_pending
+ = array_to_string (my_required_checks, ' ');
+ END IF;
+
+ RETURN;
+END $$;
diff --git a/src/exchangedb/exchangedb-postgres.conf b/src/exchangedb/exchangedb-postgres.conf
index 7d600586f..726d28576 100644
--- a/src/exchangedb/exchangedb-postgres.conf
+++ b/src/exchangedb/exchangedb-postgres.conf
@@ -1,6 +1,9 @@
[exchangedb-postgres]
-CONFIG = "postgres:///taler"
+CONFIG = "postgres:///taler-exchange"
# Where are the SQL files to setup our tables?
# Important: this MUST end with a "/"!
-SQL_DIR = $DATADIR/sql/exchange/
+SQL_DIR = ${DATADIR}sql/exchange/
+
+# Number of purses per account by default.
+DEFAULT_PURSE_LIMIT = 1 \ No newline at end of file
diff --git a/src/exchangedb/exchangedb.conf b/src/exchangedb/exchangedb.conf
index 77748bf5a..2bfcb2ca0 100644
--- a/src/exchangedb/exchangedb.conf
+++ b/src/exchangedb/exchangedb.conf
@@ -26,3 +26,11 @@ IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
# After how long do we forget about reserves? Should be above
# the legal expiration timeframe of withdrawn coins.
LEGAL_RESERVE_EXPIRATION_TIME = 7 years
+
+# What is the desired delay between a transaction being ready and the
+# aggregator triggering on it?
+AGGREGATOR_SHIFT = 1 s
+
+# How many concurrent purses may be opened by a reserve
+# if the reserve is paid for a year?
+DEFAULT_PURSE_LIMIT = 1 \ No newline at end of file
diff --git a/src/exchangedb/exchangedb_accounts.c b/src/exchangedb/exchangedb_accounts.c
index e41074822..e668134e1 100644
--- a/src/exchangedb/exchangedb_accounts.c
+++ b/src/exchangedb/exchangedb_accounts.c
@@ -199,7 +199,6 @@ add_account_cb (void *cls,
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
section,
"PAYTO_URI");
- lc->res = GNUNET_SYSERR;
return;
}
method = TALER_payto_get_method (payto_uri);
diff --git a/src/exchangedb/exchangedb_plugin.c b/src/exchangedb/exchangedb_plugin.c
index 21bb032f3..5dc41d988 100644
--- a/src/exchangedb/exchangedb_plugin.c
+++ b/src/exchangedb/exchangedb_plugin.c
@@ -24,12 +24,6 @@
#include <ltdl.h>
-/**
- * Initialize the plugin.
- *
- * @param cfg configuration to use
- * @return #GNUNET_OK on success
- */
struct TALER_EXCHANGEDB_Plugin *
TALER_EXCHANGEDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
@@ -62,11 +56,6 @@ TALER_EXCHANGEDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg)
}
-/**
- * Shutdown the plugin.
- *
- * @param plugin the plugin to unload
- */
void
TALER_EXCHANGEDB_plugin_unload (struct TALER_EXCHANGEDB_Plugin *plugin)
{
diff --git a/src/exchangedb/exchangedb_transactions.c b/src/exchangedb/exchangedb_transactions.c
index aaf28a9ea..f78393776 100644
--- a/src/exchangedb/exchangedb_transactions.c
+++ b/src/exchangedb/exchangedb_transactions.c
@@ -22,17 +22,7 @@
#include "taler_exchangedb_lib.h"
-/**
- * Calculate the total value of all transactions performed.
- * Stores @a off plus the cost of all transactions in @a tl
- * in @a ret.
- *
- * @param tl transaction list to process
- * @param off offset to use as the starting value
- * @param[out] ret where the resulting total is to be stored (may alias @a off)
- * @return #GNUNET_OK on success, #GNUNET_SYSERR on errors
- */
-int
+enum GNUNET_GenericReturnValue
TALER_EXCHANGEDB_calculate_transaction_list_totals (
struct TALER_EXCHANGEDB_TransactionList *tl,
const struct TALER_Amount *off,
@@ -129,6 +119,49 @@ TALER_EXCHANGEDB_calculate_transaction_list_totals (
return GNUNET_SYSERR;
}
break;
+ case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
+ /* spent += pos->amount_with_fee */
+ if (0 >
+ TALER_amount_add (&spent,
+ &spent,
+ &pos->details.purse_deposit->amount))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ deposit_fee = pos->details.purse_deposit->deposit_fee;
+ break;
+ case TALER_EXCHANGEDB_TT_PURSE_REFUND:
+ /* refunded += pos->refund_amount - pos->refund_fee */
+ if (0 >
+ TALER_amount_add (&refunded,
+ &refunded,
+ &pos->details.purse_refund->refund_amount))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_add (&spent,
+ &spent,
+ &pos->details.purse_refund->refund_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ have_refund = true;
+ break;
+ case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
+ /* spent += pos->amount_with_fee */
+ if (0 >
+ TALER_amount_add (&spent,
+ &spent,
+ &pos->details.reserve_open->coin_contribution))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ break;
}
}
if (have_refund)
diff --git a/src/exchangedb/irbt_callbacks.c b/src/exchangedb/irbt_callbacks.c
deleted file mode 100644
index 63d64f312..000000000
--- a/src/exchangedb/irbt_callbacks.c
+++ /dev/null
@@ -1,653 +0,0 @@
-/*
- This file is part of GNUnet
- Copyright (C) 2020 Taler Systems SA
-
- GNUnet is free software: you can redistribute it and/or modify it
- under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License,
- or (at your option) any later version.
-
- GNUnet is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- SPDX-License-Identifier: AGPL3.0-or-later
- */
-/**
- * @file exchangedb/irbt_callbacks.c
- * @brief callbacks used by postgres_insert_records_by_table, to be
- * inlined into the plugin
- * @author Christian Grothoff
- */
-
-
-/**
- * Function called with denominations records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_denominations (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_HashCode denom_hash;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (&denom_hash),
- GNUNET_PQ_query_param_rsa_public_key (
- td->details.denominations.denom_pub.rsa_public_key),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.denominations.master_sig),
- TALER_PQ_query_param_absolute_time (
- &td->details.denominations.valid_from),
- TALER_PQ_query_param_absolute_time (
- &td->details.denominations.expire_withdraw),
- TALER_PQ_query_param_absolute_time (
- &td->details.denominations.expire_deposit),
- TALER_PQ_query_param_absolute_time (
- &td->details.denominations.expire_legal),
- TALER_PQ_query_param_amount (&td->details.denominations.coin),
- TALER_PQ_query_param_amount (
- &td->details.denominations.fee_withdraw),
- TALER_PQ_query_param_amount (
- &td->details.denominations.fee_deposit),
- TALER_PQ_query_param_amount (
- &td->details.denominations.fee_refresh),
- TALER_PQ_query_param_amount (
- &td->details.denominations.fee_refund),
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_CRYPTO_rsa_public_key_hash (
- td->details.denominations.denom_pub.rsa_public_key,
- &denom_hash);
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_denominations",
- params);
-}
-
-
-/**
- * Function called with denomination_revocations records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_denomination_revocations (
- struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.denomination_revocations.master_sig),
- GNUNET_PQ_query_param_uint64 (
- &td->details.denomination_revocations.denominations_serial),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_denomination_revocations",
- params);
-}
-
-
-/**
- * Function called with reserves records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_reserves (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (&td->details.reserves.reserve_pub),
- GNUNET_PQ_query_param_string (td->details.reserves.account_details),
- TALER_PQ_query_param_amount (&td->details.reserves.current_balance),
- TALER_PQ_query_param_absolute_time (&td->details.reserves.expiration_date),
- TALER_PQ_query_param_absolute_time (&td->details.reserves.gc_date),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_reserves",
- params);
-}
-
-
-/**
- * Function called with reserves_in records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_reserves_in (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_uint64 (&td->details.reserves_in.wire_reference),
- TALER_PQ_query_param_amount (&td->details.reserves_in.credit),
- GNUNET_PQ_query_param_string (
- td->details.reserves_in.sender_account_details),
- GNUNET_PQ_query_param_string (
- td->details.reserves_in.exchange_account_section),
- TALER_PQ_query_param_absolute_time (
- &td->details.reserves_in.execution_date),
- GNUNET_PQ_query_param_uint64 (&td->details.reserves_in.reserve_uuid),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_reserves_in",
- params);
-}
-
-
-/**
- * Function called with reserves_close records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_reserves_close (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- TALER_PQ_query_param_absolute_time (
- &td->details.reserves_close.execution_date),
- GNUNET_PQ_query_param_auto_from_type (&td->details.reserves_close.wtid),
- GNUNET_PQ_query_param_string (td->details.reserves_close.receiver_account),
- TALER_PQ_query_param_amount (&td->details.reserves_close.amount),
- TALER_PQ_query_param_amount (&td->details.reserves_close.closing_fee),
- GNUNET_PQ_query_param_uint64 (&td->details.reserves_close.reserve_uuid),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_reserves_close",
- params);
-}
-
-
-/**
- * Function called with reserves_out records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_reserves_out (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (&td->details.reserves_out.h_blind_ev),
- GNUNET_PQ_query_param_rsa_signature (
- td->details.reserves_out.denom_sig.rsa_signature),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.reserves_out.reserve_sig),
- TALER_PQ_query_param_absolute_time (
- &td->details.reserves_out.execution_date),
- TALER_PQ_query_param_amount (&td->details.reserves_out.amount_with_fee),
- GNUNET_PQ_query_param_uint64 (&td->details.reserves_out.reserve_uuid),
- GNUNET_PQ_query_param_uint64 (
- &td->details.reserves_out.denominations_serial),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_reserves_out",
- params);
-}
-
-
-/**
- * Function called with auditors records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_auditors (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- uint8_t is_active = td->details.auditors.is_active ? 1 : 0;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (&td->details.auditors.auditor_pub),
- GNUNET_PQ_query_param_string (td->details.auditors.auditor_name),
- GNUNET_PQ_query_param_string (td->details.auditors.auditor_url),
- GNUNET_PQ_query_param_auto_from_type (&is_active),
- GNUNET_PQ_query_param_absolute_time (&td->details.auditors.last_change),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_auditors",
- params);
-}
-
-
-/**
- * Function called with auditor_denom_sigs records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_auditor_denom_sigs (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_uint64 (&td->details.auditor_denom_sigs.auditor_uuid),
- GNUNET_PQ_query_param_uint64 (
- &td->details.auditor_denom_sigs.denominations_serial),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.auditor_denom_sigs.auditor_sig),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_auditor_denom_sigs",
- params);
-}
-
-
-/**
- * Function called with exchange_sign_keys records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_exchange_sign_keys (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.exchange_sign_keys.exchange_pub),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.exchange_sign_keys.master_sig),
- TALER_PQ_query_param_absolute_time (
- &td->details.exchange_sign_keys.meta.start),
- TALER_PQ_query_param_absolute_time (
- &td->details.exchange_sign_keys.meta.expire_sign),
- TALER_PQ_query_param_absolute_time (
- &td->details.exchange_sign_keys.meta.expire_legal),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_exchange_sign_keys",
- params);
-}
-
-
-/**
- * Function called with signkey_revocations records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_signkey_revocations (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_uint64 (&td->details.signkey_revocations.esk_serial),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.signkey_revocations.master_sig),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_signkey_revocations",
- params);
-}
-
-
-/**
- * Function called with known_coins records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_known_coins (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (&td->details.known_coins.coin_pub),
- GNUNET_PQ_query_param_rsa_signature (
- td->details.known_coins.denom_sig.rsa_signature),
- GNUNET_PQ_query_param_uint64 (
- &td->details.known_coins.denominations_serial),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_known_coins",
- params);
-}
-
-
-/**
- * Function called with refresh_commitments records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_refresh_commitments (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (&td->details.refresh_commitments.rc),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.refresh_commitments.old_coin_sig),
- TALER_PQ_query_param_amount (
- &td->details.refresh_commitments.amount_with_fee),
- GNUNET_PQ_query_param_uint32 (
- &td->details.refresh_commitments.noreveal_index),
- GNUNET_PQ_query_param_uint64 (
- &td->details.refresh_commitments.old_known_coin_id),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_refresh_commitments",
- params);
-}
-
-
-/**
- * Function called with refresh_revealed_coins records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_refresh_revealed_coins (
- struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_HashCode h_coin_ev;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_uint32 (
- &td->details.refresh_revealed_coins.freshcoin_index),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.refresh_revealed_coins.link_sig),
- GNUNET_PQ_query_param_fixed_size (
- td->details.refresh_revealed_coins.coin_ev,
- td->details.refresh_revealed_coins.
- coin_ev_size),
- GNUNET_PQ_query_param_auto_from_type (&h_coin_ev),
- GNUNET_PQ_query_param_rsa_signature (
- td->details.refresh_revealed_coins.ev_sig.rsa_signature),
- GNUNET_PQ_query_param_uint64 (
- &td->details.refresh_revealed_coins.denominations_serial),
- GNUNET_PQ_query_param_uint64 (
- &td->details.refresh_revealed_coins.melt_serial_id),
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_CRYPTO_hash (td->details.refresh_revealed_coins.coin_ev,
- td->details.refresh_revealed_coins.coin_ev_size,
- &h_coin_ev);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_refresh_revealed_coins",
- params);
-}
-
-
-/**
- * Function called with refresh_transfer_keys records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_refresh_transfer_keys (
- struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.refresh_transfer_keys.tp),
- GNUNET_PQ_query_param_fixed_size (
- &td->details.refresh_transfer_keys.tprivs[0],
- (TALER_CNC_KAPPA - 1)
- * sizeof (struct TALER_TransferPrivateKeyP)),
- GNUNET_PQ_query_param_uint64 (
- &td->details.refresh_transfer_keys.melt_serial_id),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_refresh_transfer_keys",
- params);
-}
-
-
-/**
- * Function called with deposits records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_deposits (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- uint8_t tiny = td->details.deposits.tiny ? 1 : 0;
- uint8_t done = td->details.deposits.done ? 1 : 0;
- struct GNUNET_HashCode h_wire;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- TALER_PQ_query_param_amount (&td->details.deposits.amount_with_fee),
- TALER_PQ_query_param_absolute_time (&td->details.deposits.wallet_timestamp),
- TALER_PQ_query_param_absolute_time (
- &td->details.deposits.exchange_timestamp),
- TALER_PQ_query_param_absolute_time (&td->details.deposits.refund_deadline),
- TALER_PQ_query_param_absolute_time (&td->details.deposits.wire_deadline),
- GNUNET_PQ_query_param_auto_from_type (&td->details.deposits.merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.deposits.h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (&h_wire),
- GNUNET_PQ_query_param_auto_from_type (&td->details.deposits.coin_sig),
- TALER_PQ_query_param_json (td->details.deposits.wire),
- GNUNET_PQ_query_param_auto_from_type (&tiny),
- GNUNET_PQ_query_param_auto_from_type (&done),
- GNUNET_PQ_query_param_uint64 (&td->details.deposits.known_coin_id),
- GNUNET_PQ_query_param_end
- };
-
- TALER_JSON_merchant_wire_signature_hash (td->details.deposits.wire,
- &h_wire);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_deposits",
- params);
-}
-
-
-/**
- * Function called with refunds records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_refunds (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (&td->details.refunds.merchant_sig),
- GNUNET_PQ_query_param_uint64 (&td->details.refunds.rtransaction_id),
- TALER_PQ_query_param_amount (&td->details.refunds.amount_with_fee),
- GNUNET_PQ_query_param_uint64 (&td->details.refunds.deposit_serial_id),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_refunds",
- params);
-}
-
-
-/**
- * Function called with wire_out records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_wire_out (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- TALER_PQ_query_param_absolute_time (&td->details.wire_out.execution_date),
- GNUNET_PQ_query_param_auto_from_type (&td->details.wire_out.wtid_raw),
- TALER_PQ_query_param_json (td->details.wire_out.wire_target),
- GNUNET_PQ_query_param_string (
- td->details.wire_out.exchange_account_section),
- TALER_PQ_query_param_amount (&td->details.wire_out.amount),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_wire_out",
- params);
-}
-
-
-/**
- * Function called with aggregation_tracking records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_aggregation_tracking (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_uint64 (
- &td->details.aggregation_tracking.deposit_serial_id),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.aggregation_tracking.wtid_raw),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_aggregation_tracking",
- params);
-}
-
-
-/**
- * Function called with wire_fee records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_wire_fee (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_string (td->details.wire_fee.wire_method),
- TALER_PQ_query_param_absolute_time (&td->details.wire_fee.start_date),
- TALER_PQ_query_param_absolute_time (&td->details.wire_fee.end_date),
- TALER_PQ_query_param_amount (&td->details.wire_fee.wire_fee),
- TALER_PQ_query_param_amount (&td->details.wire_fee.closing_fee),
- GNUNET_PQ_query_param_auto_from_type (&td->details.wire_fee.master_sig),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_wire_fee",
- params);
-}
-
-
-/**
- * Function called with recoup records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_recoup (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (&td->details.recoup.coin_sig),
- GNUNET_PQ_query_param_auto_from_type (&td->details.recoup.coin_blind),
- TALER_PQ_query_param_amount (&td->details.recoup.amount),
- TALER_PQ_query_param_absolute_time (&td->details.recoup.timestamp),
- GNUNET_PQ_query_param_uint64 (&td->details.recoup.known_coin_id),
- GNUNET_PQ_query_param_uint64 (&td->details.recoup.reserve_out_serial_id),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_recoup",
- params);
-}
-
-
-/**
- * Function called with recoup_refresh records to insert into table.
- *
- * @param pg plugin context
- * @param td record to insert
- */
-static enum GNUNET_DB_QueryStatus
-irbt_cb_table_recoup_refresh (struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&td->serial),
- GNUNET_PQ_query_param_auto_from_type (&td->details.recoup_refresh.coin_sig),
- GNUNET_PQ_query_param_auto_from_type (
- &td->details.recoup_refresh.coin_blind),
- TALER_PQ_query_param_amount (&td->details.recoup_refresh.amount),
- TALER_PQ_query_param_absolute_time (&td->details.recoup_refresh.timestamp),
- GNUNET_PQ_query_param_uint64 (&td->details.recoup_refresh.known_coin_id),
- GNUNET_PQ_query_param_uint64 (&td->details.recoup_refresh.rrc_serial),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_into_table_recoup_refresh",
- params);
-}
-
-
-/* end of irbt_callbacks.c */
diff --git a/src/exchangedb/lrbt_callbacks.c b/src/exchangedb/lrbt_callbacks.c
deleted file mode 100644
index b5e53c831..000000000
--- a/src/exchangedb/lrbt_callbacks.c
+++ /dev/null
@@ -1,1201 +0,0 @@
-/*
- This file is part of GNUnet
- Copyright (C) 2020 Taler Systems SA
-
- GNUnet is free software: you can redistribute it and/or modify it
- under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License,
- or (at your option) any later version.
-
- GNUnet is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
- SPDX-License-Identifier: AGPL3.0-or-later
- */
-/**
- * @file exchangedb/lrbt_callbacks.c
- * @brief callbacks used by postgres_lookup_records_by_table, to be
- * inlined into the plugin
- * @author Christian Grothoff
- */
-
-
-/**
- * Function called with denominations table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_denominations (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_DENOMINATIONS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_rsa_public_key (
- "denom_pub",
- &td.details.denominations.denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &td.details.denominations.master_sig),
- TALER_PQ_result_spec_absolute_time ("valid_from",
- &td.details.denominations.valid_from),
- TALER_PQ_result_spec_absolute_time ("expire_withdraw",
- &td.details.denominations.
- expire_withdraw),
- TALER_PQ_result_spec_absolute_time ("expire_deposit",
- &td.details.denominations.
- expire_deposit),
- TALER_PQ_result_spec_absolute_time ("expire_legal",
- &td.details.denominations.expire_legal),
- TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
- &td.details.denominations.coin),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
- &td.details.denominations.fee_withdraw),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- &td.details.denominations.fee_deposit),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
- &td.details.denominations.fee_refresh),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
- &td.details.denominations.fee_refund),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with denomination_revocations table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_denomination_revocations (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_uint64 (
- "denominations_serial",
- &td.details.denomination_revocations.denominations_serial),
- GNUNET_PQ_result_spec_auto_from_type (
- "master_sig",
- &td.details.denomination_revocations.master_sig),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with reserves table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_reserves (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_RESERVES
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &td.details.reserves.reserve_pub),
- GNUNET_PQ_result_spec_string ("account_details",
- &td.details.reserves.account_details),
- TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
- &td.details.reserves.current_balance),
- TALER_PQ_result_spec_absolute_time ("expiration_date",
- &td.details.reserves.expiration_date),
- TALER_PQ_result_spec_absolute_time ("gc_date",
- &td.details.reserves.gc_date),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with reserves_in table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_reserves_in (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_RESERVES_IN
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_uint64 ("wire_reference",
- &td.details.reserves_in.wire_reference),
- TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
- &td.details.reserves_in.credit),
- GNUNET_PQ_result_spec_string ("sender_account_details",
- &td.details.reserves_in.
- sender_account_details),
- GNUNET_PQ_result_spec_string ("exchange_account_section",
- &td.details.reserves_in.
- exchange_account_section),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &td.details.reserves_in.execution_date),
- GNUNET_PQ_result_spec_uint64 ("reserve_uuid",
- &td.details.reserves_in.reserve_uuid),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with reserves_close table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_reserves_close (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_RESERVES_CLOSE
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- TALER_PQ_result_spec_absolute_time (
- "execution_date",
- &td.details.reserves_close.execution_date),
- GNUNET_PQ_result_spec_auto_from_type ("wtid",
- &td.details.reserves_close.wtid),
- GNUNET_PQ_result_spec_string (
- "receiver_account",
- &td.details.reserves_close.receiver_account),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &td.details.reserves_close.amount),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
- &td.details.reserves_close.closing_fee),
- GNUNET_PQ_result_spec_uint64 ("reserve_uuid",
- &td.details.reserves_close.reserve_uuid),
-
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with reserves_out table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_reserves_out (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_RESERVES_OUT
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
- &td.details.reserves_out.h_blind_ev),
- GNUNET_PQ_result_spec_rsa_signature (
- "denom_sig",
- &td.details.reserves_out.denom_sig.rsa_signature),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
- &td.details.reserves_out.reserve_sig),
- TALER_PQ_result_spec_absolute_time (
- "execution_date",
- &td.details.reserves_out.execution_date),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &td.details.reserves_out.amount_with_fee),
- GNUNET_PQ_result_spec_uint64 ("reserve_uuid",
- &td.details.reserves_out.reserve_uuid),
- GNUNET_PQ_result_spec_uint64 ("denominations_serial",
- &td.details.reserves_out.
- denominations_serial),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with auditors table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_auditors (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_AUDITORS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint8_t is_active8 = 0;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_auto_from_type ("auditor_pub",
- &td.details.auditors.auditor_pub),
- GNUNET_PQ_result_spec_string ("auditor_url",
- &td.details.auditors.auditor_url),
- GNUNET_PQ_result_spec_string ("auditor_name",
- &td.details.auditors.auditor_name),
- GNUNET_PQ_result_spec_auto_from_type ("is_active",
- &is_active8),
- TALER_PQ_result_spec_absolute_time ("last_change",
- &td.details.auditors.last_change),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- td.details.auditors.is_active = (0 != is_active8);
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with auditor_denom_sigs table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_auditor_denom_sigs (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 (
- "serial",
- &td.serial),
- GNUNET_PQ_result_spec_uint64 (
- "auditor_uuid",
- &td.details.auditor_denom_sigs.auditor_uuid),
- GNUNET_PQ_result_spec_uint64 (
- "denominations_serial",
- &td.details.auditor_denom_sigs.denominations_serial),
- GNUNET_PQ_result_spec_auto_from_type (
- "auditor_sig",
- &td.details.auditor_denom_sigs.auditor_sig),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with exchange_sign_keys table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_exchange_sign_keys (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
- &td.details.exchange_sign_keys.
- exchange_pub),
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &td.details.exchange_sign_keys.
- master_sig),
- TALER_PQ_result_spec_absolute_time ("valid_from",
- &td.details.exchange_sign_keys.meta.
- start),
- TALER_PQ_result_spec_absolute_time ("expire_sign",
- &td.details.exchange_sign_keys.meta.
- expire_sign),
- TALER_PQ_result_spec_absolute_time ("expire_legal",
- &td.details.exchange_sign_keys.meta.
- expire_legal),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with signkey_revocations table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_signkey_revocations (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_uint64 ("esk_serial",
- &td.details.signkey_revocations.esk_serial),
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &td.details.signkey_revocations.
- master_sig),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with known_coins table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_known_coins (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_KNOWN_COINS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &td.details.known_coins.coin_pub),
- GNUNET_PQ_result_spec_rsa_signature (
- "denom_sig",
- &td.details.known_coins.denom_sig.rsa_signature),
- GNUNET_PQ_result_spec_uint64 ("denominations_serial",
- &td.details.known_coins.denominations_serial),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with refresh_commitments table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_refresh_commitments (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 (
- "serial",
- &td.serial),
- GNUNET_PQ_result_spec_auto_from_type (
- "rc",
- &td.details.refresh_commitments.rc),
- GNUNET_PQ_result_spec_auto_from_type (
- "old_coin_sig",
- &td.details.refresh_commitments.old_coin_sig),
- TALER_PQ_RESULT_SPEC_AMOUNT (
- "amount_with_fee",
- &td.details.refresh_commitments.amount_with_fee),
- GNUNET_PQ_result_spec_uint32 (
- "noreveal_index",
- &td.details.refresh_commitments.noreveal_index),
- GNUNET_PQ_result_spec_uint64 (
- "old_known_coin_id",
- &td.details.refresh_commitments.old_known_coin_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with refresh_revealed_coins table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_refresh_revealed_coins (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 (
- "serial",
- &td.serial),
- GNUNET_PQ_result_spec_uint32 (
- "freshcoin_index",
- &td.details.refresh_revealed_coins.freshcoin_index),
- GNUNET_PQ_result_spec_auto_from_type (
- "link_sig",
- &td.details.refresh_revealed_coins.link_sig),
- GNUNET_PQ_result_spec_variable_size (
- "coin_ev",
- (void **) &td.details.refresh_revealed_coins.coin_ev,
- &td.details.refresh_revealed_coins.coin_ev_size),
- GNUNET_PQ_result_spec_rsa_signature (
- "ev_sig",
- &td.details.refresh_revealed_coins.ev_sig.rsa_signature),
- GNUNET_PQ_result_spec_uint64 (
- "denominations_serial",
- &td.details.refresh_revealed_coins.denominations_serial),
- GNUNET_PQ_result_spec_uint64 (
- "melt_serial_id",
- &td.details.refresh_revealed_coins.melt_serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with refresh_transfer_keys table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_refresh_transfer_keys (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- void *tpriv;
- size_t tpriv_size;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_auto_from_type ("transfer_pub",
- &td.details.refresh_transfer_keys.tp),
- GNUNET_PQ_result_spec_variable_size ("transfer_privs",
- &tpriv,
- &tpriv_size),
- GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
- &td.details.refresh_transfer_keys.
- melt_serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- /* Both conditions should be identical, but we conservatively also guard against
- unwarranted changes to the structure here. */
- if ( (tpriv_size !=
- sizeof (td.details.refresh_transfer_keys.tprivs)) ||
- (tpriv_size !=
- (TALER_CNC_KAPPA - 1) * sizeof (struct TALER_TransferPrivateKeyP)) )
- {
- GNUNET_break (0);
- GNUNET_PQ_cleanup_result (rs);
- ctx->error = true;
- return;
- }
- memcpy (&td.details.refresh_transfer_keys.tprivs[0],
- tpriv,
- tpriv_size);
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with deposits table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_deposits (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_DEPOSITS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint8_t tiny = 0; /* initialized to make compiler happy */
- uint8_t done = 0; /* initialized to make compiler happy */
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 (
- "serial",
- &td.serial),
- TALER_PQ_RESULT_SPEC_AMOUNT (
- "amount_with_fee",
- &td.details.deposits.amount_with_fee),
- TALER_PQ_result_spec_absolute_time (
- "wallet_timestamp",
- &td.details.deposits.wallet_timestamp),
- TALER_PQ_result_spec_absolute_time (
- "exchange_timestamp",
- &td.details.deposits.exchange_timestamp),
- TALER_PQ_result_spec_absolute_time (
- "refund_deadline",
- &td.details.deposits.refund_deadline),
- TALER_PQ_result_spec_absolute_time (
- "wire_deadline",
- &td.details.deposits.wire_deadline),
- GNUNET_PQ_result_spec_auto_from_type (
- "merchant_pub",
- &td.details.deposits.merchant_pub),
- GNUNET_PQ_result_spec_auto_from_type (
- "h_contract_terms",
- &td.details.deposits.h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type (
- "coin_sig",
- &td.details.deposits.coin_sig),
- TALER_PQ_result_spec_json (
- "wire",
- &td.details.deposits.wire),
- GNUNET_PQ_result_spec_auto_from_type (
- "tiny",
- &td.details.deposits.tiny),
- GNUNET_PQ_result_spec_auto_from_type (
- "done",
- &td.details.deposits.done),
- GNUNET_PQ_result_spec_uint64 (
- "known_coin_id",
- &td.details.deposits.known_coin_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- td.details.deposits.tiny = (0 != tiny);
- td.details.deposits.done = (0 != done);
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with refunds table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_refunds (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_REFUNDS
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
- &td.details.refunds.merchant_sig),
- GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
- &td.details.refunds.rtransaction_id),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &td.details.refunds.amount_with_fee),
- GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
- &td.details.refunds.deposit_serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with wire_out table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_wire_out (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_WIRE_OUT
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &td.details.wire_out.execution_date),
- GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
- &td.details.wire_out.wtid_raw),
- TALER_PQ_result_spec_json ("wire_target",
- &td.details.wire_out.wire_target),
- GNUNET_PQ_result_spec_string (
- "exchange_account_section",
- &td.details.wire_out.exchange_account_section),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &td.details.wire_out.amount),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with aggregation_tracking table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_aggregation_tracking (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_uint64 (
- "deposit_serial_id",
- &td.details.aggregation_tracking.deposit_serial_id),
- GNUNET_PQ_result_spec_auto_from_type (
- "wtid_raw",
- &td.details.aggregation_tracking.wtid_raw),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with wire_fee table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_wire_fee (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_WIRE_FEE
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_string ("wire_method",
- &td.details.wire_fee.wire_method),
- TALER_PQ_result_spec_absolute_time ("start_date",
- &td.details.wire_fee.start_date),
- TALER_PQ_result_spec_absolute_time ("end_date",
- &td.details.wire_fee.end_date),
- TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
- &td.details.wire_fee.wire_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
- &td.details.wire_fee.closing_fee),
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &td.details.wire_fee.master_sig),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with recoup table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_recoup (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_RECOUP
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &td.details.recoup.coin_sig),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &td.details.recoup.coin_blind),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &td.details.recoup.amount),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &td.details.recoup.timestamp),
- GNUNET_PQ_result_spec_uint64 ("known_coin_id",
- &td.details.recoup.known_coin_id),
- GNUNET_PQ_result_spec_uint64 ("reserve_out_serial_id",
- &td.details.recoup.reserve_out_serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called with recoup_refresh table entries.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-lrbt_cb_table_recoup_refresh (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LookupRecordsByTableContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
- struct TALER_EXCHANGEDB_TableData td = {
- .table = TALER_EXCHANGEDB_RT_RECOUP_REFRESH
- };
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- &td.serial),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &td.details.recoup_refresh.coin_sig),
- GNUNET_PQ_result_spec_auto_from_type (
- "coin_blind",
- &td.details.recoup_refresh.coin_blind),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &td.details.recoup_refresh.amount),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &td.details.recoup_refresh.timestamp),
- GNUNET_PQ_result_spec_uint64 ("known_coin_id",
- &td.details.recoup_refresh.known_coin_id),
- GNUNET_PQ_result_spec_uint64 ("rrc_serial",
- &td.details.recoup_refresh.rrc_serial),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->error = true;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &td);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/* end of lrbt_callbacks.c */
diff --git a/src/exchangedb/perf-exchangedb-reserves-in-insert-postgres b/src/exchangedb/perf-exchangedb-reserves-in-insert-postgres
new file mode 100755
index 000000000..8eafde3ce
--- /dev/null
+++ b/src/exchangedb/perf-exchangedb-reserves-in-insert-postgres
@@ -0,0 +1,210 @@
+#! /bin/bash
+
+# perf-exchangedb-reserves-in-insert-postgres - temporary wrapper script for .libs/perf-exchangedb-reserves-in-insert-postgres
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
+#
+# The perf-exchangedb-reserves-in-insert-postgres program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' libtalerexchangedb.la ../../src/json/libtalerjson.la ../../src/util/libtalerutil.la ../../src/pq/libtalerpq.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "perf-exchangedb-reserves-in-insert-postgres:perf-exchangedb-reserves-in-insert-postgres:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "perf-exchangedb-reserves-in-insert-postgres:perf-exchangedb-reserves-in-insert-postgres:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "perf-exchangedb-reserves-in-insert-postgres:perf-exchangedb-reserves-in-insert-postgres:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='perf-exchangedb-reserves-in-insert-postgres'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/priscilla/exchange/src/exchangedb/.libs:/home/priscilla/exchange/src/json/.libs:/home/priscilla/exchange/src/util/.libs:/home/priscilla/exchange/src/pq/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/exchangedb/perf_deposits_get_ready.c b/src/exchangedb/perf_deposits_get_ready.c
new file mode 100644
index 000000000..005ea6843
--- /dev/null
+++ b/src/exchangedb/perf_deposits_get_ready.c
@@ -0,0 +1,565 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchangedb/perf_deposits_get_ready.c
+ * @brief benchmark for deposits_get_ready
+git * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "math.h"
+
+/**
+ * Global result from the testcase.
+ */
+static int result;
+
+/**
+ * Report line of error if @a cond is true, and jump to label "drop".
+ */
+#define FAILIF(cond) \
+ do { \
+ if (! (cond)) {break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
+
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, \
+ sizeof (*ptr))
+
+/**
+ * Initializes @a ptr with zeros.
+ */
+#define ZR_BLK(ptr) \
+ memset (ptr, 0, sizeof (*ptr))
+
+/**
+ * Currency we use. Must match test-exchange-db-*.conf.
+ */
+#define CURRENCY "EUR"
+#define RSA_KEY_SIZE 1024
+#define NUM_ROWS 1000
+#define ROUNDS 100
+#define MELT_NEW_COINS 5
+#define MELT_NOREVEAL_INDEX 1
+
+/**
+ * Database plugin under test.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+
+static struct TALER_DenomFeeSet fees;
+
+static struct TALER_MerchantWireHashP h_wire_wt;
+
+/**
+ * Denomination keys used for fresh coins in melt test.
+ */
+static struct DenomKeyPair **new_dkp;
+
+static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins;
+
+struct DenomKeyPair
+{
+ struct TALER_DenominationPrivateKey priv;
+ struct TALER_DenominationPublicKey pub;
+};
+
+
+/**
+ * Destroy a denomination key pair. The key is not necessarily removed from the DB.
+ *
+ * @param dkp the key pair to destroy
+ */
+static void
+destroy_denom_key_pair (struct DenomKeyPair *dkp)
+{
+ TALER_denom_pub_free (&dkp->pub);
+ TALER_denom_priv_free (&dkp->priv);
+ GNUNET_free (dkp);
+}
+
+
+/**
+ * Create a denomination key pair by registering the denomination in the DB.
+ *
+ * @param size the size of the denomination key
+ * @param now time to use for key generation, legal expiration will be 3h later.
+ * @param fees fees to use
+ * @return the denominaiton key pair; NULL upon error
+ */
+static struct DenomKeyPair *
+create_denom_key_pair (unsigned int size,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_Amount *value,
+ const struct TALER_DenomFeeSet *fees)
+{
+ struct DenomKeyPair *dkp;
+ struct TALER_EXCHANGEDB_DenominationKey dki;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
+
+ dkp = GNUNET_new (struct DenomKeyPair);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_priv_create (&dkp->priv,
+ &dkp->pub,
+ GNUNET_CRYPTO_BSA_RSA,
+ size));
+ memset (&dki,
+ 0,
+ sizeof (struct TALER_EXCHANGEDB_DenominationKey));
+ dki.denom_pub = dkp->pub;
+ dki.issue.start = now;
+ dki.issue.expire_withdraw
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_UNIT_HOURS));
+ dki.issue.expire_deposit
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 2)));
+ dki.issue.expire_legal
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 3)));
+ dki.issue.value = *value;
+ dki.issue.fees = *fees;
+ TALER_denom_pub_hash (&dkp->pub,
+ &dki.issue.denom_hash);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_denomination_info (plugin->cls,
+ &dki.denom_pub,
+ &dki.issue))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ memset (&issue2, 0, sizeof (issue2));
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_denomination_info (plugin->cls,
+ &dki.issue.denom_hash,
+ &issue2))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ if (0 != GNUNET_memcmp (&dki.issue,
+ &issue2))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ return dkp;
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+
+static void
+run (void *cls)
+{
+ struct TALER_EXCHANGEDB_Refresh refresh;
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ const uint32_t num_partitions = 10;
+ struct TALER_Amount value;
+ struct TALER_EXCHANGEDB_CollectableBlindcoin cbc;
+ struct TALER_DenominationPublicKey *new_denom_pubs = NULL;
+ struct GNUNET_TIME_Relative times = GNUNET_TIME_UNIT_ZERO;
+ unsigned long long sqrs = 0;
+ struct TALER_EXCHANGEDB_CoinDepositInformation *depos;
+ struct TALER_EXCHANGEDB_BatchDeposit bd;
+ struct TALER_EXCHANGEDB_Refund *ref;
+ unsigned int *perm;
+ unsigned long long duration_sq;
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = GNUNET_CRYPTO_BSA_RSA,
+ .rc = 0
+ };
+ struct TALER_ExchangeWithdrawValues alg_values = {
+ .blinding_inputs = &bi
+ };
+
+ ref = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_Refund);
+ depos = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_CoinDepositInformation);
+
+ if (NULL ==
+ (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_break (0);
+ result = 77;
+ return;
+ }
+ (void) plugin->drop_tables (plugin->cls);
+ if (GNUNET_OK !=
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
+ {
+ GNUNET_break (0);
+ result = 77;
+ goto cleanup;
+ }
+ if (GNUNET_OK !=
+ plugin->preflight (plugin->cls))
+ {
+ GNUNET_break (0);
+ goto cleanup;
+ }
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.000010",
+ &value));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.withdraw));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.deposit));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.refresh));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.refund));
+ {
+ ZR_BLK (&cbc);
+ new_dkp = GNUNET_new_array (MELT_NEW_COINS,
+ struct DenomKeyPair *);
+ new_denom_pubs = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_DenominationPublicKey);
+ revealed_coins
+ = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin);
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_CRYPTO_RsaBlindedMessage *rp;
+ struct TALER_BlindedPlanchet *bp;
+
+ now = GNUNET_TIME_timestamp_get ();
+ new_dkp[cnt] = create_denom_key_pair (RSA_KEY_SIZE,
+ now,
+ &value,
+ &fees);
+ GNUNET_assert (NULL != new_dkp[cnt]);
+ new_denom_pubs[cnt] = new_dkp[cnt]->pub;
+ ccoin = &revealed_coins[cnt];
+ bp = &ccoin->blinded_planchet;
+ bp->blinded_message = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
+ bp->blinded_message->cipher = GNUNET_CRYPTO_BSA_RSA;
+ bp->blinded_message->rc = 1;
+ rp = &bp->blinded_message->details.rsa_blinded_message;
+ rp->blinded_msg_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ (RSA_KEY_SIZE / 8) - 1);
+ rp->blinded_msg = GNUNET_malloc (rp->blinded_msg_size);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ rp->blinded_msg,
+ rp->blinded_msg_size);
+ TALER_denom_pub_hash (&new_dkp[cnt]->pub,
+ &ccoin->h_denom_pub);
+ ccoin->exchange_vals = alg_values;
+ TALER_coin_ev_hash (bp,
+ &ccoin->h_denom_pub,
+ &ccoin->coin_envelope_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sign_blinded (&ccoin->coin_sig,
+ &new_dkp[cnt]->priv,
+ true,
+ bp));
+ TALER_coin_ev_hash (bp,
+ &cbc.denom_pub_hash,
+ &cbc.h_coin_envelope);
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_denom_sign_blinded (
+ &cbc.sig,
+ &new_dkp[cnt]->priv,
+ false,
+ bp));
+ }
+ }
+ perm = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_NONCE,
+ NUM_ROWS);
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "Transaction"));
+ for (unsigned int j = 0; j < NUM_ROWS; j++)
+ {
+ /*** NEED TO INSERT REFRESH COMMITMENTS + ENSURECOIN ***/
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct GNUNET_TIME_Timestamp deadline;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_CoinPubHashP c_hash;
+ unsigned int k = (unsigned int) rand () % 5;
+ unsigned int i = perm[j];
+
+ if (i >= ROUNDS)
+ i = ROUNDS; /* throw-away slot, do not keep around */
+ RND_BLK (&coin_pub);
+ RND_BLK (&c_hash);
+ RND_BLK (&reserve_pub);
+ RND_BLK (&cbc.reserve_sig);
+ TALER_denom_pub_hash (&new_dkp[k]->pub,
+ &cbc.denom_pub_hash);
+ deadline = GNUNET_TIME_timestamp_get ();
+ depos[i].coin.coin_pub = coin_pub;
+ TALER_denom_pub_hash (&new_dkp[k]->pub,
+ &depos[i].coin.denom_pub_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&depos[i].coin.denom_sig,
+ &ccoin->coin_sig,
+ &bks,
+ &c_hash,
+ &alg_values,
+ &new_dkp[k]->pub));
+ RND_BLK (&bd.merchant_pub);
+ RND_BLK (&depos[i].csig);
+ RND_BLK (&bd.h_contract_terms);
+ RND_BLK (&bd.wire_salt);
+ depos[i].amount_with_fee = value;
+ bd.refund_deadline = deadline;
+ bd.wire_deadline = deadline;
+ bd.receiver_wire_account =
+ "payto://iban/DE67830654080004822650?receiver-name=Test";
+ TALER_merchant_wire_signature_hash (
+ bd.receiver_wire_account,
+ &bd.wire_salt,
+ &h_wire_wt);
+ bd.num_cdis = 1;
+ bd.cdis = &depos[i];
+ cbc.reserve_pub = reserve_pub;
+ cbc.amount_with_fee = value;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (CURRENCY,
+ &cbc.withdraw_fee));
+ {
+ bool found;
+ bool nonce_reuse;
+ bool balance_ok;
+ bool age_ok;
+ bool conflict;
+ bool denom_unknown;
+ struct TALER_Amount reserve_balance;
+ uint16_t allowed_minimum_age;
+ uint64_t ruuid;
+ struct GNUNET_TIME_Timestamp now;
+
+ now = GNUNET_TIME_timestamp_get ();
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_batch_withdraw (plugin->cls,
+ now,
+ &reserve_pub,
+ &value,
+ true,
+ &found,
+ &balance_ok,
+ &reserve_balance,
+ &age_ok,
+ &allowed_minimum_age,
+ &ruuid));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_batch_withdraw_insert (plugin->cls,
+ NULL,
+ &cbc,
+ now,
+ ruuid,
+ &denom_unknown,
+ &conflict,
+ &nonce_reuse));
+ }
+ {
+ /* ENSURE_COIN_KNOWN */
+ uint64_t known_coin_id;
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+ FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+ plugin->ensure_coin_known (plugin->cls,
+ &depos[i].coin,
+ &known_coin_id,
+ &dph,
+ &agh));
+ refresh.coin = depos[i].coin;
+ RND_BLK (&refresh.coin_sig);
+ RND_BLK (&refresh.rc);
+ refresh.amount_with_fee = value;
+ refresh.noreveal_index = MELT_NOREVEAL_INDEX;
+ }
+ {
+ struct GNUNET_TIME_Timestamp now;
+ bool balance_ok;
+ uint32_t bad_idx;
+ bool ctr_conflict;
+
+ now = GNUNET_TIME_timestamp_get ();
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_deposit (plugin->cls,
+ &bd,
+ &now,
+ &balance_ok,
+ &bad_idx,
+ &ctr_conflict));
+ }
+ if (ROUNDS == i)
+ TALER_denom_sig_free (&depos[i].coin.denom_sig);
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+ GNUNET_free (perm);
+ /* End of benchmark setup */
+
+ /**** CALL GET READY DEPOSIT ****/
+ for (unsigned int r = 0; r< ROUNDS; r++)
+ {
+ struct GNUNET_TIME_Absolute time;
+ struct GNUNET_TIME_Relative duration;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ char *payto_uri;
+ enum GNUNET_DB_QueryStatus qs;
+
+ time = GNUNET_TIME_absolute_get ();
+ qs = plugin->get_ready_deposit (plugin->cls,
+ 0,
+ INT32_MAX,
+ &merchant_pub,
+ &payto_uri);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
+ duration = GNUNET_TIME_absolute_get_duration (time);
+ times = GNUNET_TIME_relative_add (times,
+ duration);
+ duration_sq = duration.rel_value_us * duration.rel_value_us;
+ GNUNET_assert (duration_sq / duration.rel_value_us ==
+ duration.rel_value_us);
+ GNUNET_assert (sqrs + duration_sq >= sqrs);
+ sqrs += duration_sq;
+ }
+
+ /* evaluation of performance */
+ {
+ struct GNUNET_TIME_Relative avg;
+ double avg_dbl;
+ double variance;
+
+ avg = GNUNET_TIME_relative_divide (times,
+ ROUNDS);
+ avg_dbl = avg.rel_value_us;
+ variance = sqrs - (avg_dbl * avg_dbl * ROUNDS);
+ fprintf (stdout,
+ "%8llu ± %6.0f\n",
+ (unsigned long long) avg.rel_value_us,
+ sqrt (variance / (ROUNDS - 1)));
+ }
+ result = 0;
+drop:
+ // GNUNET_break (GNUNET_OK == plugin->drop_tables (plugin->cls));
+cleanup:
+ if (NULL != revealed_coins)
+ {
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ TALER_blinded_denom_sig_free (&revealed_coins[cnt].coin_sig);
+ TALER_blinded_planchet_free (&revealed_coins[cnt].blinded_planchet);
+ }
+ GNUNET_free (revealed_coins);
+ revealed_coins = NULL;
+ }
+ GNUNET_free (new_denom_pubs);
+ for (unsigned int cnt = 0;
+ (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]);
+ cnt++)
+ destroy_denom_key_pair (new_dkp[cnt]);
+ GNUNET_free (new_dkp);
+ for (unsigned int i = 0; i< ROUNDS; i++)
+ {
+ TALER_denom_sig_free (&depos[i].coin.denom_sig);
+ }
+ GNUNET_free (depos);
+ GNUNET_free (ref);
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ (void) argc;
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ GNUNET_log_setup (argv[0],
+ "WARNING",
+ NULL);
+ plugin_name++;
+ {
+ char *testname;
+
+ GNUNET_asprintf (&testname,
+ "test-exchange-db-%s",
+ plugin_name);
+ GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ GNUNET_free (testname);
+ }
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ return result;
+}
+
+
+/* end of perf_deposits_get_ready.c */
diff --git a/src/exchangedb/perf_get_link_data.c b/src/exchangedb/perf_get_link_data.c
new file mode 100644
index 000000000..817789afc
--- /dev/null
+++ b/src/exchangedb/perf_get_link_data.c
@@ -0,0 +1,543 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchangedb/perf_get_link_data.c
+ * @brief benchmark for get_link_data
+ * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "math.h"
+
+/**
+ * Report line of error if @a cond is true, and jump to label "drop".
+ */
+#define FAILIF(cond) \
+ do { \
+ if (! (cond)) {break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
+
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+
+/**
+ * Initializes @a ptr with zeros.
+ */
+#define ZR_BLK(ptr) \
+ memset (ptr, 0, sizeof (*ptr))
+
+#define CURRENCY "EUR"
+#define RSA_KEY_SIZE 1024
+#define ROUNDS 100
+#define NUM_ROWS 1000
+#define MELT_NEW_COINS 5
+#define DENOMINATIONS 5
+#define MELT_NOREVEAL_INDEX 1
+/**
+ * Database plugin under test.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+static struct TALER_DenomFeeSet fees;
+/**
+ * Denomination keys used for fresh coins in melt test.
+ */
+static struct DenomKeyPair **new_dkp;
+static int result;
+
+static struct TALER_TransferPrivateKeyP tprivs[TALER_CNC_KAPPA];
+static struct TALER_TransferPublicKeyP tpub;
+struct DenomKeyPair
+{
+ struct TALER_DenominationPrivateKey priv;
+ struct TALER_DenominationPublicKey pub;
+};
+
+
+/**
+ * Destroy a denomination key pair. The key is not necessarily removed from the DB.
+ *
+ * @param dkp the key pair to destroy
+ */
+static void
+destroy_denom_key_pair (struct DenomKeyPair *dkp)
+{
+ TALER_denom_pub_free (&dkp->pub);
+ TALER_denom_priv_free (&dkp->priv);
+ GNUNET_free (dkp);
+}
+
+
+/**
+ * Create a denomination key pair by registering the denomination in the DB.
+ *
+ * @param size the size of the denomination key
+ * @param now time to use for key generation, legal expiration will be 3h later.
+ * @param fees fees to use
+ * @return the denominaiton key pair; NULL upon error
+ */
+static struct DenomKeyPair *
+create_denom_key_pair (unsigned int size,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_Amount *value,
+ const struct TALER_DenomFeeSet *fees)
+{
+ struct DenomKeyPair *dkp;
+ struct TALER_EXCHANGEDB_DenominationKey dki;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
+
+ dkp = GNUNET_new (struct DenomKeyPair);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_priv_create (&dkp->priv,
+ &dkp->pub,
+ GNUNET_CRYPTO_BSA_RSA,
+ size));
+ memset (&dki,
+ 0,
+ sizeof (struct TALER_EXCHANGEDB_DenominationKey));
+ dki.denom_pub = dkp->pub;
+ dki.issue.start = now;
+ dki.issue.expire_withdraw
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_UNIT_HOURS));
+ dki.issue.expire_deposit
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 2)));
+ dki.issue.expire_legal
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 3)));
+ dki.issue.value = *value;
+ dki.issue.fees = *fees;
+ TALER_denom_pub_hash (&dkp->pub,
+ &dki.issue.denom_hash);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_denomination_info (plugin->cls,
+ &dki.denom_pub,
+ &dki.issue))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ memset (&issue2, 0, sizeof (issue2));
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_denomination_info (plugin->cls,
+ &dki.issue.denom_hash,
+ &issue2))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ if (0 != GNUNET_memcmp (&dki.issue,
+ &issue2))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ return dkp;
+}
+
+
+/**
+ * Function called with the session hashes and transfer secret
+ * information for a given coin.
+ *
+ * @param cls closure
+ * @param transfer_pub public transfer key for the session
+ * @param ldl link data for @a transfer_pub
+ */
+static void
+handle_link_data_cb (void *cls,
+ const struct TALER_TransferPublicKeyP *transfer_pub,
+ const struct TALER_EXCHANGEDB_LinkList *ldl)
+{
+ (void) cls;
+ (void) transfer_pub;
+ (void) ldl;
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+
+static void
+run (void *cls)
+{
+ struct TALER_EXCHANGEDB_Refresh *refresh;
+ uint64_t melt_serial_id;
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ const uint32_t num_partitions = 10;
+ struct DenomKeyPair *dkp = NULL;
+ struct TALER_EXCHANGEDB_Deposit *depos = NULL;
+ struct TALER_Amount value;
+ struct GNUNET_TIME_Relative times = GNUNET_TIME_UNIT_ZERO;
+ unsigned long long sqrs = 0;
+ struct TALER_EXCHANGEDB_Refund *ref = NULL;
+ unsigned int *perm;
+ unsigned long long duration_sq;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = GNUNET_CRYPTO_BSA_RSA,
+ .rc = 0
+ };
+ struct TALER_ExchangeWithdrawValues alg_values = {
+ .blinding_inputs = &bi
+ };
+
+ ref = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_Refund);
+ depos = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_Deposit);
+ refresh = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_Refresh);
+
+ if (NULL ==
+ (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_break (0);
+ result = 77;
+ return;
+ }
+ (void) plugin->drop_tables (plugin->cls);
+ if (GNUNET_OK !=
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
+ {
+ GNUNET_break (0);
+ result = 77;
+ goto cleanup;
+ }
+ if (GNUNET_OK !=
+ plugin->preflight (plugin->cls))
+ {
+ GNUNET_break (0);
+ goto cleanup;
+ }
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.000010",
+ &value));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.withdraw));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.deposit));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.refresh));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.refund));
+ {
+ new_dkp = GNUNET_new_array (MELT_NEW_COINS,
+ struct DenomKeyPair *);
+
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+
+ new_dkp[cnt] = create_denom_key_pair (RSA_KEY_SIZE,
+ now,
+ &value,
+ &fees);
+ GNUNET_assert (NULL != new_dkp[cnt]);
+ }
+ }
+ perm = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_NONCE,
+ NUM_ROWS);
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "Transaction"));
+ for (unsigned int j = 0; j < NUM_ROWS; j++)
+ {
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct TALER_CoinPubHashP c_hash;
+ unsigned int i = perm[j];
+ uint64_t known_coin_id;
+ struct TALER_EXCHANGEDB_CollectableBlindcoin cbc;
+ if (i >= ROUNDS)
+ i = ROUNDS; /* throw-away slot, do not keep around */
+ RND_BLK (&depos[i].coin.coin_pub);
+ ZR_BLK (&cbc);
+ TALER_denom_pub_hash (&new_dkp[(unsigned int) rand ()
+ % MELT_NEW_COINS]->pub,
+ &depos[i].coin.denom_pub_hash);
+
+
+ {
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin
+ revealed_coins[MELT_NEW_COINS];
+
+ for (unsigned int p = 0; p<MELT_NEW_COINS; p++)
+ {
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coin =
+ &revealed_coins[p];
+ struct TALER_BlindedPlanchet *bp = &revealed_coin->blinded_planchet;
+ bp->blinded_message = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
+ struct GNUNET_CRYPTO_RsaBlindedMessage *rp =
+ &bp->blinded_message->details.rsa_blinded_message;
+
+ /* h_coin_ev must be unique, but we only have MELT_NEW_COINS created
+ above for NUM_ROWS iterations; instead of making "all new" coins,
+ we simply randomize the hash here as nobody is checking for consistency
+ anyway ;-) */
+ bp->blinded_message->rc = 1;
+ bp->blinded_message->cipher = GNUNET_CRYPTO_BSA_RSA;
+ rp->blinded_msg_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ (RSA_KEY_SIZE / 8) - 1);
+ rp->blinded_msg = GNUNET_malloc (rp->blinded_msg_size);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ rp->blinded_msg,
+ rp->blinded_msg_size);
+ TALER_denom_pub_hash (&new_dkp[(unsigned int) rand ()
+ % MELT_NEW_COINS]->pub,
+ &revealed_coin->h_denom_pub);
+ revealed_coin->exchange_vals = alg_values;
+ TALER_coin_ev_hash (bp,
+ &revealed_coin->h_denom_pub,
+ &revealed_coin->coin_envelope_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sign_blinded (&revealed_coin->coin_sig,
+ &new_dkp[(unsigned
+ int) rand ()
+ % MELT_NEW_COINS]->
+ priv,
+ true,
+ bp));
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_denom_sign_blinded (
+ &cbc.sig,
+ &new_dkp[(unsigned int) rand () % MELT_NEW_COINS]->priv,
+ false,
+ bp));
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&depos[i].coin.denom_sig,
+ &cbc.sig,
+ &bks,
+ &c_hash,
+ &alg_values,
+ &new_dkp[(unsigned int) rand ()
+ % MELT_NEW_COINS]->pub));
+ {
+ /* ENSURE_COIN_KNOWN */
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+ bool zombie_required = false;
+ bool balance_ok;
+
+ FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+ plugin->ensure_coin_known (plugin->cls,
+ &depos[i].coin,
+ &known_coin_id,
+ &dph,
+ &agh));
+ /**** INSERT REFRESH COMMITMENTS ****/
+ refresh[i].coin = depos[i].coin;
+ RND_BLK (&refresh[i].coin_sig);
+ RND_BLK (&refresh[i].rc);
+ refresh[i].amount_with_fee = value;
+ refresh[i].noreveal_index = MELT_NOREVEAL_INDEX;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_melt (plugin->cls,
+ NULL,
+ &refresh[i],
+ known_coin_id,
+ &zombie_required,
+ &balance_ok));
+ }
+ /****GET melt_serial_id generated by default****/
+ {
+ struct TALER_EXCHANGEDB_Melt ret_refresh_session;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_melt (plugin->cls,
+ &refresh[i].rc,
+ &ret_refresh_session,
+ &melt_serial_id));
+ }
+ /**** INSERT REFRESH_REVEAL + TRANSFER_KEYS *****/
+ {
+ static unsigned int cnt;
+
+ RND_BLK (&tprivs);
+ RND_BLK (&tpub);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_refresh_reveal (plugin->cls,
+ melt_serial_id,
+ MELT_NEW_COINS,
+ revealed_coins,
+ TALER_CNC_KAPPA - 1,
+ tprivs,
+ &tpub));
+ cnt++;
+ // fprintf (stderr, "CNT: %u - %llu\n", cnt, (unsigned long long) melt_serial_id);
+ }
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ TALER_blinded_denom_sig_free (&revealed_coins[cnt].coin_sig);
+ TALER_blinded_planchet_free (&revealed_coins[cnt].blinded_planchet);
+ }
+
+ /* {
+ struct TALER_CoinSpendPublicKeyP ocp;
+ uint64_t rrc_serial;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_old_coin_by_h_blind (plugin->cls,
+ &revealed_coins->coin_envelope_hash,
+ &ocp,
+ &rrc_serial));
+ }*/
+ }
+ if (ROUNDS == i)
+ TALER_denom_sig_free (&depos[i].coin.denom_sig);
+ }
+ /* End of benchmark setup */
+ GNUNET_free (perm);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+ for (unsigned int r = 0; r< ROUNDS; r++)
+ {
+ struct GNUNET_TIME_Absolute time;
+ struct GNUNET_TIME_Relative duration;
+ enum GNUNET_DB_QueryStatus qs;
+ time = GNUNET_TIME_absolute_get ();
+
+ qs = plugin->get_link_data (plugin->cls,
+ &refresh[r].coin.coin_pub,
+ &handle_link_data_cb,
+ NULL);
+ FAILIF (qs < 0);
+
+ duration = GNUNET_TIME_absolute_get_duration (time);
+ times = GNUNET_TIME_relative_add (times,
+ duration);
+ duration_sq = duration.rel_value_us * duration.rel_value_us;
+ GNUNET_assert (duration_sq / duration.rel_value_us ==
+ duration.rel_value_us);
+ GNUNET_assert (sqrs + duration_sq >= sqrs);
+ sqrs += duration_sq;
+ }
+
+ /* evaluation of performance */
+ {
+ struct GNUNET_TIME_Relative avg;
+ double avg_dbl;
+ double variance;
+
+ avg = GNUNET_TIME_relative_divide (times,
+ ROUNDS);
+ avg_dbl = avg.rel_value_us;
+ variance = sqrs - (avg_dbl * avg_dbl * ROUNDS);
+ fprintf (stdout,
+ "%8llu ± %6.0f\n",
+ (unsigned long long) avg.rel_value_us,
+ sqrt (variance / (ROUNDS - 1)));
+ }
+ result = 0;
+drop:
+ // GNUNET_break (GNUNET_OK == plugin->drop_tables (plugin->cls));
+cleanup:
+ if (NULL != dkp)
+ destroy_denom_key_pair (dkp);
+ for (unsigned int cnt = 0;
+ (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]);
+ cnt++)
+ destroy_denom_key_pair (new_dkp[cnt]);
+ GNUNET_free (new_dkp);
+ for (unsigned int i = 0; i< ROUNDS; i++)
+ {
+ TALER_denom_sig_free (&depos[i].coin.denom_sig);
+ }
+ GNUNET_free (depos);
+ GNUNET_free (ref);
+ GNUNET_free (refresh);
+ dkp = NULL;
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ char *testname;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ (void) argc;
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ GNUNET_log_setup (argv[0],
+ "WARNING",
+ NULL);
+ plugin_name++;
+ (void) GNUNET_asprintf (&testname,
+ "test-exchange-db-%s",
+ plugin_name);
+ (void) GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return result;
+}
+
+
+/* end of test_exchangedb_by_j.c */
diff --git a/src/exchangedb/perf_reserves_in_insert.c b/src/exchangedb/perf_reserves_in_insert.c
new file mode 100644
index 000000000..09c4a43c5
--- /dev/null
+++ b/src/exchangedb/perf_reserves_in_insert.c
@@ -0,0 +1,232 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchangedb/perf_reserves_in_insert.c
+ * @brief benchmark for 'reserves_in_insert'
+ * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Global result from the testcase.
+ */
+static int result;
+
+/**
+ * Report line of error if @a cond is true, and jump to label "drop".
+ */
+#define FAILIF(cond) \
+ do { \
+ if (! (cond)) {break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
+
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+
+/**
+ * Initializes @a ptr with zeros.
+ */
+#define ZR_BLK(ptr) \
+ memset (ptr, 0, sizeof (*ptr))
+
+/**
+ * How many rounds do we average over?
+ */
+#define ROUNDS 5
+
+/**
+ * Currency we use. Must match test-exchange-db-*.conf.
+ */
+#define CURRENCY "EUR"
+
+/**
+ * Database plugin under test.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+static void
+run (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ const uint32_t num_partitions = 10;
+ static unsigned int batches[] = {1, 1, 2, 3, 4, 16, 32, 64, 128, 256, 512,
+ 1024, 1024, 512, 256, 128, 64, 32,
+ 16, 4, 3, 2, 1 };
+ struct GNUNET_TIME_Relative times[sizeof (batches) / sizeof(*batches)];
+ unsigned long long sqrs[sizeof (batches) / sizeof(*batches)];
+
+ if (NULL ==
+ (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_break (0);
+ result = 77;
+ return;
+ }
+ (void) plugin->drop_tables (plugin->cls);
+ if (GNUNET_OK !=
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
+ {
+ GNUNET_break (0);
+ result = 77;
+ goto cleanup;
+ }
+
+ memset (times, 0, sizeof (times));
+ memset (sqrs, 0, sizeof (sqrs));
+ for (unsigned int r = 0; r < ROUNDS; r++)
+ {
+ for (unsigned int i = 0;
+ i< sizeof(batches) / sizeof(*batches);
+ i++)
+ {
+ unsigned int lcm = batches[i];
+ struct TALER_Amount value;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp ts;
+ unsigned long long duration_sq;
+ struct GNUNET_TIME_Relative duration;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.000010",
+ &value));
+ now = GNUNET_TIME_absolute_get ();
+ ts = GNUNET_TIME_timestamp_get ();
+ {
+ const char *sndr = "payto://x-taler-bank/localhost:8080/1";
+ struct TALER_ReservePublicKeyP reserve_pubs[lcm];
+ struct TALER_EXCHANGEDB_ReserveInInfo reserves[lcm];
+ enum GNUNET_DB_QueryStatus results[lcm];
+
+ for (unsigned int k = 0; k<lcm; k++)
+ {
+ RND_BLK (&reserve_pubs[k]);
+ reserves[k].reserve_pub = &reserve_pubs[k];
+ reserves[k].balance = &value;
+ reserves[k].execution_time = ts;
+ reserves[k].sender_account_details = sndr;
+ reserves[k].exchange_account_name = "name";
+ reserves[k].wire_reference = k;
+ }
+ FAILIF (lcm !=
+ plugin->reserves_in_insert (plugin->cls,
+ reserves,
+ lcm,
+ results));
+ }
+ duration = GNUNET_TIME_absolute_get_duration (now);
+ times[i] = GNUNET_TIME_relative_add (times[i],
+ duration);
+ duration_sq = duration.rel_value_us * duration.rel_value_us;
+ GNUNET_assert (duration_sq / duration.rel_value_us ==
+ duration.rel_value_us);
+ GNUNET_assert (sqrs[i] + duration_sq >= sqrs[i]);
+ sqrs[i] += duration_sq;
+ } /* for 'i' batch size */
+ } /* for 'r' ROUNDS */
+
+ for (unsigned int i = 0;
+ i< sizeof(batches) / sizeof(*batches);
+ i++)
+ {
+ unsigned int lcm = batches[i];
+ struct GNUNET_TIME_Relative avg;
+ double avg_dbl;
+ double variance;
+
+ avg = GNUNET_TIME_relative_divide (times[i],
+ ROUNDS);
+ avg_dbl = avg.rel_value_us;
+ variance = sqrs[i] - (avg_dbl * avg_dbl * ROUNDS);
+ fprintf (stdout,
+ "Batch[%4u]: %8llu us/entry ± %6.0f\n",
+ batches[i],
+ (unsigned long long) avg.rel_value_us / lcm,
+ sqrt (variance / lcm / (ROUNDS - 1)));
+ }
+ result = 0;
+drop:
+ GNUNET_break (GNUNET_OK ==
+ plugin->drop_tables (plugin->cls));
+cleanup:
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ char *testname;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ (void) argc;
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+
+ GNUNET_log_setup (argv[0],
+ "WARNING",
+ NULL);
+ plugin_name++;
+ (void) GNUNET_asprintf (&testname,
+ "test-exchange-db-%s",
+ plugin_name);
+ (void) GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return result;
+}
+
+
+/* end of perf_reserves_in_insert.c */
diff --git a/src/exchangedb/perf_select_refunds_by_coin.c b/src/exchangedb/perf_select_refunds_by_coin.c
new file mode 100644
index 000000000..84825d6d7
--- /dev/null
+++ b/src/exchangedb/perf_select_refunds_by_coin.c
@@ -0,0 +1,619 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchangedb/perf_select_refunds_by_coin.c
+ * @brief benchmark for select_refunds_by_coin
+ * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "math.h"
+
+/**
+ * Global result from the testcase.
+ */
+static int result;
+
+/**
+ * Report line of error if @a cond is true, and jump to label "drop".
+ */
+#define FAILIF(cond) \
+ do { \
+ if (! (cond)) {break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, \
+ sizeof (*ptr))
+
+/**
+ * Initializes @a ptr with zeros.
+ */
+#define ZR_BLK(ptr) \
+ memset (ptr, 0, sizeof (*ptr))
+
+/**
+ * Currency we use. Must match test-exchange-db-*.conf.
+ */
+#define CURRENCY "EUR"
+#define RSA_KEY_SIZE 1024
+#define ROUNDS 100
+#define NUM_ROWS 1000
+#define MELT_NEW_COINS 5
+#define MELT_NOREVEAL_INDEX 1
+
+/**
+ * Database plugin under test.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+
+static struct TALER_DenomFeeSet fees;
+
+static struct TALER_MerchantWireHashP h_wire_wt;
+
+static struct DenomKeyPair **new_dkp;
+
+static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins;
+
+struct DenomKeyPair
+{
+ struct TALER_DenominationPrivateKey priv;
+ struct TALER_DenominationPublicKey pub;
+};
+
+
+/**
+ * Destroy a denomination key pair. The key is not necessarily removed from the DB.
+ *
+ * @param dkp the key pair to destroy
+ */
+static void
+destroy_denom_key_pair (struct DenomKeyPair *dkp)
+{
+ TALER_denom_pub_free (&dkp->pub);
+ TALER_denom_priv_free (&dkp->priv);
+ GNUNET_free (dkp);
+}
+
+
+/**
+ * Create a denomination key pair by registering the denomination in the DB.
+ *
+ * @param size the size of the denomination key
+ * @param now time to use for key generation, legal expiration will be 3h later.
+ * @param fees fees to use
+ * @return the denominaiton key pair; NULL upon error
+ */
+static struct DenomKeyPair *
+create_denom_key_pair (unsigned int size,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_Amount *value,
+ const struct TALER_DenomFeeSet *fees)
+{
+ struct DenomKeyPair *dkp;
+ struct TALER_EXCHANGEDB_DenominationKey dki;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
+
+ dkp = GNUNET_new (struct DenomKeyPair);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_priv_create (&dkp->priv,
+ &dkp->pub,
+ GNUNET_CRYPTO_BSA_RSA,
+ size));
+ memset (&dki,
+ 0,
+ sizeof (struct TALER_EXCHANGEDB_DenominationKey));
+ dki.denom_pub = dkp->pub;
+ dki.issue.start = now;
+ dki.issue.expire_withdraw
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_UNIT_HOURS));
+ dki.issue.expire_deposit
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 2)));
+ dki.issue.expire_legal
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 3)));
+ dki.issue.value = *value;
+ dki.issue.fees = *fees;
+ TALER_denom_pub_hash (&dkp->pub,
+ &dki.issue.denom_hash);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_denomination_info (plugin->cls,
+ &dki.denom_pub,
+ &dki.issue))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ memset (&issue2, 0, sizeof (issue2));
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_denomination_info (plugin->cls,
+ &dki.issue.denom_hash,
+ &issue2))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ if (0 != GNUNET_memcmp (&dki.issue,
+ &issue2))
+ {
+ GNUNET_break (0);
+ destroy_denom_key_pair (dkp);
+ return NULL;
+ }
+ return dkp;
+}
+
+
+/**
+ * Callback invoked with information about refunds applicable
+ * to a particular coin.
+ *
+ * @param cls closure with the `struct TALER_EXCHANGEDB_Refund *` we expect to get
+ * @param amount_with_fee amount being refunded
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
+ */
+static enum GNUNET_GenericReturnValue
+check_refund_cb (void *cls,
+ const struct TALER_Amount *amount_with_fee)
+{
+ const struct TALER_EXCHANGEDB_Refund *refund = cls;
+
+ if (0 != TALER_amount_cmp (amount_with_fee,
+ &refund->details.refund_amount))
+ {
+ GNUNET_break (0);
+ result = 66;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+
+static void
+run (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ const uint32_t num_partitions = 10;
+ struct GNUNET_TIME_Timestamp ts;
+ struct TALER_EXCHANGEDB_CoinDepositInformation *depos = NULL;
+ struct GNUNET_TIME_Timestamp deadline;
+ struct TALER_Amount value;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct TALER_EXCHANGEDB_CollectableBlindcoin cbc;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = GNUNET_CRYPTO_BSA_RSA,
+ .rc = 0
+ };
+ struct TALER_ExchangeWithdrawValues alg_values = {
+ .blinding_inputs = &bi
+ };
+ struct GNUNET_TIME_Relative times = GNUNET_TIME_UNIT_ZERO;
+ unsigned long long sqrs = 0;
+ struct TALER_EXCHANGEDB_Refund *ref = NULL;
+ unsigned int *perm;
+ unsigned long long duration_sq;
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
+ struct TALER_DenominationPublicKey *new_denom_pubs = NULL;
+ unsigned int count = 0;
+
+ ref = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_Refund);
+ depos = GNUNET_new_array (ROUNDS + 1,
+ struct TALER_EXCHANGEDB_CoinDepositInformation);
+ ZR_BLK (&cbc);
+
+ if (NULL ==
+ (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_break (0);
+ result = 77;
+ return;
+ }
+ (void) plugin->drop_tables (plugin->cls);
+ if (GNUNET_OK !=
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
+ {
+ GNUNET_break (0);
+ result = 77;
+ goto cleanup;
+ }
+ if (GNUNET_OK !=
+ plugin->preflight (plugin->cls))
+ {
+ GNUNET_break (0);
+ goto cleanup;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.000010",
+ &value));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.withdraw));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.deposit));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.refresh));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":0.000010",
+ &fees.refund));
+ GNUNET_assert (NUM_ROWS >= ROUNDS);
+
+ ts = GNUNET_TIME_timestamp_get ();
+ deadline = GNUNET_TIME_timestamp_get ();
+ {
+ new_dkp = GNUNET_new_array (MELT_NEW_COINS,
+ struct DenomKeyPair *);
+ new_denom_pubs = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_DenominationPublicKey);
+ revealed_coins
+ = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin);
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_CRYPTO_RsaBlindedMessage *rp;
+ struct TALER_BlindedPlanchet *bp;
+
+ now = GNUNET_TIME_timestamp_get ();
+ new_dkp[cnt] = create_denom_key_pair (RSA_KEY_SIZE,
+ now,
+ &value,
+ &fees);
+ GNUNET_assert (NULL != new_dkp[cnt]);
+ new_denom_pubs[cnt] = new_dkp[cnt]->pub;
+ ccoin = &revealed_coins[cnt];
+ bp = &ccoin->blinded_planchet;
+ bp->blinded_message = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
+ bp->blinded_message->rc = 1;
+ bp->blinded_message->cipher = GNUNET_CRYPTO_BSA_RSA;
+ rp = &bp->blinded_message->details.rsa_blinded_message;
+ rp->blinded_msg_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ (RSA_KEY_SIZE / 8) - 1);
+ rp->blinded_msg = GNUNET_malloc (rp->blinded_msg_size);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ rp->blinded_msg,
+ rp->blinded_msg_size);
+ TALER_denom_pub_hash (&new_dkp[cnt]->pub,
+ &ccoin->h_denom_pub);
+ ccoin->exchange_vals = alg_values;
+ TALER_coin_ev_hash (bp,
+ &ccoin->h_denom_pub,
+ &ccoin->coin_envelope_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sign_blinded (&ccoin->coin_sig,
+ &new_dkp[cnt]->priv,
+ true,
+ bp));
+ TALER_coin_ev_hash (bp,
+ &cbc.denom_pub_hash,
+ &cbc.h_coin_envelope);
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_denom_sign_blinded (
+ &cbc.sig,
+ &new_dkp[cnt]->priv,
+ false,
+ bp));
+ }
+ }
+
+ perm = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_NONCE,
+ NUM_ROWS);
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "Transaction"));
+ for (unsigned int j = 0; j< NUM_ROWS; j++)
+ {
+ unsigned int i = perm[j];
+ unsigned int k = (unsigned int) rand () % 5;
+ struct TALER_CoinPubHashP c_hash;
+ uint64_t known_coin_id;
+ struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+ = &depos[i];
+ struct TALER_EXCHANGEDB_BatchDeposit bd = {
+ .cdis = cdi,
+ .num_cdis = 1,
+ .wallet_timestamp = ts,
+ .refund_deadline = deadline,
+ .wire_deadline = deadline,
+ .receiver_wire_account
+ = "payto://iban/DE67830654080004822650?receiver-name=Test"
+ };
+
+ if (i >= ROUNDS)
+ i = ROUNDS; /* throw-away slot, do not keep around */
+ RND_BLK (&bd.merchant_pub);
+ RND_BLK (&bd.h_contract_terms);
+ RND_BLK (&bd.wire_salt);
+ TALER_merchant_wire_signature_hash (
+ bd.receiver_wire_account,
+ &bd.wire_salt,
+ &h_wire_wt);
+ RND_BLK (&cdi->coin.coin_pub);
+ RND_BLK (&cdi->csig);
+ RND_BLK (&c_hash);
+ TALER_denom_pub_hash (&new_dkp[k]->pub,
+ &cdi->coin.denom_pub_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&cdi->coin.denom_sig,
+ &cbc.sig,
+ &bks,
+ &c_hash,
+ &alg_values,
+ &new_dkp[k]->pub));
+ cdi->amount_with_fee = value;
+
+ {
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+
+ FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+ plugin->ensure_coin_known (plugin->cls,
+ &cdi->coin,
+ &known_coin_id,
+ &dph,
+ &agh));
+ }
+ {
+ struct GNUNET_TIME_Timestamp now;
+ bool balance_ok;
+ uint32_t bad_idx;
+ bool in_conflict;
+
+ now = GNUNET_TIME_timestamp_get ();
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_deposit (plugin->cls,
+ &bd,
+ &now,
+ &balance_ok,
+ &bad_idx,
+ &in_conflict));
+ }
+ {
+ bool not_found;
+ bool refund_ok;
+ bool gone;
+ bool conflict;
+ unsigned int refund_percent = 0;
+ switch (refund_percent)
+ {
+ case 2: // 100% refund
+ ref[i].coin = depos[i].coin;
+ ref[i].details.merchant_pub = bd.merchant_pub;
+ RND_BLK (&ref[i].details.merchant_sig);
+ ref[i].details.h_contract_terms = bd.h_contract_terms;
+ ref[i].coin.coin_pub = depos[i].coin.coin_pub;
+ ref[i].details.rtransaction_id = i;
+ ref[i].details.refund_amount = value;
+ ref[i].details.refund_fee = fees.refund;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_refund (plugin->cls,
+ &ref[i],
+ &fees.deposit,
+ known_coin_id,
+ &not_found,
+ &refund_ok,
+ &gone,
+ &conflict));
+ break;
+ case 1:// 10% refund
+ if (count < (NUM_ROWS / 10))
+ {
+ ref[i].coin = depos[i].coin;
+ ref[i].details.merchant_pub = bd.merchant_pub;
+ RND_BLK (&ref[i].details.merchant_sig);
+ ref[i].details.h_contract_terms = bd.h_contract_terms;
+ ref[i].coin.coin_pub = depos[i].coin.coin_pub;
+ ref[i].details.rtransaction_id = i;
+ ref[i].details.refund_amount = value;
+ ref[i].details.refund_fee = fees.refund;
+ }
+ else
+ {
+ ref[i].coin = depos[i].coin;
+ RND_BLK (&ref[i].details.merchant_pub);
+ RND_BLK (&ref[i].details.merchant_sig);
+ RND_BLK (&ref[i].details.h_contract_terms);
+ RND_BLK (&ref[i].coin.coin_pub);
+ ref[i].details.rtransaction_id = i;
+ ref[i].details.refund_amount = value;
+ ref[i].details.refund_fee = fees.refund;
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_refund (plugin->cls,
+ &ref[i],
+ &fees.deposit,
+ known_coin_id,
+ &not_found,
+ &refund_ok,
+ &gone,
+ &conflict));
+ count++;
+ break;
+ case 0:// no refund
+ ref[i].coin = depos[i].coin;
+ RND_BLK (&ref[i].details.merchant_pub);
+ RND_BLK (&ref[i].details.merchant_sig);
+ RND_BLK (&ref[i].details.h_contract_terms);
+ RND_BLK (&ref[i].coin.coin_pub);
+ ref[i].details.rtransaction_id = i;
+ ref[i].details.refund_amount = value;
+ ref[i].details.refund_fee = fees.refund;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_refund (plugin->cls,
+ &ref[i],
+ &fees.deposit,
+ known_coin_id,
+ &not_found,
+ &refund_ok,
+ &gone,
+ &conflict));
+ break;
+ }/* END OF SWITCH CASE */
+ }
+ if (ROUNDS == i)
+ TALER_denom_sig_free (&depos[i].coin.denom_sig);
+ }
+ /* End of benchmark setup */
+ GNUNET_free (perm);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+ for (unsigned int r = 0; r < ROUNDS; r++)
+ {
+ struct GNUNET_TIME_Absolute time;
+ struct GNUNET_TIME_Relative duration;
+
+ time = GNUNET_TIME_absolute_get ();
+ FAILIF (0 >
+ plugin->select_refunds_by_coin (plugin->cls,
+ &ref[r].coin.coin_pub,
+ &ref[r].details.merchant_pub,
+ &ref[r].details.h_contract_terms,
+ &check_refund_cb,
+ &ref[r]));
+ duration = GNUNET_TIME_absolute_get_duration (time);
+ times = GNUNET_TIME_relative_add (times,
+ duration);
+ duration_sq = duration.rel_value_us * duration.rel_value_us;
+ GNUNET_assert (duration_sq / duration.rel_value_us ==
+ duration.rel_value_us);
+ GNUNET_assert (sqrs + duration_sq >= sqrs);
+ sqrs += duration_sq;
+ }
+ /* evaluation of performance */
+ {
+ struct GNUNET_TIME_Relative avg;
+ double avg_dbl;
+ double variance;
+
+ avg = GNUNET_TIME_relative_divide (times,
+ ROUNDS);
+ avg_dbl = avg.rel_value_us;
+ variance = sqrs - (avg_dbl * avg_dbl * ROUNDS);
+ fprintf (stdout,
+ "%8llu ± %6.0f\n",
+ (unsigned long long) avg.rel_value_us,
+ sqrt (variance / (ROUNDS - 1)));
+ }
+ result = 0;
+drop:
+ GNUNET_break (GNUNET_OK ==
+ plugin->drop_tables (plugin->cls));
+cleanup:
+ if (NULL != revealed_coins)
+ {
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ TALER_blinded_denom_sig_free (&revealed_coins[cnt].coin_sig);
+ TALER_blinded_planchet_free (&revealed_coins[cnt].blinded_planchet);
+ }
+ GNUNET_free (revealed_coins);
+ revealed_coins = NULL;
+ }
+ GNUNET_free (new_denom_pubs);
+ for (unsigned int cnt = 0;
+ (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]);
+ cnt++)
+ destroy_denom_key_pair (new_dkp[cnt]);
+ GNUNET_free (new_dkp);
+ for (unsigned int i = 0; i< ROUNDS + 1; i++)
+ {
+ TALER_denom_sig_free (&depos[i].coin.denom_sig);
+ }
+ GNUNET_free (depos);
+ GNUNET_free (ref);
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ (void) argc;
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+ GNUNET_log_setup (argv[0],
+ "WARNING",
+ NULL);
+ plugin_name++;
+ {
+ char *testname;
+
+ GNUNET_asprintf (&testname,
+ "test-exchange-db-%s",
+ plugin_name);
+ GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ GNUNET_free (testname);
+ }
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ return result;
+}
+
+
+/* end of perf_select_refunds_by_coin.c */
diff --git a/src/exchangedb/pg_abort_shard.c b/src/exchangedb/pg_abort_shard.c
new file mode 100644
index 000000000..d04680a81
--- /dev/null
+++ b/src/exchangedb/pg_abort_shard.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_abort_shard.c
+ * @brief Implementation of the abort_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_abort_shard.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_abort_shard (void *cls,
+ const char *job_name,
+ uint64_t start_row,
+ uint64_t end_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_uint64 (&start_row),
+ GNUNET_PQ_query_param_uint64 (&end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "abort_shard",
+ "UPDATE work_shards"
+ " SET last_attempt=0"
+ " WHERE job_name=$1"
+ " AND start_row=$2"
+ " AND end_row=$3;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "abort_shard",
+ params);
+}
diff --git a/src/exchangedb/pg_abort_shard.h b/src/exchangedb/pg_abort_shard.h
new file mode 100644
index 000000000..e52ace5f6
--- /dev/null
+++ b/src/exchangedb/pg_abort_shard.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_abort_shard.h
+ * @brief implementation of the abort_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ABORT_SHARD_H
+#define PG_ABORT_SHARD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to abort work on a shard.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to abort a word shard for
+ * @param start_row inclusive start row of the shard
+ * @param end_row exclusive end row of the shard
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_abort_shard (void *cls,
+ const char *job_name,
+ uint64_t start_row,
+ uint64_t end_row);
+
+#endif
diff --git a/src/exchangedb/pg_activate_signing_key.c b/src/exchangedb/pg_activate_signing_key.c
new file mode 100644
index 000000000..fab2a0ffe
--- /dev/null
+++ b/src/exchangedb/pg_activate_signing_key.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_activate_signing_key.c
+ * @brief Implementation of the activate_signing_key function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_activate_signing_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_activate_signing_key (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam iparams[] = {
+ GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+ GNUNET_PQ_query_param_timestamp (&meta->start),
+ GNUNET_PQ_query_param_timestamp (&meta->expire_sign),
+ GNUNET_PQ_query_param_timestamp (&meta->expire_legal),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_signkey",
+ "INSERT INTO exchange_sign_keys "
+ "(exchange_pub"
+ ",valid_from"
+ ",expire_sign"
+ ",expire_legal"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_signkey",
+ iparams);
+}
diff --git a/src/exchangedb/pg_activate_signing_key.h b/src/exchangedb/pg_activate_signing_key.h
new file mode 100644
index 000000000..2d4df0671
--- /dev/null
+++ b/src/exchangedb/pg_activate_signing_key.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_activate_signing_key.h
+ * @brief implementation of the activate_signing_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ACTIVATE_SIGNING_KEY_H
+#define PG_ACTIVATE_SIGNING_KEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Add signing key.
+ *
+ * @param cls closure
+ * @param exchange_pub the exchange online signing public key
+ * @param meta meta data about @a exchange_pub
+ * @param master_sig master signature to add
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_activate_signing_key (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_add_denomination_key.c b/src/exchangedb/pg_add_denomination_key.c
new file mode 100644
index 000000000..eb50304d3
--- /dev/null
+++ b/src/exchangedb/pg_add_denomination_key.c
@@ -0,0 +1,86 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_add_denomination_key.c
+ * @brief Implementation of the add_denomination_key function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_add_denomination_key.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_add_denomination_key (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam iparams[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
+ TALER_PQ_query_param_denom_pub (denom_pub),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_timestamp (&meta->start),
+ GNUNET_PQ_query_param_timestamp (&meta->expire_withdraw),
+ GNUNET_PQ_query_param_timestamp (&meta->expire_deposit),
+ GNUNET_PQ_query_param_timestamp (&meta->expire_legal),
+ TALER_PQ_query_param_amount (pg->conn,
+ &meta->value),
+ TALER_PQ_query_param_amount (pg->conn,
+ &meta->fees.withdraw),
+ TALER_PQ_query_param_amount (pg->conn,
+ &meta->fees.deposit),
+ TALER_PQ_query_param_amount (pg->conn,
+ &meta->fees.refresh),
+ TALER_PQ_query_param_amount (pg->conn,
+ &meta->fees.refund),
+ GNUNET_PQ_query_param_uint32 (&meta->age_mask.bits),
+ GNUNET_PQ_query_param_end
+ };
+
+ /* Sanity check: ensure fees match coin currency */
+ GNUNET_assert (GNUNET_YES ==
+ TALER_denom_fee_check_currency (meta->value.currency,
+ &meta->fees));
+ PREPARE (pg,
+ "denomination_insert",
+ "INSERT INTO denominations "
+ "(denom_pub_hash"
+ ",denom_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin" /* value of this denom */
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",age_mask"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+ " $11, $12, $13);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "denomination_insert",
+ iparams);
+}
diff --git a/src/exchangedb/pg_add_denomination_key.h b/src/exchangedb/pg_add_denomination_key.h
new file mode 100644
index 000000000..d131679e8
--- /dev/null
+++ b/src/exchangedb/pg_add_denomination_key.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_add_denomination_key.h
+ * @brief implementation of the add_denomination_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ADD_DENOMINATION_KEY_H
+#define PG_ADD_DENOMINATION_KEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Activate denomination key, turning it into a "current" or "valid"
+ * denomination key by adding the master signature.
+ *
+ * @param cls closure
+ * @param h_denom_pub hash of the denomination public key
+ * @param denom_pub the actual denomination key
+ * @param meta meta data about the denomination
+ * @param master_sig master signature to add
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_add_denomination_key (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_add_policy_fulfillment_proof.c b/src/exchangedb/pg_add_policy_fulfillment_proof.c
new file mode 100644
index 000000000..93d070712
--- /dev/null
+++ b/src/exchangedb/pg_add_policy_fulfillment_proof.c
@@ -0,0 +1,159 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_add_policy_fulfillment_proof.c
+ * @brief Implementation of the add_policy_fulfillment_proof function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_add_policy_fulfillment_proof.h"
+#include "pg_helper.h"
+
+
+/**
+ * Compares two indices into an array of hash codes according to
+ * GNUNET_CRYPTO_hash_cmp of the content at those index positions.
+ *
+ * Used in a call qsort_t in order to generate sorted policy_hash_codes.
+ */
+static int
+hash_code_cmp (
+ const void *hc1,
+ const void *hc2,
+ void *arg)
+{
+ size_t i1 = *(size_t *) hc1;
+ size_t i2 = *(size_t *) hc2;
+ const struct TALER_PolicyDetails *d = arg;
+
+ return GNUNET_CRYPTO_hash_cmp (&d[i1].hash_code,
+ &d[i2].hash_code);
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_add_policy_fulfillment_proof (
+ void *cls,
+ struct TALER_PolicyFulfillmentTransactionData *fulfillment)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct PostgresClosure *pg = cls;
+ size_t count = fulfillment->details_count;
+ /* FIXME: this seems to be prone to VLA attacks */
+ struct GNUNET_HashCode hcs[count];
+
+ /* Create the sorted policy_hash_codes */
+ {
+ size_t idx[count];
+ for (size_t i = 0; i < count; i++)
+ idx[i] = i;
+
+ /* Sort the indices according to the hash codes of the corresponding
+ * details. */
+ qsort_r (idx,
+ count,
+ sizeof(size_t),
+ hash_code_cmp,
+ fulfillment->details);
+
+ /* Finally, concatenate all hash_codes in sorted order */
+ for (size_t i = 0; i < count; i++)
+ hcs[i] = fulfillment->details[idx[i]].hash_code;
+ }
+
+
+ /* Now, add the proof to the policy_fulfillments table, retrieve the
+ * record_id */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&fulfillment->timestamp),
+ TALER_PQ_query_param_json (fulfillment->proof),
+ GNUNET_PQ_query_param_auto_from_type (&fulfillment->h_proof),
+ TALER_PQ_query_param_array_hash_code (count, hcs, pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("fulfillment_id",
+ &fulfillment->fulfillment_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "insert_proof_into_policy_fulfillments",
+ "INSERT INTO policy_fulfillments"
+ "(fulfillment_timestamp"
+ ",fulfillment_proof"
+ ",h_fulfillment_proof"
+ ",policy_hash_codes"
+ ") VALUES ($1, $2, $3, $4)"
+ " ON CONFLICT DO NOTHING;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_proof_into_policy_fulfillments",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ return qs;
+ }
+
+ /* Now, set the states of each entry corresponding to the hash_codes in
+ * policy_details accordingly */
+ for (size_t i = 0; i < count; i++)
+ {
+ struct TALER_PolicyDetails *pos = &fulfillment->details[i];
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&pos->hash_code),
+ GNUNET_PQ_query_param_timestamp (&pos->deadline),
+ TALER_PQ_query_param_amount (pg->conn,
+ &pos->commitment),
+ TALER_PQ_query_param_amount (pg->conn,
+ &pos->accumulated_total),
+ TALER_PQ_query_param_amount (pg->conn,
+ &pos->policy_fee),
+ TALER_PQ_query_param_amount (pg->conn,
+ &pos->transferable_amount),
+ GNUNET_PQ_query_param_auto_from_type (&pos->fulfillment_state),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "update_policy_details",
+ "UPDATE policy_details SET"
+ " deadline=$2"
+ ",commitment=$3"
+ ",accumulated_total=$4"
+ ",fee=$5"
+ ",transferable=$6"
+ ",fulfillment_state=$7"
+ " WHERE policy_hash_code=$1;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_policy_details",
+ params);
+ if (qs < 0)
+ return qs;
+ }
+ }
+
+ /*
+ * FIXME[oec]-#7999: When all policies of a deposit are fulfilled,
+ * unblock it and trigger a wire-transfer.
+ */
+
+ return qs;
+}
diff --git a/src/exchangedb/pg_add_policy_fulfillment_proof.h b/src/exchangedb/pg_add_policy_fulfillment_proof.h
new file mode 100644
index 000000000..77bddaf08
--- /dev/null
+++ b/src/exchangedb/pg_add_policy_fulfillment_proof.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_add_policy_fulfillment_proof.h
+ * @brief implementation of the add_policy_fulfillment_proof function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ADD_POLICY_FULFILLMENT_PROOF_H
+#define PG_ADD_POLICY_FULFILLMENT_PROOF_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Add a proof of fulfillment into the policy_fulfillments table
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param fulfillment fullfilment transaction data to be added
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_add_policy_fulfillment_proof (
+ void *cls,
+ struct TALER_PolicyFulfillmentTransactionData *fulfillment);
+
+#endif
diff --git a/src/exchangedb/pg_aggregate.c b/src/exchangedb/pg_aggregate.c
new file mode 100644
index 000000000..ba03e4a9c
--- /dev/null
+++ b/src/exchangedb/pg_aggregate.c
@@ -0,0 +1,205 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_aggregate.c
+ * @brief Implementation of the aggregate function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_compute_shard.h"
+#include "pg_event_notify.h"
+#include "pg_aggregate.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_aggregate (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total)
+{
+ struct PostgresClosure *pg = cls;
+ uint64_t deposit_shard = TEH_PG_compute_shard (merchant_pub);
+ struct GNUNET_TIME_Absolute now = {0};
+ uint64_t sum_deposit_value;
+ uint64_t sum_deposit_frac;
+ uint64_t sum_refund_value;
+ uint64_t sum_refund_frac;
+ uint64_t sum_fee_value;
+ uint64_t sum_fee_frac;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_Amount sum_deposit;
+ struct TALER_Amount sum_refund;
+ struct TALER_Amount sum_fee;
+ struct TALER_Amount delta;
+
+ now = GNUNET_TIME_absolute_round_down (GNUNET_TIME_absolute_get (),
+ pg->aggregator_shift);
+ PREPARE (pg,
+ "aggregate",
+ "WITH bdep AS (" /* restrict to our merchant and account and mark as done */
+ " UPDATE batch_deposits"
+ " SET done=TRUE"
+ " WHERE NOT (done OR policy_blocked)" /* only actually executable deposits */
+ " AND refund_deadline<$1"
+ " AND shard=$5" /* only for efficiency, merchant_pub is what we really filter by */
+ " AND merchant_pub=$2" /* filter by target merchant */
+ " AND wire_target_h_payto=$3" /* merchant could have a 2nd bank account */
+ " RETURNING"
+ " batch_deposit_serial_id)"
+ " ,cdep AS ("
+ " SELECT"
+ " coin_deposit_serial_id"
+ " ,batch_deposit_serial_id"
+ " ,coin_pub"
+ " ,amount_with_fee AS amount"
+ " FROM coin_deposits"
+ " WHERE batch_deposit_serial_id IN (SELECT batch_deposit_serial_id FROM bdep))"
+ " ,ref AS (" /* find applicable refunds -- NOTE: may do a full join on the master, maybe find a left-join way to integrate with query above to push it to the shards? */
+ " SELECT"
+ " amount_with_fee AS refund"
+ " ,coin_pub"
+ " ,batch_deposit_serial_id" /* theoretically, coin could be in multiple refunded transactions */
+ " FROM refunds"
+ " WHERE coin_pub IN (SELECT coin_pub FROM cdep)"
+ " AND batch_deposit_serial_id IN (SELECT batch_deposit_serial_id FROM bdep))"
+ " ,ref_by_coin AS (" /* total up refunds by coin */
+ " SELECT"
+ " SUM((ref.refund).val) AS sum_refund_val"
+ " ,SUM((ref.refund).frac) AS sum_refund_frac"
+ " ,coin_pub"
+ " ,batch_deposit_serial_id" /* theoretically, coin could be in multiple refunded transactions */
+ " FROM ref"
+ " GROUP BY coin_pub, batch_deposit_serial_id)"
+ " ,norm_ref_by_coin AS (" /* normalize */
+ " SELECT"
+ " sum_refund_val + sum_refund_frac / 100000000 AS norm_refund_val"
+ " ,sum_refund_frac % 100000000 AS norm_refund_frac"
+ " ,coin_pub"
+ " ,batch_deposit_serial_id" /* theoretically, coin could be in multiple refunded transactions */
+ " FROM ref_by_coin)"
+ " ,fully_refunded_coins AS (" /* find applicable refunds -- NOTE: may do a full join on the master, maybe find a left-join way to integrate with query above to push it to the shards? */
+ " SELECT"
+ " cdep.coin_pub"
+ " FROM norm_ref_by_coin norm"
+ " JOIN cdep"
+ " ON (norm.coin_pub = cdep.coin_pub"
+ " AND norm.batch_deposit_serial_id = cdep.batch_deposit_serial_id"
+ " AND norm.norm_refund_val = (cdep.amount).val"
+ " AND norm.norm_refund_frac = (cdep.amount).frac))"
+ " ,fees AS (" /* find deposit fees for not fully refunded deposits */
+ " SELECT"
+ " denom.fee_deposit AS fee"
+ " ,cs.batch_deposit_serial_id" /* ensures we get the fee for each coin, not once per denomination */
+ " FROM cdep cs"
+ " JOIN known_coins kc" /* NOTE: may do a full join on the master, maybe find a left-join way to integrate with query above to push it to the shards? */
+ " USING (coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE coin_pub NOT IN (SELECT coin_pub FROM fully_refunded_coins))"
+ " ,dummy AS (" /* add deposits to aggregation_tracking */
+ " INSERT INTO aggregation_tracking"
+ " (batch_deposit_serial_id"
+ " ,wtid_raw)"
+ " SELECT batch_deposit_serial_id,$4"
+ " FROM bdep)"
+ "SELECT" /* calculate totals (deposits, refunds and fees) */
+ " CAST(COALESCE(SUM((cdep.amount).val),0) AS INT8) AS sum_deposit_value"
+ /* cast needed, otherwise we get NUMBER */
+ " ,COALESCE(SUM((cdep.amount).frac),0) AS sum_deposit_fraction" /* SUM over INT returns INT8 */
+ " ,CAST(COALESCE(SUM((ref.refund).val),0) AS INT8) AS sum_refund_value"
+ " ,COALESCE(SUM((ref.refund).frac),0) AS sum_refund_fraction"
+ " ,CAST(COALESCE(SUM((fees.fee).val),0) AS INT8) AS sum_fee_value"
+ " ,COALESCE(SUM((fees.fee).frac),0) AS sum_fee_fraction"
+ " FROM cdep "
+ " FULL OUTER JOIN ref ON (FALSE)" /* We just want all sums */
+ " FULL OUTER JOIN fees ON (FALSE);");
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_uint64 (&deposit_shard),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("sum_deposit_value",
+ &sum_deposit_value),
+ GNUNET_PQ_result_spec_uint64 ("sum_deposit_fraction",
+ &sum_deposit_frac),
+ GNUNET_PQ_result_spec_uint64 ("sum_refund_value",
+ &sum_refund_value),
+ GNUNET_PQ_result_spec_uint64 ("sum_refund_fraction",
+ &sum_refund_frac),
+ GNUNET_PQ_result_spec_uint64 ("sum_fee_value",
+ &sum_fee_value),
+ GNUNET_PQ_result_spec_uint64 ("sum_fee_fraction",
+ &sum_fee_frac),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "aggregate",
+ params,
+ rs);
+ }
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ total));
+ return qs;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ &sum_deposit));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ &sum_refund));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ &sum_fee));
+ sum_deposit.value = sum_deposit_frac / TALER_AMOUNT_FRAC_BASE
+ + sum_deposit_value;
+ sum_deposit.fraction = sum_deposit_frac % TALER_AMOUNT_FRAC_BASE;
+ sum_refund.value = sum_refund_frac / TALER_AMOUNT_FRAC_BASE
+ + sum_refund_value;
+ sum_refund.fraction = sum_refund_frac % TALER_AMOUNT_FRAC_BASE;
+ sum_fee.value = sum_fee_frac / TALER_AMOUNT_FRAC_BASE
+ + sum_fee_value;
+ sum_fee.fraction = sum_fee_frac % TALER_AMOUNT_FRAC_BASE; \
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&delta,
+ &sum_deposit,
+ &sum_refund));
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (total,
+ &delta,
+ &sum_fee));
+ return qs;
+}
diff --git a/src/exchangedb/pg_aggregate.h b/src/exchangedb/pg_aggregate.h
new file mode 100644
index 000000000..1f986ef9e
--- /dev/null
+++ b/src/exchangedb/pg_aggregate.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_aggregate.h
+ * @brief implementation of the aggregate function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_AGGREGATE_H
+#define PG_AGGREGATE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Aggregate all matching deposits for @a h_payto and
+ * @a merchant_pub, returning the total amounts.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param merchant_pub public key of the merchant
+ * @param wtid wire transfer ID to set for the aggregate
+ * @param[out] total set to the sum of the total deposits minus applicable deposit fees and refunds
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_aggregate (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total);
+
+#endif
diff --git a/src/exchangedb/pg_batch_ensure_coin_known.c b/src/exchangedb/pg_batch_ensure_coin_known.c
new file mode 100644
index 000000000..aca2732c6
--- /dev/null
+++ b/src/exchangedb/pg_batch_ensure_coin_known.c
@@ -0,0 +1,462 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_batch_ensure_coin_known.c
+ * @brief Implementation of the batch_ensure_coin_known function for Postgres
+ * @author Christian Grothoff
+ *
+ * FIXME: use the array support for postgres to simplify this code!
+ *
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_pq_lib.h"
+#include "pg_batch_ensure_coin_known.h"
+#include "pg_helper.h"
+
+
+static enum GNUNET_DB_QueryStatus
+insert1 (struct PostgresClosure *pg,
+ const struct TALER_CoinPublicInfo coin[1],
+ struct TALER_EXCHANGEDB_CoinInfo result[1])
+{
+ enum GNUNET_DB_QueryStatus qs;
+ bool is_denom_pub_hash_null = false;
+ bool is_age_hash_null = false;
+ PREPARE (pg,
+ "batch1_known_coin",
+ "SELECT"
+ " existed1 AS existed"
+ ",known_coin_id1 AS known_coin_id"
+ ",denom_pub_hash1 AS denom_hash"
+ ",age_commitment_hash1 AS h_age_commitment"
+ " FROM exchange_do_batch1_known_coin"
+ " ($1, $2, $3, $4);"
+ );
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("existed",
+ &result[0].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+ &result[0].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &result[0].denom_hash),
+ &is_denom_pub_hash_null),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &result[0].h_age_commitment),
+ &is_age_hash_null),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "batch1_known_coin",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0); /* should be impossible */
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* continued below */
+ }
+
+ if ( (! is_denom_pub_hash_null) &&
+ (0 != GNUNET_memcmp (&result[0].denom_hash,
+ &coin->denom_pub_hash)) )
+ {
+ GNUNET_break_op (0);
+ result[0].denom_conflict = true;
+ }
+
+ if ( (! is_denom_pub_hash_null) &&
+ (0 != GNUNET_memcmp (&result[0].denom_hash,
+ &coin[0].denom_pub_hash)) )
+ {
+ GNUNET_break_op (0);
+ result[0].denom_conflict = true;
+ }
+
+ result[0].age_conflict = TALER_AgeCommitmentHash_NoConflict;
+
+ if (is_age_hash_null != coin[0].no_age_commitment)
+ {
+ if (is_age_hash_null)
+ {
+ GNUNET_break_op (0);
+ result[0].age_conflict = TALER_AgeCommitmentHash_NullExpected;
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ result[0].age_conflict = TALER_AgeCommitmentHash_ValueExpected;
+ }
+ }
+ else if ( (! is_age_hash_null) &&
+ (0 != GNUNET_memcmp (&result[0].h_age_commitment,
+ &coin[0].h_age_commitment)) )
+ {
+ GNUNET_break_op (0);
+ result[0].age_conflict = TALER_AgeCommitmentHash_ValueDiffers;
+ }
+
+ return qs;
+}
+
+
+static enum GNUNET_DB_QueryStatus
+insert2 (struct PostgresClosure *pg,
+ const struct TALER_CoinPublicInfo coin[2],
+ struct TALER_EXCHANGEDB_CoinInfo result[2])
+{
+ enum GNUNET_DB_QueryStatus qs;
+ bool is_denom_pub_hash_null[2] = {false, false};
+ bool is_age_hash_null[2] = {false, false};
+
+ PREPARE (pg,
+ "batch2_known_coin",
+ "SELECT"
+ " existed1 AS existed"
+ ",known_coin_id1 AS known_coin_id"
+ ",denom_pub_hash1 AS denom_hash"
+ ",age_commitment_hash1 AS h_age_commitment"
+ ",existed2 AS existed2"
+ ",known_coin_id2 AS known_coin_id2"
+ ",denom_pub_hash2 AS denom_hash2"
+ ",age_commitment_hash2 AS h_age_commitment2"
+ " FROM exchange_do_batch2_known_coin"
+ " ($1, $2, $3, $4, $5, $6, $7, $8);"
+ );
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+
+ GNUNET_PQ_query_param_auto_from_type (&coin[1].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[1].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[1].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("existed",
+ &result[0].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+ &result[0].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &result[0].denom_hash),
+ &is_denom_pub_hash_null[0]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &result[0].h_age_commitment),
+ &is_age_hash_null[0]),
+ GNUNET_PQ_result_spec_bool ("existed2",
+ &result[1].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id2",
+ &result[1].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash2",
+ &result[1].denom_hash),
+ &is_denom_pub_hash_null[1]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash2",
+ &result[1].h_age_commitment),
+ &is_age_hash_null[1]),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "batch2_known_coin",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0); /* should be impossible */
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* continued below */
+ }
+
+ for (int i = 0; i < 2; i++)
+ {
+ if ( (! is_denom_pub_hash_null[i]) &&
+ (0 != GNUNET_memcmp (&result[i].denom_hash,
+ &coin[i].denom_pub_hash)) )
+ {
+ GNUNET_break_op (0);
+ result[i].denom_conflict = true;
+ }
+
+ result[i].age_conflict = TALER_AgeCommitmentHash_NoConflict;
+
+ if (is_age_hash_null[i] != coin[i].no_age_commitment)
+ {
+ if (is_age_hash_null[i])
+ {
+ GNUNET_break_op (0);
+ result[i].age_conflict = TALER_AgeCommitmentHash_NullExpected;
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ result[i].age_conflict = TALER_AgeCommitmentHash_ValueExpected;
+ }
+ }
+ else if ( (! is_age_hash_null[i]) &&
+ (0 != GNUNET_memcmp (&result[i].h_age_commitment,
+ &coin[i].h_age_commitment)) )
+ {
+ GNUNET_break_op (0);
+ result[i].age_conflict = TALER_AgeCommitmentHash_ValueDiffers;
+ }
+ }
+
+ return qs;
+}
+
+
+static enum GNUNET_DB_QueryStatus
+insert4 (struct PostgresClosure *pg,
+ const struct TALER_CoinPublicInfo coin[4],
+ struct TALER_EXCHANGEDB_CoinInfo result[4])
+{
+ enum GNUNET_DB_QueryStatus qs;
+ bool is_denom_pub_hash_null[4] = {false, false, false, false};
+ bool is_age_hash_null[4] = {false, false, false, false};
+ PREPARE (pg,
+ "batch4_known_coin",
+ "SELECT"
+ " existed1 AS existed"
+ ",known_coin_id1 AS known_coin_id"
+ ",denom_pub_hash1 AS denom_hash"
+ ",age_commitment_hash1 AS h_age_commitment"
+ ",existed2 AS existed2"
+ ",known_coin_id2 AS known_coin_id2"
+ ",denom_pub_hash2 AS denom_hash2"
+ ",age_commitment_hash2 AS h_age_commitment2"
+ ",existed3 AS existed3"
+ ",known_coin_id3 AS known_coin_id3"
+ ",denom_pub_hash3 AS denom_hash3"
+ ",age_commitment_hash3 AS h_age_commitment3"
+ ",existed4 AS existed4"
+ ",known_coin_id4 AS known_coin_id4"
+ ",denom_pub_hash4 AS denom_hash4"
+ ",age_commitment_hash4 AS h_age_commitment4"
+ " FROM exchange_do_batch2_known_coin"
+ " ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16);"
+ );
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[0].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+
+ GNUNET_PQ_query_param_auto_from_type (&coin[1].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[1].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[1].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[0].denom_sig),
+
+ GNUNET_PQ_query_param_auto_from_type (&coin[2].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[2].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[2].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[2].denom_sig),
+
+ GNUNET_PQ_query_param_auto_from_type (&coin[3].coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin[3].denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (&coin[3].h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin[3].denom_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("existed",
+ &result[0].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+ &result[0].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &result[0].denom_hash),
+ &is_denom_pub_hash_null[0]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &result[0].h_age_commitment),
+ &is_age_hash_null[0]),
+ GNUNET_PQ_result_spec_bool ("existed2",
+ &result[1].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id2",
+ &result[1].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash2",
+ &result[1].denom_hash),
+ &is_denom_pub_hash_null[1]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash2",
+ &result[1].h_age_commitment),
+ &is_age_hash_null[1]),
+ GNUNET_PQ_result_spec_bool ("existed3",
+ &result[2].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id3",
+ &result[2].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash3",
+ &result[2].denom_hash),
+ &is_denom_pub_hash_null[2]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash3",
+ &result[2].h_age_commitment),
+ &is_age_hash_null[2]),
+ GNUNET_PQ_result_spec_bool ("existed4",
+ &result[3].existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id4",
+ &result[3].known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash4",
+ &result[3].denom_hash),
+ &is_denom_pub_hash_null[3]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash4",
+ &result[3].h_age_commitment),
+ &is_age_hash_null[3]),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "batch4_known_coin",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0); /* should be impossible */
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* continued below */
+ }
+
+ for (int i = 0; i < 4; i++)
+ {
+ if ( (! is_denom_pub_hash_null[i]) &&
+ (0 != GNUNET_memcmp (&result[i].denom_hash,
+ &coin[i].denom_pub_hash)) )
+ {
+ GNUNET_break_op (0);
+ result[i].denom_conflict = true;
+ }
+
+ result[i].age_conflict = TALER_AgeCommitmentHash_NoConflict;
+
+ if (is_age_hash_null[i] != coin[i].no_age_commitment)
+ {
+ if (is_age_hash_null[i])
+ {
+ GNUNET_break_op (0);
+ result[i].age_conflict = TALER_AgeCommitmentHash_NullExpected;
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ result[i].age_conflict = TALER_AgeCommitmentHash_ValueExpected;
+ }
+ }
+ else if ( (! is_age_hash_null[i]) &&
+ (0 != GNUNET_memcmp (&result[i].h_age_commitment,
+ &coin[i].h_age_commitment)) )
+ {
+ GNUNET_break_op (0);
+ result[i].age_conflict = TALER_AgeCommitmentHash_ValueDiffers;
+ }
+ }
+
+ return qs;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_batch_ensure_coin_known (
+ void *cls,
+ const struct TALER_CoinPublicInfo *coin,
+ struct TALER_EXCHANGEDB_CoinInfo *result,
+ unsigned int coin_length,
+ unsigned int batch_size)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs = 0;
+ unsigned int i = 0;
+
+ while ( (qs >= 0) &&
+ (i < coin_length) )
+ {
+ unsigned int bs = GNUNET_MIN (batch_size,
+ coin_length - i);
+ if (bs >= 4)
+ {
+ qs = insert4 (pg,
+ &coin[i],
+ &result[i]);
+ i += 4;
+ continue;
+ }
+ switch (bs)
+ {
+ case 3:
+ case 2:
+ qs = insert2 (pg,
+ &coin[i],
+ &result[i]);
+ i += 2;
+ break;
+ case 1:
+ qs = insert1 (pg,
+ &coin[i],
+ &result[i]);
+ i += 1;
+ break;
+ case 0:
+ GNUNET_assert (0);
+ break;
+ }
+ } /* end while */
+ if (qs < 0)
+ return qs;
+ return i;
+}
diff --git a/src/exchangedb/pg_batch_ensure_coin_known.h b/src/exchangedb/pg_batch_ensure_coin_known.h
new file mode 100644
index 000000000..2c53676d9
--- /dev/null
+++ b/src/exchangedb/pg_batch_ensure_coin_known.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_batch_ensure_coin_known.h
+ * @brief implementation of the batch_ensure_coin_known function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_BATCH_ENSURE_COIN_KNOWN_H
+#define PG_BATCH_ENSURE_COIN_KNOWN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Make sure the array of given @a coin is known to the database.
+ *
+ * @param cls database connection plugin state
+ * @param coin array of coins that must be made known
+ * @param[out] result array where to store information about each coin
+ * @param coin_length length of the @a coin and @a result arraysf
+ * @param batch_size desired (maximum) batch size
+ * @return database transaction status, non-negative on success
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_batch_ensure_coin_known (
+ void *cls,
+ const struct TALER_CoinPublicInfo *coin,
+ struct TALER_EXCHANGEDB_CoinInfo *result,
+ unsigned int coin_length,
+ unsigned int batch_size);
+
+#endif
diff --git a/src/exchangedb/pg_begin_revolving_shard.c b/src/exchangedb/pg_begin_revolving_shard.c
new file mode 100644
index 000000000..86cdf80fd
--- /dev/null
+++ b/src/exchangedb/pg_begin_revolving_shard.c
@@ -0,0 +1,263 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_begin_revolving_shard.c
+ * @brief Implementation of the begin_revolving_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_begin_revolving_shard.h"
+#include "pg_commit.h"
+#include "pg_helper.h"
+#include "pg_start.h"
+#include "pg_rollback.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_begin_revolving_shard (void *cls,
+ const char *job_name,
+ uint32_t shard_size,
+ uint32_t shard_limit,
+ uint32_t *start_row,
+ uint32_t *end_row)
+{
+ struct PostgresClosure *pg = cls;
+
+ GNUNET_assert (shard_limit <= 1U + (uint32_t) INT_MAX);
+ GNUNET_assert (shard_limit > 0);
+ GNUNET_assert (shard_size > 0);
+ for (unsigned int retries = 0; retries<3; retries++)
+ {
+ if (GNUNET_OK !=
+ TEH_PG_start (pg,
+ "begin_revolving_shard"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ /* First, find last 'end_row' */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ uint32_t last_end;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint32 ("end_row",
+ &last_end),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_begin_revolving_shard() */
+ PREPARE (pg,
+ "get_last_revolving_shard",
+ "SELECT"
+ " end_row"
+ " FROM revolving_work_shards"
+ " WHERE job_name=$1"
+ " ORDER BY end_row DESC"
+ " LIMIT 1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_last_revolving_shard",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ *start_row = 1U + last_end;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *start_row = 0; /* base-case: no shards yet */
+ break; /* continued below */
+ }
+ } /* get_last_shard */
+
+ if (*start_row < shard_limit)
+ {
+ /* Claim fresh shard */
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_uint32 (start_row),
+ GNUNET_PQ_query_param_uint32 (end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ *end_row = GNUNET_MIN (shard_limit,
+ *start_row + shard_size - 1);
+ now = GNUNET_TIME_absolute_get ();
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to claim shard %llu-%llu\n",
+ (unsigned long long) *start_row,
+ (unsigned long long) *end_row);
+
+ /* Used in #postgres_claim_revolving_shard() */
+ PREPARE (pg,
+ "create_revolving_shard",
+ "INSERT INTO revolving_work_shards"
+ "(job_name"
+ ",last_attempt"
+ ",start_row"
+ ",end_row"
+ ",active"
+ ") VALUES "
+ "($1, $2, $3, $4, TRUE);");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "create_revolving_shard",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* continued below (with commit) */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* someone else got this shard already,
+ try again */
+ TEH_PG_rollback (pg);
+ continue;
+ }
+ } /* end create fresh reovlving shard */
+ else
+ {
+ /* claim oldest existing shard */
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint32 ("start_row",
+ start_row),
+ GNUNET_PQ_result_spec_uint32 ("end_row",
+ end_row),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_begin_revolving_shard() */
+ PREPARE (pg,
+ "get_open_revolving_shard",
+ "SELECT"
+ " start_row"
+ ",end_row"
+ " FROM revolving_work_shards"
+ " WHERE job_name=$1"
+ " AND active=FALSE"
+ " ORDER BY last_attempt ASC"
+ " LIMIT 1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_open_revolving_shard",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* no open shards available */
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_uint32 (start_row),
+ GNUNET_PQ_query_param_uint32 (end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ now = GNUNET_TIME_timestamp_get ();
+
+ /* Used in #postgres_begin_revolving_shard() */
+ PREPARE (pg,
+ "reclaim_revolving_shard",
+ "UPDATE revolving_work_shards"
+ " SET last_attempt=$2"
+ " ,active=TRUE"
+ " WHERE job_name=$1"
+ " AND start_row=$3"
+ " AND end_row=$4");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "reclaim_revolving_shard",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* continue with commit */
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0); /* logic error, should be impossible */
+ TEH_PG_rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ break; /* continue with commit */
+ }
+ } /* end claim oldest existing shard */
+
+ /* commit */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_PG_commit (pg);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ }
+ } /* retry 'for' loop */
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
diff --git a/src/exchangedb/pg_begin_revolving_shard.h b/src/exchangedb/pg_begin_revolving_shard.h
new file mode 100644
index 000000000..0755f886b
--- /dev/null
+++ b/src/exchangedb/pg_begin_revolving_shard.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_begin_revolving_shard.h
+ * @brief implementation of the begin_revolving_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_BEGIN_REVOLVING_SHARD_H
+#define PG_BEGIN_REVOLVING_SHARD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to grab a revolving work shard on an operation @a op. Runs
+ * in its own transaction. Returns the oldest inactive shard.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to grab a revolving shard for
+ * @param shard_size desired shard size
+ * @param shard_limit exclusive end of the shard range
+ * @param[out] start_row inclusive start row of the shard (returned)
+ * @param[out] end_row inclusive end row of the shard (returned)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_begin_revolving_shard (void *cls,
+ const char *job_name,
+ uint32_t shard_size,
+ uint32_t shard_limit,
+ uint32_t *start_row,
+ uint32_t *end_row);
+
+#endif
diff --git a/src/exchangedb/pg_begin_shard.c b/src/exchangedb/pg_begin_shard.c
new file mode 100644
index 000000000..48e077990
--- /dev/null
+++ b/src/exchangedb/pg_begin_shard.c
@@ -0,0 +1,266 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_begin_shard.c
+ * @brief Implementation of the begin_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_begin_shard.h"
+#include "pg_helper.h"
+#include "pg_start.h"
+#include "pg_rollback.h"
+#include "pg_commit.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_begin_shard (void *cls,
+ const char *job_name,
+ struct GNUNET_TIME_Relative delay,
+ uint64_t shard_size,
+ uint64_t *start_row,
+ uint64_t *end_row)
+{
+ struct PostgresClosure *pg = cls;
+
+ for (unsigned int retries = 0; retries<10; retries++)
+ {
+ if (GNUNET_OK !=
+ TEH_PG_start (pg,
+ "begin_shard"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ {
+ struct GNUNET_TIME_Absolute past;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_absolute_time (&past),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("start_row",
+ start_row),
+ GNUNET_PQ_result_spec_uint64 ("end_row",
+ end_row),
+ GNUNET_PQ_result_spec_end
+ };
+
+ past = GNUNET_TIME_absolute_get ();
+ PREPARE (pg,
+ "get_open_shard",
+ "SELECT"
+ " start_row"
+ ",end_row"
+ " FROM work_shards"
+ " WHERE job_name=$1"
+ " AND completed=FALSE"
+ " AND last_attempt<$2"
+ " ORDER BY last_attempt ASC"
+ " LIMIT 1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_open_shard",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization error on getting open shard\n");
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_uint64 (start_row),
+ GNUNET_PQ_query_param_uint64 (end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ now = GNUNET_TIME_relative_to_absolute (delay);
+ PREPARE (pg,
+ "reclaim_shard",
+ "UPDATE work_shards"
+ " SET last_attempt=$2"
+ " WHERE job_name=$1"
+ " AND start_row=$3"
+ " AND end_row=$4");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "reclaim_shard",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization error on claiming open shard\n");
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ goto commit;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0); /* logic error, should be impossible */
+ TEH_PG_rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ break; /* actually unreachable */
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ break; /* continued below */
+ }
+ } /* get_open_shard */
+
+ /* No open shard, find last 'end_row' */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("end_row",
+ start_row),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_last_shard",
+ "SELECT"
+ " end_row"
+ " FROM work_shards"
+ " WHERE job_name=$1"
+ " ORDER BY end_row DESC"
+ " LIMIT 1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_last_shard",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization error on getting last shard\n");
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *start_row = 0; /* base-case: no shards yet */
+ break; /* continued below */
+ }
+ *end_row = *start_row + shard_size;
+ } /* get_last_shard */
+
+ /* Claim fresh shard */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_uint64 (start_row),
+ GNUNET_PQ_query_param_uint64 (end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ now = GNUNET_TIME_relative_to_absolute (delay);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Trying to claim shard (%llu-%llu]\n",
+ (unsigned long long) *start_row,
+ (unsigned long long) *end_row);
+
+ PREPARE (pg,
+ "claim_next_shard",
+ "INSERT INTO work_shards"
+ "(job_name"
+ ",last_attempt"
+ ",start_row"
+ ",end_row"
+ ") VALUES "
+ "($1, $2, $3, $4);");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "claim_next_shard",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization error on claiming next shard\n");
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* continued below */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* someone else got this shard already,
+ try again */
+ TEH_PG_rollback (pg);
+ continue;
+ }
+ } /* claim_next_shard */
+
+ /* commit */
+commit:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_PG_commit (pg);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Serialization error on commit for beginning shard\n");
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Claimed new shard\n");
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ }
+ } /* retry 'for' loop */
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
diff --git a/src/exchangedb/pg_begin_shard.h b/src/exchangedb/pg_begin_shard.h
new file mode 100644
index 000000000..16f19491d
--- /dev/null
+++ b/src/exchangedb/pg_begin_shard.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_begin_shard.h
+ * @brief implementation of the begin_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_BEGIN_SHARD_H
+#define PG_BEGIN_SHARD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called to grab a work shard on an operation @a op. Runs in its
+ * own transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to grab a word shard for
+ * @param delay minimum age of a shard to grab
+ * @param shard_size desired shard size
+ * @param[out] start_row inclusive start row of the shard (returned)
+ * @param[out] end_row exclusive end row of the shard (returned)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_begin_shard (void *cls,
+ const char *job_name,
+ struct GNUNET_TIME_Relative delay,
+ uint64_t shard_size,
+ uint64_t *start_row,
+ uint64_t *end_row);
+
+#endif
diff --git a/src/exchangedb/pg_commit.c b/src/exchangedb/pg_commit.c
new file mode 100644
index 000000000..8c4f87c90
--- /dev/null
+++ b/src/exchangedb/pg_commit.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_commit.c
+ * @brief Implementation of the commit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_commit.h"
+#include "pg_helper.h"
+
+
+/**
+ * Commit the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return final transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_commit (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_break (NULL != pg->transaction_name);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Committing transaction `%s'\n",
+ pg->transaction_name);
+ /* used in #postgres_commit */
+ PREPARE (pg,
+ "do_commit",
+ "COMMIT");
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "do_commit",
+ params);
+ pg->transaction_name = NULL;
+ return qs;
+}
diff --git a/src/exchangedb/pg_commit.h b/src/exchangedb/pg_commit.h
new file mode 100644
index 000000000..b1f4f9615
--- /dev/null
+++ b/src/exchangedb/pg_commit.h
@@ -0,0 +1,37 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_commit.h
+ * @brief implementation of the commit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_COMMIT_H
+#define PG_COMMIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Commit the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return final transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_commit (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_complete_shard.c b/src/exchangedb/pg_complete_shard.c
new file mode 100644
index 000000000..8e62809c5
--- /dev/null
+++ b/src/exchangedb/pg_complete_shard.c
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_complete_shard.c
+ * @brief Implementation of the complete_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_complete_shard.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_complete_shard (void *cls,
+ const char *job_name,
+ uint64_t start_row,
+ uint64_t end_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_uint64 (&start_row),
+ GNUNET_PQ_query_param_uint64 (&end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Completing shard %llu-%llu\n",
+ (unsigned long long) start_row,
+ (unsigned long long) end_row);
+ PREPARE (pg,
+ "complete_shard",
+ "UPDATE work_shards"
+ " SET completed=TRUE"
+ " WHERE job_name=$1"
+ " AND start_row=$2"
+ " AND end_row=$3");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "complete_shard",
+ params);
+}
diff --git a/src/exchangedb/pg_complete_shard.h b/src/exchangedb/pg_complete_shard.h
new file mode 100644
index 000000000..f06c0483b
--- /dev/null
+++ b/src/exchangedb/pg_complete_shard.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_complete_shard.h
+ * @brief implementation of the complete_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_COMPLETE_SHARD_H
+#define PG_COMPLETE_SHARD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to persist that work on a shard was completed.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to grab a word shard for
+ * @param start_row inclusive start row of the shard
+ * @param end_row exclusive end row of the shard
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_complete_shard (void *cls,
+ const char *job_name,
+ uint64_t start_row,
+ uint64_t end_row);
+
+#endif
diff --git a/src/exchangedb/pg_compute_shard.c b/src/exchangedb/pg_compute_shard.c
new file mode 100644
index 000000000..1dea591f1
--- /dev/null
+++ b/src/exchangedb/pg_compute_shard.c
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_compute_shard.c
+ * @brief Implementation of the compute_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_compute_shard.h"
+#include "pg_helper.h"
+
+
+uint64_t
+TEH_PG_compute_shard (const struct TALER_MerchantPublicKeyP *merchant_pub)
+{
+ uint32_t res;
+
+ GNUNET_assert (GNUNET_YES ==
+ GNUNET_CRYPTO_kdf (&res,
+ sizeof (res),
+ merchant_pub,
+ sizeof (*merchant_pub),
+ "VOID",
+ 4,
+ NULL, 0));
+ /* interpret hash result as NBO for platform independence,
+ convert to HBO and map to [0..2^31-1] range */
+ res = ntohl (res);
+ if (res > INT32_MAX)
+ res += INT32_MIN;
+ GNUNET_assert (res <= INT32_MAX);
+ return (uint64_t) res;
+}
diff --git a/src/exchangedb/pg_compute_shard.h b/src/exchangedb/pg_compute_shard.h
new file mode 100644
index 000000000..d9bde2d3e
--- /dev/null
+++ b/src/exchangedb/pg_compute_shard.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_compute_shard.h
+ * @brief implementation of the compute_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_COMPUTE_SHARD_H
+#define PG_COMPUTE_SHARD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Compute the shard number of a given @a merchant_pub.
+ *
+ * @param merchant_pub merchant public key to compute shard for
+ * @return shard number
+ */
+uint64_t
+TEH_PG_compute_shard (const struct TALER_MerchantPublicKeyP *merchant_pub);
+
+
+#endif
diff --git a/src/exchangedb/pg_count_known_coins.c b/src/exchangedb/pg_count_known_coins.c
new file mode 100644
index 000000000..872965ac9
--- /dev/null
+++ b/src/exchangedb/pg_count_known_coins.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_count_known_coins.c
+ * @brief Implementation of the count_known_coins function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_count_known_coins.h"
+#include "pg_helper.h"
+
+long long
+TEH_PG_count_known_coins (void *cls,
+ const struct
+ TALER_DenominationHashP *denom_pub_hash)
+{
+ struct PostgresClosure *pg = cls;
+ uint64_t count;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("count",
+ &count),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+
+ PREPARE (pg,
+ "count_known_coins",
+ "SELECT"
+ " COUNT(*) AS count"
+ " FROM known_coins"
+ " WHERE denominations_serial="
+ " (SELECT denominations_serial"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$1);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "count_known_coins",
+ params,
+ rs);
+ if (0 > qs)
+ return (long long) qs;
+ return (long long) count;
+}
diff --git a/src/exchangedb/pg_count_known_coins.h b/src/exchangedb/pg_count_known_coins.h
new file mode 100644
index 000000000..69f07bdf4
--- /dev/null
+++ b/src/exchangedb/pg_count_known_coins.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_count_known_coins.h
+ * @brief implementation of the count_known_coins function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_COUNT_KNOWN_COINS_H
+#define PG_COUNT_KNOWN_COINS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Count the number of known coins by denomination.
+ *
+ * @param cls database connection plugin state
+ * @param denom_pub_hash denomination to count by
+ * @return number of coins if non-negative, otherwise an `enum GNUNET_DB_QueryStatus`
+ */
+long long
+TEH_PG_count_known_coins (void *cls,
+ const struct
+ TALER_DenominationHashP *denom_pub_hash);
+
+#endif
diff --git a/src/exchangedb/pg_create_aggregation_transient.c b/src/exchangedb/pg_create_aggregation_transient.c
new file mode 100644
index 000000000..4ab537d3a
--- /dev/null
+++ b/src/exchangedb/pg_create_aggregation_transient.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_create_aggregation_transient.c
+ * @brief Implementation of the create_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_create_aggregation_transient.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_create_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *exchange_account_section,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t kyc_requirement_row,
+ const struct TALER_Amount *total)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ total),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_uint64 (&kyc_requirement_row),
+ GNUNET_PQ_query_param_string (exchange_account_section),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "create_aggregation_transient",
+ "INSERT INTO aggregation_transient"
+ " (amount"
+ " ,merchant_pub"
+ " ,wire_target_h_payto"
+ " ,legitimization_requirement_serial_id"
+ " ,exchange_account_section"
+ " ,wtid_raw)"
+ " VALUES ($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "create_aggregation_transient",
+ params);
+}
diff --git a/src/exchangedb/pg_create_aggregation_transient.h b/src/exchangedb/pg_create_aggregation_transient.h
new file mode 100644
index 000000000..2f0a348b2
--- /dev/null
+++ b/src/exchangedb/pg_create_aggregation_transient.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_create_aggregation_transient.h
+ * @brief implementation of the create_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_CREATE_AGGREGATION_TRANSIENT_H
+#define PG_CREATE_AGGREGATION_TRANSIENT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Create a new entry in the transient aggregation table.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param exchange_account_section exchange account to use
+ * @param merchant_pub public key of the merchant receiving the transfer
+ * @param wtid the raw wire transfer identifier to be used
+ * @param kyc_requirement_row row in legitimization_requirements that need to be satisfied to continue, or 0 for none
+ * @param total amount to be wired in the future
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_create_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *exchange_account_section,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t kyc_requirement_row,
+ const struct TALER_Amount *total);
+
+#endif
diff --git a/src/exchangedb/pg_create_tables.c b/src/exchangedb/pg_create_tables.c
new file mode 100644
index 000000000..f6a061904
--- /dev/null
+++ b/src/exchangedb/pg_create_tables.c
@@ -0,0 +1,72 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_create_tables.c
+ * @brief Implementation of the create_tables function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_create_tables.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_create_tables (void *cls,
+ bool support_partitions,
+ uint32_t num_partitions)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_Context *conn;
+ enum GNUNET_GenericReturnValue ret = GNUNET_OK;
+ struct GNUNET_PQ_QueryParam params[] = {
+ support_partitions
+ ? GNUNET_PQ_query_param_uint32 (&num_partitions)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_PreparedStatement ps[] = {
+ GNUNET_PQ_make_prepare ("create_tables",
+ "SELECT"
+ " exchange.do_create_tables"
+ " ($1);"),
+ GNUNET_PQ_PREPARED_STATEMENT_END
+ };
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+ "exchangedb-postgres",
+ "exchange-",
+ es,
+ ps);
+ if (NULL == conn)
+ return GNUNET_SYSERR;
+ if (0 >
+ GNUNET_PQ_eval_prepared_non_select (conn,
+ "create_tables",
+ params))
+ ret = GNUNET_SYSERR;
+ if (GNUNET_OK == ret)
+ ret = GNUNET_PQ_exec_sql (conn,
+ "procedures");
+ GNUNET_PQ_disconnect (conn);
+ return ret;
+}
diff --git a/src/exchangedb/pg_create_tables.h b/src/exchangedb/pg_create_tables.h
new file mode 100644
index 000000000..58f5aae73
--- /dev/null
+++ b/src/exchangedb/pg_create_tables.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_create_tables.h
+ * @brief implementation of the create_tables function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_CREATE_TABLES_H
+#define PG_CREATE_TABLES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Create the necessary tables if they are not present
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param support_partitions true to enable partitioning support (disables foreign key constraints)
+ * @param num_partitions number of partitions to create,
+ * (0 to not actually use partitions, 1 to only
+ * setup a default partition, >1 for real partitions)
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_create_tables (void *cls,
+ bool support_partitions,
+ uint32_t num_partitions);
+
+
+#endif
diff --git a/src/exchangedb/pg_delete_aggregation_transient.c b/src/exchangedb/pg_delete_aggregation_transient.c
new file mode 100644
index 000000000..63c5c0a23
--- /dev/null
+++ b/src/exchangedb/pg_delete_aggregation_transient.c
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_delete_aggregation_transient.c
+ * @brief Implementation of the delete_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_delete_aggregation_transient.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_delete_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "delete_aggregation_transient",
+ "DELETE FROM aggregation_transient"
+ " WHERE wire_target_h_payto=$1"
+ " AND wtid_raw=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_aggregation_transient",
+ params);
+}
diff --git a/src/exchangedb/pg_delete_aggregation_transient.h b/src/exchangedb/pg_delete_aggregation_transient.h
new file mode 100644
index 000000000..f74b0179e
--- /dev/null
+++ b/src/exchangedb/pg_delete_aggregation_transient.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_delete_aggregation_transient.h
+ * @brief implementation of the delete_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DELETE_AGGREGATION_TRANSIENT_H
+#define PG_DELETE_AGGREGATION_TRANSIENT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Delete existing entry in the transient aggregation table.
+ * @a h_payto is only needed for query performance.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param wtid the raw wire transfer identifier to update
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_delete_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid);
+
+#endif
diff --git a/src/exchangedb/pg_delete_shard_locks.c b/src/exchangedb/pg_delete_shard_locks.c
new file mode 100644
index 000000000..dbb0f29ac
--- /dev/null
+++ b/src/exchangedb/pg_delete_shard_locks.c
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_delete_shard_locks.c
+ * @brief Implementation of the delete_shard_locks function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_delete_shard_locks.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_delete_shard_locks (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("DELETE FROM work_shards;"),
+ GNUNET_PQ_make_execute ("DELETE FROM revolving_work_shards;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ return GNUNET_PQ_exec_statements (pg->conn,
+ es);
+}
diff --git a/src/exchangedb/pg_delete_shard_locks.h b/src/exchangedb/pg_delete_shard_locks.h
new file mode 100644
index 000000000..e8df2d67d
--- /dev/null
+++ b/src/exchangedb/pg_delete_shard_locks.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_delete_shard_locks.h
+ * @brief implementation of the delete_shard_locks function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DELETE_SHARD_LOCKS_H
+#define PG_DELETE_SHARD_LOCKS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called to delete all revolving shards.
+ * To be used after a crash or when the shard size is
+ * changed.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @return transaction status code
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_delete_shard_locks (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_do_age_withdraw.c b/src/exchangedb/pg_do_age_withdraw.c
new file mode 100644
index 000000000..970e65b5d
--- /dev/null
+++ b/src/exchangedb/pg_do_age_withdraw.c
@@ -0,0 +1,108 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw.c
+ * @brief Implementation of the do_batch_withdraw function for Postgres
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_pq_lib.h"
+#include "taler_pq_lib.h"
+#include "pg_do_batch_withdraw.h"
+#include "pg_helper.h"
+#include <gnunet/gnunet_time_lib.h>
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_age_withdraw (
+ void *cls,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+ struct GNUNET_TIME_Timestamp now,
+ bool *found,
+ bool *balance_ok,
+ struct TALER_Amount *reserve_balance,
+ bool *age_ok,
+ uint16_t *required_age,
+ uint32_t *reserve_birthday,
+ bool *conflict)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp gc;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ &commitment->amount_with_fee),
+ GNUNET_PQ_query_param_auto_from_type (&commitment->reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (&commitment->reserve_sig),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_timestamp (&gc),
+ GNUNET_PQ_query_param_auto_from_type (&commitment->h_commitment),
+ GNUNET_PQ_query_param_uint16 (&commitment->max_age),
+ GNUNET_PQ_query_param_uint16 (&commitment->noreveal_index),
+ TALER_PQ_query_param_array_blinded_coin_hash (commitment->num_coins,
+ commitment->h_coin_evs,
+ pg->conn),
+ GNUNET_PQ_query_param_array_uint64 (commitment->num_coins,
+ commitment->denom_serials,
+ pg->conn),
+ TALER_PQ_query_param_array_blinded_denom_sig (commitment->num_coins,
+ commitment->denom_sigs,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("reserve_found",
+ found),
+ GNUNET_PQ_result_spec_bool ("balance_ok",
+ balance_ok),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_balance",
+ reserve_balance),
+ GNUNET_PQ_result_spec_bool ("age_ok",
+ age_ok),
+ GNUNET_PQ_result_spec_uint16 ("required_age",
+ required_age),
+ GNUNET_PQ_result_spec_uint32 ("reserve_birthday",
+ reserve_birthday),
+ GNUNET_PQ_result_spec_bool ("conflict",
+ conflict),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ gc = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (now.abs_time,
+ pg->legal_reserve_expiration_time));
+ PREPARE (pg,
+ "call_age_withdraw",
+ "SELECT "
+ " reserve_found"
+ ",balance_ok"
+ ",reserve_balance"
+ ",age_ok"
+ ",required_age"
+ ",reserve_birthday"
+ ",conflict"
+ " FROM exchange_do_age_withdraw"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_age_withdraw",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
+}
diff --git a/src/exchangedb/pg_do_age_withdraw.h b/src/exchangedb/pg_do_age_withdraw.h
new file mode 100644
index 000000000..fb435a309
--- /dev/null
+++ b/src/exchangedb/pg_do_age_withdraw.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_age_withdraw.h
+ * @brief implementation of the do_age_withdraw function for Postgres
+ * @author Özgür Kesim
+ */
+#ifndef PG_DO_AGE_WITHDRAW_H
+#define PG_DO_AGE_WITHDRAW_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform reserve update as part of an age-withdraw operation, checking for
+ * sufficient balance and fulfillment of age requirements. Finally persisting
+ * the withdrawal details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param commitment the commitment with all parameters
+ * @param now current time (rounded)
+ * @param[out] found set to true if the reserve was found
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] reserve_balance set to the original reserve balance (at the start of this transaction)
+ * @param[out] age_ok set to true if no age requirements are present on the reserve
+ * @param[out] required_age if @e age_ok is false, set to the maximum allowed age when withdrawing from this reserve
+ * @param[out] reserve_birthday if @e age_ok is false, set to the birthday of the reserve
+ * @param[out] conflict set to true if there already is an entry in the database for the given pair (h_commitment, reserve_pub)
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_age_withdraw (
+ void *cls,
+ const struct TALER_EXCHANGEDB_AgeWithdraw *commitment,
+ const struct GNUNET_TIME_Timestamp now,
+ bool *found,
+ bool *balance_ok,
+ struct TALER_Amount *reserve_balance,
+ bool *age_ok,
+ uint16_t *required_age,
+ uint32_t *reserve_birthday,
+ bool *conflict);
+
+#endif
diff --git a/src/exchangedb/pg_do_batch_withdraw.c b/src/exchangedb/pg_do_batch_withdraw.c
new file mode 100644
index 000000000..f5571ddbb
--- /dev/null
+++ b/src/exchangedb/pg_do_batch_withdraw.c
@@ -0,0 +1,89 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw.c
+ * @brief Implementation of the do_batch_withdraw function for Postgres
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_batch_withdraw.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_batch_withdraw (
+ void *cls,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *amount,
+ bool do_age_check,
+ bool *found,
+ bool *balance_ok,
+ struct TALER_Amount *reserve_balance,
+ bool *age_ok,
+ uint16_t *allowed_maximum_age,
+ uint64_t *ruuid)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp gc;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ amount),
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_timestamp (&gc),
+ GNUNET_PQ_query_param_bool (do_age_check),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("reserve_found",
+ found),
+ GNUNET_PQ_result_spec_bool ("balance_ok",
+ balance_ok),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_balance",
+ reserve_balance),
+ GNUNET_PQ_result_spec_bool ("age_ok",
+ age_ok),
+ GNUNET_PQ_result_spec_uint16 ("allowed_maximum_age",
+ allowed_maximum_age),
+ GNUNET_PQ_result_spec_uint64 ("ruuid",
+ ruuid),
+ GNUNET_PQ_result_spec_end
+ };
+
+ gc = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (now.abs_time,
+ pg->legal_reserve_expiration_time));
+ PREPARE (pg,
+ "call_batch_withdraw",
+ "SELECT "
+ " reserve_found"
+ ",balance_ok"
+ ",reserve_balance"
+ ",age_ok"
+ ",allowed_maximum_age"
+ ",ruuid"
+ " FROM exchange_do_batch_withdraw"
+ " ($1,$2,$3,$4,$5);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_batch_withdraw",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_batch_withdraw.h b/src/exchangedb/pg_do_batch_withdraw.h
new file mode 100644
index 000000000..486f8d1b2
--- /dev/null
+++ b/src/exchangedb/pg_do_batch_withdraw.h
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw.h
+ * @brief implementation of the do_batch_withdraw function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_BATCH_WITHDRAW_H
+#define PG_DO_BATCH_WITHDRAW_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform reserve update as part of a batch withdraw operation, checking
+ * for sufficient balance. Persisting the withdrawal details is done
+ * separately!
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param now current time (rounded)
+ * @param reserve_pub public key of the reserve to debit
+ * @param amount total amount to withdraw
+ * @param age_check_required if true, fail if age requirements are set on the reserve
+ * @param[out] found set to true if the reserve was found
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] reserve_balance set to the original reserve balance (at the start of this transaction)
+ * @param[out] age_ok set to true if no age requirements are present on the reserve
+ * @param[out] allowed_maximum_age if @e age_ok is false, set to the maximum allowed age when withdrawing from this reserve (client needs to call age-withdraw)
+ * @param[out] ruuid set to the reserve's UUID (reserves table row)
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_batch_withdraw (
+ void *cls,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *amount,
+ bool age_check_required,
+ bool *found,
+ bool *balance_ok,
+ struct TALER_Amount *reserve_balance,
+ bool *age_ok,
+ uint16_t *allowed_maximum_age,
+ uint64_t *ruuid);
+
+#endif
diff --git a/src/exchangedb/pg_do_batch_withdraw_insert.c b/src/exchangedb/pg_do_batch_withdraw_insert.c
new file mode 100644
index 000000000..758f502f2
--- /dev/null
+++ b/src/exchangedb/pg_do_batch_withdraw_insert.c
@@ -0,0 +1,77 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw_insert.c
+ * @brief Implementation of the do_batch_withdraw_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_batch_withdraw_insert.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_batch_withdraw_insert (
+ void *cls,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+ struct GNUNET_TIME_Timestamp now,
+ uint64_t ruuid,
+ bool *denom_unknown,
+ bool *conflict,
+ bool *nonce_reuse)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ NULL == nonce
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (nonce),
+ TALER_PQ_query_param_amount (pg->conn,
+ &collectable->amount_with_fee),
+ GNUNET_PQ_query_param_auto_from_type (&collectable->denom_pub_hash),
+ GNUNET_PQ_query_param_uint64 (&ruuid),
+ GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_sig),
+ GNUNET_PQ_query_param_auto_from_type (&collectable->h_coin_envelope),
+ TALER_PQ_query_param_blinded_denom_sig (&collectable->sig),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("denom_unknown",
+ denom_unknown),
+ GNUNET_PQ_result_spec_bool ("conflict",
+ conflict),
+ GNUNET_PQ_result_spec_bool ("nonce_reuse",
+ nonce_reuse),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "call_batch_withdraw_insert",
+ "SELECT "
+ " out_denom_unknown AS denom_unknown"
+ ",out_conflict AS conflict"
+ ",out_nonce_reuse AS nonce_reuse"
+ " FROM exchange_do_batch_withdraw_insert"
+ " ($1,$2,$3,$4,$5,$6,$7,$8);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_batch_withdraw_insert",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_batch_withdraw_insert.h b/src/exchangedb/pg_do_batch_withdraw_insert.h
new file mode 100644
index 000000000..18fcfc9ae
--- /dev/null
+++ b/src/exchangedb/pg_do_batch_withdraw_insert.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_batch_withdraw_insert.h
+ * @brief implementation of the do_batch_withdraw_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_BATCH_WITHDRAW_INSERT_H
+#define PG_DO_BATCH_WITHDRAW_INSERT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform insert as part of a batch withdraw operation, and persisting the
+ * withdrawal details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param nonce client-contributed input for CS denominations that must be checked for idempotency, or NULL for non-CS withdrawals
+ * @param collectable corresponding collectable coin (blind signature)
+ * @param now current time (rounded)
+ * @param ruuid reserve UUID
+ * @param[out] denom_unknown set if the denomination is unknown in the DB
+ * @param[out] conflict if the envelope was already in the DB
+ * @param[out] nonce_reuse if @a nonce was non-NULL and reused
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_batch_withdraw_insert (
+ void *cls,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable,
+ struct GNUNET_TIME_Timestamp now,
+ uint64_t ruuid,
+ bool *denom_unknown,
+ bool *conflict,
+ bool *nonce_reuse);
+
+#endif
diff --git a/src/exchangedb/pg_do_deposit.c b/src/exchangedb/pg_do_deposit.c
new file mode 100644
index 000000000..0ba45b628
--- /dev/null
+++ b/src/exchangedb/pg_do_deposit.c
@@ -0,0 +1,119 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_deposit.c
+ * @brief Implementation of the do_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_deposit.h"
+#include "pg_helper.h"
+#include "pg_compute_shard.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_deposit (
+ void *cls,
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp,
+ bool *balance_ok,
+ uint32_t *bad_balance_index,
+ bool *ctr_conflict)
+{
+ struct PostgresClosure *pg = cls;
+ uint64_t deposit_shard = TEH_PG_compute_shard (&bd->merchant_pub);
+ const struct TALER_CoinSpendPublicKeyP *coin_pubs[GNUNET_NZL (bd->num_cdis)];
+ const struct TALER_CoinSpendSignatureP *coin_sigs[GNUNET_NZL (bd->num_cdis)];
+ struct TALER_Amount amounts_with_fee[GNUNET_NZL (bd->num_cdis)];
+ struct GNUNET_PQ_QueryParam params[] = {
+ /* data for batch_deposits */
+ GNUNET_PQ_query_param_uint64 (&deposit_shard),
+ GNUNET_PQ_query_param_auto_from_type (&bd->merchant_pub),
+ GNUNET_PQ_query_param_timestamp (&bd->wallet_timestamp),
+ GNUNET_PQ_query_param_timestamp (exchange_timestamp),
+ GNUNET_PQ_query_param_timestamp (&bd->refund_deadline),
+ GNUNET_PQ_query_param_timestamp (&bd->wire_deadline),
+ GNUNET_PQ_query_param_auto_from_type (&bd->h_contract_terms),
+ (bd->no_wallet_data_hash)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (&bd->wallet_data_hash),
+ GNUNET_PQ_query_param_auto_from_type (&bd->wire_salt),
+ GNUNET_PQ_query_param_auto_from_type (&bd->wire_target_h_payto),
+ (0 == bd->policy_details_serial_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&bd->policy_details_serial_id),
+ GNUNET_PQ_query_param_bool (bd->policy_blocked),
+ /* to create entry in wire_targets */
+ GNUNET_PQ_query_param_string (bd->receiver_wire_account),
+ /* arrays for coin_deposits */
+ GNUNET_PQ_query_param_array_ptrs_auto_from_type (bd->num_cdis,
+ coin_pubs,
+ pg->conn),
+ GNUNET_PQ_query_param_array_ptrs_auto_from_type (bd->num_cdis,
+ coin_sigs,
+ pg->conn),
+ TALER_PQ_query_param_array_amount (bd->num_cdis,
+ amounts_with_fee,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ bool no_time;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("exchange_timestamp",
+ exchange_timestamp),
+ &no_time),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("insufficient_balance_coin_index",
+ bad_balance_index),
+ balance_ok),
+ GNUNET_PQ_result_spec_bool ("conflicted",
+ ctr_conflict),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int i = 0; i < bd->num_cdis; i++)
+ {
+ const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+ = &bd->cdis[i];
+
+ amounts_with_fee[i] = cdi->amount_with_fee;
+ coin_pubs[i] = &cdi->coin.coin_pub;
+ coin_sigs[i] = &cdi->csig;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Do deposit %u = %s\n",
+ i,
+ TALER_B2S (&cdi->coin.coin_pub));
+ }
+ PREPARE (pg,
+ "call_deposit",
+ "SELECT "
+ " out_exchange_timestamp AS exchange_timestamp"
+ ",out_insufficient_balance_coin_index AS insufficient_balance_coin_index"
+ ",out_conflict AS conflicted"
+ " FROM exchange_do_deposit"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_deposit",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ return qs;
+}
diff --git a/src/exchangedb/pg_do_deposit.h b/src/exchangedb/pg_do_deposit.h
new file mode 100644
index 000000000..449ec04be
--- /dev/null
+++ b/src/exchangedb/pg_do_deposit.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_deposit.h
+ * @brief implementation of the do_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_DEPOSIT_H
+#define PG_DO_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Perform deposit operation, checking for sufficient balance
+ * of the coins and possibly persisting the deposit details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param bd batch deposit operation details
+ * @param[in,out] exchange_timestamp time to use for the deposit (possibly updated)
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @param[out] bad_balance_index set to the first index of a coin for which the balance was insufficient,
+ * only used if @a balance_ok is set to false.
+ * @param[out] in_conflict set to true if the deposit conflicted
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_deposit (
+ void *cls,
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp,
+ bool *balance_ok,
+ uint32_t *bad_balance_index,
+ bool *in_conflict);
+
+#endif
diff --git a/src/exchangedb/pg_do_melt.c b/src/exchangedb/pg_do_melt.c
new file mode 100644
index 000000000..0b26386d8
--- /dev/null
+++ b/src/exchangedb/pg_do_melt.c
@@ -0,0 +1,82 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_melt.c
+ * @brief Implementation of the do_melt function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_melt.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_melt (
+ void *cls,
+ const struct TALER_RefreshMasterSecretP *rms,
+ struct TALER_EXCHANGEDB_Refresh *refresh,
+ uint64_t known_coin_id,
+ bool *zombie_required,
+ bool *balance_ok)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ NULL == rms
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (rms),
+ TALER_PQ_query_param_amount (pg->conn,
+ &refresh->amount_with_fee),
+ GNUNET_PQ_query_param_auto_from_type (&refresh->rc),
+ GNUNET_PQ_query_param_auto_from_type (&refresh->coin.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&refresh->coin_sig),
+ GNUNET_PQ_query_param_uint64 (&known_coin_id),
+ GNUNET_PQ_query_param_uint32 (&refresh->noreveal_index),
+ GNUNET_PQ_query_param_bool (*zombie_required),
+ GNUNET_PQ_query_param_end
+ };
+ bool is_null;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("balance_ok",
+ balance_ok),
+ GNUNET_PQ_result_spec_bool ("zombie_required",
+ zombie_required),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("noreveal_index",
+ &refresh->noreveal_index),
+ &is_null),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "call_melt",
+ "SELECT "
+ " out_balance_ok AS balance_ok"
+ ",out_zombie_bad AS zombie_required"
+ ",out_noreveal_index AS noreveal_index"
+ " FROM exchange_do_melt"
+ " ($1,$2,$3,$4,$5,$6,$7,$8);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_melt",
+ params,
+ rs);
+ if (is_null)
+ refresh->noreveal_index = UINT32_MAX; /* set to very invalid value */
+ return qs;
+}
diff --git a/src/exchangedb/pg_do_melt.h b/src/exchangedb/pg_do_melt.h
new file mode 100644
index 000000000..554ce08b2
--- /dev/null
+++ b/src/exchangedb/pg_do_melt.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_melt.h
+ * @brief implementation of the do_melt function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_MELT_H
+#define PG_DO_MELT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform melt operation, checking for sufficient balance
+ * of the coin and possibly persisting the melt details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param rms client-contributed input for CS denominations that must be checked for idempotency, or NULL for non-CS withdrawals
+ * @param[in,out] refresh refresh operation details; the noreveal_index
+ * is set in case the coin was already melted before
+ * @param known_coin_id row of the coin in the known_coins table
+ * @param[in,out] zombie_required true if the melt must only succeed if the coin is a zombie, set to false if the requirement was satisfied
+ * @param[out] balance_ok set to true if the balance was sufficient
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_melt (
+ void *cls,
+ const struct TALER_RefreshMasterSecretP *rms,
+ struct TALER_EXCHANGEDB_Refresh *refresh,
+ uint64_t known_coin_id,
+ bool *zombie_required,
+ bool *balance_ok);
+
+#endif
diff --git a/src/exchangedb/pg_do_purse_delete.c b/src/exchangedb/pg_do_purse_delete.c
new file mode 100644
index 000000000..27b81cab9
--- /dev/null
+++ b/src/exchangedb/pg_do_purse_delete.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_purse_delete.c
+ * @brief Implementation of the do_purse_delete function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_purse_delete.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_purse_delete (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig,
+ bool *decided,
+ bool *found)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (purse_sig),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("decided",
+ decided),
+ GNUNET_PQ_result_spec_bool ("found",
+ found),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "call_purse_delete",
+ "SELECT "
+ " out_decided AS decided"
+ ",out_found AS found"
+ " FROM exchange_do_purse_delete"
+ " ($1,$2,$3);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_purse_delete",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_purse_delete.h b/src/exchangedb/pg_do_purse_delete.h
new file mode 100644
index 000000000..01c7dd91b
--- /dev/null
+++ b/src/exchangedb/pg_do_purse_delete.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_purse_delete.h
+ * @brief implementation of the do_purse_delete function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_PURSE_DELETE_H
+#define PG_DO_PURSE_DELETE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to explicitly delete a purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to delete
+ * @param purse_sig signature affirming the deletion
+ * @param[out] decided set to true if the purse was
+ * already decided and thus could not be deleted
+ * @param[out] found set to true if the purse was found
+ * (if false, purse could not be deleted)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_purse_delete (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseContractSignatureP *purse_sig,
+ bool *decided,
+ bool *found);
+
+#endif
diff --git a/src/exchangedb/pg_do_purse_deposit.c b/src/exchangedb/pg_do_purse_deposit.c
new file mode 100644
index 000000000..bdb1f4749
--- /dev/null
+++ b/src/exchangedb/pg_do_purse_deposit.c
@@ -0,0 +1,90 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_purse_deposit.c
+ * @brief Implementation of the do_purse_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_purse_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_purse_deposit (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *amount,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_minus_fee,
+ bool *balance_ok,
+ bool *too_late,
+ bool *conflict)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+ struct GNUNET_TIME_Timestamp reserve_expiration;
+ uint64_t partner_id = 0; /* FIXME #7271: WAD support... */
+ struct GNUNET_PQ_QueryParam params[] = {
+ (0 == partner_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&partner_id),
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ amount),
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (coin_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ amount_minus_fee),
+ GNUNET_PQ_query_param_timestamp (&reserve_expiration),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("balance_ok",
+ balance_ok),
+ GNUNET_PQ_result_spec_bool ("too_late",
+ too_late),
+ GNUNET_PQ_result_spec_bool ("conflict",
+ conflict),
+ GNUNET_PQ_result_spec_end
+ };
+
+ reserve_expiration
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
+ pg->legal_reserve_expiration_time));
+
+ PREPARE (pg,
+ "call_purse_deposit",
+ "SELECT "
+ " out_balance_ok AS balance_ok"
+ ",out_conflict AS conflict"
+ ",out_late AS too_late"
+ " FROM exchange_do_purse_deposit"
+ " ($1,$2,$3,$4,$5,$6,$7,$8);");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_purse_deposit",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_purse_deposit.h b/src/exchangedb/pg_do_purse_deposit.h
new file mode 100644
index 000000000..779b6c0c8
--- /dev/null
+++ b/src/exchangedb/pg_do_purse_deposit.h
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_purse_deposit.h
+ * @brief implementation of the do_purse_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_PURSE_DEPOSIT_H
+#define PG_DO_PURSE_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to execute a transaction crediting
+ * a purse with @a amount from @a coin_pub. Reduces the
+ * value of @a coin_pub and increase the balance of
+ * the @a purse_pub purse. If the balance reaches the
+ * target amount and the purse has been merged, triggers
+ * the updates of the reserve/account balance.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to credit
+ * @param coin_pub coin to deposit (debit)
+ * @param amount fraction of the coin's value to deposit
+ * @param coin_sig signature affirming the operation
+ * @param amount_minus_fee amount to add to the purse
+ * @param[out] balance_ok set to false if the coin's
+ * remaining balance is below @a amount;
+ * in this case, the return value will be
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT despite the failure
+ * @param[out] too_late set to true if it is too late to deposit into the purse
+ * @param[out] conflict set to true if the deposit failed due to a conflict (coin already spent,
+ * or deposited into this purse with a different amount)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_purse_deposit (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *amount,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_minus_fee,
+ bool *balance_ok,
+ bool *too_late,
+ bool *conflict);
+
+#endif
diff --git a/src/exchangedb/pg_do_purse_merge.c b/src/exchangedb/pg_do_purse_merge.c
new file mode 100644
index 000000000..5a174ed02
--- /dev/null
+++ b/src/exchangedb/pg_do_purse_merge.c
@@ -0,0 +1,91 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_purse_merge.c
+ * @brief Implementation of the do_purse_merge function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_purse_merge.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_purse_merge (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const char *partner_url,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *no_partner,
+ bool *no_balance,
+ bool *in_conflict)
+{
+ struct PostgresClosure *pg = cls;
+ struct TALER_PaytoHashP h_payto;
+ struct GNUNET_TIME_Timestamp expiration
+ = GNUNET_TIME_relative_to_timestamp (pg->legal_reserve_expiration_time);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (merge_sig),
+ GNUNET_PQ_query_param_timestamp (&merge_timestamp),
+ GNUNET_PQ_query_param_auto_from_type (reserve_sig),
+ (NULL == partner_url)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (partner_url),
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (&h_payto),
+ GNUNET_PQ_query_param_timestamp (&expiration),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("no_partner",
+ no_partner),
+ GNUNET_PQ_result_spec_bool ("no_balance",
+ no_balance),
+ GNUNET_PQ_result_spec_bool ("conflict",
+ in_conflict),
+ GNUNET_PQ_result_spec_end
+ };
+
+ {
+ char *payto_uri;
+
+ payto_uri = TALER_reserve_make_payto (pg->exchange_url,
+ reserve_pub);
+ TALER_payto_hash (payto_uri,
+ &h_payto);
+ GNUNET_free (payto_uri);
+ }
+ PREPARE (pg,
+ "call_purse_merge",
+ "SELECT"
+ " out_no_partner AS no_partner"
+ ",out_no_balance AS no_balance"
+ ",out_conflict AS conflict"
+ " FROM exchange_do_purse_merge"
+ " ($1, $2, $3, $4, $5, $6, $7, $8);");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_purse_merge",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_purse_merge.h b/src/exchangedb/pg_do_purse_merge.h
new file mode 100644
index 000000000..a51de47bf
--- /dev/null
+++ b/src/exchangedb/pg_do_purse_merge.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_purse_merge.h
+ * @brief implementation of the do_purse_merge function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_PURSE_MERGE_H
+#define PG_DO_PURSE_MERGE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to approve merging a purse into a
+ * reserve by the respective purse merge key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to merge
+ * @param merge_sig signature affirming the merge
+ * @param merge_timestamp time of the merge
+ * @param reserve_sig signature of the reserve affirming the merge
+ * @param partner_url URL of the partner exchange, can be NULL if the reserves lives with us
+ * @param reserve_pub public key of the reserve to credit
+ * @param[out] no_partner set to true if @a partner_url is unknown
+ * @param[out] no_balance set to true if the @a purse_pub is not paid up yet
+ * @param[out] in_conflict set to true if @a purse_pub was merged into a different reserve already
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_purse_merge (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const char *partner_url,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *no_partner,
+ bool *no_balance,
+ bool *in_conflict);
+
+#endif
diff --git a/src/exchangedb/pg_do_recoup.c b/src/exchangedb/pg_do_recoup.c
new file mode 100644
index 000000000..07566a607
--- /dev/null
+++ b/src/exchangedb/pg_do_recoup.c
@@ -0,0 +1,85 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_recoup.c
+ * @brief Implementation of the do_recoup function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_recoup.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_recoup (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t reserve_out_serial_id,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t known_coin_id,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ struct GNUNET_TIME_Timestamp *recoup_timestamp,
+ bool *recoup_ok,
+ bool *internal_failure)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp reserve_gc
+ = GNUNET_TIME_relative_to_timestamp (pg->legal_reserve_expiration_time);
+ struct GNUNET_TIME_Timestamp reserve_expiration
+ = GNUNET_TIME_relative_to_timestamp (pg->idle_reserve_expiration_time);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_uint64 (&reserve_out_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (coin_bks),
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_uint64 (&known_coin_id),
+ GNUNET_PQ_query_param_auto_from_type (coin_sig),
+ GNUNET_PQ_query_param_timestamp (&reserve_gc),
+ GNUNET_PQ_query_param_timestamp (&reserve_expiration),
+ GNUNET_PQ_query_param_timestamp (recoup_timestamp),
+ GNUNET_PQ_query_param_end
+ };
+ bool is_null;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ recoup_timestamp),
+ &is_null),
+ GNUNET_PQ_result_spec_bool ("recoup_ok",
+ recoup_ok),
+ GNUNET_PQ_result_spec_bool ("internal_failure",
+ internal_failure),
+ GNUNET_PQ_result_spec_end
+ };
+
+
+ PREPARE (pg,
+ "call_recoup",
+ "SELECT "
+ " out_recoup_timestamp AS recoup_timestamp"
+ ",out_recoup_ok AS recoup_ok"
+ ",out_internal_failure AS internal_failure"
+ " FROM exchange_do_recoup_to_reserve"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_recoup",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_recoup.h b/src/exchangedb/pg_do_recoup.h
new file mode 100644
index 000000000..2cf3eb976
--- /dev/null
+++ b/src/exchangedb/pg_do_recoup.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_recoup.h
+ * @brief implementation of the do_recoup function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_RECOUP_H
+#define PG_DO_RECOUP_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform recoup operation, checking for sufficient deposits
+ * of the coin and possibly persisting the recoup details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub public key of the reserve to credit
+ * @param reserve_out_serial_id row in the reserves_out table justifying the recoup
+ * @param coin_bks coin blinding key secret to persist
+ * @param coin_pub public key of the coin being recouped
+ * @param known_coin_id row of the @a coin_pub in the known_coins table
+ * @param coin_sig signature of the coin requesting the recoup
+ * @param[in,out] recoup_timestamp recoup timestamp, set if recoup existed
+ * @param[out] recoup_ok set if the recoup succeeded (balance ok)
+ * @param[out] internal_failure set on internal failures
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_recoup (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t reserve_out_serial_id,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t known_coin_id,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ struct GNUNET_TIME_Timestamp *recoup_timestamp,
+ bool *recoup_ok,
+ bool *internal_failure);
+
+#endif
diff --git a/src/exchangedb/pg_do_recoup_refresh.c b/src/exchangedb/pg_do_recoup_refresh.c
new file mode 100644
index 000000000..7d099bcd5
--- /dev/null
+++ b/src/exchangedb/pg_do_recoup_refresh.c
@@ -0,0 +1,79 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_recoup_refresh.c
+ * @brief Implementation of the do_recoup_refresh function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_recoup_refresh.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_recoup_refresh (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ uint64_t rrc_serial,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t known_coin_id,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ struct GNUNET_TIME_Timestamp *recoup_timestamp,
+ bool *recoup_ok,
+ bool *internal_failure)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (old_coin_pub),
+ GNUNET_PQ_query_param_uint64 (&rrc_serial),
+ GNUNET_PQ_query_param_auto_from_type (coin_bks),
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_uint64 (&known_coin_id),
+ GNUNET_PQ_query_param_auto_from_type (coin_sig),
+ GNUNET_PQ_query_param_timestamp (recoup_timestamp),
+ GNUNET_PQ_query_param_end
+ };
+ bool is_null;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ recoup_timestamp),
+ &is_null),
+ GNUNET_PQ_result_spec_bool ("recoup_ok",
+ recoup_ok),
+ GNUNET_PQ_result_spec_bool ("internal_failure",
+ internal_failure),
+ GNUNET_PQ_result_spec_end
+ };
+
+
+ PREPARE (pg,
+ "call_recoup_refresh",
+ "SELECT "
+ " out_recoup_timestamp AS recoup_timestamp"
+ ",out_recoup_ok AS recoup_ok"
+ ",out_internal_failure AS internal_failure"
+ " FROM exchange_do_recoup_to_coin"
+ " ($1,$2,$3,$4,$5,$6,$7);");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_recoup_refresh",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_recoup_refresh.h b/src/exchangedb/pg_do_recoup_refresh.h
new file mode 100644
index 000000000..16b0fd208
--- /dev/null
+++ b/src/exchangedb/pg_do_recoup_refresh.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_recoup_refresh.h
+ * @brief implementation of the do_recoup_refresh function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_RECOUP_REFRESH_H
+#define PG_DO_RECOUP_REFRESH_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Perform recoup-refresh operation, checking for sufficient deposits of the
+ * coin and possibly persisting the recoup-refresh details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param old_coin_pub public key of the old coin to credit
+ * @param rrc_serial row in the refresh_revealed_coins table justifying the recoup-refresh
+ * @param coin_bks coin blinding key secret to persist
+ * @param coin_pub public key of the coin being recouped
+ * @param known_coin_id row of the @a coin_pub in the known_coins table
+ * @param coin_sig signature of the coin requesting the recoup
+ * @param[in,out] recoup_timestamp recoup timestamp, set if recoup existed
+ * @param[out] recoup_ok set if the recoup-refresh succeeded (balance ok)
+ * @param[out] internal_failure set on internal failures
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_recoup_refresh (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ uint64_t rrc_serial,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t known_coin_id,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ struct GNUNET_TIME_Timestamp *recoup_timestamp,
+ bool *recoup_ok,
+ bool *internal_failure);
+
+#endif
diff --git a/src/exchangedb/pg_do_refund.c b/src/exchangedb/pg_do_refund.c
new file mode 100644
index 000000000..194dab18d
--- /dev/null
+++ b/src/exchangedb/pg_do_refund.c
@@ -0,0 +1,93 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_refund.c
+ * @brief Implementation of the do_refund function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_refund.h"
+#include "pg_helper.h"
+#include "pg_compute_shard.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_refund (
+ void *cls,
+ const struct TALER_EXCHANGEDB_Refund *refund,
+ const struct TALER_Amount *deposit_fee,
+ uint64_t known_coin_id,
+ bool *not_found,
+ bool *refund_ok,
+ bool *gone,
+ bool *conflict)
+{
+ struct PostgresClosure *pg = cls;
+ uint64_t deposit_shard = TEH_PG_compute_shard (&refund->details.merchant_pub);
+ struct TALER_Amount amount_without_fee;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ &refund->details.refund_amount),
+ TALER_PQ_query_param_amount (pg->conn,
+ &amount_without_fee),
+ TALER_PQ_query_param_amount (pg->conn,
+ deposit_fee),
+ GNUNET_PQ_query_param_auto_from_type (&refund->details.h_contract_terms),
+ GNUNET_PQ_query_param_uint64 (&refund->details.rtransaction_id),
+ GNUNET_PQ_query_param_uint64 (&deposit_shard),
+ GNUNET_PQ_query_param_uint64 (&known_coin_id),
+ GNUNET_PQ_query_param_auto_from_type (&refund->coin.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("not_found",
+ not_found),
+ GNUNET_PQ_result_spec_bool ("refund_ok",
+ refund_ok),
+ GNUNET_PQ_result_spec_bool ("gone",
+ gone),
+ GNUNET_PQ_result_spec_bool ("conflict",
+ conflict),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (0 >
+ TALER_amount_subtract (&amount_without_fee,
+ &refund->details.refund_amount,
+ &refund->details.refund_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ PREPARE (pg,
+ "call_refund",
+ "SELECT "
+ " out_not_found AS not_found"
+ ",out_refund_ok AS refund_ok"
+ ",out_gone AS gone"
+ ",out_conflict AS conflict"
+ " FROM exchange_do_refund"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_refund",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_refund.h b/src/exchangedb/pg_do_refund.h
new file mode 100644
index 000000000..7118858dc
--- /dev/null
+++ b/src/exchangedb/pg_do_refund.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_refund.h
+ * @brief implementation of the do_refund function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_REFUND_H
+#define PG_DO_REFUND_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Perform refund operation, checking for sufficient deposits
+ * of the coin and possibly persisting the refund details.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param refund refund operation details
+ * @param deposit_fee deposit fee applicable for the coin, possibly refunded
+ * @param known_coin_id row of the coin in the known_coins table
+ * @param[out] not_found set if the deposit was not found
+ * @param[out] refund_ok set if the refund succeeded (below deposit amount)
+ * @param[out] gone if the merchant was already paid
+ * @param[out] conflict set if the refund ID was re-used
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_refund (
+ void *cls,
+ const struct TALER_EXCHANGEDB_Refund *refund,
+ const struct TALER_Amount *deposit_fee,
+ uint64_t known_coin_id,
+ bool *not_found,
+ bool *refund_ok,
+ bool *gone,
+ bool *conflict);
+
+#endif
diff --git a/src/exchangedb/pg_do_reserve_open.c b/src/exchangedb/pg_do_reserve_open.c
new file mode 100644
index 000000000..b15c96dd1
--- /dev/null
+++ b/src/exchangedb/pg_do_reserve_open.c
@@ -0,0 +1,101 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_do_reserve_open.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_reserve_open.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_reserve_open (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *total_paid,
+ const struct TALER_Amount *reserve_payment,
+ uint32_t min_purse_limit,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ struct GNUNET_TIME_Timestamp desired_expiration,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_Amount *open_fee,
+ bool *no_funds,
+ struct TALER_Amount *reserve_balance,
+ struct TALER_Amount *open_cost,
+ struct GNUNET_TIME_Timestamp *final_expiration)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ total_paid),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ reserve_payment),
+ GNUNET_PQ_query_param_uint32 (&min_purse_limit),
+ GNUNET_PQ_query_param_uint32 (&pg->def_purse_limit),
+ GNUNET_PQ_query_param_auto_from_type (reserve_sig),
+ GNUNET_PQ_query_param_timestamp (&desired_expiration),
+ GNUNET_PQ_query_param_relative_time (&pg->legal_reserve_expiration_time),
+ GNUNET_PQ_query_param_timestamp (&now),
+ TALER_PQ_query_param_amount (pg->conn,
+ open_fee),
+ GNUNET_PQ_query_param_end
+ };
+ bool no_reserve = true;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount ("out_open_cost",
+ pg->currency,
+ open_cost),
+ TALER_PQ_result_spec_amount ("out_reserve_balance",
+ pg->currency,
+ reserve_balance),
+ GNUNET_PQ_result_spec_timestamp ("out_final_expiration",
+ final_expiration),
+ GNUNET_PQ_result_spec_bool ("out_no_reserve",
+ &no_reserve),
+ GNUNET_PQ_result_spec_bool ("out_no_funds",
+ no_funds),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "do_reserve_open",
+ "SELECT "
+ " out_open_cost"
+ ",out_final_expiration"
+ ",out_no_funds"
+ ",out_no_reserve"
+ ",out_reserve_balance"
+ " FROM exchange_do_reserve_open"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "do_reserve_open",
+ params,
+ rs);
+ if (qs <= 0)
+ return qs;
+ if (no_reserve)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ return qs;
+}
diff --git a/src/exchangedb/pg_do_reserve_open.h b/src/exchangedb/pg_do_reserve_open.h
new file mode 100644
index 000000000..432f3f664
--- /dev/null
+++ b/src/exchangedb/pg_do_reserve_open.h
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_do_reserve_open.h
+ * @brief implementation of the do_reserve_open function
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_RESERVE_OPEN_H
+#define PG_DO_RESERVE_OPEN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Perform reserve open operation on database.
+ *
+ * @param cls closure
+ * @param reserve_pub which reserve is this about?
+ * @param total_paid total amount paid (coins and reserve)
+ * @param reserve_payment amount to be paid from the reserve
+ * @param min_purse_limit minimum number of purses we should be able to open
+ * @param reserve_sig signature by the reserve for the operation
+ * @param desired_expiration when should the reserve expire (earliest time)
+ * @param now when did we the client initiate the action
+ * @param open_fee annual fee to be charged for the open operation by the exchange
+ * @param[out] no_funds set to true if reserve balance is insufficient
+ * @param[out] reserve_balance set to the original reserve balance (at the start of this transaction)
+ * @param[out] open_cost set to the actual cost
+ * @param[out] final_expiration when will the reserve expire now
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_reserve_open (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_Amount *total_paid,
+ const struct TALER_Amount *reserve_payment,
+ uint32_t min_purse_limit,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ struct GNUNET_TIME_Timestamp desired_expiration,
+ struct GNUNET_TIME_Timestamp now,
+ const struct TALER_Amount *open_fee,
+ bool *no_funds,
+ struct TALER_Amount *reserve_balance,
+ struct TALER_Amount *open_cost,
+ struct GNUNET_TIME_Timestamp *final_expiration);
+
+
+#endif
diff --git a/src/exchangedb/pg_do_reserve_purse.c b/src/exchangedb/pg_do_reserve_purse.c
new file mode 100644
index 000000000..e03e23fec
--- /dev/null
+++ b/src/exchangedb/pg_do_reserve_purse.c
@@ -0,0 +1,121 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_reserve_purse.c
+ * @brief Implementation of the do_reserve_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_do_reserve_purse.h"
+#include "pg_helper.h"
+/**
+ * Function called insert request to merge a purse into a reserve by the
+ * respective purse merge key. The purse must not have been merged into a
+ * different reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to merge
+ * @param merge_sig signature affirming the merge
+ * @param merge_timestamp time of the merge
+ * @param reserve_sig signature of the reserve affirming the merge
+ * @param purse_fee amount to charge the reserve for the purse creation, NULL to use the quota
+ * @param reserve_pub public key of the reserve to credit
+ * @param[out] in_conflict set to true if @a purse_pub was merged into a different reserve already
+ * @param[out] no_reserve set to true if @a reserve_pub is not a known reserve
+ * @param[out] insufficient_funds set to true if @a reserve_pub has insufficient capacity to create another purse
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_reserve_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_Amount *purse_fee,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *in_conflict,
+ bool *no_reserve,
+ bool *insufficient_funds)
+{
+ struct PostgresClosure *pg = cls;
+ struct TALER_Amount zero_fee;
+ struct TALER_PaytoHashP h_payto;
+ struct GNUNET_TIME_Timestamp reserve_expiration
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
+ pg->idle_reserve_expiration_time));
+ struct GNUNET_TIME_Timestamp reserve_gc
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
+ pg->legal_reserve_expiration_time));
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (merge_sig),
+ GNUNET_PQ_query_param_timestamp (&merge_timestamp),
+ GNUNET_PQ_query_param_timestamp (&reserve_expiration),
+ GNUNET_PQ_query_param_timestamp (&reserve_gc),
+ GNUNET_PQ_query_param_auto_from_type (reserve_sig),
+ GNUNET_PQ_query_param_bool (NULL == purse_fee),
+ TALER_PQ_query_param_amount (pg->conn,
+ NULL == purse_fee
+ ? &zero_fee
+ : purse_fee),
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (&h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("insufficient_funds",
+ insufficient_funds),
+ GNUNET_PQ_result_spec_bool ("conflict",
+ in_conflict),
+ GNUNET_PQ_result_spec_bool ("no_reserve",
+ no_reserve),
+ GNUNET_PQ_result_spec_end
+ };
+
+ {
+ char *payto_uri;
+
+ payto_uri = TALER_reserve_make_payto (pg->exchange_url,
+ reserve_pub);
+ TALER_payto_hash (payto_uri,
+ &h_payto);
+ GNUNET_free (payto_uri);
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ &zero_fee));
+ /* Used in #postgres_do_reserve_purse() */
+ PREPARE (pg,
+ "call_reserve_purse",
+ "SELECT"
+ " out_no_funds AS insufficient_funds"
+ ",out_no_reserve AS no_reserve"
+ ",out_conflict AS conflict"
+ " FROM exchange_do_reserve_purse"
+ " ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_reserve_purse",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_do_reserve_purse.h b/src/exchangedb/pg_do_reserve_purse.h
new file mode 100644
index 000000000..dde2d6ce1
--- /dev/null
+++ b/src/exchangedb/pg_do_reserve_purse.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_do_reserve_purse.h
+ * @brief implementation of the do_reserve_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DO_RESERVE_PURSE_H
+#define PG_DO_RESERVE_PURSE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called insert request to merge a purse into a reserve by the
+ * respective purse merge key. The purse must not have been merged into a
+ * different reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to merge
+ * @param merge_sig signature affirming the merge
+ * @param merge_timestamp time of the merge
+ * @param reserve_sig signature of the reserve affirming the merge
+ * @param purse_fee amount to charge the reserve for the purse creation, NULL to use the quota
+ * @param reserve_pub public key of the reserve to credit
+ * @param[out] in_conflict set to true if @a purse_pub was merged into a different reserve already
+ * @param[out] no_reserve set to true if @a reserve_pub is not a known reserve
+ * @param[out] insufficient_funds set to true if @a reserve_pub has insufficient capacity to create another purse
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_do_reserve_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergeSignatureP *merge_sig,
+ const struct GNUNET_TIME_Timestamp merge_timestamp,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_Amount *purse_fee,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *in_conflict,
+ bool *no_reserve,
+ bool *insufficient_funds);
+
+#endif
diff --git a/src/exchangedb/pg_drain_kyc_alert.c b/src/exchangedb/pg_drain_kyc_alert.c
new file mode 100644
index 000000000..4388334e9
--- /dev/null
+++ b/src/exchangedb/pg_drain_kyc_alert.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_drain_kyc_alert.c
+ * @brief Implementation of the drain_kyc_alert function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_drain_kyc_alert.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_drain_kyc_alert (void *cls,
+ uint32_t trigger_type,
+ struct TALER_PaytoHashP *h_payto)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint32 (&trigger_type),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_payto",
+ h_payto),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "drain_kyc_alert",
+ "DELETE FROM kyc_alerts"
+ " WHERE trigger_type=$1"
+ " AND h_payto = "
+ " (SELECT h_payto "
+ " FROM kyc_alerts"
+ " WHERE trigger_type=$1"
+ " LIMIT 1)"
+ " RETURNING h_payto;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "drain_kyc_alert",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_drain_kyc_alert.h b/src/exchangedb/pg_drain_kyc_alert.h
new file mode 100644
index 000000000..7425f472d
--- /dev/null
+++ b/src/exchangedb/pg_drain_kyc_alert.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_drain_kyc_alert.h
+ * @brief implementation of the drain_kyc_alert function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DRAIN_KYC_ALERT_H
+#define PG_DRAIN_KYC_ALERT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Extract next KYC alert. Deletes the alert.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param trigger_type which type of alert to drain
+ * @param[out] h_payto set to hash of payto-URI where KYC status changed
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_drain_kyc_alert (void *cls,
+ uint32_t trigger_type,
+ struct TALER_PaytoHashP *h_payto);
+
+#endif
diff --git a/src/exchangedb/pg_drop_tables.c b/src/exchangedb/pg_drop_tables.c
new file mode 100644
index 000000000..55857018b
--- /dev/null
+++ b/src/exchangedb/pg_drop_tables.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_drop_tables.c
+ * @brief Implementation of the drop_tables function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_drop_tables.h"
+#include "pg_helper.h"
+
+
+/**
+ * Drop all Taler tables. This should only be used by testcases.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_drop_tables (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_Context *conn;
+ enum GNUNET_GenericReturnValue ret;
+
+ if (NULL != pg->conn)
+ {
+ GNUNET_PQ_disconnect (pg->conn);
+ pg->conn = NULL;
+ }
+ conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+ "exchangedb-postgres",
+ NULL,
+ NULL,
+ NULL);
+ if (NULL == conn)
+ return GNUNET_SYSERR;
+ ret = GNUNET_PQ_exec_sql (conn,
+ "drop");
+ GNUNET_PQ_disconnect (conn);
+ return ret;
+}
diff --git a/src/exchangedb/pg_drop_tables.h b/src/exchangedb/pg_drop_tables.h
new file mode 100644
index 000000000..58729d5ec
--- /dev/null
+++ b/src/exchangedb/pg_drop_tables.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_drop_tables.h
+ * @brief implementation of the drop_tables function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DROP_TABLES_H
+#define PG_DROP_TABLES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Drop all Taler tables. This should only be used by testcases.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_drop_tables (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_ensure_coin_known.c b/src/exchangedb/pg_ensure_coin_known.c
new file mode 100644
index 000000000..307b8df52
--- /dev/null
+++ b/src/exchangedb/pg_ensure_coin_known.c
@@ -0,0 +1,169 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_ensure_coin_known.c
+ * @brief Implementation of the ensure_coin_known function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_pq_lib.h"
+#include "pg_ensure_coin_known.h"
+#include "pg_helper.h"
+
+
+enum TALER_EXCHANGEDB_CoinKnownStatus
+TEH_PG_ensure_coin_known (void *cls,
+ const struct TALER_CoinPublicInfo *coin,
+ uint64_t *known_coin_id,
+ struct TALER_DenominationHashP *denom_hash,
+ struct TALER_AgeCommitmentHash *h_age_commitment)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ bool existed;
+ bool is_denom_pub_hash_null = false;
+ bool is_age_hash_null = false;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&coin->denom_pub_hash),
+ coin->no_age_commitment
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (&coin->h_age_commitment),
+ TALER_PQ_query_param_denom_sig (&coin->denom_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("existed",
+ &existed),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+ known_coin_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ denom_hash),
+ &is_denom_pub_hash_null),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ h_age_commitment),
+ &is_age_hash_null),
+ GNUNET_PQ_result_spec_end
+ };
+
+ /*
+ See also:
+ https://stackoverflow.com/questions/34708509/how-to-use-returning-with-on-conflict-in-postgresql/37543015#37543015
+ */
+ PREPARE (pg,
+ "insert_known_coin",
+ "WITH dd"
+ " (denominations_serial"
+ " ,coin"
+ " ) AS ("
+ " SELECT "
+ " denominations_serial"
+ " ,coin"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$2"
+ " ), input_rows"
+ " (coin_pub) AS ("
+ " VALUES ($1::BYTEA)"
+ " ), ins AS ("
+ " INSERT INTO known_coins "
+ " (coin_pub"
+ " ,denominations_serial"
+ " ,age_commitment_hash"
+ " ,denom_sig"
+ " ,remaining"
+ " ) SELECT "
+ " $1"
+ " ,denominations_serial"
+ " ,$3"
+ " ,$4"
+ " ,coin"
+ " FROM dd"
+ " ON CONFLICT DO NOTHING" /* CONFLICT on (coin_pub) */
+ " RETURNING "
+ " known_coin_id"
+ " ) "
+ "SELECT "
+ " FALSE AS existed"
+ " ,known_coin_id"
+ " ,NULL AS denom_pub_hash"
+ " ,NULL AS age_commitment_hash"
+ " FROM ins "
+ "UNION ALL "
+ "SELECT "
+ " TRUE AS existed"
+ " ,known_coin_id"
+ " ,denom_pub_hash"
+ " ,kc.age_commitment_hash"
+ " FROM input_rows"
+ " JOIN known_coins kc USING (coin_pub)"
+ " JOIN denominations USING (denominations_serial)"
+ " LIMIT 1");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_known_coin",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_EXCHANGEDB_CKS_HARD_FAIL;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return TALER_EXCHANGEDB_CKS_SOFT_FAIL;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0); /* should be impossible */
+ return TALER_EXCHANGEDB_CKS_HARD_FAIL;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ if (! existed)
+ return TALER_EXCHANGEDB_CKS_ADDED;
+ break; /* continued below */
+ }
+
+ if ( (! is_denom_pub_hash_null) &&
+ (0 != GNUNET_memcmp (&denom_hash->hash,
+ &coin->denom_pub_hash.hash)) )
+ {
+ GNUNET_break_op (0);
+ return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT;
+ }
+
+ if (is_age_hash_null != coin->no_age_commitment)
+ {
+ if (is_age_hash_null)
+ {
+ GNUNET_break_op (0);
+ return TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL;
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ return TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL;
+ }
+ }
+ else if ( (! is_age_hash_null) &&
+ (0 != GNUNET_memcmp (h_age_commitment,
+ &coin->h_age_commitment)) )
+ {
+ GNUNET_break_op (0);
+ return TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS;
+ }
+
+ return TALER_EXCHANGEDB_CKS_PRESENT;
+}
diff --git a/src/exchangedb/pg_ensure_coin_known.h b/src/exchangedb/pg_ensure_coin_known.h
new file mode 100644
index 000000000..68c101735
--- /dev/null
+++ b/src/exchangedb/pg_ensure_coin_known.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_ensure_coin_known.h
+ * @brief implementation of the ensure_coin_known function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ENSURE_COIN_KNOWN_H
+#define PG_ENSURE_COIN_KNOWN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Make sure the given @a coin is known to the database.
+ *
+ * @param cls database connection plugin state
+ * @param coin the coin that must be made known
+ * @param[out] known_coin_id set to the unique row of the coin
+ * @param[out] denom_hash set to the denomination hash of the existing
+ * coin (for conflict error reporting)
+ * @param[out] h_age_commitment set to the conflicting age commitment hash on conflict
+ * @return database transaction status, non-negative on success
+ */
+enum TALER_EXCHANGEDB_CoinKnownStatus
+TEH_PG_ensure_coin_known (void *cls,
+ const struct TALER_CoinPublicInfo *coin,
+ uint64_t *known_coin_id,
+ struct TALER_DenominationHashP *denom_hash,
+ struct TALER_AgeCommitmentHash *h_age_commitment);
+
+#endif
diff --git a/src/exchangedb/pg_event_listen.c b/src/exchangedb/pg_event_listen.c
new file mode 100644
index 000000000..6e1d32843
--- /dev/null
+++ b/src/exchangedb/pg_event_listen.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_event_listen.c
+ * @brief Implementation of the event_listen function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_event_listen.h"
+#include "pg_helper.h"
+
+/**
+ * Register callback to be invoked on events of type @a es.
+ *
+ * @param cls database context to use
+ * @param timeout how long until to generate a timeout event
+ * @param es specification of the event to listen for
+ * @param cb function to call when the event happens, possibly
+ * multiple times (until cancel is invoked)
+ * @param cb_cls closure for @a cb
+ * @return handle useful to cancel the listener
+ */
+struct GNUNET_DB_EventHandler *
+TEH_PG_event_listen (void *cls,
+ struct GNUNET_TIME_Relative timeout,
+ const struct GNUNET_DB_EventHeaderP *es,
+ GNUNET_DB_EventCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+
+ return GNUNET_PQ_event_listen (pg->conn,
+ es,
+ timeout,
+ cb,
+ cb_cls);
+}
diff --git a/src/exchangedb/pg_event_listen.h b/src/exchangedb/pg_event_listen.h
new file mode 100644
index 000000000..7e1e83a0e
--- /dev/null
+++ b/src/exchangedb/pg_event_listen.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_event_listen.h
+ * @brief implementation of the event_listen function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_EVENT_LISTEN_H
+#define PG_EVENT_LISTEN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Register callback to be invoked on events of type @a es.
+ *
+ * @param cls database context to use
+ * @param timeout how long until to generate a timeout event
+ * @param es specification of the event to listen for
+ * @param cb function to call when the event happens, possibly
+ * multiple times (until cancel is invoked)
+ * @param cb_cls closure for @a cb
+ * @return handle useful to cancel the listener
+ */
+struct GNUNET_DB_EventHandler *
+TEH_PG_event_listen (void *cls,
+ struct GNUNET_TIME_Relative timeout,
+ const struct GNUNET_DB_EventHeaderP *es,
+ GNUNET_DB_EventCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_event_listen_cancel.c b/src/exchangedb/pg_event_listen_cancel.c
new file mode 100644
index 000000000..9d776684d
--- /dev/null
+++ b/src/exchangedb/pg_event_listen_cancel.c
@@ -0,0 +1,36 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_event_listen_cancel.c
+ * @brief Implementation of the event_listen_cancel function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_event_listen_cancel.h"
+#include "pg_helper.h"
+
+
+void
+TEH_PG_event_listen_cancel (void *cls,
+ struct GNUNET_DB_EventHandler *eh)
+
+{
+ (void) cls;
+ GNUNET_PQ_event_listen_cancel (eh);
+}
diff --git a/src/exchangedb/pg_event_listen_cancel.h b/src/exchangedb/pg_event_listen_cancel.h
new file mode 100644
index 000000000..258d7a58b
--- /dev/null
+++ b/src/exchangedb/pg_event_listen_cancel.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_event_listen_cancel.h
+ * @brief implementation of the event_listen_cancel function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_EVENT_LISTEN_CANCEL_H
+#define PG_EVENT_LISTEN_CANCEL_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Stop notifications.
+ *
+ * @param cls the plugin's `struct PostgresClosure`
+ * @param eh handle to unregister.
+ */
+void
+TEH_PG_event_listen_cancel (void *cls,
+ struct GNUNET_DB_EventHandler *eh);
+#endif
diff --git a/src/exchangedb/pg_event_notify.c b/src/exchangedb/pg_event_notify.c
new file mode 100644
index 000000000..188855775
--- /dev/null
+++ b/src/exchangedb/pg_event_notify.c
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_event_notify.c
+ * @brief Implementation of the event_notify function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_event_notify.h"
+#include "pg_helper.h"
+
+
+void
+TEH_PG_event_notify (void *cls,
+ const struct GNUNET_DB_EventHeaderP *es,
+ const void *extra,
+ size_t extra_size)
+{
+ struct PostgresClosure *pg = cls;
+
+ GNUNET_PQ_event_notify (pg->conn,
+ es,
+ extra,
+ extra_size);
+}
diff --git a/src/exchangedb/pg_event_notify.h b/src/exchangedb/pg_event_notify.h
new file mode 100644
index 000000000..85069659b
--- /dev/null
+++ b/src/exchangedb/pg_event_notify.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_event_notify.h
+ * @brief implementation of the event_notify function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_EVENT_NOTIFY_H
+#define PG_EVENT_NOTIFY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Notify all that listen on @a es of an event.
+ *
+ * @param cls database context to use
+ * @param es specification of the event to generate
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+void
+TEH_PG_event_notify (void *cls,
+ const struct GNUNET_DB_EventHeaderP *es,
+ const void *extra,
+ size_t extra_size);
+
+#endif
diff --git a/src/exchangedb/pg_expire_purse.c b/src/exchangedb/pg_expire_purse.c
new file mode 100644
index 000000000..6ef7a2779
--- /dev/null
+++ b/src/exchangedb/pg_expire_purse.c
@@ -0,0 +1,69 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_expire_purse.c
+ * @brief Implementation of the expire_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_expire_purse.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_expire_purse (
+ void *cls,
+ struct GNUNET_TIME_Absolute start_time,
+ struct GNUNET_TIME_Absolute end_time)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&start_time),
+ GNUNET_PQ_query_param_absolute_time (&end_time),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ bool found = false;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("found",
+ &found),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+
+ PREPARE (pg,
+ "call_expire_purse",
+ "SELECT "
+ " out_found AS found"
+ " FROM exchange_do_expire_purse"
+ " ($1,$2,$3);");
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_expire_purse",
+ params,
+ rs);
+ if (qs < 0)
+ return qs;
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs);
+ return found
+ ? GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+ : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+}
diff --git a/src/exchangedb/pg_expire_purse.h b/src/exchangedb/pg_expire_purse.h
new file mode 100644
index 000000000..fe05fd52c
--- /dev/null
+++ b/src/exchangedb/pg_expire_purse.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_expire_purse.h
+ * @brief implementation of the expire_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_EXPIRE_PURSE_H
+#define PG_EXPIRE_PURSE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called to clean up one expired purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param start_time select purse expired after this time
+ * @param end_time select purse expired before this time
+ * @return transaction status code (#GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if no purse expired in the given time interval).
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_expire_purse (
+ void *cls,
+ struct GNUNET_TIME_Absolute start_time,
+ struct GNUNET_TIME_Absolute end_time);
+
+#endif
diff --git a/src/exchangedb/pg_find_aggregation_transient.c b/src/exchangedb/pg_find_aggregation_transient.c
new file mode 100644
index 000000000..b931188a8
--- /dev/null
+++ b/src/exchangedb/pg_find_aggregation_transient.c
@@ -0,0 +1,150 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_find_aggregation_transient.c
+ * @brief Implementation of the find_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_find_aggregation_transient.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_refunds_cb().
+ */
+struct FindAggregationTransientContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_TransientAggregationCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct SelectRefundContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+get_transients_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct FindAggregationTransientContext *srctx = cls;
+ struct PostgresClosure *pg = srctx->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount amount;
+ char *payto_uri;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &merchant_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
+ &wtid),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_end
+ };
+ bool cont;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ srctx->status = GNUNET_SYSERR;
+ return;
+ }
+ cont = srctx->cb (srctx->cb_cls,
+ payto_uri,
+ &wtid,
+ &merchant_pub,
+ &amount);
+ GNUNET_free (payto_uri);
+ if (! cont)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_find_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_TransientAggregationCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ struct FindAggregationTransientContext srctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+
+ PREPARE (pg,
+ "find_transient_aggregations",
+ "SELECT"
+ " amount"
+ " ,wtid_raw"
+ " ,merchant_pub"
+ " ,payto_uri"
+ " FROM aggregation_transient atr"
+ " JOIN wire_targets wt USING (wire_target_h_payto)"
+ " WHERE atr.wire_target_h_payto=$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "find_transient_aggregations",
+ params,
+ &get_transients_cb,
+ &srctx);
+ if (GNUNET_SYSERR == srctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_find_aggregation_transient.h b/src/exchangedb/pg_find_aggregation_transient.h
new file mode 100644
index 000000000..c7ba4ea38
--- /dev/null
+++ b/src/exchangedb/pg_find_aggregation_transient.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_find_aggregation_transient.h
+ * @brief implementation of the find_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_FIND_AGGREGATION_TRANSIENT_H
+#define PG_FIND_AGGREGATION_TRANSIENT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Find existing entry in the transient aggregation table.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param cb function to call on each matching entry
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_find_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_TransientAggregationCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_gc.c b/src/exchangedb/pg_gc.c
new file mode 100644
index 000000000..e01c1e101
--- /dev/null
+++ b/src/exchangedb/pg_gc.c
@@ -0,0 +1,80 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_gc.c
+ * @brief Implementation of the gc function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_gc.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_gc (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Absolute long_ago;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&long_ago),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_Context *conn;
+ enum GNUNET_GenericReturnValue ret;
+
+ /* Keep wire fees for 10 years, that should always
+ be enough _and_ they are tiny so it does not
+ matter to make this tight */
+ long_ago = GNUNET_TIME_absolute_subtract (
+ now,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_YEARS,
+ 10));
+ {
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+ struct GNUNET_PQ_PreparedStatement ps[] = {
+ /* Used in #postgres_gc() */
+ GNUNET_PQ_make_prepare ("run_gc",
+ "CALL"
+ " exchange_do_gc"
+ " ($1,$2);"),
+ GNUNET_PQ_PREPARED_STATEMENT_END
+ };
+
+ conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+ "exchangedb-postgres",
+ NULL,
+ es,
+ ps);
+ }
+ if (NULL == conn)
+ return GNUNET_SYSERR;
+ ret = GNUNET_OK;
+ if (0 > GNUNET_PQ_eval_prepared_non_select (conn,
+ "run_gc",
+ params))
+ ret = GNUNET_SYSERR;
+ GNUNET_PQ_disconnect (conn);
+ return ret;
+}
diff --git a/src/exchangedb/pg_gc.h b/src/exchangedb/pg_gc.h
new file mode 100644
index 000000000..803581488
--- /dev/null
+++ b/src/exchangedb/pg_gc.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_gc.h
+ * @brief implementation of the gc function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GC_H
+#define PG_GC_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to perform "garbage collection" on the
+ * database, expiring records we no longer require.
+ *
+ * @param cls closure
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR on DB errors
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_gc (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_age_withdraw.c b/src/exchangedb/pg_get_age_withdraw.c
new file mode 100644
index 000000000..ea4d3b909
--- /dev/null
+++ b/src/exchangedb/pg_get_age_withdraw.c
@@ -0,0 +1,119 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_age_withdraw.c
+ * @brief Implementation of the get_age_withdraw function for Postgres
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_age_withdraw.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_age_withdraw (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_AgeWithdrawCommitmentHashP *ach,
+ struct TALER_EXCHANGEDB_AgeWithdraw *aw)
+{
+ enum GNUNET_DB_QueryStatus ret;
+ struct PostgresClosure *pg = cls;
+ size_t num_sigs;
+ size_t num_hashes;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (ach),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_commitment",
+ &aw->h_commitment),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &aw->reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &aw->reserve_pub),
+ GNUNET_PQ_result_spec_uint16 ("max_age",
+ &aw->max_age),
+ TALER_PQ_result_spec_amount ("amount_with_fee",
+ pg->currency,
+ &aw->amount_with_fee),
+ GNUNET_PQ_result_spec_uint16 ("noreveal_index",
+ &aw->noreveal_index),
+ TALER_PQ_result_spec_array_blinded_coin_hash (
+ pg->conn,
+ "h_blind_evs",
+ &aw->num_coins,
+ &aw->h_coin_evs),
+ TALER_PQ_result_spec_array_blinded_denom_sig (
+ pg->conn,
+ "denom_sigs",
+ &num_sigs,
+ &aw->denom_sigs),
+ TALER_PQ_result_spec_array_denom_hash (
+ pg->conn,
+ "denom_pub_hashes",
+ &num_hashes,
+ &aw->denom_pub_hashes),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_age_withdraw",
+ "SELECT"
+ " h_commitment"
+ ",reserve_sig"
+ ",reserve_pub"
+ ",max_age"
+ ",amount_with_fee"
+ ",noreveal_index"
+ ",h_blind_evs"
+ ",denom_sigs"
+ ",ARRAY("
+ " SELECT denominations.denom_pub_hash FROM ("
+ " SELECT UNNEST(denom_serials) AS id,"
+ " generate_subscripts(denom_serials, 1) AS nr" /* for order */
+ " ) AS denoms"
+ " LEFT JOIN denominations ON denominations.denominations_serial=denoms.id"
+ ") AS denom_pub_hashes"
+ " FROM age_withdraw"
+ " WHERE reserve_pub=$1 and h_commitment=$2;");
+
+ ret = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_age_withdraw",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != ret)
+ return ret;
+
+ if ((aw->num_coins != num_sigs) ||
+ (aw->num_coins != num_hashes))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "got inconsistent number of entries from DB: "
+ "num_coins=%ld, num_sigs=%ld, num_hashes=%ld\n",
+ aw->num_coins,
+ num_sigs,
+ num_hashes);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ return ret;
+}
diff --git a/src/exchangedb/pg_get_age_withdraw.h b/src/exchangedb/pg_get_age_withdraw.h
new file mode 100644
index 000000000..2257aa43c
--- /dev/null
+++ b/src/exchangedb/pg_get_age_withdraw.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_age_withdraw.h
+ * @brief implementation of the get_age_withdraw function for Postgres
+ * @author Özgür KESIM
+ */
+#ifndef PG_GET_AGE_WITHDRAW_H
+#define PG_GET_AGE_WITHDRAW_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Locate the response for a age-withdraw request under a hash that uniquely
+ * identifies the age-withdraw operation. Used to ensure idempotency of the
+ * request.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param reserve_pub public key of the reserve for which the age-withdraw request is made
+ * @param ach hash that uniquely identifies the age-withdraw operation
+ * @param[out] aw corresponding details of the previous age-withdraw request if an entry was found
+ * @return statement execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_age_withdraw (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const struct TALER_AgeWithdrawCommitmentHashP *ach,
+ struct TALER_EXCHANGEDB_AgeWithdraw *aw);
+#endif
diff --git a/src/exchangedb/pg_get_coin_denomination.c b/src/exchangedb/pg_get_coin_denomination.c
new file mode 100644
index 000000000..9f9256f6f
--- /dev/null
+++ b/src/exchangedb/pg_get_coin_denomination.c
@@ -0,0 +1,69 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_coin_denomination.c
+ * @brief Implementation of the get_coin_denomination function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_coin_denomination.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_denomination (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t *known_coin_id,
+ struct TALER_DenominationHashP *denom_hash)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ denom_hash),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+ known_coin_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Getting coin denomination of coin %s\n",
+ TALER_B2S (coin_pub));
+
+ /* Used in #postgres_get_coin_denomination() to fetch
+ the denomination public key hash for
+ a coin known to the exchange. */
+ PREPARE (pg,
+ "get_coin_denomination",
+ "SELECT"
+ " denominations.denom_pub_hash"
+ ",known_coin_id"
+ " FROM known_coins"
+ " JOIN denominations USING (denominations_serial)"
+ " WHERE coin_pub=$1"
+ " FOR SHARE;");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_coin_denomination",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_coin_denomination.h b/src/exchangedb/pg_get_coin_denomination.h
new file mode 100644
index 000000000..3b9f03f31
--- /dev/null
+++ b/src/exchangedb/pg_get_coin_denomination.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_coin_denomination.h
+ * @brief implementation of the get_coin_denomination function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_COIN_DENOMINATION_H
+#define PG_GET_COIN_DENOMINATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Retrieve the denomination of a known coin.
+ *
+ * @param cls the plugin closure
+ * @param coin_pub the public key of the coin to search for
+ * @param[out] known_coin_id set to the ID of the coin in the known_coins table
+ * @param[out] denom_hash where to store the hash of the coins denomination
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_denomination (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t *known_coin_id,
+ struct TALER_DenominationHashP *denom_hash);
+
+#endif
diff --git a/src/exchangedb/pg_get_coin_transactions.c b/src/exchangedb/pg_get_coin_transactions.c
new file mode 100644
index 000000000..fef33a486
--- /dev/null
+++ b/src/exchangedb/pg_get_coin_transactions.c
@@ -0,0 +1,1144 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_get_coin_transactions.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_pq_lib.h"
+#include "pg_get_coin_transactions.h"
+#include "pg_helper.h"
+#include "pg_start_read_committed.h"
+#include "pg_commit.h"
+#include "pg_rollback.h"
+#include "plugin_exchangedb_common.h"
+
+/**
+ * How often do we re-try when encountering DB serialization issues?
+ * (We are read-only, so can only happen due to concurrent insert,
+ * which should be very rare.)
+ */
+#define RETRIES 3
+
+/**
+ * Closure for callbacks called from #postgres_get_coin_transactions()
+ */
+struct CoinHistoryContext
+{
+ /**
+ * Head of the coin's history list.
+ */
+ struct TALER_EXCHANGEDB_TransactionList *head;
+
+ /**
+ * Public key of the coin we are building the history for.
+ */
+ const struct TALER_CoinSpendPublicKeyP *coin_pub;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to 'true' if the transaction failed.
+ */
+ bool failed;
+
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_deposit (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_DepositListEntry *deposit;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &deposit->amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &deposit->deposit_fee),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &deposit->h_denom_pub),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &deposit->h_age_commitment),
+ &deposit->no_age_commitment),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("wallet_data_hash",
+ &deposit->wallet_data_hash),
+ &deposit->no_wallet_data_hash),
+ GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
+ &deposit->timestamp),
+ GNUNET_PQ_result_spec_timestamp ("refund_deadline",
+ &deposit->refund_deadline),
+ GNUNET_PQ_result_spec_timestamp ("wire_deadline",
+ &deposit->wire_deadline),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &deposit->merchant_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &deposit->h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+ &deposit->wire_salt),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &deposit->receiver_wire_account),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &deposit->csig),
+ GNUNET_PQ_result_spec_uint64 ("coin_deposit_serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_auto_from_type ("done",
+ &deposit->done),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (deposit);
+ chc->failed = true;
+ return;
+ }
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
+ tl->details.deposit = deposit;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_purse_deposit (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_PurseDepositListEntry *deposit;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ deposit = GNUNET_new (struct TALER_EXCHANGEDB_PurseDepositListEntry);
+ {
+ bool not_finished;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &deposit->amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &deposit->deposit_fee),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &deposit->purse_pub),
+ GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("partner_base_url",
+ &deposit->exchange_base_url),
+ NULL),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &deposit->coin_sig),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &deposit->h_age_commitment),
+ &deposit->no_age_commitment),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_bool ("refunded",
+ &deposit->refunded),
+ &not_finished),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (deposit);
+ chc->failed = true;
+ return;
+ }
+ if (not_finished)
+ deposit->refunded = false;
+ deposit->no_age_commitment = GNUNET_is_zero (&deposit->h_age_commitment);
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_PURSE_DEPOSIT;
+ tl->details.purse_deposit = deposit;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_melt (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_MeltListEntry *melt;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("rc",
+ &melt->rc),
+ /* oldcoin_index not needed */
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &melt->h_denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
+ &melt->coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &melt->amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+ &melt->melt_fee),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &melt->h_age_commitment),
+ &melt->no_age_commitment),
+ GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (melt);
+ chc->failed = true;
+ return;
+ }
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_MELT;
+ tl->details.melt = melt;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_refund (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_RefundListEntry *refund;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ refund = GNUNET_new (struct TALER_EXCHANGEDB_RefundListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &refund->merchant_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
+ &refund->merchant_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &refund->h_contract_terms),
+ GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
+ &refund->rtransaction_id),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &refund->refund_amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+ &refund->refund_fee),
+ GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (refund);
+ chc->failed = true;
+ return;
+ }
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_REFUND;
+ tl->details.refund = refund;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_purse_decision (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ prefund = GNUNET_new (struct TALER_EXCHANGEDB_PurseRefundListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &prefund->purse_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &prefund->refund_amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+ &prefund->refund_fee),
+ GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (prefund);
+ chc->failed = true;
+ return;
+ }
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_PURSE_REFUND;
+ tl->details.purse_refund = prefund;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_old_coin_recoup (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &recoup->coin.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &recoup->coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &recoup->coin_blind),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &recoup->value),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &recoup->timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &recoup->coin.denom_pub_hash),
+ TALER_PQ_result_spec_denom_sig ("denom_sig",
+ &recoup->coin.denom_sig),
+ GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (recoup);
+ chc->failed = true;
+ return;
+ }
+ recoup->old_coin_pub = *chc->coin_pub;
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP;
+ tl->details.old_coin_recoup = recoup;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_recoup (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_RecoupListEntry *recoup;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &recoup->reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &recoup->coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &recoup->h_denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &recoup->coin_blind),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &recoup->value),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &recoup->timestamp),
+ GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (recoup);
+ chc->failed = true;
+ return;
+ }
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_RECOUP;
+ tl->details.recoup = recoup;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_recoup_refresh (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
+ &recoup->old_coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &recoup->coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &recoup->coin_blind),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &recoup->value),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &recoup->timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &recoup->coin.denom_pub_hash),
+ TALER_PQ_result_spec_denom_sig ("denom_sig",
+ &recoup->coin.denom_sig),
+ GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (recoup);
+ chc->failed = true;
+ return;
+ }
+ recoup->coin.coin_pub = *chc->coin_pub;
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH;
+ tl->details.recoup_refresh = recoup;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinHistoryContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_coin_reserve_open (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_ReserveOpenListEntry *role;
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ uint64_t serial_id;
+
+ role = GNUNET_new (struct TALER_EXCHANGEDB_ReserveOpenListEntry);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &role->reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &role->coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("contribution",
+ &role->coin_contribution),
+ GNUNET_PQ_result_spec_uint64 ("reserve_open_deposit_uuid",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (role);
+ chc->failed = true;
+ return;
+ }
+ }
+ tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
+ tl->next = chc->head;
+ tl->type = TALER_EXCHANGEDB_TT_RESERVE_OPEN;
+ tl->details.reserve_open = role;
+ tl->serial_id = serial_id;
+ chc->head = tl;
+ }
+}
+
+
+/**
+ * Work we need to do.
+ */
+struct Work
+{
+ /**
+ * Name of the table.
+ */
+ const char *table;
+
+ /**
+ * SQL prepared statement name.
+ */
+ const char *statement;
+
+ /**
+ * Function to call to handle the result(s).
+ */
+ GNUNET_PQ_PostgresResultHandler cb;
+};
+
+
+/**
+ * We found a coin history entry. Lookup details
+ * from the respective table and store in @a cls.
+ *
+ * @param[in,out] cls a `struct CoinHistoryContext`
+ * @param result a coin history entry result set
+ * @param num_results total number of results in @a results
+ */
+static void
+handle_history_entry (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinHistoryContext *chc = cls;
+ struct PostgresClosure *pg = chc->pg;
+ static const struct Work work[] = {
+ [TALER_EXCHANGEDB_TT_DEPOSIT] =
+ { "coin_deposits",
+ "get_deposit_with_coin_pub",
+ &add_coin_deposit },
+ [TALER_EXCHANGEDB_TT_MELT] =
+ { "refresh_commitments",
+ "get_refresh_session_by_coin",
+ &add_coin_melt },
+ [TALER_EXCHANGEDB_TT_PURSE_DEPOSIT] =
+ { "purse_deposits",
+ "get_purse_deposit_by_coin_pub",
+ &add_coin_purse_deposit },
+ [TALER_EXCHANGEDB_TT_PURSE_REFUND] =
+ { "purse_decision",
+ "get_purse_decision_by_coin_pub",
+ &add_coin_purse_decision },
+ [TALER_EXCHANGEDB_TT_REFUND] =
+ { "refunds",
+ "get_refunds_by_coin",
+ &add_coin_refund },
+ [TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP] =
+ { "recoup_refresh::OLD",
+ "recoup_by_old_coin",
+ &add_old_coin_recoup },
+ [TALER_EXCHANGEDB_TT_RECOUP] =
+ { "recoup",
+ "recoup_by_coin",
+ &add_coin_recoup },
+ [TALER_EXCHANGEDB_TT_RECOUP_REFRESH] =
+ { "recoup_refresh::NEW",
+ "recoup_by_refreshed_coin",
+ &add_coin_recoup_refresh },
+ [TALER_EXCHANGEDB_TT_RESERVE_OPEN] =
+ { "reserves_open_deposits",
+ "reserve_open_by_coin",
+ &add_coin_reserve_open },
+ { NULL, NULL, NULL }
+ };
+ char *table_name;
+ uint64_t serial_id;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("table_name",
+ &table_name),
+ GNUNET_PQ_result_spec_uint64 ("serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (chc->coin_pub),
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ bool found = false;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ chc->failed = true;
+ return;
+ }
+
+ for (unsigned int s = 0;
+ NULL != work[s].statement;
+ s++)
+ {
+ if (0 != strcmp (table_name,
+ work[s].table))
+ continue;
+ found = true;
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ work[s].statement,
+ params,
+ work[s].cb,
+ chc);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Coin %s had %d transactions at %llu in table %s\n",
+ TALER_B2S (chc->coin_pub),
+ (int) qs,
+ (unsigned long long) serial_id,
+ table_name);
+ if (0 >= qs)
+ chc->failed = true;
+ break;
+ }
+ if (! found)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Coin history includes unsupported table `%s`\n",
+ table_name);
+ chc->failed = true;
+ }
+ GNUNET_PQ_cleanup_result (rs);
+ if (chc->failed)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_transactions (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t start_off,
+ uint64_t etag_in,
+ uint64_t *etag_out,
+ struct TALER_Amount *balance,
+ struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_EXCHANGEDB_TransactionList **tlp)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_QueryParam lparams[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_uint64 (&start_off),
+ GNUNET_PQ_query_param_end
+ };
+ struct CoinHistoryContext chc = {
+ .head = NULL,
+ .coin_pub = coin_pub,
+ .pg = pg
+ };
+
+ *tlp = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Getting transactions for coin %s\n",
+ TALER_B2S (coin_pub));
+ PREPARE (pg,
+ "get_coin_history_etag_balance",
+ "SELECT"
+ " ch.coin_history_serial_id"
+ ",kc.remaining"
+ ",denom.denom_pub_hash"
+ " FROM coin_history ch"
+ " JOIN known_coins kc"
+ " USING (coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE coin_pub=$1"
+ " ORDER BY coin_history_serial_id DESC"
+ " LIMIT 1;");
+ PREPARE (pg,
+ "get_coin_history",
+ "SELECT"
+ " table_name"
+ ",serial_id"
+ " FROM coin_history"
+ " WHERE coin_pub=$1"
+ " AND coin_history_serial_id > $2"
+ " ORDER BY coin_history_serial_id DESC;");
+ PREPARE (pg,
+ "get_deposit_with_coin_pub",
+ "SELECT"
+ " cdep.amount_with_fee"
+ ",denoms.fee_deposit"
+ ",denoms.denom_pub_hash"
+ ",kc.age_commitment_hash"
+ ",bdep.wallet_timestamp"
+ ",bdep.refund_deadline"
+ ",bdep.wire_deadline"
+ ",bdep.merchant_pub"
+ ",bdep.h_contract_terms"
+ ",bdep.wallet_data_hash"
+ ",bdep.wire_salt"
+ ",wt.payto_uri"
+ ",cdep.coin_sig"
+ ",cdep.coin_deposit_serial_id"
+ ",bdep.done"
+ " FROM coin_deposits cdep"
+ " JOIN batch_deposits bdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN wire_targets wt"
+ " USING (wire_target_h_payto)"
+ " JOIN known_coins kc"
+ " ON (kc.coin_pub = cdep.coin_pub)"
+ " JOIN denominations denoms"
+ " USING (denominations_serial)"
+ " WHERE cdep.coin_pub=$1"
+ " AND cdep.coin_deposit_serial_id=$2;");
+ PREPARE (pg,
+ "get_refresh_session_by_coin",
+ "SELECT"
+ " rc"
+ ",old_coin_sig"
+ ",amount_with_fee"
+ ",denoms.denom_pub_hash"
+ ",denoms.fee_refresh"
+ ",kc.age_commitment_hash"
+ ",melt_serial_id"
+ " FROM refresh_commitments"
+ " JOIN known_coins kc"
+ " ON (refresh_commitments.old_coin_pub = kc.coin_pub)"
+ " JOIN denominations denoms"
+ " USING (denominations_serial)"
+ " WHERE old_coin_pub=$1"
+ " AND melt_serial_id=$2;");
+ PREPARE (pg,
+ "get_purse_deposit_by_coin_pub",
+ "SELECT"
+ " partner_base_url"
+ ",pd.amount_with_fee"
+ ",denoms.fee_deposit"
+ ",pd.purse_pub"
+ ",kc.age_commitment_hash"
+ ",pd.coin_sig"
+ ",pd.purse_deposit_serial_id"
+ ",pdes.refunded"
+ " FROM purse_deposits pd"
+ " LEFT JOIN partners"
+ " USING (partner_serial_id)"
+ " JOIN purse_requests pr"
+ " USING (purse_pub)"
+ " LEFT JOIN purse_decision pdes"
+ " USING (purse_pub)"
+ " JOIN known_coins kc"
+ " ON (pd.coin_pub = kc.coin_pub)"
+ " JOIN denominations denoms"
+ " USING (denominations_serial)"
+ " WHERE pd.purse_deposit_serial_id=$2"
+ " AND pd.coin_pub=$1;");
+ PREPARE (pg,
+ "get_purse_decision_by_coin_pub",
+ "SELECT"
+ " pdes.purse_pub"
+ ",pd.amount_with_fee"
+ ",denom.fee_refund"
+ ",pdes.purse_decision_serial_id"
+ " FROM purse_decision pdes"
+ " JOIN purse_deposits pd"
+ " USING (purse_pub)"
+ " JOIN known_coins kc"
+ " ON (pd.coin_pub = kc.coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE pd.coin_pub=$1"
+ " AND pdes.purse_decision_serial_id=$2"
+ " AND pdes.refunded;");
+ PREPARE (pg,
+ "get_refunds_by_coin",
+ "SELECT"
+ " bdep.merchant_pub"
+ ",ref.merchant_sig"
+ ",bdep.h_contract_terms"
+ ",ref.rtransaction_id"
+ ",ref.amount_with_fee"
+ ",denom.fee_refund"
+ ",ref.refund_serial_id"
+ " FROM refunds ref"
+ " JOIN coin_deposits cdep"
+ " ON (ref.coin_pub = cdep.coin_pub AND ref.batch_deposit_serial_id = cdep.batch_deposit_serial_id)"
+ " JOIN batch_deposits bdep"
+ " ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)"
+ " JOIN known_coins kc"
+ " ON (ref.coin_pub = kc.coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE ref.coin_pub=$1"
+ " AND ref.refund_serial_id=$2;");
+ PREPARE (pg,
+ "recoup_by_old_coin",
+ "SELECT"
+ " coins.coin_pub"
+ ",rr.coin_sig"
+ ",rr.coin_blind"
+ ",rr.amount"
+ ",rr.recoup_timestamp"
+ ",denoms.denom_pub_hash"
+ ",coins.denom_sig"
+ ",rr.recoup_refresh_uuid"
+ " FROM recoup_refresh rr"
+ " JOIN known_coins coins"
+ " USING (coin_pub)"
+ " JOIN denominations denoms"
+ " USING (denominations_serial)"
+ " WHERE recoup_refresh_uuid=$2"
+ " AND rrc_serial IN"
+ " (SELECT rrc.rrc_serial"
+ " FROM refresh_commitments melt"
+ " JOIN refresh_revealed_coins rrc"
+ " USING (melt_serial_id)"
+ " WHERE melt.old_coin_pub=$1);");
+ PREPARE (pg,
+ "recoup_by_coin",
+ "SELECT"
+ " res.reserve_pub"
+ ",denoms.denom_pub_hash"
+ ",rcp.coin_sig"
+ ",rcp.coin_blind"
+ ",rcp.amount"
+ ",rcp.recoup_timestamp"
+ ",rcp.recoup_uuid"
+ " FROM recoup rcp"
+ " JOIN reserves_out ro"
+ " USING (reserve_out_serial_id)"
+ " JOIN reserves res"
+ " USING (reserve_uuid)"
+ " JOIN known_coins coins"
+ " USING (coin_pub)"
+ " JOIN denominations denoms"
+ " ON (denoms.denominations_serial = coins.denominations_serial)"
+ " WHERE rcp.recoup_uuid=$2"
+ " AND coins.coin_pub=$1;");
+ /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
+ for a refreshed coin */
+ PREPARE (pg,
+ "recoup_by_refreshed_coin",
+ "SELECT"
+ " old_coins.coin_pub AS old_coin_pub"
+ ",rr.coin_sig"
+ ",rr.coin_blind"
+ ",rr.amount"
+ ",rr.recoup_timestamp"
+ ",denoms.denom_pub_hash"
+ ",coins.denom_sig"
+ ",recoup_refresh_uuid"
+ " FROM recoup_refresh rr"
+ " JOIN refresh_revealed_coins rrc"
+ " USING (rrc_serial)"
+ " JOIN refresh_commitments rfc"
+ " ON (rrc.melt_serial_id = rfc.melt_serial_id)"
+ " JOIN known_coins old_coins"
+ " ON (rfc.old_coin_pub = old_coins.coin_pub)"
+ " JOIN known_coins coins"
+ " ON (rr.coin_pub = coins.coin_pub)"
+ " JOIN denominations denoms"
+ " ON (denoms.denominations_serial = coins.denominations_serial)"
+ " WHERE rr.recoup_refresh_uuid=$2"
+ " AND coins.coin_pub=$1;");
+ PREPARE (pg,
+ "reserve_open_by_coin",
+ "SELECT"
+ " reserve_open_deposit_uuid"
+ ",coin_sig"
+ ",reserve_sig"
+ ",contribution"
+ " FROM reserves_open_deposits"
+ " WHERE coin_pub=$1"
+ " AND reserve_open_deposit_uuid=$2;");
+
+ for (unsigned int i = 0; i<RETRIES; i++)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t end;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("coin_history_serial_id",
+ &end),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ h_denom_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("remaining",
+ balance),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ TEH_PG_start_read_committed (pg,
+ "get-coin-transactions"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* First only check the last item, to see if
+ we even need to iterate */
+ qs = GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "get_coin_history_etag_balance",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ *etag_out = end;
+ if (end == etag_in)
+ return qs;
+ }
+ /* We indeed need to iterate over the history */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Current ETag for coin %s is %llu\n",
+ TALER_B2S (coin_pub),
+ (unsigned long long) end);
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "get_coin_history",
+ lparams,
+ &handle_history_entry,
+ &chc);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ default:
+ break;
+ }
+ if (chc.failed)
+ {
+ TEH_PG_rollback (pg);
+ TEH_COMMON_free_coin_transaction_list (pg,
+ chc.head);
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ }
+ qs = TEH_PG_commit (pg);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TEH_COMMON_free_coin_transaction_list (pg,
+ chc.head);
+ chc.head = NULL;
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_COMMON_free_coin_transaction_list (pg,
+ chc.head);
+ chc.head = NULL;
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ *tlp = chc.head;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ }
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
diff --git a/src/exchangedb/pg_get_coin_transactions.h b/src/exchangedb/pg_get_coin_transactions.h
new file mode 100644
index 000000000..46e32e094
--- /dev/null
+++ b/src/exchangedb/pg_get_coin_transactions.h
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_get_coin_transactions.h
+ * @brief implementation of the get_coin_transactions function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_COIN_TRANSACTIONS_H
+#define PG_GET_COIN_TRANSACTIONS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Compile a list of (historic) transactions performed with the given coin
+ * (melt, refund, recoup and deposit operations). Should return 0 if the @a
+ * coin_pub is unknown, otherwise determine @a etag_out and if it is past @a
+ * etag_in return the history after @a start_off. @a etag_out should be set
+ * to the last row ID of the given @a coin_pub in the coin history table.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param coin_pub coin to investigate
+ * @param start_off starting offset from which on to return entries
+ * @param etag_in up to this offset the client already has a response, do not
+ * return anything unless @a etag_out will be larger
+ * @param[out] etag_out set to the latest history offset known for this @a coin_pub
+ * @param[out] balance set to current balance of the coin
+ * @param[out] h_denom_pub set to denomination public key of the coin
+ * @param[out] tlp set to list of transactions, set to NULL if coin has no
+ * transaction history past @a start_off or if @a etag_in is equal
+ * to the value written to @a etag_out.
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_coin_transactions (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ uint64_t start_off,
+ uint64_t etag_in,
+ uint64_t *etag_out,
+ struct TALER_Amount *balance,
+ struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_EXCHANGEDB_TransactionList **tlp);
+
+
+#endif
diff --git a/src/exchangedb/pg_get_denomination_info.c b/src/exchangedb/pg_get_denomination_info.c
new file mode 100644
index 000000000..4bae29795
--- /dev/null
+++ b/src/exchangedb/pg_get_denomination_info.c
@@ -0,0 +1,91 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_denomination_info.c
+ * @brief Implementation of the get_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_denomination_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_denomination_info (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &issue->signature),
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &issue->start),
+ GNUNET_PQ_result_spec_timestamp ("expire_withdraw",
+ &issue->expire_withdraw),
+ GNUNET_PQ_result_spec_timestamp ("expire_deposit",
+ &issue->expire_deposit),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &issue->expire_legal),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
+ &issue->value),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+ &issue->fees.withdraw),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &issue->fees.deposit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+ &issue->fees.refresh),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+ &issue->fees.refund),
+ GNUNET_PQ_result_spec_uint32 ("age_mask",
+ &issue->age_mask.bits),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "denomination_get",
+ "SELECT"
+ " master_sig"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin" /* value of this denom */
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",age_mask"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "denomination_get",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ return qs;
+ issue->denom_hash = *denom_pub_hash;
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_denomination_info.h b/src/exchangedb/pg_get_denomination_info.h
new file mode 100644
index 000000000..843227759
--- /dev/null
+++ b/src/exchangedb/pg_get_denomination_info.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_denomination_info.h
+ * @brief implementation of the get_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_DENOMINATION_INFO_H
+#define PG_GET_DENOMINATION_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Fetch information about a denomination key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param denom_pub_hash hash of the public key used for signing coins of this denomination
+ * @param[out] issue set to issue information with value, fees and other info about the coin
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_denomination_info (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
+
+#endif
diff --git a/src/exchangedb/pg_get_denomination_revocation.c b/src/exchangedb/pg_get_denomination_revocation.c
new file mode 100644
index 000000000..5e7a3a322
--- /dev/null
+++ b/src/exchangedb/pg_get_denomination_revocation.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_denomination_revocation.c
+ * @brief Implementation of the get_denomination_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_denomination_revocation.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_denomination_revocation (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_MasterSignatureP *master_sig,
+ uint64_t *rowid)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_uint64 ("denom_revocations_serial_id",
+ rowid),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "denomination_revocation_get",
+ "SELECT"
+ " master_sig"
+ ",denom_revocations_serial_id"
+ " FROM denomination_revocations"
+ " WHERE denominations_serial="
+ " (SELECT denominations_serial"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$1);");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "denomination_revocation_get",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_denomination_revocation.h b/src/exchangedb/pg_get_denomination_revocation.h
new file mode 100644
index 000000000..5f7f27227
--- /dev/null
+++ b/src/exchangedb/pg_get_denomination_revocation.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_denomination_revocation.h
+ * @brief implementation of the get_denomination_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_DENOMINATION_REVOCATION_H
+#define PG_GET_DENOMINATION_REVOCATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain information about a denomination key's revocation from
+ * the database.
+ *
+ * @param cls closure
+ * @param denom_pub_hash hash of the revoked denomination key
+ * @param[out] master_sig signature affirming the revocation
+ * @param[out] rowid row where the information is stored
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_denomination_revocation (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ struct TALER_MasterSignatureP *master_sig,
+ uint64_t *rowid);
+
+#endif
diff --git a/src/exchangedb/pg_get_drain_profit.c b/src/exchangedb/pg_get_drain_profit.c
new file mode 100644
index 000000000..75fccefcd
--- /dev/null
+++ b/src/exchangedb/pg_get_drain_profit.c
@@ -0,0 +1,76 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_drain_profit.c
+ * @brief Implementation of the get_drain_profit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_drain_profit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_drain_profit (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t *serial,
+ char **account_section,
+ char **payto_uri,
+ struct GNUNET_TIME_Timestamp *request_timestamp,
+ struct TALER_Amount *amount,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("profit_drain_serial_id",
+ serial),
+ GNUNET_PQ_result_spec_string ("account_section",
+ account_section),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ payto_uri),
+ GNUNET_PQ_result_spec_timestamp ("trigger_date",
+ request_timestamp),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ amount),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_profit_drain",
+ "SELECT"
+ " profit_drain_serial_id"
+ ",account_section"
+ ",payto_uri"
+ ",trigger_date"
+ ",amount"
+ ",master_sig"
+ " FROM profit_drains"
+ " WHERE wtid=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_profit_drain",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_drain_profit.h b/src/exchangedb/pg_get_drain_profit.h
new file mode 100644
index 000000000..dd05d8afd
--- /dev/null
+++ b/src/exchangedb/pg_get_drain_profit.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_drain_profit.h
+ * @brief implementation of the get_drain_profit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_DRAIN_PROFIT_H
+#define PG_GET_DRAIN_PROFIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to get information about a profit drain event.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param wtid wire transfer ID to look up drain event for
+ * @param[out] serial set to serial ID of the entry
+ * @param[out] account_section set to account to drain
+ * @param[out] payto_uri set to account to wire funds to
+ * @param[out] request_timestamp set to time of the signature
+ * @param[out] amount set to amount to wire
+ * @param[out] master_sig set to signature affirming the operation
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_drain_profit (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t *serial,
+ char **account_section,
+ char **payto_uri,
+ struct GNUNET_TIME_Timestamp *request_timestamp,
+ struct TALER_Amount *amount,
+ struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_get_expired_reserves.c b/src/exchangedb/pg_get_expired_reserves.c
new file mode 100644
index 000000000..be9ece98a
--- /dev/null
+++ b/src/exchangedb/pg_get_expired_reserves.c
@@ -0,0 +1,174 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_get_expired_reserves.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_expired_reserves.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #reserve_expired_cb().
+ */
+struct ExpiredReserveContext
+{
+ /**
+ * Function to call for each expired reserve.
+ */
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec;
+
+ /**
+ * Closure to give to @e rec.
+ */
+ void *rec_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserve_expired_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ExpiredReserveContext *erc = cls;
+ struct PostgresClosure *pg = erc->pg;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_OK;
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_TIME_Timestamp exp_date;
+ char *account_details;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_Amount remaining_balance;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &exp_date),
+ GNUNET_PQ_result_spec_string ("account_details",
+ &account_details),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ TALER_PQ_result_spec_amount ("current_balance",
+ pg->currency,
+ &remaining_balance),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ret = GNUNET_SYSERR;
+ break;
+ }
+ ret = erc->rec (erc->rec_cls,
+ &reserve_pub,
+ &remaining_balance,
+ account_details,
+ exp_date,
+ 0);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+ erc->status = ret;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_expired_reserves (void *cls,
+ struct GNUNET_TIME_Timestamp now,
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+ void *rec_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct ExpiredReserveContext ectx = {
+ .rec = rec,
+ .rec_cls = rec_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "get_expired_reserves",
+ "WITH ed AS MATERIALIZED ( "
+ " SELECT * "
+ " FROM reserves "
+ " WHERE expiration_date <= $1 "
+ " AND ((current_balance).val != 0 OR (current_balance).frac != 0) "
+ " ORDER BY expiration_date ASC "
+ " LIMIT 1 "
+ ") "
+ "SELECT "
+ " ed.expiration_date "
+ " ,payto_uri AS account_details "
+ " ,ed.reserve_pub "
+ " ,current_balance "
+ "FROM ( "
+ " SELECT "
+ " * "
+ " FROM reserves_in "
+ " WHERE reserve_pub = ( "
+ " SELECT reserve_pub FROM ed) "
+ " ) ri "
+ "JOIN wire_targets wt ON (ri.wire_source_h_payto = wt.wire_target_h_payto) "
+ "JOIN ed ON (ri.reserve_pub = ed.reserve_pub);");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_expired_reserves",
+ params,
+ &reserve_expired_cb,
+ &ectx);
+ switch (ectx.status)
+ {
+ case GNUNET_SYSERR:
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_NO:
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ case GNUNET_OK:
+ break;
+ }
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_expired_reserves.h b/src/exchangedb/pg_get_expired_reserves.h
new file mode 100644
index 000000000..0874b531a
--- /dev/null
+++ b/src/exchangedb/pg_get_expired_reserves.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_get_expired_reserves.h
+ * @brief implementation of the get_expired_reserves function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_EXPIRED_RESERVES_H
+#define PG_GET_EXPIRED_RESERVES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Obtain information about expired reserves and their
+ * remaining balances.
+ *
+ * @param cls closure of the plugin
+ * @param now timestamp based on which we decide expiration
+ * @param rec function to call on expired reserves
+ * @param rec_cls closure for @a rec
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_expired_reserves (void *cls,
+ struct GNUNET_TIME_Timestamp now,
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+ void *rec_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_extension_manifest.c b/src/exchangedb/pg_get_extension_manifest.c
new file mode 100644
index 000000000..c6b5948cf
--- /dev/null
+++ b/src/exchangedb/pg_get_extension_manifest.c
@@ -0,0 +1,67 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_extension_manifest.c
+ * @brief Implementation of the get_extension_manifest function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_extension_manifest.h"
+#include "pg_helper.h"
+
+/**
+ * Function called to get the manifest of an extension
+ * (age-restriction, policy_extension_...)
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param extension_name the name of the extension
+ * @param[out] manifest JSON object of the manifest as string
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_extension_manifest (void *cls,
+ const char *extension_name,
+ char **manifest)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (extension_name),
+ GNUNET_PQ_query_param_end
+ };
+ bool is_null;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("manifest",
+ manifest),
+ &is_null),
+ GNUNET_PQ_result_spec_end
+ };
+
+ *manifest = NULL;
+ PREPARE (pg,
+ "get_extension_manifest",
+ "SELECT"
+ " manifest"
+ " FROM extensions"
+ " WHERE name=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_extension_manifest",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_extension_manifest.h b/src/exchangedb/pg_get_extension_manifest.h
new file mode 100644
index 000000000..e8331ad9b
--- /dev/null
+++ b/src/exchangedb/pg_get_extension_manifest.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_extension_manifest.h
+ * @brief implementation of the get_extension_manifest function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_EXTENSION_MANIFEST_H
+#define PG_GET_EXTENSION_MANIFEST_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to get the manifest of an extension
+ * (age-restriction, policy_extension_...)
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param extension_name the name of the extension
+ * @param[out] manifest JSON object of the manifest as string
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_extension_manifest (void *cls,
+ const char *extension_name,
+ char **manifest);
+
+#endif
diff --git a/src/exchangedb/pg_get_global_fee.c b/src/exchangedb/pg_get_global_fee.c
new file mode 100644
index 000000000..46addfa17
--- /dev/null
+++ b/src/exchangedb/pg_get_global_fee.c
@@ -0,0 +1,86 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_global_fee.c
+ * @brief Implementation of the get_global_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_global_fee.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_global_fee (void *cls,
+ struct GNUNET_TIME_Timestamp date,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative *purse_timeout,
+ struct GNUNET_TIME_Relative *history_expiration,
+ uint32_t *purse_account_limit,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&date),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("start_date",
+ start_date),
+ GNUNET_PQ_result_spec_timestamp ("end_date",
+ end_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("history_fee",
+ &fees->history),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("account_fee",
+ &fees->account),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
+ &fees->purse),
+ GNUNET_PQ_result_spec_relative_time ("purse_timeout",
+ purse_timeout),
+ GNUNET_PQ_result_spec_relative_time ("history_expiration",
+ history_expiration),
+ GNUNET_PQ_result_spec_uint32 ("purse_account_limit",
+ purse_account_limit),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_global_fee",
+ "SELECT "
+ " start_date"
+ ",end_date"
+ ",history_fee"
+ ",account_fee"
+ ",purse_fee"
+ ",purse_timeout"
+ ",history_expiration"
+ ",purse_account_limit"
+ ",master_sig"
+ " FROM global_fee"
+ " WHERE start_date <= $1"
+ " AND end_date > $1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_global_fee",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_global_fee.h b/src/exchangedb/pg_get_global_fee.h
new file mode 100644
index 000000000..1e7c9e94b
--- /dev/null
+++ b/src/exchangedb/pg_get_global_fee.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_global_fee.h
+ * @brief implementation of the get_global_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_GLOBAL_FEE_H
+#define PG_GET_GLOBAL_FEE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Obtain global fees from database.
+ *
+ * @param cls closure
+ * @param date for which date do we want the fee?
+ * @param[out] start_date when does the fee go into effect
+ * @param[out] end_date when does the fee end being valid
+ * @param[out] fees how high are the wire fees
+ * @param[out] purse_timeout set to how long we keep unmerged purses
+ * @param[out] history_expiration set to how long we keep account histories
+ * @param[out] purse_account_limit set to the number of free purses per account
+ * @param[out] master_sig signature over the above by the exchange master key
+ * @return status of the transaction
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_global_fee (void *cls,
+ struct GNUNET_TIME_Timestamp date,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative *purse_timeout,
+ struct GNUNET_TIME_Relative *history_expiration,
+ uint32_t *purse_account_limit,
+ struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_get_global_fees.c b/src/exchangedb/pg_get_global_fees.c
new file mode 100644
index 000000000..21be35989
--- /dev/null
+++ b/src/exchangedb/pg_get_global_fees.c
@@ -0,0 +1,165 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_global_fees.c
+ * @brief Implementation of the get_global_fees function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_global_fees.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #global_fees_cb().
+ */
+struct GlobalFeeContext
+{
+ /**
+ * Function to call for each global fee block.
+ */
+ TALER_EXCHANGEDB_GlobalFeeCallback cb;
+
+ /**
+ * Closure to give to @e rec.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+global_fees_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GlobalFeeContext *gctx = cls;
+ struct PostgresClosure *pg = gctx->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_GlobalFeeSet fees;
+ struct GNUNET_TIME_Relative purse_timeout;
+ struct GNUNET_TIME_Relative history_expiration;
+ uint32_t purse_account_limit;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("start_date",
+ &start_date),
+ GNUNET_PQ_result_spec_timestamp ("end_date",
+ &end_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("history_fee",
+ &fees.history),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("account_fee",
+ &fees.account),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
+ &fees.purse),
+ GNUNET_PQ_result_spec_relative_time ("purse_timeout",
+ &purse_timeout),
+ GNUNET_PQ_result_spec_relative_time ("history_expiration",
+ &history_expiration),
+ GNUNET_PQ_result_spec_uint32 ("purse_account_limit",
+ &purse_account_limit),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ gctx->status = GNUNET_SYSERR;
+ break;
+ }
+ gctx->cb (gctx->cb_cls,
+ &fees,
+ purse_timeout,
+ history_expiration,
+ purse_account_limit,
+ start_date,
+ end_date,
+ &master_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_global_fees (void *cls,
+ TALER_EXCHANGEDB_GlobalFeeCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp date
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_subtract (
+ GNUNET_TIME_absolute_get (),
+ GNUNET_TIME_UNIT_YEARS));
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&date),
+ GNUNET_PQ_query_param_end
+ };
+ struct GlobalFeeContext gctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+
+ PREPARE (pg,
+ "get_global_fees",
+ "SELECT "
+ " start_date"
+ ",end_date"
+ ",history_fee"
+ ",account_fee"
+ ",purse_fee"
+ ",purse_timeout"
+ ",history_expiration"
+ ",purse_account_limit"
+ ",master_sig"
+ " FROM global_fee"
+ " WHERE start_date >= $1");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_global_fees",
+ params,
+ &global_fees_cb,
+ &gctx);
+}
diff --git a/src/exchangedb/pg_get_global_fees.h b/src/exchangedb/pg_get_global_fees.h
new file mode 100644
index 000000000..80c9b812f
--- /dev/null
+++ b/src/exchangedb/pg_get_global_fees.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_global_fees.h
+ * @brief implementation of the get_global_fees function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_GLOBAL_FEES_H
+#define PG_GET_GLOBAL_FEES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain global fees from database.
+ *
+ * @param cls closure
+ * @param cb function to call on each fee entry
+ * @param cb_cls closure for @a cb
+ * @return status of the transaction
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_global_fees (void *cls,
+ TALER_EXCHANGEDB_GlobalFeeCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_known_coin.c b/src/exchangedb/pg_get_known_coin.c
new file mode 100644
index 000000000..2c4a82d67
--- /dev/null
+++ b/src/exchangedb/pg_get_known_coin.c
@@ -0,0 +1,67 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_known_coin.c
+ * @brief Implementation of the get_known_coin function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_known_coin.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_known_coin (void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_CoinPublicInfo *coin_info)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &coin_info->denom_pub_hash),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &coin_info->h_age_commitment),
+ &coin_info->no_age_commitment),
+ TALER_PQ_result_spec_denom_sig ("denom_sig",
+ &coin_info->denom_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Getting known coin data for coin %s\n",
+ TALER_B2S (coin_pub));
+ coin_info->coin_pub = *coin_pub;
+ PREPARE (pg,
+ "get_known_coin",
+ "SELECT"
+ " denominations.denom_pub_hash"
+ ",age_commitment_hash"
+ ",denom_sig"
+ " FROM known_coins"
+ " JOIN denominations USING (denominations_serial)"
+ " WHERE coin_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_known_coin",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_known_coin.h b/src/exchangedb/pg_get_known_coin.h
new file mode 100644
index 000000000..c34bd2a97
--- /dev/null
+++ b/src/exchangedb/pg_get_known_coin.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_known_coin.h
+ * @brief implementation of the get_known_coin function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_KNOWN_COIN_H
+#define PG_GET_KNOWN_COIN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Retrieve the record for a known coin.
+ *
+ * @param cls the plugin closure
+ * @param coin_pub the public key of the coin to search for
+ * @param coin_info place holder for the returned coin information object
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_known_coin (void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_CoinPublicInfo *coin_info);
+
+#endif
diff --git a/src/exchangedb/pg_get_link_data.c b/src/exchangedb/pg_get_link_data.c
new file mode 100644
index 000000000..1b0cb3e20
--- /dev/null
+++ b/src/exchangedb/pg_get_link_data.c
@@ -0,0 +1,367 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_link_data.c
+ * @brief Implementation of the get_link_data function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_link_data.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #add_ldl().
+ */
+struct LinkDataContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_LinkCallback ldc;
+
+ /**
+ * Closure for @e ldc.
+ */
+ void *ldc_cls;
+
+ /**
+ * Last transfer public key for which we have information in @e last.
+ * Only valid if @e last is non-NULL.
+ */
+ struct TALER_TransferPublicKeyP transfer_pub;
+
+ /**
+ * Status, set to #GNUNET_SYSERR on errors,
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Free memory of the link data list.
+ *
+ * @param ldl link data list to release
+ */
+static void
+free_link_data_list (struct TALER_EXCHANGEDB_LinkList *ldl)
+{
+ struct TALER_EXCHANGEDB_LinkList *next;
+
+ while (NULL != ldl)
+ {
+ next = ldl->next;
+ TALER_denom_pub_free (&ldl->denom_pub);
+ TALER_blinded_denom_sig_free (&ldl->ev_sig);
+ TALER_denom_ewv_free (&ldl->alg_values);
+ GNUNET_free (ldl);
+ ldl = next;
+ }
+}
+
+
+struct Results
+{
+ struct TALER_EXCHANGEDB_LinkList *pos;
+ struct TALER_TransferPublicKeyP transfer_pub;
+};
+
+
+static int
+transfer_pub_cmp (const void *a,
+ const void *b)
+{
+ const struct Results *ra = a;
+ const struct Results *rb = b;
+
+ return GNUNET_memcmp (&ra->transfer_pub,
+ &rb->transfer_pub);
+}
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct LinkDataContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_ldl (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LinkDataContext *ldctx = cls;
+ struct Results *temp = GNUNET_new_array (num_results,
+ struct Results);
+ unsigned int temp_off = 0;
+
+ for (int i = num_results - 1; i >= 0; i--)
+ {
+ struct TALER_EXCHANGEDB_LinkList *pos;
+
+ pos = GNUNET_new (struct TALER_EXCHANGEDB_LinkList);
+ {
+ struct TALER_BlindedPlanchet bp;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("transfer_pub",
+ &temp[temp_off].transfer_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("link_sig",
+ &pos->orig_coin_link_sig),
+ TALER_PQ_result_spec_blinded_denom_sig ("ev_sig",
+ &pos->ev_sig),
+ GNUNET_PQ_result_spec_uint32 ("freshcoin_index",
+ &pos->coin_refresh_offset),
+ TALER_PQ_result_spec_exchange_withdraw_values ("ewv",
+ &pos->alg_values),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &pos->denom_pub),
+ TALER_PQ_result_spec_blinded_planchet ("coin_ev",
+ &bp),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ GNUNET_free (pos);
+ ldctx->status = GNUNET_SYSERR;
+ return;
+ }
+ if (GNUNET_CRYPTO_BSA_CS == bp.blinded_message->cipher)
+ {
+ pos->nonce.cs_nonce
+ = bp.blinded_message->details.cs_blinded_message.nonce;
+ pos->have_nonce = true;
+ }
+ TALER_blinded_planchet_free (&bp);
+ }
+ temp[temp_off].pos = pos;
+ temp_off++;
+ }
+ qsort (temp,
+ temp_off,
+ sizeof (struct Results),
+ &transfer_pub_cmp);
+ if (temp_off > 0)
+ {
+ struct TALER_EXCHANGEDB_LinkList *head = NULL;
+
+ head = temp[0].pos;
+ for (unsigned int i = 1; i < temp_off; i++)
+ {
+ struct TALER_EXCHANGEDB_LinkList *pos = temp[i].pos;
+ const struct TALER_TransferPublicKeyP *tp = &temp[i].transfer_pub;
+
+ if (0 == GNUNET_memcmp (tp,
+ &temp[i - 1].transfer_pub))
+ {
+ pos->next = head;
+ head = pos;
+ }
+ else
+ {
+ ldctx->ldc (ldctx->ldc_cls,
+ &temp[i - 1].transfer_pub,
+ head);
+ free_link_data_list (head);
+ head = pos;
+ }
+ }
+ ldctx->ldc (ldctx->ldc_cls,
+ &temp[temp_off - 1].transfer_pub,
+ head);
+ free_link_data_list (head);
+ }
+ GNUNET_free (temp);
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_link_data (void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ TALER_EXCHANGEDB_LinkCallback ldc,
+ void *ldc_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ struct LinkDataContext ldctx;
+ static int percent_refund = -2;
+ const char *query;
+
+ if (-2 == percent_refund)
+ {
+ const char *mode = getenv ("TALER_POSTGRES_GET_LINK_DATA_LOGIC");
+ char dummy;
+
+ if ( (NULL==mode) ||
+ (1 != sscanf (mode,
+ "%d%c",
+ &percent_refund,
+ &dummy)) )
+ {
+ if (NULL != mode)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Bad mode `%s' specified\n",
+ mode);
+ percent_refund = 4; /* Fastest known */
+ }
+ }
+ switch (percent_refund)
+ {
+ case 0:
+ query = "get_link";
+ PREPARE (pg,
+ query,
+ "SELECT "
+ " tp.transfer_pub"
+ ",denoms.denom_pub"
+ ",rrc.ev_sig"
+ ",rrc.ewv"
+ ",rrc.link_sig"
+ ",rrc.freshcoin_index"
+ ",rrc.coin_ev"
+ " FROM refresh_commitments"
+ " JOIN refresh_revealed_coins rrc"
+ " USING (melt_serial_id)"
+ " JOIN refresh_transfer_keys tp"
+ " USING (melt_serial_id)"
+ " JOIN denominations denoms"
+ " ON (rrc.denominations_serial = denoms.denominations_serial)"
+ " WHERE old_coin_pub=$1"
+ " ORDER BY tp.transfer_pub, rrc.freshcoin_index ASC");
+ break;
+ case 1:
+ query = "get_link_v1";
+ PREPARE (pg,
+ query,
+ "WITH rc AS MATERIALIZED ("
+ "SELECT"
+ " melt_serial_id"
+ " FROM refresh_commitments"
+ " WHERE old_coin_pub=$1"
+ ")"
+ "SELECT "
+ " tp.transfer_pub"
+ ",denoms.denom_pub"
+ ",rrc.ev_sig"
+ ",rrc.ewv"
+ ",rrc.link_sig"
+ ",rrc.freshcoin_index"
+ ",rrc.coin_ev "
+ "FROM "
+ "refresh_revealed_coins rrc"
+ " JOIN refresh_transfer_keys tp"
+ " USING (melt_serial_id)"
+ " JOIN denominations denoms"
+ " USING (denominations_serial)"
+ " WHERE rrc.melt_serial_id = (SELECT melt_serial_id FROM rc)"
+ " ORDER BY tp.transfer_pub, rrc.freshcoin_index ASC");
+ break;
+ case 2:
+ query = "get_link_v2";
+ PREPARE (pg,
+ query,
+ "SELECT"
+ " *"
+ " FROM"
+ " exchange_do_get_link_data"
+ " ($1) "
+ " AS "
+ " (transfer_pub BYTEA"
+ " ,denom_pub BYTEA"
+ " ,ev_sig BYTEA"
+ " ,ewv BYTEA"
+ " ,link_sig BYTEA"
+ " ,freshcoin_index INT4"
+ " ,coin_ev BYTEA);");
+ break;
+ case 3:
+ query = "get_link_v3";
+ PREPARE (pg,
+ query,
+ "SELECT "
+ " tp.transfer_pub"
+ ",denoms.denom_pub"
+ ",rrc.ev_sig"
+ ",rrc.ewv"
+ ",rrc.link_sig"
+ ",rrc.freshcoin_index"
+ ",rrc.coin_ev"
+ " FROM refresh_commitments"
+ " JOIN refresh_revealed_coins rrc"
+ " USING (melt_serial_id)"
+ " JOIN refresh_transfer_keys tp"
+ " USING (melt_serial_id)"
+ " JOIN denominations denoms"
+ " ON (rrc.denominations_serial = denoms.denominations_serial)"
+ " WHERE old_coin_pub=$1");
+ break;
+ case 4:
+ query = "get_link_v4";
+ PREPARE (pg,
+ query,
+ "WITH rc AS MATERIALIZED ("
+ "SELECT"
+ " melt_serial_id"
+ " FROM refresh_commitments"
+ " WHERE old_coin_pub=$1"
+ ")"
+ "SELECT "
+ " tp.transfer_pub"
+ ",denoms.denom_pub"
+ ",rrc.ev_sig"
+ ",rrc.ewv"
+ ",rrc.link_sig"
+ ",rrc.freshcoin_index"
+ ",rrc.coin_ev "
+ "FROM "
+ "refresh_revealed_coins rrc"
+ " JOIN refresh_transfer_keys tp"
+ " USING (melt_serial_id)"
+ " JOIN denominations denoms"
+ " USING (denominations_serial)"
+ " WHERE rrc.melt_serial_id = (SELECT melt_serial_id FROM rc)"
+ );
+ break;
+ default:
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ ldctx.ldc = ldc;
+ ldctx.ldc_cls = ldc_cls;
+ ldctx.status = GNUNET_OK;
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ query,
+ params,
+ &add_ldl,
+ &ldctx);
+ if (GNUNET_OK != ldctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_link_data.h b/src/exchangedb/pg_get_link_data.h
new file mode 100644
index 000000000..09d3a69fc
--- /dev/null
+++ b/src/exchangedb/pg_get_link_data.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_link_data.h
+ * @brief implementation of the get_link_data function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_LINK_DATA_H
+#define PG_GET_LINK_DATA_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Obtain the link data of a coin, that is the encrypted link
+ * information, the denomination keys and the signatures.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param coin_pub public key of the coin
+ * @param ldc function to call for each session the coin was melted into
+ * @param ldc_cls closure for @a tdc
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_link_data (void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ TALER_EXCHANGEDB_LinkCallback ldc,
+ void *ldc_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_melt.c b/src/exchangedb/pg_get_melt.c
new file mode 100644
index 000000000..2221054ba
--- /dev/null
+++ b/src/exchangedb/pg_get_melt.c
@@ -0,0 +1,124 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_melt.c
+ * @brief Implementation of the get_melt function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_melt.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_melt (void *cls,
+ const struct TALER_RefreshCommitmentP *rc,
+ struct TALER_EXCHANGEDB_Melt *melt,
+ uint64_t *melt_serial_id)
+{
+ struct PostgresClosure *pg = cls;
+ bool h_age_commitment_is_null;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (rc),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &melt->session.coin.
+ denom_pub_hash),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+ &melt->melt_fee),
+ GNUNET_PQ_result_spec_uint32 ("noreveal_index",
+ &melt->session.noreveal_index),
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
+ &melt->session.coin.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
+ &melt->session.coin_sig),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &melt->session.coin.h_age_commitment),
+ &h_age_commitment_is_null),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &melt->session.amount_with_fee),
+ GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
+ melt_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ memset (&melt->session.coin.denom_sig,
+ 0,
+ sizeof (melt->session.coin.denom_sig));
+
+ /* Used in #postgres_get_melt() to fetch
+ high-level information about a melt operation */
+ PREPARE (pg,
+ "get_melt",
+ /* "SELECT"
+ " denoms.denom_pub_hash"
+ ",denoms.fee_refresh"
+ ",old_coin_pub"
+ ",old_coin_sig"
+ ",kc.age_commitment_hash"
+ ",amount_with_fee"
+ ",noreveal_index"
+ ",melt_serial_id"
+ " FROM refresh_commitments"
+ " JOIN known_coins kc"
+ " ON (old_coin_pub = kc.coin_pub)"
+ " JOIN denominations denoms"
+ " ON (kc.denominations_serial = denoms.denominations_serial)"
+ " WHERE rc=$1;", */
+ "WITH rc AS MATERIALIZED ( "
+ " SELECT"
+ " * FROM refresh_commitments"
+ " WHERE rc=$1"
+ ")"
+ "SELECT"
+ " denoms.denom_pub_hash"
+ ",denoms.fee_refresh"
+ ",rc.old_coin_pub"
+ ",rc.old_coin_sig"
+ ",kc.age_commitment_hash"
+ ",amount_with_fee"
+ ",noreveal_index"
+ ",melt_serial_id "
+ "FROM ("
+ " SELECT"
+ " * "
+ " FROM known_coins"
+ " WHERE coin_pub=(SELECT old_coin_pub from rc)"
+ ") kc "
+ "JOIN rc"
+ " ON (kc.coin_pub=rc.old_coin_pub) "
+ "JOIN denominations denoms"
+ " USING (denominations_serial);");
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_melt",
+ params,
+ rs);
+ if (h_age_commitment_is_null)
+ memset (&melt->session.coin.h_age_commitment,
+ 0,
+ sizeof(melt->session.coin.h_age_commitment));
+
+ melt->session.rc = *rc;
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_melt.h b/src/exchangedb/pg_get_melt.h
new file mode 100644
index 000000000..269960bad
--- /dev/null
+++ b/src/exchangedb/pg_get_melt.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_melt.h
+ * @brief implementation of the get_melt function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_MELT_H
+#define PG_GET_MELT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Lookup refresh melt commitment data under the given @a rc.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param rc commitment hash to use to locate the operation
+ * @param[out] melt where to store the result; note that
+ * melt->session.coin.denom_sig will be set to NULL
+ * and is not fetched by this routine (as it is not needed by the client)
+ * @param[out] melt_serial_id set to the row ID of @a rc in the refresh_commitments table
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_melt (void *cls,
+ const struct TALER_RefreshCommitmentP *rc,
+ struct TALER_EXCHANGEDB_Melt *melt,
+ uint64_t *melt_serial_id);
+
+#endif
diff --git a/src/exchangedb/pg_get_old_coin_by_h_blind.c b/src/exchangedb/pg_get_old_coin_by_h_blind.c
new file mode 100644
index 000000000..dcce7b32f
--- /dev/null
+++ b/src/exchangedb/pg_get_old_coin_by_h_blind.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_old_coin_by_h_blind.c
+ * @brief Implementation of the get_old_coin_by_h_blind function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_old_coin_by_h_blind.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_old_coin_by_h_blind (
+ void *cls,
+ const struct TALER_BlindedCoinHashP *h_blind_ev,
+ struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ uint64_t *rrc_serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_blind_ev),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
+ old_coin_pub),
+ GNUNET_PQ_result_spec_uint64 ("rrc_serial",
+ rrc_serial),
+ GNUNET_PQ_result_spec_end
+ };
+
+ /* Used in #postgres_get_old_coin_by_h_blind() */
+ PREPARE (pg,
+ "old_coin_by_h_blind",
+ "SELECT"
+ " okc.coin_pub AS old_coin_pub"
+ ",rrc_serial"
+ " FROM refresh_revealed_coins rrc"
+ " JOIN refresh_commitments rcom USING (melt_serial_id)"
+ " JOIN known_coins okc ON (rcom.old_coin_pub = okc.coin_pub)"
+ " WHERE h_coin_ev=$1"
+ " LIMIT 1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "old_coin_by_h_blind",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_old_coin_by_h_blind.h b/src/exchangedb/pg_get_old_coin_by_h_blind.h
new file mode 100644
index 000000000..93ed541b6
--- /dev/null
+++ b/src/exchangedb/pg_get_old_coin_by_h_blind.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_old_coin_by_h_blind.h
+ * @brief implementation of the get_old_coin_by_h_blind function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_OLD_COIN_BY_H_BLIND_H
+#define PG_GET_OLD_COIN_BY_H_BLIND_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain information about which old coin a coin was refreshed
+ * given the hash of the blinded (fresh) coin.
+ *
+ * @param cls closure
+ * @param h_blind_ev hash of the blinded coin
+ * @param[out] old_coin_pub set to information about the old coin (on success only)
+ * @param[out] rrc_serial set to serial number of the entry in the database
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_old_coin_by_h_blind (
+ void *cls,
+ const struct TALER_BlindedCoinHashP *h_blind_ev,
+ struct TALER_CoinSpendPublicKeyP *old_coin_pub,
+ uint64_t *rrc_serial);
+
+#endif
diff --git a/src/exchangedb/pg_get_pending_kyc_requirement_process.c b/src/exchangedb/pg_get_pending_kyc_requirement_process.c
new file mode 100644
index 000000000..b9acddad1
--- /dev/null
+++ b/src/exchangedb/pg_get_pending_kyc_requirement_process.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_pending_kyc_requirement_process.c
+ * @brief Implementation of the get_pending_kyc_requirement_process function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_pending_kyc_requirement_process.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_pending_kyc_requirement_process (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ char **redirect_url)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (provider_section),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("redirect_url",
+ redirect_url),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ *redirect_url = NULL;
+ PREPARE (pg,
+ "get_pending_kyc_requirement_process",
+ "SELECT"
+ " redirect_url"
+ " FROM legitimization_processes"
+ " WHERE provider_section=$1"
+ " AND h_payto=$2"
+ " AND NOT finished"
+ " ORDER BY start_time DESC"
+ " LIMIT 1");
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "get_pending_kyc_requirement_process",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_pending_kyc_requirement_process.h b/src/exchangedb/pg_get_pending_kyc_requirement_process.h
new file mode 100644
index 000000000..738c4d65b
--- /dev/null
+++ b/src/exchangedb/pg_get_pending_kyc_requirement_process.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_pending_kyc_requirement_process.h
+ * @brief implementation of the get_pending_kyc_requirement_process function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_PENDING_KYC_REQUIREMENT_PROCESS_H
+#define PG_GET_PENDING_KYC_REQUIREMENT_PROCESS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Fetch information about pending KYC requirement process.
+ *
+ * @param cls closure
+ * @param h_payto account that must be KYC'ed
+ * @param provider_section provider that must be checked
+ * @param[out] redirect_url set to redirect URL for the process
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_pending_kyc_requirement_process (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ char **redirect_url);
+
+#endif
diff --git a/src/exchangedb/pg_get_policy_details.c b/src/exchangedb/pg_get_policy_details.c
new file mode 100644
index 000000000..6e1b5c5dc
--- /dev/null
+++ b/src/exchangedb/pg_get_policy_details.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_policy_details.c
+ * @brief Implementation of the get_policy_details function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_policy_details.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_policy_details (
+ void *cls,
+ const struct GNUNET_HashCode *hc,
+ struct TALER_PolicyDetails *details)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (hc),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("deadline",
+ &details->deadline),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("commitment",
+ &details->commitment),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total",
+ &details->accumulated_total),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("policy_fee",
+ &details->policy_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("transferable_amount",
+ &details->transferable_amount),
+ GNUNET_PQ_result_spec_auto_from_type ("state",
+ &details->fulfillment_state),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("policy_fulfillment_id",
+ &details->policy_fulfillment_id),
+ &details->no_policy_fulfillment_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_policy_details",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_policy_details.h b/src/exchangedb/pg_get_policy_details.h
new file mode 100644
index 000000000..e3d2b0a2c
--- /dev/null
+++ b/src/exchangedb/pg_get_policy_details.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_policy_details.h
+ * @brief implementation of the get_policy_details function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_POLICY_DETAILS_H
+#define PG_GET_POLICY_DETAILS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/* Get the details of a policy, referenced by its hash code
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param hc The hash code under which the details to a particular policy should be found
+ * @param[out] details The found details
+ * @return query execution status
+ * */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_policy_details (
+ void *cls,
+ const struct GNUNET_HashCode *hc,
+ struct TALER_PolicyDetails *details);
+
+#endif
diff --git a/src/exchangedb/pg_get_purse_deposit.c b/src/exchangedb/pg_get_purse_deposit.c
new file mode 100644
index 000000000..cb24855a1
--- /dev/null
+++ b/src/exchangedb/pg_get_purse_deposit.c
@@ -0,0 +1,84 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_purse_deposit.c
+ * @brief Implementation of the get_purse_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_purse_deposit.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_purse_deposit (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_Amount *amount,
+ struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_AgeCommitmentHash *phac,
+ struct TALER_CoinSpendSignatureP *coin_sig,
+ char **partner_url)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ bool is_null;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ h_denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ phac),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ amount),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("partner_base_url",
+ partner_url),
+ &is_null),
+ GNUNET_PQ_result_spec_end
+ };
+
+ *partner_url = NULL;
+ PREPARE (pg,
+ "select_purse_deposit_by_coin_pub",
+ "SELECT "
+ " coin_sig"
+ ",amount_with_fee"
+ ",denom_pub_hash"
+ ",age_commitment_hash"
+ ",partner_base_url"
+ " FROM purse_deposits"
+ " LEFT JOIN partners"
+ " USING (partner_serial_id)"
+ " JOIN known_coins kc"
+ " USING (coin_pub)"
+ " JOIN denominations"
+ " USING (denominations_serial)"
+ " WHERE purse_pub=$1"
+ " AND coin_pub=$2;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_purse_deposit_by_coin_pub",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_purse_deposit.h b/src/exchangedb/pg_get_purse_deposit.h
new file mode 100644
index 000000000..b9c9947f4
--- /dev/null
+++ b/src/exchangedb/pg_get_purse_deposit.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_purse_deposit.h
+ * @brief implementation of the get_purse_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_PURSE_DEPOSIT_H
+#define PG_GET_PURSE_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to obtain a coin deposit data from
+ * depositing the coin into a purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub purse to credit
+ * @param coin_pub coin to deposit (debit)
+ * @param[out] amount set fraction of the coin's value that was deposited (with fee)
+ * @param[out] h_denom_pub set to hash of denomination of the coin
+ * @param[out] phac set to hash of age restriction on the coin
+ * @param[out] coin_sig set to signature affirming the operation
+ * @param[out] partner_url set to the URL of the partner exchange, or NULL for ourselves, must be freed by caller
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_purse_deposit (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_Amount *amount,
+ struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_AgeCommitmentHash *phac,
+ struct TALER_CoinSpendSignatureP *coin_sig,
+ char **partner_url);
+
+#endif
diff --git a/src/exchangedb/pg_get_purse_request.c b/src/exchangedb/pg_get_purse_request.c
new file mode 100644
index 000000000..9d2ee5654
--- /dev/null
+++ b/src/exchangedb/pg_get_purse_request.c
@@ -0,0 +1,79 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_template.c
+ * @brief Implementation of the template function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_purse_request.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_purse_request (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t *age_limit,
+ struct TALER_Amount *target_amount,
+ struct TALER_Amount *balance,
+ struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merge_pub",
+ merge_pub),
+ GNUNET_PQ_result_spec_timestamp ("purse_expiration",
+ purse_expiration),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ h_contract_terms),
+ GNUNET_PQ_result_spec_uint32 ("age_limit",
+ age_limit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ target_amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ balance),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_sig",
+ purse_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_purse_request",
+ "SELECT "
+ " merge_pub"
+ ",purse_expiration"
+ ",h_contract_terms"
+ ",age_limit"
+ ",amount_with_fee"
+ ",balance"
+ ",purse_sig"
+ " FROM purse_requests"
+ " WHERE purse_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_purse_request",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_purse_request.h b/src/exchangedb/pg_get_purse_request.h
new file mode 100644
index 000000000..24620e1b8
--- /dev/null
+++ b/src/exchangedb/pg_get_purse_request.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+
+/**
+ * @file exchangedb/pg_get_purse_request.h
+ * @brief implementation of the get_purse_request function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_PURSE_REQUEST_H
+#define PG_GET_PURSE_REQUEST_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to return meta data about a purse by the
+ * purse public key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the purse
+ * @param[out] merge_pub public key representing the merge capability
+ * @param[out] purse_expiration when would an unmerged purse expire
+ * @param[out] h_contract_terms contract associated with the purse
+ * @param[out] age_limit the age limit for deposits into the purse
+ * @param[out] target_amount amount to be put into the purse
+ * @param[out] balance amount put so far into the purse
+ * @param[out] purse_sig signature of the purse over the initialization data
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_purse_request (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t *age_limit,
+ struct TALER_Amount *target_amount,
+ struct TALER_Amount *balance,
+ struct TALER_PurseContractSignatureP *purse_sig);
+
+#endif
diff --git a/src/exchangedb/pg_get_ready_deposit.c b/src/exchangedb/pg_get_ready_deposit.c
new file mode 100644
index 000000000..d8344faf1
--- /dev/null
+++ b/src/exchangedb/pg_get_ready_deposit.c
@@ -0,0 +1,74 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_ready_deposit.c
+ * @brief Implementation of the get_ready_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_ready_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_ready_deposit (void *cls,
+ uint64_t start_shard_row,
+ uint64_t end_shard_row,
+ struct TALER_MerchantPublicKeyP *merchant_pub,
+ char **payto_uri)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now
+ = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_uint64 (&start_shard_row),
+ GNUNET_PQ_query_param_uint64 (&end_shard_row),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ merchant_pub),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ payto_uri),
+ GNUNET_PQ_result_spec_end
+ };
+ const char *query = "deposits_get_ready";
+
+ PREPARE (pg,
+ query,
+ "SELECT"
+ " wts.payto_uri"
+ ",bdep.merchant_pub"
+ " FROM batch_deposits bdep"
+ " JOIN wire_targets wts"
+ " USING (wire_target_h_payto)"
+ " WHERE NOT (bdep.done OR bdep.policy_blocked)"
+ " AND bdep.wire_deadline<=$1"
+ " AND bdep.shard >= $2"
+ " AND bdep.shard <= $3"
+ " ORDER BY "
+ " bdep.wire_deadline ASC"
+ " ,bdep.shard ASC"
+ " LIMIT 1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ query,
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_ready_deposit.h b/src/exchangedb/pg_get_ready_deposit.h
new file mode 100644
index 000000000..b1dd7a968
--- /dev/null
+++ b/src/exchangedb/pg_get_ready_deposit.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_ready_deposit.h
+ * @brief implementation of the get_ready_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_READY_DEPOSIT_H
+#define PG_GET_READY_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Obtain information about deposits that are ready to be executed. Such
+ * deposits must not be marked as "done", the execution time must be
+ * in the past, and the KYC status must be 'ok'.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param start_shard_row minimum shard row to select
+ * @param end_shard_row maximum shard row to select (inclusive)
+ * @param[out] merchant_pub set to the public key of a merchant with a ready deposit
+ * @param[out] payto_uri set to the account of the merchant, to be freed by caller
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_ready_deposit (void *cls,
+ uint64_t start_shard_row,
+ uint64_t end_shard_row,
+ struct TALER_MerchantPublicKeyP *merchant_pub,
+ char **payto_uri);
+
+#endif
diff --git a/src/exchangedb/pg_get_refresh_reveal.c b/src/exchangedb/pg_get_refresh_reveal.c
new file mode 100644
index 000000000..08d4b21a5
--- /dev/null
+++ b/src/exchangedb/pg_get_refresh_reveal.c
@@ -0,0 +1,213 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_refresh_reveal.c
+ * @brief Implementation of the get_refresh_reveal function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_refresh_reveal.h"
+#include "pg_helper.h"
+
+
+/**
+ * Context where we aggregate data from the database.
+ * Closure for #add_revealed_coins().
+ */
+struct GetRevealContext
+{
+ /**
+ * Array of revealed coins we obtained from the DB.
+ */
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs;
+
+ /**
+ * Length of the @a rrcs array.
+ */
+ unsigned int rrcs_len;
+
+ /**
+ * Set to an error code if we ran into trouble.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct GetRevealContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+add_revealed_coins (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetRevealContext *grctx = cls;
+
+ if (0 == num_results)
+ return;
+ grctx->rrcs = GNUNET_new_array (num_results,
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin);
+ grctx->rrcs_len = num_results;
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint32_t off;
+ struct GNUNET_PQ_ResultSpec rso[] = {
+ GNUNET_PQ_result_spec_uint32 ("freshcoin_index",
+ &off),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rso,
+ i))
+ {
+ GNUNET_break (0);
+ grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ if (off >= num_results)
+ {
+ GNUNET_break (0);
+ grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ {
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &grctx->rrcs[off];
+ struct GNUNET_PQ_ResultSpec rsi[] = {
+ /* NOTE: freshcoin_index selected and discarded here... */
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &rrc->h_denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("link_sig",
+ &rrc->orig_coin_link_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("h_coin_ev",
+ &rrc->coin_envelope_hash),
+ TALER_PQ_result_spec_blinded_planchet ("coin_ev",
+ &rrc->blinded_planchet),
+ TALER_PQ_result_spec_exchange_withdraw_values ("ewv",
+ &rrc->exchange_vals),
+ TALER_PQ_result_spec_blinded_denom_sig ("ev_sig",
+ &rrc->coin_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (NULL !=
+ rrc->blinded_planchet.blinded_message)
+ {
+ /* duplicate offset, not allowed */
+ GNUNET_break (0);
+ grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rsi,
+ i))
+ {
+ GNUNET_break (0);
+ grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ }
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_refresh_reveal (void *cls,
+ const struct TALER_RefreshCommitmentP *rc,
+ TALER_EXCHANGEDB_RefreshCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GetRevealContext grctx;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (rc),
+ GNUNET_PQ_query_param_end
+ };
+
+ memset (&grctx,
+ 0,
+ sizeof (grctx));
+
+ /* Obtain information about the coins created in a refresh
+ operation, used in #postgres_get_refresh_reveal() */
+ PREPARE (pg,
+ "get_refresh_revealed_coins",
+ "SELECT "
+ " rrc.freshcoin_index"
+ ",denom.denom_pub_hash"
+ ",rrc.h_coin_ev"
+ ",rrc.link_sig"
+ ",rrc.coin_ev"
+ ",rrc.ewv"
+ ",rrc.ev_sig"
+ " FROM refresh_commitments"
+ " JOIN refresh_revealed_coins rrc"
+ " USING (melt_serial_id)"
+ " JOIN denominations denom "
+ " USING (denominations_serial)"
+ " WHERE rc=$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_refresh_revealed_coins",
+ params,
+ &add_revealed_coins,
+ &grctx);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ goto cleanup;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ default: /* can have more than one result */
+ break;
+ }
+ switch (grctx.qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto cleanup;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* should be impossible */
+ break;
+ }
+
+ /* Pass result back to application */
+ cb (cb_cls,
+ grctx.rrcs_len,
+ grctx.rrcs);
+cleanup:
+ for (unsigned int i = 0; i < grctx.rrcs_len; i++)
+ {
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &grctx.rrcs[i];
+
+ TALER_blinded_denom_sig_free (&rrc->coin_sig);
+ TALER_blinded_planchet_free (&rrc->blinded_planchet);
+ TALER_denom_ewv_free (&rrc->exchange_vals);
+ }
+ GNUNET_free (grctx.rrcs);
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_refresh_reveal.h b/src/exchangedb/pg_get_refresh_reveal.h
new file mode 100644
index 000000000..15b57b343
--- /dev/null
+++ b/src/exchangedb/pg_get_refresh_reveal.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_refresh_reveal.h
+ * @brief implementation of the get_refresh_reveal function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_REFRESH_REVEAL_H
+#define PG_GET_REFRESH_REVEAL_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Lookup in the database the coins that we want to
+ * create in the given refresh operation.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param rc identify commitment and thus refresh operation
+ * @param cb function to call with the results
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_refresh_reveal (void *cls,
+ const struct TALER_RefreshCommitmentP *rc,
+ TALER_EXCHANGEDB_RefreshCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_reserve_balance.c b/src/exchangedb/pg_get_reserve_balance.c
new file mode 100644
index 000000000..140bf3b70
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_balance.c
@@ -0,0 +1,55 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_reserve_balance.c
+ * @brief Implementation of the get_reserve_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_reserve_balance.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_balance (void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_Amount *balance)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount ("current_balance",
+ pg->currency,
+ balance),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_reserve_balance",
+ "SELECT"
+ " current_balance"
+ " FROM reserves"
+ " WHERE reserve_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_reserve_balance",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_reserve_balance.h b/src/exchangedb/pg_get_reserve_balance.h
new file mode 100644
index 000000000..6dc88d906
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_balance.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_reserve_balance.h
+ * @brief implementation of the get_reserve_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_RESERVE_BALANCE_H
+#define PG_GET_RESERVE_BALANCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Get the balance of the specified reserve.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param[out] balance set to the reserve balance
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_balance (void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_Amount *balance);
+
+#endif
diff --git a/src/exchangedb/pg_get_reserve_by_h_blind.c b/src/exchangedb/pg_get_reserve_by_h_blind.c
new file mode 100644
index 000000000..f87fe6cd4
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_by_h_blind.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_reserve_by_h_blind.c
+ * @brief Implementation of the get_reserve_by_h_blind function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_reserve_by_h_blind.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_by_h_blind (
+ void *cls,
+ const struct TALER_BlindedCoinHashP *bch,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *reserve_out_serial_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (bch),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ reserve_pub),
+ GNUNET_PQ_result_spec_uint64 ("reserve_out_serial_id",
+ reserve_out_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_get_reserve_by_h_blind() */
+ PREPARE (pg,
+ "reserve_by_h_blind",
+ "SELECT"
+ " reserves.reserve_pub"
+ ",reserve_out_serial_id"
+ " FROM reserves_out"
+ " JOIN reserves"
+ " USING (reserve_uuid)"
+ " WHERE h_blind_ev=$1"
+ " LIMIT 1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "reserve_by_h_blind",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_reserve_by_h_blind.h b/src/exchangedb/pg_get_reserve_by_h_blind.h
new file mode 100644
index 000000000..49c1c8403
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_by_h_blind.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_reserve_by_h_blind.h
+ * @brief implementation of the get_reserve_by_h_blind function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_RESERVE_BY_H_BLIND_H
+#define PG_GET_RESERVE_BY_H_BLIND_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Obtain information about which reserve a coin was generated
+ * from given the hash of the blinded coin.
+ *
+ * @param cls closure
+ * @param bch hash that uniquely identifies the withdraw request
+ * @param[out] reserve_pub set to information about the reserve (on success only)
+ * @param[out] reserve_out_serial_id set to row of the @a h_blind_ev in reserves_out
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_by_h_blind (
+ void *cls,
+ const struct TALER_BlindedCoinHashP *bch,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *reserve_out_serial_id);
+
+#endif
diff --git a/src/exchangedb/pg_get_reserve_history.c b/src/exchangedb/pg_get_reserve_history.c
new file mode 100644
index 000000000..1f1ca95b5
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_history.c
@@ -0,0 +1,936 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_get_reserve_history.c
+ * @brief Obtain (parts of) the history of a reserve.
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_reserve_history.h"
+#include "pg_start_read_committed.h"
+#include "pg_commit.h"
+#include "pg_rollback.h"
+#include "plugin_exchangedb_common.h"
+#include "pg_helper.h"
+
+/**
+ * How often do we re-try when encountering DB serialization issues?
+ * (We are read-only, so can only happen due to concurrent insert,
+ * which should be very rare.)
+ */
+#define RETRIES 3
+
+
+/**
+ * Closure for callbacks invoked via #TEH_PG_get_reserve_history().
+ */
+struct ReserveHistoryContext
+{
+
+ /**
+ * Which reserve are we building the history for?
+ */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /**
+ * Where we build the history.
+ */
+ struct TALER_EXCHANGEDB_ReserveHistory *rh;
+
+ /**
+ * Tail of @e rh list.
+ */
+ struct TALER_EXCHANGEDB_ReserveHistory *rh_tail;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Sum of all credit transactions.
+ */
+ struct TALER_Amount balance_in;
+
+ /**
+ * Sum of all debit transactions.
+ */
+ struct TALER_Amount balance_out;
+
+ /**
+ * Set to true on serious internal errors during
+ * the callbacks.
+ */
+ bool failed;
+};
+
+
+/**
+ * Append and return a fresh element to the reserve
+ * history kept in @a rhc.
+ *
+ * @param rhc where the history is kept
+ * @return the fresh element that was added
+ */
+static struct TALER_EXCHANGEDB_ReserveHistory *
+append_rh (struct ReserveHistoryContext *rhc)
+{
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ tail = GNUNET_new (struct TALER_EXCHANGEDB_ReserveHistory);
+ if (NULL != rhc->rh_tail)
+ {
+ rhc->rh_tail->next = tail;
+ rhc->rh_tail = tail;
+ }
+ else
+ {
+ rhc->rh_tail = tail;
+ rhc->rh = tail;
+ }
+ return tail;
+}
+
+
+/**
+ * Add bank transfers to result set for #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_bank_to_exchange (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+ struct PostgresClosure *pg = rhc->pg;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_BankTransfer *bt;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ bt = GNUNET_new (struct TALER_EXCHANGEDB_BankTransfer);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("wire_reference",
+ &bt->wire_reference),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
+ &bt->amount),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &bt->execution_date),
+ GNUNET_PQ_result_spec_string ("sender_account_details",
+ &bt->sender_account_details),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (bt);
+ rhc->failed = true;
+ return;
+ }
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_in,
+ &rhc->balance_in,
+ &bt->amount));
+ bt->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
+ tail->details.bank = bt;
+ } /* end of 'while (0 < rows)' */
+}
+
+
+/**
+ * Add coin withdrawals to result set for #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_withdraw_coin (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+ struct PostgresClosure *pg = rhc->pg;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_CollectableBlindcoin *cbc;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ cbc = GNUNET_new (struct TALER_EXCHANGEDB_CollectableBlindcoin);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
+ &cbc->h_coin_envelope),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &cbc->denom_pub_hash),
+ TALER_PQ_result_spec_blinded_denom_sig ("denom_sig",
+ &cbc->sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &cbc->reserve_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &cbc->amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+ &cbc->withdraw_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (cbc);
+ rhc->failed = true;
+ return;
+ }
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_out,
+ &rhc->balance_out,
+ &cbc->amount_with_fee));
+ cbc->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COIN;
+ tail->details.withdraw = cbc;
+ }
+}
+
+
+/**
+ * Add recoups to result set for #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_recoup (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+ struct PostgresClosure *pg = rhc->pg;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_Recoup *recoup;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ recoup = GNUNET_new (struct TALER_EXCHANGEDB_Recoup);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &recoup->value),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &recoup->coin.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &recoup->coin_blind),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &recoup->coin_sig),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &recoup->timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &recoup->coin.denom_pub_hash),
+ TALER_PQ_result_spec_denom_sig (
+ "denom_sig",
+ &recoup->coin.denom_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (recoup);
+ rhc->failed = true;
+ return;
+ }
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_in,
+ &rhc->balance_in,
+ &recoup->value));
+ recoup->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN;
+ tail->details.recoup = recoup;
+ } /* end of 'while (0 < rows)' */
+}
+
+
+/**
+ * Add exchange-to-bank transfers to result set for
+ * #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_exchange_to_bank (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+ struct PostgresClosure *pg = rhc->pg;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_ClosingTransfer *closing;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ closing = GNUNET_new (struct TALER_EXCHANGEDB_ClosingTransfer);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &closing->amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+ &closing->closing_fee),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &closing->execution_date),
+ GNUNET_PQ_result_spec_string ("receiver_account",
+ &closing->receiver_account_details),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid",
+ &closing->wtid),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (closing);
+ rhc->failed = true;
+ return;
+ }
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_out,
+ &rhc->balance_out,
+ &closing->amount));
+ closing->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK;
+ tail->details.closing = closing;
+ } /* end of 'while (0 < rows)' */
+}
+
+
+/**
+ * Add purse merge transfers to result set for
+ * #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_p2p_merge (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+ struct PostgresClosure *pg = rhc->pg;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_PurseMerge *merge;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ merge = GNUNET_new (struct TALER_EXCHANGEDB_PurseMerge);
+ {
+ uint32_t flags32;
+ struct TALER_Amount balance;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
+ &merge->purse_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ &balance),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &merge->amount_with_fee),
+ GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
+ &merge->merge_timestamp),
+ GNUNET_PQ_result_spec_timestamp ("purse_expiration",
+ &merge->purse_expiration),
+ GNUNET_PQ_result_spec_uint32 ("age_limit",
+ &merge->min_age),
+ GNUNET_PQ_result_spec_uint32 ("flags",
+ &flags32),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &merge->h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("merge_pub",
+ &merge->merge_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &merge->purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &merge->reserve_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (merge);
+ rhc->failed = true;
+ return;
+ }
+ merge->flags = (enum TALER_WalletAccountMergeFlags) flags32;
+ if ( (! GNUNET_TIME_absolute_is_future (
+ merge->merge_timestamp.abs_time)) &&
+ (-1 != TALER_amount_cmp (&balance,
+ &merge->amount_with_fee)) )
+ merge->merged = true;
+ }
+ if (merge->merged)
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_in,
+ &rhc->balance_in,
+ &merge->amount_with_fee));
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_out,
+ &rhc->balance_out,
+ &merge->purse_fee));
+ merge->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_PURSE_MERGE;
+ tail->details.merge = merge;
+ }
+}
+
+
+/**
+ * Add paid for history requests to result set for
+ * #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_open_requests (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+ struct PostgresClosure *pg = rhc->pg;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_OpenRequest *orq;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ orq = GNUNET_new (struct TALER_EXCHANGEDB_OpenRequest);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("open_fee",
+ &orq->open_fee),
+ GNUNET_PQ_result_spec_timestamp ("request_timestamp",
+ &orq->request_timestamp),
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &orq->reserve_expiration),
+ GNUNET_PQ_result_spec_uint32 ("requested_purse_limit",
+ &orq->purse_limit),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &orq->reserve_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (orq);
+ rhc->failed = true;
+ return;
+ }
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&rhc->balance_out,
+ &rhc->balance_out,
+ &orq->open_fee));
+ orq->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_OPEN_REQUEST;
+ tail->details.open_request = orq;
+ }
+}
+
+
+/**
+ * Add paid for history requests to result set for
+ * #TEH_PG_get_reserve_history.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+add_close_requests (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveHistoryContext *rhc = cls;
+
+ while (0 < num_results)
+ {
+ struct TALER_EXCHANGEDB_CloseRequest *crq;
+ struct TALER_EXCHANGEDB_ReserveHistory *tail;
+
+ crq = GNUNET_new (struct TALER_EXCHANGEDB_CloseRequest);
+ {
+ char *payto_uri;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("close_timestamp",
+ &crq->request_timestamp),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &crq->reserve_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ --num_results))
+ {
+ GNUNET_break (0);
+ GNUNET_free (crq);
+ rhc->failed = true;
+ return;
+ }
+ TALER_payto_hash (payto_uri,
+ &crq->target_account_h_payto);
+ GNUNET_free (payto_uri);
+ }
+ crq->reserve_pub = *rhc->reserve_pub;
+ tail = append_rh (rhc);
+ tail->type = TALER_EXCHANGEDB_RO_CLOSE_REQUEST;
+ tail->details.close_request = crq;
+ }
+}
+
+
+/**
+ * Add reserve history entries found.
+ *
+ * @param cls a `struct ReserveHistoryContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+handle_history_entry (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ static const struct
+ {
+ /**
+ * Table with reserve history entry we are responsible for.
+ */
+ const char *table;
+ /**
+ * Name of the prepared statement to run.
+ */
+ const char *statement;
+ /**
+ * Function to use to process the results.
+ */
+ GNUNET_PQ_PostgresResultHandler cb;
+ } work[] = {
+ /** #TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE */
+ { "reserves_in",
+ "reserves_in_get_transactions",
+ add_bank_to_exchange },
+ /** #TALER_EXCHANGEDB_RO_WITHDRAW_COIN */
+ { "reserves_out",
+ "get_reserves_out",
+ &add_withdraw_coin },
+ /** #TALER_EXCHANGEDB_RO_RECOUP_COIN */
+ { "recoup",
+ "recoup_by_reserve",
+ &add_recoup },
+ /** #TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK */
+ { "reserves_close",
+ "close_by_reserve",
+ &add_exchange_to_bank },
+ /** #TALER_EXCHANGEDB_RO_PURSE_MERGE */
+ { "purse_decision",
+ "merge_by_reserve",
+ &add_p2p_merge },
+ /** #TALER_EXCHANGEDB_RO_OPEN_REQUEST */
+ { "reserves_open_requests",
+ "open_request_by_reserve",
+ &add_open_requests },
+ /** #TALER_EXCHANGEDB_RO_CLOSE_REQUEST */
+ { "close_requests",
+ "close_request_by_reserve",
+ &add_close_requests },
+ /* List terminator */
+ { NULL, NULL, NULL }
+ };
+ struct ReserveHistoryContext *rhc = cls;
+ char *table_name;
+ uint64_t serial_id;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("table_name",
+ &table_name),
+ GNUNET_PQ_result_spec_uint64 ("serial_id",
+ &serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (rhc->reserve_pub),
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ while (0 < num_results--)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ bool found = false;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ num_results))
+ {
+ GNUNET_break (0);
+ rhc->failed = true;
+ return;
+ }
+
+ for (unsigned int i = 0;
+ NULL != work[i].cb;
+ i++)
+ {
+ if (0 != strcmp (table_name,
+ work[i].table))
+ continue;
+ found = true;
+ qs = GNUNET_PQ_eval_prepared_multi_select (rhc->pg->conn,
+ work[i].statement,
+ params,
+ work[i].cb,
+ rhc);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Reserve %s had %d transactions at %llu in table %s\n",
+ TALER_B2S (rhc->reserve_pub),
+ (int) qs,
+ (unsigned long long) serial_id,
+ table_name);
+ if (0 >= qs)
+ rhc->failed = true;
+ break;
+ }
+ if (! found)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Coin history includes unsupported table `%s`\n",
+ table_name);
+ rhc->failed = true;
+ }
+ GNUNET_PQ_cleanup_result (rs);
+ if (rhc->failed)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_history (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t start_off,
+ uint64_t etag_in,
+ uint64_t *etag_out,
+ struct TALER_Amount *balance,
+ struct TALER_EXCHANGEDB_ReserveHistory **rhp)
+{
+ struct PostgresClosure *pg = cls;
+ struct ReserveHistoryContext rhc = {
+ .pg = pg,
+ .reserve_pub = reserve_pub
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_QueryParam lparams[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_uint64 (&start_off),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ &rhc.balance_in));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (pg->currency,
+ &rhc.balance_out));
+
+ *rhp = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Getting transactions for reserve %s\n",
+ TALER_B2S (reserve_pub));
+ PREPARE (pg,
+ "get_reserve_history_etag",
+ "SELECT"
+ " hist.reserve_history_serial_id"
+ ",r.current_balance"
+ " FROM reserve_history hist"
+ " JOIN reserves r USING (reserve_pub)"
+ " WHERE hist.reserve_pub=$1"
+ " ORDER BY reserve_history_serial_id DESC"
+ " LIMIT 1;");
+ PREPARE (pg,
+ "get_reserve_history",
+ "SELECT"
+ " table_name"
+ ",serial_id"
+ " FROM reserve_history"
+ " WHERE reserve_pub=$1"
+ " AND reserve_history_serial_id > $2"
+ " ORDER BY reserve_history_serial_id DESC;");
+
+ PREPARE (pg,
+ "reserves_in_get_transactions",
+ "SELECT"
+ " ri.wire_reference"
+ ",ri.credit"
+ ",ri.execution_date"
+ ",wt.payto_uri AS sender_account_details"
+ " FROM reserves_in ri"
+ " JOIN wire_targets wt"
+ " ON (wire_source_h_payto = wire_target_h_payto)"
+ " WHERE ri.reserve_pub=$1"
+ " AND ri.reserve_in_serial_id=$2;");
+ PREPARE (pg,
+ "get_reserves_out",
+ "SELECT"
+ " ro.h_blind_ev"
+ ",denom.denom_pub_hash"
+ ",ro.denom_sig"
+ ",ro.reserve_sig"
+ ",ro.execution_date"
+ ",ro.amount_with_fee"
+ ",denom.fee_withdraw"
+ " FROM reserves_out ro"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " JOIN reserves res"
+ " USING (reserve_uuid)"
+ " WHERE ro.reserve_out_serial_id=$2"
+ " AND res.reserve_pub=$1;");
+ PREPARE (pg,
+ "recoup_by_reserve",
+ "SELECT"
+ " rec.coin_pub"
+ ",rec.coin_sig"
+ ",rec.coin_blind"
+ ",rec.amount"
+ ",rec.recoup_timestamp"
+ ",denom.denom_pub_hash"
+ ",kc.denom_sig"
+ " FROM recoup rec"
+ " JOIN reserves_out ro"
+ " USING (reserve_out_serial_id)"
+ " JOIN reserves res"
+ " USING (reserve_uuid)"
+ " JOIN known_coins kc"
+ " USING (coin_pub)"
+ " JOIN denominations denom"
+ " ON (denom.denominations_serial = kc.denominations_serial)"
+ " WHERE rec.recoup_uuid=$2"
+ " AND res.reserve_pub=$1;");
+ PREPARE (pg,
+ "close_by_reserve",
+ "SELECT"
+ " rc.amount"
+ ",rc.closing_fee"
+ ",rc.execution_date"
+ ",wt.payto_uri AS receiver_account"
+ ",rc.wtid"
+ " FROM reserves_close rc"
+ " JOIN wire_targets wt"
+ " USING (wire_target_h_payto)"
+ " WHERE reserve_pub=$1"
+ " AND close_uuid=$2;");
+ PREPARE (pg,
+ "merge_by_reserve",
+ "SELECT"
+ " pr.amount_with_fee"
+ ",pr.balance"
+ ",pr.purse_fee"
+ ",pr.h_contract_terms"
+ ",pr.merge_pub"
+ ",am.reserve_sig"
+ ",pm.purse_pub"
+ ",pm.merge_timestamp"
+ ",pr.purse_expiration"
+ ",pr.age_limit"
+ ",pr.flags"
+ " FROM purse_decision pdes"
+ " JOIN purse_requests pr"
+ " ON (pr.purse_pub = pdes.purse_pub)"
+ " JOIN purse_merges pm"
+ " ON (pm.purse_pub = pdes.purse_pub)"
+ " JOIN account_merges am"
+ " ON (am.purse_pub = pm.purse_pub AND"
+ " am.reserve_pub = pm.reserve_pub)"
+ " WHERE pdes.purse_decision_serial_id=$2"
+ " AND pm.reserve_pub=$1"
+ " AND COALESCE(pm.partner_serial_id,0)=0" /* must be local! */
+ " AND NOT pdes.refunded;");
+ PREPARE (pg,
+ "open_request_by_reserve",
+ "SELECT"
+ " reserve_payment"
+ ",request_timestamp"
+ ",expiration_date"
+ ",requested_purse_limit"
+ ",reserve_sig"
+ " FROM reserves_open_requests"
+ " WHERE reserve_pub=$1"
+ " AND open_request_uuid=$2;");
+ PREPARE (pg,
+ "close_request_by_reserve",
+ "SELECT"
+ " close_timestamp"
+ ",payto_uri"
+ ",reserve_sig"
+ " FROM close_requests"
+ " WHERE reserve_pub=$1"
+ " AND close_request_serial_id=$2;");
+
+ for (unsigned int i = 0; i<RETRIES; i++)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t end;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("reserve_history_serial_id",
+ &end),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
+ balance),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ TEH_PG_start_read_committed (pg,
+ "get-reserve-transactions"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* First only check the last item, to see if
+ we even need to iterate */
+ qs = GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "get_reserve_history_etag",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ *etag_out = end;
+ if (end == etag_in)
+ return qs;
+ }
+ /* We indeed need to iterate over the history */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Current ETag for reserve %s is %llu\n",
+ TALER_B2S (reserve_pub),
+ (unsigned long long) end);
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "get_reserve_history",
+ lparams,
+ &handle_history_entry,
+ &rhc);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TEH_PG_rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_PG_rollback (pg);
+ continue;
+ default:
+ break;
+ }
+ if (rhc.failed)
+ {
+ TEH_PG_rollback (pg);
+ TEH_COMMON_free_reserve_history (pg,
+ rhc.rh);
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ }
+ qs = TEH_PG_commit (pg);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TEH_COMMON_free_reserve_history (pg,
+ rhc.rh);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TEH_COMMON_free_reserve_history (pg,
+ rhc.rh);
+ rhc.rh = NULL;
+ continue;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ *rhp = rhc.rh;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ }
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
diff --git a/src/exchangedb/pg_get_reserve_history.h b/src/exchangedb/pg_get_reserve_history.h
new file mode 100644
index 000000000..15765f127
--- /dev/null
+++ b/src/exchangedb/pg_get_reserve_history.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_get_reserve_history.h
+ * @brief implementation of the get_reserve_history function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_RESERVE_HISTORY_H
+#define PG_GET_RESERVE_HISTORY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Compile a list of (historic) transactions performed with the given reserve
+ * (withdraw, incoming wire, open, close operations). Should return 0 if the @a
+ * reserve_pub is unknown, otherwise determine @a etag_out and if it is past @a
+ * etag_in return the history after @a start_off. @a etag_out should be set
+ * to the last row ID of the given @a reserve_pub in the reserve history table.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param start_off maximum starting offset in history to exclude from returning
+ * @param etag_in up to this offset the client already has a response, do not
+ * return anything unless @a etag_out will be larger
+ * @param[out] etag_out set to the latest history offset known for this @a coin_pub
+ * @param[out] balance set to the reserve balance
+ * @param[out] rhp set to known transaction history (NULL if reserve is unknown)
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_reserve_history (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t start_off,
+ uint64_t etag_in,
+ uint64_t *etag_out,
+ struct TALER_Amount *balance,
+ struct TALER_EXCHANGEDB_ReserveHistory **rhp);
+
+
+#endif
diff --git a/src/exchangedb/pg_get_signature_for_known_coin.c b/src/exchangedb/pg_get_signature_for_known_coin.c
new file mode 100644
index 000000000..06074312f
--- /dev/null
+++ b/src/exchangedb/pg_get_signature_for_known_coin.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_signature_for_known_coin.c
+ * @brief Implementation of the get_signature_for_known_coin function for Postgres
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_signature_for_known_coin.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_signature_for_known_coin (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_DenominationPublicKey *denom_pub,
+ struct TALER_DenominationSignature *denom_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ denom_pub),
+ TALER_PQ_result_spec_denom_sig ("denom_sig",
+ denom_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Getting denomination and signature for (potentially) known coin %s\n",
+ TALER_B2S (coin_pub));
+ PREPARE (pg,
+ "get_signature_for_known_coin",
+ "SELECT"
+ " denominations.denom_pub"
+ ",denom_sig"
+ " FROM known_coins"
+ " JOIN denominations USING (denominations_serial)"
+ " WHERE coin_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_signature_for_known_coin",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_signature_for_known_coin.h b/src/exchangedb/pg_get_signature_for_known_coin.h
new file mode 100644
index 000000000..ec389176b
--- /dev/null
+++ b/src/exchangedb/pg_get_signature_for_known_coin.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_signature_for_known_coin.h
+ * @brief implementation of the get_signature_for_known_coin function for Postgres
+ * @author Özgür Kesim
+ */
+#ifndef PG_GET_SIGNATURE_FOR_KNOWN_COIN_H
+#define PG_GET_SIGNATURE_FOR_KNOWN_COIN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Retrieve the denomination and the corresponding signature for a known coin.
+ *
+ * @param cls the plugin closure
+ * @param coin_pub the public key of the coin to search for
+ * @param[out] denom_pub the denomination of the public key, if coin was present
+ * @param[out] denom_sig the signature with the denomination key of the coin, if coin was present
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_signature_for_known_coin (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ struct TALER_DenominationPublicKey *denom_pub,
+ struct TALER_DenominationSignature *denom_sig);
+
+#endif
diff --git a/src/exchangedb/pg_get_unfinished_close_requests.c b/src/exchangedb/pg_get_unfinished_close_requests.c
new file mode 100644
index 000000000..990e8e00e
--- /dev/null
+++ b/src/exchangedb/pg_get_unfinished_close_requests.c
@@ -0,0 +1,166 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_get_unfinished_close_requests.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_unfinished_close_requests.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #reserve_close_cb().
+ */
+struct CloseReserveContext
+{
+ /**
+ * Function to call for each to be closed reserve.
+ */
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec;
+
+ /**
+ * Closure to give to @e rec.
+ */
+ void *rec_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserve_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CloseReserveContext *erc = cls;
+ struct PostgresClosure *pg = erc->pg;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_OK;
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_TIME_Timestamp exp_date;
+ char *account_details;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_Amount remaining_balance;
+ uint64_t close_request_row;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &exp_date),
+ GNUNET_PQ_result_spec_string ("account_details",
+ &account_details),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("close",
+ &remaining_balance),
+ GNUNET_PQ_result_spec_uint64 ("close_request_serial_id",
+ &close_request_row),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ret = GNUNET_SYSERR;
+ break;
+ }
+ ret = erc->rec (erc->rec_cls,
+ &reserve_pub,
+ &remaining_balance,
+ account_details,
+ exp_date,
+ close_request_row);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+ erc->status = ret;
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_unfinished_close_requests (
+ void *cls,
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+ void *rec_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct CloseReserveContext ectx = {
+ .rec = rec,
+ .rec_cls = rec_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "get_unfinished_close_requests",
+ "UPDATE close_requests AS rc"
+ " SET done=TRUE"
+ " WHERE done=FALSE"
+ " RETURNING"
+ " reserve_pub"
+ " ,close_request_serial_id"
+ " ,close_timestamp AS expiration_date"
+ " ,close"
+ " ,(SELECT payto_uri"
+ " FROM reserves_in ri"
+ " JOIN wire_targets wt ON (ri.wire_source_h_payto = wt.wire_target_h_payto)"
+ " WHERE ri.reserve_pub=rc.reserve_pub)"
+ " AS account_details;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_unfinished_close_requests",
+ params,
+ &reserve_cb,
+ &ectx);
+ switch (ectx.status)
+ {
+ case GNUNET_SYSERR:
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_NO:
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+ case GNUNET_OK:
+ break;
+ }
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_unfinished_close_requests.h b/src/exchangedb/pg_get_unfinished_close_requests.h
new file mode 100644
index 000000000..4c5aa0d1d
--- /dev/null
+++ b/src/exchangedb/pg_get_unfinished_close_requests.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_get_unfinished_close_requests.h
+ * @brief implementation of the get_unfinished_close_requests function
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_UNFINISHED_CLOSE_REQUESTS_H
+#define PG_GET_UNFINISHED_CLOSE_REQUESTS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Obtain information about force-closed reserves
+ * where the close was not yet done (and their remaining
+ * balances). Updates the returned reserve's close
+ * status to "done".
+ *
+ * @param cls closure of the plugin
+ * @param rec function to call on expired reserves
+ * @param rec_cls closure for @a rec
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_unfinished_close_requests (
+ void *cls,
+ TALER_EXCHANGEDB_ReserveExpiredCallback rec,
+ void *rec_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_wire_accounts.c b/src/exchangedb/pg_get_wire_accounts.c
new file mode 100644
index 000000000..9770be719
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_accounts.c
@@ -0,0 +1,169 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_accounts.c
+ * @brief Implementation of the get_wire_accounts function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_wire_accounts.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_wire_accounts_cb().
+ */
+struct GetWireAccountsContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_WireAccountCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct MissingWireContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_wire_accounts_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetWireAccountsContext *ctx = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *payto_uri;
+ char *conversion_url = NULL;
+ json_t *debit_restrictions = NULL;
+ json_t *credit_restrictions = NULL;
+ struct TALER_MasterSignatureP master_sig;
+ char *bank_label = NULL;
+ int64_t priority;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("conversion_url",
+ &conversion_url),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("bank_label",
+ &bank_label),
+ NULL),
+ GNUNET_PQ_result_spec_int64 ("priority",
+ &priority),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("debit_restrictions",
+ &debit_restrictions),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("credit_restrictions",
+ &credit_restrictions),
+ NULL),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ if (NULL == debit_restrictions)
+ {
+ debit_restrictions = json_array ();
+ GNUNET_assert (NULL != debit_restrictions);
+ }
+ if (NULL == credit_restrictions)
+ {
+ credit_restrictions = json_array ();
+ GNUNET_assert (NULL != credit_restrictions);
+ }
+ ctx->cb (ctx->cb_cls,
+ payto_uri,
+ conversion_url,
+ debit_restrictions,
+ credit_restrictions,
+ &master_sig,
+ bank_label,
+ priority);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_accounts (void *cls,
+ TALER_EXCHANGEDB_WireAccountCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GetWireAccountsContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .status = GNUNET_OK
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "get_wire_accounts",
+ "SELECT"
+ " payto_uri"
+ ",conversion_url"
+ ",debit_restrictions"
+ ",credit_restrictions"
+ ",master_sig"
+ ",bank_label"
+ ",priority"
+ " FROM wire_accounts"
+ " WHERE is_active");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_wire_accounts",
+ params,
+ &get_wire_accounts_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_wire_accounts.h b/src/exchangedb/pg_get_wire_accounts.h
new file mode 100644
index 000000000..f4dc97ce0
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_accounts.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_accounts.h
+ * @brief implementation of the get_wire_accounts function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_WIRE_ACCOUNTS_H
+#define PG_GET_WIRE_ACCOUNTS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Obtain information about the enabled wire accounts of the exchange.
+ *
+ * @param cls closure
+ * @param cb function to call on each account
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_accounts (void *cls,
+ TALER_EXCHANGEDB_WireAccountCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_wire_fee.c b/src/exchangedb/pg_get_wire_fee.c
new file mode 100644
index 000000000..40f517f28
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_fee.c
@@ -0,0 +1,73 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_fee.c
+ * @brief Implementation of the get_wire_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_wire_fee.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_fee (void *cls,
+ const char *type,
+ struct GNUNET_TIME_Timestamp date,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_WireFeeSet *fees,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (type),
+ GNUNET_PQ_query_param_timestamp (&date),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("start_date",
+ start_date),
+ GNUNET_PQ_result_spec_timestamp ("end_date",
+ end_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
+ &fees->wire),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+ &fees->closing),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_wire_fee",
+ "SELECT "
+ " start_date"
+ ",end_date"
+ ",wire_fee"
+ ",closing_fee"
+ ",master_sig"
+ " FROM wire_fee"
+ " WHERE wire_method=$1"
+ " AND start_date <= $2"
+ " AND end_date > $2;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_wire_fee",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_wire_fee.h b/src/exchangedb/pg_get_wire_fee.h
new file mode 100644
index 000000000..409a5c48b
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_fee.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_fee.h
+ * @brief implementation of the get_wire_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_WIRE_FEE_H
+#define PG_GET_WIRE_FEE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain wire fee from database.
+ *
+ * @param cls closure
+ * @param type type of wire transfer the fee applies for
+ * @param date for which date do we want the fee?
+ * @param[out] start_date when does the fee go into effect
+ * @param[out] end_date when does the fee end being valid
+ * @param[out] fees how high are the wire fees
+ * @param[out] master_sig signature over the above by the exchange master key
+ * @return status of the transaction
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_fee (void *cls,
+ const char *type,
+ struct GNUNET_TIME_Timestamp date,
+ struct GNUNET_TIME_Timestamp *start_date,
+ struct GNUNET_TIME_Timestamp *end_date,
+ struct TALER_WireFeeSet *fees,
+ struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_get_wire_fees.c b/src/exchangedb/pg_get_wire_fees.c
new file mode 100644
index 000000000..193ccff6a
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_fees.c
@@ -0,0 +1,147 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_fees.c
+ * @brief Implementation of the get_wire_fees function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_wire_fees.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #get_wire_fees_cb().
+ */
+struct GetWireFeesContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_WireFeeCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct GetWireFeesContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_wire_fees_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetWireFeesContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct TALER_MasterSignatureP master_sig;
+ struct TALER_WireFeeSet fees;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
+ &fees.wire),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+ &fees.closing),
+ GNUNET_PQ_result_spec_timestamp ("start_date",
+ &start_date),
+ GNUNET_PQ_result_spec_timestamp ("end_date",
+ &end_date),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &fees,
+ start_date,
+ end_date,
+ &master_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_fees (void *cls,
+ const char *wire_method,
+ TALER_EXCHANGEDB_WireFeeCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (wire_method),
+ GNUNET_PQ_query_param_end
+ };
+ struct GetWireFeesContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "get_wire_fees",
+ "SELECT"
+ " wire_fee"
+ ",closing_fee"
+ ",start_date"
+ ",end_date"
+ ",master_sig"
+ " FROM wire_fee"
+ " WHERE wire_method=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "get_wire_fees",
+ params,
+ &get_wire_fees_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_wire_fees.h b/src/exchangedb/pg_get_wire_fees.h
new file mode 100644
index 000000000..798a514db
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_fees.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_fees.h
+ * @brief implementation of the get_wire_fees function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_WIRE_FEES_H
+#define PG_GET_WIRE_FEES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain information about the fee structure of the exchange for
+ * a given @a wire_method
+ *
+ * @param cls closure
+ * @param wire_method which wire method to obtain fees for
+ * @param cb function to call on each account
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_fees (void *cls,
+ const char *wire_method,
+ TALER_EXCHANGEDB_WireFeeCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_get_wire_hash_for_contract.c b/src/exchangedb/pg_get_wire_hash_for_contract.c
new file mode 100644
index 000000000..afd659b18
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_hash_for_contract.c
@@ -0,0 +1,81 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_hash_for_contract.c
+ * @brief Implementation of the get_wire_hash_for_contract function for Postgres
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_exchangedb_plugin.h"
+#include "taler_pq_lib.h"
+#include "pg_get_wire_hash_for_contract.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_hash_for_contract (
+ void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct TALER_MerchantWireHashP *h_wire)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_end
+ };
+ char *payto_uri;
+ struct TALER_WireSaltP wire_salt;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+ &wire_salt),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_end
+ };
+
+ /* check if the necessary records exist and get them */
+ PREPARE (pg,
+ "get_wire_hash_for_contract",
+ "SELECT"
+ " bdep.wire_salt"
+ ",wt.payto_uri"
+ " FROM coin_deposits"
+ " JOIN batch_deposits bdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN wire_targets wt"
+ " USING (wire_target_h_payto)"
+ " WHERE bdep.merchant_pub=$1"
+ " AND bdep.h_contract_terms=$2");
+ /* NOTE: above query might be more efficient if we computed the shard
+ from the merchant_pub and included that in the query */
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_wire_hash_for_contract",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ TALER_merchant_wire_signature_hash (payto_uri,
+ &wire_salt,
+ h_wire);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ return qs;
+}
diff --git a/src/exchangedb/pg_get_wire_hash_for_contract.h b/src/exchangedb/pg_get_wire_hash_for_contract.h
new file mode 100644
index 000000000..f95cd29c1
--- /dev/null
+++ b/src/exchangedb/pg_get_wire_hash_for_contract.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_wire_hash_for_contract.h
+ * @brief implementation of the get_wire_hash_for_contract function for Postgres
+ * @author Özgür Kesim
+ */
+#ifndef PG_GET_WIRE_HASH_FOR_CONTRACT_H
+#define PG_GET_WIRE_HASH_FOR_CONTRACT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Try to get the salted hash of a merchant's bank account to a deposit
+ * contract. This is necessary in the event of a conflict with a given
+ * (merchant_pub, h_contract_terms) during deposit.
+ *
+ * @param cls closure
+ * @param merchant_pub merchant public key
+ * @param h_contract_terms hash of the proposal data
+ * @param[out] h_wire salted hash of a merchant's bank account
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_wire_hash_for_contract (
+ void *cls,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ struct TALER_MerchantWireHashP *h_wire);
+
+#endif
diff --git a/src/exchangedb/pg_get_withdraw_info.c b/src/exchangedb/pg_get_withdraw_info.c
new file mode 100644
index 000000000..e06fa3764
--- /dev/null
+++ b/src/exchangedb/pg_get_withdraw_info.c
@@ -0,0 +1,79 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_withdraw_info.c
+ * @brief Implementation of the get_withdraw_info function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_withdraw_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_withdraw_info (
+ void *cls,
+ const struct TALER_BlindedCoinHashP *bch,
+ struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (bch),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &collectable->denom_pub_hash),
+ TALER_PQ_result_spec_blinded_denom_sig ("denom_sig",
+ &collectable->sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &collectable->reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &collectable->reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
+ &collectable->h_coin_envelope),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &collectable->amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+ &collectable->withdraw_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_withdraw_info",
+ "SELECT"
+ " denom.denom_pub_hash"
+ ",denom_sig"
+ ",reserve_sig"
+ ",reserves.reserve_pub"
+ ",execution_date"
+ ",h_blind_ev"
+ ",amount_with_fee"
+ ",denom.fee_withdraw"
+ " FROM reserves_out"
+ " JOIN reserves"
+ " USING (reserve_uuid)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE h_blind_ev=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_withdraw_info",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_get_withdraw_info.h b/src/exchangedb/pg_get_withdraw_info.h
new file mode 100644
index 000000000..7c3e06a02
--- /dev/null
+++ b/src/exchangedb/pg_get_withdraw_info.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_get_withdraw_info.h
+ * @brief implementation of the get_withdraw_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_GET_WITHDRAW_INFO_H
+#define PG_GET_WITHDRAW_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Locate the response for a /reserve/withdraw request under the
+ * key of the hash of the blinded message.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param bch hash that uniquely identifies the withdraw operation
+ * @param collectable corresponding collectable coin (blind signature)
+ * if a coin is found
+ * @return statement execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_withdraw_info (
+ void *cls,
+ const struct TALER_BlindedCoinHashP *bch,
+ struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable);
+
+#endif
diff --git a/src/exchangedb/pg_have_deposit2.c b/src/exchangedb/pg_have_deposit2.c
new file mode 100644
index 000000000..e00ad7490
--- /dev/null
+++ b/src/exchangedb/pg_have_deposit2.c
@@ -0,0 +1,117 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_have_deposit2.c
+ * @brief Implementation of the have_deposit2 function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_have_deposit2.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_have_deposit2 (
+ void *cls,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct TALER_Amount *deposit_fee,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_auto_from_type (merchant),
+ GNUNET_PQ_query_param_end
+ };
+ struct TALER_EXCHANGEDB_Deposit deposit2;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &deposit2.amount_with_fee),
+ GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
+ &deposit2.timestamp),
+ GNUNET_PQ_result_spec_timestamp ("exchange_timestamp",
+ exchange_timestamp),
+ GNUNET_PQ_result_spec_timestamp ("refund_deadline",
+ &deposit2.refund_deadline),
+ GNUNET_PQ_result_spec_timestamp ("wire_deadline",
+ &deposit2.wire_deadline),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ deposit_fee),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+ &deposit2.wire_salt),
+ GNUNET_PQ_result_spec_string ("receiver_wire_account",
+ &deposit2.receiver_wire_account),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_MerchantWireHashP h_wire2;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Getting deposits for coin %s\n",
+ TALER_B2S (coin_pub));
+ PREPARE (pg,
+ "get_deposit",
+ "SELECT"
+ " cdep.amount_with_fee"
+ ",denominations.fee_deposit"
+ ",bdep.wallet_timestamp"
+ ",bdep.exchange_timestamp"
+ ",bdep.refund_deadline"
+ ",bdep.wire_deadline"
+ ",bdep.h_contract_terms"
+ ",bdep.wire_salt"
+ ",wt.payto_uri AS receiver_wire_account"
+ " FROM coin_deposits cdep"
+ " JOIN batch_deposits bdep USING (batch_deposit_serial_id)"
+ " JOIN known_coins kc ON (kc.coin_pub = cdep.coin_pub)"
+ " JOIN denominations USING (denominations_serial)"
+ " JOIN wire_targets wt USING (wire_target_h_payto)"
+ " WHERE cdep.coin_pub=$1"
+ " AND bdep.merchant_pub=$3"
+ " AND bdep.h_contract_terms=$2;");
+ /* Note: query might be made more efficient if we computed the 'shard'
+ from merchant_pub and included that as a constraint on bdep! */
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_deposit",
+ params,
+ rs);
+ if (0 >= qs)
+ return qs;
+ TALER_merchant_wire_signature_hash (deposit2.receiver_wire_account,
+ &deposit2.wire_salt,
+ &h_wire2);
+ GNUNET_free (deposit2.receiver_wire_account);
+ /* Now we check that the other information in @a deposit
+ also matches, and if not report inconsistencies. */
+ if ( (GNUNET_TIME_timestamp_cmp (refund_deadline,
+ !=,
+ deposit2.refund_deadline)) ||
+ (0 != GNUNET_memcmp (h_wire,
+ &h_wire2) ) )
+ {
+ /* Inconsistencies detected! Does not match! */
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
diff --git a/src/exchangedb/pg_have_deposit2.h b/src/exchangedb/pg_have_deposit2.h
new file mode 100644
index 000000000..0e8119c20
--- /dev/null
+++ b/src/exchangedb/pg_have_deposit2.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_have_deposit2.h
+ * @brief implementation of the have_deposit2 function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_HAVE_DEPOSIT2_H
+#define PG_HAVE_DEPOSIT2_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Check if we have the specified deposit already in the database.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param h_contract_terms contract to check for
+ * @param h_wire wire hash to check for
+ * @param coin_pub public key of the coin to check for
+ * @param merchant merchant public key to check for
+ * @param refund_deadline expected refund deadline
+ * @param[out] deposit_fee set to the deposit fee the exchange charged
+ * @param[out] exchange_timestamp set to the time when the exchange received the deposit
+ * @return 1 if we know this operation,
+ * 0 if this exact deposit is unknown to us,
+ * otherwise transaction error status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_have_deposit2 (
+ void *cls,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct TALER_Amount *deposit_fee,
+ struct GNUNET_TIME_Timestamp *exchange_timestamp);
+#endif
diff --git a/src/exchangedb/pg_helper.h b/src/exchangedb/pg_helper.h
new file mode 100644
index 000000000..c63c9111d
--- /dev/null
+++ b/src/exchangedb/pg_helper.h
@@ -0,0 +1,149 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_helper.h
+ * @brief shared internal definitions for postgres DB plugin
+ * @author Christian Grothoff
+ */
+#ifndef PG_HELPER_H
+#define PG_HELPER_H
+
+
+/**
+ * Type of the "cls" argument given to each of the functions in
+ * our API.
+ */
+struct PostgresClosure
+{
+
+ /**
+ * Our configuration.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Directory with SQL statements to run to create tables.
+ */
+ char *sql_dir;
+
+ /**
+ * After how long should idle reserves be closed?
+ */
+ struct GNUNET_TIME_Relative idle_reserve_expiration_time;
+
+ /**
+ * After how long should reserves that have seen withdraw operations
+ * be garbage collected?
+ */
+ struct GNUNET_TIME_Relative legal_reserve_expiration_time;
+
+ /**
+ * What delay should we introduce before ready transactions
+ * are actually aggregated?
+ */
+ struct GNUNET_TIME_Relative aggregator_shift;
+
+ /**
+ * Which currency should we assume all amounts to be in?
+ */
+ char *currency;
+
+ /**
+ * Our base URL.
+ */
+ char *exchange_url;
+
+ /**
+ * Postgres connection handle.
+ */
+ struct GNUNET_PQ_Context *conn;
+
+ /**
+ * Name of the current transaction, for debugging.
+ */
+ const char *transaction_name;
+
+ /**
+ * Counts how often we have established a fresh @e conn
+ * to the database. Used to re-prepare statements.
+ */
+ unsigned long long prep_gen;
+
+ /**
+ * Number of purses we allow to be opened concurrently
+ * for one year per annual fee payment.
+ */
+ uint32_t def_purse_limit;
+
+};
+
+
+/**
+ * Prepares SQL statement @a sql under @a name for
+ * connection @a pg once.
+ * Returns with #GNUNET_DB_STATUS_HARD_ERROR on failure.
+ *
+ * @param pg a `struct PostgresClosure`
+ * @param name name to prepare the statement under
+ * @param sql actual SQL text
+ */
+#define PREPARE(pg,name,sql) \
+ do { \
+ static struct { \
+ unsigned long long cnt; \
+ struct PostgresClosure *pg; \
+ } preps[2]; /* 2 ctrs for taler-auditor-sync*/ \
+ unsigned int off = 0; \
+ \
+ while ( (NULL != preps[off].pg) && \
+ (pg != preps[off].pg) && \
+ (off < sizeof(preps) / sizeof(*preps)) ) \
+ off++; \
+ GNUNET_assert (off < \
+ sizeof(preps) / sizeof(*preps)); \
+ if (preps[off].cnt < pg->prep_gen) \
+ { \
+ struct GNUNET_PQ_PreparedStatement ps[] = { \
+ GNUNET_PQ_make_prepare (name, sql), \
+ GNUNET_PQ_PREPARED_STATEMENT_END \
+ }; \
+ \
+ if (GNUNET_OK != \
+ GNUNET_PQ_prepare_statements (pg->conn, \
+ ps)) \
+ { \
+ GNUNET_break (0); \
+ return GNUNET_DB_STATUS_HARD_ERROR; \
+ } \
+ preps[off].pg = pg; \
+ preps[off].cnt = pg->prep_gen; \
+ } \
+ } while (0)
+
+
+/**
+ * Wrapper macro to add the currency from the plugin's state
+ * when fetching amounts from the database.
+ *
+ * @param field name of the database field to fetch amount from
+ * @param[out] amountp pointer to amount to set
+ */
+#define TALER_PQ_RESULT_SPEC_AMOUNT(field, \
+ amountp) TALER_PQ_result_spec_amount ( \
+ field,pg->currency,amountp)
+
+
+#endif
diff --git a/src/exchangedb/pg_inject_auditor_triggers.c b/src/exchangedb/pg_inject_auditor_triggers.c
new file mode 100644
index 000000000..562a1a22c
--- /dev/null
+++ b/src/exchangedb/pg_inject_auditor_triggers.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_inject_auditor_triggers.c
+ * @brief Implementation of the inject_auditor_triggers function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_gc.h"
+#include "pg_helper.h"
+
+
+/**
+ * Function called to inject auditor triggers into the
+ * database, triggering the real-time auditor upon
+ * relevant INSERTs.
+ *
+ * @param cls closure
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR on DB errors
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_inject_auditor_triggers (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_Context *conn;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+ "exchangedb-postgres",
+ "auditor-triggers-",
+ es,
+ NULL);
+ if (NULL == conn)
+ return GNUNET_SYSERR;
+ GNUNET_PQ_disconnect (conn);
+ return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_inject_auditor_triggers.h b/src/exchangedb/pg_inject_auditor_triggers.h
new file mode 100644
index 000000000..2dfa9468c
--- /dev/null
+++ b/src/exchangedb/pg_inject_auditor_triggers.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_inject_auditor_triggers.h
+ * @brief implementation of the inject_auditor_triggers function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INJECT_AUDITOR_TRIGGERS_H
+#define PG_INJECT_AUDITOR_TRIGGERS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to inject auditor triggers into the
+ * database, triggering the real-time auditor upon
+ * relevant INSERTs.
+ *
+ * @param cls closure
+ * @return #GNUNET_OK on success,
+ * #GNUNET_SYSERR on DB errors
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_inject_auditor_triggers (void *cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_aml_decision.c b/src/exchangedb/pg_insert_aml_decision.c
new file mode 100644
index 000000000..39419be59
--- /dev/null
+++ b/src/exchangedb/pg_insert_aml_decision.c
@@ -0,0 +1,97 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_aml_decision.c
+ * @brief Implementation of the insert_aml_decision function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_aml_decision.h"
+#include "pg_helper.h"
+#include <gnunet/gnunet_pq_lib.h>
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_aml_decision (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_Amount *new_threshold,
+ enum TALER_AmlDecisionState new_status,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const char *justification,
+ const json_t *kyc_requirements,
+ uint64_t requirements_row,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const struct TALER_AmlOfficerSignatureP *decider_sig,
+ bool *invalid_officer,
+ struct GNUNET_TIME_Timestamp *last_date)
+{
+ struct PostgresClosure *pg = cls;
+ uint32_t ns = (uint32_t) new_status;
+ struct TALER_KycCompletedEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+ .h_payto = *h_payto
+ };
+ char *notify_s = GNUNET_PQ_get_event_notify_channel (&rep.header);
+ char *kyc_s = (NULL != kyc_requirements)
+ ? json_dumps (kyc_requirements, JSON_COMPACT)
+ : NULL;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ TALER_PQ_query_param_amount (pg->conn,
+ new_threshold),
+ GNUNET_PQ_query_param_uint32 (&ns),
+ GNUNET_PQ_query_param_timestamp (&decision_time),
+ GNUNET_PQ_query_param_string (justification),
+ GNUNET_PQ_query_param_auto_from_type (decider_pub),
+ GNUNET_PQ_query_param_auto_from_type (decider_sig),
+ GNUNET_PQ_query_param_string (notify_s),
+ NULL != kyc_requirements
+ ? GNUNET_PQ_query_param_string (kyc_s)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_uint64 (&requirements_row),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("out_invalid_officer",
+ invalid_officer),
+ GNUNET_PQ_result_spec_timestamp ("out_last_date",
+ last_date),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "do_insert_aml_decision",
+ "SELECT"
+ " out_invalid_officer"
+ ",out_last_date"
+ " FROM exchange_do_insert_aml_decision"
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "do_insert_aml_decision",
+ params,
+ rs);
+ GNUNET_free (notify_s);
+ GNUNET_PQ_event_do_poll (pg->conn);
+ if (NULL != kyc_s)
+ free (kyc_s);
+ return qs;
+}
diff --git a/src/exchangedb/pg_insert_aml_decision.h b/src/exchangedb/pg_insert_aml_decision.h
new file mode 100644
index 000000000..073a2f50a
--- /dev/null
+++ b/src/exchangedb/pg_insert_aml_decision.h
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_aml_decision.h
+ * @brief implementation of the insert_aml_decision function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_AML_DECISION_H
+#define PG_INSERT_AML_DECISION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert an AML decision. Inserts into AML history and insert or updates AML
+ * status.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param new_threshold new monthly threshold that would trigger an AML check
+ * @param new_status AML decision status
+ * @param decision_time when was the decision made
+ * @param justification human-readable text justifying the decision
+ * @param kyc_requirements JSON array with KYC requirements
+ * @param requirements_row row in the KYC table for this process, 0 for none
+ * @param decider_pub public key of the staff member
+ * @param decider_sig signature of the staff member
+ * @param[out] invalid_officer set to TRUE if @a decider_pub is not allowed to make decisions right now
+ * @param[out] last_date set to the previous decision time;
+ * the INSERT is not performed if @a last_date is not before @a decision_time
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_aml_decision (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_Amount *new_threshold,
+ enum TALER_AmlDecisionState new_status,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const char *justification,
+ const json_t *kyc_requirements,
+ uint64_t requirements_row,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const struct TALER_AmlOfficerSignatureP *decider_sig,
+ bool *invalid_officer,
+ struct GNUNET_TIME_Timestamp *last_date);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_aml_officer.c b/src/exchangedb/pg_insert_aml_officer.c
new file mode 100644
index 000000000..c1f635a64
--- /dev/null
+++ b/src/exchangedb/pg_insert_aml_officer.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_aml_officer.c
+ * @brief Implementation of the insert_aml_officer function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_aml_officer.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_aml_officer (
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *decider_name,
+ bool is_active,
+ bool read_only,
+ struct GNUNET_TIME_Timestamp last_change,
+ struct GNUNET_TIME_Timestamp *previous_change)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (decider_pub),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_string (decider_name),
+ GNUNET_PQ_query_param_bool (is_active),
+ GNUNET_PQ_query_param_bool (read_only),
+ GNUNET_PQ_query_param_timestamp (&last_change),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("out_last_change",
+ previous_change),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "do_insert_aml_staff",
+ "SELECT"
+ " out_last_change"
+ " FROM exchange_do_insert_aml_officer"
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "do_insert_aml_staff",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_insert_aml_officer.h b/src/exchangedb/pg_insert_aml_officer.h
new file mode 100644
index 000000000..3c6f5d82e
--- /dev/null
+++ b/src/exchangedb/pg_insert_aml_officer.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_aml_officer.h
+ * @brief implementation of the insert_aml_officer function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_AML_OFFICER_H
+#define PG_INSERT_AML_OFFICER_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert AML staff record. If the time given in
+ * @a last_change is before the previous change in the
+ * database, only @e previous_change is returned and
+ * no actual change is committed to the database.
+ *
+ * @param cls closure
+ * @param decider_pub public key of the staff member
+ * @param master_sig offline signature affirming the AML officer
+ * @param decider_name full name of the staff member
+ * @param is_active true to enable, false to set as inactive
+ * @param read_only true to set read-only access
+ * @param last_change when was the change made effective
+ * @param[out] previous_change set to the time of the previous change
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_aml_officer (
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *decider_name,
+ bool is_active,
+ bool read_only,
+ struct GNUNET_TIME_Timestamp last_change,
+ struct GNUNET_TIME_Timestamp *previous_change);
+
+#endif
diff --git a/src/exchangedb/pg_insert_auditor.c b/src/exchangedb/pg_insert_auditor.c
new file mode 100644
index 000000000..2f1de7ba7
--- /dev/null
+++ b/src/exchangedb/pg_insert_auditor.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_auditor.c
+ * @brief Implementation of the insert_auditor function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_auditor.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_auditor (void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp start_date)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (auditor_pub),
+ GNUNET_PQ_query_param_string (auditor_name),
+ GNUNET_PQ_query_param_string (auditor_url),
+ GNUNET_PQ_query_param_timestamp (&start_date),
+ GNUNET_PQ_query_param_end
+ };
+
+ /* used in #postgres_insert_auditor() */
+ PREPARE (pg,
+ "insert_auditor",
+ "INSERT INTO auditors "
+ "(auditor_pub"
+ ",auditor_name"
+ ",auditor_url"
+ ",is_active"
+ ",last_change"
+ ") VALUES "
+ "($1, $2, $3, true, $4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_auditor",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_auditor.h b/src/exchangedb/pg_insert_auditor.h
new file mode 100644
index 000000000..8de388f23
--- /dev/null
+++ b/src/exchangedb/pg_insert_auditor.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_auditor.h
+ * @brief implementation of the insert_auditor function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_AUDITOR_H
+#define PG_INSERT_AUDITOR_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Insert information about an auditor that will audit this exchange.
+ *
+ * @param cls closure
+ * @param auditor_pub key of the auditor
+ * @param auditor_url base URL of the auditor's REST service
+ * @param auditor_name name of the auditor (for humans)
+ * @param start_date date when the auditor was added by the offline system
+ * (only to be used for replay detection)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_auditor (void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp start_date);
+#endif
diff --git a/src/exchangedb/pg_insert_auditor_denom_sig.c b/src/exchangedb/pg_insert_auditor_denom_sig.c
new file mode 100644
index 000000000..3643a87f2
--- /dev/null
+++ b/src/exchangedb/pg_insert_auditor_denom_sig.c
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_auditor_denom_sig.c
+ * @brief Implementation of the insert_auditor_denom_sig function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_auditor_denom_sig.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_auditor_denom_sig (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (auditor_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
+ GNUNET_PQ_query_param_auto_from_type (auditor_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_auditor_denom_sig",
+ "WITH ax AS"
+ " (SELECT auditor_uuid"
+ " FROM auditors"
+ " WHERE auditor_pub=$1)"
+ "INSERT INTO auditor_denom_sigs "
+ "(auditor_uuid"
+ ",denominations_serial"
+ ",auditor_sig"
+ ") SELECT ax.auditor_uuid, denominations_serial, $3 "
+ " FROM denominations"
+ " CROSS JOIN ax"
+ " WHERE denom_pub_hash=$2;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_auditor_denom_sig",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_auditor_denom_sig.h b/src/exchangedb/pg_insert_auditor_denom_sig.h
new file mode 100644
index 000000000..baa67cfe8
--- /dev/null
+++ b/src/exchangedb/pg_insert_auditor_denom_sig.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_auditor_denom_sig.h
+ * @brief implementation of the insert_auditor_denom_sig function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_AUDITOR_DENOM_SIG_H
+#define PG_INSERT_AUDITOR_DENOM_SIG_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Insert information about an auditor auditing a denomination key.
+ *
+ * @param cls closure
+ * @param h_denom_pub the audited denomination
+ * @param auditor_pub the auditor's key
+ * @param auditor_sig signature affirming the auditor's audit activity
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_auditor_denom_sig (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig);
+#endif
diff --git a/src/exchangedb/pg_insert_close_request.c b/src/exchangedb/pg_insert_close_request.c
new file mode 100644
index 000000000..b4bc5f4a7
--- /dev/null
+++ b/src/exchangedb/pg_insert_close_request.c
@@ -0,0 +1,68 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_insert_close_request.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_close_request.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_close_request (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *payto_uri,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_Amount *balance,
+ const struct TALER_Amount *closing_fee)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_timestamp (&request_timestamp),
+ GNUNET_PQ_query_param_auto_from_type (reserve_sig),
+ TALER_PQ_query_param_amount (pg->conn,
+ balance),
+ TALER_PQ_query_param_amount (pg->conn,
+ closing_fee),
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_account_close",
+ "INSERT INTO close_requests"
+ "(reserve_pub"
+ ",close_timestamp"
+ ",reserve_sig"
+ ",close"
+ ",close_fee"
+ ",payto_uri"
+ ")"
+ "VALUES "
+ "($1, $2, $3, $4, $5, $6)"
+ " ON CONFLICT DO NOTHING;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_account_close",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_close_request.h b/src/exchangedb/pg_insert_close_request.h
new file mode 100644
index 000000000..c014a10b9
--- /dev/null
+++ b/src/exchangedb/pg_insert_close_request.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_insert_close_request.h
+ * @brief implementation of the insert_close_request function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_CLOSE_REQUEST_H
+#define PG_INSERT_CLOSE_REQUEST_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to initiate closure of an account.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param reserve_pub public key of the account to close
+ * @param payto_uri where to wire the funds
+ * @param reserve_sig signature affiming that the account is to be closed
+ * @param request_timestamp time of the close request (client-side?)
+ * @param balance final balance in the reserve
+ * @param closing_fee closing fee to charge
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_close_request (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const char *payto_uri,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_Amount *balance,
+ const struct TALER_Amount *closing_fee);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_contract.c b/src/exchangedb/pg_insert_contract.c
new file mode 100644
index 000000000..0274f8d93
--- /dev/null
+++ b/src/exchangedb/pg_insert_contract.c
@@ -0,0 +1,93 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_contract.c
+ * @brief Implementation of the insert_contract function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_contract.h"
+#include "pg_select_contract_by_purse.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_contract (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_EncryptedContract *econtract,
+ bool *in_conflict)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (&econtract->contract_pub),
+ GNUNET_PQ_query_param_fixed_size (econtract->econtract,
+ econtract->econtract_size),
+ GNUNET_PQ_query_param_auto_from_type (&econtract->econtract_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ *in_conflict = false;
+ /* Used in #postgres_insert_contract() */
+ PREPARE (pg,
+ "insert_contract",
+ "INSERT INTO contracts"
+ " (purse_pub"
+ " ,pub_ckey"
+ " ,e_contract"
+ " ,contract_sig"
+ " ,purse_expiration"
+ " ) SELECT "
+ " $1, $2, $3, $4, purse_expiration"
+ " FROM purse_requests"
+ " WHERE purse_pub=$1"
+ " ON CONFLICT DO NOTHING;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_contract",
+ params);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+ return qs;
+ {
+ struct TALER_EncryptedContract econtract2;
+
+ qs = TEH_PG_select_contract_by_purse (pg,
+ purse_pub,
+ &econtract2);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (0 == GNUNET_memcmp (&econtract->contract_pub,
+ &econtract2.contract_pub)) &&
+ (econtract2.econtract_size ==
+ econtract->econtract_size) &&
+ (0 == memcmp (econtract2.econtract,
+ econtract->econtract,
+ econtract->econtract_size)) )
+ {
+ GNUNET_free (econtract2.econtract);
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ GNUNET_free (econtract2.econtract);
+ *in_conflict = true;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+}
diff --git a/src/exchangedb/pg_insert_contract.h b/src/exchangedb/pg_insert_contract.h
new file mode 100644
index 000000000..e2e6b5ee9
--- /dev/null
+++ b/src/exchangedb/pg_insert_contract.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_contract.h
+ * @brief implementation of the insert_contract function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_CONTRACT_H
+#define PG_INSERT_CONTRACT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to persist an encrypted contract associated with a reserve.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub the purse the contract is associated with (must exist)
+ * @param econtract the encrypted contract
+ * @param[out] in_conflict set to true if @a econtract
+ * conflicts with an existing contract;
+ * in this case, the return value will be
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT despite the failure
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_contract (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_EncryptedContract *econtract,
+ bool *in_conflict);
+
+#endif
diff --git a/src/exchangedb/pg_insert_denomination_info.c b/src/exchangedb/pg_insert_denomination_info.c
new file mode 100644
index 000000000..878bc5d81
--- /dev/null
+++ b/src/exchangedb/pg_insert_denomination_info.c
@@ -0,0 +1,101 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_denomination_info.c
+ * @brief Implementation of the insert_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_denomination_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_denomination_info (
+ void *cls,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue)
+{
+ struct PostgresClosure *pg = cls;
+ struct TALER_DenominationHashP denom_hash;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&issue->denom_hash),
+ TALER_PQ_query_param_denom_pub (denom_pub),
+ GNUNET_PQ_query_param_auto_from_type (&issue->signature),
+ GNUNET_PQ_query_param_timestamp (&issue->start),
+ GNUNET_PQ_query_param_timestamp (&issue->expire_withdraw),
+ GNUNET_PQ_query_param_timestamp (&issue->expire_deposit),
+ GNUNET_PQ_query_param_timestamp (&issue->expire_legal),
+ TALER_PQ_query_param_amount (pg->conn,
+ &issue->value),
+ TALER_PQ_query_param_amount (pg->conn,
+ &issue->fees.withdraw),
+ TALER_PQ_query_param_amount (pg->conn,
+ &issue->fees.deposit),
+ TALER_PQ_query_param_amount (pg->conn,
+ &issue->fees.refresh),
+ TALER_PQ_query_param_amount (pg->conn,
+ &issue->fees.refund),
+ GNUNET_PQ_query_param_uint32 (&denom_pub->age_mask.bits),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_assert (denom_pub->age_mask.bits ==
+ issue->age_mask.bits);
+ TALER_denom_pub_hash (denom_pub,
+ &denom_hash);
+ GNUNET_assert (0 ==
+ GNUNET_memcmp (&denom_hash,
+ &issue->denom_hash));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ issue->start.abs_time));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ issue->expire_withdraw.abs_time));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ issue->expire_deposit.abs_time));
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ issue->expire_legal.abs_time));
+ /* check fees match denomination currency */
+ GNUNET_assert (GNUNET_YES ==
+ TALER_denom_fee_check_currency (
+ issue->value.currency,
+ &issue->fees));
+ PREPARE (pg,
+ "denomination_insert",
+ "INSERT INTO denominations "
+ "(denom_pub_hash"
+ ",denom_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin" /* value of this denom */
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",age_mask"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+ " $11, $12, $13);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "denomination_insert",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_denomination_info.h b/src/exchangedb/pg_insert_denomination_info.h
new file mode 100644
index 000000000..663f45bd4
--- /dev/null
+++ b/src/exchangedb/pg_insert_denomination_info.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_denomination_info.h
+ * @brief implementation of the insert_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_DENOMINATION_INFO_H
+#define PG_INSERT_DENOMINATION_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Insert a denomination key's public information into the database for
+ * reference by auditors and other consistency checks.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param denom_pub the public key used for signing coins of this denomination
+ * @param issue issuing information with value, fees and other info about the coin
+ * @return status of the query
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_denomination_info (
+ void *cls,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyInformation *issue);
+
+#endif
diff --git a/src/exchangedb/pg_insert_denomination_revocation.c b/src/exchangedb/pg_insert_denomination_revocation.c
new file mode 100644
index 000000000..49445f262
--- /dev/null
+++ b/src/exchangedb/pg_insert_denomination_revocation.c
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_denomination_revocation.c
+ * @brief Implementation of the insert_denomination_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_denomination_revocation.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_denomination_revocation (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ /* Used in #postgres_insert_denomination_revocation() */
+ PREPARE (pg,
+ "denomination_revocation_insert",
+ "INSERT INTO denomination_revocations "
+ "(denominations_serial"
+ ",master_sig"
+ ") SELECT denominations_serial,$2"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "denomination_revocation_insert",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_denomination_revocation.h b/src/exchangedb/pg_insert_denomination_revocation.h
new file mode 100644
index 000000000..e3da87666
--- /dev/null
+++ b/src/exchangedb/pg_insert_denomination_revocation.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_denomination_revocation.h
+ * @brief implementation of the insert_denomination_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_DENOMINATION_REVOCATION_H
+#define PG_INSERT_DENOMINATION_REVOCATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Store information that a denomination key was revoked
+ * in the database.
+ *
+ * @param cls closure
+ * @param denom_pub_hash hash of the revoked denomination key
+ * @param master_sig signature affirming the revocation
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_denomination_revocation (
+ void *cls,
+ const struct TALER_DenominationHashP *denom_pub_hash,
+ const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_insert_drain_profit.c b/src/exchangedb/pg_insert_drain_profit.c
new file mode 100644
index 000000000..a0de02e9b
--- /dev/null
+++ b/src/exchangedb/pg_insert_drain_profit.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_drain_profit.c
+ * @brief Implementation of the insert_drain_profit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_drain_profit.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_drain_profit (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const char *account_section,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_Amount *amount,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_string (account_section),
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_timestamp (&request_timestamp),
+ TALER_PQ_query_param_amount (pg->conn,
+ amount),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "drain_profit_insert",
+ "INSERT INTO profit_drains "
+ "(wtid"
+ ",account_section"
+ ",payto_uri"
+ ",trigger_date"
+ ",amount"
+ ",master_sig"
+ ") VALUES ($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "drain_profit_insert",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_drain_profit.h b/src/exchangedb/pg_insert_drain_profit.h
new file mode 100644
index 000000000..90183d850
--- /dev/null
+++ b/src/exchangedb/pg_insert_drain_profit.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_drain_profit.h
+ * @brief implementation of the insert_drain_profit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_DRAIN_PROFIT_H
+#define PG_INSERT_DRAIN_PROFIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to persist a request to drain profits.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param wtid wire transfer ID to use
+ * @param account_section account to drain
+ * @param payto_uri account to wire funds to
+ * @param request_timestamp when was the request made
+ * @param amount amount to wire
+ * @param master_sig signature affirming the operation
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_drain_profit (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const char *account_section,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp request_timestamp,
+ const struct TALER_Amount *amount,
+ const struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_insert_global_fee.c b/src/exchangedb/pg_insert_global_fee.c
new file mode 100644
index 000000000..e78cd0b83
--- /dev/null
+++ b/src/exchangedb/pg_insert_global_fee.c
@@ -0,0 +1,137 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_global_fee.c
+ * @brief Implementation of the insert_global_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_global_fee.h"
+#include "pg_helper.h"
+#include "pg_get_global_fee.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_global_fee (void *cls,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&start_date),
+ GNUNET_PQ_query_param_timestamp (&end_date),
+ TALER_PQ_query_param_amount (pg->conn,
+ &fees->history),
+ TALER_PQ_query_param_amount (pg->conn,
+ &fees->account),
+ TALER_PQ_query_param_amount (pg->conn,
+ &fees->purse),
+ GNUNET_PQ_query_param_relative_time (&purse_timeout),
+ GNUNET_PQ_query_param_relative_time (&history_expiration),
+ GNUNET_PQ_query_param_uint32 (&purse_account_limit),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct TALER_GlobalFeeSet wx;
+ struct TALER_MasterSignatureP sig;
+ struct GNUNET_TIME_Timestamp sd;
+ struct GNUNET_TIME_Timestamp ed;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Relative pt;
+ struct GNUNET_TIME_Relative he;
+ uint32_t pal;
+
+ qs = TEH_PG_get_global_fee (pg,
+ start_date,
+ &sd,
+ &ed,
+ &wx,
+ &pt,
+ &he,
+ &pal,
+ &sig);
+ if (qs < 0)
+ return qs;
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (0 != GNUNET_memcmp (&sig,
+ master_sig))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (0 !=
+ TALER_global_fee_set_cmp (fees,
+ &wx))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (GNUNET_TIME_timestamp_cmp (sd,
+ !=,
+ start_date)) ||
+ (GNUNET_TIME_timestamp_cmp (ed,
+ !=,
+ end_date)) )
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (GNUNET_TIME_relative_cmp (purse_timeout,
+ !=,
+ pt)) ||
+ (GNUNET_TIME_relative_cmp (history_expiration,
+ !=,
+ he)) )
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (purse_account_limit != pal)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* equal record already exists */
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+
+ /* Used in #postgres_insert_global_fee */
+ PREPARE (pg,
+ "insert_global_fee",
+ "INSERT INTO global_fee "
+ "(start_date"
+ ",end_date"
+ ",history_fee"
+ ",account_fee"
+ ",purse_fee"
+ ",purse_timeout"
+ ",history_expiration"
+ ",purse_account_limit"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_global_fee",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_global_fee.h b/src/exchangedb/pg_insert_global_fee.h
new file mode 100644
index 000000000..411345dc4
--- /dev/null
+++ b/src/exchangedb/pg_insert_global_fee.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_global_fee.h
+ * @brief implementation of the insert_global_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_GLOBAL_FEE_H
+#define PG_INSERT_GLOBAL_FEE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Insert global fee data into database.
+ *
+ * @param cls closure
+ * @param start_date when does the fees go into effect
+ * @param end_date when does the fees end being valid
+ * @param fees how high is are the global fees
+ * @param purse_timeout when do purses time out
+ * @param history_expiration how long are account histories preserved
+ * @param purse_account_limit how many purses are free per account
+ * @param master_sig signature over the above by the exchange master key
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_global_fee (void *cls,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_insert_kyc_attributes.c b/src/exchangedb/pg_insert_kyc_attributes.c
new file mode 100644
index 000000000..3c94abb85
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_attributes.c
@@ -0,0 +1,110 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_kyc_attributes.c
+ * @brief Implementation of the insert_kyc_attributes function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_kyc_attributes.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_attributes (
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct GNUNET_ShortHashCode *kyc_prox,
+ const char *provider_section,
+ unsigned int num_checks,
+ const char *satisfied_checks[static num_checks],
+ uint32_t birthday,
+ struct GNUNET_TIME_Timestamp collection_time,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ struct GNUNET_TIME_Absolute expiration_time,
+ size_t enc_attributes_size,
+ const void *enc_attributes,
+ bool require_aml)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Timestamp expiration
+ = GNUNET_TIME_absolute_to_timestamp (expiration_time);
+ struct TALER_KycCompletedEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+ .h_payto = *h_payto
+ };
+ char *kyc_completed_notify_s
+ = GNUNET_PQ_get_event_notify_channel (&rep.header);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&process_row),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_auto_from_type (kyc_prox),
+ GNUNET_PQ_query_param_string (provider_section),
+ GNUNET_PQ_query_param_array_ptrs_string (num_checks,
+ satisfied_checks,
+ pg->conn),
+ GNUNET_PQ_query_param_uint32 (&birthday),
+ (NULL == provider_account_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (provider_account_id),
+ (NULL == provider_legitimization_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (provider_legitimization_id),
+ GNUNET_PQ_query_param_timestamp (&collection_time),
+ GNUNET_PQ_query_param_absolute_time (&expiration_time),
+ GNUNET_PQ_query_param_timestamp (&expiration),
+ GNUNET_PQ_query_param_fixed_size (enc_attributes,
+ enc_attributes_size),
+ GNUNET_PQ_query_param_bool (require_aml),
+ GNUNET_PQ_query_param_string (kyc_completed_notify_s),
+ GNUNET_PQ_query_param_end
+ };
+ bool ok;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("out_ok",
+ &ok),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Inserting KYC attributes, wake up on %s\n",
+ kyc_completed_notify_s);
+ PREPARE (pg,
+ "insert_kyc_attributes",
+ "SELECT "
+ " out_ok"
+ " FROM exchange_do_insert_kyc_attributes "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_kyc_attributes",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ GNUNET_free (kyc_completed_notify_s);
+ GNUNET_PQ_event_do_poll (pg->conn);
+ if (qs < 0)
+ return qs;
+ if (! ok)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ return qs;
+}
diff --git a/src/exchangedb/pg_insert_kyc_attributes.h b/src/exchangedb/pg_insert_kyc_attributes.h
new file mode 100644
index 000000000..35b25bdc8
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_attributes.h
@@ -0,0 +1,69 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_kyc_attributes.h
+ * @brief implementation of the insert_kyc_attributes function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_KYC_ATTRIBUTES_H
+#define PG_INSERT_KYC_ATTRIBUTES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Store KYC attribute data, update KYC process status and
+ * AML status for the given account.
+ *
+ * @param cls closure
+ * @param process_row KYC process row to update
+ * @param h_payto account for which the attribute data is stored
+ * @param kyc_prox key for similarity search
+ * @param provider_section provider that must be checked
+ * @param num_checks how many checks do these attributes satisfy
+ * @param satisfied_checks array of checks satisfied by these attributes
+ * @param provider_account_id provider account ID
+ * @param provider_legitimization_id provider legitimization ID
+ * @param birthday birthdate of user, in days after 1990, or 0 if unknown or definitively adult
+ * @param collection_time when was the data collected
+ * @param expiration_time when does the data expire
+ * @param enc_attributes_size number of bytes in @a enc_attributes
+ * @param enc_attributes encrypted attribute data
+ * @param require_aml true to trigger AML
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_attributes (
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct GNUNET_ShortHashCode *kyc_prox,
+ const char *provider_section,
+ unsigned int num_checks,
+ const char *satisfied_checks[static num_checks],
+ uint32_t birthday,
+ struct GNUNET_TIME_Timestamp collection_time,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ struct GNUNET_TIME_Absolute expiration_time,
+ size_t enc_attributes_size,
+ const void *enc_attributes,
+ bool require_aml);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_kyc_failure.c b/src/exchangedb/pg_insert_kyc_failure.c
new file mode 100644
index 000000000..568c39ca8
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_failure.c
@@ -0,0 +1,82 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_kyc_failure.c
+ * @brief Implementation of the insert_kyc_failure function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_kyc_failure.h"
+#include "pg_helper.h"
+#include "pg_event_notify.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_failure (
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ const char *provider_account_id,
+ const char *provider_legitimization_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&process_row),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_string (provider_section),
+ NULL != provider_account_id
+ ? GNUNET_PQ_query_param_string (provider_account_id)
+ : GNUNET_PQ_query_param_null (),
+ NULL != provider_legitimization_id
+ ? GNUNET_PQ_query_param_string (provider_legitimization_id)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "insert_kyc_failure",
+ "UPDATE legitimization_processes"
+ " SET"
+ " finished=TRUE"
+ " ,provider_user_id=$4"
+ " ,provider_legitimization_id=$5"
+ " WHERE h_payto=$2"
+ " AND legitimization_process_serial_id=$1"
+ " AND provider_section=$3;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_kyc_failure",
+ params);
+ if (qs > 0)
+ {
+ /* FIXME: might want to do this eventually in the same transaction... */
+ struct TALER_KycCompletedEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+ .h_payto = *h_payto
+ };
+
+ TEH_PG_event_notify (pg,
+ &rep.header,
+ NULL,
+ 0);
+ }
+ return qs;
+}
diff --git a/src/exchangedb/pg_insert_kyc_failure.h b/src/exchangedb/pg_insert_kyc_failure.h
new file mode 100644
index 000000000..46d08df9c
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_failure.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_kyc_failure.h
+ * @brief implementation of the insert_kyc_failure function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_KYC_FAILURE_H
+#define PG_INSERT_KYC_FAILURE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Update KYC process status to finished (and failed).
+ *
+ * @param cls closure
+ * @param process_row KYC process row to update
+ * @param h_payto account for which the attribute data is stored
+ * @param provider_section provider that must be checked
+ * @param provider_account_id provider account ID
+ * @param provider_legitimization_id provider legitimization ID
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_failure (
+ void *cls,
+ uint64_t process_row,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ const char *provider_account_id,
+ const char *provider_legitimization_id);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_kyc_requirement_for_account.c b/src/exchangedb/pg_insert_kyc_requirement_for_account.c
new file mode 100644
index 000000000..95f695297
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_requirement_for_account.c
@@ -0,0 +1,67 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_kyc_requirement_for_account.c
+ * @brief Implementation of the insert_kyc_requirement_for_account function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_kyc_requirement_for_account.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_requirement_for_account (
+ void *cls,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *requirement_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ (NULL == reserve_pub)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_string (provider_section),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("legitimization_requirement_serial_id",
+ requirement_row),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_insert_kyc_requirement_for_account() */
+ PREPARE (pg,
+ "insert_legitimization_requirement",
+ "INSERT INTO legitimization_requirements"
+ " (h_payto"
+ " ,reserve_pub"
+ " ,required_checks"
+ " ) VALUES "
+ " ($1, $2, $3)"
+ " ON CONFLICT (h_payto,required_checks) "
+ " DO UPDATE SET h_payto=$1" /* syntax requirement: dummy op */
+ " RETURNING legitimization_requirement_serial_id");
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "insert_legitimization_requirement",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_insert_kyc_requirement_for_account.h b/src/exchangedb/pg_insert_kyc_requirement_for_account.h
new file mode 100644
index 000000000..331c8ba0c
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_requirement_for_account.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_kyc_requirement_for_account.h
+ * @brief implementation of the insert_kyc_requirement_for_account function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_KYC_REQUIREMENT_FOR_ACCOUNT_H
+#define PG_INSERT_KYC_REQUIREMENT_FOR_ACCOUNT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert KYC requirement for @a h_payto account into table.
+ *
+ * @param cls closure
+ * @param provider_section provider that must be checked
+ * @param h_payto account that must be KYC'ed
+ * @param reserve_pub if the account is a reserve, its public key. Maybe NULL
+ * @param[out] requirement_row set to legitimization requirement row for this check
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_requirement_for_account (
+ void *cls,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ uint64_t *requirement_row);
+
+#endif
diff --git a/src/exchangedb/pg_insert_kyc_requirement_process.c b/src/exchangedb/pg_insert_kyc_requirement_process.c
new file mode 100644
index 000000000..a20db3388
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_requirement_process.c
@@ -0,0 +1,75 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_kyc_requirement_process.c
+ * @brief Implementation of the insert_kyc_requirement_process function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_kyc_requirement_process.h"
+#include "pg_helper.h"
+#include <gnunet/gnunet_pq_lib.h>
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_requirement_process (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ uint64_t *process_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now
+ = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_string (provider_section),
+ (NULL != provider_account_id)
+ ? GNUNET_PQ_query_param_string (provider_account_id)
+ : GNUNET_PQ_query_param_null (),
+ (NULL != provider_legitimization_id)
+ ? GNUNET_PQ_query_param_string (provider_legitimization_id)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("legitimization_process_serial_id",
+ process_row),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "insert_legitimization_process",
+ "INSERT INTO legitimization_processes"
+ " (h_payto"
+ " ,start_time"
+ " ,provider_section"
+ " ,provider_user_id"
+ " ,provider_legitimization_id"
+ " ) VALUES "
+ " ($1, $2, $3, $4, $5)"
+ " RETURNING legitimization_process_serial_id");
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "insert_legitimization_process",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_insert_kyc_requirement_process.h b/src/exchangedb/pg_insert_kyc_requirement_process.h
new file mode 100644
index 000000000..df21db8cd
--- /dev/null
+++ b/src/exchangedb/pg_insert_kyc_requirement_process.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_kyc_requirement_process.h
+ * @brief implementation of the insert_kyc_requirement_process function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_KYC_REQUIREMENT_PROCESS_H
+#define PG_INSERT_KYC_REQUIREMENT_PROCESS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Begin KYC requirement process.
+ *
+ * @param cls closure
+ * @param h_payto account that must be KYC'ed
+ * @param provider_section provider that must be checked
+ * @param provider_account_id provider account ID
+ * @param provider_legitimization_id provider legitimization ID
+ * @param[out] process_row row the process is stored under
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_kyc_requirement_process (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_section,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ uint64_t *process_row);
+
+#endif
diff --git a/src/exchangedb/pg_insert_partner.c b/src/exchangedb/pg_insert_partner.c
new file mode 100644
index 000000000..d1d6069de
--- /dev/null
+++ b/src/exchangedb/pg_insert_partner.c
@@ -0,0 +1,69 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_partner.c
+ * @brief Implementation of the insert_partner function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_partner.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_partner (void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (master_pub),
+ GNUNET_PQ_query_param_timestamp (&start_date),
+ GNUNET_PQ_query_param_timestamp (&end_date),
+ GNUNET_PQ_query_param_relative_time (&wad_frequency),
+ TALER_PQ_query_param_amount (pg->conn,
+ wad_fee),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_string (partner_base_url),
+ GNUNET_PQ_query_param_end
+ };
+
+
+ PREPARE (pg,
+ "insert_partner",
+ "INSERT INTO partners"
+ " (partner_master_pub"
+ " ,start_date"
+ " ,end_date"
+ " ,wad_frequency"
+ " ,wad_fee"
+ " ,master_sig"
+ " ,partner_base_url"
+ " ) VALUES "
+ " ($1, $2, $3, $4, $5, $6, $7)"
+ " ON CONFLICT DO NOTHING;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_partner",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_partner.h b/src/exchangedb/pg_insert_partner.h
new file mode 100644
index 000000000..3ebae786c
--- /dev/null
+++ b/src/exchangedb/pg_insert_partner.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_partner.h
+ * @brief implementation of the insert_partner function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_PARTNER_H
+#define PG_INSERT_PARTNER_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called to store configuration data about a partner
+ * exchange that we are federated with.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param master_pub public offline signing key of the partner exchange
+ * @param start_date when does the following data start to be valid
+ * @param end_date when does the validity end (exclusive)
+ * @param wad_frequency how often do we do exchange-to-exchange settlements?
+ * @param wad_fee how much do we charge for transfers to the partner
+ * @param partner_base_url base URL of the partner exchange
+ * @param master_sig signature with our offline signing key affirming the above
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_partner (void *cls,
+ const struct TALER_MasterPublicKeyP *master_pub,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ struct GNUNET_TIME_Relative wad_frequency,
+ const struct TALER_Amount *wad_fee,
+ const char *partner_base_url,
+ const struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_insert_purse_request.c b/src/exchangedb/pg_insert_purse_request.c
new file mode 100644
index 000000000..d8d68abe8
--- /dev/null
+++ b/src/exchangedb/pg_insert_purse_request.c
@@ -0,0 +1,126 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_purse_request.c
+ * @brief Implementation of the insert_purse_request function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_purse_request.h"
+#include "pg_get_purse_request.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_purse_request (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t age_limit,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *purse_fee,
+ const struct TALER_Amount *amount,
+ const struct TALER_PurseContractSignatureP *purse_sig,
+ bool *in_conflict)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
+ uint32_t flags32 = (uint32_t) flags;
+ bool in_reserve_quota = (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
+ == (flags & TALER_WAMF_MERGE_MODE_MASK));
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (merge_pub),
+ GNUNET_PQ_query_param_timestamp (&now),
+ GNUNET_PQ_query_param_timestamp (&purse_expiration),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_uint32 (&age_limit),
+ GNUNET_PQ_query_param_uint32 (&flags32),
+ GNUNET_PQ_query_param_bool (in_reserve_quota),
+ TALER_PQ_query_param_amount (pg->conn,
+ amount),
+ TALER_PQ_query_param_amount (pg->conn,
+ purse_fee),
+ GNUNET_PQ_query_param_auto_from_type (purse_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ *in_conflict = false;
+ PREPARE (pg,
+ "insert_purse_request",
+ "INSERT INTO purse_requests"
+ " (purse_pub"
+ " ,merge_pub"
+ " ,purse_creation"
+ " ,purse_expiration"
+ " ,h_contract_terms"
+ " ,age_limit"
+ " ,flags"
+ " ,in_reserve_quota"
+ " ,amount_with_fee"
+ " ,purse_fee"
+ " ,purse_sig"
+ " ) VALUES "
+ " ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)"
+ " ON CONFLICT DO NOTHING;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_purse_request",
+ params);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+ return qs;
+ {
+ struct TALER_PurseMergePublicKeyP merge_pub2;
+ struct GNUNET_TIME_Timestamp purse_expiration2;
+ struct TALER_PrivateContractHashP h_contract_terms2;
+ uint32_t age_limit2;
+ struct TALER_Amount amount2;
+ struct TALER_Amount balance;
+ struct TALER_PurseContractSignatureP purse_sig2;
+
+ qs = TEH_PG_get_purse_request (pg,
+ purse_pub,
+ &merge_pub2,
+ &purse_expiration2,
+ &h_contract_terms2,
+ &age_limit2,
+ &amount2,
+ &balance,
+ &purse_sig2);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (age_limit2 == age_limit) &&
+ (0 == TALER_amount_cmp (amount,
+ &amount2)) &&
+ (0 == GNUNET_memcmp (&h_contract_terms2,
+ h_contract_terms)) &&
+ (0 == GNUNET_memcmp (&merge_pub2,
+ merge_pub)) )
+ {
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ *in_conflict = true;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+}
diff --git a/src/exchangedb/pg_insert_purse_request.h b/src/exchangedb/pg_insert_purse_request.h
new file mode 100644
index 000000000..37cce2a96
--- /dev/null
+++ b/src/exchangedb/pg_insert_purse_request.h
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_purse_request.h
+ * @brief implementation of the insert_purse_request function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_PURSE_REQUEST_H
+#define PG_INSERT_PURSE_REQUEST_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to create a new purse with certain meta data.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the new purse
+ * @param merge_pub public key providing the merge capability
+ * @param purse_expiration time when the purse will expire
+ * @param h_contract_terms hash of the contract for the purse
+ * @param age_limit age limit to enforce for payments into the purse
+ * @param flags flags for the operation
+ * @param purse_fee fee we are allowed to charge to the reserve (depending on @a flags)
+ * @param amount target amount (with fees) to be put into the purse
+ * @param purse_sig signature with @a purse_pub's private key affirming the above
+ * @param[out] in_conflict set to true if the meta data
+ * conflicts with an existing purse;
+ * in this case, the return value will be
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT despite the failure
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_purse_request (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct GNUNET_TIME_Timestamp purse_expiration,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t age_limit,
+ enum TALER_WalletAccountMergeFlags flags,
+ const struct TALER_Amount *purse_fee,
+ const struct TALER_Amount *amount,
+ const struct TALER_PurseContractSignatureP *purse_sig,
+ bool *in_conflict);
+
+#endif
diff --git a/src/exchangedb/pg_insert_records_by_table.c b/src/exchangedb/pg_insert_records_by_table.c
new file mode 100644
index 000000000..6ecec5bcf
--- /dev/null
+++ b/src/exchangedb/pg_insert_records_by_table.c
@@ -0,0 +1,2334 @@
+/*
+ This file is part of GNUnet
+ Copyright (C) 2020-2023 Taler Systems SA
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/**
+ * @file exchangedb/pg_insert_records_by_table.c
+ * @brief replicate_records_by_table implementation
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_records_by_table.h"
+#include "pg_helper.h"
+#include <gnunet/gnunet_pq_lib.h>
+
+
+/**
+ * Signature of helper functions of #TEH_PG_insert_records_by_table().
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ * @return transaction status code
+ */
+typedef enum GNUNET_DB_QueryStatus
+(*InsertRecordCallback)(struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td);
+
+
+/**
+ * Function called with denominations records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_denominations (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct TALER_DenominationHashP denom_hash;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&denom_hash),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.denominations.denom_type),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.denominations.age_mask),
+ TALER_PQ_query_param_denom_pub (
+ &td->details.denominations.denom_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.denominations.master_sig),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.denominations.valid_from),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.denominations.expire_withdraw),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.denominations.expire_deposit),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.denominations.expire_legal),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.denominations.coin),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.denominations.fees.withdraw),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.denominations.fees.deposit),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.denominations.fees.refresh),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.denominations.fees.refund),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_denominations",
+ "INSERT INTO denominations"
+ "(denominations_serial"
+ ",denom_pub_hash"
+ ",denom_type"
+ ",age_mask"
+ ",denom_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin"
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+ " $11, $12, $13, $14, $15);");
+
+ TALER_denom_pub_hash (
+ &td->details.denominations.denom_pub,
+ &denom_hash);
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_denominations",
+ params);
+}
+
+
+/**
+ * Function called with denomination_revocations records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_denomination_revocations (
+ struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.denomination_revocations.master_sig),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.denomination_revocations.denominations_serial),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_denomination_revocations",
+ "INSERT INTO denomination_revocations"
+ "(denom_revocations_serial_id"
+ ",master_sig"
+ ",denominations_serial"
+ ") VALUES "
+ "($1, $2, $3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_denomination_revocations",
+ params);
+}
+
+
+/**
+ * Function called with denominations records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wire_targets (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct TALER_PaytoHashP payto_hash;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&payto_hash),
+ GNUNET_PQ_query_param_string (
+ td->details.wire_targets.payto_uri),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wire_targets",
+ "INSERT INTO wire_targets"
+ "(wire_target_serial_id"
+ ",wire_target_h_payto"
+ ",payto_uri"
+ ") VALUES "
+ "($1, $2, $3);");
+ TALER_payto_hash (
+ td->details.wire_targets.payto_uri,
+ &payto_hash);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wire_targets",
+ params);
+}
+
+
+/**
+ * Function called with records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_legitimization_processes (struct PostgresClosure *pg,
+ const struct
+ TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.legitimization_processes.h_payto),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.legitimization_processes.expiration_time),
+ GNUNET_PQ_query_param_string (
+ td->details.legitimization_processes.provider_section),
+ GNUNET_PQ_query_param_string (
+ td->details.legitimization_processes.provider_user_id),
+ GNUNET_PQ_query_param_string (
+ td->details.legitimization_processes.provider_legitimization_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_legitimization_processes",
+ "INSERT INTO legitimization_processes"
+ "(legitimization_process_serial_id"
+ ",h_payto"
+ ",expiration_time"
+ ",provider_section"
+ ",provider_user_id"
+ ",provider_legitimization_id"
+ ") VALUES "
+ "($1, $3, $4, $5, $6, %7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_legitimization_processes",
+ params);
+}
+
+
+/**
+ * Function called with records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_legitimization_requirements (struct PostgresClosure *pg,
+ const struct
+ TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.legitimization_requirements.h_payto),
+ td->details.legitimization_requirements.no_reserve_pub
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (
+ &td->details.legitimization_requirements.reserve_pub),
+ GNUNET_PQ_query_param_string (
+ td->details.legitimization_requirements.required_checks),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_legitimization_requirements",
+ "INSERT INTO legitimization_requirements"
+ "(legitimization_requirement_serial_id"
+ ",h_payto"
+ ",reserve_pub"
+ ",required_checks"
+ ") VALUES "
+ "($1, $2, $3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_legitimization_requirements",
+ params);
+}
+
+
+/**
+ * Function called with reserves records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_reserves (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.reserves.reserve_pub),
+ GNUNET_PQ_query_param_timestamp (&td->details.reserves.expiration_date),
+ GNUNET_PQ_query_param_timestamp (&td->details.reserves.gc_date),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_reserves",
+ "INSERT INTO reserves"
+ "(reserve_uuid"
+ ",reserve_pub"
+ ",expiration_date"
+ ",gc_date"
+ ") VALUES "
+ "($1, $2, $3, $4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_reserves",
+ params);
+}
+
+
+/**
+ * Function called with reserves_in records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_reserves_in (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (&td->details.reserves_in.wire_reference),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.reserves_in.credit),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_in.sender_account_h_payto),
+ GNUNET_PQ_query_param_string (
+ td->details.reserves_in.exchange_account_section),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.reserves_in.execution_date),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.reserves_in.reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_reserves_in",
+ "INSERT INTO reserves_in"
+ "(reserve_in_serial_id"
+ ",wire_reference"
+ ",credit"
+ ",wire_source_h_payto"
+ ",exchange_account_section"
+ ",execution_date"
+ ",reserve_pub"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_reserves_in",
+ params);
+}
+
+
+/**
+ * Function called with reserves_open_requests records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_reserves_open_requests (struct PostgresClosure *pg,
+ const struct
+ TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.reserves_open_requests.expiration_date),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_open_requests.reserve_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.reserves_open_requests.reserve_payment),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.reserves_open_requests.requested_purse_limit),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_reserves_open_requests",
+ "INSERT INTO reserves_open_requests"
+ "(open_request_uuid"
+ ",reserve_pub"
+ ",request_timestamp"
+ ",expiration_date"
+ ",reserve_sig"
+ ",reserve_payment"
+ ",requested_purse_limit"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_reserves_open_requests",
+ params);
+}
+
+
+/**
+ * Function called with reserves_open_requests records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_reserves_open_deposits (
+ struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_open_deposits.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_open_deposits.coin_sig),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_open_deposits.reserve_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.reserves_open_deposits.contribution),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_reserves_open_deposits",
+ "INSERT INTO reserves_open_deposits"
+ "(reserve_open_deposit_uuid"
+ ",reserve_sig"
+ ",reserve_pub"
+ ",coin_pub"
+ ",coin_sig"
+ ",contribution"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_reserves_open_deposits",
+ params);
+}
+
+
+/**
+ * Function called with reserves_close records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_reserves_close (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.reserves_close.execution_date),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_close.wtid),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_close.sender_account_h_payto),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.reserves_close.amount),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.reserves_close.closing_fee),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_close.reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_reserves_close",
+ "INSERT INTO reserves_close"
+ "(close_uuid"
+ ",execution_date"
+ ",wtid"
+ ",wire_target_h_payto"
+ ",amount"
+ ",closing_fee"
+ ",reserve_pub"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_reserves_close",
+ params);
+}
+
+
+/**
+ * Function called with reserves_out records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_reserves_out (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_out.h_blind_ev),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.reserves_out.denominations_serial),
+ TALER_PQ_query_param_blinded_denom_sig (
+ &td->details.reserves_out.denom_sig),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.reserves_out.reserve_uuid),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.reserves_out.reserve_sig),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.reserves_out.execution_date),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.reserves_out.amount_with_fee),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_reserves_out",
+ "INSERT INTO reserves_out"
+ "(reserve_out_serial_id"
+ ",h_blind_ev"
+ ",denominations_serial"
+ ",denom_sig"
+ ",reserve_uuid"
+ ",reserve_sig"
+ ",execution_date"
+ ",amount_with_fee"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_reserves_out",
+ params);
+}
+
+
+/**
+ * Function called with auditors records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_auditors (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.auditors.auditor_pub),
+ GNUNET_PQ_query_param_string (td->details.auditors.auditor_name),
+ GNUNET_PQ_query_param_string (td->details.auditors.auditor_url),
+ GNUNET_PQ_query_param_bool (td->details.auditors.is_active),
+ GNUNET_PQ_query_param_timestamp (&td->details.auditors.last_change),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_auditors",
+ "INSERT INTO auditors"
+ "(auditor_uuid"
+ ",auditor_pub"
+ ",auditor_name"
+ ",auditor_url"
+ ",is_active"
+ ",last_change"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_auditors",
+ params);
+}
+
+
+/**
+ * Function called with auditor_denom_sigs records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_auditor_denom_sigs (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (&td->details.auditor_denom_sigs.auditor_uuid),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.auditor_denom_sigs.denominations_serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.auditor_denom_sigs.auditor_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_auditor_denom_sigs",
+ "INSERT INTO auditor_denom_sigs"
+ "(auditor_denom_serial"
+ ",auditor_uuid"
+ ",denominations_serial"
+ ",auditor_sig"
+ ") VALUES "
+ "($1, $2, $3, $4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_auditor_denom_sigs",
+ params);
+}
+
+
+/**
+ * Function called with exchange_sign_keys records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_exchange_sign_keys (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.exchange_sign_keys.exchange_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.exchange_sign_keys.master_sig),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.exchange_sign_keys.meta.start),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.exchange_sign_keys.meta.expire_sign),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.exchange_sign_keys.meta.expire_legal),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_exchange_sign_keys",
+ "INSERT INTO exchange_sign_keys"
+ "(esk_serial"
+ ",exchange_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_sign"
+ ",expire_legal"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_exchange_sign_keys",
+ params);
+}
+
+
+/**
+ * Function called with signkey_revocations records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_signkey_revocations (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (&td->details.signkey_revocations.esk_serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.signkey_revocations.master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_signkey_revocations",
+ "INSERT INTO signkey_revocations"
+ "(signkey_revocations_serial_id"
+ ",esk_serial"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_signkey_revocations",
+ params);
+}
+
+
+/**
+ * Function called with known_coins records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_known_coins (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.known_coins.coin_pub),
+ TALER_PQ_query_param_denom_sig (
+ &td->details.known_coins.denom_sig),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.known_coins.denominations_serial),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_known_coins",
+ "INSERT INTO known_coins"
+ "(known_coin_id"
+ ",coin_pub"
+ ",denom_sig"
+ ",denominations_serial"
+ ") VALUES "
+ "($1, $2, $3, $4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_known_coins",
+ params);
+}
+
+
+/**
+ * Function called with refresh_commitments records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_refresh_commitments (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.refresh_commitments.rc),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.refresh_commitments.old_coin_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.refresh_commitments.amount_with_fee),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.refresh_commitments.noreveal_index),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.refresh_commitments.old_coin_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_refresh_commitments",
+ "INSERT INTO refresh_commitments"
+ "(melt_serial_id"
+ ",rc"
+ ",old_coin_sig"
+ ",amount_with_fee"
+ ",noreveal_index"
+ ",old_coin_pub"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_refresh_commitments",
+ params);
+}
+
+
+/**
+ * Function called with refresh_revealed_coins records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_refresh_revealed_coins (
+ struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_HashCode h_coin_ev;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.refresh_revealed_coins.freshcoin_index),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.refresh_revealed_coins.link_sig),
+ GNUNET_PQ_query_param_fixed_size (
+ td->details.refresh_revealed_coins.coin_ev,
+ td->details.refresh_revealed_coins.
+ coin_ev_size),
+ GNUNET_PQ_query_param_auto_from_type (&h_coin_ev),
+ TALER_PQ_query_param_blinded_denom_sig (
+ &td->details.refresh_revealed_coins.ev_sig),
+ TALER_PQ_query_param_exchange_withdraw_values (
+ &td->details.refresh_revealed_coins.ewv),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.refresh_revealed_coins.denominations_serial),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.refresh_revealed_coins.melt_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_refresh_revealed_coins",
+ "INSERT INTO refresh_revealed_coins"
+ "(rrc_serial"
+ ",freshcoin_index"
+ ",link_sig"
+ ",coin_ev"
+ ",h_coin_ev"
+ ",ev_sig"
+ ",ewv"
+ ",denominations_serial"
+ ",melt_serial_id"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9);");
+ GNUNET_CRYPTO_hash (td->details.refresh_revealed_coins.coin_ev,
+ td->details.refresh_revealed_coins.coin_ev_size,
+ &h_coin_ev);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_refresh_revealed_coins",
+ params);
+}
+
+
+/**
+ * Function called with refresh_transfer_keys records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_refresh_transfer_keys (
+ struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.refresh_transfer_keys.tp),
+ GNUNET_PQ_query_param_fixed_size (
+ &td->details.refresh_transfer_keys.tprivs[0],
+ (TALER_CNC_KAPPA - 1)
+ * sizeof (struct TALER_TransferPrivateKeyP)),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.refresh_transfer_keys.melt_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_refresh_transfer_keys",
+ "INSERT INTO refresh_transfer_keys"
+ "(rtc_serial"
+ ",transfer_pub"
+ ",transfer_privs"
+ ",melt_serial_id"
+ ") VALUES "
+ "($1, $2, $3, $4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_refresh_transfer_keys",
+ params);
+}
+
+
+/**
+ * Function called with batch deposits records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_batch_deposits (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (&td->details.batch_deposits.shard),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.batch_deposits.merchant_pub),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.batch_deposits.wallet_timestamp),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.batch_deposits.exchange_timestamp),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.batch_deposits.refund_deadline),
+ GNUNET_PQ_query_param_timestamp (&td->details.batch_deposits.wire_deadline),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.batch_deposits.h_contract_terms),
+ td->details.batch_deposits.no_wallet_data_hash
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (
+ &td->details.batch_deposits.wallet_data_hash),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.batch_deposits.wire_salt),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.batch_deposits.wire_target_h_payto),
+ GNUNET_PQ_query_param_bool (td->details.batch_deposits.policy_blocked),
+ td->details.batch_deposits.no_policy_details
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (
+ &td->details.batch_deposits.policy_details_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_batch_deposits",
+ "INSERT INTO batch_deposits"
+ "(batch_deposit_serial_id"
+ ",shard"
+ ",merchant_pub"
+ ",wallet_timestamp"
+ ",exchange_timestamp"
+ ",refund_deadline"
+ ",wire_deadline"
+ ",h_contract_terms"
+ ",wallet_data_hash"
+ ",wire_salt"
+ ",wire_target_h_payto"
+ ",policy_details_serial_id"
+ ",policy_blocked"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
+ " $11, $12, $13);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_batch_deposits",
+ params);
+}
+
+
+/**
+ * Function called with deposits records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_coin_deposits (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.coin_deposits.batch_deposit_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.coin_deposits.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.coin_deposits.coin_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.coin_deposits.amount_with_fee),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_coin_deposits",
+ "INSERT INTO coin_deposits"
+ "(coin_deposit_serial_id"
+ ",batch_deposit_serial_id"
+ ",coin_pub"
+ ",coin_sig"
+ ",amount_with_fee"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_coin_deposits",
+ params);
+}
+
+
+/**
+ * Function called with refunds records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_refunds (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.refunds.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.refunds.merchant_sig),
+ GNUNET_PQ_query_param_uint64 (&td->details.refunds.rtransaction_id),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.refunds.amount_with_fee),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.refunds.batch_deposit_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_refunds",
+ "INSERT INTO refunds"
+ "(refund_serial_id"
+ ",coin_pub"
+ ",merchant_sig"
+ ",rtransaction_id"
+ ",amount_with_fee"
+ ",batch_deposit_serial_id"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_refunds",
+ params);
+}
+
+
+/**
+ * Function called with wire_out records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wire_out (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_timestamp (&td->details.wire_out.execution_date),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.wire_out.wtid_raw),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wire_out.wire_target_h_payto),
+ GNUNET_PQ_query_param_string (
+ td->details.wire_out.exchange_account_section),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wire_out.amount),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wire_out",
+ "INSERT INTO wire_out"
+ "(wireout_uuid"
+ ",execution_date"
+ ",wtid_raw"
+ ",wire_target_h_payto"
+ ",exchange_account_section"
+ ",amount"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wire_out",
+ params);
+}
+
+
+/**
+ * Function called with aggregation_tracking records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_aggregation_tracking (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.aggregation_tracking.batch_deposit_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.aggregation_tracking.wtid_raw),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_aggregation_tracking",
+ "INSERT INTO aggregation_tracking"
+ "(aggregation_serial_id"
+ ",batch_deposit_serial_id"
+ ",wtid_raw"
+ ") VALUES "
+ "($1, $2, $3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_aggregation_tracking",
+ params);
+}
+
+
+/**
+ * Function called with wire_fee records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wire_fee (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_string (td->details.wire_fee.wire_method),
+ GNUNET_PQ_query_param_timestamp (&td->details.wire_fee.start_date),
+ GNUNET_PQ_query_param_timestamp (&td->details.wire_fee.end_date),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wire_fee.fees.wire),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wire_fee.fees.closing),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.wire_fee.master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wire_fee",
+ "INSERT INTO wire_fee"
+ "(wire_fee_serial"
+ ",wire_method"
+ ",start_date"
+ ",end_date"
+ ",wire_fee"
+ ",closing_fee"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wire_fee",
+ params);
+}
+
+
+/**
+ * Function called with wire_fee records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_global_fee (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (
+ &td->serial),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.global_fee.start_date),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.global_fee.end_date),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.global_fee.fees.history),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.global_fee.fees.account),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.global_fee.fees.purse),
+ GNUNET_PQ_query_param_relative_time (
+ &td->details.global_fee.purse_timeout),
+ GNUNET_PQ_query_param_relative_time (
+ &td->details.global_fee.history_expiration),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.global_fee.purse_account_limit),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.global_fee.master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_global_fee",
+ "INSERT INTO global_fee"
+ "(global_fee_serial"
+ ",start_date"
+ ",end_date"
+ ",history_fee"
+ ",account_fee"
+ ",purse_fee"
+ ",purse_timeout"
+ ",history_expiration"
+ ",purse_account_limit"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_global_fee",
+ params);
+}
+
+
+/**
+ * Function called with recoup records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_recoup (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.recoup.coin_sig),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.recoup.coin_blind),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.recoup.amount),
+ GNUNET_PQ_query_param_timestamp (&td->details.recoup.timestamp),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.recoup.coin_pub),
+ GNUNET_PQ_query_param_uint64 (&td->details.recoup.reserve_out_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_recoup",
+ "INSERT INTO recoup"
+ "(recoup_uuid"
+ ",coin_sig"
+ ",coin_blind"
+ ",amount"
+ ",recoup_timestamp"
+ ",coin_pub"
+ ",reserve_out_serial_id"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_recoup",
+ params);
+}
+
+
+/**
+ * Function called with recoup_refresh records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_recoup_refresh (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.recoup_refresh.coin_sig),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.recoup_refresh.coin_blind),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.recoup_refresh.amount),
+ GNUNET_PQ_query_param_timestamp (&td->details.recoup_refresh.timestamp),
+ GNUNET_PQ_query_param_uint64 (&td->details.recoup_refresh.known_coin_id),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.recoup.coin_pub),
+ GNUNET_PQ_query_param_uint64 (&td->details.recoup_refresh.rrc_serial),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_recoup_refresh",
+ "INSERT INTO recoup_refresh"
+ "(recoup_refresh_uuid"
+ ",coin_sig"
+ ",coin_blind"
+ ",amount"
+ ",recoup_timestamp"
+ ",known_coin_id"
+ ",coin_pub"
+ ",rrc_serial"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_recoup_refresh",
+ params);
+}
+
+
+/**
+ * Function called with extensions records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_extensions (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_string (td->details.extensions.name),
+ NULL == td->details.extensions.manifest ?
+ GNUNET_PQ_query_param_null () :
+ GNUNET_PQ_query_param_string (td->details.extensions.manifest),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_extensions",
+ "INSERT INTO extensions"
+ "(extension_id"
+ ",name"
+ ",manifest"
+ ") VALUES "
+ "($1, $2, $3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_extensions",
+ params);
+}
+
+
+/**
+ * Function called with policy_details records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_policy_details (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.policy_details.hash_code),
+ (td->details.policy_details.no_policy_json)
+ ? GNUNET_PQ_query_param_null ()
+ : TALER_PQ_query_param_json (td->details.policy_details.policy_json),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.policy_details.commitment),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.policy_details.accumulated_total),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.policy_details.fee),
+ TALER_PQ_query_param_amount (pg->conn,
+ &td->details.policy_details.transferable),
+ GNUNET_PQ_query_param_timestamp (&td->details.policy_details.deadline),
+ GNUNET_PQ_query_param_uint16 (
+ &td->details.policy_details.fulfillment_state),
+ (td->details.policy_details.no_fulfillment_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (
+ &td->details.policy_details.fulfillment_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_policy_details",
+ "INSERT INTO policy_details"
+ "(policy_details_serial_id"
+ ",policy_hash_code"
+ ",policy_json"
+ ",deadline"
+ ",commitment"
+ ",accumulated_total"
+ ",fee"
+ ",transferable"
+ ",fulfillment_state"
+ ",fulfillment_id"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_policy_details",
+ params);
+}
+
+
+/**
+ * Function called with policy_fulfillment records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_policy_fulfillments (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.policy_fulfillments.fulfillment_timestamp),
+ (NULL == td->details.policy_fulfillments.fulfillment_proof)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (
+ td->details.policy_fulfillments.fulfillment_proof),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.policy_fulfillments.h_fulfillment_proof),
+ GNUNET_PQ_query_param_fixed_size (
+ td->details.policy_fulfillments.policy_hash_codes,
+ td->details.policy_fulfillments.policy_hash_codes_count),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_policy_fulfillments",
+ "INSERT INTO policy_fulfillments "
+ "(fulfillment_id"
+ ",fulfillment_timestamp"
+ ",fulfillment_proof"
+ ",h_fulfillment_proof"
+ ",policy_hash_codes"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_policy_fulfillments",
+ params);
+}
+
+
+/**
+ * Function called with purse_requests records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_purse_requests (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_requests.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_requests.merge_pub),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.purse_requests.purse_creation),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.purse_requests.purse_expiration),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_requests.h_contract_terms),
+ GNUNET_PQ_query_param_uint32 (&td->details.purse_requests.age_limit),
+ GNUNET_PQ_query_param_uint32 (&td->details.purse_requests.flags),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.purse_requests.amount_with_fee),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.purse_requests.purse_fee),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_requests.purse_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_purse_requests",
+ "INSERT INTO purse_requests"
+ "(purse_requests_serial_id"
+ ",purse_pub"
+ ",merge_pub"
+ ",purse_creation"
+ ",purse_expiration"
+ ",h_contract_terms"
+ ",age_limit"
+ ",flags"
+ ",amount_with_fee"
+ ",purse_fee"
+ ",purse_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_purse_requests",
+ params);
+}
+
+
+/**
+ * Function called with purse_decision records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_purse_decision (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_decision.purse_pub),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.purse_decision.action_timestamp),
+ GNUNET_PQ_query_param_bool (
+ td->details.purse_decision.refunded),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_purse_refunds",
+ "INSERT INTO purse_refunds"
+ "(purse_refunds_serial_id"
+ ",purse_pub"
+ ",action_timestamp"
+ ",refunded"
+ ") VALUES "
+ "($1, $2, $3, $4);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_purse_decision",
+ params);
+}
+
+
+/**
+ * Function called with purse_merges records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_purse_merges (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (&td->details.purse_merges.partner_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_merges.reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.purse_merges.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.purse_merges.merge_sig),
+ GNUNET_PQ_query_param_timestamp (&td->details.purse_merges.merge_timestamp),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_purse_merges",
+ "INSERT INTO purse_merges"
+ "(purse_merge_request_serial_id"
+ ",partner_serial_id"
+ ",reserve_pub"
+ ",purse_pub"
+ ",merge_sig"
+ ",merge_timestamp"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_purse_merges",
+ params);
+}
+
+
+/**
+ * Function called with purse_deposits records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_purse_deposits (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.purse_deposits.partner_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_deposits.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.purse_deposits.coin_pub),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.purse_deposits.amount_with_fee),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.purse_deposits.coin_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_purse_deposits",
+ "INSERT INTO purse_deposits"
+ "(purse_deposit_serial_id"
+ ",partner_serial_id"
+ ",purse_pub"
+ ",coin_pub"
+ ",amount_with_fee"
+ ",coin_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_purse_deposits",
+ params);
+}
+
+
+/**
+x * Function called with account_mergers records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_account_mergers (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.account_merges.reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.account_merges.reserve_sig),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.account_merges.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.account_merges.wallet_h_payto),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_account_merges",
+ "INSERT INTO account_merges"
+ "(account_merge_request_serial_id"
+ ",reserve_pub"
+ ",reserve_sig"
+ ",purse_pub"
+ ",wallet_h_payto"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_account_merges",
+ params);
+}
+
+
+/**
+ * Function called with history_requests records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_history_requests (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.history_requests.reserve_pub),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.history_requests.request_timestamp),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.history_requests.reserve_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.history_requests.history_fee),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_history_requests",
+ "INSERT INTO history_requests"
+ "(history_request_serial_id"
+ ",reserve_pub"
+ ",request_timestamp"
+ ",reserve_sig"
+ ",history_fee"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_history_requests",
+ params);
+}
+
+
+/**
+ * Function called with close_requests records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_close_requests (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.close_requests.reserve_pub),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.close_requests.close_timestamp),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.close_requests.reserve_sig),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.close_requests.close),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.close_requests.close_fee),
+ GNUNET_PQ_query_param_string (
+ td->details.close_requests.payto_uri),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_close_requests",
+ "INSERT INTO close_requests"
+ "(close_request_serial_id"
+ ",reserve_pub"
+ ",close_timestamp"
+ ",reserve_sig"
+ ",close"
+ ",close_fee"
+ ",payto_uri"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_close_requests",
+ params);
+}
+
+
+/**
+ * Function called with wads_out records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wads_out (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.wads_out.wad_id),
+ GNUNET_PQ_query_param_uint64 (&td->details.wads_out.partner_serial_id),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_out.amount),
+ GNUNET_PQ_query_param_timestamp (&td->details.wads_out.execution_time),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wads_out",
+ "INSERT INTO wads_out"
+ "(wad_out_serial_id"
+ ",wad_id"
+ ",partner_serial_id"
+ ",amount"
+ ",execution_time"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wads_out",
+ params);
+}
+
+
+/**
+ * Function called with wads_out_entries records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wads_out_entries (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.wads_out_entries.wad_out_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_out_entries.reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_out_entries.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_out_entries.h_contract),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.wads_out_entries.purse_expiration),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.wads_out_entries.merge_timestamp),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_out_entries.amount_with_fee),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_out_entries.wad_fee),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_out_entries.deposit_fees),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_out_entries.reserve_sig),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_out_entries.purse_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wad_out_entries",
+ "INSERT INTO wad_out_entries"
+ "(wad_out_entry_serial_id"
+ ",wad_out_serial_id"
+ ",reserve_pub"
+ ",purse_pub"
+ ",h_contract"
+ ",purse_expiration"
+ ",merge_timestamp"
+ ",amount_with_fee"
+ ",wad_fee"
+ ",deposit_fees"
+ ",reserve_sig"
+ ",purse_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wads_out_entries",
+ params);
+}
+
+
+/**
+ * Function called with wads_in records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wads_in (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (&td->details.wads_in.wad_id),
+ GNUNET_PQ_query_param_string (td->details.wads_in.origin_exchange_url),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_in.amount),
+ GNUNET_PQ_query_param_timestamp (&td->details.wads_in.arrival_time),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wads_in",
+ "INSERT INTO wads_in"
+ "(wad_in_serial_id"
+ ",wad_id"
+ ",origin_exchange_url"
+ ",amount"
+ ",arrival_time"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wads_in",
+ params);
+}
+
+
+/**
+ * Function called with wads_in_entries records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_wads_in_entries (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_in_entries.reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_in_entries.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_in_entries.h_contract),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.wads_in_entries.purse_expiration),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.wads_in_entries.merge_timestamp),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_in_entries.amount_with_fee),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_in_entries.wad_fee),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.wads_in_entries.deposit_fees),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_in_entries.reserve_sig),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.wads_in_entries.purse_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_wad_in_entries",
+ "INSERT INTO wad_in_entries"
+ "(wad_in_entry_serial_id"
+ ",wad_in_serial_id"
+ ",reserve_pub"
+ ",purse_pub"
+ ",h_contract"
+ ",purse_expiration"
+ ",merge_timestamp"
+ ",amount_with_fee"
+ ",wad_fee"
+ ",deposit_fees"
+ ",reserve_sig"
+ ",purse_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_wads_in_entries",
+ params);
+}
+
+
+/**
+ * Function called with profit_drains records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_profit_drains (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.profit_drains.wtid),
+ GNUNET_PQ_query_param_string (
+ td->details.profit_drains.account_section),
+ GNUNET_PQ_query_param_string (
+ td->details.profit_drains.payto_uri),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.profit_drains.trigger_date),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.profit_drains.amount),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.profit_drains.master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_profit_drains",
+ "INSERT INTO profit_drains"
+ "(profit_drain_serial_id"
+ ",wtid"
+ ",account_section"
+ ",payto_uri"
+ ",trigger_date"
+ ",amount"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_profit_drains",
+ params);
+}
+
+
+/**
+ * Function called with aml_staff records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_aml_staff (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.aml_staff.decider_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.aml_staff.master_sig),
+ GNUNET_PQ_query_param_string (
+ td->details.aml_staff.decider_name),
+ GNUNET_PQ_query_param_bool (
+ td->details.aml_staff.is_active),
+ GNUNET_PQ_query_param_bool (
+ td->details.aml_staff.read_only),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.aml_staff.last_change),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_aml_staff",
+ "INSERT INTO aml_staff"
+ "(aml_staff_uuid"
+ ",decider_pub"
+ ",master_sig"
+ ",decider_name"
+ ",is_active"
+ ",read_only"
+ ",last_change"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_aml_staff",
+ params);
+}
+
+
+/**
+ * Function called with aml_history records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_aml_history (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ uint32_t status32 = td->details.aml_history.new_status;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.aml_history.h_payto),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.aml_history.new_threshold),
+ GNUNET_PQ_query_param_uint32 (
+ &status32),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.aml_history.decision_time),
+ GNUNET_PQ_query_param_string (
+ td->details.aml_history.justification),
+ (NULL == td->details.aml_history.kyc_requirements)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (
+ td->details.aml_history.kyc_requirements),
+ GNUNET_PQ_query_param_uint64 (
+ &td->details.aml_history.kyc_req_row),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.aml_history.decider_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.aml_history.decider_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_aml_history",
+ "INSERT INTO aml_history"
+ "(aml_history_serial_id"
+ ",h_payto"
+ ",new_threshold"
+ ",new_status"
+ ",decision_time"
+ ",justification"
+ ",kyc_requirements"
+ ",kyc_req_row"
+ ",decider_pub"
+ ",decider_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_aml_history",
+ params);
+}
+
+
+/**
+ * Function called with kyc_attributes records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_kyc_attributes (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.kyc_attributes.h_payto),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.kyc_attributes.kyc_prox),
+ GNUNET_PQ_query_param_string (
+ td->details.kyc_attributes.provider),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.kyc_attributes.collection_time),
+ GNUNET_PQ_query_param_timestamp (
+ &td->details.kyc_attributes.expiration_time),
+ GNUNET_PQ_query_param_fixed_size (
+ &td->details.kyc_attributes.encrypted_attributes,
+ td->details.kyc_attributes.encrypted_attributes_size),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_kyc_attributes",
+ "INSERT INTO kyc_attributes"
+ "(kyc_attributes_serial_id"
+ ",h_payto"
+ ",kyc_prox"
+ ",provider"
+ ",collection_time"
+ ",expiration_time"
+ ",encrypted_attributes"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_kyc_attributes",
+ params);
+}
+
+
+/**
+ * Function called with purse_deletion records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_purse_deletion (struct PostgresClosure *pg,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_deletion.purse_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.purse_deletion.purse_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_purse_deletion",
+ "INSERT INTO purse_deletion"
+ "(purse_deletion_serial_id"
+ ",purse_pub"
+ ",purse_sig"
+ ") VALUES "
+ "($1, $2, $3);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_purse_deletion",
+ params);
+}
+
+
+/**
+ * Function called with age_withdraw records to insert into table.
+ *
+ * @param pg plugin context
+ * @param td record to insert
+ */
+static enum GNUNET_DB_QueryStatus
+irbt_cb_table_age_withdraw (struct PostgresClosure *pg,
+ const struct
+ TALER_EXCHANGEDB_TableData *td)
+{
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&td->serial),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.age_withdraw.h_commitment),
+ TALER_PQ_query_param_amount (
+ pg->conn,
+ &td->details.age_withdraw.amount_with_fee),
+ GNUNET_PQ_query_param_uint16 (
+ &td->details.age_withdraw.max_age),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.age_withdraw.reserve_pub),
+ GNUNET_PQ_query_param_auto_from_type (
+ &td->details.age_withdraw.reserve_sig),
+ GNUNET_PQ_query_param_uint32 (
+ &td->details.age_withdraw.noreveal_index),
+ /* TODO: other fields, too! */
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_into_table_age_withdraw",
+ "INSERT INTO age_withdraw"
+ "(age_withdraw_commitment_id"
+ ",h_commitment"
+ ",amount_with_fee"
+ ",max_age"
+ ",reserve_pub"
+ ",reserve_sig"
+ ",noreveal_index"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6, $7, $8);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_into_table_age_withdraw",
+ params);
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_records_by_table (void *cls,
+ const struct TALER_EXCHANGEDB_TableData *td)
+{
+ struct PostgresClosure *pg = cls;
+ InsertRecordCallback rh = NULL;
+
+ switch (td->table)
+ {
+ case TALER_EXCHANGEDB_RT_DENOMINATIONS:
+ rh = &irbt_cb_table_denominations;
+ break;
+ case TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS:
+ rh = &irbt_cb_table_denomination_revocations;
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_TARGETS:
+ rh = &irbt_cb_table_wire_targets;
+ break;
+ case TALER_EXCHANGEDB_RT_LEGITIMIZATION_PROCESSES:
+ rh = &irbt_cb_table_legitimization_processes;
+ break;
+ case TALER_EXCHANGEDB_RT_LEGITIMIZATION_REQUIREMENTS:
+ rh = &irbt_cb_table_legitimization_requirements;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES:
+ rh = &irbt_cb_table_reserves;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_IN:
+ rh = &irbt_cb_table_reserves_in;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_CLOSE:
+ rh = &irbt_cb_table_reserves_close;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OPEN_REQUESTS:
+ rh = &irbt_cb_table_reserves_open_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OPEN_DEPOSITS:
+ rh = &irbt_cb_table_reserves_open_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OUT:
+ rh = &irbt_cb_table_reserves_out;
+ break;
+ case TALER_EXCHANGEDB_RT_AUDITORS:
+ rh = &irbt_cb_table_auditors;
+ break;
+ case TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS:
+ rh = &irbt_cb_table_auditor_denom_sigs;
+ break;
+ case TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS:
+ rh = &irbt_cb_table_exchange_sign_keys;
+ break;
+ case TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS:
+ rh = &irbt_cb_table_signkey_revocations;
+ break;
+ case TALER_EXCHANGEDB_RT_KNOWN_COINS:
+ rh = &irbt_cb_table_known_coins;
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS:
+ rh = &irbt_cb_table_refresh_commitments;
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS:
+ rh = &irbt_cb_table_refresh_revealed_coins;
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS:
+ rh = &irbt_cb_table_refresh_transfer_keys;
+ break;
+ case TALER_EXCHANGEDB_RT_BATCH_DEPOSITS:
+ rh = &irbt_cb_table_batch_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_COIN_DEPOSITS:
+ rh = &irbt_cb_table_coin_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_REFUNDS:
+ rh = &irbt_cb_table_refunds;
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_OUT:
+ rh = &irbt_cb_table_wire_out;
+ break;
+ case TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING:
+ rh = &irbt_cb_table_aggregation_tracking;
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_FEE:
+ rh = &irbt_cb_table_wire_fee;
+ break;
+ case TALER_EXCHANGEDB_RT_GLOBAL_FEE:
+ rh = &irbt_cb_table_global_fee;
+ break;
+ case TALER_EXCHANGEDB_RT_RECOUP:
+ rh = &irbt_cb_table_recoup;
+ break;
+ case TALER_EXCHANGEDB_RT_RECOUP_REFRESH:
+ rh = &irbt_cb_table_recoup_refresh;
+ break;
+ case TALER_EXCHANGEDB_RT_EXTENSIONS:
+ rh = &irbt_cb_table_extensions;
+ break;
+ case TALER_EXCHANGEDB_RT_POLICY_DETAILS:
+ rh = &irbt_cb_table_policy_details;
+ break;
+ case TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS:
+ rh = &irbt_cb_table_policy_fulfillments;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_REQUESTS:
+ rh = &irbt_cb_table_purse_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DECISION:
+ rh = &irbt_cb_table_purse_decision;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_MERGES:
+ rh = &irbt_cb_table_purse_merges;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DEPOSITS:
+ rh = &irbt_cb_table_purse_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_ACCOUNT_MERGES:
+ rh = &irbt_cb_table_account_mergers;
+ break;
+ case TALER_EXCHANGEDB_RT_HISTORY_REQUESTS:
+ rh = &irbt_cb_table_history_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_CLOSE_REQUESTS:
+ rh = &irbt_cb_table_close_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_OUT:
+ rh = &irbt_cb_table_wads_out;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_OUT_ENTRIES:
+ rh = &irbt_cb_table_wads_out_entries;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_IN:
+ rh = &irbt_cb_table_wads_in;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_IN_ENTRIES:
+ rh = &irbt_cb_table_wads_in_entries;
+ break;
+ case TALER_EXCHANGEDB_RT_PROFIT_DRAINS:
+ rh = &irbt_cb_table_profit_drains;
+ break;
+ case TALER_EXCHANGEDB_RT_AML_STAFF:
+ rh = &irbt_cb_table_aml_staff;
+ break;
+ case TALER_EXCHANGEDB_RT_AML_HISTORY:
+ rh = &irbt_cb_table_aml_history;
+ break;
+ case TALER_EXCHANGEDB_RT_KYC_ATTRIBUTES:
+ rh = &irbt_cb_table_kyc_attributes;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DELETION:
+ rh = &irbt_cb_table_purse_deletion;
+ break;
+ case TALER_EXCHANGEDB_RT_AGE_WITHDRAW:
+ rh = &irbt_cb_table_age_withdraw;
+ break;
+ }
+ if (NULL == rh)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return rh (pg,
+ td);
+}
+
+
+/* end of pg_insert_records_by_table.c */
diff --git a/src/exchangedb/pg_insert_records_by_table.h b/src/exchangedb/pg_insert_records_by_table.h
new file mode 100644
index 000000000..d9817e0ec
--- /dev/null
+++ b/src/exchangedb/pg_insert_records_by_table.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_insert_records_by_table.h
+ * @brief implementation of the insert_records_by_table function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_RECORDS_BY_TABLE_H
+#define PG_INSERT_RECORDS_BY_TABLE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert record set into @a table. Used in exchange-auditor database
+ * replication.
+ *
+ * @param cls closure
+ * @param td table data to insert
+ * @return transaction status code, #GNUNET_DB_STATUS_HARD_ERROR if
+ * @e table in @a tr is not supported
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_records_by_table (void *cls,
+ const struct TALER_EXCHANGEDB_TableData *td);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_refresh_reveal.c b/src/exchangedb/pg_insert_refresh_reveal.c
new file mode 100644
index 000000000..bddca472b
--- /dev/null
+++ b/src/exchangedb/pg_insert_refresh_reveal.c
@@ -0,0 +1,109 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_refresh_reveal.c
+ * @brief Implementation of the insert_refresh_reveal function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_refresh_reveal.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_refresh_reveal (
+ void *cls,
+ uint64_t melt_serial_id,
+ uint32_t num_rrcs,
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
+ unsigned int num_tprivs,
+ const struct TALER_TransferPrivateKeyP *tprivs,
+ const struct TALER_TransferPublicKeyP *tp)
+{
+ struct PostgresClosure *pg = cls;
+
+ if (TALER_CNC_KAPPA != num_tprivs + 1)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ PREPARE (pg,
+ "insert_refresh_revealed_coin",
+ "INSERT INTO refresh_revealed_coins "
+ "(melt_serial_id "
+ ",freshcoin_index "
+ ",link_sig "
+ ",denominations_serial "
+ ",coin_ev"
+ ",ewv"
+ ",h_coin_ev"
+ ",ev_sig"
+ ") SELECT $1, $2, $3, "
+ " denominations_serial, $5, $6, $7, $8"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$4"
+ " ON CONFLICT DO NOTHING;");
+ for (uint32_t i = 0; i<num_rrcs; i++)
+ {
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&melt_serial_id),
+ GNUNET_PQ_query_param_uint32 (&i),
+ GNUNET_PQ_query_param_auto_from_type (&rrc->orig_coin_link_sig),
+ GNUNET_PQ_query_param_auto_from_type (&rrc->h_denom_pub),
+ TALER_PQ_query_param_blinded_planchet (&rrc->blinded_planchet),
+ TALER_PQ_query_param_exchange_withdraw_values (&rrc->exchange_vals),
+ GNUNET_PQ_query_param_auto_from_type (&rrc->coin_envelope_hash),
+ TALER_PQ_query_param_blinded_denom_sig (&rrc->coin_sig),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_refresh_revealed_coin",
+ params);
+ if (0 > qs)
+ return qs;
+ }
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&melt_serial_id),
+ GNUNET_PQ_query_param_auto_from_type (tp),
+ GNUNET_PQ_query_param_fixed_size (
+ tprivs,
+ num_tprivs * sizeof (struct TALER_TransferPrivateKeyP)),
+ GNUNET_PQ_query_param_end
+ };
+
+ /* Used in #postgres_insert_refresh_reveal() to store the transfer
+ keys we learned */
+ PREPARE (pg,
+ "insert_refresh_transfer_keys",
+ "INSERT INTO refresh_transfer_keys "
+ "(melt_serial_id"
+ ",transfer_pub"
+ ",transfer_privs"
+ ") VALUES ($1, $2, $3)"
+ " ON CONFLICT DO NOTHING;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_refresh_transfer_keys",
+ params);
+ }
+}
diff --git a/src/exchangedb/pg_insert_refresh_reveal.h b/src/exchangedb/pg_insert_refresh_reveal.h
new file mode 100644
index 000000000..ad53ab197
--- /dev/null
+++ b/src/exchangedb/pg_insert_refresh_reveal.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_refresh_reveal.h
+ * @brief implementation of the insert_refresh_reveal function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_REFRESH_REVEAL_H
+#define PG_INSERT_REFRESH_REVEAL_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Store in the database which coin(s) the wallet wanted to create
+ * in a given refresh operation and all of the other information
+ * we learned or created in the /refresh/reveal step.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param melt_serial_id row ID of the commitment / melt operation in refresh_commitments
+ * @param num_rrcs number of coins to generate, size of the @a rrcs array
+ * @param rrcs information about the new coins
+ * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
+ * @param tprivs transfer private keys to store
+ * @param tp public key to store
+ * @return query status for the transaction
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_refresh_reveal (
+ void *cls,
+ uint64_t melt_serial_id,
+ uint32_t num_rrcs,
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
+ unsigned int num_tprivs,
+ const struct TALER_TransferPrivateKeyP *tprivs,
+ const struct TALER_TransferPublicKeyP *tp);
+
+#endif
diff --git a/src/exchangedb/pg_insert_refund.c b/src/exchangedb/pg_insert_refund.c
new file mode 100644
index 000000000..e989c91bc
--- /dev/null
+++ b/src/exchangedb/pg_insert_refund.c
@@ -0,0 +1,65 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_refund.c
+ * @brief Implementation of the insert_refund function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_refund.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_refund (void *cls,
+ const struct TALER_EXCHANGEDB_Refund *refund)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&refund->coin.coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_sig),
+ GNUNET_PQ_query_param_auto_from_type (&refund->details.h_contract_terms),
+ GNUNET_PQ_query_param_uint64 (&refund->details.rtransaction_id),
+ TALER_PQ_query_param_amount (pg->conn,
+ &refund->details.refund_amount),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_assert (GNUNET_YES ==
+ TALER_amount_cmp_currency (&refund->details.refund_amount,
+ &refund->details.refund_fee));
+ PREPARE (pg,
+ "insert_refund",
+ "INSERT INTO refunds "
+ "(coin_pub"
+ ",batch_deposit_serial_id"
+ ",merchant_sig"
+ ",rtransaction_id"
+ ",amount_with_fee"
+ ") SELECT $1, cdep.batch_deposit_serial_id, $3, $5, $6"
+ " FROM coin_deposits cdep"
+ " JOIN batch_deposits bdep USING (batch_deposit_serial_id)"
+ " WHERE coin_pub=$1"
+ " AND h_contract_terms=$4"
+ " AND merchant_pub=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_refund",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_refund.h b/src/exchangedb/pg_insert_refund.h
new file mode 100644
index 000000000..02190bcc9
--- /dev/null
+++ b/src/exchangedb/pg_insert_refund.h
@@ -0,0 +1,38 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_refund.h
+ * @brief implementation of the insert_refund function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_REFUND_H
+#define PG_INSERT_REFUND_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Insert information about refunded coin into the database.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param refund refund information to store
+ * @return query result status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_refund (void *cls,
+ const struct TALER_EXCHANGEDB_Refund *refund);
+
+#endif
diff --git a/src/exchangedb/pg_insert_reserve_closed.c b/src/exchangedb/pg_insert_reserve_closed.c
new file mode 100644
index 000000000..6644fb892
--- /dev/null
+++ b/src/exchangedb/pg_insert_reserve_closed.c
@@ -0,0 +1,113 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_reserve_closed.c
+ * @brief Implementation of the insert_reserve_closed function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_reserve_closed.h"
+#include "pg_helper.h"
+#include "pg_reserves_get.h"
+#include "pg_reserves_update.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_reserve_closed (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct GNUNET_TIME_Timestamp execution_date,
+ const char *receiver_account,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *closing_fee,
+ uint64_t close_request_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct TALER_EXCHANGEDB_Reserve reserve;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_PaytoHashP h_payto;
+
+ TALER_payto_hash (receiver_account,
+ &h_payto);
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_timestamp (&execution_date),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_auto_from_type (&h_payto),
+ TALER_PQ_query_param_amount (pg->conn,
+ amount_with_fee),
+ TALER_PQ_query_param_amount (pg->conn,
+ closing_fee),
+ GNUNET_PQ_query_param_uint64 (&close_request_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "reserves_close_insert",
+ "INSERT INTO reserves_close "
+ "(reserve_pub"
+ ",execution_date"
+ ",wtid"
+ ",wire_target_h_payto"
+ ",amount"
+ ",closing_fee"
+ ",close_request_row"
+ ") VALUES ($1, $2, $3, $4, $5, $6, $7);");
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "reserves_close_insert",
+ params);
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ return qs;
+
+ /* update reserve balance */
+ reserve.pub = *reserve_pub;
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ (qs = TEH_PG_reserves_get (cls,
+ &reserve)))
+ {
+ /* Existence should have been checked before we got here... */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+ }
+ {
+ enum TALER_AmountArithmeticResult ret;
+
+ ret = TALER_amount_subtract (&reserve.balance,
+ &reserve.balance,
+ amount_with_fee);
+ if (ret < 0)
+ {
+ /* The reserve history was checked to make sure there is enough of a balance
+ left before we tried this; however, concurrent operations may have changed
+ the situation by now. We should re-try the transaction. */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Closing of reserve `%s' refused due to balance mismatch. Retrying.\n",
+ TALER_B2S (reserve_pub));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_break (TALER_AAR_RESULT_ZERO == ret);
+ }
+ return TEH_PG_reserves_update (cls,
+ &reserve);
+}
diff --git a/src/exchangedb/pg_insert_reserve_closed.h b/src/exchangedb/pg_insert_reserve_closed.h
new file mode 100644
index 000000000..2ac1a6e30
--- /dev/null
+++ b/src/exchangedb/pg_insert_reserve_closed.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_reserve_closed.h
+ * @brief implementation of the insert_reserve_closed function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_RESERVE_CLOSED_H
+#define PG_INSERT_RESERVE_CLOSED_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Insert reserve close operation into database.
+ *
+ * @param cls closure
+ * @param reserve_pub which reserve is this about?
+ * @param execution_date when did we perform the transfer?
+ * @param receiver_account to which account do we transfer?
+ * @param wtid wire transfer details
+ * @param amount_with_fee amount we charged to the reserve
+ * @param closing_fee how high is the closing fee
+ * @param close_request_row identifies explicit close request, 0 for none
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_reserve_closed (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct GNUNET_TIME_Timestamp execution_date,
+ const char *receiver_account,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *closing_fee,
+ uint64_t close_request_row);
+
+#endif
diff --git a/src/exchangedb/pg_insert_reserve_open_deposit.c b/src/exchangedb/pg_insert_reserve_open_deposit.c
new file mode 100644
index 000000000..f9cedcbe7
--- /dev/null
+++ b/src/exchangedb/pg_insert_reserve_open_deposit.c
@@ -0,0 +1,67 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_insert_reserve_open_deposit.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_reserve_open_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_reserve_open_deposit (
+ void *cls,
+ const struct TALER_CoinPublicInfo *cpi,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ uint64_t known_coin_id,
+ const struct TALER_Amount *coin_total,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *insufficient_funds)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&cpi->coin_pub),
+ GNUNET_PQ_query_param_uint64 (&known_coin_id),
+ GNUNET_PQ_query_param_auto_from_type (coin_sig),
+ GNUNET_PQ_query_param_auto_from_type (reserve_sig),
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ TALER_PQ_query_param_amount (pg->conn,
+ coin_total),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("out_insufficient_funds",
+ insufficient_funds),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "insert_reserve_open_deposit",
+ "SELECT "
+ " out_insufficient_funds"
+ " FROM exchange_do_reserve_open_deposit"
+ " ($1,$2,$3,$4,$5,$6);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_reserve_open_deposit",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_insert_reserve_open_deposit.h b/src/exchangedb/pg_insert_reserve_open_deposit.h
new file mode 100644
index 000000000..7eb2fe093
--- /dev/null
+++ b/src/exchangedb/pg_insert_reserve_open_deposit.h
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_insert_reserve_open_deposit.h
+ * @brief implementation of the insert_reserve_open_deposit function
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_RESERVE_OPEN_DEPOSIT_H
+#define PG_INSERT_RESERVE_OPEN_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert reserve open coin deposit data into database.
+ * Subtracts the @a coin_total from the coin's balance.
+ *
+ * @param cls closure
+ * @param cpi public information about the coin
+ * @param coin_sig signature with @e coin_pub of type #TALER_SIGNATURE_WALLET_RESERVE_OPEN_DEPOSIT
+ * @param known_coin_id ID of the coin in the known_coins table
+ * @param coin_total amount to be spent of the coin (including deposit fee)
+ * @param reserve_sig signature by the reserve affirming the open operation
+ * @param reserve_pub public key of the reserve being opened
+ * @param[out] insufficient_funds set to true if the coin's balance is insufficient, otherwise to false
+ * @return transaction status code, 0 if operation is already in the DB
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_reserve_open_deposit (
+ void *cls,
+ const struct TALER_CoinPublicInfo *cpi,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ uint64_t known_coin_id,
+ const struct TALER_Amount *coin_total,
+ const struct TALER_ReserveSignatureP *reserve_sig,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *insufficient_funds);
+
+#endif
diff --git a/src/exchangedb/pg_insert_signkey_revocation.c b/src/exchangedb/pg_insert_signkey_revocation.c
new file mode 100644
index 000000000..9197be6ad
--- /dev/null
+++ b/src/exchangedb/pg_insert_signkey_revocation.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_signkey_revocation.c
+ * @brief Implementation of the insert_signkey_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_signkey_revocation.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_signkey_revocation (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+
+ PREPARE (pg,
+ "insert_signkey_revocation",
+ "INSERT INTO signkey_revocations "
+ "(esk_serial"
+ ",master_sig"
+ ") SELECT esk_serial, $2 "
+ " FROM exchange_sign_keys"
+ " WHERE exchange_pub=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_signkey_revocation",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_signkey_revocation.h b/src/exchangedb/pg_insert_signkey_revocation.h
new file mode 100644
index 000000000..534e6d451
--- /dev/null
+++ b/src/exchangedb/pg_insert_signkey_revocation.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_signkey_revocation.h
+ * @brief implementation of the insert_signkey_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_SIGNKEY_REVOCATION_H
+#define PG_INSERT_SIGNKEY_REVOCATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Store information about a revoked online signing key.
+ *
+ * @param cls closure
+ * @param exchange_pub exchange online signing key that was revoked
+ * @param master_sig signature affirming the revocation
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_signkey_revocation (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_insert_wire.c b/src/exchangedb/pg_insert_wire.c
new file mode 100644
index 000000000..b1364cbb3
--- /dev/null
+++ b/src/exchangedb/pg_insert_wire.c
@@ -0,0 +1,74 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023, 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_wire.c
+ * @brief Implementation of the insert_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_wire.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_wire (void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp start_date,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (payto_uri),
+ NULL == conversion_url
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (conversion_url),
+ TALER_PQ_query_param_json (debit_restrictions),
+ TALER_PQ_query_param_json (credit_restrictions),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_timestamp (&start_date),
+ NULL == bank_label
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (bank_label),
+ GNUNET_PQ_query_param_int64 (&priority),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_wire",
+ "INSERT INTO wire_accounts "
+ "(payto_uri"
+ ",conversion_url"
+ ",debit_restrictions"
+ ",credit_restrictions"
+ ",master_sig"
+ ",is_active"
+ ",last_change"
+ ",bank_label"
+ ",priority"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, true, $6, $7, $8);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_wire",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_wire.h b/src/exchangedb/pg_insert_wire.h
new file mode 100644
index 000000000..7a5e4caca
--- /dev/null
+++ b/src/exchangedb/pg_insert_wire.h
@@ -0,0 +1,55 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_wire.h
+ * @brief implementation of the insert_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_WIRE_H
+#define PG_INSERT_WIRE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Insert information about an wire account used by this exchange.
+ *
+ * @param cls closure
+ * @param payto_uri wire account of the exchange
+ * @param conversion_url URL of a conversion service, NULL if there is no conversion
+ * @param debit_restrictions JSON array with debit restrictions on the account
+ * @param credit_restrictions JSON array with credit restrictions on the account
+ * @param start_date date when the account was added by the offline system
+ * (only to be used for replay detection)
+ * @param master_sig public signature affirming the existence of the account,
+ * must be of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS
+ * @param bank_label label to show this entry under in the UI, can be NULL
+ * @param priority determines order in which entries are shown in the UI
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_wire (void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp start_date,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority);
+
+
+#endif
diff --git a/src/exchangedb/pg_insert_wire_fee.c b/src/exchangedb/pg_insert_wire_fee.c
new file mode 100644
index 000000000..af818bcca
--- /dev/null
+++ b/src/exchangedb/pg_insert_wire_fee.c
@@ -0,0 +1,108 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_wire_fee.c
+ * @brief Implementation of the insert_wire_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_wire_fee.h"
+#include "pg_helper.h"
+#include "pg_get_wire_fee.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_wire_fee (void *cls,
+ const char *type,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_WireFeeSet *fees,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (type),
+ GNUNET_PQ_query_param_timestamp (&start_date),
+ GNUNET_PQ_query_param_timestamp (&end_date),
+ TALER_PQ_query_param_amount (pg->conn,
+ &fees->wire),
+ TALER_PQ_query_param_amount (pg->conn,
+ &fees->closing),
+ GNUNET_PQ_query_param_auto_from_type (master_sig),
+ GNUNET_PQ_query_param_end
+ };
+ struct TALER_WireFeeSet wx;
+ struct TALER_MasterSignatureP sig;
+ struct GNUNET_TIME_Timestamp sd;
+ struct GNUNET_TIME_Timestamp ed;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_PG_get_wire_fee (pg,
+ type,
+ start_date,
+ &sd,
+ &ed,
+ &wx,
+ &sig);
+ if (qs < 0)
+ return qs;
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (0 != GNUNET_memcmp (&sig,
+ master_sig))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (0 !=
+ TALER_wire_fee_set_cmp (fees,
+ &wx))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (GNUNET_TIME_timestamp_cmp (sd,
+ !=,
+ start_date)) ||
+ (GNUNET_TIME_timestamp_cmp (ed,
+ !=,
+ end_date)) )
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* equal record already exists */
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+
+ PREPARE (pg,
+ "insert_wire_fee",
+ "INSERT INTO wire_fee "
+ "(wire_method"
+ ",start_date"
+ ",end_date"
+ ",wire_fee"
+ ",closing_fee"
+ ",master_sig"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_wire_fee",
+ params);
+}
diff --git a/src/exchangedb/pg_insert_wire_fee.h b/src/exchangedb/pg_insert_wire_fee.h
new file mode 100644
index 000000000..15c1a39f8
--- /dev/null
+++ b/src/exchangedb/pg_insert_wire_fee.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_insert_wire_fee.h
+ * @brief implementation of the insert_wire_fee function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_WIRE_FEE_H
+#define PG_INSERT_WIRE_FEE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Insert wire transfer fee into database.
+ *
+ * @param cls closure
+ * @param type type of wire transfer this fee applies for
+ * @param start_date when does the fee go into effect
+ * @param end_date when does the fee end being valid
+ * @param fees how high are the wire fees
+ * @param master_sig signature over the above by the exchange master key
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_insert_wire_fee (void *cls,
+ const char *type,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_WireFeeSet *fees,
+ const struct TALER_MasterSignatureP *master_sig);
+#endif
diff --git a/src/exchangedb/pg_iterate_active_auditors.c b/src/exchangedb/pg_iterate_active_auditors.c
new file mode 100644
index 000000000..4f1c6ec66
--- /dev/null
+++ b/src/exchangedb/pg_iterate_active_auditors.c
@@ -0,0 +1,123 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_iterate_active_auditors.c
+ * @brief Implementation of the iterate_active_auditors function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_iterate_active_auditors.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #auditors_cb_helper()
+ */
+struct AuditorsIteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_AuditorsCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_active_auditors().
+ * Calls the callback with each auditor.
+ *
+ * @param cls a `struct SignkeysIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+auditors_cb_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AuditorsIteratorContext *dic = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_AuditorPublicKeyP auditor_pub;
+ char *auditor_url;
+ char *auditor_name;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("auditor_pub",
+ &auditor_pub),
+ GNUNET_PQ_result_spec_string ("auditor_url",
+ &auditor_url),
+ GNUNET_PQ_result_spec_string ("auditor_name",
+ &auditor_name),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ dic->cb (dic->cb_cls,
+ &auditor_pub,
+ auditor_url,
+ auditor_name);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_active_auditors (void *cls,
+ TALER_EXCHANGEDB_AuditorsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct AuditorsIteratorContext dic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ };
+
+ PREPARE (pg,
+ "select_auditors",
+ "SELECT"
+ " auditor_pub"
+ ",auditor_url"
+ ",auditor_name"
+ " FROM auditors"
+ " WHERE"
+ " is_active;");
+
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_auditors",
+ params,
+ &auditors_cb_helper,
+ &dic);
+}
diff --git a/src/exchangedb/pg_iterate_active_auditors.h b/src/exchangedb/pg_iterate_active_auditors.h
new file mode 100644
index 000000000..f0e2808e9
--- /dev/null
+++ b/src/exchangedb/pg_iterate_active_auditors.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_iterate_active_auditors.h
+ * @brief implementation of the iterate_active_auditors function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_ACTIVE_AUDITORS_H
+#define PG_ITERATE_ACTIVE_AUDITORS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to invoke @a cb on every active auditor. Disabled
+ * auditors are skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each active auditor
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_active_auditors (void *cls,
+ TALER_EXCHANGEDB_AuditorsCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_active_signkeys.c b/src/exchangedb/pg_iterate_active_signkeys.c
new file mode 100644
index 000000000..9c280c95d
--- /dev/null
+++ b/src/exchangedb/pg_iterate_active_signkeys.c
@@ -0,0 +1,144 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_iterate_active_signkeys.c
+ * @brief Implementation of the iterate_active_signkeys function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_iterate_active_signkeys.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #signkeys_cb_helper()
+ */
+struct SignkeysIteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_ActiveSignkeysCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_active_signkeys().
+ * Calls the callback with each signkey.
+ *
+ * @param cls a `struct SignkeysIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+signkeys_cb_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct SignkeysIteratorContext *dic = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &master_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
+ &exchange_pub),
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &meta.start),
+ GNUNET_PQ_result_spec_timestamp ("expire_sign",
+ &meta.expire_sign),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &meta.expire_legal),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ dic->cb (dic->cb_cls,
+ &exchange_pub,
+ &meta,
+ &master_sig);
+ }
+}
+
+
+/**
+ * Function called to invoke @a cb on every non-revoked exchange signing key
+ * that has been signed by the master key. Revoked and (for signing!)
+ * expired keys are skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each signing key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_active_signkeys (void *cls,
+ TALER_EXCHANGEDB_ActiveSignkeysCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = {0};
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct SignkeysIteratorContext dic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ };
+
+ PREPARE (pg,
+ "select_signkeys",
+ "SELECT"
+ " master_sig"
+ ",exchange_pub"
+ ",valid_from"
+ ",expire_sign"
+ ",expire_legal"
+ " FROM exchange_sign_keys esk"
+ " WHERE"
+ " expire_sign > $1"
+ " AND NOT EXISTS "
+ " (SELECT esk_serial "
+ " FROM signkey_revocations skr"
+ " WHERE esk.esk_serial = skr.esk_serial);");
+ now = GNUNET_TIME_absolute_get ();
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_signkeys",
+ params,
+ &signkeys_cb_helper,
+ &dic);
+}
diff --git a/src/exchangedb/pg_iterate_active_signkeys.h b/src/exchangedb/pg_iterate_active_signkeys.h
new file mode 100644
index 000000000..5ebba9f5a
--- /dev/null
+++ b/src/exchangedb/pg_iterate_active_signkeys.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_iterate_active_signkeys.h
+ * @brief implementation of the iterate_active_signkeys function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_ACTIVE_SIGNKEYS_H
+#define PG_ITERATE_ACTIVE_SIGNKEYS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to invoke @a cb on every non-revoked exchange signing key
+ * that has been signed by the master key. Revoked and (for signing!)
+ * expired keys are skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each signing key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_active_signkeys (void *cls,
+ TALER_EXCHANGEDB_ActiveSignkeysCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_auditor_denominations.c b/src/exchangedb/pg_iterate_auditor_denominations.c
new file mode 100644
index 000000000..1fd4cdea6
--- /dev/null
+++ b/src/exchangedb/pg_iterate_auditor_denominations.c
@@ -0,0 +1,121 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_iterate_auditor_denominations.c
+ * @brief Implementation of the iterate_auditor_denominations function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_iterate_auditor_denominations.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #auditor_denoms_cb_helper()
+ */
+struct AuditorDenomsIteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_AuditorDenominationsCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_auditor_denominations().
+ * Calls the callback with each auditor and denomination pair.
+ *
+ * @param cls a `struct AuditorDenomsIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+auditor_denoms_cb_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AuditorDenomsIteratorContext *dic = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_AuditorPublicKeyP auditor_pub;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_AuditorSignatureP auditor_sig;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("auditor_pub",
+ &auditor_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &h_denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("auditor_sig",
+ &auditor_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ dic->cb (dic->cb_cls,
+ &auditor_pub,
+ &h_denom_pub,
+ &auditor_sig);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_auditor_denominations (
+ void *cls,
+ TALER_EXCHANGEDB_AuditorDenominationsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct AuditorDenomsIteratorContext dic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ };
+ /* Used in #postgres_iterate_auditor_denominations() */
+ PREPARE (pg,
+ "select_auditor_denoms",
+ "SELECT"
+ " auditors.auditor_pub"
+ ",denominations.denom_pub_hash"
+ ",auditor_denom_sigs.auditor_sig"
+ " FROM auditor_denom_sigs"
+ " JOIN auditors USING (auditor_uuid)"
+ " JOIN denominations USING (denominations_serial)"
+ " WHERE auditors.is_active;");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_auditor_denoms",
+ params,
+ &auditor_denoms_cb_helper,
+ &dic);
+}
diff --git a/src/exchangedb/pg_iterate_auditor_denominations.h b/src/exchangedb/pg_iterate_auditor_denominations.h
new file mode 100644
index 000000000..1278e8a9f
--- /dev/null
+++ b/src/exchangedb/pg_iterate_auditor_denominations.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_iterate_auditor_denominations.h
+ * @brief implementation of the iterate_auditor_denominations function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_AUDITOR_DENOMINATIONS_H
+#define PG_ITERATE_AUDITOR_DENOMINATIONS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to invoke @a cb on every denomination with an active
+ * auditor. Disabled auditors and denominations without auditor are
+ * skipped. Runs in its own read-only transaction.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each active auditor
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_auditor_denominations (
+ void *cls,
+ TALER_EXCHANGEDB_AuditorDenominationsCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_denomination_info.c b/src/exchangedb/pg_iterate_denomination_info.c
new file mode 100644
index 000000000..cab51d5ce
--- /dev/null
+++ b/src/exchangedb/pg_iterate_denomination_info.c
@@ -0,0 +1,180 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_iterate_denomination_info.c
+ * @brief Implementation of the iterate_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_iterate_denomination_info.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #domination_cb_helper()
+ */
+struct DenomIteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_DenominationCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_denomination_info().
+ * Calls the callback with each denomination key.
+ *
+ * @param cls a `struct DenomIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+domination_cb_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct DenomIteratorContext *dic = cls;
+ struct PostgresClosure *pg = dic->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_DenominationHashP denom_hash;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &issue.signature),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &denom_hash),
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &issue.start),
+ GNUNET_PQ_result_spec_timestamp ("expire_withdraw",
+ &issue.expire_withdraw),
+ GNUNET_PQ_result_spec_timestamp ("expire_deposit",
+ &issue.expire_deposit),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &issue.expire_legal),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
+ &issue.value),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+ &issue.fees.withdraw),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &issue.fees.deposit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+ &issue.fees.refresh),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+ &issue.fees.refund),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_uint32 ("age_mask",
+ &issue.age_mask.bits),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+
+ /* Unfortunately we have to carry the age mask in both, the
+ * TALER_DenominationPublicKey and
+ * TALER_EXCHANGEDB_DenominationKeyInformation at different times.
+ * Here we use _both_ so let's make sure the values are the same. */
+ denom_pub.age_mask = issue.age_mask;
+ TALER_denom_pub_hash (&denom_pub,
+ &issue.denom_hash);
+ if (0 !=
+ GNUNET_memcmp (&issue.denom_hash,
+ &denom_hash))
+ {
+ GNUNET_break (0);
+ }
+ else
+ {
+ dic->cb (dic->cb_cls,
+ &denom_pub,
+ &issue);
+ }
+ TALER_denom_pub_free (&denom_pub);
+ }
+}
+
+
+/**
+ * Fetch information about all known denomination keys.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each denomination key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_denomination_info (void *cls,
+ TALER_EXCHANGEDB_DenominationCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct DenomIteratorContext dic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+
+ PREPARE (pg,
+ "denomination_iterate",
+ "SELECT"
+ " master_sig"
+ ",denom_pub_hash"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin" /* value of this denom */
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",denom_pub"
+ ",age_mask"
+ " FROM denominations;");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "denomination_iterate",
+ params,
+ &domination_cb_helper,
+ &dic);
+}
diff --git a/src/exchangedb/pg_iterate_denomination_info.h b/src/exchangedb/pg_iterate_denomination_info.h
new file mode 100644
index 000000000..27c08d0a9
--- /dev/null
+++ b/src/exchangedb/pg_iterate_denomination_info.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_iterate_denomination_info.h
+ * @brief implementation of the iterate_denomination_info function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_DENOMINATION_INFO_H
+#define PG_ITERATE_DENOMINATION_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Fetch information about all known denomination keys.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each denomination key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_denomination_info (void *cls,
+ TALER_EXCHANGEDB_DenominationCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_denominations.c b/src/exchangedb/pg_iterate_denominations.c
new file mode 100644
index 000000000..684aa165a
--- /dev/null
+++ b/src/exchangedb/pg_iterate_denominations.c
@@ -0,0 +1,172 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_iterate_denominations.c
+ * @brief Implementation of the iterate_denominations function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_iterate_denominations.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #dominations_cb_helper()
+ */
+struct DenomsIteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_DenominationsCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_denominations().
+ * Calls the callback with each denomination key.
+ *
+ * @param cls a `struct DenomsIteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+dominations_cb_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct DenomsIteratorContext *dic = cls;
+ struct PostgresClosure *pg = dic->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};
+ struct TALER_DenominationPublicKey denom_pub = {0};
+ struct TALER_MasterSignatureP master_sig = {0};
+ struct TALER_DenominationHashP h_denom_pub = {0};
+ bool revoked;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("denominations_serial",
+ &meta.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &master_sig),
+ GNUNET_PQ_result_spec_bool ("revoked",
+ &revoked),
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &meta.start),
+ GNUNET_PQ_result_spec_timestamp ("expire_withdraw",
+ &meta.expire_withdraw),
+ GNUNET_PQ_result_spec_timestamp ("expire_deposit",
+ &meta.expire_deposit),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &meta.expire_legal),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
+ &meta.value),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+ &meta.fees.withdraw),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &meta.fees.deposit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+ &meta.fees.refresh),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+ &meta.fees.refund),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_uint32 ("age_mask",
+ &meta.age_mask.bits),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+
+ /* make sure the mask information is the same */
+ denom_pub.age_mask = meta.age_mask;
+
+ TALER_denom_pub_hash (&denom_pub,
+ &h_denom_pub);
+ dic->cb (dic->cb_cls,
+ &denom_pub,
+ &h_denom_pub,
+ &meta,
+ &master_sig,
+ revoked);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_denominations (void *cls,
+ TALER_EXCHANGEDB_DenominationsCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct DenomsIteratorContext dic = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg
+ };
+
+ PREPARE (pg,
+ "select_denominations",
+ "SELECT"
+ " denominations_serial"
+ ",denominations.master_sig"
+ ",denom_revocations_serial_id IS NOT NULL AS revoked"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin" /* value of this denom */
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",denom_type"
+ ",age_mask"
+ ",denom_pub"
+ " FROM denominations"
+ " LEFT JOIN "
+ " denomination_revocations USING (denominations_serial);");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_denominations",
+ params,
+ &dominations_cb_helper,
+ &dic);
+}
diff --git a/src/exchangedb/pg_iterate_denominations.h b/src/exchangedb/pg_iterate_denominations.h
new file mode 100644
index 000000000..9f59fc803
--- /dev/null
+++ b/src/exchangedb/pg_iterate_denominations.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_iterate_denominations.h
+ * @brief implementation of the iterate_denominations function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_DENOMINATIONS_H
+#define PG_ITERATE_DENOMINATIONS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to invoke @a cb on every known denomination key (revoked
+ * and non-revoked) that has been signed by the master key. Runs in its own
+ * read-only transaction.
+ *
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param cb function to call on each denomination key
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_denominations (void *cls,
+ TALER_EXCHANGEDB_DenominationsCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_kyc_reference.c b/src/exchangedb/pg_iterate_kyc_reference.c
new file mode 100644
index 000000000..772c51e2c
--- /dev/null
+++ b/src/exchangedb/pg_iterate_kyc_reference.c
@@ -0,0 +1,129 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_iterate_kyc_reference.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_iterate_kyc_reference.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #iterate_kyc_reference_cb()
+ */
+struct IteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_LegitimizationProcessCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_kyc_reference().
+ * Calls the callback with each denomination key.
+ *
+ * @param cls a `struct IteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+iterate_kyc_reference_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct IteratorContext *ic = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ char *kyc_provider_section_name;
+ char *provider_user_id;
+ char *legitimization_id;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("provider_section",
+ &kyc_provider_section_name),
+ GNUNET_PQ_result_spec_string ("provider_user_id",
+ &provider_user_id),
+ GNUNET_PQ_result_spec_string ("provider_legitimization_id",
+ &legitimization_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ ic->cb (ic->cb_cls,
+ kyc_provider_section_name,
+ provider_user_id,
+ legitimization_id);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_kyc_reference (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_LegitimizationProcessCallback lpc,
+ void *lpc_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ struct IteratorContext ic = {
+ .cb = lpc,
+ .cb_cls = lpc_cls,
+ .pg = pg
+ };
+
+ PREPARE (pg,
+ "iterate_kyc_reference",
+ "SELECT "
+ " provider_section"
+ ",provider_user_id"
+ ",provider_legitimization_id"
+ " FROM legitimization_processes"
+ " WHERE h_payto=$1;");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "iterate_kyc_reference",
+ params,
+ &iterate_kyc_reference_cb,
+ &ic);
+}
diff --git a/src/exchangedb/pg_iterate_kyc_reference.h b/src/exchangedb/pg_iterate_kyc_reference.h
new file mode 100644
index 000000000..0242fdcf1
--- /dev/null
+++ b/src/exchangedb/pg_iterate_kyc_reference.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_iterate_kyc_reference.h
+ * @brief implementation of the iterate_kyc_reference function
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_KYC_REFERENCE_H
+#define PG_ITERATE_KYC_REFERENCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Call us on KYC legitimization processes satisfied and not expired for the
+ * given account.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param lpc function to call for each satisfied KYC legitimization process
+ * @param lpc_cls closure for @a lpc
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_kyc_reference (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_LegitimizationProcessCallback lpc,
+ void *lpc_cls);
+
+#endif
diff --git a/src/exchangedb/pg_iterate_reserve_close_info.c b/src/exchangedb/pg_iterate_reserve_close_info.c
new file mode 100644
index 000000000..ff0a813c3
--- /dev/null
+++ b/src/exchangedb/pg_iterate_reserve_close_info.c
@@ -0,0 +1,128 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_iterate_reserve_close_info.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_insert_reserve_open_deposit.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #iterate_reserve_close_info_cb()
+ */
+struct IteratorContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_KycAmountCallback cb;
+
+ /**
+ * Closure to pass to @e cb
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_iterate_reserve_close_info().
+ * Calls the callback with each denomination key.
+ *
+ * @param cls a `struct IteratorContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+iterate_reserve_close_info_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct IteratorContext *ic = cls;
+ struct PostgresClosure *pg = ic->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount amount;
+ struct GNUNET_TIME_Absolute ts;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("execution_date",
+ &ts),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ return;
+ }
+ ic->cb (ic->cb_cls,
+ &amount,
+ ts);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_reserve_close_info (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&time_limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct IteratorContext ic = {
+ .cb = kac,
+ .cb_cls = kac_cls,
+ .pg = pg
+ };
+
+ PREPARE (pg,
+ "iterate_reserve_close_info",
+ "SELECT"
+ " amount"
+ ",execution_date"
+ " FROM reserves_close"
+ " WHERE wire_target_h_payto=$1"
+ " AND execution_date >= $2"
+ " ORDER BY execution_date DESC");
+ return GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "iterate_reserve_close_info",
+ params,
+ &iterate_reserve_close_info_cb,
+ &ic);
+}
diff --git a/src/exchangedb/pg_iterate_reserve_close_info.h b/src/exchangedb/pg_iterate_reserve_close_info.h
new file mode 100644
index 000000000..34692e656
--- /dev/null
+++ b/src/exchangedb/pg_iterate_reserve_close_info.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_iterate_reserve_close_info.h
+ * @brief implementation of the iterate_reserve_close_info function
+ * @author Christian Grothoff
+ */
+#ifndef PG_ITERATE_RESERVE_CLOSE_INFO_H
+#define PG_ITERATE_RESERVE_CLOSE_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select information needed for KYC checks on reserve close: historic
+ * reserve closures going to the same account.
+ *
+ * @param cls closure
+ * @param h_payto which target account is this about?
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_iterate_reserve_close_info (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_kyc_provider_account_lookup.c b/src/exchangedb/pg_kyc_provider_account_lookup.c
new file mode 100644
index 000000000..f9db2cbc1
--- /dev/null
+++ b/src/exchangedb/pg_kyc_provider_account_lookup.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_kyc_provider_account_lookup.c
+ * @brief Implementation of the kyc_provider_account_lookup function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_kyc_provider_account_lookup.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_kyc_provider_account_lookup (
+ void *cls,
+ const char *provider_section,
+ const char *provider_legitimization_id,
+ struct TALER_PaytoHashP *h_payto,
+ uint64_t *process_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (provider_legitimization_id),
+ GNUNET_PQ_query_param_string (provider_section),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_payto",
+ h_payto),
+ GNUNET_PQ_result_spec_uint64 ("legitimization_process_serial_id",
+ process_row),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_wire_target_by_legitimization_id",
+ "SELECT "
+ " h_payto"
+ ",legitimization_process_serial_id"
+ " FROM legitimization_processes"
+ " WHERE provider_legitimization_id=$1"
+ " AND provider_section=$2;");
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "get_wire_target_by_legitimization_id",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_kyc_provider_account_lookup.h b/src/exchangedb/pg_kyc_provider_account_lookup.h
new file mode 100644
index 000000000..74f90d88d
--- /dev/null
+++ b/src/exchangedb/pg_kyc_provider_account_lookup.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_kyc_provider_account_lookup.h
+ * @brief implementation of the kyc_provider_account_lookup function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_KYC_PROVIDER_ACCOUNT_LOOKUP_H
+#define PG_KYC_PROVIDER_ACCOUNT_LOOKUP_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup an
+ * @a h_payto by @a provider_legitimization_id.
+ *
+ * @param cls closure
+ * @param provider_section
+ * @param provider_legitimization_id legi to look up
+ * @param[out] h_payto where to write the result
+ * @param[out] process_row where to write the row of the entry
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_kyc_provider_account_lookup (
+ void *cls,
+ const char *provider_section,
+ const char *provider_legitimization_id,
+ struct TALER_PaytoHashP *h_payto,
+ uint64_t *process_row);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_aml_officer.c b/src/exchangedb/pg_lookup_aml_officer.c
new file mode 100644
index 000000000..c18e47eaf
--- /dev/null
+++ b/src/exchangedb/pg_lookup_aml_officer.c
@@ -0,0 +1,71 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_aml_officer.c
+ * @brief Implementation of the lookup_aml_officer function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_aml_officer.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_aml_officer (
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ struct TALER_MasterSignatureP *master_sig,
+ char **decider_name,
+ bool *is_active,
+ bool *read_only,
+ struct GNUNET_TIME_Absolute *last_change)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (decider_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_string ("decider_name",
+ decider_name),
+ GNUNET_PQ_result_spec_bool ("is_active",
+ is_active),
+ GNUNET_PQ_result_spec_bool ("read_only",
+ read_only),
+ GNUNET_PQ_result_spec_absolute_time ("last_change",
+ last_change),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "lookup_aml_officer",
+ "SELECT "
+ " master_sig"
+ ",decider_name"
+ ",is_active"
+ ",read_only"
+ ",last_change"
+ " FROM aml_staff"
+ " WHERE decider_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_aml_officer",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_aml_officer.h b/src/exchangedb/pg_lookup_aml_officer.h
new file mode 100644
index 000000000..161d2e7e7
--- /dev/null
+++ b/src/exchangedb/pg_lookup_aml_officer.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_aml_officer.h
+ * @brief implementation of the lookup_aml_officer function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_AML_OFFICER_H
+#define PG_LOOKUP_AML_OFFICER_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Fetch AML staff record.
+ *
+ * @param cls closure
+ * @param decider_pub public key of the staff member
+ * @param[out] master_sig offline signature affirming the AML officer
+ * @param[out] decider_name full name of the staff member
+ * @param[out] is_active true to enable, false to set as inactive
+ * @param[out] read_only true to set read-only access
+ * @param[out] last_change when was the change made effective
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_aml_officer (
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ struct TALER_MasterSignatureP *master_sig,
+ char **decider_name,
+ bool *is_active,
+ bool *read_only,
+ struct GNUNET_TIME_Absolute *last_change);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_auditor_status.c b/src/exchangedb/pg_lookup_auditor_status.c
new file mode 100644
index 000000000..91afe6eaa
--- /dev/null
+++ b/src/exchangedb/pg_lookup_auditor_status.c
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_auditor_status.c
+ * @brief Implementation of the lookup_auditor_status function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_auditor_status.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_auditor_status (
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ char **auditor_url,
+ bool *enabled)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (auditor_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("auditor_url",
+ auditor_url),
+ GNUNET_PQ_result_spec_bool ("is_active",
+ enabled),
+ GNUNET_PQ_result_spec_end
+ };
+
+ /* Used in #postgres_lookup_auditor_status() */
+ PREPARE (pg,
+ "lookup_auditor_status",
+ "SELECT"
+ " auditor_url"
+ ",is_active"
+ " FROM auditors"
+ " WHERE auditor_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_auditor_status",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_auditor_status.h b/src/exchangedb/pg_lookup_auditor_status.h
new file mode 100644
index 000000000..b75788e11
--- /dev/null
+++ b/src/exchangedb/pg_lookup_auditor_status.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_auditor_status.h
+ * @brief implementation of the lookup_auditor_status function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_AUDITOR_STATUS_H
+#define PG_LOOKUP_AUDITOR_STATUS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Lookup current state of an auditor.
+ *
+ * @param cls closure
+ * @param auditor_pub key to look up information for
+ * @param[out] auditor_url set to the base URL of the auditor's REST API; memory to be
+ * released by the caller!
+ * @param[out] enabled set if the auditor is currently in use
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_auditor_status (
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ char **auditor_url,
+ bool *enabled);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_auditor_timestamp.c b/src/exchangedb/pg_lookup_auditor_timestamp.c
new file mode 100644
index 000000000..eb85876fe
--- /dev/null
+++ b/src/exchangedb/pg_lookup_auditor_timestamp.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_auditor_timestamp.c
+ * @brief Implementation of the lookup_auditor_timestamp function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_auditor_timestamp.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_auditor_timestamp (
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp *last_date)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (auditor_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("last_change",
+ last_date),
+ GNUNET_PQ_result_spec_end
+ };
+
+ /* Used in #postgres_lookup_auditor_timestamp() */
+ PREPARE (pg,
+ "lookup_auditor_timestamp",
+ "SELECT"
+ " last_change"
+ " FROM auditors"
+ " WHERE auditor_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_auditor_timestamp",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_auditor_timestamp.h b/src/exchangedb/pg_lookup_auditor_timestamp.h
new file mode 100644
index 000000000..5674ba225
--- /dev/null
+++ b/src/exchangedb/pg_lookup_auditor_timestamp.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_auditor_timestamp.h
+ * @brief implementation of the lookup_auditor_timestamp function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_AUDITOR_TIMESTAMP_H
+#define PG_LOOKUP_AUDITOR_TIMESTAMP_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Check the last date an auditor was modified.
+ *
+ * @param cls closure
+ * @param auditor_pub key to look up information for
+ * @param[out] last_date last modification date to auditor status
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_auditor_timestamp (
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct GNUNET_TIME_Timestamp *last_date);
+#endif
diff --git a/src/exchangedb/pg_lookup_denomination_key.c b/src/exchangedb/pg_lookup_denomination_key.c
new file mode 100644
index 000000000..a358528ad
--- /dev/null
+++ b/src/exchangedb/pg_lookup_denomination_key.c
@@ -0,0 +1,82 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_denomination_key.c
+ * @brief Implementation of the lookup_denomination_key function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_denomination_key.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_denomination_key (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &meta->start),
+ GNUNET_PQ_result_spec_timestamp ("expire_withdraw",
+ &meta->expire_withdraw),
+ GNUNET_PQ_result_spec_timestamp ("expire_deposit",
+ &meta->expire_deposit),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &meta->expire_legal),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
+ &meta->value),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
+ &meta->fees.withdraw),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &meta->fees.deposit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
+ &meta->fees.refresh),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
+ &meta->fees.refund),
+ GNUNET_PQ_result_spec_uint32 ("age_mask",
+ &meta->age_mask.bits),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "lookup_denomination_key",
+ "SELECT"
+ " valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin"
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",age_mask"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_denomination_key",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_denomination_key.h b/src/exchangedb/pg_lookup_denomination_key.h
new file mode 100644
index 000000000..b7317ac4a
--- /dev/null
+++ b/src/exchangedb/pg_lookup_denomination_key.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_denomination_key.h
+ * @brief implementation of the lookup_denomination_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_DENOMINATION_KEY_H
+#define PG_LOOKUP_DENOMINATION_KEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Lookup information about current denomination key.
+ *
+ * @param cls closure
+ * @param h_denom_pub hash of the denomination public key
+ * @param[out] meta set to various meta data about the key
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_denomination_key (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_global_fee_by_time.c b/src/exchangedb/pg_lookup_global_fee_by_time.c
new file mode 100644
index 000000000..c3a6ec8b7
--- /dev/null
+++ b/src/exchangedb/pg_lookup_global_fee_by_time.c
@@ -0,0 +1,182 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_global_fee_by_time.c
+ * @brief Implementation of the lookup_global_fee_by_time function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_global_fee_by_time.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #global_fee_by_time_helper()
+ */
+struct GlobalFeeLookupContext
+{
+
+ /**
+ * Set to the wire fees. Set to invalid if fees conflict over
+ * the given time period.
+ */
+ struct TALER_GlobalFeeSet *fees;
+
+ /**
+ * Set to timeout of unmerged purses
+ */
+ struct GNUNET_TIME_Relative *purse_timeout;
+
+ /**
+ * Set to history expiration for reserves.
+ */
+ struct GNUNET_TIME_Relative *history_expiration;
+
+ /**
+ * Set to number of free purses per account.
+ */
+ uint32_t *purse_account_limit;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_lookup_global_fee_by_time().
+ * Calls the callback with each denomination key.
+ *
+ * @param cls a `struct GlobalFeeLookupContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+global_fee_by_time_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GlobalFeeLookupContext *wlc = cls;
+ struct PostgresClosure *pg = wlc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_GlobalFeeSet fs;
+ struct GNUNET_TIME_Relative purse_timeout;
+ struct GNUNET_TIME_Relative history_expiration;
+ uint32_t purse_account_limit;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("history_fee",
+ &fs.history),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("account_fee",
+ &fs.account),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
+ &fs.purse),
+ GNUNET_PQ_result_spec_relative_time ("purse_timeout",
+ &purse_timeout),
+ GNUNET_PQ_result_spec_relative_time ("history_expiration",
+ &history_expiration),
+ GNUNET_PQ_result_spec_uint32 ("purse_account_limit",
+ &purse_account_limit),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ /* invalidate */
+ memset (wlc->fees,
+ 0,
+ sizeof (struct TALER_GlobalFeeSet));
+ return;
+ }
+ if (0 == i)
+ {
+ *wlc->fees = fs;
+ *wlc->purse_timeout = purse_timeout;
+ *wlc->history_expiration = history_expiration;
+ *wlc->purse_account_limit = purse_account_limit;
+ continue;
+ }
+ if ( (0 !=
+ TALER_global_fee_set_cmp (&fs,
+ wlc->fees)) ||
+ (purse_account_limit != *wlc->purse_account_limit) ||
+ (GNUNET_TIME_relative_cmp (purse_timeout,
+ !=,
+ *wlc->purse_timeout)) ||
+ (GNUNET_TIME_relative_cmp (history_expiration,
+ !=,
+ *wlc->history_expiration)) )
+ {
+ /* invalidate */
+ memset (wlc->fees,
+ 0,
+ sizeof (struct TALER_GlobalFeeSet));
+ return;
+ }
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_global_fee_by_time (
+ void *cls,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative *purse_timeout,
+ struct GNUNET_TIME_Relative *history_expiration,
+ uint32_t *purse_account_limit)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&start_time),
+ GNUNET_PQ_query_param_timestamp (&end_time),
+ GNUNET_PQ_query_param_end
+ };
+ struct GlobalFeeLookupContext wlc = {
+ .fees = fees,
+ .purse_timeout = purse_timeout,
+ .history_expiration = history_expiration,
+ .purse_account_limit = purse_account_limit,
+ .pg = pg
+ };
+
+ PREPARE (pg,
+ "lookup_global_fee_by_time",
+ "SELECT"
+ " history_fee"
+ ",account_fee"
+ ",purse_fee"
+ ",purse_timeout"
+ ",history_expiration"
+ ",purse_account_limit"
+ " FROM global_fee"
+ " WHERE end_date > $1"
+ " AND start_date < $2;");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_global_fee_by_time",
+ params,
+ &global_fee_by_time_helper,
+ &wlc);
+}
diff --git a/src/exchangedb/pg_lookup_global_fee_by_time.h b/src/exchangedb/pg_lookup_global_fee_by_time.h
new file mode 100644
index 000000000..c5ff95fc6
--- /dev/null
+++ b/src/exchangedb/pg_lookup_global_fee_by_time.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_global_fee_by_time.h
+ * @brief implementation of the lookup_global_fee_by_time function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_GLOBAL_FEE_BY_TIME_H
+#define PG_LOOKUP_GLOBAL_FEE_BY_TIME_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Lookup information about known global fees.
+ *
+ * @param cls closure
+ * @param start_time starting time of fee
+ * @param end_time end time of fee
+ * @param[out] fees set to wire fees for that time period; if
+ * different global fee exists within this time
+ * period, an 'invalid' amount is returned.
+ * @param[out] purse_timeout set to when unmerged purses expire
+ * @param[out] history_expiration set to when we expire reserve histories
+ * @param[out] purse_account_limit set to number of free purses
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_global_fee_by_time (
+ void *cls,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative *purse_timeout,
+ struct GNUNET_TIME_Relative *history_expiration,
+ uint32_t *purse_account_limit);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_kyc_process_by_account.c b/src/exchangedb/pg_lookup_kyc_process_by_account.c
new file mode 100644
index 000000000..b077661c5
--- /dev/null
+++ b/src/exchangedb/pg_lookup_kyc_process_by_account.c
@@ -0,0 +1,83 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_kyc_process_by_account.c
+ * @brief Implementation of the lookup_kyc_process_by_account function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_kyc_process_by_account.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_kyc_process_by_account (
+ void *cls,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ uint64_t *process_row,
+ struct GNUNET_TIME_Absolute *expiration,
+ char **provider_account_id,
+ char **provider_legitimization_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_string (provider_section),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("legitimization_process_serial_id",
+ process_row),
+ GNUNET_PQ_result_spec_absolute_time ("expiration_time",
+ expiration),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("provider_user_id",
+ provider_account_id),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("provider_legitimization_id",
+ provider_legitimization_id),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ *provider_account_id = NULL;
+ *provider_legitimization_id = NULL;
+ PREPARE (pg,
+ "lookup_process_by_account",
+ "SELECT "
+ " legitimization_process_serial_id"
+ ",expiration_time"
+ ",provider_user_id"
+ ",provider_legitimization_id"
+ " FROM legitimization_processes"
+ " WHERE h_payto=$1"
+ " AND provider_section=$2"
+ " AND NOT finished"
+ /* Note: there *should* only be one unfinished
+ match, so this is just to be safe(r): */
+ " ORDER BY expiration_time DESC"
+ " LIMIT 1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "lookup_process_by_account",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_kyc_process_by_account.h b/src/exchangedb/pg_lookup_kyc_process_by_account.h
new file mode 100644
index 000000000..0300b498c
--- /dev/null
+++ b/src/exchangedb/pg_lookup_kyc_process_by_account.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_kyc_process_by_account.h
+ * @brief implementation of the lookup_kyc_process_by_account function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_KYC_PROCESS_BY_ACCOUNT_H
+#define PG_LOOKUP_KYC_PROCESS_BY_ACCOUNT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup KYC provider meta data.
+ *
+ * @param cls closure
+ * @param provider_section provider that must be checked
+ * @param h_payto account that must be KYC'ed
+ * @param[out] process_row row with the legitimization data
+ * @param[out] expiration how long is this KYC check set to be valid (in the past if invalid)
+ * @param[out] provider_account_id provider account ID
+ * @param[out] provider_legitimization_id provider legitimization ID
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_kyc_process_by_account (
+ void *cls,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ uint64_t *process_row,
+ struct GNUNET_TIME_Absolute *expiration,
+ char **provider_account_id,
+ char **provider_legitimization_id);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_kyc_requirement_by_row.c b/src/exchangedb/pg_lookup_kyc_requirement_by_row.c
new file mode 100644
index 000000000..6f9d76786
--- /dev/null
+++ b/src/exchangedb/pg_lookup_kyc_requirement_by_row.c
@@ -0,0 +1,71 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_kyc_requirement_by_row.c
+ * @brief Implementation of the lookup_kyc_requirement_by_row function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_kyc_requirement_by_row.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_kyc_requirement_by_row (
+ void *cls,
+ uint64_t requirement_row,
+ char **requirements,
+ enum TALER_AmlDecisionState *aml_status,
+ struct TALER_PaytoHashP *h_payto)
+{
+ struct PostgresClosure *pg = cls;
+ uint32_t status = TALER_AML_NORMAL;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&requirement_row),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("required_checks",
+ requirements),
+ GNUNET_PQ_result_spec_auto_from_type ("h_payto",
+ h_payto),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("status",
+ &status),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "lookup_legitimization_requirement_by_row",
+ "SELECT "
+ " lr.required_checks"
+ ",lr.h_payto"
+ ",aml.status"
+ " FROM legitimization_requirements lr"
+ " LEFT JOIN aml_status aml USING (h_payto)"
+ " WHERE legitimization_requirement_serial_id=$1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "lookup_legitimization_requirement_by_row",
+ params,
+ rs);
+ *aml_status = (enum TALER_AmlDecisionState) status;
+ return qs;
+}
diff --git a/src/exchangedb/pg_lookup_kyc_requirement_by_row.h b/src/exchangedb/pg_lookup_kyc_requirement_by_row.h
new file mode 100644
index 000000000..3d223c985
--- /dev/null
+++ b/src/exchangedb/pg_lookup_kyc_requirement_by_row.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_kyc_requirement_by_row.h
+ * @brief implementation of the lookup_kyc_requirement_by_row function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_KYC_REQUIREMENT_BY_ROW_H
+#define PG_LOOKUP_KYC_REQUIREMENT_BY_ROW_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup KYC requirement.
+ *
+ * @param cls closure
+ * @param requirement_row identifies requirement to look up
+ * @param[out] requirements provider that must be checked
+ * @param[out] aml_status set to the AML status of the account
+ * @param[out] h_payto account that must be KYC'ed
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_kyc_requirement_by_row (
+ void *cls,
+ uint64_t requirement_row,
+ char **requirements,
+ enum TALER_AmlDecisionState *aml_status,
+ struct TALER_PaytoHashP *h_payto);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_records_by_table.c b/src/exchangedb/pg_lookup_records_by_table.c
new file mode 100644
index 000000000..fc4af32a8
--- /dev/null
+++ b/src/exchangedb/pg_lookup_records_by_table.c
@@ -0,0 +1,3607 @@
+/*
+ This file is part of GNUnet
+ Copyright (C) 2020-2023 Taler Systems SA
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/**
+ * @file exchangedb/pg_lookup_records_by_table.c
+ * @brief implementation of lookup_records_by_table
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_records_by_table.h"
+#include "pg_helper.h"
+#include <gnunet/gnunet_pq_lib.h>
+
+
+/**
+ * Closure for callbacks used by #postgres_lookup_records_by_table.
+ */
+struct LookupRecordsByTableContext
+{
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Function to call with the results.
+ */
+ TALER_EXCHANGEDB_ReplicationCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Set to true on errors.
+ */
+ bool error;
+};
+
+
+/**
+ * Function called with denominations table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_denominations (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_DENOMINATIONS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint32 (
+ "denom_type",
+ &td.details.denominations.denom_type),
+ GNUNET_PQ_result_spec_uint32 (
+ "age_mask",
+ &td.details.denominations.age_mask),
+ TALER_PQ_result_spec_denom_pub (
+ "denom_pub",
+ &td.details.denominations.denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "master_sig",
+ &td.details.denominations.master_sig),
+ GNUNET_PQ_result_spec_timestamp (
+ "valid_from",
+ &td.details.denominations.valid_from),
+ GNUNET_PQ_result_spec_timestamp (
+ "expire_withdraw",
+ &td.details.denominations.
+ expire_withdraw),
+ GNUNET_PQ_result_spec_timestamp (
+ "expire_deposit",
+ &td.details.denominations.
+ expire_deposit),
+ GNUNET_PQ_result_spec_timestamp (
+ "expire_legal",
+ &td.details.denominations.expire_legal),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "coin",
+ &td.details.denominations.coin),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "fee_withdraw",
+ &td.details.denominations.fees.withdraw),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "fee_deposit",
+ &td.details.denominations.fees.deposit),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "fee_refresh",
+ &td.details.denominations.fees.refresh),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "fee_refund",
+ &td.details.denominations.fees.refund),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with denomination_revocations table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_denomination_revocations (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "denominations_serial",
+ &td.details.denomination_revocations.denominations_serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "master_sig",
+ &td.details.denomination_revocations.master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wire_targets table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wire_targets (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WIRE_TARGETS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &td.details.wire_targets.payto_uri),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with legitimization_processes table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_legitimization_processes (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_LEGITIMIZATION_PROCESSES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_payto",
+ &td.details.legitimization_processes.h_payto),
+ GNUNET_PQ_result_spec_timestamp (
+ "expiration_time",
+ &td.details.legitimization_processes.expiration_time),
+ GNUNET_PQ_result_spec_string (
+ "provider_section",
+ &td.details.legitimization_processes.provider_section),
+ GNUNET_PQ_result_spec_string (
+ "provider_user_id",
+ &td.details.legitimization_processes.provider_user_id),
+ GNUNET_PQ_result_spec_string (
+ "provider_legitimization_id",
+ &td.details.legitimization_processes.provider_legitimization_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with legitimization_requirements table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_legitimization_requirements (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_LEGITIMIZATION_REQUIREMENTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_payto",
+ &td.details.legitimization_requirements.h_payto),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.legitimization_requirements.reserve_pub),
+ &td.details.legitimization_requirements.no_reserve_pub),
+ GNUNET_PQ_result_spec_string (
+ "required_checks",
+ &td.details.legitimization_requirements.required_checks),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with reserves table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_reserves (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RESERVES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &td.details.reserves.reserve_pub),
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &td.details.reserves.expiration_date),
+ GNUNET_PQ_result_spec_timestamp ("gc_date",
+ &td.details.reserves.gc_date),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with reserves_in table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_reserves_in (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RESERVES_IN
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.reserves_in.reserve_pub),
+ GNUNET_PQ_result_spec_uint64 (
+ "wire_reference",
+ &td.details.reserves_in.wire_reference),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "credit",
+ &td.details.reserves_in.credit),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wire_source_h_payto",
+ &td.details.reserves_in.sender_account_h_payto),
+ GNUNET_PQ_result_spec_string (
+ "exchange_account_section",
+ &td.details.reserves_in.exchange_account_section),
+ GNUNET_PQ_result_spec_timestamp (
+ "execution_date",
+ &td.details.reserves_in.execution_date),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with reserves_close table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_reserves_close (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RESERVES_CLOSE
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.reserves_close.reserve_pub),
+ GNUNET_PQ_result_spec_timestamp (
+ "execution_date",
+ &td.details.reserves_close.execution_date),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wtid",
+ &td.details.reserves_close.wtid),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wire_target_h_payto",
+ &td.details.reserves_close.sender_account_h_payto),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount",
+ &td.details.reserves_close.amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "closing_fee",
+ &td.details.reserves_close.closing_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with reserves_open_requests table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_reserves_open_requests (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RESERVES_OPEN_REQUESTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.reserves_open_requests.reserve_pub),
+ GNUNET_PQ_result_spec_timestamp (
+ "request_timestamp",
+ &td.details.reserves_open_requests.request_timestamp),
+ GNUNET_PQ_result_spec_timestamp (
+ "expiration_date",
+ &td.details.reserves_open_requests.expiration_date),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.reserves_open_requests.reserve_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "reserve_payment",
+ &td.details.reserves_open_requests.reserve_payment),
+ GNUNET_PQ_result_spec_uint32 (
+ "requested_purse_limit",
+ &td.details.reserves_open_requests.requested_purse_limit),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with reserves_open_deposits table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_reserves_open_deposits (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RESERVES_OPEN_DEPOSITS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.reserves_open_deposits.reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.reserves_open_deposits.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.reserves_open_deposits.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_sig",
+ &td.details.reserves_open_deposits.coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "contribution",
+ &td.details.reserves_open_deposits.contribution),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with reserves_out table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_reserves_out (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RESERVES_OUT
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_blind_ev",
+ &td.details.reserves_out.h_blind_ev),
+ GNUNET_PQ_result_spec_uint64 (
+ "denominations_serial",
+ &td.details.reserves_out.denominations_serial),
+ TALER_PQ_result_spec_blinded_denom_sig (
+ "denom_sig",
+ &td.details.reserves_out.denom_sig),
+ GNUNET_PQ_result_spec_uint64 (
+ "reserve_uuid",
+ &td.details.reserves_out.reserve_uuid),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.reserves_out.reserve_sig),
+ GNUNET_PQ_result_spec_timestamp (
+ "execution_date",
+ &td.details.reserves_out.execution_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.reserves_out.amount_with_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with auditors table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_auditors (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_AUDITORS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("auditor_pub",
+ &td.details.auditors.auditor_pub),
+ GNUNET_PQ_result_spec_string ("auditor_url",
+ &td.details.auditors.auditor_url),
+ GNUNET_PQ_result_spec_string ("auditor_name",
+ &td.details.auditors.auditor_name),
+ GNUNET_PQ_result_spec_bool ("is_active",
+ &td.details.auditors.is_active),
+ GNUNET_PQ_result_spec_timestamp ("last_change",
+ &td.details.auditors.last_change),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with auditor_denom_sigs table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_auditor_denom_sigs (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "auditor_uuid",
+ &td.details.auditor_denom_sigs.auditor_uuid),
+ GNUNET_PQ_result_spec_uint64 (
+ "denominations_serial",
+ &td.details.auditor_denom_sigs.denominations_serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "auditor_sig",
+ &td.details.auditor_denom_sigs.auditor_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with exchange_sign_keys table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_exchange_sign_keys (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
+ &td.details.exchange_sign_keys.
+ exchange_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &td.details.exchange_sign_keys.
+ master_sig),
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &td.details.exchange_sign_keys.meta.
+ start),
+ GNUNET_PQ_result_spec_timestamp ("expire_sign",
+ &td.details.exchange_sign_keys.meta.
+ expire_sign),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &td.details.exchange_sign_keys.meta.
+ expire_legal),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with signkey_revocations table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_signkey_revocations (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 ("esk_serial",
+ &td.details.signkey_revocations.esk_serial),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &td.details.signkey_revocations.
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with known_coins table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_known_coins (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_KNOWN_COINS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.known_coins.coin_pub),
+ TALER_PQ_result_spec_denom_sig (
+ "denom_sig",
+ &td.details.known_coins.denom_sig),
+ GNUNET_PQ_result_spec_uint64 (
+ "denominations_serial",
+ &td.details.known_coins.denominations_serial),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with refresh_commitments table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_refresh_commitments (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "rc",
+ &td.details.refresh_commitments.rc),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "old_coin_sig",
+ &td.details.refresh_commitments.old_coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.refresh_commitments.amount_with_fee),
+ GNUNET_PQ_result_spec_uint32 (
+ "noreveal_index",
+ &td.details.refresh_commitments.noreveal_index),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "old_coin_pub",
+ &td.details.refresh_commitments.old_coin_pub),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with refresh_revealed_coins table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_refresh_revealed_coins (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint32 (
+ "freshcoin_index",
+ &td.details.refresh_revealed_coins.freshcoin_index),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "link_sig",
+ &td.details.refresh_revealed_coins.link_sig),
+ GNUNET_PQ_result_spec_variable_size (
+ "coin_ev",
+ (void **) &td.details.refresh_revealed_coins.coin_ev,
+ &td.details.refresh_revealed_coins.coin_ev_size),
+ TALER_PQ_result_spec_blinded_denom_sig (
+ "ev_sig",
+ &td.details.refresh_revealed_coins.ev_sig),
+ TALER_PQ_result_spec_exchange_withdraw_values (
+ "ewv",
+ &td.details.refresh_revealed_coins.ewv),
+ GNUNET_PQ_result_spec_uint64 (
+ "denominations_serial",
+ &td.details.refresh_revealed_coins.denominations_serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "melt_serial_id",
+ &td.details.refresh_revealed_coins.melt_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with refresh_transfer_keys table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_refresh_transfer_keys (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ void *tpriv;
+ size_t tpriv_size;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("transfer_pub",
+ &td.details.refresh_transfer_keys.tp),
+ GNUNET_PQ_result_spec_variable_size ("transfer_privs",
+ &tpriv,
+ &tpriv_size),
+ GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
+ &td.details.refresh_transfer_keys.
+ melt_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ /* Both conditions should be identical, but we conservatively also guard against
+ unwarranted changes to the structure here. */
+ if ( (tpriv_size !=
+ sizeof (td.details.refresh_transfer_keys.tprivs)) ||
+ (tpriv_size !=
+ (TALER_CNC_KAPPA - 1) * sizeof (struct TALER_TransferPrivateKeyP)) )
+ {
+ GNUNET_break (0);
+ GNUNET_PQ_cleanup_result (rs);
+ ctx->error = true;
+ return;
+ }
+ GNUNET_memcpy (&td.details.refresh_transfer_keys.tprivs[0],
+ tpriv,
+ tpriv_size);
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with batch deposits table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_batch_deposits (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_BATCH_DEPOSITS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "shard",
+ &td.details.batch_deposits.shard),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "merchant_pub",
+ &td.details.batch_deposits.merchant_pub),
+ GNUNET_PQ_result_spec_timestamp (
+ "wallet_timestamp",
+ &td.details.batch_deposits.wallet_timestamp),
+ GNUNET_PQ_result_spec_timestamp (
+ "exchange_timestamp",
+ &td.details.batch_deposits.exchange_timestamp),
+ GNUNET_PQ_result_spec_timestamp (
+ "refund_deadline",
+ &td.details.batch_deposits.refund_deadline),
+ GNUNET_PQ_result_spec_timestamp (
+ "wire_deadline",
+ &td.details.batch_deposits.wire_deadline),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_contract_terms",
+ &td.details.batch_deposits.h_contract_terms),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wallet_data_hash",
+ &td.details.batch_deposits.wallet_data_hash),
+ &td.details.batch_deposits.no_wallet_data_hash),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wire_salt",
+ &td.details.batch_deposits.wire_salt),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wire_target_h_payto",
+ &td.details.batch_deposits.wire_target_h_payto),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "policy_blocked",
+ &td.details.batch_deposits.policy_blocked),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 (
+ "policy_details_serial_id",
+ &td.details.batch_deposits.policy_details_serial_id),
+ &td.details.batch_deposits.no_policy_details),
+ GNUNET_PQ_result_spec_end
+ };
+
+ td.details.batch_deposits.policy_details_serial_id = 0;
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with coin deposits table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_coin_deposits (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_COIN_DEPOSITS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "batch_deposit_serial_id",
+ &td.details.coin_deposits.batch_deposit_serial_id),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.coin_deposits.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_sig",
+ &td.details.coin_deposits.coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.coin_deposits.amount_with_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with refunds table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_refunds (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_REFUNDS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.refunds.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "merchant_sig",
+ &td.details.refunds.merchant_sig),
+ GNUNET_PQ_result_spec_uint64 (
+ "rtransaction_id",
+ &td.details.refunds.rtransaction_id),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.refunds.amount_with_fee),
+ GNUNET_PQ_result_spec_uint64 (
+ "batch_deposit_serial_id",
+ &td.details.refunds.batch_deposit_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wire_out table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wire_out (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WIRE_OUT
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_timestamp (
+ "execution_date",
+ &td.details.wire_out.execution_date),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wtid_raw",
+ &td.details.wire_out.wtid_raw),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wire_target_h_payto",
+ &td.details.wire_out.wire_target_h_payto),
+ GNUNET_PQ_result_spec_string (
+ "exchange_account_section",
+ &td.details.wire_out.exchange_account_section),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount",
+ &td.details.wire_out.amount),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with aggregation_tracking table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_aggregation_tracking (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "batch_deposit_serial_id",
+ &td.details.aggregation_tracking.batch_deposit_serial_id),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wtid_raw",
+ &td.details.aggregation_tracking.wtid_raw),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wire_fee table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wire_fee (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WIRE_FEE
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_string ("wire_method",
+ &td.details.wire_fee.wire_method),
+ GNUNET_PQ_result_spec_timestamp ("start_date",
+ &td.details.wire_fee.start_date),
+ GNUNET_PQ_result_spec_timestamp ("end_date",
+ &td.details.wire_fee.end_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
+ &td.details.wire_fee.fees.wire),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+ &td.details.wire_fee.fees.closing),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ &td.details.wire_fee.master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wire_fee table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_global_fee (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_GLOBAL_FEE
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_timestamp (
+ "start_date",
+ &td.details.global_fee.start_date),
+ GNUNET_PQ_result_spec_timestamp (
+ "end_date",
+ &td.details.global_fee.end_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "history_fee",
+ &td.details.global_fee.fees.history),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "account_fee",
+ &td.details.global_fee.fees.account),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "purse_fee",
+ &td.details.global_fee.fees.purse),
+ GNUNET_PQ_result_spec_relative_time (
+ "purse_timeout",
+ &td.details.global_fee.purse_timeout),
+ GNUNET_PQ_result_spec_relative_time (
+ "history_expiration",
+ &td.details.global_fee.history_expiration),
+ GNUNET_PQ_result_spec_uint32 (
+ "purse_account_limit",
+ &td.details.global_fee.purse_account_limit),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "master_sig",
+ &td.details.global_fee.master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with recoup table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_recoup (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RECOUP
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &td.details.recoup.coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &td.details.recoup.coin_blind),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &td.details.recoup.amount),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &td.details.recoup.timestamp),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.recoup.coin_pub),
+ GNUNET_PQ_result_spec_uint64 ("reserve_out_serial_id",
+ &td.details.recoup.reserve_out_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with recoup_refresh table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_recoup_refresh (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_RECOUP_REFRESH
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &td.details.recoup_refresh.coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_blind",
+ &td.details.recoup_refresh.coin_blind),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &td.details.recoup_refresh.amount),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &td.details.recoup_refresh.timestamp),
+ GNUNET_PQ_result_spec_uint64 ("known_coin_id",
+ &td.details.recoup_refresh.known_coin_id),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.recoup_refresh.coin_pub),
+ GNUNET_PQ_result_spec_uint64 ("rrc_serial",
+ &td.details.recoup_refresh.rrc_serial),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with extensions table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_extensions (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_EXTENSIONS
+ };
+ bool no_manifest = false;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("extension_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_string ("name",
+ &td.details.extensions.name),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("manifest",
+ &td.details.extensions.manifest),
+ &no_manifest),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with policy_details table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_policy_details (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_POLICY_DETAILS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("policy_details_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type ("hash_code",
+ &td.details.policy_details.
+ hash_code),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_result_spec_json ("policy_json",
+ &td.details.policy_details.
+ policy_json),
+ &td.details.policy_details.no_policy_json),
+ GNUNET_PQ_result_spec_timestamp ("deadline",
+ &td.details.policy_details.
+ deadline),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("commitment",
+ &td.details.policy_details.
+ commitment),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total",
+ &td.details.policy_details.
+ accumulated_total),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee",
+ &td.details.policy_details.
+ fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("transferable",
+ &td.details.policy_details.
+ transferable),
+ GNUNET_PQ_result_spec_uint16 ("fulfillment_state",
+ &td.details.policy_details.
+ fulfillment_state),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("fulfillment_id",
+ &td.details.policy_details.
+ fulfillment_id),
+ &td.details.policy_details.no_fulfillment_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with policy_fulfillments table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_policy_fulfillments (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ bool no_proof = false;
+ bool no_timestamp = false;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("fulfillment_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("fulfillment_timestamp",
+ &td.details.policy_fulfillments.
+ fulfillment_timestamp),
+ &no_timestamp),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("fulfillment_proof",
+ &td.details.policy_fulfillments.
+ fulfillment_proof),
+ &no_proof),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with purse_requests table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_purse_requests (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_PURSE_REQUESTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "purse_requests_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.purse_requests.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "merge_pub",
+ &td.details.purse_requests.merge_pub),
+ GNUNET_PQ_result_spec_timestamp (
+ "purse_creation",
+ &td.details.purse_requests.purse_creation),
+ GNUNET_PQ_result_spec_timestamp (
+ "purse_expiration",
+ &td.details.purse_requests.purse_expiration),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_contract_terms",
+ &td.details.purse_requests.h_contract_terms),
+ GNUNET_PQ_result_spec_uint32 (
+ "age_limit",
+ &td.details.purse_requests.age_limit),
+ GNUNET_PQ_result_spec_uint32 (
+ "flags",
+ &td.details.purse_requests.flags),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.purse_requests.amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "purse_fee",
+ &td.details.purse_requests.purse_fee),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_sig",
+ &td.details.purse_requests.purse_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with purse_decision table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_purse_decision (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_PURSE_DECISION
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "purse_refunds_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.purse_decision.purse_pub),
+ GNUNET_PQ_result_spec_timestamp (
+ "action_timestamp",
+ &td.details.purse_decision.action_timestamp),
+ GNUNET_PQ_result_spec_bool (
+ "refunded",
+ &td.details.purse_decision.refunded),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with purse_merges table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_purse_merges (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_PURSE_MERGES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "purse_merge_request_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "partner_serial_id",
+ &td.details.purse_merges.partner_serial_id),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.purse_merges.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.purse_merges.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "merge_sig",
+ &td.details.purse_merges.merge_sig),
+ GNUNET_PQ_result_spec_timestamp (
+ "merge_timestamp",
+ &td.details.purse_merges.merge_timestamp),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with purse_deposits table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_purse_deposits (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_PURSE_DEPOSITS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "purse_deposit_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_uint64 (
+ "partner_serial_id",
+ &td.details.purse_deposits.partner_serial_id),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.purse_deposits.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_pub",
+ &td.details.purse_deposits.coin_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.purse_deposits.amount_with_fee),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "coin_sig",
+ &td.details.purse_deposits.coin_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with account_merges table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_account_merges (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_ACCOUNT_MERGES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "account_merge_request_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.account_merges.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.account_merges.reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.account_merges.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wallet_h_payto",
+ &td.details.account_merges.wallet_h_payto),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with history_requests table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_history_requests (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_HISTORY_REQUESTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "history_request_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.history_requests.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.history_requests.reserve_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "history_fee",
+ &td.details.history_requests.history_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with close_requests table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_close_requests (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_CLOSE_REQUESTS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "close_request_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.close_requests.reserve_pub),
+ GNUNET_PQ_result_spec_timestamp (
+ "close_timestamp",
+ &td.details.close_requests.close_timestamp),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.close_requests.reserve_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "close",
+ &td.details.close_requests.close),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "close_fee",
+ &td.details.close_requests.close_fee),
+ GNUNET_PQ_result_spec_string (
+ "payto_uri",
+ &td.details.close_requests.payto_uri),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wads_out table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wads_out (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WADS_OUT
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "wad_out_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wad_id",
+ &td.details.wads_out.wad_id),
+ GNUNET_PQ_result_spec_uint64 (
+ "partner_serial_id",
+ &td.details.wads_out.partner_serial_id),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount",
+ &td.details.wads_out.amount),
+ GNUNET_PQ_result_spec_timestamp (
+ "execution_time",
+ &td.details.wads_out.execution_time),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wads_out_entries table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wads_out_entries (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WADS_OUT_ENTRIES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "wad_out_entry_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.wads_out_entries.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.wads_out_entries.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_contract",
+ &td.details.wads_out_entries.h_contract),
+ GNUNET_PQ_result_spec_timestamp (
+ "purse_expiration",
+ &td.details.wads_out_entries.purse_expiration),
+ GNUNET_PQ_result_spec_timestamp (
+ "merge_timestamp",
+ &td.details.wads_out_entries.merge_timestamp),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.wads_out_entries.amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "wad_fee",
+ &td.details.wads_out_entries.wad_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "deposit_fees",
+ &td.details.wads_out_entries.deposit_fees),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.wads_out_entries.reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_sig",
+ &td.details.wads_out_entries.purse_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wads_in table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wads_in (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WADS_IN
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "wad_in_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wad_id",
+ &td.details.wads_in.wad_id),
+ GNUNET_PQ_result_spec_string (
+ "origin_exchange_url",
+ &td.details.wads_in.origin_exchange_url),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount",
+ &td.details.wads_in.amount),
+ GNUNET_PQ_result_spec_timestamp (
+ "arrival_time",
+ &td.details.wads_in.arrival_time),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with wads_in_entries table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_wads_in_entries (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_WADS_IN_ENTRIES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "wad_in_entry_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.wads_in_entries.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.wads_in_entries.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_contract",
+ &td.details.wads_in_entries.h_contract),
+ GNUNET_PQ_result_spec_timestamp (
+ "purse_expiration",
+ &td.details.wads_in_entries.purse_expiration),
+ GNUNET_PQ_result_spec_timestamp (
+ "merge_timestamp",
+ &td.details.wads_in_entries.merge_timestamp),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.wads_in_entries.amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "wad_fee",
+ &td.details.wads_in_entries.wad_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "deposit_fees",
+ &td.details.wads_in_entries.deposit_fees),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.wads_in_entries.reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_sig",
+ &td.details.wads_in_entries.purse_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with profit_drains table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_profit_drains (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_PROFIT_DRAINS
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "profit_drain_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "wtid",
+ &td.details.profit_drains.wtid),
+ GNUNET_PQ_result_spec_string (
+ "account_section",
+ &td.details.profit_drains.account_section),
+ GNUNET_PQ_result_spec_string (
+ "payto_uri",
+ &td.details.profit_drains.payto_uri),
+ GNUNET_PQ_result_spec_timestamp (
+ "trigger_date",
+ &td.details.profit_drains.trigger_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount",
+ &td.details.profit_drains.amount),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "master_sig",
+ &td.details.profit_drains.master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with aml_staff table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_aml_staff (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_AML_STAFF
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "aml_staff_uuid",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "decider_pub",
+ &td.details.aml_staff.decider_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "master_sig",
+ &td.details.aml_staff.master_sig),
+ GNUNET_PQ_result_spec_string (
+ "decider_name",
+ &td.details.aml_staff.decider_name),
+ GNUNET_PQ_result_spec_bool (
+ "is_active",
+ &td.details.aml_staff.is_active),
+ GNUNET_PQ_result_spec_bool (
+ "read_only",
+ &td.details.aml_staff.read_only),
+ GNUNET_PQ_result_spec_timestamp (
+ "last_change",
+ &td.details.aml_staff.last_change),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with aml_history table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_aml_history (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_AML_HISTORY
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint32_t status32;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "aml_history_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_payto",
+ &td.details.aml_history.h_payto),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "new_threshold",
+ &td.details.aml_history.new_threshold),
+ GNUNET_PQ_result_spec_uint32 (
+ "new_status",
+ &status32),
+ GNUNET_PQ_result_spec_timestamp (
+ "decision_time",
+ &td.details.aml_history.decision_time),
+ GNUNET_PQ_result_spec_string (
+ "justification",
+ &td.details.aml_history.justification),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string (
+ "kyc_requirements",
+ &td.details.aml_history.kyc_requirements),
+ NULL),
+ GNUNET_PQ_result_spec_uint64 (
+ "kyc_req_row",
+ &td.details.aml_history.kyc_req_row),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "decider_pub",
+ &td.details.aml_history.decider_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "decider_sig",
+ &td.details.aml_history.decider_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ td.details.aml_history.kyc_requirements = NULL;
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ td.details.aml_history.new_status
+ = (enum TALER_AmlDecisionState) status32;
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with kyc_attributes table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_kyc_attributes (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_KYC_ATTRIBUTES
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "kyc_attributes_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_payto",
+ &td.details.kyc_attributes.h_payto),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "kyc_prox",
+ &td.details.kyc_attributes.kyc_prox),
+ GNUNET_PQ_result_spec_string (
+ "provider",
+ &td.details.kyc_attributes.provider),
+ GNUNET_PQ_result_spec_timestamp (
+ "collection_time",
+ &td.details.kyc_attributes.collection_time),
+ GNUNET_PQ_result_spec_timestamp (
+ "expiration_time",
+ &td.details.kyc_attributes.expiration_time),
+ GNUNET_PQ_result_spec_variable_size (
+ "encrypted_attributes",
+ &td.details.kyc_attributes.encrypted_attributes,
+ &td.details.kyc_attributes.encrypted_attributes_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with purse_deletion table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_purse_deletion (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_PURSE_DELETION
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "purse_deletion_serial_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_sig",
+ &td.details.purse_deletion.purse_sig),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "purse_pub",
+ &td.details.purse_deletion.purse_pub),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Function called with age_withdraw table entries.
+ *
+ * @param cls closure
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lrbt_cb_table_age_withdraw (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupRecordsByTableContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+ struct TALER_EXCHANGEDB_TableData td = {
+ .table = TALER_EXCHANGEDB_RT_AGE_WITHDRAW
+ };
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 (
+ "age_withdraw_id",
+ &td.serial),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "h_commitment",
+ &td.details.age_withdraw.h_commitment),
+ GNUNET_PQ_result_spec_uint16 (
+ "max_age",
+ &td.details.age_withdraw.max_age),
+ TALER_PQ_RESULT_SPEC_AMOUNT (
+ "amount_with_fee",
+ &td.details.age_withdraw.amount_with_fee),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_pub",
+ &td.details.age_withdraw.reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type (
+ "reserve_sig",
+ &td.details.age_withdraw.reserve_sig),
+ GNUNET_PQ_result_spec_uint32 (
+ "noreveal_index",
+ &td.details.age_withdraw.noreveal_index),
+ /* TODO[oec]: more fields! */
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->error = true;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &td);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+/**
+ * Assign statement to @a n and PREPARE
+ * @a sql under name @a n.
+ */
+#define XPREPARE(n,sql) \
+ statement = n; \
+ PREPARE (pg, n, sql);
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_records_by_table (void *cls,
+ enum TALER_EXCHANGEDB_ReplicatedTable table,
+ uint64_t serial,
+ TALER_EXCHANGEDB_ReplicationCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial),
+ GNUNET_PQ_query_param_end
+ };
+ struct LookupRecordsByTableContext ctx = {
+ .pg = pg,
+ .cb = cb,
+ .cb_cls = cb_cls
+ };
+ GNUNET_PQ_PostgresResultHandler rh = NULL;
+ const char *statement = NULL;
+ enum GNUNET_DB_QueryStatus qs;
+
+ switch (table)
+ {
+ case TALER_EXCHANGEDB_RT_DENOMINATIONS:
+ XPREPARE ("select_above_serial_by_table_denominations",
+ "SELECT"
+ " denominations_serial AS serial"
+ ",denom_type"
+ ",denom_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_withdraw"
+ ",expire_deposit"
+ ",expire_legal"
+ ",coin"
+ ",fee_withdraw"
+ ",fee_deposit"
+ ",fee_refresh"
+ ",fee_refund"
+ ",age_mask"
+ " FROM denominations"
+ " WHERE denominations_serial > $1"
+ " ORDER BY denominations_serial ASC;");
+ rh = &lrbt_cb_table_denominations;
+ break;
+ case TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS:
+ XPREPARE ("select_above_serial_by_table_denomination_revocations",
+ "SELECT"
+ " denom_revocations_serial_id AS serial"
+ ",master_sig"
+ ",denominations_serial"
+ " FROM denomination_revocations"
+ " WHERE denom_revocations_serial_id > $1"
+ " ORDER BY denom_revocations_serial_id ASC;");
+ rh = &lrbt_cb_table_denomination_revocations;
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_TARGETS:
+ XPREPARE ("select_above_serial_by_table_wire_targets",
+ "SELECT"
+ " wire_target_serial_id AS serial"
+ ",payto_uri"
+ " FROM wire_targets"
+ " WHERE wire_target_serial_id > $1"
+ " ORDER BY wire_target_serial_id ASC;");
+ rh = &lrbt_cb_table_wire_targets;
+ break;
+ case TALER_EXCHANGEDB_RT_LEGITIMIZATION_PROCESSES:
+ XPREPARE ("select_above_serial_by_table_legitimization_processes",
+ "SELECT"
+ " legitimization_process_serial_id AS serial"
+ ",h_payto"
+ ",reserve_pub"
+ ",expiration_time"
+ ",provider_section"
+ ",provider_user_id"
+ ",provider_legitimization_id"
+ " FROM legitimization_processes"
+ " WHERE legitimization_process_serial_id > $1"
+ " ORDER BY legitimization_process_serial_id ASC;");
+ rh = &lrbt_cb_table_legitimization_processes;
+ break;
+ case TALER_EXCHANGEDB_RT_LEGITIMIZATION_REQUIREMENTS:
+ XPREPARE ("select_above_serial_by_table_legitimization_requirements",
+ "SELECT"
+ " legitimization_requirement_serial_id AS serial"
+ ",h_payto"
+ ",reserve_pub"
+ ",required_checks"
+ " FROM legitimization_requirements"
+ " WHERE legitimization_requirement_serial_id > $1"
+ " ORDER BY legitimization_requirement_serial_id ASC;");
+ rh = &lrbt_cb_table_legitimization_requirements;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES:
+ XPREPARE ("select_above_serial_by_table_reserves",
+ "SELECT"
+ " reserve_uuid AS serial"
+ ",reserve_pub"
+ ",expiration_date"
+ ",gc_date"
+ " FROM reserves"
+ " WHERE reserve_uuid > $1"
+ " ORDER BY reserve_uuid ASC;");
+ rh = &lrbt_cb_table_reserves;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_IN:
+ XPREPARE ("select_above_serial_by_table_reserves_in",
+ "SELECT"
+ " reserve_in_serial_id AS serial"
+ ",reserve_pub"
+ ",wire_reference"
+ ",credit"
+ ",wire_source_h_payto"
+ ",exchange_account_section"
+ ",execution_date"
+ " FROM reserves_in"
+ " WHERE reserve_in_serial_id > $1"
+ " ORDER BY reserve_in_serial_id ASC;");
+ rh = &lrbt_cb_table_reserves_in;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_CLOSE:
+ XPREPARE ("select_above_serial_by_table_reserves_close",
+ "SELECT"
+ " close_uuid AS serial"
+ ",reserve_pub"
+ ",execution_date"
+ ",wtid"
+ ",wire_target_h_payto"
+ ",amount"
+ ",closing_fee"
+ " FROM reserves_close"
+ " WHERE close_uuid > $1"
+ " ORDER BY close_uuid ASC;");
+ rh = &lrbt_cb_table_reserves_close;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OPEN_REQUESTS:
+ XPREPARE ("select_above_serial_by_table_reserves_open_requests",
+ "SELECT"
+ " open_request_uuid AS serial"
+ ",reserve_pub"
+ ",request_timestamp"
+ ",expiration_date"
+ ",reserve_sig"
+ ",reserve_payment"
+ ",requested_purse_limit"
+ " FROM reserves_open_requests"
+ " WHERE open_request_uuid > $1"
+ " ORDER BY open_request_uuid ASC;");
+ rh = &lrbt_cb_table_reserves_open_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OPEN_DEPOSITS:
+ XPREPARE ("select_above_serial_by_table_reserves_open_deposits",
+ "SELECT"
+ " reserves_open_deposit_uuid AS serial"
+ ",reserve_sig"
+ ",reserve_pub"
+ ",coin_pub"
+ ",coin_sig"
+ ",contribution"
+ " FROM reserves_open_deposits"
+ " WHERE reserves_open_deposit_uuid > $1"
+ " ORDER BY reserves_open_deposit_uuid ASC;");
+ rh = &lrbt_cb_table_reserves_open_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OUT:
+ XPREPARE ("select_above_serial_by_table_reserves_out",
+ "SELECT"
+ " reserve_out_serial_id AS serial"
+ ",h_blind_ev"
+ ",denominations_serial"
+ ",denom_sig"
+ ",reserve_uuid"
+ ",reserve_sig"
+ ",execution_date"
+ ",amount_with_fee"
+ " FROM reserves_out"
+ " JOIN reserves USING (reserve_uuid)"
+ " WHERE reserve_out_serial_id > $1"
+ " ORDER BY reserve_out_serial_id ASC;");
+ rh = &lrbt_cb_table_reserves_out;
+ break;
+ case TALER_EXCHANGEDB_RT_AUDITORS:
+ XPREPARE ("select_above_serial_by_table_auditors",
+ "SELECT"
+ " auditor_uuid AS serial"
+ ",auditor_pub"
+ ",auditor_name"
+ ",auditor_url"
+ ",is_active"
+ ",last_change"
+ " FROM auditors"
+ " WHERE auditor_uuid > $1"
+ " ORDER BY auditor_uuid ASC;");
+ rh = &lrbt_cb_table_auditors;
+ break;
+ case TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS:
+ XPREPARE ("select_above_serial_by_table_auditor_denom_sigs",
+ "SELECT"
+ " auditor_denom_serial AS serial"
+ ",auditor_uuid"
+ ",denominations_serial"
+ ",auditor_sig"
+ " FROM auditor_denom_sigs"
+ " WHERE auditor_denom_serial > $1"
+ " ORDER BY auditor_denom_serial ASC;");
+ rh = &lrbt_cb_table_auditor_denom_sigs;
+ break;
+ case TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS:
+ XPREPARE ("select_above_serial_by_table_exchange_sign_keys",
+ "SELECT"
+ " esk_serial AS serial"
+ ",exchange_pub"
+ ",master_sig"
+ ",valid_from"
+ ",expire_sign"
+ ",expire_legal"
+ " FROM exchange_sign_keys"
+ " WHERE esk_serial > $1"
+ " ORDER BY esk_serial ASC;");
+ rh = &lrbt_cb_table_exchange_sign_keys;
+ break;
+ case TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS:
+ XPREPARE ("select_above_serial_by_table_signkey_revocations",
+ "SELECT"
+ " signkey_revocations_serial_id AS serial"
+ ",esk_serial"
+ ",master_sig"
+ " FROM signkey_revocations"
+ " WHERE signkey_revocations_serial_id > $1"
+ " ORDER BY signkey_revocations_serial_id ASC;");
+ rh = &lrbt_cb_table_signkey_revocations;
+ break;
+ case TALER_EXCHANGEDB_RT_KNOWN_COINS:
+ XPREPARE ("select_above_serial_by_table_known_coins",
+ "SELECT"
+ " known_coin_id AS serial"
+ ",coin_pub"
+ ",denom_sig"
+ ",denominations_serial"
+ " FROM known_coins"
+ " WHERE known_coin_id > $1"
+ " ORDER BY known_coin_id ASC;");
+ rh = &lrbt_cb_table_known_coins;
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS:
+ XPREPARE ("select_above_serial_by_table_refresh_commitments",
+ "SELECT"
+ " melt_serial_id AS serial"
+ ",rc"
+ ",old_coin_sig"
+ ",amount_with_fee"
+ ",noreveal_index"
+ ",old_coin_pub"
+ " FROM refresh_commitments"
+ " WHERE melt_serial_id > $1"
+ " ORDER BY melt_serial_id ASC;");
+ rh = &lrbt_cb_table_refresh_commitments;
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS:
+ XPREPARE ("select_above_serial_by_table_refresh_revealed_coins",
+ "SELECT"
+ " rrc_serial AS serial"
+ ",freshcoin_index"
+ ",link_sig"
+ ",coin_ev"
+ ",ev_sig"
+ ",ewv"
+ ",denominations_serial"
+ ",melt_serial_id"
+ " FROM refresh_revealed_coins"
+ " WHERE rrc_serial > $1"
+ " ORDER BY rrc_serial ASC;");
+ rh = &lrbt_cb_table_refresh_revealed_coins;
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS:
+ XPREPARE ("select_above_serial_by_table_refresh_transfer_keys",
+ "SELECT"
+ " rtc_serial AS serial"
+ ",transfer_pub"
+ ",transfer_privs"
+ ",melt_serial_id"
+ " FROM refresh_transfer_keys"
+ " WHERE rtc_serial > $1"
+ " ORDER BY rtc_serial ASC;");
+ rh = &lrbt_cb_table_refresh_transfer_keys;
+ break;
+ case TALER_EXCHANGEDB_RT_BATCH_DEPOSITS:
+ XPREPARE ("select_above_serial_by_table_batch_deposits",
+ "SELECT"
+ " batch_deposit_serial_id AS serial"
+ ",shard"
+ ",merchant_pub"
+ ",wallet_timestamp"
+ ",exchange_timestamp"
+ ",refund_deadline"
+ ",wire_deadline"
+ ",h_contract_terms"
+ ",wallet_data_hash"
+ ",wire_salt"
+ ",wire_target_h_payto"
+ ",done"
+ ",policy_blocked"
+ ",policy_details_serial_id"
+ " FROM batch_deposits"
+ " WHERE batch_deposit_serial_id > $1"
+ " ORDER BY batch_deposit_serial_id ASC;");
+ rh = &lrbt_cb_table_batch_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_COIN_DEPOSITS:
+ XPREPARE ("select_above_serial_by_table_coin_deposits",
+ "SELECT"
+ " coin_deposit_serial_id AS serial"
+ ",batch_deposit_serial_id"
+ ",coin_pub"
+ ",coin_sig"
+ ",amount_with_fee"
+ " FROM coin_deposits"
+ " WHERE coin_deposit_serial_id > $1"
+ " ORDER BY coin_deposit_serial_id ASC;");
+ rh = &lrbt_cb_table_coin_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_REFUNDS:
+ XPREPARE ("select_above_serial_by_table_refunds",
+ "SELECT"
+ " refund_serial_id AS serial"
+ ",coin_pub"
+ ",merchant_sig"
+ ",rtransaction_id"
+ ",amount_with_fee"
+ ",batch_deposit_serial_id"
+ " FROM refunds"
+ " WHERE refund_serial_id > $1"
+ " ORDER BY refund_serial_id ASC;");
+ rh = &lrbt_cb_table_refunds;
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_OUT:
+ XPREPARE ("select_above_serial_by_table_wire_out",
+ "SELECT"
+ " wireout_uuid AS serial"
+ ",execution_date"
+ ",wtid_raw"
+ ",wire_target_h_payto"
+ ",exchange_account_section"
+ ",amount"
+ " FROM wire_out"
+ " WHERE wireout_uuid > $1"
+ " ORDER BY wireout_uuid ASC;");
+ rh = &lrbt_cb_table_wire_out;
+ break;
+ case TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING:
+ XPREPARE ("select_above_serial_by_table_aggregation_tracking",
+ "SELECT"
+ " aggregation_serial_id AS serial"
+ ",batch_deposit_serial_id"
+ ",wtid_raw"
+ " FROM aggregation_tracking"
+ " WHERE aggregation_serial_id > $1"
+ " ORDER BY aggregation_serial_id ASC;");
+ rh = &lrbt_cb_table_aggregation_tracking;
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_FEE:
+ XPREPARE ("select_above_serial_by_table_wire_fee",
+ "SELECT"
+ " wire_fee_serial AS serial"
+ ",wire_method"
+ ",start_date"
+ ",end_date"
+ ",wire_fee"
+ ",closing_fee"
+ ",master_sig"
+ " FROM wire_fee"
+ " WHERE wire_fee_serial > $1"
+ " ORDER BY wire_fee_serial ASC;");
+ rh = &lrbt_cb_table_wire_fee;
+ break;
+ case TALER_EXCHANGEDB_RT_GLOBAL_FEE:
+ XPREPARE ("select_above_serial_by_table_global_fee",
+ "SELECT"
+ " global_fee_serial AS serial"
+ ",start_date"
+ ",end_date"
+ ",history_fee"
+ ",account_fee"
+ ",purse_fee"
+ ",purse_timeout"
+ ",history_expiration"
+ ",purse_account_limit"
+ ",master_sig"
+ " FROM global_fee"
+ " WHERE global_fee_serial > $1"
+ " ORDER BY global_fee_serial ASC;");
+ rh = &lrbt_cb_table_global_fee;
+ break;
+ case TALER_EXCHANGEDB_RT_RECOUP:
+ XPREPARE ("select_above_serial_by_table_recoup",
+ "SELECT"
+ " recoup_uuid AS serial"
+ ",coin_sig"
+ ",coin_blind"
+ ",amount"
+ ",recoup_timestamp"
+ ",coin_pub"
+ ",reserve_out_serial_id"
+ " FROM recoup"
+ " WHERE recoup_uuid > $1"
+ " ORDER BY recoup_uuid ASC;");
+ rh = &lrbt_cb_table_recoup;
+ break;
+ case TALER_EXCHANGEDB_RT_RECOUP_REFRESH:
+ XPREPARE ("select_above_serial_by_table_recoup_refresh",
+ "SELECT"
+ " recoup_refresh_uuid AS serial"
+ ",coin_sig"
+ ",coin_blind"
+ ",amount"
+ ",recoup_timestamp"
+ ",coin_pub"
+ ",known_coin_id"
+ ",rrc_serial"
+ " FROM recoup_refresh"
+ " WHERE recoup_refresh_uuid > $1"
+ " ORDER BY recoup_refresh_uuid ASC;");
+ rh = &lrbt_cb_table_recoup_refresh;
+ break;
+ case TALER_EXCHANGEDB_RT_EXTENSIONS:
+ statement = "select_above_serial_by_table_extensions";
+ rh = &lrbt_cb_table_extensions;
+ break;
+ case TALER_EXCHANGEDB_RT_POLICY_DETAILS:
+ statement = "select_above_serial_by_table_policy_details";
+ rh = &lrbt_cb_table_policy_details;
+ break;
+ case TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS:
+ statement = "select_above_serial_by_table_policy_fulfillments";
+ rh = &lrbt_cb_table_policy_fulfillments;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_REQUESTS:
+ XPREPARE ("select_above_serial_by_table_purse_requests",
+ "SELECT"
+ " purse_requests_serial_id"
+ ",purse_pub"
+ ",merge_pub"
+ ",purse_creation"
+ ",purse_expiration"
+ ",h_contract_terms"
+ ",age_limit"
+ ",flags"
+ ",amount_with_fee"
+ ",purse_fee"
+ ",purse_sig"
+ " FROM purse_requests"
+ " WHERE purse_requests_serial_id > $1"
+ " ORDER BY purse_requests_serial_id ASC;");
+ rh = &lrbt_cb_table_purse_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DECISION:
+ XPREPARE ("select_above_serial_by_table_purse_decision",
+ "SELECT"
+ " purse_decision_serial_id"
+ ",action_timestamp"
+ ",refunded"
+ ",purse_pub"
+ " FROM purse_decision"
+ " WHERE purse_decision_serial_id > $1"
+ " ORDER BY purse_decision_serial_id ASC;");
+ rh = &lrbt_cb_table_purse_decision;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_MERGES:
+ XPREPARE ("select_above_serial_by_table_purse_merges",
+ "SELECT"
+ " purse_merge_request_serial_id"
+ ",partner_serial_id"
+ ",reserve_pub"
+ ",purse_pub"
+ ",merge_sig"
+ ",merge_timestamp"
+ " FROM purse_merges"
+ " WHERE purse_merge_request_serial_id > $1"
+ " ORDER BY purse_merge_request_serial_id ASC;");
+ rh = &lrbt_cb_table_purse_merges;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DEPOSITS:
+ XPREPARE ("select_above_serial_by_table_purse_deposits",
+ "SELECT"
+ " purse_deposit_serial_id"
+ ",partner_serial_id"
+ ",purse_pub"
+ ",coin_pub"
+ ",amount_with_fee"
+ ",coin_sig"
+ " FROM purse_deposits"
+ " WHERE purse_deposit_serial_id > $1"
+ " ORDER BY purse_deposit_serial_id ASC;");
+ rh = &lrbt_cb_table_purse_deposits;
+ break;
+ case TALER_EXCHANGEDB_RT_ACCOUNT_MERGES:
+ XPREPARE ("select_above_serial_by_table_account_merges",
+ "SELECT"
+ " account_merge_request_serial_id"
+ ",reserve_pub"
+ ",reserve_sig"
+ ",purse_pub"
+ ",wallet_h_payto"
+ " FROM account_merges"
+ " WHERE account_merge_request_serial_id > $1"
+ " ORDER BY account_merge_request_serial_id ASC;");
+ rh = &lrbt_cb_table_account_merges;
+ break;
+ case TALER_EXCHANGEDB_RT_HISTORY_REQUESTS:
+ XPREPARE ("select_above_serial_by_table_history_requests",
+ "SELECT"
+ " history_request_serial_id"
+ ",reserve_pub"
+ ",request_timestamp"
+ ",reserve_sig"
+ ",history_fee"
+ " FROM history_requests"
+ " WHERE history_request_serial_id > $1"
+ " ORDER BY history_request_serial_id ASC;");
+ rh = &lrbt_cb_table_history_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_CLOSE_REQUESTS:
+ XPREPARE ("select_above_serial_by_table_close_requests",
+ "SELECT"
+ " close_request_serial_id"
+ ",reserve_pub"
+ ",close_timestamp"
+ ",reserve_sig"
+ ",close"
+ " FROM close_requests"
+ " WHERE close_request_serial_id > $1"
+ " ORDER BY close_request_serial_id ASC;");
+ rh = &lrbt_cb_table_close_requests;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_OUT:
+ XPREPARE ("select_above_serial_by_table_wads_out",
+ "SELECT"
+ " wad_out_serial_id"
+ ",wad_id"
+ ",partner_serial_id"
+ ",amount"
+ ",execution_time"
+ " FROM wads_out"
+ " WHERE wad_out_serial_id > $1"
+ " ORDER BY wad_out_serial_id ASC;");
+ rh = &lrbt_cb_table_wads_out;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_OUT_ENTRIES:
+ XPREPARE ("select_above_serial_by_table_wads_out_entries",
+ "SELECT"
+ " wad_out_entry_serial_id"
+ ",reserve_pub"
+ ",purse_pub"
+ ",h_contract"
+ ",purse_expiration"
+ ",merge_timestamp"
+ ",amount_with_fee"
+ ",wad_fee"
+ ",deposit_fees"
+ ",reserve_sig"
+ ",purse_sig"
+ " FROM wad_out_entries"
+ " WHERE wad_out_entry_serial_id > $1"
+ " ORDER BY wad_out_entry_serial_id ASC;");
+ rh = &lrbt_cb_table_wads_out_entries;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_IN:
+ XPREPARE ("select_above_serial_by_table_wads_in",
+ "SELECT"
+ " wad_in_serial_id"
+ ",wad_id"
+ ",origin_exchange_url"
+ ",amount"
+ ",arrival_time"
+ " FROM wads_in"
+ " WHERE wad_in_serial_id > $1"
+ " ORDER BY wad_in_serial_id ASC;");
+ rh = &lrbt_cb_table_wads_in;
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_IN_ENTRIES:
+ XPREPARE ("select_above_serial_by_table_wads_in_entries",
+ "SELECT"
+ " wad_in_entry_serial_id"
+ ",reserve_pub"
+ ",purse_pub"
+ ",h_contract"
+ ",purse_expiration"
+ ",merge_timestamp"
+ ",amount_with_fee"
+ ",wad_fee"
+ ",deposit_fees"
+ ",reserve_sig"
+ ",purse_sig"
+ " FROM wad_in_entries"
+ " WHERE wad_in_entry_serial_id > $1"
+ " ORDER BY wad_in_entry_serial_id ASC;");
+ rh = &lrbt_cb_table_wads_in_entries;
+ break;
+ case TALER_EXCHANGEDB_RT_PROFIT_DRAINS:
+ XPREPARE ("select_above_serial_by_table_profit_drains",
+ "SELECT"
+ " profit_drain_serial_id"
+ ",wtid"
+ ",account_section"
+ ",payto_uri"
+ ",trigger_date"
+ ",amount"
+ ",master_sig"
+ " FROM profit_drains"
+ " WHERE profit_drain_serial_id > $1"
+ " ORDER BY profit_drain_serial_id ASC;");
+ rh = &lrbt_cb_table_profit_drains;
+ break;
+
+ case TALER_EXCHANGEDB_RT_AML_STAFF:
+ XPREPARE ("select_above_serial_by_table_aml_staff",
+ "SELECT"
+ " aml_staff_uuid"
+ ",decider_pub"
+ ",master_sig"
+ ",decider_name"
+ ",is_active"
+ ",read_only"
+ ",last_change"
+ " FROM aml_staff"
+ " WHERE aml_staff_uuid > $1"
+ " ORDER BY aml_staff_uuid ASC;");
+ rh = &lrbt_cb_table_aml_staff;
+ break;
+ case TALER_EXCHANGEDB_RT_AML_HISTORY:
+ XPREPARE ("select_above_serial_by_table_aml_history",
+ "SELECT"
+ " aml_history_serial_id"
+ ",h_payto"
+ ",new_threshold"
+ ",new_status"
+ ",decision_time"
+ ",justification"
+ ",kyc_requirements"
+ ",kyc_req_row"
+ ",decider_pub"
+ ",decider_sig"
+ " FROM aml_history"
+ " WHERE aml_history_serial_id > $1"
+ " ORDER BY aml_history_serial_id ASC;");
+ rh = &lrbt_cb_table_aml_history;
+ break;
+ case TALER_EXCHANGEDB_RT_KYC_ATTRIBUTES:
+ XPREPARE ("select_above_serial_by_table_kyc_attributes",
+ "SELECT"
+ " kyc_attributes_serial_id"
+ ",h_payto"
+ ",kyc_prox"
+ ",provider"
+ ",collection_time"
+ ",expiration_time"
+ ",encrypted_attributes"
+ " FROM kyc_attributes"
+ " WHERE kyc_attributes_serial_id > $1"
+ " ORDER BY kyc_attributes_serial_id ASC;");
+ rh = &lrbt_cb_table_kyc_attributes;
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DELETION:
+ XPREPARE ("select_above_serial_by_table_purse_deletion",
+ "SELECT"
+ " purse_deletion_serial_id"
+ ",purse_pub"
+ ",purse_sig"
+ " FROM purse_deletion"
+ " WHERE purse_deletion_serial_id > $1"
+ " ORDER BY purse_deletion_serial_id ASC;");
+ rh = &lrbt_cb_table_purse_deletion;
+ break;
+ case TALER_EXCHANGEDB_RT_AGE_WITHDRAW:
+ XPREPARE ("select_above_serial_by_table_age_withdraw",
+ "SELECT"
+ " age_withdraw_id"
+ ",h_commitment"
+ ",amount_with_fee"
+ ",max_age"
+ ",reserve_pub"
+ ",reserve_sig"
+ ",noreveal_index"
+ " FROM age_withdraw"
+ " WHERE age_withdraw_id > $1"
+ " ORDER BY age_withdraw_id ASC;");
+ /* TODO[oec]: MORE FIELDS! */
+ rh = &lrbt_cb_table_age_withdraw;
+ break;
+ }
+ if (NULL == rh)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ statement,
+ params,
+ rh,
+ &ctx);
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to run `%s'\n",
+ statement);
+ return qs;
+ }
+ if (ctx.error)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+}
+
+
+#undef XPREPARE
+
+/* end of pg_lookup_records_by_table.c */
diff --git a/src/exchangedb/pg_lookup_records_by_table.h b/src/exchangedb/pg_lookup_records_by_table.h
new file mode 100644
index 000000000..9dc25fff4
--- /dev/null
+++ b/src/exchangedb/pg_lookup_records_by_table.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_lookup_records_by_table.h
+ * @brief implementation of the lookup_records_by_table function
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_RECORDS_BY_TABLE_H
+#define PG_LOOKUP_RECORDS_BY_TABLE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup records above @a serial number in @a table. Used in
+ * exchange-auditor database replication.
+ *
+ * @param cls closure
+ * @param table table for which we should return the serial
+ * @param serial largest serial number to exclude
+ * @param cb function to call on the records
+ * @param cb_cls closure for @a cb
+ * @return transaction status code, GNUNET_DB_STATUS_HARD_ERROR if
+ * @a table does not have a serial number
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_records_by_table (void *cls,
+ enum TALER_EXCHANGEDB_ReplicatedTable table,
+ uint64_t serial,
+ TALER_EXCHANGEDB_ReplicationCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_lookup_serial_by_table.c b/src/exchangedb/pg_lookup_serial_by_table.c
new file mode 100644
index 000000000..9fda7ddf8
--- /dev/null
+++ b/src/exchangedb/pg_lookup_serial_by_table.c
@@ -0,0 +1,459 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_lookup_serial_by_table.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_serial_by_table.h"
+#include "pg_helper.h"
+
+
+/**
+ * Assign statement to @a n and PREPARE
+ * @a sql under name @a n.
+ */
+#define XPREPARE(n,sql) \
+ statement = n; \
+ PREPARE (pg, n, sql);
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_serial_by_table (void *cls,
+ enum TALER_EXCHANGEDB_ReplicatedTable table,
+ uint64_t *serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("serial",
+ serial),
+ GNUNET_PQ_result_spec_end
+ };
+ const char *statement = NULL;
+
+ switch (table)
+ {
+ case TALER_EXCHANGEDB_RT_DENOMINATIONS:
+ XPREPARE ("select_serial_by_table_denominations",
+ "SELECT"
+ " denominations_serial AS serial"
+ " FROM denominations"
+ " ORDER BY denominations_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS:
+ XPREPARE ("select_serial_by_table_denomination_revocations",
+ "SELECT"
+ " denom_revocations_serial_id AS serial"
+ " FROM denomination_revocations"
+ " ORDER BY denom_revocations_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_TARGETS:
+ XPREPARE ("select_serial_by_table_wire_targets",
+ "SELECT"
+ " wire_target_serial_id AS serial"
+ " FROM wire_targets"
+ " ORDER BY wire_target_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_LEGITIMIZATION_PROCESSES:
+ XPREPARE ("select_serial_by_table_legitimization_processes",
+ "SELECT"
+ " legitimization_process_serial_id AS serial"
+ " FROM legitimization_processes"
+ " ORDER BY legitimization_process_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_LEGITIMIZATION_REQUIREMENTS:
+ XPREPARE ("select_serial_by_table_legitimization_requiremetns",
+ "SELECT"
+ " legitimization_requirement_serial_id AS serial"
+ " FROM legitimization_requirements"
+ " ORDER BY legitimization_requirement_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES:
+ XPREPARE ("select_serial_by_table_reserves",
+ "SELECT"
+ " reserve_uuid AS serial"
+ " FROM reserves"
+ " ORDER BY reserve_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_IN:
+ XPREPARE ("select_serial_by_table_reserves_in",
+ "SELECT"
+ " reserve_in_serial_id AS serial"
+ " FROM reserves_in"
+ " ORDER BY reserve_in_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_CLOSE:
+ XPREPARE ("select_serial_by_table_reserves_close",
+ "SELECT"
+ " close_uuid AS serial"
+ " FROM reserves_close"
+ " ORDER BY close_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OPEN_REQUESTS:
+ XPREPARE ("select_serial_by_table_reserves_open_requests",
+ "SELECT"
+ " open_request_uuid AS serial"
+ " FROM reserves_open_requests"
+ " ORDER BY open_request_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OPEN_DEPOSITS:
+ XPREPARE ("select_serial_by_table_reserves_open_deposits",
+ "SELECT"
+ " reserve_open_deposit_uuid AS serial"
+ " FROM reserves_open_deposits"
+ " ORDER BY reserve_open_deposit_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RESERVES_OUT:
+ XPREPARE ("select_serial_by_table_reserves_out",
+ "SELECT"
+ " reserve_out_serial_id AS serial"
+ " FROM reserves_out"
+ " ORDER BY reserve_out_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_AUDITORS:
+ XPREPARE ("select_serial_by_table_auditors",
+ "SELECT"
+ " auditor_uuid AS serial"
+ " FROM auditors"
+ " ORDER BY auditor_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS:
+ XPREPARE ("select_serial_by_table_auditor_denom_sigs",
+ "SELECT"
+ " auditor_denom_serial AS serial"
+ " FROM auditor_denom_sigs"
+ " ORDER BY auditor_denom_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS:
+ XPREPARE ("select_serial_by_table_exchange_sign_keys",
+ "SELECT"
+ " esk_serial AS serial"
+ " FROM exchange_sign_keys"
+ " ORDER BY esk_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS:
+ XPREPARE ("select_serial_by_table_signkey_revocations",
+ "SELECT"
+ " signkey_revocations_serial_id AS serial"
+ " FROM signkey_revocations"
+ " ORDER BY signkey_revocations_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_KNOWN_COINS:
+ XPREPARE ("select_serial_by_table_known_coins",
+ "SELECT"
+ " known_coin_id AS serial"
+ " FROM known_coins"
+ " ORDER BY known_coin_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS:
+ XPREPARE ("select_serial_by_table_refresh_commitments",
+ "SELECT"
+ " melt_serial_id AS serial"
+ " FROM refresh_commitments"
+ " ORDER BY melt_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS:
+ XPREPARE ("select_serial_by_table_refresh_revealed_coins",
+ "SELECT"
+ " rrc_serial AS serial"
+ " FROM refresh_revealed_coins"
+ " ORDER BY rrc_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS:
+ XPREPARE ("select_serial_by_table_refresh_transfer_keys",
+ "SELECT"
+ " rtc_serial AS serial"
+ " FROM refresh_transfer_keys"
+ " ORDER BY rtc_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_BATCH_DEPOSITS:
+ XPREPARE ("select_serial_by_table_batch_deposits",
+ "SELECT"
+ " batch_deposit_serial_id AS serial"
+ " FROM batch_deposits"
+ " ORDER BY batch_deposit_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_COIN_DEPOSITS:
+ XPREPARE ("select_serial_by_table_coin_deposits",
+ "SELECT"
+ " coin_deposit_serial_id AS serial"
+ " FROM coin_deposits"
+ " ORDER BY coin_deposit_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_REFUNDS:
+ XPREPARE ("select_serial_by_table_refunds",
+ "SELECT"
+ " refund_serial_id AS serial"
+ " FROM refunds"
+ " ORDER BY refund_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_OUT:
+ XPREPARE ("select_serial_by_table_wire_out",
+ "SELECT"
+ " wireout_uuid AS serial"
+ " FROM wire_out"
+ " ORDER BY wireout_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING:
+ XPREPARE ("select_serial_by_table_aggregation_tracking",
+ "SELECT"
+ " aggregation_serial_id AS serial"
+ " FROM aggregation_tracking"
+ " ORDER BY aggregation_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WIRE_FEE:
+ XPREPARE ("select_serial_by_table_wire_fee",
+ "SELECT"
+ " wire_fee_serial AS serial"
+ " FROM wire_fee"
+ " ORDER BY wire_fee_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_GLOBAL_FEE:
+ XPREPARE ("select_serial_by_table_global_fee",
+ "SELECT"
+ " global_fee_serial AS serial"
+ " FROM global_fee"
+ " ORDER BY global_fee_serial DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RECOUP:
+ XPREPARE ("select_serial_by_table_recoup",
+ "SELECT"
+ " recoup_uuid AS serial"
+ " FROM recoup"
+ " ORDER BY recoup_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_RECOUP_REFRESH:
+ XPREPARE ("select_serial_by_table_recoup_refresh",
+ "SELECT"
+ " recoup_refresh_uuid AS serial"
+ " FROM recoup_refresh"
+ " ORDER BY recoup_refresh_uuid DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_EXTENSIONS:
+ XPREPARE ("select_serial_by_table_extensions",
+ "SELECT"
+ " extension_id AS serial"
+ " FROM extensions"
+ " ORDER BY extension_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_POLICY_DETAILS:
+ XPREPARE ("select_serial_by_table_policy_details",
+ "SELECT"
+ " policy_details_serial_id AS serial"
+ " FROM policy_details"
+ " ORDER BY policy_details_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_POLICY_FULFILLMENTS:
+ XPREPARE ("select_serial_by_table_policy_fulfillments",
+ "SELECT"
+ " fulfillment_id AS serial"
+ " FROM policy_fulfillments"
+ " ORDER BY fulfillment_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_REQUESTS:
+ XPREPARE ("select_serial_by_table_purse_requests",
+ "SELECT"
+ " purse_requests_serial_id AS serial"
+ " FROM purse_requests"
+ " ORDER BY purse_requests_serial_id DESC"
+ " LIMIT 1;")
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DECISION:
+ XPREPARE ("select_serial_by_table_purse_decision",
+ "SELECT"
+ " purse_decision_serial_id AS serial"
+ " FROM purse_decision"
+ " ORDER BY purse_decision_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_MERGES:
+ XPREPARE ("select_serial_by_table_purse_merges",
+ "SELECT"
+ " purse_merge_request_serial_id AS serial"
+ " FROM purse_merges"
+ " ORDER BY purse_merge_request_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DEPOSITS:
+ XPREPARE ("select_serial_by_table_purse_deposits",
+ "SELECT"
+ " purse_deposit_serial_id AS serial"
+ " FROM purse_deposits"
+ " ORDER BY purse_deposit_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_ACCOUNT_MERGES:
+ XPREPARE ("select_serial_by_table_account_merges",
+ "SELECT"
+ " account_merge_request_serial_id AS serial"
+ " FROM account_merges"
+ " ORDER BY account_merge_request_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_HISTORY_REQUESTS:
+ XPREPARE ("select_serial_by_table_history_requests",
+ "SELECT"
+ " history_request_serial_id AS serial"
+ " FROM history_requests"
+ " ORDER BY history_request_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_CLOSE_REQUESTS:
+ XPREPARE ("select_serial_by_table_close_requests",
+ "SELECT"
+ " close_request_serial_id AS serial"
+ " FROM close_requests"
+ " ORDER BY close_request_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_OUT:
+ XPREPARE ("select_serial_by_table_wads_out",
+ "SELECT"
+ " wad_out_serial_id AS serial"
+ " FROM wads_out"
+ " ORDER BY wad_out_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_OUT_ENTRIES:
+ XPREPARE ("select_serial_by_table_wads_out_entries",
+ "SELECT"
+ " wad_out_entry_serial_id AS serial"
+ " FROM wad_out_entries"
+ " ORDER BY wad_out_entry_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_IN:
+ XPREPARE ("select_serial_by_table_wads_in",
+ "SELECT"
+ " wad_in_serial_id AS serial"
+ " FROM wads_in"
+ " ORDER BY wad_in_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_WADS_IN_ENTRIES:
+ XPREPARE ("select_serial_by_table_wads_in_entries",
+ "SELECT"
+ " wad_in_entry_serial_id AS serial"
+ " FROM wad_in_entries"
+ " ORDER BY wad_in_entry_serial_id DESC"
+ " LIMIT 1;");
+ break;
+ case TALER_EXCHANGEDB_RT_PROFIT_DRAINS:
+ XPREPARE ("select_serial_by_table_profit_drains",
+ "SELECT"
+ " profit_drain_serial_id AS serial"
+ " FROM profit_drains"
+ " ORDER BY profit_drain_serial_id DESC"
+ " LIMIT 1;");
+ statement = "select_serial_by_table_profit_drains";
+ break;
+ case TALER_EXCHANGEDB_RT_AML_STAFF:
+ XPREPARE ("select_serial_by_table_aml_staff",
+ "SELECT"
+ " aml_staff_uuid AS serial"
+ " FROM aml_staff"
+ " ORDER BY aml_staff_uuid DESC"
+ " LIMIT 1;");
+ statement = "select_serial_by_table_aml_staff";
+ break;
+ case TALER_EXCHANGEDB_RT_AML_HISTORY:
+ XPREPARE ("select_serial_by_table_aml_history",
+ "SELECT"
+ " aml_history_serial_id AS serial"
+ " FROM aml_history"
+ " ORDER BY aml_history_serial_id DESC"
+ " LIMIT 1;");
+ statement = "select_serial_by_table_aml_history";
+ break;
+ case TALER_EXCHANGEDB_RT_KYC_ATTRIBUTES:
+ XPREPARE ("select_serial_by_table_kyc_attributes",
+ "SELECT"
+ " kyc_attributes_serial_id AS serial"
+ " FROM kyc_attributes"
+ " ORDER BY kyc_attributes_serial_id DESC"
+ " LIMIT 1;");
+ statement = "select_serial_by_table_kyc_attributes";
+ break;
+ case TALER_EXCHANGEDB_RT_PURSE_DELETION:
+ XPREPARE ("select_serial_by_table_purse_deletion",
+ "SELECT"
+ " purse_deletion_serial_id AS serial"
+ " FROM purse_deletion"
+ " ORDER BY purse_deletion_serial_id DESC"
+ " LIMIT 1;");
+ statement = "select_serial_by_table_purse_deletion";
+ break;
+ case TALER_EXCHANGEDB_RT_AGE_WITHDRAW:
+ XPREPARE ("select_serial_by_table_age_withdraw",
+ "SELECT"
+ " age_withdraw_id AS serial"
+ " FROM age_withdraw"
+ " ORDER BY age_withdraw_id DESC"
+ " LIMIT 1;");
+ statement = "select_serial_by_table_age_withdraw";
+ break;
+ }
+ if (NULL == statement)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ statement,
+ params,
+ rs);
+}
+
+
+#undef XPREPARE
diff --git a/src/exchangedb/pg_lookup_serial_by_table.h b/src/exchangedb/pg_lookup_serial_by_table.h
new file mode 100644
index 000000000..815b6477d
--- /dev/null
+++ b/src/exchangedb/pg_lookup_serial_by_table.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_lookup_serial_by_table.h
+ * @brief implementation of the lookup_serial_by_table function
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_SERIAL_BY_TABLE_H
+#define PG_LOOKUP_SERIAL_BY_TABLE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup the latest serial number of @a table. Used in
+ * exchange-auditor database replication.
+ *
+ * @param cls closure
+ * @param table table for which we should return the serial
+ * @param[out] serial latest serial number in use
+ * @return transaction status code, GNUNET_DB_STATUS_HARD_ERROR if
+ * @a table does not have a serial number
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_serial_by_table (void *cls,
+ enum TALER_EXCHANGEDB_ReplicatedTable table,
+ uint64_t *serial);
+
+
+#endif
diff --git a/src/exchangedb/pg_lookup_signing_key.c b/src/exchangedb/pg_lookup_signing_key.c
new file mode 100644
index 000000000..3803d114f
--- /dev/null
+++ b/src/exchangedb/pg_lookup_signing_key.c
@@ -0,0 +1,64 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_signing_key.c
+ * @brief Implementation of the lookup_signing_key function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_signing_key.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_signing_key (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_EXCHANGEDB_SignkeyMetaData *meta)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("valid_from",
+ &meta->start),
+ GNUNET_PQ_result_spec_timestamp ("expire_sign",
+ &meta->expire_sign),
+ GNUNET_PQ_result_spec_timestamp ("expire_legal",
+ &meta->expire_legal),
+ GNUNET_PQ_result_spec_end
+ };
+
+
+ PREPARE (pg,
+ "lookup_signing_key",
+ "SELECT"
+ " valid_from"
+ ",expire_sign"
+ ",expire_legal"
+ " FROM exchange_sign_keys"
+ " WHERE exchange_pub=$1");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_signing_key",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_signing_key.h b/src/exchangedb/pg_lookup_signing_key.h
new file mode 100644
index 000000000..487d60d2b
--- /dev/null
+++ b/src/exchangedb/pg_lookup_signing_key.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_signing_key.h
+ * @brief implementation of the lookup_signing_key function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_SIGNING_KEY_H
+#define PG_LOOKUP_SIGNING_KEY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup signing key meta data.
+ *
+ * @param cls closure
+ * @param exchange_pub the exchange online signing public key
+ * @param[out] meta meta data about @a exchange_pub
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_signing_key (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_EXCHANGEDB_SignkeyMetaData *meta);
+#endif
diff --git a/src/exchangedb/pg_lookup_signkey_revocation.c b/src/exchangedb/pg_lookup_signkey_revocation.c
new file mode 100644
index 000000000..056ecddc4
--- /dev/null
+++ b/src/exchangedb/pg_lookup_signkey_revocation.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_signkey_revocation.c
+ * @brief Implementation of the lookup_signkey_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_signkey_revocation.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_signkey_revocation (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (exchange_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "lookup_signkey_revocation",
+ "SELECT "
+ " master_sig"
+ " FROM signkey_revocations"
+ " WHERE esk_serial="
+ " (SELECT esk_serial"
+ " FROM exchange_sign_keys"
+ " WHERE exchange_pub=$1);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_signkey_revocation",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_signkey_revocation.h b/src/exchangedb/pg_lookup_signkey_revocation.h
new file mode 100644
index 000000000..de0fb1d74
--- /dev/null
+++ b/src/exchangedb/pg_lookup_signkey_revocation.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_signkey_revocation.h
+ * @brief implementation of the lookup_signkey_revocation function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_SIGNKEY_REVOCATION_H
+#define PG_LOOKUP_SIGNKEY_REVOCATION_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Obtain information about a revoked online signing key.
+ *
+ * @param cls closure
+ * @param exchange_pub exchange online signing key
+ * @param[out] master_sig set to signature affirming the revocation (if revoked)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_signkey_revocation (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_transfer_by_deposit.c b/src/exchangedb/pg_lookup_transfer_by_deposit.c
new file mode 100644
index 000000000..192556130
--- /dev/null
+++ b/src/exchangedb/pg_lookup_transfer_by_deposit.c
@@ -0,0 +1,239 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_transfer_by_deposit.c
+ * @brief Implementation of the lookup_transfer_by_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_transfer_by_deposit.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_transfer_by_deposit (
+ void *cls,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ bool *pending,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Timestamp *exec_time,
+ struct TALER_Amount *amount_with_fee,
+ struct TALER_Amount *deposit_fee,
+ struct TALER_EXCHANGEDB_KycStatus *kyc,
+ enum TALER_AmlDecisionState *aml_decision)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_end
+ };
+ char *payto_uri;
+ struct TALER_WireSaltP wire_salt;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
+ wtid),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+ &wire_salt),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ exec_time),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ deposit_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ memset (kyc,
+ 0,
+ sizeof (*kyc));
+ /* check if the aggregation record exists and get it */
+ PREPARE (pg,
+ "lookup_deposit_wtid",
+ "SELECT"
+ " atr.wtid_raw"
+ ",wire_out.execution_date"
+ ",cdep.amount_with_fee"
+ ",bdep.wire_salt"
+ ",wt.payto_uri"
+ ",denom.fee_deposit"
+ " FROM coin_deposits cdep"
+ " JOIN batch_deposits bdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN wire_targets wt"
+ " USING (wire_target_h_payto)"
+ " JOIN aggregation_tracking atr"
+ " ON (cdep.batch_deposit_serial_id = atr.batch_deposit_serial_id)"
+ " JOIN known_coins kc"
+ " ON (kc.coin_pub = cdep.coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " JOIN wire_out"
+ " USING (wtid_raw)"
+ " WHERE cdep.coin_pub=$1"
+ " AND bdep.merchant_pub=$3"
+ " AND bdep.h_contract_terms=$2");
+ /* NOTE: above query might be more efficient if we computed the shard
+ from the merchant_pub and included that in the query */
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_deposit_wtid",
+ params,
+ rs);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ struct TALER_MerchantWireHashP wh;
+
+ TALER_merchant_wire_signature_hash (payto_uri,
+ &wire_salt,
+ &wh);
+ if (0 ==
+ GNUNET_memcmp (&wh,
+ h_wire))
+ {
+ *pending = false;
+ kyc->ok = true;
+ *aml_decision = TALER_AML_NORMAL;
+ GNUNET_PQ_cleanup_result (rs);
+ return qs;
+ }
+ qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ GNUNET_PQ_cleanup_result (rs);
+ }
+ if (0 > qs)
+ return qs;
+ *pending = true;
+ memset (wtid,
+ 0,
+ sizeof (*wtid));
+ GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "lookup_deposit_wtid returned 0 matching rows\n");
+ {
+ /* Check if transaction exists in deposits, so that we just
+ do not have a WTID yet. In that case, return without wtid
+ (by setting 'pending' true). */
+ uint32_t status32 = TALER_AML_NORMAL;
+ uint64_t aml_kyc_row = 0;
+ struct GNUNET_PQ_ResultSpec rs2[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+ &wire_salt),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ /* See Postgresql bug #18380 */
+#define BUG 1
+#if BUG
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("legitimization_requirement_serial_id",
+ &kyc->requirement_row),
+ NULL),
+#endif
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("kyc_requirement",
+ &aml_kyc_row),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("status",
+ &status32),
+ NULL),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ deposit_fee),
+ GNUNET_PQ_result_spec_timestamp ("wire_deadline",
+ exec_time),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "get_deposit_without_wtid",
+ "SELECT"
+ " bdep.wire_salt"
+ ",wt.payto_uri"
+ ",cdep.amount_with_fee"
+ ",denom.fee_deposit"
+ ",bdep.wire_deadline"
+#if BUG
+ ",agt.legitimization_requirement_serial_id"
+#endif
+ ",aml.status"
+ ",aml.kyc_requirement"
+ " FROM coin_deposits cdep"
+ " JOIN batch_deposits bdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN wire_targets wt"
+ " USING (wire_target_h_payto)"
+ " JOIN known_coins kc"
+ " ON (kc.coin_pub = cdep.coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+#if BUG
+ " LEFT JOIN aggregation_transient agt "
+ " ON ( (bdep.wire_target_h_payto = agt.wire_target_h_payto) AND"
+ " (bdep.merchant_pub = agt.merchant_pub) )"
+#endif
+ " LEFT JOIN aml_status aml"
+ " ON (wt.wire_target_h_payto = aml.h_payto)"
+ " WHERE cdep.coin_pub=$1"
+ " AND bdep.merchant_pub=$3"
+ " AND bdep.h_contract_terms=$2"
+ " LIMIT 1;");
+ /* NOTE: above query might be more efficient if we computed the shard
+ from the merchant_pub and included that in the query */
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_deposit_without_wtid",
+ params,
+ rs2);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ struct TALER_MerchantWireHashP wh;
+
+ *aml_decision = (enum TALER_AmlDecisionState) status32;
+ if (0 == kyc->requirement_row)
+ kyc->ok = true; /* technically: unknown */
+ if ( (kyc->ok) &&
+ (TALER_AML_FROZEN == *aml_decision) &&
+ (0 != aml_kyc_row) )
+ {
+ /* KYC required via AML */
+ kyc->ok = false;
+ kyc->requirement_row = aml_kyc_row;
+ }
+ TALER_merchant_wire_signature_hash (payto_uri,
+ &wire_salt,
+ &wh);
+ if (0 !=
+ GNUNET_memcmp (&wh,
+ h_wire))
+ {
+ GNUNET_PQ_cleanup_result (rs2);
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ GNUNET_PQ_cleanup_result (rs2);
+ }
+ *aml_decision = TALER_AML_NORMAL;
+ return qs;
+ }
+}
diff --git a/src/exchangedb/pg_lookup_transfer_by_deposit.h b/src/exchangedb/pg_lookup_transfer_by_deposit.h
new file mode 100644
index 000000000..83782d5a0
--- /dev/null
+++ b/src/exchangedb/pg_lookup_transfer_by_deposit.h
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_transfer_by_deposit.h
+ * @brief implementation of the lookup_transfer_by_deposit function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_TRANSFER_BY_DEPOSIT_H
+#define PG_LOOKUP_TRANSFER_BY_DEPOSIT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Try to find the wire transfer details for a deposit operation.
+ * If we did not execute the deposit yet, return when it is supposed
+ * to be executed.
+ *
+ * @param cls closure
+ * @param h_contract_terms hash of the proposal data
+ * @param h_wire hash of merchant wire details
+ * @param coin_pub public key of deposited coin
+ * @param merchant_pub merchant public key
+ * @param[out] pending set to true if the transaction is still pending
+ * @param[out] wtid wire transfer identifier, only set if @a pending is false
+ * @param[out] exec_time when was the transaction done, or
+ * when we expect it to be done (if @a pending is false)
+ * @param[out] amount_with_fee set to the total deposited amount
+ * @param[out] deposit_fee set to how much the exchange did charge for the deposit
+ * @param[out] kyc set to the kyc status of the receiver (if @a pending)
+ * @param[out] aml_decision set to the AML status of the receiver
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_transfer_by_deposit (
+ void *cls,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_MerchantWireHashP *h_wire,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ bool *pending,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ struct GNUNET_TIME_Timestamp *exec_time,
+ struct TALER_Amount *amount_with_fee,
+ struct TALER_Amount *deposit_fee,
+ struct TALER_EXCHANGEDB_KycStatus *kyc,
+ enum TALER_AmlDecisionState *aml_decision);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_wire_fee_by_time.c b/src/exchangedb/pg_lookup_wire_fee_by_time.c
new file mode 100644
index 000000000..775232a48
--- /dev/null
+++ b/src/exchangedb/pg_lookup_wire_fee_by_time.c
@@ -0,0 +1,156 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_wire_fee_by_time.c
+ * @brief Implementation of the lookup_wire_fee_by_time function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_wire_fee_by_time.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #wire_fee_by_time_helper()
+ */
+struct WireFeeLookupContext
+{
+
+ /**
+ * Set to the wire fees. Set to invalid if fees conflict over
+ * the given time period.
+ */
+ struct TALER_WireFeeSet *fees;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+};
+
+
+/**
+ * Helper function for #TEH_PG_lookup_wire_fee_by_time().
+ * Calls the callback with the wire fee structure.
+ *
+ * @param cls a `struct WireFeeLookupContext`
+ * @param result db results
+ * @param num_results number of results in @a result
+ */
+static void
+wire_fee_by_time_helper (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct WireFeeLookupContext *wlc = cls;
+ struct PostgresClosure *pg = wlc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_WireFeeSet fs;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
+ &fs.wire),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+ &fs.closing),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ /* invalidate */
+ memset (wlc->fees,
+ 0,
+ sizeof (struct TALER_WireFeeSet));
+ return;
+ }
+ if (0 == i)
+ {
+ *wlc->fees = fs;
+ continue;
+ }
+ if (0 !=
+ TALER_wire_fee_set_cmp (&fs,
+ wlc->fees))
+ {
+ /* invalidate */
+ memset (wlc->fees,
+ 0,
+ sizeof (struct TALER_WireFeeSet));
+ return;
+ }
+ }
+}
+
+
+/**
+ * Lookup information about known wire fees. Finds all applicable
+ * fees in the given range. If they are identical, returns the
+ * respective @a fees. If any of the fees
+ * differ between @a start_time and @a end_time, the transaction
+ * succeeds BUT returns an invalid amount for both fees.
+ *
+ * @param cls closure
+ * @param wire_method the wire method to lookup fees for
+ * @param start_time starting time of fee
+ * @param end_time end time of fee
+ * @param[out] fees wire fees for that time period; if
+ * different fees exists within this time
+ * period, an 'invalid' amount is returned.
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_fee_by_time (
+ void *cls,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ struct TALER_WireFeeSet *fees)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (wire_method),
+ GNUNET_PQ_query_param_timestamp (&start_time),
+ GNUNET_PQ_query_param_timestamp (&end_time),
+ GNUNET_PQ_query_param_end
+ };
+ struct WireFeeLookupContext wlc = {
+ .fees = fees,
+ .pg = pg
+ };
+ /* used in #postgres_lookup_wire_fee_by_time() */
+ PREPARE (pg,
+ "lookup_wire_fee_by_time",
+ "SELECT"
+ " wire_fee"
+ ",closing_fee"
+ " FROM wire_fee"
+ " WHERE wire_method=$1"
+ " AND end_date > $2"
+ " AND start_date < $3;");
+ return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_wire_fee_by_time",
+ params,
+ &wire_fee_by_time_helper,
+ &wlc);
+}
diff --git a/src/exchangedb/pg_lookup_wire_fee_by_time.h b/src/exchangedb/pg_lookup_wire_fee_by_time.h
new file mode 100644
index 000000000..cbfc36e76
--- /dev/null
+++ b/src/exchangedb/pg_lookup_wire_fee_by_time.h
@@ -0,0 +1,76 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_wire_fee_by_time.h
+ * @brief implementation of the lookup_wire_fee_by_time function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_WIRE_FEE_BY_TIME_H
+#define PG_LOOKUP_WIRE_FEE_BY_TIME_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup information about known wire fees. Finds all applicable
+ * fees in the given range. If they are identical, returns the
+ * respective @a fees. If any of the fees
+ * differ between @a start_time and @a end_time, the transaction
+ * succeeds BUT returns an invalid amount for both fees.
+ *
+ * @param cls closure
+ * @param wire_method the wire method to lookup fees for
+ * @param start_time starting time of fee
+ * @param end_time end time of fee
+ * @param[out] fees wire fees for that time period; if
+ * different fees exists within this time
+ * period, an 'invalid' amount is returned.
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_fee_by_time (
+ void *cls,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ struct TALER_WireFeeSet *fees);
+
+/**
+ * Lookup information about known wire fees. Finds all applicable
+ * fees in the given range. If they are identical, returns the
+ * respective @a fees. If any of the fees
+ * differ between @a start_time and @a end_time, the transaction
+ * succeeds BUT returns an invalid amount for both fees.
+ *
+ * @param cls closure
+ * @param wire_method the wire method to lookup fees for
+ * @param start_time starting time of fee
+ * @param end_time end time of fee
+ * @param[out] fees wire fees for that time period; if
+ * different fees exists within this time
+ * period, an 'invalid' amount is returned.
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_fee_by_time (
+ void *cls,
+ const char *wire_method,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Timestamp end_time,
+ struct TALER_WireFeeSet *fees);
+#endif
diff --git a/src/exchangedb/pg_lookup_wire_timestamp.c b/src/exchangedb/pg_lookup_wire_timestamp.c
new file mode 100644
index 000000000..17dffc706
--- /dev/null
+++ b/src/exchangedb/pg_lookup_wire_timestamp.c
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_wire_timestamp.c
+ * @brief Implementation of the lookup_wire_timestamp function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_wire_timestamp.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_timestamp (void *cls,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp *last_date)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("last_change",
+ last_date),
+ GNUNET_PQ_result_spec_end
+ };
+
+
+ PREPARE (pg,
+ "lookup_wire_timestamp",
+ "SELECT"
+ " last_change"
+ " FROM wire_accounts"
+ " WHERE payto_uri=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_wire_timestamp",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_lookup_wire_timestamp.h b/src/exchangedb/pg_lookup_wire_timestamp.h
new file mode 100644
index 000000000..f2ee117de
--- /dev/null
+++ b/src/exchangedb/pg_lookup_wire_timestamp.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_wire_timestamp.h
+ * @brief implementation of the lookup_wire_timestamp function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_WIRE_TIMESTAMP_H
+#define PG_LOOKUP_WIRE_TIMESTAMP_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Check the last date an exchange wire account was modified.
+ *
+ * @param cls closure
+ * @param payto_uri key to look up information for
+ * @param[out] last_date last modification date to auditor status
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_timestamp (void *cls,
+ const char *payto_uri,
+ struct GNUNET_TIME_Timestamp *last_date);
+
+#endif
diff --git a/src/exchangedb/pg_lookup_wire_transfer.c b/src/exchangedb/pg_lookup_wire_transfer.c
new file mode 100644
index 000000000..7ab023fe7
--- /dev/null
+++ b/src/exchangedb/pg_lookup_wire_transfer.c
@@ -0,0 +1,188 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_wire_transfer.c
+ * @brief Implementation of the lookup_wire_transfer function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_lookup_wire_transfer.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #handle_wt_result.
+ */
+struct WireTransferResultContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_AggregationDataCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on serious errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results. Helper function
+ * for #TEH_PG_lookup_wire_transfer().
+ *
+ * @param cls closure of type `struct WireTransferResultContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+handle_wt_result (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct WireTransferResultContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_PaytoHashP h_payto;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_TIME_Timestamp exec_time;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount deposit_fee;
+ struct TALER_DenominationPublicKey denom_pub;
+ char *payto_uri;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("aggregation_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_target_h_payto",
+ &h_payto),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &merchant_pub),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &exec_time),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
+ &deposit_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ rowid,
+ &merchant_pub,
+ payto_uri,
+ &h_payto,
+ exec_time,
+ &h_contract_terms,
+ &denom_pub,
+ &coin_pub,
+ &amount_with_fee,
+ &deposit_fee);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_transfer (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_EXCHANGEDB_AggregationDataCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_end
+ };
+ struct WireTransferResultContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "lookup_transactions",
+ "SELECT"
+ " aggregation_serial_id"
+ ",bdep.h_contract_terms"
+ ",payto_uri"
+ ",wire_targets.wire_target_h_payto"
+ ",kc.coin_pub"
+ ",bdep.merchant_pub"
+ ",wire_out.execution_date"
+ ",cdep.amount_with_fee"
+ ",denom.fee_deposit"
+ ",denom.denom_pub"
+ " FROM aggregation_tracking"
+ " JOIN batch_deposits bdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN coin_deposits cdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN wire_targets"
+ " USING (wire_target_h_payto)"
+ " JOIN known_coins kc"
+ " USING (coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " JOIN wire_out"
+ " USING (wtid_raw)"
+ " WHERE wtid_raw=$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_transactions",
+ params,
+ &handle_wt_result,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_lookup_wire_transfer.h b/src/exchangedb/pg_lookup_wire_transfer.h
new file mode 100644
index 000000000..ae5355f40
--- /dev/null
+++ b/src/exchangedb/pg_lookup_wire_transfer.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_lookup_wire_transfer.h
+ * @brief implementation of the lookup_wire_transfer function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_WIRE_TRANSFER_H
+#define PG_LOOKUP_WIRE_TRANSFER_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Lookup the list of Taler transactions that were aggregated
+ * into a wire transfer by the respective @a wtid.
+ *
+ * @param cls closure
+ * @param wtid the raw wire transfer identifier we used
+ * @param cb function to call on each transaction found
+ * @param cb_cls closure for @a cb
+ * @return query status of the transaction
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_lookup_wire_transfer (
+ void *cls,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ TALER_EXCHANGEDB_AggregationDataCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_persist_policy_details.c b/src/exchangedb/pg_persist_policy_details.c
new file mode 100644
index 000000000..d97b92eac
--- /dev/null
+++ b/src/exchangedb/pg_persist_policy_details.c
@@ -0,0 +1,78 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_persist_policy_details.c
+ * @brief Implementation of the persist_policy_details function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_persist_policy_details.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_persist_policy_details (
+ void *cls,
+ const struct TALER_PolicyDetails *details,
+ uint64_t *policy_details_serial_id,
+ struct TALER_Amount *accumulated_total,
+ enum TALER_PolicyFulfillmentState *fulfillment_state)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&details->hash_code),
+ TALER_PQ_query_param_json (details->policy_json),
+ GNUNET_PQ_query_param_timestamp (&details->deadline),
+ TALER_PQ_query_param_amount (pg->conn,
+ &details->commitment),
+ TALER_PQ_query_param_amount (pg->conn,
+ &details->accumulated_total),
+ TALER_PQ_query_param_amount (pg->conn,
+ &details->policy_fee),
+ TALER_PQ_query_param_amount (pg->conn,
+ &details->transferable_amount),
+ GNUNET_PQ_query_param_auto_from_type (&details->fulfillment_state),
+ (details->no_policy_fulfillment_id)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_uint64 (&details->policy_fulfillment_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("policy_details_serial_id",
+ policy_details_serial_id),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("accumulated_total",
+ accumulated_total),
+ GNUNET_PQ_result_spec_uint32 ("fulfillment_state",
+ fulfillment_state),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "call_insert_or_update_policy_details",
+ "SELECT"
+ " out_policy_details_serial_id AS policy_details_serial_id"
+ ",out_accumulated_total AS accumulated_total"
+ ",out_fulfillment_state AS fulfillment_state"
+ " FROM exchange_do_insert_or_update_policy_details"
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9);");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "call_insert_or_update_policy_details",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_persist_policy_details.h b/src/exchangedb/pg_persist_policy_details.h
new file mode 100644
index 000000000..4fe709d92
--- /dev/null
+++ b/src/exchangedb/pg_persist_policy_details.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_persist_policy_details.h
+ * @brief implementation of the persist_policy_details function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_PERSIST_POLICY_DETAILS_H
+#define PG_PERSIST_POLICY_DETAILS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/* Persist the details to a policy in the policy_details table. If there
+ * already exists a policy, update the fields accordingly.
+ *
+ * @param details The policy details that should be persisted. If an entry for
+ * the given details->hash_code exists, the values will be updated.
+ * @param[out] policy_details_serial_id The row ID of the policy details
+ * @param[out] accumulated_total The total amount accumulated in that policy
+ * @param[out] fulfillment_state The state of policy. If the state was Insufficient prior to the call and the provided deposit raises the accumulated_total above the commitment, it will be set to Ready.
+ * @return query execution status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_persist_policy_details (
+ void *cls,
+ const struct TALER_PolicyDetails *details,
+ uint64_t *policy_details_serial_id,
+ struct TALER_Amount *accumulated_total,
+ enum TALER_PolicyFulfillmentState *fulfillment_state);
+
+#endif
diff --git a/src/exchangedb/pg_preflight.c b/src/exchangedb/pg_preflight.c
new file mode 100644
index 000000000..4533c9a97
--- /dev/null
+++ b/src/exchangedb/pg_preflight.c
@@ -0,0 +1,69 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_preflight.c
+ * @brief Implementation of the preflight function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_preflight.h"
+#include "pg_helper.h"
+
+
+/**
+ * Connect to the database if the connection does not exist yet.
+ *
+ * @param pg the plugin-specific state
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_internal_setup (struct PostgresClosure *pg);
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_preflight (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("ROLLBACK"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (GNUNET_OK !=
+ TEH_PG_internal_setup (pg))
+ return GNUNET_SYSERR;
+ if (NULL == pg->transaction_name)
+ return GNUNET_OK; /* all good */
+ if (GNUNET_OK ==
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "BUG: Preflight check rolled back transaction `%s'!\n",
+ pg->transaction_name);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "BUG: Preflight check failed to rollback transaction `%s'!\n",
+ pg->transaction_name);
+ }
+ pg->transaction_name = NULL;
+ return GNUNET_NO;
+}
diff --git a/src/exchangedb/pg_preflight.h b/src/exchangedb/pg_preflight.h
new file mode 100644
index 000000000..ba994f1e6
--- /dev/null
+++ b/src/exchangedb/pg_preflight.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_preflight.h
+ * @brief implementation of the preflight function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_PREFLIGTH_H
+#define PG_PREFLIGTH_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Do a pre-flight check that we are not in an uncommitted transaction.
+ * If we are, try to commit the previous transaction and output a warning.
+ * Does not return anything, as we will continue regardless of the outcome.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return #GNUNET_OK if everything is fine
+ * #GNUNET_NO if a transaction was rolled back
+ * #GNUNET_SYSERR on hard errors
+ */
+
+
+enum GNUNET_GenericReturnValue
+TEH_PG_preflight (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_profit_drains_get_pending.c b/src/exchangedb/pg_profit_drains_get_pending.c
new file mode 100644
index 000000000..c844a3f38
--- /dev/null
+++ b/src/exchangedb/pg_profit_drains_get_pending.c
@@ -0,0 +1,78 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_profit_drains_get_pending.c
+ * @brief Implementation of the profit_drains_get_pending function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_profit_drains_get_pending.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_profit_drains_get_pending (
+ void *cls,
+ uint64_t *serial,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ char **account_section,
+ char **payto_uri,
+ struct GNUNET_TIME_Timestamp *request_timestamp,
+ struct TALER_Amount *amount,
+ struct TALER_MasterSignatureP *master_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("profit_drain_serial_id",
+ serial),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid",
+ wtid),
+ GNUNET_PQ_result_spec_string ("account_section",
+ account_section),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ payto_uri),
+ GNUNET_PQ_result_spec_timestamp ("trigger_date",
+ request_timestamp),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ amount),
+ GNUNET_PQ_result_spec_auto_from_type ("master_sig",
+ master_sig),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_profit_drains_get_pending() */
+ PREPARE (pg,
+ "get_ready_profit_drain",
+ "SELECT"
+ " profit_drain_serial_id"
+ ",wtid"
+ ",account_section"
+ ",payto_uri"
+ ",trigger_date"
+ ",amount"
+ ",master_sig"
+ " FROM profit_drains"
+ " WHERE NOT executed"
+ " ORDER BY trigger_date ASC;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_ready_profit_drain",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_profit_drains_get_pending.h b/src/exchangedb/pg_profit_drains_get_pending.h
new file mode 100644
index 000000000..cd793a129
--- /dev/null
+++ b/src/exchangedb/pg_profit_drains_get_pending.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_profit_drains_get_pending.h
+ * @brief implementation of the profit_drains_get_pending function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_PROFIT_DRAINS_GET_PENDING_H
+#define PG_PROFIT_DRAINS_GET_PENDING_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Get profit drain operation ready to execute.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param[out] serial set to serial ID of the entry
+ * @param[out] wtid set set to wire transfer ID to use
+ * @param[out] account_section set to account to drain
+ * @param[out] payto_uri set to account to wire funds to
+ * @param[out] request_timestamp set to time of the signature
+ * @param[out] amount set to amount to wire
+ * @param[out] master_sig set to signature affirming the operation
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_profit_drains_get_pending (
+ void *cls,
+ uint64_t *serial,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ char **account_section,
+ char **payto_uri,
+ struct GNUNET_TIME_Timestamp *request_timestamp,
+ struct TALER_Amount *amount,
+ struct TALER_MasterSignatureP *master_sig);
+
+#endif
diff --git a/src/exchangedb/pg_profit_drains_set_finished.c b/src/exchangedb/pg_profit_drains_set_finished.c
new file mode 100644
index 000000000..f0de27945
--- /dev/null
+++ b/src/exchangedb/pg_profit_drains_set_finished.c
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_profit_drains_set_finished.c
+ * @brief Implementation of the profit_drains_set_finished function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_profit_drains_set_finished.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_profit_drains_set_finished (
+ void *cls,
+ uint64_t serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "drain_profit_set_finished",
+ "UPDATE profit_drains"
+ " SET"
+ " executed=TRUE"
+ " WHERE profit_drain_serial_id=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "drain_profit_set_finished",
+ params);
+}
diff --git a/src/exchangedb/pg_profit_drains_set_finished.h b/src/exchangedb/pg_profit_drains_set_finished.h
new file mode 100644
index 000000000..b0878b5b8
--- /dev/null
+++ b/src/exchangedb/pg_profit_drains_set_finished.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_profit_drains_set_finished.h
+ * @brief implementation of the profit_drains_set_finished function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_PROFIT_DRAINS_SET_FINISHED_H
+#define PG_PROFIT_DRAINS_SET_FINISHED_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Set profit drain operation to finished.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param serial serial ID of the entry to mark finished
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_profit_drains_set_finished (
+ void *cls,
+ uint64_t serial);
+
+#endif
diff --git a/src/exchangedb/pg_release_revolving_shard.c b/src/exchangedb/pg_release_revolving_shard.c
new file mode 100644
index 000000000..43e45c4bc
--- /dev/null
+++ b/src/exchangedb/pg_release_revolving_shard.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_release_revolving_shard.c
+ * @brief Implementation of the release_revolving_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_release_revolving_shard.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_release_revolving_shard (void *cls,
+ const char *job_name,
+ uint32_t start_row,
+ uint32_t end_row)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (job_name),
+ GNUNET_PQ_query_param_uint32 (&start_row),
+ GNUNET_PQ_query_param_uint32 (&end_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Releasing revolving shard %s %u-%u\n",
+ job_name,
+ (unsigned int) start_row,
+ (unsigned int) end_row);
+
+
+ PREPARE (pg,
+ "release_revolving_shard",
+ "UPDATE revolving_work_shards"
+ " SET active=FALSE"
+ " WHERE job_name=$1"
+ " AND start_row=$2"
+ " AND end_row=$3");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "release_revolving_shard",
+ params);
+}
diff --git a/src/exchangedb/pg_release_revolving_shard.h b/src/exchangedb/pg_release_revolving_shard.h
new file mode 100644
index 000000000..ea65ab605
--- /dev/null
+++ b/src/exchangedb/pg_release_revolving_shard.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_release_revolving_shard.h
+ * @brief implementation of the release_revolving_shard function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RELEASE_REVOLVING_SHARD_H
+#define PG_RELEASE_REVOLVING_SHARD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called to release a revolving shard
+ * back into the work pool. Clears the
+ * "completed" flag.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param job_name name of the operation to grab a word shard for
+ * @param start_row inclusive start row of the shard
+ * @param end_row exclusive end row of the shard
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_release_revolving_shard (void *cls,
+ const char *job_name,
+ uint32_t start_row,
+ uint32_t end_row);
+
+#endif
diff --git a/src/exchangedb/pg_reserves_get.c b/src/exchangedb/pg_reserves_get.c
new file mode 100644
index 000000000..cae4764a5
--- /dev/null
+++ b/src/exchangedb/pg_reserves_get.c
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_reserves_get.c
+ * @brief Implementation of the reserves_get function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_reserves_get.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_get (void *cls,
+ struct TALER_EXCHANGEDB_Reserve *reserve)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount ("current_balance",
+ pg->currency,
+ &reserve->balance),
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &reserve->expiry),
+ GNUNET_PQ_result_spec_timestamp ("gc_date",
+ &reserve->gc),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_reserves_get() */
+ PREPARE (pg,
+ "reserves_get",
+ "SELECT"
+ " current_balance"
+ ",expiration_date"
+ ",gc_date"
+ " FROM reserves"
+ " WHERE reserve_pub=$1"
+ " LIMIT 1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "reserves_get",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_reserves_get.h b/src/exchangedb/pg_reserves_get.h
new file mode 100644
index 000000000..8a96d53ed
--- /dev/null
+++ b/src/exchangedb/pg_reserves_get.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_reserves_get.h
+ * @brief implementation of the reserves_get function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RESERVES_GET_H
+#define PG_RESERVES_GET_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Get the summary of a reserve.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param[in,out] reserve the reserve data. The public key of the reserve should be
+ * set in this structure; it is used to query the database. The balance
+ * and expiration are then filled accordingly.
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_get (void *cls,
+ struct TALER_EXCHANGEDB_Reserve *reserve);
+
+#endif
diff --git a/src/exchangedb/pg_reserves_get_origin.c b/src/exchangedb/pg_reserves_get_origin.c
new file mode 100644
index 000000000..55d3179d1
--- /dev/null
+++ b/src/exchangedb/pg_reserves_get_origin.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_reserves_get_origin.c
+ * @brief Implementation of the reserves_get_origin function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_reserves_get_origin.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_get_origin (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_PaytoHashP *h_payto)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("wire_source_h_payto",
+ h_payto),
+ GNUNET_PQ_result_spec_end
+ };
+
+
+ PREPARE (pg,
+ "get_h_wire_source_of_reserve",
+ "SELECT"
+ " wire_source_h_payto"
+ " FROM reserves_in"
+ " WHERE reserve_pub=$1");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "get_h_wire_source_of_reserve",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_reserves_get_origin.h b/src/exchangedb/pg_reserves_get_origin.h
new file mode 100644
index 000000000..22085d8f0
--- /dev/null
+++ b/src/exchangedb/pg_reserves_get_origin.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_reserves_get_origin.h
+ * @brief implementation of the reserves_get_origin function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RESERVES_GET_ORIGIN_H
+#define PG_RESERVES_GET_ORIGIN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Get the origin of funds of a reserve.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve_pub public key of the reserve
+ * @param[out] h_payto set to hash of the wire source payto://-URI
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_get_origin (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_PaytoHashP *h_payto);
+
+#endif
diff --git a/src/exchangedb/pg_reserves_in_insert.c b/src/exchangedb/pg_reserves_in_insert.c
new file mode 100644
index 000000000..21734942a
--- /dev/null
+++ b/src/exchangedb/pg_reserves_in_insert.c
@@ -0,0 +1,373 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_reserves_in_insert.c
+ * @brief Implementation of the reserves_in_insert function for Postgres
+ * @author Christian Grothoff
+ * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_reserves_in_insert.h"
+#include "pg_helper.h"
+#include "pg_start.h"
+#include "pg_start_read_committed.h"
+#include "pg_commit.h"
+#include "pg_preflight.h"
+#include "pg_rollback.h"
+#include "pg_reserves_get.h"
+#include "pg_reserves_update.h"
+#include "pg_setup_wire_target.h"
+#include "pg_event_notify.h"
+
+
+/**
+ * Generate event notification for the reserve change.
+ *
+ * @param reserve_pub reserve to notfiy on
+ * @return string to pass to postgres for the notification
+ */
+static char *
+compute_notify_on_reserve (const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+ struct TALER_ReserveEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
+ .reserve_pub = *reserve_pub
+ };
+
+ return GNUNET_PQ_get_event_notify_channel (&rep.header);
+}
+
+
+/**
+ * Closure for our helper_cb()
+ */
+struct Context
+{
+ /**
+ * Array of reserve UUIDs to initialize.
+ */
+ uint64_t *reserve_uuids;
+
+ /**
+ * Array with entries set to 'true' for duplicate transactions.
+ */
+ bool *transaction_duplicates;
+
+ /**
+ * Array with entries set to 'true' for rows with conflicts.
+ */
+ bool *conflicts;
+
+ /**
+ * Set to #GNUNET_SYSERR on failures.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+ /**
+ * Single value (no array) set to true if we need
+ * to follow-up with an update.
+ */
+ bool needs_update;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct Context *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct Context *ctx = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool (
+ "transaction_duplicate",
+ &ctx->transaction_duplicates[i]),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("ruuid",
+ &ctx->reserve_uuids[i]),
+ &ctx->conflicts[i]),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ if (! ctx->transaction_duplicates[i])
+ ctx->needs_update |= ctx->conflicts[i];
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_in_insert (
+ void *cls,
+ const struct TALER_EXCHANGEDB_ReserveInInfo *reserves,
+ unsigned int reserves_length,
+ enum GNUNET_DB_QueryStatus *results)
+{
+ struct PostgresClosure *pg = cls;
+ unsigned int dups = 0;
+
+ struct TALER_PaytoHashP h_paytos[GNUNET_NZL (reserves_length)];
+ char *notify_s[GNUNET_NZL (reserves_length)];
+ struct TALER_ReservePublicKeyP reserve_pubs[GNUNET_NZL (reserves_length)];
+ struct TALER_Amount balances[GNUNET_NZL (reserves_length)];
+ struct GNUNET_TIME_Timestamp execution_times[GNUNET_NZL (reserves_length)];
+ const char *sender_account_details[GNUNET_NZL (reserves_length)];
+ const char *exchange_account_names[GNUNET_NZL (reserves_length)];
+ uint64_t wire_references[GNUNET_NZL (reserves_length)];
+ uint64_t reserve_uuids[GNUNET_NZL (reserves_length)];
+ bool transaction_duplicates[GNUNET_NZL (reserves_length)];
+ bool conflicts[GNUNET_NZL (reserves_length)];
+ struct GNUNET_TIME_Timestamp reserve_expiration
+ = GNUNET_TIME_relative_to_timestamp (pg->idle_reserve_expiration_time);
+ struct GNUNET_TIME_Timestamp gc
+ = GNUNET_TIME_relative_to_timestamp (pg->legal_reserve_expiration_time);
+ enum GNUNET_DB_QueryStatus qs;
+ bool need_update;
+
+ for (unsigned int i = 0; i<reserves_length; i++)
+ {
+ const struct TALER_EXCHANGEDB_ReserveInInfo *reserve = &reserves[i];
+
+ TALER_payto_hash (reserve->sender_account_details,
+ &h_paytos[i]);
+ notify_s[i] = compute_notify_on_reserve (reserve->reserve_pub);
+ reserve_pubs[i] = *reserve->reserve_pub;
+ balances[i] = *reserve->balance;
+ execution_times[i] = reserve->execution_time;
+ sender_account_details[i] = reserve->sender_account_details;
+ exchange_account_names[i] = reserve->exchange_account_name;
+ wire_references[i] = reserve->wire_reference;
+ }
+
+ /* NOTE: kind-of pointless to explicitly start a transaction here... */
+ if (GNUNET_OK !=
+ TEH_PG_preflight (pg))
+ {
+ GNUNET_break (0);
+ qs = GNUNET_DB_STATUS_HARD_ERROR;
+ goto finished;
+ }
+ if (GNUNET_OK !=
+ TEH_PG_start_read_committed (pg,
+ "READ_COMMITED"))
+ {
+ GNUNET_break (0);
+ qs = GNUNET_DB_STATUS_HARD_ERROR;
+ goto finished;
+ }
+ PREPARE (pg,
+ "reserves_insert_with_array",
+ "SELECT"
+ " transaction_duplicate"
+ ",ruuid"
+ " FROM exchange_do_array_reserves_insert"
+ " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10);");
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&gc),
+ GNUNET_PQ_query_param_timestamp (&reserve_expiration),
+ GNUNET_PQ_query_param_array_auto_from_type (reserves_length,
+ reserve_pubs,
+ pg->conn),
+ GNUNET_PQ_query_param_array_uint64 (reserves_length,
+ wire_references,
+ pg->conn),
+ TALER_PQ_query_param_array_amount (
+ reserves_length,
+ balances,
+ pg->conn),
+ GNUNET_PQ_query_param_array_ptrs_string (
+ reserves_length,
+ (const char **) exchange_account_names,
+ pg->conn),
+ GNUNET_PQ_query_param_array_timestamp (
+ reserves_length,
+ execution_times,
+ pg->conn),
+ GNUNET_PQ_query_param_array_auto_from_type (
+ reserves_length,
+ h_paytos,
+ pg->conn),
+ GNUNET_PQ_query_param_array_ptrs_string (
+ reserves_length,
+ (const char **) sender_account_details,
+ pg->conn),
+ GNUNET_PQ_query_param_array_ptrs_string (
+ reserves_length,
+ (const char **) notify_s,
+ pg->conn),
+ GNUNET_PQ_query_param_end
+ };
+ struct Context ctx = {
+ .reserve_uuids = reserve_uuids,
+ .transaction_duplicates = transaction_duplicates,
+ .conflicts = conflicts,
+ .needs_update = false,
+ .status = GNUNET_OK
+ };
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "reserves_insert_with_array",
+ params,
+ &helper_cb,
+ &ctx);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ if ( (qs < 0) ||
+ (GNUNET_OK != ctx.status) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to insert into reserves (%d)\n",
+ qs);
+ goto finished;
+ }
+ need_update = ctx.needs_update;
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus cs;
+
+ cs = TEH_PG_commit (pg);
+ if (cs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to commit\n");
+ qs = cs;
+ goto finished;
+ }
+ }
+
+ for (unsigned int i = 0; i<reserves_length; i++)
+ {
+ if (transaction_duplicates[i])
+ dups++;
+ results[i] = transaction_duplicates[i]
+ ? GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+
+ if (! need_update)
+ {
+ qs = reserves_length;
+ goto finished;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Reserve update needed for some reserves in the batch\n");
+ PREPARE (pg,
+ "reserves_update",
+ "SELECT"
+ " out_duplicate AS duplicate "
+ "FROM exchange_do_batch_reserves_update"
+ " ($1,$2,$3,$4,$5,$6,$7);");
+
+ if (GNUNET_OK !=
+ TEH_PG_start (pg,
+ "reserve-insert-continued"))
+ {
+ GNUNET_break (0);
+ qs = GNUNET_DB_STATUS_HARD_ERROR;
+ goto finished;
+ }
+
+ for (unsigned int i = 0; i<reserves_length; i++)
+ {
+ if (transaction_duplicates[i])
+ continue;
+ if (! conflicts[i])
+ continue;
+ {
+ bool duplicate;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (&reserve_pubs[i]),
+ GNUNET_PQ_query_param_timestamp (&reserve_expiration),
+ GNUNET_PQ_query_param_uint64 (&wire_references[i]),
+ TALER_PQ_query_param_amount (pg->conn,
+ &balances[i]),
+ GNUNET_PQ_query_param_string (exchange_account_names[i]),
+ GNUNET_PQ_query_param_auto_from_type (&h_paytos[i]),
+ GNUNET_PQ_query_param_string (notify_s[i]),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("duplicate",
+ &duplicate),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "reserves_update",
+ params,
+ rs);
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to update reserves (%d)\n",
+ qs);
+ results[i] = qs;
+ goto finished;
+ }
+ results[i] = duplicate
+ ? GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ : GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ }
+ {
+ enum GNUNET_DB_QueryStatus cs;
+
+ cs = TEH_PG_commit (pg);
+ if (cs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to commit\n");
+ qs = cs;
+ goto finished;
+ }
+ }
+finished:
+ for (unsigned int i = 0; i<reserves_length; i++)
+ GNUNET_free (notify_s[i]);
+ if (qs < 0)
+ return qs;
+ GNUNET_PQ_event_do_poll (pg->conn);
+ if (0 != dups)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "%u/%u duplicates among incoming transactions. Try increasing WIREWATCH_IDLE_SLEEP_INTERVAL in the [exchange] configuration section (if this happens a lot).\n",
+ dups,
+ reserves_length);
+ return qs;
+}
diff --git a/src/exchangedb/pg_reserves_in_insert.h b/src/exchangedb/pg_reserves_in_insert.h
new file mode 100644
index 000000000..938df3adb
--- /dev/null
+++ b/src/exchangedb/pg_reserves_in_insert.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_reserves_in_insert.h
+ * @brief implementation of the reserves_in_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RESERVES_IN_INSERT_H
+#define PG_RESERVES_IN_INSERT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Insert an incoming transaction into reserves. New reserves are also
+ * created through this function. Runs its own transaction(s).
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserves array of reserves to insert
+ * @param reserves_length length of the @a reserves array
+ * @param[out] results set to query status per reserve, must be of length @a reserves_length
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_in_insert (
+ void *cls,
+ const struct TALER_EXCHANGEDB_ReserveInInfo *reserves,
+ unsigned int reserves_length,
+ enum GNUNET_DB_QueryStatus *results);
+
+
+#endif
diff --git a/src/exchangedb/pg_reserves_update.c b/src/exchangedb/pg_reserves_update.c
new file mode 100644
index 000000000..bfd32c6ce
--- /dev/null
+++ b/src/exchangedb/pg_reserves_update.c
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_reserves_update.c
+ * @brief Implementation of the reserves_update function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_reserves_update.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_update (void *cls,
+ const struct TALER_EXCHANGEDB_Reserve *reserve)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&reserve->expiry),
+ GNUNET_PQ_query_param_timestamp (&reserve->gc),
+ TALER_PQ_query_param_amount (pg->conn,
+ &reserve->balance),
+ GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "reserve_update",
+ "UPDATE reserves"
+ " SET"
+ " expiration_date=$1"
+ ",gc_date=$2"
+ ",current_balance=$3"
+ " WHERE reserve_pub=$4;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "reserve_update",
+ params);
+}
diff --git a/src/exchangedb/pg_reserves_update.h b/src/exchangedb/pg_reserves_update.h
new file mode 100644
index 000000000..24cf671da
--- /dev/null
+++ b/src/exchangedb/pg_reserves_update.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_reserves_update.h
+ * @brief implementation of the reserves_update function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_RESERVES_UPDATE_H
+#define PG_RESERVES_UPDATE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Updates a reserve with the data from the given reserve structure.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param reserve the reserve structure whose data will be used to update the
+ * corresponding record in the database.
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_reserves_update (void *cls,
+ const struct TALER_EXCHANGEDB_Reserve *reserve);
+
+#endif
diff --git a/src/exchangedb/pg_rollback.c b/src/exchangedb/pg_rollback.c
new file mode 100644
index 000000000..3610487f3
--- /dev/null
+++ b/src/exchangedb/pg_rollback.c
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_rollback.c
+ * @brief Implementation of the rollback function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_rollback.h"
+#include "pg_helper.h"
+
+
+void
+TEH_PG_rollback (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("ROLLBACK"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (NULL == pg->transaction_name)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Skipping rollback, no transaction active\n");
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Rolling back transaction\n");
+ GNUNET_break (GNUNET_OK ==
+ GNUNET_PQ_exec_statements (pg->conn,
+ es));
+ pg->transaction_name = NULL;
+}
diff --git a/src/exchangedb/pg_rollback.h b/src/exchangedb/pg_rollback.h
new file mode 100644
index 000000000..ddb9e4111
--- /dev/null
+++ b/src/exchangedb/pg_rollback.h
@@ -0,0 +1,36 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_rollback.h
+ * @brief implementation of the rollback function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_ROLLBACK_H
+#define PG_ROLLBACK_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Roll back the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ */
+void
+TEH_PG_rollback (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_account_merges_above_serial_id.c b/src/exchangedb/pg_select_account_merges_above_serial_id.c
new file mode 100644
index 000000000..6c3c81121
--- /dev/null
+++ b/src/exchangedb/pg_select_account_merges_above_serial_id.c
@@ -0,0 +1,192 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_account_merges_above_serial_id.c
+ * @brief Implementation of the select_account_merges_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_account_merges_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #account_merge_serial_helper_cb().
+ */
+struct AccountMergeSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_AccountMergeCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct AccountMergeSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+account_merge_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AccountMergeSerialContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct GNUNET_TIME_Timestamp purse_expiration;
+ struct TALER_Amount amount;
+ uint32_t min_age;
+ uint32_t flags32;
+ enum TALER_WalletAccountMergeFlags flags;
+ struct TALER_Amount purse_fee;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ struct TALER_ReserveSignatureP reserve_sig;
+ uint64_t rowid;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("purse_fee",
+ &purse_fee),
+ GNUNET_PQ_result_spec_uint32 ("flags",
+ &flags32),
+ GNUNET_PQ_result_spec_uint32 ("age_limit",
+ &min_age),
+ GNUNET_PQ_result_spec_timestamp ("purse_expiration",
+ &purse_expiration),
+ GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
+ &merge_timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &reserve_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_uint64 ("account_merge_request_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ flags = (enum TALER_WalletAccountMergeFlags) flags32;
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ &reserve_pub,
+ &purse_pub,
+ &h_contract_terms,
+ purse_expiration,
+ &amount,
+ min_age,
+ flags,
+ &purse_fee,
+ merge_timestamp,
+ &reserve_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_account_merges_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_AccountMergeCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct AccountMergeSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_account_merge_incr",
+ "SELECT"
+ " am.account_merge_request_serial_id"
+ ",am.reserve_pub"
+ ",am.purse_pub"
+ ",pr.h_contract_terms"
+ ",pr.purse_expiration"
+ ",pr.amount_with_fee"
+ ",pr.age_limit"
+ ",pr.flags"
+ ",pr.purse_fee"
+ ",pm.merge_timestamp"
+ ",am.reserve_sig"
+ " FROM account_merges am"
+ " JOIN purse_requests pr USING (purse_pub)"
+ " JOIN purse_merges pm USING (purse_pub)"
+ " WHERE ("
+ " (account_merge_request_serial_id>=$1)"
+ " )"
+ " ORDER BY account_merge_request_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_account_merge_incr",
+ params,
+ &account_merge_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_account_merges_above_serial_id.h b/src/exchangedb/pg_select_account_merges_above_serial_id.h
new file mode 100644
index 000000000..be3bd7124
--- /dev/null
+++ b/src/exchangedb/pg_select_account_merges_above_serial_id.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_account_merges_above_serial_id.h
+ * @brief implementation of the select_account_merges_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_ACCOUNT_MERGES_ABOVE_SERIAL_ID_H
+#define PG_SELECT_ACCOUNT_MERGES_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select account merges above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_account_merges_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_AccountMergeCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.c b/src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.c
new file mode 100644
index 000000000..0d5d0ee25
--- /dev/null
+++ b/src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.c
@@ -0,0 +1,154 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aggregation_amounts_for_kyc_check.c
+ * @brief Implementation of the select_aggregation_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_aggregation_amounts_for_kyc_check.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_kyc_amounts_cb().
+ */
+struct KycAmountCheckContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_KycAmountCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct KycAmountCheckContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_kyc_amounts_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct KycAmountCheckContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct GNUNET_TIME_Absolute date;
+ struct TALER_Amount amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_absolute_time ("date",
+ &date),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = ctx->cb (ctx->cb_cls,
+ &amount,
+ date);
+ GNUNET_PQ_cleanup_result (rs);
+ switch (ret)
+ {
+ case GNUNET_OK:
+ continue;
+ case GNUNET_NO:
+ break;
+ case GNUNET_SYSERR:
+ ctx->status = GNUNET_SYSERR;
+ break;
+ }
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aggregation_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&time_limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct KycAmountCheckContext ctx = {
+ .cb = kac,
+ .cb_cls = kac_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_kyc_relevant_aggregation_events",
+ "SELECT"
+ " amount"
+ ",execution_date AS date"
+ " FROM wire_out"
+ " WHERE wire_target_h_payto=$1"
+ " AND execution_date >= $2"
+ " ORDER BY execution_date DESC");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_kyc_relevant_aggregation_events",
+ params,
+ &get_kyc_amounts_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.h b/src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.h
new file mode 100644
index 000000000..b91740581
--- /dev/null
+++ b/src/exchangedb/pg_select_aggregation_amounts_for_kyc_check.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aggregation_amounts_for_kyc_check.h
+ * @brief implementation of the select_aggregation_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AGGREGATION_AMOUNTS_FOR_KYC_CHECK_H
+#define PG_SELECT_AGGREGATION_AMOUNTS_FOR_KYC_CHECK_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Call @a kac on deposited amounts after @a time_limit which are relevant for a
+ * KYC trigger for a the (credited) account identified by @a h_payto.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aggregation_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_aggregation_transient.c b/src/exchangedb/pg_select_aggregation_transient.c
new file mode 100644
index 000000000..f9b6193ed
--- /dev/null
+++ b/src/exchangedb/pg_select_aggregation_transient.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aggregation_transient.c
+ * @brief Implementation of the select_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_aggregation_transient.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const char *exchange_account_section,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_string (exchange_account_section),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ total),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
+ wtid),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_select_aggregation_transient() */
+ PREPARE (pg,
+ "select_aggregation_transient",
+ "SELECT"
+ " amount"
+ " ,wtid_raw"
+ " FROM aggregation_transient"
+ " WHERE wire_target_h_payto=$1"
+ " AND merchant_pub=$2"
+ " AND exchange_account_section=$3;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_aggregation_transient",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_select_aggregation_transient.h b/src/exchangedb/pg_select_aggregation_transient.h
new file mode 100644
index 000000000..fd82a97aa
--- /dev/null
+++ b/src/exchangedb/pg_select_aggregation_transient.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aggregation_transient.h
+ * @brief implementation of the select_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AGGREGATION_TRANSIENT_H
+#define PG_SELECT_AGGREGATION_TRANSIENT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Find existing entry in the transient aggregation table.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param merchant_pub public key of the merchant receiving the transfer
+ * @param exchange_account_section exchange account to use
+ * @param[out] wtid set to the raw wire transfer identifier to be used
+ * @param[out] total existing amount to be wired in the future
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const char *exchange_account_section,
+ struct TALER_WireTransferIdentifierRawP *wtid,
+ struct TALER_Amount *total);
+#endif
diff --git a/src/exchangedb/pg_select_aggregations_above_serial.c b/src/exchangedb/pg_select_aggregations_above_serial.c
new file mode 100644
index 000000000..52d202702
--- /dev/null
+++ b/src/exchangedb/pg_select_aggregations_above_serial.c
@@ -0,0 +1,137 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aggregations_above_serial.c
+ * @brief Implementation of the select_aggregations_above_serial function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_aggregations_above_serial.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #aggregation_serial_helper_cb().
+ */
+struct AggregationSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_AggregationCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct AggregationSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+aggregation_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AggregationSerialContext *dsc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t tracking_rowid;
+ uint64_t batch_deposit_serial_id;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("aggregation_serial_id",
+ &tracking_rowid),
+ GNUNET_PQ_result_spec_uint64 ("batch_deposit_serial_id",
+ &batch_deposit_serial_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ dsc->cb (dsc->cb_cls,
+ tracking_rowid,
+ batch_deposit_serial_id);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aggregations_above_serial (
+ void *cls,
+ uint64_t min_tracking_serial_id,
+ TALER_EXCHANGEDB_AggregationCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&min_tracking_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct AggregationSerialContext asc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Fetch aggregations with rowid '\geq' the given parameter */
+ PREPARE (pg,
+ "select_aggregations_above_serial",
+ "SELECT"
+ " aggregation_serial_id"
+ ",batch_deposit_serial_id"
+ " FROM aggregation_tracking"
+ " WHERE aggregation_serial_id>=$1"
+ " ORDER BY aggregation_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_aggregations_above_serial",
+ params,
+ &aggregation_serial_helper_cb,
+ &asc);
+ if (GNUNET_OK != asc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_aggregations_above_serial.h b/src/exchangedb/pg_select_aggregations_above_serial.h
new file mode 100644
index 000000000..2883b19f2
--- /dev/null
+++ b/src/exchangedb/pg_select_aggregations_above_serial.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aggregations_above_serial.h
+ * @brief implementation of the select_aggregations_above_serial function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AGGREGATIONS_ABOVE_SERIAL_H
+#define PG_SELECT_AGGREGATIONS_ABOVE_SERIAL_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select all aggregation tracking IDs in the database
+ * above a given @a min_tracking_serial_id.
+ *
+ * @param cls closure
+ * @param min_tracking_serial_id only return entries strictly above this row (and in order)
+ * @param cb function to call on all such aggregations
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aggregations_above_serial (
+ void *cls,
+ uint64_t min_tracking_serial_id,
+ TALER_EXCHANGEDB_AggregationCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.c b/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.c
new file mode 100644
index 000000000..ba3307690
--- /dev/null
+++ b/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.c
@@ -0,0 +1,146 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_all_purse_decisions_above_serial_id.c
+ * @brief Implementation of the select_all_purse_decisions_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_all_purse_decisions_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #all_purse_decision_serial_helper_cb().
+ */
+struct AllPurseDecisionSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_AllPurseDecisionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct PurseRefundSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+all_purse_decision_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AllPurseDecisionSerialContext *dsc = cls;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ bool refunded;
+ uint64_t rowid;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &purse_pub),
+ GNUNET_PQ_result_spec_bool ("refunded",
+ &refunded),
+ GNUNET_PQ_result_spec_uint64 ("purse_decision_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ &purse_pub,
+ refunded);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_all_purse_decisions_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_AllPurseDecisionCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct AllPurseDecisionSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_all_purse_decision_incr",
+ "SELECT"
+ " purse_pub"
+ ",refunded"
+ ",purse_decision_serial_id"
+ " FROM purse_decision"
+ " WHERE purse_decision_serial_id>=$1"
+ " ORDER BY purse_decision_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "audit_get_all_purse_decision_incr",
+ params,
+ &all_purse_decision_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.h b/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.h
new file mode 100644
index 000000000..634be4965
--- /dev/null
+++ b/src/exchangedb/pg_select_all_purse_decisions_above_serial_id.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_all_purse_decisions_above_serial_id.h
+ * @brief implementation of the select_all_purse_decisions_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_ALL_PURSE_DECISIONS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_ALL_PURSE_DECISIONS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select purse decisions above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_all_purse_decisions_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_AllPurseDecisionCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_aml_history.c b/src/exchangedb/pg_select_aml_history.c
new file mode 100644
index 000000000..0461e0d9b
--- /dev/null
+++ b/src/exchangedb/pg_select_aml_history.c
@@ -0,0 +1,157 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aml_history.c
+ * @brief Implementation of the select_aml_history function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_aml_history.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #handle_aml_result.
+ */
+struct AmlHistoryResultContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_AmlHistoryCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on serious errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results. Helper function
+ * for #TEH_PG_select_aml_history().
+ *
+ * @param cls closure of type `struct AmlHistoryResultContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+handle_aml_result (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AmlHistoryResultContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount new_threshold;
+ uint32_t ns;
+ struct GNUNET_TIME_Timestamp decision_time;
+ char *justification;
+ struct TALER_AmlOfficerPublicKeyP decider_pub;
+ struct TALER_AmlOfficerSignatureP decider_sig;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("new_threshold",
+ &new_threshold),
+ GNUNET_PQ_result_spec_uint32 ("new_status",
+ &ns),
+ GNUNET_PQ_result_spec_timestamp ("decision_time",
+ &decision_time),
+ GNUNET_PQ_result_spec_string ("justification",
+ &justification),
+ GNUNET_PQ_result_spec_auto_from_type ("decider_pub",
+ &decider_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("decider_sig",
+ &decider_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &new_threshold,
+ (enum TALER_AmlDecisionState) ns,
+ decision_time,
+ justification,
+ &decider_pub,
+ &decider_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aml_history (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_AmlHistoryCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ struct AmlHistoryResultContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "lookup_aml_history",
+ "SELECT"
+ " new_threshold"
+ ",new_status"
+ ",decision_time"
+ ",justification"
+ ",decider_pub"
+ ",decider_sig"
+ " FROM aml_history"
+ " WHERE h_payto=$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_aml_history",
+ params,
+ &handle_aml_result,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_aml_history.h b/src/exchangedb/pg_select_aml_history.h
new file mode 100644
index 000000000..78569947f
--- /dev/null
+++ b/src/exchangedb/pg_select_aml_history.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aml_history.h
+ * @brief implementation of the select_aml_history function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AML_HISTORY_H
+#define PG_SELECT_AML_HISTORY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup AML decision history for a particular account.
+ *
+ * @param cls closure
+ * @param h_payto which account should we return the AML decision history for
+ * @param cb callback to invoke on each match
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aml_history (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_AmlHistoryCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_aml_process.c b/src/exchangedb/pg_select_aml_process.c
new file mode 100644
index 000000000..c34cae4bb
--- /dev/null
+++ b/src/exchangedb/pg_select_aml_process.c
@@ -0,0 +1,170 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aml_process.c
+ * @brief Implementation of the select_aml_process function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_aml_process.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #handle_aml_result.
+ */
+struct AmlProcessResultContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_AmlStatusCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on serious errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results. Helper function
+ * for #TEH_PG_select_aml_process().
+ *
+ * @param cls closure of type `struct AmlProcessResultContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+handle_aml_result (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct AmlProcessResultContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_PaytoHashP h_payto;
+ struct TALER_Amount threshold;
+ uint64_t rowid;
+ uint32_t sv;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("aml_status_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_auto_from_type ("h_payto",
+ &h_payto),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("threshold",
+ &threshold),
+ GNUNET_PQ_result_spec_uint32 ("status",
+ &sv),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ rowid,
+ &h_payto,
+ &threshold,
+ (enum TALER_AmlDecisionState) sv);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aml_process (
+ void *cls,
+ enum TALER_AmlDecisionState decision,
+ uint64_t row_off,
+ uint64_t limit,
+ bool forward,
+ TALER_EXCHANGEDB_AmlStatusCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint32 (&decision),
+ GNUNET_PQ_query_param_uint64 (&row_off),
+ GNUNET_PQ_query_param_uint64 (&limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct AmlProcessResultContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ const char *stmt = forward
+ ? "select_aml_process_inc"
+ : "select_aml_process_dec";
+
+ PREPARE (pg,
+ "select_aml_process_inc",
+ "SELECT"
+ " aml_status_serial_id"
+ ",h_payto"
+ ",threshold"
+ ",status"
+ " FROM aml_status"
+ " WHERE aml_status_serial_id > $2"
+ " AND status = $1"
+ " ORDER BY aml_status_serial_id ASC"
+ " LIMIT $3");
+ PREPARE (pg,
+ "select_aml_process_dec",
+ "SELECT"
+ " aml_status_serial_id"
+ ",h_payto"
+ ",threshold"
+ ",status"
+ " FROM aml_status"
+ " WHERE aml_status_serial_id < $2"
+ " AND status = $1"
+ " ORDER BY aml_status_serial_id DESC"
+ " LIMIT $3");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ stmt,
+ params,
+ &handle_aml_result,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_aml_process.h b/src/exchangedb/pg_select_aml_process.h
new file mode 100644
index 000000000..648cace2e
--- /dev/null
+++ b/src/exchangedb/pg_select_aml_process.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aml_process.h
+ * @brief implementation of the select_aml_process function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AML_PROCESS_H
+#define PG_SELECT_AML_PROCESS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup AML decisions that have a particular state.
+ *
+ * @param cls closure
+ * @param decision which decision states to filter by
+ * @param row_off offset to start from
+ * @param limit how many rows to return at most
+ * @param forward true to go forward in time, false to go backwards
+ * @param cb callback to invoke on each match
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aml_process (
+ void *cls,
+ enum TALER_AmlDecisionState decision,
+ uint64_t row_off,
+ uint64_t limit,
+ bool forward,
+ TALER_EXCHANGEDB_AmlStatusCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_aml_threshold.c b/src/exchangedb/pg_select_aml_threshold.c
new file mode 100644
index 000000000..23286f029
--- /dev/null
+++ b/src/exchangedb/pg_select_aml_threshold.c
@@ -0,0 +1,70 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aml_threshold.c
+ * @brief Implementation of the select_aml_threshold function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_aml_threshold.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aml_threshold (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_AmlDecisionState *decision,
+ struct TALER_EXCHANGEDB_KycStatus *kyc,
+ struct TALER_Amount *threshold)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ uint32_t status32 = TALER_AML_NORMAL;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("threshold",
+ threshold),
+ GNUNET_PQ_result_spec_uint32 ("status",
+ &status32),
+ GNUNET_PQ_result_spec_uint64 ("kyc_requirement",
+ &kyc->requirement_row),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_aml_threshold",
+ "SELECT"
+ " threshold"
+ ",status"
+ ",kyc_requirement"
+ " FROM aml_status"
+ " WHERE h_payto=$1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_aml_threshold",
+ params,
+ rs);
+ *decision = (enum TALER_AmlDecisionState) status32;
+ kyc->ok = (TALER_AML_FROZEN != *decision)
+ || (0 != kyc->requirement_row);
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_aml_threshold.h b/src/exchangedb/pg_select_aml_threshold.h
new file mode 100644
index 000000000..8f0e3bcfc
--- /dev/null
+++ b/src/exchangedb/pg_select_aml_threshold.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_aml_threshold.h
+ * @brief implementation of the select_aml_threshold function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AML_THRESHOLD_H
+#define PG_SELECT_AML_THRESHOLD_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Obtain the current AML threshold set for an account.
+ *
+ * @param cls closure
+ * @param h_payto account for which the AML threshold is stored
+ * @param[out] decision set to current AML decision
+ * @param[out] kyc set to KYC requirements imposed by AML, if any
+ * @param[out] threshold set to the existing threshold
+ * @return database transaction status, 0 if no threshold was set
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_aml_threshold (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ enum TALER_AmlDecisionState *decision,
+ struct TALER_EXCHANGEDB_KycStatus *kyc,
+ struct TALER_Amount *threshold);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_auditor_denom_sig.c b/src/exchangedb/pg_select_auditor_denom_sig.c
new file mode 100644
index 000000000..1dd6bd3d1
--- /dev/null
+++ b/src/exchangedb/pg_select_auditor_denom_sig.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_auditor_denom_sig.c
+ * @brief Implementation of the select_auditor_denom_sig function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_auditor_denom_sig.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_auditor_denom_sig (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct TALER_AuditorSignatureP *auditor_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (auditor_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("auditor_sig",
+ auditor_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "select_auditor_denom_sig",
+ "SELECT"
+ " auditor_sig"
+ " FROM auditor_denom_sigs"
+ " WHERE auditor_uuid="
+ " (SELECT auditor_uuid"
+ " FROM auditors"
+ " WHERE auditor_pub=$1)"
+ " AND denominations_serial="
+ " (SELECT denominations_serial"
+ " FROM denominations"
+ " WHERE denom_pub_hash=$2);");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_auditor_denom_sig",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_select_auditor_denom_sig.h b/src/exchangedb/pg_select_auditor_denom_sig.h
new file mode 100644
index 000000000..0f635cf42
--- /dev/null
+++ b/src/exchangedb/pg_select_auditor_denom_sig.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_auditor_denom_sig.h
+ * @brief implementation of the select_auditor_denom_sig function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_AUDITOR_DENOM_SIG_H
+#define PG_SELECT_AUDITOR_DENOM_SIG_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Select information about an auditor auditing a denomination key.
+ *
+ * @param cls closure
+ * @param h_denom_pub the audited denomination
+ * @param auditor_pub the auditor's key
+ * @param[out] auditor_sig set to signature affirming the auditor's audit activity
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_auditor_denom_sig (
+ void *cls,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ struct TALER_AuditorSignatureP *auditor_sig);
+
+#endif
diff --git a/src/exchangedb/pg_select_batch_deposits_missing_wire.c b/src/exchangedb/pg_select_batch_deposits_missing_wire.c
new file mode 100644
index 000000000..8f966326a
--- /dev/null
+++ b/src/exchangedb/pg_select_batch_deposits_missing_wire.c
@@ -0,0 +1,144 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_batch_deposits_missing_wire.c
+ * @brief Implementation of the select_batch_deposits_missing_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_batch_deposits_missing_wire.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #missing_wire_cb().
+ */
+struct MissingWireContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_WireMissingCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct MissingWireContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+missing_wire_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct MissingWireContext *mwc = cls;
+ struct PostgresClosure *pg = mwc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t batch_deposit_serial_id;
+ struct GNUNET_TIME_Timestamp deadline;
+ struct TALER_PaytoHashP wire_target_h_payto;
+ struct TALER_Amount total_amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("batch_deposit_serial_id",
+ &batch_deposit_serial_id),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_target_h_payto",
+ &wire_target_h_payto),
+ GNUNET_PQ_result_spec_timestamp ("deadline",
+ &deadline),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("total_amount",
+ &total_amount),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ mwc->status = GNUNET_SYSERR;
+ return;
+ }
+ mwc->cb (mwc->cb_cls,
+ batch_deposit_serial_id,
+ &total_amount,
+ &wire_target_h_payto,
+ deadline);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_batch_deposits_missing_wire (
+ void *cls,
+ uint64_t min_batch_deposit_serial_id,
+ TALER_EXCHANGEDB_WireMissingCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&min_batch_deposit_serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct MissingWireContext mwc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "deposits_get_deposits_missing_wire",
+ "SELECT"
+ " batch_deposit_serial_id"
+ ",wire_target_h_payto"
+ ",deadline"
+ ",total_amount"
+ " FROM exchange_do_select_deposits_missing_wire"
+ " ($1);");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "deposits_get_deposits_missing_wire",
+ params,
+ &missing_wire_cb,
+ &mwc);
+ if (GNUNET_OK != mwc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_batch_deposits_missing_wire.h b/src/exchangedb/pg_select_batch_deposits_missing_wire.h
new file mode 100644
index 000000000..16f1d0cb3
--- /dev/null
+++ b/src/exchangedb/pg_select_batch_deposits_missing_wire.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_batch_deposits_missing_wire.h
+ * @brief implementation of the select_batch_deposits_missing_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_DEPOSITS_MISSING_WIRE_H
+#define PG_SELECT_DEPOSITS_MISSING_WIRE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Select all of those batch deposits in the database
+ * above the given serial ID.
+ *
+ * @param cls closure
+ * @param min_batch_deposit_serial_id select all batch deposits above this ID
+ * @param cb function to call on all such deposits
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_batch_deposits_missing_wire (
+ void *cls,
+ uint64_t min_batch_deposit_serial_id,
+ TALER_EXCHANGEDB_WireMissingCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_coin_deposits_above_serial_id.c b/src/exchangedb/pg_select_coin_deposits_above_serial_id.c
new file mode 100644
index 000000000..000b908ed
--- /dev/null
+++ b/src/exchangedb/pg_select_coin_deposits_above_serial_id.c
@@ -0,0 +1,204 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_coin_deposits_above_serial_id.c
+ * @brief Implementation of the select_coin_deposits_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_coin_deposits_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #deposit_serial_helper_cb().
+ */
+struct CoinDepositSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_DepositCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CoinDepositSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+coin_deposit_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CoinDepositSerialContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_Deposit deposit;
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+ struct TALER_DenominationPublicKey denom_pub;
+ bool done;
+ uint64_t rowid;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &deposit.amount_with_fee),
+ GNUNET_PQ_result_spec_timestamp ("wallet_timestamp",
+ &deposit.timestamp),
+ GNUNET_PQ_result_spec_timestamp ("exchange_timestamp",
+ &exchange_timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &deposit.merchant_pub),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &deposit.coin.coin_pub),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &deposit.coin.h_age_commitment),
+ &deposit.coin.no_age_commitment),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("wallet_data_hash",
+ &deposit.wallet_data_hash),
+ &deposit.no_wallet_data_hash),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &deposit.csig),
+ GNUNET_PQ_result_spec_timestamp ("refund_deadline",
+ &deposit.refund_deadline),
+ GNUNET_PQ_result_spec_timestamp ("wire_deadline",
+ &deposit.wire_deadline),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &deposit.h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("wire_salt",
+ &deposit.wire_salt),
+ GNUNET_PQ_result_spec_string ("receiver_wire_account",
+ &deposit.receiver_wire_account),
+ GNUNET_PQ_result_spec_bool ("done",
+ &done),
+ GNUNET_PQ_result_spec_uint64 ("coin_deposit_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ memset (&deposit,
+ 0,
+ sizeof (deposit));
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ exchange_timestamp,
+ &deposit,
+ &denom_pub,
+ done);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_coin_deposits_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_DepositCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct CoinDepositSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Fetch deposits with rowid '\geq' the given parameter */
+ PREPARE (pg,
+ "audit_get_coin_deposits_incr",
+ "SELECT"
+ " cdep.amount_with_fee"
+ ",bdep.wallet_timestamp"
+ ",bdep.exchange_timestamp"
+ ",bdep.merchant_pub"
+ ",bdep.wallet_data_hash"
+ ",denom.denom_pub"
+ ",kc.coin_pub"
+ ",kc.age_commitment_hash"
+ ",cdep.coin_sig"
+ ",bdep.refund_deadline"
+ ",bdep.wire_deadline"
+ ",bdep.h_contract_terms"
+ ",bdep.wire_salt"
+ ",wt.payto_uri AS receiver_wire_account"
+ ",bdep.done"
+ ",cdep.coin_deposit_serial_id"
+ " FROM coin_deposits cdep"
+ " JOIN batch_deposits bdep"
+ " USING (batch_deposit_serial_id)"
+ " JOIN wire_targets wt"
+ " USING (wire_target_h_payto)"
+ " JOIN known_coins kc"
+ " USING (coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE (coin_deposit_serial_id>=$1)"
+ " ORDER BY coin_deposit_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_coin_deposits_incr",
+ params,
+ &coin_deposit_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_coin_deposits_above_serial_id.h b/src/exchangedb/pg_select_coin_deposits_above_serial_id.h
new file mode 100644
index 000000000..5202336a4
--- /dev/null
+++ b/src/exchangedb/pg_select_coin_deposits_above_serial_id.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_coin_deposits_above_serial_id.h
+ * @brief implementation of the select_coin_deposits_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_DEPOSITS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_DEPOSITS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Select deposits above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_coin_deposits_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_DepositCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_contract.c b/src/exchangedb/pg_select_contract.c
new file mode 100644
index 000000000..3e6bf22bc
--- /dev/null
+++ b/src/exchangedb/pg_select_contract.c
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_contract.c
+ * @brief Implementation of the select_contract function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_contract.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_contract (void *cls,
+ const struct TALER_ContractDiffiePublicP *pub_ckey,
+ struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseContractSignatureP *econtract_sig,
+ size_t *econtract_size,
+ void **econtract)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (pub_ckey),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("contract_sig",
+ econtract_sig),
+ GNUNET_PQ_result_spec_variable_size ("e_contract",
+ econtract,
+ econtract_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ /* Used in #postgres_select_contract */
+ PREPARE (pg,
+ "select_contract",
+ "SELECT "
+ " purse_pub"
+ ",e_contract"
+ ",contract_sig"
+ " FROM contracts"
+ " WHERE pub_ckey=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_contract",
+ params,
+ rs);
+
+}
diff --git a/src/exchangedb/pg_select_contract.h b/src/exchangedb/pg_select_contract.h
new file mode 100644
index 000000000..747a82753
--- /dev/null
+++ b/src/exchangedb/pg_select_contract.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_contract.h
+ * @brief implementation of the select_contract function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_CONTRACT_H
+#define PG_SELECT_CONTRACT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to retrieve an encrypted contract.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub key to lookup the contract by
+ * @param[out] pub_ckey set to the ephemeral DH used to encrypt the contract
+ * @param[out] econtract_sig set to the signature over the encrypted contract
+ * @param[out] econtract_size set to the number of bytes in @a econtract
+ * @param[out] econtract set to the encrypted contract on success, to be freed by the caller
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_contract (void *cls,
+ const struct TALER_ContractDiffiePublicP *pub_ckey,
+ struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseContractSignatureP *econtract_sig,
+ size_t *econtract_size,
+ void **econtract);
+
+#endif
diff --git a/src/exchangedb/pg_select_contract_by_purse.c b/src/exchangedb/pg_select_contract_by_purse.c
new file mode 100644
index 000000000..8d29b3954
--- /dev/null
+++ b/src/exchangedb/pg_select_contract_by_purse.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_contract_by_purse.c
+ * @brief Implementation of the select_contract_by_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_contract_by_purse.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_contract_by_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_EncryptedContract *econtract)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("pub_ckey",
+ &econtract->contract_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("contract_sig",
+ &econtract->econtract_sig),
+ GNUNET_PQ_result_spec_variable_size ("e_contract",
+ &econtract->econtract,
+ &econtract->econtract_size),
+ GNUNET_PQ_result_spec_end
+ };
+ /* Used in #postgres_select_contract_by_purse */
+ PREPARE (pg,
+ "select_contract_by_purse",
+ "SELECT "
+ " pub_ckey"
+ ",e_contract"
+ ",contract_sig"
+ " FROM contracts"
+ " WHERE purse_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_contract_by_purse",
+ params,
+ rs);
+
+}
diff --git a/src/exchangedb/pg_select_contract_by_purse.h b/src/exchangedb/pg_select_contract_by_purse.h
new file mode 100644
index 000000000..0e33a6ba1
--- /dev/null
+++ b/src/exchangedb/pg_select_contract_by_purse.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_contract_by_purse.h
+ * @brief implementation of the select_contract_by_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_CONTRACT_BY_PURSE_H
+#define PG_SELECT_CONTRACT_BY_PURSE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to retrieve an encrypted contract.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub key to lookup the contract by
+ * @param[out] econtract set to the encrypted contract on success, to be freed by the caller
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_contract_by_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_EncryptedContract *econtract);
+#endif
diff --git a/src/exchangedb/pg_select_justification_for_missing_wire.c b/src/exchangedb/pg_select_justification_for_missing_wire.c
new file mode 100644
index 000000000..77d5b4de7
--- /dev/null
+++ b/src/exchangedb/pg_select_justification_for_missing_wire.c
@@ -0,0 +1,89 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_batch_deposits_missing_wire.c
+ * @brief Implementation of the select_batch_deposits_missing_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_batch_deposits_missing_wire.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_justification_for_missing_wire (
+ void *cls,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ char **payto_uri,
+ char **kyc_pending,
+ enum TALER_AmlDecisionState *status,
+ struct TALER_Amount *aml_limit)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now
+ = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (wire_target_h_payto),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ uint32_t aml_status32;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ payto_uri),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("kyc_pending",
+ kyc_pending),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint32 ("aml_status",
+ &aml_status32),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ TALER_PQ_RESULT_SPEC_AMOUNT ("aml_limit",
+ aml_limit),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "deposits_get_overdue",
+ "SELECT"
+ " out_payto_uri AS payto_uri"
+ ",out_kyc_pending AS kyc_pending"
+ ",out_deadline AS deadline"
+ ",out_aml_status AS aml_status"
+ ",out_aml_limit AS aml_limit"
+ " FROM exchange_do_select_justification_missing_wire"
+ " ($1, $2);");
+ memset (aml_limit,
+ 0,
+ sizeof (*aml_limit));
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "",
+ params,
+ rs);
+ if (qs <= 0)
+ return qs;
+ *status = (enum TALER_AmlDecisionState) aml_status32;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_justification_for_missing_wire.h b/src/exchangedb/pg_select_justification_for_missing_wire.h
new file mode 100644
index 000000000..7f73eb511
--- /dev/null
+++ b/src/exchangedb/pg_select_justification_for_missing_wire.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_justification_for_missing_wire.h
+ * @brief implementation of the select_justification_for_missing_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_JUSTIFICATION_FOR_MISSING_WIRE_H
+#define PG_SELECT_JUSTIFICATION_FOR_MISSING_WIRE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select all of those justifications for why we might not have
+ * done a wire transfer from in the database for a particular target account.
+ *
+ * @param cls closure
+ * @param wire_target_h_payto effected target account
+ * @param[out] payto_uri target account URI, set to NULL if unknown
+ * @param[out] kyc_pending set to string describing missing KYC data
+ * @param[out] status set to AML status
+ * @param[out] aml_limit set to AML limit, or invalid amount for none
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_justification_for_missing_wire (
+ void *cls,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ char **payto_uri,
+ char **kyc_pending,
+ enum TALER_AmlDecisionState *status,
+ struct TALER_Amount *aml_limit);
+
+#endif
diff --git a/src/exchangedb/pg_select_kyc_attributes.c b/src/exchangedb/pg_select_kyc_attributes.c
new file mode 100644
index 000000000..99ac43b3e
--- /dev/null
+++ b/src/exchangedb/pg_select_kyc_attributes.c
@@ -0,0 +1,156 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_kyc_attributes.c
+ * @brief Implementation of the select_kyc_attributes function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_kyc_attributes.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_attributes_cb().
+ */
+struct GetAttributesContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_AttributeCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Key of our query.
+ */
+ const struct TALER_PaytoHashP *h_payto;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct GetAttributesContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_attributes_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetAttributesContext *ctx = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct GNUNET_TIME_Timestamp collection_time;
+ struct GNUNET_TIME_Timestamp expiration_time;
+ size_t enc_attributes_size;
+ void *enc_attributes;
+ char *provider;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("provider",
+ &provider),
+ GNUNET_PQ_result_spec_timestamp ("collection_time",
+ &collection_time),
+ GNUNET_PQ_result_spec_timestamp ("expiration_time",
+ &expiration_time),
+ GNUNET_PQ_result_spec_variable_size ("encrypted_attributes",
+ &enc_attributes,
+ &enc_attributes_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ ctx->h_payto,
+ provider,
+ collection_time,
+ expiration_time,
+ enc_attributes_size,
+ enc_attributes);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_kyc_attributes (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_AttributeCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_end
+ };
+ struct GetAttributesContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .h_payto = h_payto,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_kyc_attributes",
+ "SELECT "
+ " provider"
+ ",collection_time"
+ ",expiration_time"
+ ",encrypted_attributes"
+ " FROM kyc_attributes"
+ " WHERE h_payto=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_kyc_attributes",
+ params,
+ &get_attributes_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_kyc_attributes.h b/src/exchangedb/pg_select_kyc_attributes.h
new file mode 100644
index 000000000..7458aefe8
--- /dev/null
+++ b/src/exchangedb/pg_select_kyc_attributes.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_kyc_attributes.h
+ * @brief implementation of the select_kyc_attributes function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_KYC_ATTRIBUTES_H
+#define PG_SELECT_KYC_ATTRIBUTES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup KYC attribute data for a specific account.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param cb callback to invoke on each match
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_kyc_attributes (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_AttributeCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c
new file mode 100644
index 000000000..417d78ec7
--- /dev/null
+++ b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.c
@@ -0,0 +1,157 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_merge_amounts_for_kyc_check.c
+ * @brief Implementation of the select_merge_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_merge_amounts_for_kyc_check.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_kyc_amounts_cb().
+ */
+struct KycAmountCheckContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_KycAmountCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct KycAmountCheckContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_kyc_amounts_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct KycAmountCheckContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct GNUNET_TIME_Absolute date;
+ struct TALER_Amount amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_absolute_time ("date",
+ &date),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = ctx->cb (ctx->cb_cls,
+ &amount,
+ date);
+ GNUNET_PQ_cleanup_result (rs);
+ switch (ret)
+ {
+ case GNUNET_OK:
+ continue;
+ case GNUNET_NO:
+ break;
+ case GNUNET_SYSERR:
+ ctx->status = GNUNET_SYSERR;
+ break;
+ }
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_merge_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&time_limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct KycAmountCheckContext ctx = {
+ .cb = kac,
+ .cb_cls = kac_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+
+ PREPARE (pg,
+ "select_kyc_relevant_merge_events",
+ "SELECT"
+ " amount_with_fee AS amount"
+ ",merge_timestamp AS date"
+ " FROM account_merges"
+ " JOIN purse_merges USING (purse_pub)"
+ " JOIN purse_requests USING (purse_pub)"
+ " JOIN purse_decision USING (purse_pub)"
+ " WHERE wallet_h_payto=$1"
+ " AND merge_timestamp >= $2"
+ " AND NOT refunded"
+ " ORDER BY merge_timestamp DESC");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_kyc_relevant_merge_events",
+ params,
+ &get_kyc_amounts_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_merge_amounts_for_kyc_check.h b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.h
new file mode 100644
index 000000000..5d0a96359
--- /dev/null
+++ b/src/exchangedb/pg_select_merge_amounts_for_kyc_check.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_merge_amounts_for_kyc_check.h
+ * @brief implementation of the select_merge_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_MERGE_AMOUNTS_FOR_KYC_CHECK_H
+#define PG_SELECT_MERGE_AMOUNTS_FOR_KYC_CHECK_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Call @a kac on merged reserve amounts after @a time_limit which are relevant for a
+ * KYC trigger for a the wallet identified by @a h_payto.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_merge_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_purse.c b/src/exchangedb/pg_select_purse.c
new file mode 100644
index 000000000..ffccb905c
--- /dev/null
+++ b/src/exchangedb/pg_select_purse.c
@@ -0,0 +1,94 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_purse.c
+ * @brief Implementation of the select_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp *purse_creation,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_Amount *amount,
+ struct TALER_Amount *deposited,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp *merge_timestamp,
+ bool *purse_deleted,
+ bool *purse_refunded)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_timestamp ("purse_expiration",
+ purse_expiration),
+ GNUNET_PQ_result_spec_timestamp ("purse_creation",
+ purse_creation),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ deposited),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ h_contract_terms),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
+ merge_timestamp),
+ NULL),
+ GNUNET_PQ_result_spec_bool ("purse_deleted",
+ purse_deleted),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_bool ("purse_refunded",
+ purse_refunded),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "select_purse",
+ "SELECT "
+ " pr.merge_pub"
+ ",pr.purse_creation"
+ ",pr.purse_expiration"
+ ",pr.h_contract_terms"
+ ",pr.amount_with_fee"
+ ",pr.balance"
+ ",pm.merge_timestamp"
+ ",pd.purse_sig IS NOT NULL AS purse_deleted"
+ ",pc.refunded AS purse_refunded"
+ " FROM purse_requests pr"
+ " LEFT JOIN purse_merges pm ON (pm.purse_pub = pr.purse_pub)"
+ " LEFT JOIN purse_decision pc ON (pc.purse_pub = pr.purse_pub)"
+ " LEFT JOIN purse_deletion pd ON (pd.purse_pub = pr.purse_pub)"
+ " WHERE pr.purse_pub=$1;");
+ *merge_timestamp = GNUNET_TIME_UNIT_FOREVER_TS;
+ *purse_refunded = false;
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_purse",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_select_purse.h b/src/exchangedb/pg_select_purse.h
new file mode 100644
index 000000000..8f88c5cf7
--- /dev/null
+++ b/src/exchangedb/pg_select_purse.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_purse.h
+ * @brief implementation of the select_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_H
+#define PG_SELECT_PURSE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to obtain information about a purse.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the new purse
+ * @param[out] purse_creation set to time when the purse was created
+ * @param[out] purse_expiration set to time when the purse will expire
+ * @param[out] amount set to target amount (with fees) to be put into the purse
+ * @param[out] deposited set to actual amount put into the purse so far
+ * @param[out] h_contract_terms set to hash of the contract for the purse
+ * @param[out] merge_timestamp set to time when the purse was merged, or NEVER if not
+ * @param[out] purse_deleted set to true if purse was deleted
+ * @param[out] purse_refunded set to true if purse was refunded
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp *purse_creation,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_Amount *amount,
+ struct TALER_Amount *deposited,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ struct GNUNET_TIME_Timestamp *merge_timestamp,
+ bool *purse_deleted,
+ bool *purse_refunded);
+
+#endif
diff --git a/src/exchangedb/pg_select_purse_by_merge_pub.c b/src/exchangedb/pg_select_purse_by_merge_pub.c
new file mode 100644
index 000000000..d035b3255
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_by_merge_pub.c
@@ -0,0 +1,79 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_by_merge_pub.c
+ * @brief Implementation of the select_purse_by_merge_pub function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_by_merge_pub.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_by_merge_pub (
+ void *cls,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t *age_limit,
+ struct TALER_Amount *target_amount,
+ struct TALER_Amount *balance,
+ struct TALER_PurseContractSignatureP *purse_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (merge_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ purse_pub),
+ GNUNET_PQ_result_spec_timestamp ("purse_expiration",
+ purse_expiration),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ h_contract_terms),
+ GNUNET_PQ_result_spec_uint32 ("age_limit",
+ age_limit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ target_amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ balance),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_sig",
+ purse_sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "select_purse_by_merge_pub",
+ "SELECT "
+ " purse_pub"
+ ",purse_expiration"
+ ",h_contract_terms"
+ ",age_limit"
+ ",amount_with_fee"
+ ",balance"
+ ",purse_sig"
+ " FROM purse_requests"
+ " WHERE merge_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_purse_by_merge_pub",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_select_purse_by_merge_pub.h b/src/exchangedb/pg_select_purse_by_merge_pub.h
new file mode 100644
index 000000000..2a7667132
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_by_merge_pub.h
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_by_merge_pub.h
+ * @brief implementation of the select_purse_by_merge_pub function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_BY_MERGE_PUB_H
+#define PG_SELECT_PURSE_BY_MERGE_PUB_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to return meta data about a purse by the
+ * merge capability key.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param merge_pub public key representing the merge capability
+ * @param[out] purse_pub public key of the purse
+ * @param[out] purse_expiration when would an unmerged purse expire
+ * @param[out] h_contract_terms contract associated with the purse
+ * @param[out] age_limit the age limit for deposits into the purse
+ * @param[out] target_amount amount to be put into the purse
+ * @param[out] balance amount put so far into the purse
+ * @param[out] purse_sig signature of the purse over the initialization data
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_by_merge_pub (
+ void *cls,
+ const struct TALER_PurseMergePublicKeyP *merge_pub,
+ struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct GNUNET_TIME_Timestamp *purse_expiration,
+ struct TALER_PrivateContractHashP *h_contract_terms,
+ uint32_t *age_limit,
+ struct TALER_Amount *target_amount,
+ struct TALER_Amount *balance,
+ struct TALER_PurseContractSignatureP *purse_sig);
+#endif
diff --git a/src/exchangedb/pg_select_purse_decisions_above_serial_id.c b/src/exchangedb/pg_select_purse_decisions_above_serial_id.c
new file mode 100644
index 000000000..f301ea78a
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_decisions_above_serial_id.c
@@ -0,0 +1,162 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_decisions_above_serial_id.c
+ * @brief Implementation of the select_purse_decisions_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_decisions_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #purse_decision_serial_helper_cb().
+ */
+struct PurseDecisionSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_PurseDecisionCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct PurseRefundSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+purse_decision_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PurseDecisionSerialContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ bool no_reserve = true;
+ uint64_t rowid;
+ struct TALER_Amount val;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &purse_pub),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ &no_reserve),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &val),
+ GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ &purse_pub,
+ no_reserve ? NULL : &reserve_pub,
+ &val);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_decisions_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ bool refunded,
+ TALER_EXCHANGEDB_PurseDecisionCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_bool (refunded),
+ GNUNET_PQ_query_param_end
+ };
+ struct PurseDecisionSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_purse_decisions_incr",
+ "SELECT"
+ " pd.purse_pub"
+ ",pm.reserve_pub"
+ ",pd.purse_decision_serial_id"
+ ",pr.amount_with_fee"
+ " FROM purse_decision pd"
+ " JOIN purse_requests pr ON (pd.purse_pub = pr.purse_pub)"
+ " LEFT JOIN purse_merges pm ON (pm.purse_pub = pd.purse_pub)"
+ " WHERE ("
+ " (purse_decision_serial_id>=$1) AND "
+ " (refunded=$2)"
+ " )"
+ " ORDER BY purse_decision_serial_id ASC;");
+
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_purse_decisions_incr",
+ params,
+ &purse_decision_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_purse_decisions_above_serial_id.h b/src/exchangedb/pg_select_purse_decisions_above_serial_id.h
new file mode 100644
index 000000000..83168d546
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_decisions_above_serial_id.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_decisions_above_serial_id.h
+ * @brief implementation of the select_purse_decisions_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_DECISIONS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_PURSE_DECISIONS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select purse decisions above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param refunded which refund status to select for
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_decisions_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ bool refunded,
+ TALER_EXCHANGEDB_PurseDecisionCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_purse_deposits_above_serial_id.c b/src/exchangedb/pg_select_purse_deposits_above_serial_id.c
new file mode 100644
index 000000000..bb4320663
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_deposits_above_serial_id.c
@@ -0,0 +1,201 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_purse_deposits_above_serial_id.c
+ * @brief Implementation of the select_purse_deposits_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_deposits_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #purse_deposit_serial_helper_cb().
+ */
+struct PurseDepositSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_PurseDepositCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct DepositSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+purse_deposit_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PurseDepositSerialContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_PurseDeposit deposit = {
+ .exchange_base_url = NULL
+ };
+ struct TALER_DenominationPublicKey denom_pub;
+ uint64_t rowid;
+ uint32_t flags32;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ bool not_merged = false;
+ struct TALER_Amount purse_balance;
+ struct TALER_Amount purse_total;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &deposit.amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ &purse_balance),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("total",
+ &purse_total),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("deposit_fee",
+ &deposit.deposit_fee),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("partner_base_url",
+ &deposit.exchange_base_url),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ &not_merged),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &deposit.purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &deposit.coin_sig),
+ GNUNET_PQ_result_spec_uint32 ("flags",
+ &flags32),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &deposit.coin_pub),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &deposit.h_age_commitment),
+ &deposit.no_age_commitment),
+ GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ memset (&deposit,
+ 0,
+ sizeof (deposit));
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ &deposit,
+ not_merged ? NULL : &reserve_pub,
+ (enum TALER_WalletAccountMergeFlags) flags32,
+ &purse_balance,
+ &purse_total,
+ &denom_pub);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_deposits_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseDepositCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct PurseDepositSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_purse_deposits_incr",
+ "SELECT"
+ " pd.amount_with_fee"
+ ",pr.amount_with_fee AS total"
+ ",pr.balance"
+ ",pr.flags"
+ ",pd.purse_pub"
+ ",pd.coin_sig"
+ ",partner_base_url"
+ ",denom.denom_pub"
+ ",pm.reserve_pub"
+ ",kc.coin_pub"
+ ",kc.age_commitment_hash"
+ ",pd.purse_deposit_serial_id"
+ " FROM purse_deposits pd"
+ " LEFT JOIN partners USING (partner_serial_id)"
+ " LEFT JOIN purse_merges pm USING (purse_pub)"
+ " JOIN purse_requests pr USING (purse_pub)"
+ " JOIN known_coins kc USING (coin_pub)"
+ " JOIN denominations denom USING (denominations_serial)"
+ " WHERE ("
+ " (purse_deposit_serial_id>=$1)"
+ " )"
+ " ORDER BY purse_deposit_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_purse_deposits_incr",
+ params,
+ &purse_deposit_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_purse_deposits_above_serial_id.h b/src/exchangedb/pg_select_purse_deposits_above_serial_id.h
new file mode 100644
index 000000000..34d50b2ab
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_deposits_above_serial_id.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_purse_deposits_above_serial_id.h
+ * @brief implementation of the select_purse_deposits_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_DEPOSITS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_PURSE_DEPOSITS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select deposits above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_deposits_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseDepositCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_purse_deposits_by_purse.c b/src/exchangedb/pg_select_purse_deposits_by_purse.c
new file mode 100644
index 000000000..94b935cb4
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_deposits_by_purse.c
@@ -0,0 +1,153 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_deposits_by_purse.c
+ * @brief Implementation of the select_purse_deposits_by_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_deposits_by_purse.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #purse_refund_coin_helper_cb().
+ */
+struct PurseRefundCoinContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_PurseRefundCoinCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct PurseRefundCoinContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+purse_refund_coin_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PurseRefundCoinContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount amount_with_fee;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_DenominationPublicKey denom_pub;
+ uint64_t rowid;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount_with_fee),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin_pub),
+ GNUNET_PQ_result_spec_uint64 ("purse_deposit_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ &amount_with_fee,
+ &coin_pub,
+ &denom_pub);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_deposits_by_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ TALER_EXCHANGEDB_PurseRefundCoinCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct PurseRefundCoinContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_purse_deposits_by_purse",
+ "SELECT"
+ " pd.purse_deposit_serial_id"
+ ",pd.amount_with_fee"
+ ",pd.coin_pub"
+ ",denom.denom_pub"
+ " FROM purse_deposits pd"
+ " JOIN known_coins kc"
+ " USING (coin_pub)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE purse_pub=$1;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_purse_deposits_by_purse",
+ params,
+ &purse_refund_coin_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_purse_deposits_by_purse.h b/src/exchangedb/pg_select_purse_deposits_by_purse.h
new file mode 100644
index 000000000..203f9a151
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_deposits_by_purse.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_deposits_by_purse.h
+ * @brief implementation of the select_purse_deposits_by_purse function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_DEPOSITS_BY_PURSE_H
+#define PG_SELECT_PURSE_DEPOSITS_BY_PURSE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select coin affected by purse refund.
+ *
+ * @param cls closure
+ * @param purse_pub purse that was refunded
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_deposits_by_purse (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ TALER_EXCHANGEDB_PurseRefundCoinCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_purse_merge.c b/src/exchangedb/pg_select_purse_merge.c
new file mode 100644
index 000000000..ecc047cc5
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_merge.c
@@ -0,0 +1,80 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_merge.c
+ * @brief Implementation of the select_purse_merge function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_merge.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_merge (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseMergeSignatureP *merge_sig,
+ struct GNUNET_TIME_Timestamp *merge_timestamp,
+ char **partner_url,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *refunded)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merge_sig",
+ merge_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ reserve_pub),
+ GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
+ merge_timestamp),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("partner_base_url",
+ partner_url),
+ NULL),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_bool ("refunded",
+ refunded),
+ NULL),
+ GNUNET_PQ_result_spec_end
+ };
+
+ *partner_url = NULL;
+ *refunded = false;
+ PREPARE (pg,
+ "select_purse_merge",
+ "SELECT "
+ " pm.reserve_pub"
+ ",pm.merge_sig"
+ ",pm.merge_timestamp"
+ ",pr.partner_base_url"
+ ",pd.refunded"
+ " FROM purse_merges pm"
+ " LEFT JOIN purse_decision pd USING (purse_pub)"
+ " LEFT JOIN partners pr USING (partner_serial_id)"
+ " WHERE pm.purse_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_purse_merge",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_select_purse_merge.h b/src/exchangedb/pg_select_purse_merge.h
new file mode 100644
index 000000000..8054974aa
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_merge.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_purse_merge.h
+ * @brief implementation of the select_purse_merge function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_MERGE_H
+#define PG_SELECT_PURSE_MERGE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to approve merging of a purse with
+ * an account, made by the receiving account.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param purse_pub public key of the purse
+ * @param[out] merge_sig set to the signature confirming the merge
+ * @param[out] merge_timestamp set to the time of the merge
+ * @param[out] partner_url set to the URL of the target exchange, or NULL if the target exchange is us. To be freed by the caller.
+ * @param[out] reserve_pub set to the public key of the reserve/account being credited
+ * @param[out] refunded set to true if purse was refunded
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_merge (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ struct TALER_PurseMergeSignatureP *merge_sig,
+ struct GNUNET_TIME_Timestamp *merge_timestamp,
+ char **partner_url,
+ struct TALER_ReservePublicKeyP *reserve_pub,
+ bool *refunded);
+
+#endif
diff --git a/src/exchangedb/pg_select_purse_merges_above_serial_id.c b/src/exchangedb/pg_select_purse_merges_above_serial_id.c
new file mode 100644
index 000000000..cd06d65e9
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_merges_above_serial_id.c
@@ -0,0 +1,190 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_purse_merges_above_serial_id.c
+ * @brief Implementation of the select_purse_merges_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_merges_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #purse_deposit_serial_helper_cb().
+ */
+struct PurseMergeSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_PurseMergeCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct PurseMergeSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+purse_merges_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PurseMergeSerialContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ char *partner_base_url = NULL;
+ struct TALER_Amount amount;
+ struct TALER_Amount balance;
+ uint32_t flags32;
+ enum TALER_WalletAccountMergeFlags flags;
+ struct TALER_PurseMergePublicKeyP merge_pub;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_PurseMergeSignatureP merge_sig;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("balance",
+ &balance),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("partner_base_url",
+ &partner_base_url),
+ NULL),
+ GNUNET_PQ_result_spec_uint32 ("flags",
+ &flags32),
+ GNUNET_PQ_result_spec_timestamp ("merge_timestamp",
+ &merge_timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("merge_sig",
+ &merge_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("merge_pub",
+ &merge_pub),
+ GNUNET_PQ_result_spec_uint64 ("purse_merge_request_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ flags = (enum TALER_WalletAccountMergeFlags) flags32;
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ partner_base_url,
+ &amount,
+ &balance,
+ flags,
+ &merge_pub,
+ &reserve_pub,
+ &merge_sig,
+ &purse_pub,
+ merge_timestamp);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_merges_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseMergeCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct PurseMergeSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_purse_merge_incr",
+ "SELECT"
+ " pm.purse_merge_request_serial_id"
+ ",partner_base_url"
+ ",pr.amount_with_fee"
+ ",pr.balance"
+ ",pr.flags"
+ ",pr.merge_pub"
+ ",pm.reserve_pub"
+ ",pm.merge_sig"
+ ",pm.purse_pub"
+ ",pm.merge_timestamp"
+ " FROM purse_merges pm"
+ " JOIN purse_requests pr USING (purse_pub)"
+ " LEFT JOIN partners USING (partner_serial_id)"
+ " WHERE ("
+ " (purse_merge_request_serial_id>=$1)"
+ " )"
+ " ORDER BY purse_merge_request_serial_id ASC;");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_purse_merge_incr",
+ params,
+ &purse_merges_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_purse_merges_above_serial_id.h b/src/exchangedb/pg_select_purse_merges_above_serial_id.h
new file mode 100644
index 000000000..d63db55dd
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_merges_above_serial_id.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_purse_merges_above_serial_id.h
+ * @brief implementation of the select_purse_merges_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_MERGES_ABOVE_SERIAL_ID_H
+#define PG_SELECT_PURSE_MERGES_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select purse merges deposits above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_merges_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseMergeCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_purse_requests_above_serial_id.c b/src/exchangedb/pg_select_purse_requests_above_serial_id.c
new file mode 100644
index 000000000..61a4f2041
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_requests_above_serial_id.c
@@ -0,0 +1,178 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_purse_requests_above_serial_id.c
+ * @brief Implementation of the select_purse_requests_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_purse_requests_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #purse_deposit_serial_helper_cb().
+ */
+struct PurseRequestsSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_PurseRequestCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct PurseRequestsSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+purse_requests_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PurseRequestsSerialContext *dsc = cls;
+ struct PostgresClosure *pg = dsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct TALER_Amount target_amount;
+ uint32_t age_limit;
+ struct TALER_PurseMergePublicKeyP merge_pub;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_PurseContractSignatureP purse_sig;
+ struct GNUNET_TIME_Timestamp purse_creation;
+ struct GNUNET_TIME_Timestamp purse_expiration;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &target_amount),
+ GNUNET_PQ_result_spec_uint32 ("age_limit",
+ &age_limit),
+ GNUNET_PQ_result_spec_timestamp ("purse_creation",
+ &purse_creation),
+ GNUNET_PQ_result_spec_timestamp ("purse_expiration",
+ &purse_expiration),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_pub",
+ &purse_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_PQ_result_spec_auto_from_type ("purse_sig",
+ &purse_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("merge_pub",
+ &merge_pub),
+ GNUNET_PQ_result_spec_uint64 ("purse_requests_request_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ dsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = dsc->cb (dsc->cb_cls,
+ rowid,
+ &purse_pub,
+ &merge_pub,
+ purse_creation,
+ purse_expiration,
+ &h_contract_terms,
+ age_limit,
+ &target_amount,
+ &purse_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_requests_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseRequestCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct PurseRequestsSerialContext dsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_purse_requests_incr",
+ "SELECT"
+ " purse_requests_serial_id"
+ ",purse_pub"
+ ",amount_with_fee"
+ ",age_limit"
+ ",h_contract_terms"
+ ",purse_creation"
+ ",purse_expiration"
+ ",merge_pub"
+ ",purse_sig"
+ " FROM purse_requests"
+ " WHERE ("
+ " (purse_requests_serial_id>=$1)"
+ " )"
+ " ORDER BY purse_requests_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_purse_requests_incr",
+ params,
+ &purse_requests_serial_helper_cb,
+ &dsc);
+ if (GNUNET_OK != dsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_purse_requests_above_serial_id.h b/src/exchangedb/pg_select_purse_requests_above_serial_id.h
new file mode 100644
index 000000000..25323e3d0
--- /dev/null
+++ b/src/exchangedb/pg_select_purse_requests_above_serial_id.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_purse_requests_above_serial_id.h
+ * @brief implementation of the select_purse_requests_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_PURSE_REQUESTS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_PURSE_REQUESTS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select purse requestss deposits above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_purse_requests_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_PurseRequestCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_recoup_above_serial_id.c b/src/exchangedb/pg_select_recoup_above_serial_id.c
new file mode 100644
index 000000000..5791ee500
--- /dev/null
+++ b/src/exchangedb/pg_select_recoup_above_serial_id.c
@@ -0,0 +1,194 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_recoup_above_serial_id.c
+ * @brief Implementation of the select_recoup_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_recoup_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #recoup_serial_helper_cb().
+ */
+struct RecoupSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_RecoupCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct RecoupSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+recoup_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct RecoupSerialContext *psc = cls;
+ struct PostgresClosure *pg = psc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_CoinPublicInfo coin;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
+ struct TALER_Amount amount;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_BlindedCoinHashP h_blind_ev;
+ struct GNUNET_TIME_Timestamp timestamp;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
+ &rowid),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin.coin_pub),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &coin_blind),
+ GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
+ &h_blind_ev),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &coin.denom_pub_hash),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &coin.h_age_commitment),
+ &coin.no_age_commitment),
+ TALER_PQ_result_spec_denom_sig ("denom_sig",
+ &coin.denom_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_end
+ };
+ int ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ psc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = psc->cb (psc->cb_cls,
+ rowid,
+ timestamp,
+ &amount,
+ &reserve_pub,
+ &coin,
+ &denom_pub,
+ &coin_sig,
+ &coin_blind);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_recoup_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RecoupCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct RecoupSerialContext psc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "recoup_get_incr",
+ "SELECT"
+ " recoup_uuid"
+ ",recoup_timestamp"
+ ",reserves.reserve_pub"
+ ",coins.coin_pub"
+ ",coin_sig"
+ ",coin_blind"
+ ",ro.h_blind_ev"
+ ",denoms.denom_pub_hash"
+ ",coins.denom_sig"
+ ",coins.age_commitment_hash"
+ ",denoms.denom_pub"
+ ",amount"
+ " FROM recoup"
+ " JOIN known_coins coins"
+ " USING (coin_pub)"
+ " JOIN reserves_out ro"
+ " USING (reserve_out_serial_id)"
+ " JOIN reserves"
+ " USING (reserve_uuid)"
+ " JOIN denominations denoms"
+ " ON (coins.denominations_serial = denoms.denominations_serial)"
+ " WHERE recoup_uuid>=$1"
+ " ORDER BY recoup_uuid ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "recoup_get_incr",
+ params,
+ &recoup_serial_helper_cb,
+ &psc);
+ if (GNUNET_OK != psc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_recoup_above_serial_id.h b/src/exchangedb/pg_select_recoup_above_serial_id.h
new file mode 100644
index 000000000..9be0b5c35
--- /dev/null
+++ b/src/exchangedb/pg_select_recoup_above_serial_id.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_recoup_above_serial_id.h
+ * @brief implementation of the select_recoup_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RECOUP_ABOVE_SERIAL_ID_H
+#define PG_SELECT_RECOUP_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Function called to select recoup requests the exchange
+ * received, ordered by serial ID (monotonically increasing).
+ *
+ * @param cls closure
+ * @param serial_id lowest serial ID to include (select larger or equal)
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_recoup_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RecoupCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_recoup_refresh_above_serial_id.c b/src/exchangedb/pg_select_recoup_refresh_above_serial_id.c
new file mode 100644
index 000000000..22f906738
--- /dev/null
+++ b/src/exchangedb/pg_select_recoup_refresh_above_serial_id.c
@@ -0,0 +1,203 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_recoup_refresh_above_serial_id.c
+ * @brief Implementation of the select_recoup_refresh_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_recoup_refresh_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #recoup_refresh_serial_helper_cb().
+ */
+struct RecoupRefreshSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_RecoupRefreshCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct RecoupRefreshSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+recoup_refresh_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct RecoupRefreshSerialContext *psc = cls;
+ struct PostgresClosure *pg = psc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct TALER_CoinSpendPublicKeyP old_coin_pub;
+ struct TALER_CoinPublicInfo coin;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_DenominationHashP old_denom_pub_hash;
+ struct TALER_Amount amount;
+ struct TALER_BlindedCoinHashP h_blind_ev;
+ struct GNUNET_TIME_Timestamp timestamp;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
+ &rowid),
+ GNUNET_PQ_result_spec_timestamp ("recoup_timestamp",
+ &timestamp),
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
+ &old_coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("old_denom_pub_hash",
+ &old_denom_pub_hash),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &coin.coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
+ &coin_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
+ &coin_blind),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
+ &h_blind_ev),
+ GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
+ &coin.denom_pub_hash),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &coin.h_age_commitment),
+ &coin.no_age_commitment),
+ TALER_PQ_result_spec_denom_sig ("denom_sig",
+ &coin.denom_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ psc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = psc->cb (psc->cb_cls,
+ rowid,
+ timestamp,
+ &amount,
+ &old_coin_pub,
+ &old_denom_pub_hash,
+ &coin,
+ &denom_pub,
+ &coin_sig,
+ &coin_blind);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_recoup_refresh_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RecoupRefreshCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct RecoupRefreshSerialContext psc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "recoup_refresh_get_incr",
+ "SELECT"
+ " recoup_refresh_uuid"
+ ",recoup_timestamp"
+ ",old_coins.coin_pub AS old_coin_pub"
+ ",new_coins.age_commitment_hash"
+ ",old_denoms.denom_pub_hash AS old_denom_pub_hash"
+ ",new_coins.coin_pub As coin_pub"
+ ",coin_sig"
+ ",coin_blind"
+ ",new_denoms.denom_pub AS denom_pub"
+ ",rrc.h_coin_ev AS h_blind_ev"
+ ",new_denoms.denom_pub_hash"
+ ",new_coins.denom_sig AS denom_sig"
+ ",amount"
+ " FROM recoup_refresh"
+ " INNER JOIN refresh_revealed_coins rrc"
+ " USING (rrc_serial)"
+ " INNER JOIN refresh_commitments rfc"
+ " ON (rrc.melt_serial_id = rfc.melt_serial_id)"
+ " INNER JOIN known_coins old_coins"
+ " ON (rfc.old_coin_pub = old_coins.coin_pub)"
+ " INNER JOIN known_coins new_coins"
+ " ON (new_coins.coin_pub = recoup_refresh.coin_pub)"
+ " INNER JOIN denominations new_denoms"
+ " ON (new_coins.denominations_serial = new_denoms.denominations_serial)"
+ " INNER JOIN denominations old_denoms"
+ " ON (old_coins.denominations_serial = old_denoms.denominations_serial)"
+ " WHERE recoup_refresh_uuid>=$1"
+ " ORDER BY recoup_refresh_uuid ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "recoup_refresh_get_incr",
+ params,
+ &recoup_refresh_serial_helper_cb,
+ &psc);
+ if (GNUNET_OK != psc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_recoup_refresh_above_serial_id.h b/src/exchangedb/pg_select_recoup_refresh_above_serial_id.h
new file mode 100644
index 000000000..0d7b72fc3
--- /dev/null
+++ b/src/exchangedb/pg_select_recoup_refresh_above_serial_id.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_recoup_refresh_above_serial_id.h
+ * @brief implementation of the select_recoup_refresh_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RECOUP_REFRESH_ABOVE_SERIAL_ID_H
+#define PG_SELECT_RECOUP_REFRESH_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to select recoup requests the exchange received for
+ * refreshed coins, ordered by serial ID (monotonically increasing).
+ *
+ * @param cls closure
+ * @param serial_id lowest serial ID to include (select larger or equal)
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_recoup_refresh_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RecoupRefreshCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_refreshes_above_serial_id.c b/src/exchangedb/pg_select_refreshes_above_serial_id.c
new file mode 100644
index 000000000..db432269c
--- /dev/null
+++ b/src/exchangedb/pg_select_refreshes_above_serial_id.c
@@ -0,0 +1,179 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_refreshes_above_serial_id.c
+ * @brief Implementation of the select_refreshes_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_refreshes_above_serial_id.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #refreshs_serial_helper_cb().
+ */
+struct RefreshsSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_RefreshesCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct RefreshsSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+refreshs_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct RefreshsSerialContext *rsc = cls;
+ struct PostgresClosure *pg = rsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_AgeCommitmentHash h_age_commitment;
+ bool ac_isnull;
+ struct TALER_Amount amount_with_fee;
+ uint32_t noreveal_index;
+ uint64_t rowid;
+ struct TALER_RefreshCommitmentP rc;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash",
+ &h_age_commitment),
+ &ac_isnull),
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
+ &coin_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
+ &coin_sig),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount_with_fee),
+ GNUNET_PQ_result_spec_uint32 ("noreveal_index",
+ &noreveal_index),
+ GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_auto_from_type ("rc",
+ &rc),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ rsc->status = GNUNET_SYSERR;
+ return;
+ }
+
+ ret = rsc->cb (rsc->cb_cls,
+ rowid,
+ &denom_pub,
+ ac_isnull ? NULL : &h_age_commitment,
+ &coin_pub,
+ &coin_sig,
+ &amount_with_fee,
+ noreveal_index,
+ &rc);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_refreshes_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RefreshesCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct RefreshsSerialContext rsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_refresh_commitments_incr",
+ "SELECT"
+ " denom.denom_pub"
+ ",kc.coin_pub AS old_coin_pub"
+ ",kc.age_commitment_hash"
+ ",old_coin_sig"
+ ",amount_with_fee"
+ ",noreveal_index"
+ ",melt_serial_id"
+ ",rc"
+ " FROM refresh_commitments"
+ " JOIN known_coins kc"
+ " ON (refresh_commitments.old_coin_pub = kc.coin_pub)"
+ " JOIN denominations denom"
+ " ON (kc.denominations_serial = denom.denominations_serial)"
+ " WHERE melt_serial_id>=$1"
+ " ORDER BY melt_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_refresh_commitments_incr",
+ params,
+ &refreshs_serial_helper_cb,
+ &rsc);
+ if (GNUNET_OK != rsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_refreshes_above_serial_id.h b/src/exchangedb/pg_select_refreshes_above_serial_id.h
new file mode 100644
index 000000000..2d1db275c
--- /dev/null
+++ b/src/exchangedb/pg_select_refreshes_above_serial_id.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_refreshes_above_serial_id.h
+ * @brief implementation of the select_refreshes_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_REFRESHES_ABOVE_SERIAL_ID_H
+#define PG_SELECT_REFRESHES_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select refresh sessions above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_refreshes_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RefreshesCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_refunds_above_serial_id.c b/src/exchangedb/pg_select_refunds_above_serial_id.c
new file mode 100644
index 000000000..396e8d3d5
--- /dev/null
+++ b/src/exchangedb/pg_select_refunds_above_serial_id.c
@@ -0,0 +1,223 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_refunds_above_serial_id.c
+ * @brief Implementation of the select_refunds_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_refunds_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #refunds_serial_helper_cb().
+ */
+struct RefundsSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_RefundCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct RefundsSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+refunds_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct RefundsSerialContext *rsc = cls;
+ struct PostgresClosure *pg = rsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_EXCHANGEDB_Refund refund;
+ struct TALER_DenominationPublicKey denom_pub;
+ uint64_t rowid;
+ bool full_refund;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
+ &refund.details.merchant_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
+ &refund.details.merchant_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
+ &refund.details.h_contract_terms),
+ GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
+ &refund.details.rtransaction_id),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
+ &refund.coin.coin_pub),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &refund.details.refund_amount),
+ GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ rsc->status = GNUNET_SYSERR;
+ return;
+ }
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&rowid),
+ GNUNET_PQ_query_param_end
+ };
+ struct TALER_Amount amount_with_fee;
+ uint64_t s_f;
+ uint64_t s_v;
+ struct GNUNET_PQ_ResultSpec rs2[] = {
+ GNUNET_PQ_result_spec_uint64 ("s_v",
+ &s_v),
+ GNUNET_PQ_result_spec_uint64 ("s_f",
+ &s_f),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount_with_fee),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "test_refund_full",
+ params,
+ rs2);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ rsc->status = GNUNET_SYSERR;
+ return;
+ }
+ /* normalize */
+ s_v += s_f / TALER_AMOUNT_FRAC_BASE;
+ s_f %= TALER_AMOUNT_FRAC_BASE;
+ full_refund = (s_v >= amount_with_fee.value) &&
+ (s_f >= amount_with_fee.fraction);
+ }
+ ret = rsc->cb (rsc->cb_cls,
+ rowid,
+ &denom_pub,
+ &refund.coin.coin_pub,
+ &refund.details.merchant_pub,
+ &refund.details.merchant_sig,
+ &refund.details.h_contract_terms,
+ refund.details.rtransaction_id,
+ full_refund,
+ &refund.details.refund_amount);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_refunds_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RefundCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct RefundsSerialContext rsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Fetch refunds with rowid '\geq' the given parameter */
+ PREPARE (pg,
+ "audit_get_refunds_incr",
+ "SELECT"
+ " bdep.merchant_pub"
+ ",ref.merchant_sig"
+ ",bdep.h_contract_terms"
+ ",ref.rtransaction_id"
+ ",denom.denom_pub"
+ ",kc.coin_pub"
+ ",ref.amount_with_fee"
+ ",ref.refund_serial_id"
+ " FROM refunds ref"
+ " JOIN batch_deposits bdep"
+ " ON (ref.batch_deposit_serial_id=bdep.batch_deposit_serial_id)"
+ " JOIN coin_deposits cdep"
+ " ON (ref.coin_pub=cdep.coin_pub AND ref.batch_deposit_serial_id=cdep.batch_deposit_serial_id)"
+ " JOIN known_coins kc"
+ " ON (cdep.coin_pub=kc.coin_pub)"
+ " JOIN denominations denom"
+ " ON (kc.denominations_serial=denom.denominations_serial)"
+ " WHERE ref.refund_serial_id>=$1"
+ " ORDER BY ref.refund_serial_id ASC;");
+ PREPARE (pg,
+ "test_refund_full",
+ "SELECT"
+ " CAST(SUM(CAST((ref.amount_with_fee).frac AS INT8)) AS INT8) AS s_f"
+ ",CAST(SUM((ref.amount_with_fee).val) AS INT8) AS s_v"
+ ",cdep.amount_with_fee"
+ " FROM refunds ref"
+ " JOIN coin_deposits cdep"
+ " ON (ref.coin_pub=cdep.coin_pub AND ref.batch_deposit_serial_id=cdep.batch_deposit_serial_id)"
+ " WHERE ref.refund_serial_id=$1"
+ " GROUP BY (cdep.amount_with_fee);");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_refunds_incr",
+ params,
+ &refunds_serial_helper_cb,
+ &rsc);
+ if (GNUNET_OK != rsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_refunds_above_serial_id.h b/src/exchangedb/pg_select_refunds_above_serial_id.h
new file mode 100644
index 000000000..b33816a94
--- /dev/null
+++ b/src/exchangedb/pg_select_refunds_above_serial_id.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_refunds_above_serial_id.h
+ * @brief implementation of the select_refunds_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_REFUNDS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_REFUNDS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select refunds above @a serial_id in monotonically increasing
+ * order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_refunds_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_RefundCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_refunds_by_coin.c b/src/exchangedb/pg_select_refunds_by_coin.c
new file mode 100644
index 000000000..d9cd6dd3c
--- /dev/null
+++ b/src/exchangedb/pg_select_refunds_by_coin.c
@@ -0,0 +1,143 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_refunds_by_coin.c
+ * @brief Implementation of the select_refunds_by_coin function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_refunds_by_coin.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_refunds_cb().
+ */
+struct SelectRefundContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_RefundCoinCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to #GNUNET_SYSERR on error.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct SelectRefundContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+get_refunds_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct SelectRefundContext *srctx = cls;
+ struct PostgresClosure *pg = srctx->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_Amount amount_with_fee;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount_with_fee),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ srctx->status = GNUNET_SYSERR;
+ return;
+ }
+ if (GNUNET_OK !=
+ srctx->cb (srctx->cb_cls,
+ &amount_with_fee))
+ return;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_refunds_by_coin (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PrivateContractHashP *h_contract,
+ TALER_EXCHANGEDB_RefundCoinCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (coin_pub),
+ GNUNET_PQ_query_param_auto_from_type (merchant_pub),
+ GNUNET_PQ_query_param_auto_from_type (h_contract),
+ GNUNET_PQ_query_param_end
+ };
+ struct SelectRefundContext srctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ const char *query = "get_refunds_by_coin_and_contract";
+
+ PREPARE (pg,
+ query,
+ "SELECT"
+ " ref.amount_with_fee"
+ " FROM refunds ref"
+ " JOIN coin_deposits cdep"
+ " USING (coin_pub,batch_deposit_serial_id)"
+ " JOIN batch_deposits bdep"
+ " ON (ref.batch_deposit_serial_id = bdep.batch_deposit_serial_id)"
+ " WHERE ref.coin_pub=$1"
+ " AND bdep.merchant_pub=$2"
+ " AND bdep.h_contract_terms=$3;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ query,
+ params,
+ &get_refunds_cb,
+ &srctx);
+ if (GNUNET_SYSERR == srctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_refunds_by_coin.h b/src/exchangedb/pg_select_refunds_by_coin.h
new file mode 100644
index 000000000..72df13fda
--- /dev/null
+++ b/src/exchangedb/pg_select_refunds_by_coin.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_refunds_by_coin.h
+ * @brief implementation of the select_refunds_by_coin function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_REFUNDS_BY_COIN_H
+#define PG_SELECT_REFUNDS_BY_COIN_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select refunds by @a coin_pub, @a merchant_pub and @a h_contract.
+ *
+ * @param cls closure of plugin
+ * @param coin_pub coin to get refunds for
+ * @param merchant_pub merchant to get refunds for
+ * @param h_contract contract (hash) to get refunds for
+ * @param cb function to call for each refund found
+ * @param cb_cls closure for @a cb
+ * @return query result status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_refunds_by_coin (
+ void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_PrivateContractHashP *h_contract,
+ TALER_EXCHANGEDB_RefundCoinCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_reserve_close_info.c b/src/exchangedb/pg_select_reserve_close_info.c
new file mode 100644
index 000000000..eccba8e4c
--- /dev/null
+++ b/src/exchangedb/pg_select_reserve_close_info.c
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_reserve_close_info.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_reserve_close_info.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserve_close_info (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_Amount *balance,
+ char **payto_uri)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (reserve_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_result_spec_amount ("current_balance",
+ pg->currency,
+ balance),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ payto_uri),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "select_reserve_close_info",
+ "SELECT "
+ " r.current_balance"
+ ",wt.payto_uri"
+ " FROM reserves r"
+ " LEFT JOIN reserves_in ri USING (reserve_pub)"
+ " LEFT JOIN wire_targets wt ON (ri.wire_source_h_payto = wt.wire_target_h_payto)"
+ " WHERE reserve_pub=$1;");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "select_reserve_close_info",
+ params,
+ rs);
+}
diff --git a/src/exchangedb/pg_select_reserve_close_info.h b/src/exchangedb/pg_select_reserve_close_info.h
new file mode 100644
index 000000000..2b90ffd05
--- /dev/null
+++ b/src/exchangedb/pg_select_reserve_close_info.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_reserve_close_info.h
+ * @brief implementation of the select_reserve_close_info function
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RESERVE_CLOSE_INFO_H
+#define PG_SELECT_RESERVE_CLOSE_INFO_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Select information needed to see if we can close
+ * a reserve.
+ *
+ * @param cls closure
+ * @param reserve_pub which reserve is this about?
+ * @param[out] balance current reserve balance
+ * @param[out] payto_uri set to URL of account that
+ * originally funded the reserve;
+ * could be set to NULL if not known
+ * @return transaction status code, 0 if reserve unknown
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserve_close_info (
+ void *cls,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ struct TALER_Amount *balance,
+ char **payto_uri);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_reserve_closed_above_serial_id.c b/src/exchangedb/pg_select_reserve_closed_above_serial_id.c
new file mode 100644
index 000000000..d24d6a600
--- /dev/null
+++ b/src/exchangedb/pg_select_reserve_closed_above_serial_id.c
@@ -0,0 +1,178 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_reserve_closed_above_serial_id.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_reserve_history.h"
+#include "plugin_exchangedb_common.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #reserve_closed_serial_helper_cb().
+ */
+struct ReserveClosedSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_ReserveClosedCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin's context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct ReserveClosedSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserve_closed_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveClosedSerialContext *rcsc = cls;
+ struct PostgresClosure *pg = rcsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ char *receiver_account;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct TALER_Amount amount_with_fee;
+ struct TALER_Amount closing_fee;
+ struct GNUNET_TIME_Timestamp execution_date;
+ uint64_t close_request_row;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("close_uuid",
+ &rowid),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &execution_date),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid",
+ &wtid),
+ GNUNET_PQ_result_spec_string ("receiver_account",
+ &receiver_account),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount_with_fee),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
+ &closing_fee),
+ GNUNET_PQ_result_spec_uint64 ("close_request_row",
+ &close_request_row),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ rcsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = rcsc->cb (rcsc->cb_cls,
+ rowid,
+ execution_date,
+ &amount_with_fee,
+ &closing_fee,
+ &reserve_pub,
+ receiver_account,
+ &wtid,
+ close_request_row);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserve_closed_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveClosedCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct ReserveClosedSerialContext rcsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Used in #postgres_select_reserve_closed_above_serial_id() to
+ obtain information about closed reserves */
+ PREPARE (
+ pg,
+ "reserves_close_get_incr",
+ "SELECT"
+ " close_uuid"
+ ",reserves.reserve_pub"
+ ",execution_date"
+ ",wtid"
+ ",payto_uri AS receiver_account"
+ ",amount"
+ ",closing_fee"
+ ",close_request_row"
+ " FROM reserves_close"
+ " JOIN wire_targets"
+ " USING (wire_target_h_payto)"
+ " JOIN reserves"
+ " USING (reserve_pub)"
+ " WHERE close_uuid>=$1"
+ " ORDER BY close_uuid ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "reserves_close_get_incr",
+ params,
+ &reserve_closed_serial_helper_cb,
+ &rcsc);
+ if (GNUNET_OK != rcsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_reserve_closed_above_serial_id.h b/src/exchangedb/pg_select_reserve_closed_above_serial_id.h
new file mode 100644
index 000000000..af3c8631e
--- /dev/null
+++ b/src/exchangedb/pg_select_reserve_closed_above_serial_id.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_reserve_closed_above_serial_id.h
+ * @brief implementation of the select_reserve_closed_above_serial_id function
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RESERVE_CLOSED_ABOVE_SERIAL_ID_H
+#define PG_SELECT_RESERVE_CLOSED_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to select reserve close operations the aggregator
+ * triggered, ordered by serial ID (monotonically increasing).
+ *
+ * @param cls closure
+ * @param serial_id lowest serial ID to include (select larger or equal)
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserve_closed_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveClosedCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_reserve_open_above_serial_id.c b/src/exchangedb/pg_select_reserve_open_above_serial_id.c
new file mode 100644
index 000000000..1675e71a7
--- /dev/null
+++ b/src/exchangedb/pg_select_reserve_open_above_serial_id.c
@@ -0,0 +1,168 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_reserve_open_above_serial_id.c
+ * @brief Low-level (statement-level) Postgres database access for the exchange
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_reserve_open_above_serial_id.h"
+#include "plugin_exchangedb_common.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #reserve_open_serial_helper_cb().
+ */
+struct ReserveOpenSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_ReserveOpenCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin's context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct ReserveOpenSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserve_open_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReserveOpenSerialContext *rcsc = cls;
+ struct PostgresClosure *pg = rcsc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_ReserveSignatureP reserve_sig;
+ uint32_t requested_purse_limit;
+ struct GNUNET_TIME_Timestamp request_timestamp;
+ struct GNUNET_TIME_Timestamp reserve_expiration;
+ struct TALER_Amount reserve_payment;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("open_request_uuid",
+ &rowid),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &reserve_sig),
+ GNUNET_PQ_result_spec_timestamp ("request_timestamp",
+ &request_timestamp),
+ GNUNET_PQ_result_spec_timestamp ("expiration_date",
+ &reserve_expiration),
+ GNUNET_PQ_result_spec_uint32 ("requested_purse_limit",
+ &requested_purse_limit),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("reserve_payment",
+ &reserve_payment),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ rcsc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = rcsc->cb (rcsc->cb_cls,
+ rowid,
+ &reserve_payment,
+ request_timestamp,
+ reserve_expiration,
+ requested_purse_limit,
+ &reserve_pub,
+ &reserve_sig);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserve_open_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveOpenCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct ReserveOpenSerialContext rcsc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (
+ pg,
+ "reserves_open_get_incr",
+ "SELECT"
+ " open_request_uuid"
+ ",reserve_pub"
+ ",request_timestamp"
+ ",expiration_date"
+ ",reserve_sig"
+ ",reserve_payment"
+ ",requested_purse_limit"
+ " FROM reserves_open_requests"
+ " WHERE open_request_uuid>=$1"
+ " ORDER BY open_request_uuid ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "reserves_open_get_incr",
+ params,
+ &reserve_open_serial_helper_cb,
+ &rcsc);
+ if (GNUNET_OK != rcsc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_reserve_open_above_serial_id.h b/src/exchangedb/pg_select_reserve_open_above_serial_id.h
new file mode 100644
index 000000000..4ec5b705a
--- /dev/null
+++ b/src/exchangedb/pg_select_reserve_open_above_serial_id.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file pg_select_reserve_open_above_serial_id.h
+ * @brief implementation of the select_reserve_open_above_serial_id function
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RESERVE_OPEN_ABOVE_SERIAL_ID_H
+#define PG_SELECT_RESERVE_OPEN_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Function called to select reserve open operations, ordered by serial ID
+ * (monotonically increasing).
+ *
+ * @param cls closure
+ * @param serial_id lowest serial ID to include (select larger or equal)
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserve_open_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveOpenCallback cb,
+ void *cb_cls);
+
+
+#endif
diff --git a/src/exchangedb/pg_select_reserves_in_above_serial_id.c b/src/exchangedb/pg_select_reserves_in_above_serial_id.c
new file mode 100644
index 000000000..21033e80d
--- /dev/null
+++ b/src/exchangedb/pg_select_reserves_in_above_serial_id.c
@@ -0,0 +1,164 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_reserves_in_above_serial_id.c
+ * @brief Implementation of the select_reserves_in_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_reserves_in_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #reserves_in_serial_helper_cb().
+ */
+struct ReservesInSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_ReserveInCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct ReservesInSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserves_in_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReservesInSerialContext *risc = cls;
+ struct PostgresClosure *pg = risc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_Amount credit;
+ char *sender_account_details;
+ struct GNUNET_TIME_Timestamp execution_date;
+ uint64_t rowid;
+ uint64_t wire_reference;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_uint64 ("wire_reference",
+ &wire_reference),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
+ &credit),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &execution_date),
+ GNUNET_PQ_result_spec_string ("sender_account_details",
+ &sender_account_details),
+ GNUNET_PQ_result_spec_uint64 ("reserve_in_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ risc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = risc->cb (risc->cb_cls,
+ rowid,
+ &reserve_pub,
+ &credit,
+ sender_account_details,
+ wire_reference,
+ execution_date);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserves_in_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveInCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct ReservesInSerialContext risc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_reserves_in_get_transactions_incr",
+ "SELECT"
+ " reserves.reserve_pub"
+ ",wire_reference"
+ ",credit"
+ ",execution_date"
+ ",payto_uri AS sender_account_details"
+ ",reserve_in_serial_id"
+ " FROM reserves_in"
+ " JOIN reserves"
+ " USING (reserve_pub)"
+ " JOIN wire_targets"
+ " ON (wire_source_h_payto = wire_target_h_payto)"
+ " WHERE reserve_in_serial_id>=$1"
+ " ORDER BY reserve_in_serial_id;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_reserves_in_get_transactions_incr",
+ params,
+ &reserves_in_serial_helper_cb,
+ &risc);
+ if (GNUNET_OK != risc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_reserves_in_above_serial_id.h b/src/exchangedb/pg_select_reserves_in_above_serial_id.h
new file mode 100644
index 000000000..5f5dd2ecc
--- /dev/null
+++ b/src/exchangedb/pg_select_reserves_in_above_serial_id.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_reserves_in_above_serial_id.h
+ * @brief implementation of the select_reserves_in_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RESERVES_IN_ABOVE_SERIAL_ID_H
+#define PG_SELECT_RESERVES_IN_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Select inbound wire transfers into reserves_in above @a serial_id
+ * in monotonically increasing order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserves_in_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveInCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.c b/src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.c
new file mode 100644
index 000000000..1c7bc15a0
--- /dev/null
+++ b/src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.c
@@ -0,0 +1,168 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_reserves_in_above_serial_id_by_account.c
+ * @brief Implementation of the select_reserves_in_above_serial_id_by_account function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_reserves_in_above_serial_id_by_account.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #reserves_in_serial_helper_cb().
+ */
+struct ReservesInSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_ReserveInCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct ReservesInSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserves_in_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReservesInSerialContext *risc = cls;
+ struct PostgresClosure *pg = risc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_Amount credit;
+ char *sender_account_details;
+ struct GNUNET_TIME_Timestamp execution_date;
+ uint64_t rowid;
+ uint64_t wire_reference;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_uint64 ("wire_reference",
+ &wire_reference),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
+ &credit),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &execution_date),
+ GNUNET_PQ_result_spec_string ("sender_account_details",
+ &sender_account_details),
+ GNUNET_PQ_result_spec_uint64 ("reserve_in_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ risc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = risc->cb (risc->cb_cls,
+ rowid,
+ &reserve_pub,
+ &credit,
+ sender_account_details,
+ wire_reference,
+ execution_date);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserves_in_above_serial_id_by_account (
+ void *cls,
+ const char *account_name,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveInCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_string (account_name),
+ GNUNET_PQ_query_param_end
+ };
+ struct ReservesInSerialContext risc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_reserves_in_get_transactions_incr_by_account",
+ "SELECT"
+ " reserves.reserve_pub"
+ ",wire_reference"
+ ",credit"
+ ",execution_date"
+ ",payto_uri AS sender_account_details"
+ ",reserve_in_serial_id"
+ " FROM reserves_in"
+ " JOIN reserves "
+ " USING (reserve_pub)"
+ " JOIN wire_targets"
+ " ON (wire_source_h_payto = wire_target_h_payto)"
+ " WHERE reserve_in_serial_id>=$1"
+ " AND exchange_account_section=$2"
+ " ORDER BY reserve_in_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_reserves_in_get_transactions_incr_by_account",
+ params,
+ &reserves_in_serial_helper_cb,
+ &risc);
+ if (GNUNET_OK != risc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.h b/src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.h
new file mode 100644
index 000000000..81855ede9
--- /dev/null
+++ b/src/exchangedb/pg_select_reserves_in_above_serial_id_by_account.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_reserves_in_above_serial_id_by_account.h
+ * @brief implementation of the select_reserves_in_above_serial_id_by_account function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_RESERVES_IN_ABOVE_SERIAL_ID_BY_ACCOUNT_H
+#define PG_SELECT_RESERVES_IN_ABOVE_SERIAL_ID_BY_ACCOUNT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Select inbound wire transfers into reserves_in above @a serial_id
+ * in monotonically increasing order by account.
+ *
+ * @param cls closure
+ * @param account_name name of the account to select by
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_reserves_in_above_serial_id_by_account (
+ void *cls,
+ const char *account_name,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_ReserveInCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_satisfied_kyc_processes.c b/src/exchangedb/pg_select_satisfied_kyc_processes.c
new file mode 100644
index 000000000..e0d884ef1
--- /dev/null
+++ b/src/exchangedb/pg_select_satisfied_kyc_processes.c
@@ -0,0 +1,141 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_satisfied_kyc_processes.c
+ * @brief Implementation of the select_satisfied_kyc_processes function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_satisfied_kyc_processes.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_legitimizations_cb().
+ */
+struct GetLegitimizationsContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_SatisfiedProviderCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct GetLegitimizationsContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_legitimizations_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetLegitimizationsContext *ctx = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *provider_section;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("provider_section",
+ &provider_section),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found satisfied LEGI: %s\n",
+ provider_section);
+ ctx->cb (ctx->cb_cls,
+ provider_section);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_satisfied_kyc_processes (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
+ void *spc_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now
+ = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GetLegitimizationsContext ctx = {
+ .cb = spc,
+ .cb_cls = spc_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "get_satisfied_legitimizations",
+ "SELECT "
+ " provider_section"
+ " FROM legitimization_processes"
+ " WHERE h_payto=$1"
+ " AND expiration_time>=$2;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "get_satisfied_legitimizations",
+ params,
+ &get_legitimizations_cb,
+ &ctx);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Satisfied LEGI check returned %d\n",
+ qs);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_satisfied_kyc_processes.h b/src/exchangedb/pg_select_satisfied_kyc_processes.h
new file mode 100644
index 000000000..bc1639ff9
--- /dev/null
+++ b/src/exchangedb/pg_select_satisfied_kyc_processes.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_satisfied_kyc_processes.h
+ * @brief implementation of the select_satisfied_kyc_processes function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_SATISFIED_KYC_PROCESSES_H
+#define PG_SELECT_SATISFIED_KYC_PROCESSES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Call us on KYC processes satisfied for the given
+ * account.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param spc function to call for each satisfied KYC process
+ * @param spc_cls closure for @a spc
+ * @return transaction status code
+ */
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_satisfied_kyc_processes (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
+ void *spc_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_similar_kyc_attributes.c b/src/exchangedb/pg_select_similar_kyc_attributes.c
new file mode 100644
index 000000000..342f9ef33
--- /dev/null
+++ b/src/exchangedb/pg_select_similar_kyc_attributes.c
@@ -0,0 +1,154 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_similar_kyc_attributes.c
+ * @brief Implementation of the select_similar_kyc_attributes function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_similar_kyc_attributes.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_similar_attributes_cb().
+ */
+struct GetAttributesContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_AttributeCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct GetAttributesContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_attributes_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct GetAttributesContext *ctx = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct TALER_PaytoHashP h_payto;
+ struct GNUNET_TIME_Timestamp collection_time;
+ struct GNUNET_TIME_Timestamp expiration_time;
+ size_t enc_attributes_size;
+ void *enc_attributes;
+ char *provider;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_payto",
+ &h_payto),
+ GNUNET_PQ_result_spec_string ("provider",
+ &provider),
+ GNUNET_PQ_result_spec_timestamp ("collection_time",
+ &collection_time),
+ GNUNET_PQ_result_spec_timestamp ("expiration_time",
+ &expiration_time),
+ GNUNET_PQ_result_spec_variable_size ("encrypted_attributes",
+ &enc_attributes,
+ &enc_attributes_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &h_payto,
+ provider,
+ collection_time,
+ expiration_time,
+ enc_attributes_size,
+ enc_attributes);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_similar_kyc_attributes (
+ void *cls,
+ const struct GNUNET_ShortHashCode *kyc_prox,
+ TALER_EXCHANGEDB_AttributeCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (kyc_prox),
+ GNUNET_PQ_query_param_end
+ };
+ struct GetAttributesContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_similar_kyc_attributes",
+ "SELECT "
+ " h_payto"
+ ",provider"
+ ",collection_time"
+ ",expiration_time"
+ ",encrypted_attributes"
+ " FROM kyc_attributes"
+ " WHERE kyc_prox=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_similar_kyc_attributes",
+ params,
+ &get_attributes_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_similar_kyc_attributes.h b/src/exchangedb/pg_select_similar_kyc_attributes.h
new file mode 100644
index 000000000..72f2407e8
--- /dev/null
+++ b/src/exchangedb/pg_select_similar_kyc_attributes.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_similar_kyc_attributes.h
+ * @brief implementation of the select_similar_kyc_attributes function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_SIMILAR_KYC_ATTRIBUTES_H
+#define PG_SELECT_SIMILAR_KYC_ATTRIBUTES_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Lookup similar KYC attribute data.
+ *
+ * @param cls closure
+ * @param kyc_prox key for similarity search
+ * @param cb callback to invoke on each match
+ * @param cb_cls closure for @a cb
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_similar_kyc_attributes (
+ void *cls,
+ const struct GNUNET_ShortHashCode *kyc_prox,
+ TALER_EXCHANGEDB_AttributeCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_wire_out_above_serial_id.c b/src/exchangedb/pg_select_wire_out_above_serial_id.c
new file mode 100644
index 000000000..8668c429d
--- /dev/null
+++ b/src/exchangedb/pg_select_wire_out_above_serial_id.c
@@ -0,0 +1,157 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_wire_out_above_serial_id.c
+ * @brief Implementation of the select_wire_out_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_wire_out_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #wire_out_serial_helper_cb().
+ */
+struct WireOutSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_WireTransferOutCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ int status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct WireOutSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+wire_out_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct WireOutSerialContext *wosc = cls;
+ struct PostgresClosure *pg = wosc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct GNUNET_TIME_Timestamp date;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ char *payto_uri;
+ struct TALER_Amount amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("wireout_uuid",
+ &rowid),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &date),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
+ &wtid),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_end
+ };
+ int ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ wosc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = wosc->cb (wosc->cb_cls,
+ rowid,
+ date,
+ &wtid,
+ payto_uri,
+ &amount);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_wire_out_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_WireTransferOutCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct WireOutSerialContext wosc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ /* Used in #postgres_select_wire_out_above_serial_id() */
+ PREPARE (pg,
+ "audit_get_wire_incr",
+ "SELECT"
+ " wireout_uuid"
+ ",execution_date"
+ ",wtid_raw"
+ ",payto_uri"
+ ",amount"
+ " FROM wire_out"
+ " JOIN wire_targets"
+ " USING (wire_target_h_payto)"
+ " WHERE wireout_uuid>=$1"
+ " ORDER BY wireout_uuid ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_wire_incr",
+ params,
+ &wire_out_serial_helper_cb,
+ &wosc);
+ if (GNUNET_OK != wosc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_wire_out_above_serial_id.h b/src/exchangedb/pg_select_wire_out_above_serial_id.h
new file mode 100644
index 000000000..e42cb9b0f
--- /dev/null
+++ b/src/exchangedb/pg_select_wire_out_above_serial_id.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_wire_out_above_serial_id.h
+ * @brief implementation of the select_wire_out_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_WIRE_OUT_ABOVE_SERIAL_ID_H
+#define PG_SELECT_WIRE_OUT_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to select all wire transfers the exchange
+ * executed.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_wire_out_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_WireTransferOutCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_wire_out_above_serial_id_by_account.c b/src/exchangedb/pg_select_wire_out_above_serial_id_by_account.c
new file mode 100644
index 000000000..3448c5a49
--- /dev/null
+++ b/src/exchangedb/pg_select_wire_out_above_serial_id_by_account.c
@@ -0,0 +1,161 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_wire_out_above_serial_id_by_account.c
+ * @brief Implementation of the select_wire_out_above_serial_id_by_account function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_wire_out_above_serial_id_by_account.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #wire_out_serial_helper_cb().
+ */
+struct WireOutSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_WireTransferOutCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ int status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct WireOutSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+wire_out_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct WireOutSerialContext *wosc = cls;
+ struct PostgresClosure *pg = wosc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ uint64_t rowid;
+ struct GNUNET_TIME_Timestamp date;
+ struct TALER_WireTransferIdentifierRawP wtid;
+ char *payto_uri;
+ struct TALER_Amount amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("wireout_uuid",
+ &rowid),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &date),
+ GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
+ &wtid),
+ GNUNET_PQ_result_spec_string ("payto_uri",
+ &payto_uri),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_end
+ };
+ int ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ wosc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = wosc->cb (wosc->cb_cls,
+ rowid,
+ date,
+ &wtid,
+ payto_uri,
+ &amount);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_wire_out_above_serial_id_by_account (
+ void *cls,
+ const char *account_name,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_WireTransferOutCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_string (account_name),
+ GNUNET_PQ_query_param_end
+ };
+ struct WireOutSerialContext wosc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "audit_get_wire_incr_by_account",
+ "SELECT"
+ " wireout_uuid"
+ ",execution_date"
+ ",wtid_raw"
+ ",payto_uri"
+ ",amount"
+ " FROM wire_out"
+ " JOIN wire_targets"
+ " USING (wire_target_h_payto)"
+ " WHERE "
+ " wireout_uuid>=$1 "
+ " AND exchange_account_section=$2"
+ " ORDER BY wireout_uuid ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_wire_incr_by_account",
+ params,
+ &wire_out_serial_helper_cb,
+ &wosc);
+ if (GNUNET_OK != wosc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_wire_out_above_serial_id_by_account.h b/src/exchangedb/pg_select_wire_out_above_serial_id_by_account.h
new file mode 100644
index 000000000..04c6a62b2
--- /dev/null
+++ b/src/exchangedb/pg_select_wire_out_above_serial_id_by_account.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_wire_out_above_serial_id_by_account.h
+ * @brief implementation of the select_wire_out_above_serial_id_by_account function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_WIRE_OUT_ABOVE_SERIAL_ID_BY_ACCOUNT_H
+#define PG_SELECT_WIRE_OUT_ABOVE_SERIAL_ID_BY_ACCOUNT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to select all wire transfers the exchange
+ * executed by account.
+ *
+ * @param cls closure
+ * @param account_name account to select
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_wire_out_above_serial_id_by_account (
+ void *cls,
+ const char *account_name,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_WireTransferOutCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.c b/src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.c
new file mode 100644
index 000000000..71ed81833
--- /dev/null
+++ b/src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.c
@@ -0,0 +1,158 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_withdraw_amounts_for_kyc_check.c
+ * @brief Implementation of the select_withdraw_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_withdraw_amounts_for_kyc_check.h"
+#include "pg_select_aggregation_amounts_for_kyc_check.h"
+#include "pg_helper.h"
+
+
+/**
+ * Closure for #get_kyc_amounts_cb().
+ */
+struct KycAmountCheckContext
+{
+ /**
+ * Function to call per result.
+ */
+ TALER_EXCHANGEDB_KycAmountCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Flag set to #GNUNET_OK as long as everything is fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+
+};
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct KycAmountCheckContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+get_kyc_amounts_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct KycAmountCheckContext *ctx = cls;
+ struct PostgresClosure *pg = ctx->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct GNUNET_TIME_Absolute date;
+ struct TALER_Amount amount;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
+ &amount),
+ GNUNET_PQ_result_spec_absolute_time ("date",
+ &date),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ ctx->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = ctx->cb (ctx->cb_cls,
+ &amount,
+ date);
+ GNUNET_PQ_cleanup_result (rs);
+ switch (ret)
+ {
+ case GNUNET_OK:
+ continue;
+ case GNUNET_NO:
+ break;
+ case GNUNET_SYSERR:
+ ctx->status = GNUNET_SYSERR;
+ break;
+ }
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_withdraw_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_absolute_time (&time_limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct KycAmountCheckContext ctx = {
+ .cb = kac,
+ .cb_cls = kac_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "select_kyc_relevant_withdraw_events",
+ "SELECT"
+ " ro.amount_with_fee AS amount"
+ ",ro.execution_date AS date"
+ " FROM reserves_in ri"
+ " JOIN reserve_history rh"
+ " ON (rh.reserve_pub = ri.reserve_pub)"
+ " JOIN reserves_out ro"
+ " ON (ro.reserve_out_serial_id = rh.serial_id)"
+ " WHERE ri.wire_source_h_payto=$1"
+ " AND rh.table_name='reserves_out'"
+ " AND ro.execution_date >= $2"
+ " ORDER BY rh.reserve_history_serial_id DESC");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_kyc_relevant_withdraw_events",
+ params,
+ &get_kyc_amounts_cb,
+ &ctx);
+ if (GNUNET_OK != ctx.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.h b/src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.h
new file mode 100644
index 000000000..9a780adbe
--- /dev/null
+++ b/src/exchangedb/pg_select_withdraw_amounts_for_kyc_check.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_withdraw_amounts_for_kyc_check.h
+ * @brief implementation of the select_withdraw_amounts_for_kyc_check function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_WITHDRAW_AMOUNTS_FOR_KYC_CHECK_H
+#define PG_SELECT_WITHDRAW_AMOUNTS_FOR_KYC_CHECK_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Call @a kac on withdrawn amounts after @a time_limit which are relevant
+ * for a KYC trigger for a the (debited) account identified by @a h_payto.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param time_limit oldest transaction that could be relevant
+ * @param kac function to call for each applicable amount, in reverse chronological order (or until @a kac aborts by returning anything except #GNUNET_OK).
+ * @param kac_cls closure for @a kac
+ * @return transaction status code, @a kac aborting with #GNUNET_NO is not an error
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_withdraw_amounts_for_kyc_check (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute time_limit,
+ TALER_EXCHANGEDB_KycAmountCallback kac,
+ void *kac_cls);
+
+#endif
diff --git a/src/exchangedb/pg_select_withdrawals_above_serial_id.c b/src/exchangedb/pg_select_withdrawals_above_serial_id.c
new file mode 100644
index 000000000..9beb0f936
--- /dev/null
+++ b/src/exchangedb/pg_select_withdrawals_above_serial_id.c
@@ -0,0 +1,170 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_withdrawals_above_serial_id.c
+ * @brief Implementation of the select_withdrawals_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_select_withdrawals_above_serial_id.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #reserves_out_serial_helper_cb().
+ */
+struct ReservesOutSerialContext
+{
+
+ /**
+ * Callback to call.
+ */
+ TALER_EXCHANGEDB_WithdrawCallback cb;
+
+ /**
+ * Closure for @e cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Plugin context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Status code, set to #GNUNET_SYSERR on hard errors.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Helper function to be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct ReservesOutSerialContext`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+reserves_out_serial_helper_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct ReservesOutSerialContext *rosc = cls;
+ struct PostgresClosure *pg = rosc->pg;
+
+ for (unsigned int i = 0; i<num_results; i++)
+ {
+ struct TALER_BlindedCoinHashP h_blind_ev;
+ struct TALER_DenominationPublicKey denom_pub;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct GNUNET_TIME_Timestamp execution_date;
+ struct TALER_Amount amount_with_fee;
+ uint64_t rowid;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
+ &h_blind_ev),
+ TALER_PQ_result_spec_denom_pub ("denom_pub",
+ &denom_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
+ &reserve_pub),
+ GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
+ &reserve_sig),
+ GNUNET_PQ_result_spec_timestamp ("execution_date",
+ &execution_date),
+ TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
+ &amount_with_fee),
+ GNUNET_PQ_result_spec_uint64 ("reserve_out_serial_id",
+ &rowid),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ rosc->status = GNUNET_SYSERR;
+ return;
+ }
+ ret = rosc->cb (rosc->cb_cls,
+ rowid,
+ &h_blind_ev,
+ &denom_pub,
+ &reserve_pub,
+ &reserve_sig,
+ execution_date,
+ &amount_with_fee);
+ GNUNET_PQ_cleanup_result (rs);
+ if (GNUNET_OK != ret)
+ break;
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_withdrawals_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_WithdrawCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial_id),
+ GNUNET_PQ_query_param_end
+ };
+ struct ReservesOutSerialContext rosc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Fetch deposits with rowid '\geq' the given parameter */
+ PREPARE (pg,
+ "audit_get_reserves_out_incr",
+ "SELECT"
+ " h_blind_ev"
+ ",denom.denom_pub"
+ ",reserve_sig"
+ ",reserves.reserve_pub"
+ ",execution_date"
+ ",amount_with_fee"
+ ",reserve_out_serial_id"
+ " FROM reserves_out"
+ " JOIN reserves"
+ " USING (reserve_uuid)"
+ " JOIN denominations denom"
+ " USING (denominations_serial)"
+ " WHERE reserve_out_serial_id>=$1"
+ " ORDER BY reserve_out_serial_id ASC;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "audit_get_reserves_out_incr",
+ params,
+ &reserves_out_serial_helper_cb,
+ &rosc);
+ if (GNUNET_OK != rosc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_select_withdrawals_above_serial_id.h b/src/exchangedb/pg_select_withdrawals_above_serial_id.h
new file mode 100644
index 000000000..2b741a3b4
--- /dev/null
+++ b/src/exchangedb/pg_select_withdrawals_above_serial_id.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_select_withdrawals_above_serial_id.h
+ * @brief implementation of the select_withdrawals_above_serial_id function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_WITHDRAWALS_ABOVE_SERIAL_ID_H
+#define PG_SELECT_WITHDRAWALS_ABOVE_SERIAL_ID_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Select withdraw operations from reserves_out above @a serial_id
+ * in monotonically increasing order.
+ *
+ * @param cls closure
+ * @param serial_id highest serial ID to exclude (select strictly larger)
+ * @param cb function to call on each result
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_select_withdrawals_above_serial_id (
+ void *cls,
+ uint64_t serial_id,
+ TALER_EXCHANGEDB_WithdrawCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_set_extension_manifest.c b/src/exchangedb/pg_set_extension_manifest.c
new file mode 100644
index 000000000..c7db04312
--- /dev/null
+++ b/src/exchangedb/pg_set_extension_manifest.c
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_set_extension_manifest.c
+ * @brief Implementation of the set_extension_manifest function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_set_extension_manifest.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_set_extension_manifest (void *cls,
+ const char *extension_name,
+ const char *manifest)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam pcfg =
+ (NULL == manifest || 0 == *manifest)
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (manifest);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (extension_name),
+ pcfg,
+ GNUNET_PQ_query_param_end
+ };
+
+
+ PREPARE (pg,
+ "set_extension_manifest",
+ "INSERT INTO extensions (name, manifest) VALUES ($1, $2) "
+ "ON CONFLICT (name) "
+ "DO UPDATE SET manifest=$2");
+
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "set_extension_manifest",
+ params);
+}
diff --git a/src/exchangedb/pg_set_extension_manifest.h b/src/exchangedb/pg_set_extension_manifest.h
new file mode 100644
index 000000000..0befeedd8
--- /dev/null
+++ b/src/exchangedb/pg_set_extension_manifest.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_set_extension_manifest.h
+ * @brief implementation of the set_extension_manifest function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SET_EXTENSION_MANIFEST_H
+#define PG_SET_EXTENSION_MANIFEST_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to save the manifest of an extension
+ * (age-restriction, policy_extension_...) After successful storage of the
+ * configuration it triggers the corresponding event.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param extension_name the name of the extension
+ * @param manifest JSON object of the configuration as string
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_set_extension_manifest (void *cls,
+ const char *extension_name,
+ const char *manifest);
+
+#endif
diff --git a/src/exchangedb/pg_set_purse_balance.c b/src/exchangedb/pg_set_purse_balance.c
new file mode 100644
index 000000000..1e34ea6ed
--- /dev/null
+++ b/src/exchangedb/pg_set_purse_balance.c
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_set_purse_balance.c
+ * @brief Implementation of the set_purse_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_set_purse_balance.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_set_purse_balance (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (purse_pub),
+ TALER_PQ_query_param_amount (pg->conn,
+ balance),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "set_purse_balance",
+ "UPDATE purse_requests"
+ " SET balance=$2"
+ " WHERE purse_pub=$1;");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "set_purse_balance",
+ params);
+}
diff --git a/src/exchangedb/pg_set_purse_balance.h b/src/exchangedb/pg_set_purse_balance.h
new file mode 100644
index 000000000..44b765568
--- /dev/null
+++ b/src/exchangedb/pg_set_purse_balance.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_set_purse_balance.h
+ * @brief implementation of the set_purse_balance function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SET_PURSE_BALANCE_H
+#define PG_SET_PURSE_BALANCE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Set the current @a balance in the purse
+ * identified by @a purse_pub. Used by the auditor
+ * to update the balance as calculated by the auditor.
+ *
+ * @param cls closure
+ * @param purse_pub public key of a purse
+ * @param balance new balance to store under the purse
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_set_purse_balance (
+ void *cls,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const struct TALER_Amount *balance);
+
+#endif
diff --git a/src/exchangedb/pg_setup_wire_target.c b/src/exchangedb/pg_setup_wire_target.c
new file mode 100644
index 000000000..ed6fbe338
--- /dev/null
+++ b/src/exchangedb/pg_setup_wire_target.c
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_setup_wire_target.c
+ * @brief Implementation of the setup_wire_target function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_setup_wire_target.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_setup_wire_target (
+ struct PostgresClosure *pg,
+ const char *payto_uri,
+ struct TALER_PaytoHashP *h_payto)
+{
+ struct GNUNET_PQ_QueryParam iparams[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_end
+ };
+
+ TALER_payto_hash (payto_uri,
+ h_payto);
+
+ PREPARE (pg,
+ "insert_kyc_status",
+ "INSERT INTO wire_targets"
+ " (wire_target_h_payto"
+ " ,payto_uri"
+ " ) VALUES "
+ " ($1, $2)"
+ " ON CONFLICT DO NOTHING");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_kyc_status",
+ iparams);
+}
diff --git a/src/exchangedb/pg_setup_wire_target.h b/src/exchangedb/pg_setup_wire_target.h
new file mode 100644
index 000000000..77512a600
--- /dev/null
+++ b/src/exchangedb/pg_setup_wire_target.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_setup_wire_target.h
+ * @brief implementation of the setup_wire_target function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SETUP_WIRE_TARGET_H
+#define PG_SETUP_WIRE_TARGET_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "pg_helper.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Setup new wire target for @a payto_uri.
+ *
+ * @param pg the plugin-specific state
+ * @param payto_uri the payto URI to check
+ * @param[out] h_payto set to the hash of @a payto_uri
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_setup_wire_target (
+ struct PostgresClosure *pg,
+ const char *payto_uri,
+ struct TALER_PaytoHashP *h_payto);
+
+#endif
diff --git a/src/exchangedb/pg_start.c b/src/exchangedb/pg_start.c
new file mode 100644
index 000000000..de5d698f6
--- /dev/null
+++ b/src/exchangedb/pg_start.c
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_start.c
+ * @brief Implementation of the start function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_preflight.h"
+#include "pg_start.h"
+#include "pg_helper.h"
+
+enum GNUNET_GenericReturnValue
+TEH_PG_start (void *cls,
+ const char *name)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ GNUNET_assert (NULL != name);
+ if (GNUNET_SYSERR ==
+ TEH_PG_preflight (pg))
+ return GNUNET_SYSERR;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting transaction `%s'\n",
+ name);
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR ("Failed to start transaction\n");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ pg->transaction_name = name;
+ return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_start.h b/src/exchangedb/pg_start.h
new file mode 100644
index 000000000..0a3bb9e43
--- /dev/null
+++ b/src/exchangedb/pg_start.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_start.h
+ * @brief implementation of the start function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_START_H
+#define PG_START_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Start a transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging)
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_start (void *cls,
+ const char *name);
+
+#endif
diff --git a/src/exchangedb/pg_start_deferred_wire_out.c b/src/exchangedb/pg_start_deferred_wire_out.c
new file mode 100644
index 000000000..abdc16028
--- /dev/null
+++ b/src/exchangedb/pg_start_deferred_wire_out.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_start_deferred_wire_out.c
+ * @brief Implementation of the start_deferred_wire_out function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_start_deferred_wire_out.h"
+#include "pg_helper.h"
+#include "pg_preflight.h"
+#include "pg_rollback.h"
+
+enum GNUNET_GenericReturnValue
+TEH_PG_start_deferred_wire_out (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute (
+ "START TRANSACTION ISOLATION LEVEL READ COMMITTED;"),
+ GNUNET_PQ_make_execute ("SET CONSTRAINTS ALL DEFERRED;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (GNUNET_SYSERR ==
+ TEH_PG_preflight (pg))
+ return GNUNET_SYSERR;
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR (
+ "Failed to defer wire_out_ref constraint on transaction\n");
+ GNUNET_break (0);
+ TEH_PG_rollback (pg);
+ return GNUNET_SYSERR;
+ }
+ pg->transaction_name = "deferred wire out";
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting READ COMMITTED DEFERRED transaction `%s'\n",
+ pg->transaction_name);
+ return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_start_deferred_wire_out.h b/src/exchangedb/pg_start_deferred_wire_out.h
new file mode 100644
index 000000000..ed444ef70
--- /dev/null
+++ b/src/exchangedb/pg_start_deferred_wire_out.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_start_deferred_wire_out.h
+ * @brief implementation of the start_deferred_wire_out function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_START_DEFERRED_WIRE_OUT_H
+#define PG_START_DEFERRED_WIRE_OUT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Starts a READ COMMITTED transaction where we transiently violate the foreign
+ * constraints on the "wire_out" table as we insert aggregations
+ * and only add the wire transfer out at the end.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_start_deferred_wire_out (void *cls);
+
+#endif
diff --git a/src/exchangedb/pg_start_read_committed.c b/src/exchangedb/pg_start_read_committed.c
new file mode 100644
index 000000000..1c8248ab0
--- /dev/null
+++ b/src/exchangedb/pg_start_read_committed.c
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_start_read_committed.c
+ * @brief Implementation of the start_read_committed function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_start_read_committed.h"
+#include "pg_preflight.h"
+#include "pg_helper.h"
+
+enum GNUNET_GenericReturnValue
+TEH_PG_start_read_committed (void *cls,
+ const char *name)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ COMMITTED"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ GNUNET_assert (NULL != name);
+ if (GNUNET_SYSERR ==
+ TEH_PG_preflight (pg))
+ return GNUNET_SYSERR;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting READ COMMITTED transaction `%s`\n",
+ name);
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR ("Failed to start transaction\n");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ pg->transaction_name = name;
+ return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_start_read_committed.h b/src/exchangedb/pg_start_read_committed.h
new file mode 100644
index 000000000..08b60e688
--- /dev/null
+++ b/src/exchangedb/pg_start_read_committed.h
@@ -0,0 +1,39 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_start_read_committed.h
+ * @brief implementation of the start_read_committed function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_START_READ_COMMITTED_H
+#define PG_START_READ_COMMITTED_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Start a READ COMMITTED transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging)
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_start_read_committed (void *cls,
+ const char *name);
+
+#endif
diff --git a/src/exchangedb/pg_start_read_only.c b/src/exchangedb/pg_start_read_only.c
new file mode 100644
index 000000000..741d94ba1
--- /dev/null
+++ b/src/exchangedb/pg_start_read_only.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_start_read_only.c
+ * @brief Implementation of the start_read_only function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_start_read_only.h"
+#include "pg_preflight.h"
+#include "pg_helper.h"
+
+enum GNUNET_GenericReturnValue
+TEH_PG_start_read_only (void *cls,
+ const char *name)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute (
+ "START TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ GNUNET_assert (NULL != name);
+ if (GNUNET_SYSERR ==
+ TEH_PG_preflight (pg))
+ return GNUNET_SYSERR;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting READ ONLY transaction `%s`\n",
+ name);
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR ("Failed to start transaction\n");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ pg->transaction_name = name;
+ return GNUNET_OK;
+}
diff --git a/src/exchangedb/pg_start_read_only.h b/src/exchangedb/pg_start_read_only.h
new file mode 100644
index 000000000..bf639c19b
--- /dev/null
+++ b/src/exchangedb/pg_start_read_only.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_start_read_only.h
+ * @brief implementation of the start_read_only function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_START_READ_ONLY_H
+#define PG_START_READ_ONLY_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Start a READ ONLY serializable transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging)
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_PG_start_read_only (void *cls,
+ const char *name);
+
+#endif
diff --git a/src/exchangedb/pg_store_wire_transfer_out.c b/src/exchangedb/pg_store_wire_transfer_out.c
new file mode 100644
index 000000000..337dc5855
--- /dev/null
+++ b/src/exchangedb/pg_store_wire_transfer_out.c
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_store_wire_transfer_out.c
+ * @brief Implementation of the store_wire_transfer_out function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_store_wire_transfer_out.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_store_wire_transfer_out (
+ void *cls,
+ struct GNUNET_TIME_Timestamp date,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *exchange_account_section,
+ const struct TALER_Amount *amount)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_timestamp (&date),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_string (exchange_account_section),
+ TALER_PQ_query_param_amount (pg->conn,
+ amount),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "insert_wire_out",
+ "INSERT INTO wire_out "
+ "(execution_date"
+ ",wtid_raw"
+ ",wire_target_h_payto"
+ ",exchange_account_section"
+ ",amount"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_wire_out",
+ params);
+}
diff --git a/src/exchangedb/pg_store_wire_transfer_out.h b/src/exchangedb/pg_store_wire_transfer_out.h
new file mode 100644
index 000000000..79950e65a
--- /dev/null
+++ b/src/exchangedb/pg_store_wire_transfer_out.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_store_wire_transfer_out.h
+ * @brief implementation of the store_wire_transfer_out function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_STORE_WIRE_TRANSFER_OUT_H
+#define PG_STORE_WIRE_TRANSFER_OUT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Store information about an outgoing wire transfer that was executed.
+ *
+ * @param cls closure
+ * @param date time of the wire transfer
+ * @param wtid subject of the wire transfer
+ * @param h_payto identifies the receiver account of the wire transfer
+ * @param exchange_account_section configuration section of the exchange specifying the
+ * exchange's bank account being used
+ * @param amount amount that was transmitted
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_store_wire_transfer_out (
+ void *cls,
+ struct GNUNET_TIME_Timestamp date,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *exchange_account_section,
+ const struct TALER_Amount *amount);
+
+#endif
diff --git a/src/exchangedb/pg_template.c b/src/exchangedb/pg_template.c
new file mode 100644
index 000000000..69cd45035
--- /dev/null
+++ b/src/exchangedb/pg_template.c
@@ -0,0 +1,26 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_template.c
+ * @brief Implementation of the template function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_template.h"
+#include "pg_helper.h"
diff --git a/src/exchangedb/pg_template.h b/src/exchangedb/pg_template.h
new file mode 100644
index 000000000..d858689fb
--- /dev/null
+++ b/src/exchangedb/pg_template.h
@@ -0,0 +1,29 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_template.h
+ * @brief implementation of the template function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_TEMPLATE_H
+#define PG_TEMPLATE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+#endif
diff --git a/src/exchangedb/pg_template.sh b/src/exchangedb/pg_template.sh
new file mode 100755
index 000000000..73bd7e989
--- /dev/null
+++ b/src/exchangedb/pg_template.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# This file is in the public domain.
+#
+# Instantiates pg_template for a particular function.
+
+for n in $*
+do
+ NCAPS=`echo $n | tr a-z A-Z`
+ if test ! -e pg_$n.c
+ then
+ cat pg_template.c | sed -e s/template/$n/g -e s/TEMPLATE/$NCAPS/g > pg_$n.c
+ cat pg_template.h | sed -e s/template/$n/g -e s/TEMPLATE/$NCAPS/g > pg_$n.h
+ echo " plugin->$n\n = &TEH_PG_$n;" >> tmpl.c
+ echo "#include \"pg_$n.h\"" >> tmpl.inc
+ echo " pg_$n.h pg_$n.c \\" >> tmpl.am
+ fi
+done
+
+echo "Add lines from tmpl.am to Makefile.am"
+echo "Add lines from tmpl.inc to plugin_exchangedb_postgres.c at the beginning"
+echo "Add lines from tmpl.c to plugin_exchangedb_postgres.c at the end"
diff --git a/src/exchangedb/pg_test_aml_officer.c b/src/exchangedb/pg_test_aml_officer.c
new file mode 100644
index 000000000..b00828244
--- /dev/null
+++ b/src/exchangedb/pg_test_aml_officer.c
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_test_aml_officer.c
+ * @brief Implementation of the test_aml_officer function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_test_aml_officer.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_test_aml_officer (
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (decider_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "test_aml_staff",
+ "SELECT 1 FROM aml_staff"
+ " WHERE decider_pub=$1"
+ " AND is_active;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "test_aml_staff",
+ params);
+}
diff --git a/src/exchangedb/pg_test_aml_officer.h b/src/exchangedb/pg_test_aml_officer.h
new file mode 100644
index 000000000..e034007bd
--- /dev/null
+++ b/src/exchangedb/pg_test_aml_officer.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_test_aml_officer.h
+ * @brief implementation of the test_aml_officer function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_TEST_AML_OFFICER_H
+#define PG_TEST_AML_OFFICER_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Test if the given AML staff member is active
+ * (at least read-only).
+ *
+ * @param cls closure
+ * @param decider_pub public key of the staff member
+ * @return database transaction status, if member is unknown or not active, 1 if member is active
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_test_aml_officer (
+ void *cls,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub);
+
+
+#endif
diff --git a/src/exchangedb/pg_trigger_aml_process.c b/src/exchangedb/pg_trigger_aml_process.c
new file mode 100644
index 000000000..7534fe3df
--- /dev/null
+++ b/src/exchangedb/pg_trigger_aml_process.c
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_trigger_aml_process.c
+ * @brief Implementation of the trigger_aml_process function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_trigger_aml_process.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_trigger_aml_process (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_Amount *threshold_crossed)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ TALER_PQ_query_param_amount (pg->conn,
+ threshold_crossed),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "trigger_aml_process",
+ "INSERT INTO aml_status"
+ "(h_payto"
+ ",threshold"
+ ",status)"
+ " VALUES"
+ " ($1, $2, 1)" // 1: decision needed
+ " ON CONFLICT (h_payto) DO"
+ " UPDATE SET"
+ " threshold=$2"
+ " ,status=aml_status.status | 1;"); // do not clear 'frozen' status
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "trigger_aml_process",
+ params);
+}
diff --git a/src/exchangedb/pg_trigger_aml_process.h b/src/exchangedb/pg_trigger_aml_process.h
new file mode 100644
index 000000000..2283571af
--- /dev/null
+++ b/src/exchangedb/pg_trigger_aml_process.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_trigger_aml_process.h
+ * @brief implementation of the trigger_aml_process function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_TRIGGER_AML_PROCESS_H
+#define PG_TRIGGER_AML_PROCESS_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Trigger AML process, an account has crossed the threshold. Inserts or
+ * updates the AML status.
+ *
+ * @param cls closure
+ * @param h_payto account for which the attribute data is stored
+ * @param threshold_crossed existing threshold that was crossed
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_trigger_aml_process (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_Amount *threshold_crossed);
+
+
+#endif
diff --git a/src/exchangedb/pg_update_aggregation_transient.c b/src/exchangedb/pg_update_aggregation_transient.c
new file mode 100644
index 000000000..38b65316e
--- /dev/null
+++ b/src/exchangedb/pg_update_aggregation_transient.c
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_update_aggregation_transient.c
+ * @brief Implementation of the update_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_aggregation_transient.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t kyc_requirement_row,
+ const struct TALER_Amount *total)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_amount (pg->conn,
+ total),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_auto_from_type (wtid),
+ GNUNET_PQ_query_param_uint64 (&kyc_requirement_row),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "update_aggregation_transient",
+ "UPDATE aggregation_transient"
+ " SET amount=$1"
+ " ,legitimization_requirement_serial_id=$4"
+ " WHERE wire_target_h_payto=$2"
+ " AND wtid_raw=$3");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_aggregation_transient",
+ params);
+}
diff --git a/src/exchangedb/pg_update_aggregation_transient.h b/src/exchangedb/pg_update_aggregation_transient.h
new file mode 100644
index 000000000..c444e85bb
--- /dev/null
+++ b/src/exchangedb/pg_update_aggregation_transient.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_update_aggregation_transient.h
+ * @brief implementation of the update_aggregation_transient function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_AGGREGATION_TRANSIENT_H
+#define PG_UPDATE_AGGREGATION_TRANSIENT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Update existing entry in the transient aggregation table.
+ * @a h_payto is only needed for query performance.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto destination of the wire transfer
+ * @param wtid the raw wire transfer identifier to update
+ * @param kyc_requirement_row row in legitimization_requirements that need to be satisfied to continue, or 0 for none
+ * @param total new total amount to be wired in the future
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_aggregation_transient (
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ const struct TALER_WireTransferIdentifierRawP *wtid,
+ uint64_t kyc_requirement_row,
+ const struct TALER_Amount *total);
+
+#endif
diff --git a/src/exchangedb/pg_update_auditor.c b/src/exchangedb/pg_update_auditor.c
new file mode 100644
index 000000000..167a270b9
--- /dev/null
+++ b/src/exchangedb/pg_update_auditor.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_update_auditor.c
+ * @brief Implementation of the update_auditor function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_auditor.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_auditor (void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool enabled)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (auditor_pub),
+ GNUNET_PQ_query_param_string (auditor_url),
+ GNUNET_PQ_query_param_string (auditor_name),
+ GNUNET_PQ_query_param_bool (enabled),
+ GNUNET_PQ_query_param_timestamp (&change_date),
+ GNUNET_PQ_query_param_end
+ };
+ /* used in #postgres_update_auditor() */
+ PREPARE (pg,
+ "update_auditor",
+ "UPDATE auditors"
+ " SET"
+ " auditor_url=$2"
+ " ,auditor_name=$3"
+ " ,is_active=$4"
+ " ,last_change=$5"
+ " WHERE auditor_pub=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_auditor",
+ params);
+}
diff --git a/src/exchangedb/pg_update_auditor.h b/src/exchangedb/pg_update_auditor.h
new file mode 100644
index 000000000..ee869f8b7
--- /dev/null
+++ b/src/exchangedb/pg_update_auditor.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_update_auditor.h
+ * @brief implementation of the update_auditor function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_AUDITOR_H
+#define PG_UPDATE_AUDITOR_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+/**
+ * Update information about an auditor that will audit this exchange.
+ *
+ * @param cls closure
+ * @param auditor_pub key of the auditor (primary key for the existing record)
+ * @param auditor_url base URL of the auditor's REST service, to be updated
+ * @param auditor_name name of the auditor (for humans)
+ * @param change_date date when the auditor status was last changed
+ * (only to be used for replay detection)
+ * @param enabled true to enable, false to disable
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_auditor (void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name,
+ struct GNUNET_TIME_Timestamp change_date,
+ bool enabled);
+
+#endif
diff --git a/src/exchangedb/pg_update_kyc_process_by_row.c b/src/exchangedb/pg_update_kyc_process_by_row.c
new file mode 100644
index 000000000..c339436a8
--- /dev/null
+++ b/src/exchangedb/pg_update_kyc_process_by_row.c
@@ -0,0 +1,122 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_update_kyc_process_by_row.c
+ * @brief Implementation of the update_kyc_process_by_row function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_kyc_process_by_row.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_kyc_process_by_row (
+ void *cls,
+ uint64_t process_row,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ const char *redirect_url,
+ struct GNUNET_TIME_Absolute expiration)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&process_row),
+ GNUNET_PQ_query_param_string (provider_section),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ (NULL != provider_account_id)
+ ? GNUNET_PQ_query_param_string (provider_account_id)
+ : GNUNET_PQ_query_param_null (),
+ (NULL != provider_legitimization_id)
+ ? GNUNET_PQ_query_param_string (provider_legitimization_id)
+ : GNUNET_PQ_query_param_null (),
+ (NULL != redirect_url)
+ ? GNUNET_PQ_query_param_string (redirect_url)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_absolute_time (&expiration),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Updating KYC data for %llu (%s)\n",
+ (unsigned long long) process_row,
+ provider_section);
+ PREPARE (pg,
+ "update_legitimization_process",
+ "UPDATE legitimization_processes"
+ " SET provider_user_id=$4"
+ " ,provider_legitimization_id=$5"
+ " ,redirect_url=$6"
+ " ,expiration_time=GREATEST(expiration_time,$7)"
+ " WHERE"
+ " h_payto=$3"
+ " AND legitimization_process_serial_id=$1"
+ " AND provider_section=$2;");
+ qs = GNUNET_PQ_eval_prepared_non_select (
+ pg->conn,
+ "update_legitimization_process",
+ params);
+ if (qs <= 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to update legitimization process %llu: %d\n",
+ (unsigned long long) process_row,
+ qs);
+ return qs;
+ }
+ if (GNUNET_TIME_absolute_is_future (expiration))
+ {
+ enum GNUNET_DB_QueryStatus qs2;
+ struct TALER_KycCompletedEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+ .h_payto = *h_payto
+ };
+ uint32_t trigger_type = 1;
+ struct GNUNET_PQ_QueryParam params2[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_uint32 (&trigger_type),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_PQ_event_notify (pg->conn,
+ &rep.header,
+ NULL,
+ 0);
+ PREPARE (pg,
+ "alert_kyc_status_change",
+ "INSERT INTO kyc_alerts"
+ " (h_payto"
+ " ,trigger_type)"
+ " VALUES"
+ " ($1,$2);");
+ qs2 = GNUNET_PQ_eval_prepared_non_select (
+ pg->conn,
+ "alert_kyc_status_change",
+ params2);
+ if (qs2 < 0)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to store KYC alert: %d\n",
+ qs2);
+ }
+ return qs;
+}
diff --git a/src/exchangedb/pg_update_kyc_process_by_row.h b/src/exchangedb/pg_update_kyc_process_by_row.h
new file mode 100644
index 000000000..7ef5285e9
--- /dev/null
+++ b/src/exchangedb/pg_update_kyc_process_by_row.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_update_kyc_process_by_row.h
+ * @brief implementation of the update_kyc_process_by_row function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_KYC_PROCESS_BY_ROW_H
+#define PG_UPDATE_KYC_PROCESS_BY_ROW_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Update KYC requirement check with provider-linkage and/or
+ * expiration data.
+ *
+ * @param cls closure
+ * @param process_row row to select by
+ * @param provider_section provider that must be checked (technically redundant)
+ * @param h_payto account that must be KYC'ed (helps access by shard, otherwise also redundant)
+ * @param provider_account_id provider account ID
+ * @param provider_legitimization_id provider legitimization ID
+ * @param redirect_url where the user should be redirected to start the KYC process
+ * @param expiration how long is this KYC check set to be valid (in the past if invalid)
+ * @return database transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_kyc_process_by_row (
+ void *cls,
+ uint64_t process_row,
+ const char *provider_section,
+ const struct TALER_PaytoHashP *h_payto,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ const char *redirect_url,
+ struct GNUNET_TIME_Absolute expiration);
+
+#endif
diff --git a/src/exchangedb/pg_update_wire.c b/src/exchangedb/pg_update_wire.c
new file mode 100644
index 000000000..5c4bb9045
--- /dev/null
+++ b/src/exchangedb/pg_update_wire.c
@@ -0,0 +1,81 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022, 2023, 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_update_wire.c
+ * @brief Implementation of the update_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_update_wire.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_wire (void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp change_date,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority,
+ bool enabled)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (payto_uri),
+ GNUNET_PQ_query_param_bool (enabled),
+ NULL == conversion_url
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (conversion_url),
+ enabled
+ ? TALER_PQ_query_param_json (debit_restrictions)
+ : GNUNET_PQ_query_param_null (),
+ enabled
+ ? TALER_PQ_query_param_json (credit_restrictions)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_timestamp (&change_date),
+ NULL == master_sig
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_auto_from_type (master_sig),
+ NULL == bank_label
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (bank_label),
+ GNUNET_PQ_query_param_int64 (&priority),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "update_wire",
+ "UPDATE wire_accounts"
+ " SET"
+ " is_active=$2"
+ " ,conversion_url=$3"
+ " ,debit_restrictions=$4"
+ " ,credit_restrictions=$5"
+ " ,last_change=$6"
+ " ,master_sig=$7"
+ " ,bank_label=$8"
+ " ,priority=$9"
+ " WHERE payto_uri=$1");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_wire",
+ params);
+}
diff --git a/src/exchangedb/pg_update_wire.h b/src/exchangedb/pg_update_wire.h
new file mode 100644
index 000000000..a596a0802
--- /dev/null
+++ b/src/exchangedb/pg_update_wire.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_update_wire.h
+ * @brief implementation of the update_wire function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_WIRE_H
+#define PG_UPDATE_WIRE_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+
+/**
+ * Update information about a wire account of the exchange.
+ *
+ * @param cls closure
+ * @param payto_uri account the update is about
+ * @param conversion_url URL of a conversion service, NULL if there is no conversion
+ * @param debit_restrictions JSON array with debit restrictions on the account; NULL allowed if not @a enabled
+ * @param credit_restrictions JSON array with credit restrictions on the account; NULL allowed if not @a enabled
+ * @param change_date date when the account status was last changed
+ * (only to be used for replay detection)
+ * @param master_sig master signature to store, can be NULL (if @a enabled is false)
+ * @param bank_label label to show this entry under in the UI, can be NULL
+ * @param priority determines order in which entries are shown in the UI
+ * @param enabled true to enable, false to disable (the actual change)
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_update_wire (void *cls,
+ const char *payto_uri,
+ const char *conversion_url,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ struct GNUNET_TIME_Timestamp change_date,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority,
+ bool enabled);
+
+#endif
diff --git a/src/exchangedb/pg_wire_prepare_data_get.c b/src/exchangedb/pg_wire_prepare_data_get.c
new file mode 100644
index 000000000..0cc57e41f
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_get.c
@@ -0,0 +1,140 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_get.c
+ * @brief Implementation of the wire_prepare_data_get function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_wire_prepare_data_get.h"
+#include "pg_helper.h"
+
+/**
+ * Closure for #prewire_cb().
+ */
+struct PrewireContext
+{
+ /**
+ * Function to call on each result.
+ */
+ TALER_EXCHANGEDB_WirePreparationIterator cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * #GNUNET_OK if everything went fine.
+ */
+ enum GNUNET_GenericReturnValue status;
+};
+
+
+/**
+ * Invoke the callback for each result.
+ *
+ * @param cls a `struct MissingWireContext *`
+ * @param result SQL result
+ * @param num_results number of rows in @a result
+ */
+static void
+prewire_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct PrewireContext *pc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t prewire_uuid;
+ char *wire_method;
+ void *buf = NULL;
+ size_t buf_size;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("prewire_uuid",
+ &prewire_uuid),
+ GNUNET_PQ_result_spec_string ("wire_method",
+ &wire_method),
+ GNUNET_PQ_result_spec_variable_size ("buf",
+ &buf,
+ &buf_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ pc->status = GNUNET_SYSERR;
+ return;
+ }
+ pc->cb (pc->cb_cls,
+ prewire_uuid,
+ wire_method,
+ buf,
+ buf_size);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_get (void *cls,
+ uint64_t start_row,
+ uint64_t limit,
+ TALER_EXCHANGEDB_WirePreparationIterator cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&start_row),
+ GNUNET_PQ_query_param_uint64 (&limit),
+ GNUNET_PQ_query_param_end
+ };
+ struct PrewireContext pc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .status = GNUNET_OK
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ PREPARE (pg,
+ "wire_prepare_data_get",
+ "SELECT"
+ " prewire_uuid"
+ ",wire_method"
+ ",buf"
+ " FROM prewire"
+ " WHERE prewire_uuid >= $1"
+ " AND finished=FALSE"
+ " AND failed=FALSE"
+ " ORDER BY prewire_uuid ASC"
+ " LIMIT $2;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "wire_prepare_data_get",
+ params,
+ &prewire_cb,
+ &pc);
+ if (GNUNET_OK != pc.status)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/exchangedb/pg_wire_prepare_data_get.h b/src/exchangedb/pg_wire_prepare_data_get.h
new file mode 100644
index 000000000..815c14bdf
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_get.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_get.h
+ * @brief implementation of the wire_prepare_data_get function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_WIRE_PREPARE_DATA_GET_H
+#define PG_WIRE_PREPARE_DATA_GET_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to get an unfinished wire transfer
+ * preparation data. Fetches at most one item.
+ *
+ * @param cls closure
+ * @param start_row offset to query table at
+ * @param limit maximum number of results to return
+ * @param cb function to call for ONE unfinished item
+ * @param cb_cls closure for @a cb
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_get (void *cls,
+ uint64_t start_row,
+ uint64_t limit,
+ TALER_EXCHANGEDB_WirePreparationIterator cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/exchangedb/pg_wire_prepare_data_insert.c b/src/exchangedb/pg_wire_prepare_data_insert.c
new file mode 100644
index 000000000..919fccdaf
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_insert.c
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_insert.c
+ * @brief Implementation of the wire_prepare_data_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_wire_prepare_data_insert.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_insert (void *cls,
+ const char *type,
+ const char *buf,
+ size_t buf_size)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (type),
+ GNUNET_PQ_query_param_fixed_size (buf, buf_size),
+ GNUNET_PQ_query_param_end
+ };
+
+
+ /* Used in #postgres_wire_prepare_data_insert() to store
+ wire transfer information before actually committing it with the bank */
+ PREPARE (pg,
+ "wire_prepare_data_insert",
+ "INSERT INTO prewire "
+ "(wire_method"
+ ",buf"
+ ") VALUES "
+ "($1, $2);");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "wire_prepare_data_insert",
+ params);
+}
diff --git a/src/exchangedb/pg_wire_prepare_data_insert.h b/src/exchangedb/pg_wire_prepare_data_insert.h
new file mode 100644
index 000000000..e73ee152d
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_insert.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_insert.h
+ * @brief implementation of the wire_prepare_data_insert function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_WIRE_PREPARE_DATA_INSERT_H
+#define PG_WIRE_PREPARE_DATA_INSERT_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to insert wire transfer commit data into the DB.
+ *
+ * @param cls closure
+ * @param type type of the wire transfer (i.e. "iban")
+ * @param buf buffer with wire transfer preparation data
+ * @param buf_size number of bytes in @a buf
+ * @return query status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_insert (void *cls,
+ const char *type,
+ const char *buf,
+ size_t buf_size);
+
+#endif
diff --git a/src/exchangedb/pg_wire_prepare_data_mark_failed.c b/src/exchangedb/pg_wire_prepare_data_mark_failed.c
new file mode 100644
index 000000000..1d46c84d4
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_mark_failed.c
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_mark_failed.c
+ * @brief Implementation of the wire_prepare_data_mark_failed function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_wire_prepare_data_mark_failed.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_mark_failed (
+ void *cls,
+ uint64_t rowid)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&rowid),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "wire_prepare_data_mark_failed",
+ "UPDATE prewire"
+ " SET failed=TRUE"
+ " WHERE prewire_uuid=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "wire_prepare_data_mark_failed",
+ params);
+}
diff --git a/src/exchangedb/pg_wire_prepare_data_mark_failed.h b/src/exchangedb/pg_wire_prepare_data_mark_failed.h
new file mode 100644
index 000000000..98846b284
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_mark_failed.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_mark_failed.h
+ * @brief implementation of the wire_prepare_data_mark_failed function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_WIRE_PREPARE_DATA_MARK_FAILED_H
+#define PG_WIRE_PREPARE_DATA_MARK_FAILED_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to mark wire transfer commit data as failed.
+ *
+ * @param cls closure
+ * @param rowid which entry to mark as failed
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_mark_failed (
+ void *cls,
+ uint64_t rowid);
+
+#endif
diff --git a/src/exchangedb/pg_wire_prepare_data_mark_finished.c b/src/exchangedb/pg_wire_prepare_data_mark_finished.c
new file mode 100644
index 000000000..998b9d731
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_mark_finished.c
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_mark_finished.c
+ * @brief Implementation of the wire_prepare_data_mark_finished function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_wire_prepare_data_mark_finished.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_mark_finished (
+ void *cls,
+ uint64_t rowid)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&rowid),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "wire_prepare_data_mark_done",
+ "UPDATE prewire"
+ " SET finished=TRUE"
+ " WHERE prewire_uuid=$1;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "wire_prepare_data_mark_done",
+ params);
+}
diff --git a/src/exchangedb/pg_wire_prepare_data_mark_finished.h b/src/exchangedb/pg_wire_prepare_data_mark_finished.h
new file mode 100644
index 000000000..ba2e384cd
--- /dev/null
+++ b/src/exchangedb/pg_wire_prepare_data_mark_finished.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file exchangedb/pg_wire_prepare_data_mark_finished.h
+ * @brief implementation of the wire_prepare_data_mark_finished function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_WIRE_PREPARE_DATA_MARK_FINISHED_H
+#define PG_WIRE_PREPARE_DATA_MARK_FINISHED_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Function called to mark wire transfer commit data as finished.
+ *
+ * @param cls closure
+ * @param rowid which entry to mark as finished
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TEH_PG_wire_prepare_data_mark_finished (
+ void *cls,
+ uint64_t rowid);
+
+#endif
diff --git a/src/exchangedb/plugin_exchangedb_common.c b/src/exchangedb/plugin_exchangedb_common.c
index fb697dae8..562710eaa 100644
--- a/src/exchangedb/plugin_exchangedb_common.c
+++ b/src/exchangedb/plugin_exchangedb_common.c
@@ -19,16 +19,14 @@
* included in each plugin.
* @author Christian Grothoff
*/
+#include "platform.h"
+#include "plugin_exchangedb_common.h"
-/**
- * Free memory associated with the given reserve history.
- *
- * @param cls the @e cls of this struct with the plugin-specific state (unused)
- * @param rh history to free.
- */
-static void
-common_free_reserve_history (void *cls,
- struct TALER_EXCHANGEDB_ReserveHistory *rh)
+
+void
+TEH_COMMON_free_reserve_history (
+ void *cls,
+ struct TALER_EXCHANGEDB_ReserveHistory *rh)
{
(void) cls;
while (NULL != rh)
@@ -49,7 +47,7 @@ common_free_reserve_history (void *cls,
struct TALER_EXCHANGEDB_CollectableBlindcoin *cbc;
cbc = rh->details.withdraw;
- GNUNET_CRYPTO_rsa_signature_free (cbc->sig.rsa_signature);
+ TALER_blinded_denom_sig_free (&cbc->sig);
GNUNET_free (cbc);
break;
}
@@ -58,7 +56,7 @@ common_free_reserve_history (void *cls,
struct TALER_EXCHANGEDB_Recoup *recoup;
recoup = rh->details.recoup;
- GNUNET_CRYPTO_rsa_signature_free (recoup->coin.denom_sig.rsa_signature);
+ TALER_denom_sig_free (&recoup->coin.denom_sig);
GNUNET_free (recoup);
break;
}
@@ -71,6 +69,38 @@ common_free_reserve_history (void *cls,
GNUNET_free (closing);
break;
}
+ case TALER_EXCHANGEDB_RO_PURSE_MERGE:
+ {
+ struct TALER_EXCHANGEDB_PurseMerge *merge;
+
+ merge = rh->details.merge;
+ GNUNET_free (merge);
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
+ {
+ struct TALER_EXCHANGEDB_HistoryRequest *history;
+
+ history = rh->details.history;
+ GNUNET_free (history);
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
+ {
+ struct TALER_EXCHANGEDB_OpenRequest *or;
+
+ or = rh->details.open_request;
+ GNUNET_free (or);
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
+ {
+ struct TALER_EXCHANGEDB_CloseRequest *cr;
+
+ cr = rh->details.close_request;
+ GNUNET_free (cr);
+ break;
+ }
}
{
struct TALER_EXCHANGEDB_ReserveHistory *next;
@@ -83,15 +113,10 @@ common_free_reserve_history (void *cls,
}
-/**
- * Free linked list of transactions.
- *
- * @param cls the @e cls of this struct with the plugin-specific state (unused)
- * @param tl list to free
- */
-static void
-common_free_coin_transaction_list (void *cls,
- struct TALER_EXCHANGEDB_TransactionList *tl)
+void
+TEH_COMMON_free_coin_transaction_list (
+ void *cls,
+ struct TALER_EXCHANGEDB_TransactionList *tl)
{
(void) cls;
while (NULL != tl)
@@ -103,8 +128,7 @@ common_free_coin_transaction_list (void *cls,
struct TALER_EXCHANGEDB_DepositListEntry *deposit;
deposit = tl->details.deposit;
- if (NULL != deposit->receiver_wire_account)
- json_decref (deposit->receiver_wire_account);
+ GNUNET_free (deposit->receiver_wire_account);
GNUNET_free (deposit);
break;
}
@@ -116,8 +140,7 @@ common_free_coin_transaction_list (void *cls,
struct TALER_EXCHANGEDB_RecoupRefreshListEntry *rr;
rr = tl->details.old_coin_recoup;
- if (NULL != rr->coin.denom_sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (rr->coin.denom_sig.rsa_signature);
+ TALER_denom_sig_free (&rr->coin.denom_sig);
GNUNET_free (rr);
break;
}
@@ -132,11 +155,35 @@ common_free_coin_transaction_list (void *cls,
struct TALER_EXCHANGEDB_RecoupRefreshListEntry *rr;
rr = tl->details.recoup_refresh;
- if (NULL != rr->coin.denom_sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (rr->coin.denom_sig.rsa_signature);
+ TALER_denom_sig_free (&rr->coin.denom_sig);
GNUNET_free (rr);
break;
}
+ case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
+ {
+ struct TALER_EXCHANGEDB_PurseDepositListEntry *deposit;
+
+ deposit = tl->details.purse_deposit;
+ GNUNET_free (deposit->exchange_base_url);
+ GNUNET_free (deposit);
+ break;
+ }
+ case TALER_EXCHANGEDB_TT_PURSE_REFUND:
+ {
+ struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund;
+
+ prefund = tl->details.purse_refund;
+ GNUNET_free (prefund);
+ break;
+ }
+ case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
+ {
+ struct TALER_EXCHANGEDB_ReserveOpenListEntry *role;
+
+ role = tl->details.reserve_open;
+ GNUNET_free (role);
+ break;
+ }
}
{
struct TALER_EXCHANGEDB_TransactionList *next;
diff --git a/src/exchangedb/plugin_exchangedb_common.h b/src/exchangedb/plugin_exchangedb_common.h
new file mode 100644
index 000000000..0355c44ab
--- /dev/null
+++ b/src/exchangedb/plugin_exchangedb_common.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file plugin_exchangedb_common.h
+ * @brief implementation of database-independent functions
+ * @author Christian Grothoff
+ */
+#ifndef PLUGIN_EXCHANGEDB_COMMON_H
+#define PLUGIN_EXCHANGEDB_COMMON_H
+
+#include "taler_util.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+
+/**
+ * Free memory associated with the given reserve history.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state (unused)
+ * @param[in] rh history to free.
+ */
+void
+TEH_COMMON_free_reserve_history (
+ void *cls,
+ struct TALER_EXCHANGEDB_ReserveHistory *rh);
+
+
+/**
+ * Free linked list of transactions.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state (unused)
+ * @param[in] tl list to free
+ */
+void
+TEH_COMMON_free_coin_transaction_list (
+ void *cls,
+ struct TALER_EXCHANGEDB_TransactionList *tl);
+
+#endif
diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c
index 4b3ae19d7..108b55219 100644
--- a/src/exchangedb/plugin_exchangedb_postgres.c
+++ b/src/exchangedb/plugin_exchangedb_postgres.c
@@ -1,18 +1,18 @@
/*
- This file is part of TALER
- Copyright (C) 2014--2021 Taler Systems SA
+ This file is part of TALER
+ Copyright (C) 2014--2023 Taler Systems SA
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
/**
* @file plugin_exchangedb_postgres.c
@@ -21,53 +21,213 @@
* @author Christian Grothoff
* @author Sree Harsha Totakura
* @author Marcello Stanisci
+ * @author Özgür Kesim
*/
#include "platform.h"
+#include <poll.h>
+#include <pthread.h>
+#include <libpq-fe.h>
#include "taler_error_codes.h"
#include "taler_dbevents.h"
#include "taler_pq_lib.h"
+#include "taler_util.h"
#include "taler_json_lib.h"
#include "taler_exchangedb_plugin.h"
-#include <poll.h>
-#include <pthread.h>
-#include <sys/eventfd.h>
-#include <libpq-fe.h>
-
-#include "plugin_exchangedb_common.c"
+#include "plugin_exchangedb_common.h"
+#include "pg_delete_aggregation_transient.h"
+#include "pg_get_link_data.h"
+#include "pg_helper.h"
+#include "pg_do_reserve_open.h"
+#include "pg_get_coin_transactions.h"
+#include "pg_get_expired_reserves.h"
+#include "pg_get_purse_request.h"
+#include "pg_get_reserve_history.h"
+#include "pg_get_unfinished_close_requests.h"
+#include "pg_insert_close_request.h"
+#include "pg_insert_records_by_table.h"
+#include "pg_insert_reserve_open_deposit.h"
+#include "pg_get_pending_kyc_requirement_process.h"
+#include "pg_iterate_kyc_reference.h"
+#include "pg_iterate_reserve_close_info.h"
+#include "pg_lookup_records_by_table.h"
+#include "pg_lookup_serial_by_table.h"
+#include "pg_select_account_merges_above_serial_id.h"
+#include "pg_select_aml_threshold.h"
+#include "pg_select_all_purse_decisions_above_serial_id.h"
+#include "pg_select_purse.h"
+#include "pg_select_purse_deposits_above_serial_id.h"
+#include "pg_select_purse_merges_above_serial_id.h"
+#include "pg_select_purse_requests_above_serial_id.h"
+#include "pg_select_reserve_close_info.h"
+#include "pg_select_reserve_closed_above_serial_id.h"
+#include "pg_select_reserve_open_above_serial_id.h"
+#include "pg_insert_purse_request.h"
+#include "pg_iterate_active_signkeys.h"
+#include "pg_preflight.h"
+#include "pg_commit.h"
+#include "pg_drop_tables.h"
+#include "pg_select_satisfied_kyc_processes.h"
+#include "pg_select_aggregation_amounts_for_kyc_check.h"
+#include "pg_kyc_provider_account_lookup.h"
+#include "pg_lookup_kyc_requirement_by_row.h"
+#include "pg_insert_kyc_requirement_for_account.h"
+#include "pg_lookup_kyc_process_by_account.h"
+#include "pg_update_kyc_process_by_row.h"
+#include "pg_insert_kyc_requirement_process.h"
+#include "pg_select_withdraw_amounts_for_kyc_check.h"
+#include "pg_select_merge_amounts_for_kyc_check.h"
+#include "pg_profit_drains_set_finished.h"
+#include "pg_profit_drains_get_pending.h"
+#include "pg_get_drain_profit.h"
+#include "pg_get_purse_deposit.h"
+#include "pg_insert_contract.h"
+#include "pg_insert_kyc_failure.h"
+#include "pg_select_contract.h"
+#include "pg_select_purse_merge.h"
+#include "pg_select_contract_by_purse.h"
+#include "pg_insert_drain_profit.h"
+#include "pg_do_reserve_purse.h"
+#include "pg_lookup_global_fee_by_time.h"
+#include "pg_do_purse_deposit.h"
+#include "pg_activate_signing_key.h"
+#include "pg_update_auditor.h"
+#include "pg_begin_revolving_shard.h"
+#include "pg_get_extension_manifest.h"
+#include "pg_do_purse_delete.h"
+#include "pg_do_purse_merge.h"
+#include "pg_start_read_committed.h"
+#include "pg_start_read_only.h"
+#include "pg_insert_denomination_info.h"
+#include "pg_do_batch_withdraw_insert.h"
+#include "pg_lookup_wire_fee_by_time.h"
+#include "pg_start.h"
+#include "pg_rollback.h"
+#include "pg_create_tables.h"
+#include "pg_event_listen.h"
+#include "pg_event_listen_cancel.h"
+#include "pg_event_notify.h"
+#include "pg_get_denomination_info.h"
+#include "pg_iterate_denomination_info.h"
+#include "pg_iterate_denominations.h"
+#include "pg_iterate_active_auditors.h"
+#include "pg_iterate_auditor_denominations.h"
+#include "pg_reserves_get.h"
+#include "pg_reserves_get_origin.h"
+#include "pg_drain_kyc_alert.h"
+#include "pg_reserves_in_insert.h"
+#include "pg_get_withdraw_info.h"
+#include "pg_get_age_withdraw.h"
+#include "pg_do_batch_withdraw.h"
+#include "pg_do_age_withdraw.h"
+#include "pg_get_policy_details.h"
+#include "pg_persist_policy_details.h"
+#include "pg_do_deposit.h"
+#include "pg_get_wire_hash_for_contract.h"
+#include "pg_add_policy_fulfillment_proof.h"
+#include "pg_do_melt.h"
+#include "pg_do_refund.h"
+#include "pg_do_recoup.h"
+#include "pg_do_recoup_refresh.h"
+#include "pg_get_reserve_balance.h"
+#include "pg_count_known_coins.h"
+#include "pg_ensure_coin_known.h"
+#include "pg_get_known_coin.h"
+#include "pg_get_signature_for_known_coin.h"
+#include "pg_get_coin_denomination.h"
+#include "pg_have_deposit2.h"
+#include "pg_aggregate.h"
+#include "pg_create_aggregation_transient.h"
+#include "pg_select_aggregation_transient.h"
+#include "pg_find_aggregation_transient.h"
+#include "pg_update_aggregation_transient.h"
+#include "pg_get_ready_deposit.h"
+#include "pg_insert_refund.h"
+#include "pg_select_refunds_by_coin.h"
+#include "pg_get_melt.h"
+#include "pg_insert_refresh_reveal.h"
+#include "pg_get_refresh_reveal.h"
+#include "pg_lookup_wire_transfer.h"
+#include "pg_lookup_transfer_by_deposit.h"
+#include "pg_insert_wire_fee.h"
+#include "pg_insert_global_fee.h"
+#include "pg_get_wire_fee.h"
+#include "pg_get_global_fee.h"
+#include "pg_get_global_fees.h"
+#include "pg_insert_reserve_closed.h"
+#include "pg_wire_prepare_data_insert.h"
+#include "pg_wire_prepare_data_mark_finished.h"
+#include "pg_wire_prepare_data_mark_failed.h"
+#include "pg_wire_prepare_data_get.h"
+#include "pg_start_deferred_wire_out.h"
+#include "pg_store_wire_transfer_out.h"
+#include "pg_gc.h"
+#include "pg_inject_auditor_triggers.h"
+#include "pg_select_coin_deposits_above_serial_id.h"
+#include "pg_select_purse_decisions_above_serial_id.h"
+#include "pg_select_purse_deposits_by_purse.h"
+#include "pg_select_refreshes_above_serial_id.h"
+#include "pg_select_refunds_above_serial_id.h"
+#include "pg_select_reserves_in_above_serial_id.h"
+#include "pg_select_reserves_in_above_serial_id_by_account.h"
+#include "pg_select_withdrawals_above_serial_id.h"
+#include "pg_select_wire_out_above_serial_id.h"
+#include "pg_select_wire_out_above_serial_id_by_account.h"
+#include "pg_select_recoup_above_serial_id.h"
+#include "pg_select_recoup_refresh_above_serial_id.h"
+#include "pg_get_reserve_by_h_blind.h"
+#include "pg_get_old_coin_by_h_blind.h"
+#include "pg_insert_denomination_revocation.h"
+#include "pg_get_denomination_revocation.h"
+#include "pg_select_batch_deposits_missing_wire.h"
+#include "pg_select_justification_for_missing_wire.h"
+#include "pg_select_aggregations_above_serial.h"
+#include "pg_lookup_auditor_timestamp.h"
+#include "pg_lookup_auditor_status.h"
+#include "pg_insert_auditor.h"
+#include "pg_lookup_wire_timestamp.h"
+#include "pg_insert_wire.h"
+#include "pg_update_wire.h"
+#include "pg_get_wire_accounts.h"
+#include "pg_get_wire_fees.h"
+#include "pg_insert_signkey_revocation.h"
+#include "pg_lookup_signkey_revocation.h"
+#include "pg_lookup_denomination_key.h"
+#include "pg_insert_auditor_denom_sig.h"
+#include "pg_select_auditor_denom_sig.h"
+#include "pg_add_denomination_key.h"
+#include "pg_lookup_signing_key.h"
+#include "pg_begin_shard.h"
+#include "pg_abort_shard.h"
+#include "pg_complete_shard.h"
+#include "pg_release_revolving_shard.h"
+#include "pg_delete_shard_locks.h"
+#include "pg_set_extension_manifest.h"
+#include "pg_insert_partner.h"
+#include "pg_expire_purse.h"
+#include "pg_select_purse_by_merge_pub.h"
+#include "pg_set_purse_balance.h"
+#include "pg_reserves_update.h"
+#include "pg_setup_wire_target.h"
+#include "pg_compute_shard.h"
+#include "pg_insert_kyc_attributes.h"
+#include "pg_select_similar_kyc_attributes.h"
+#include "pg_select_kyc_attributes.h"
+#include "pg_insert_aml_officer.h"
+#include "pg_test_aml_officer.h"
+#include "pg_lookup_aml_officer.h"
+#include "pg_trigger_aml_process.h"
+#include "pg_select_aml_process.h"
+#include "pg_select_aml_history.h"
+#include "pg_insert_aml_decision.h"
+#include "pg_batch_ensure_coin_known.h"
/**
* Set to 1 to enable Postgres auto_explain module. This will
* slow down things a _lot_, but also provide extensive logging
* in the Postgres database logger for performance analysis.
*/
-#define AUTO_EXPLAIN 1
-
-/**
- * Should we explicitly lock certain individual tables prior to SELECT+INSERT
- * combis?
- */
-#define EXPLICIT_LOCKS 0
-
-/**
- * Wrapper macro to add the currency from the plugin's state
- * when fetching amounts from the database.
- *
- * @param field name of the database field to fetch amount from
- * @param[out] amountp pointer to amount to set
- */
-#define TALER_PQ_RESULT_SPEC_AMOUNT(field,amountp) TALER_PQ_result_spec_amount ( \
- field,pg->currency,amountp)
+#define AUTO_EXPLAIN 0
-/**
- * Wrapper macro to add the currency from the plugin's state
- * when fetching amounts from the database. NBO variant.
- *
- * @param field name of the database field to fetch amount from
- * @param[out] amountp pointer to amount to set
- */
-#define TALER_PQ_RESULT_SPEC_AMOUNT_NBO(field, \
- amountp) TALER_PQ_result_spec_amount_nbo ( \
- field,pg->currency,amountp)
/**
* Log a really unexpected PQ error with all the details we can get hold of.
@@ -88,2485 +248,13 @@
/**
- * Type of the "cls" argument given to each of the functions in
- * our API.
- */
-struct PostgresClosure
-{
-
- /**
- * Our configuration.
- */
- const struct GNUNET_CONFIGURATION_Handle *cfg;
-
- /**
- * Directory with SQL statements to run to create tables.
- */
- char *sql_dir;
-
- /**
- * After how long should idle reserves be closed?
- */
- struct GNUNET_TIME_Relative idle_reserve_expiration_time;
-
- /**
- * After how long should reserves that have seen withdraw operations
- * be garbage collected?
- */
- struct GNUNET_TIME_Relative legal_reserve_expiration_time;
-
- /**
- * Which currency should we assume all amounts to be in?
- */
- char *currency;
-
- /**
- * Postgres connection handle.
- */
- struct GNUNET_PQ_Context *conn;
-
- /**
- * Name of the current transaction, for debugging.
- */
- const char *transaction_name;
-
- /**
- * Did we initialize the prepared statements
- * for this session?
- */
- bool init;
-
-};
-
-
-/**
- * Drop all Taler tables. This should only be used by testcases.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
- */
-static enum GNUNET_GenericReturnValue
-postgres_drop_tables (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_Context *conn;
-
- conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
- "exchangedb-postgres",
- "drop",
- NULL,
- NULL);
- if (NULL == conn)
- return GNUNET_SYSERR;
- GNUNET_PQ_disconnect (conn);
- return GNUNET_OK;
-}
-
-
-/**
- * Create the necessary tables if they are not present
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
- */
-static enum GNUNET_GenericReturnValue
-postgres_create_tables (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_Context *conn;
-
- conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
- "exchangedb-postgres",
- "exchange-",
- NULL,
- NULL);
- if (NULL == conn)
- return GNUNET_SYSERR;
- GNUNET_PQ_disconnect (conn);
- return GNUNET_OK;
-}
-
-
-/**
- * Initialize prepared statements for @a pg.
- *
- * @param[in,out] pg connection to initialize
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-prepare_statements (struct PostgresClosure *pg)
-{
- enum GNUNET_GenericReturnValue ret;
- struct GNUNET_PQ_PreparedStatement ps[] = {
- /* Used in #postgres_insert_denomination_info() and
- #postgres_add_denomination_key() */
- GNUNET_PQ_make_prepare ("denomination_insert",
- "INSERT INTO denominations "
- "(denom_pub_hash"
- ",denom_pub"
- ",master_sig"
- ",valid_from"
- ",expire_withdraw"
- ",expire_deposit"
- ",expire_legal"
- ",coin_val" /* value of this denom */
- ",coin_frac" /* fractional value of this denom */
- ",fee_withdraw_val"
- ",fee_withdraw_frac"
- ",fee_deposit_val"
- ",fee_deposit_frac"
- ",fee_refresh_val"
- ",fee_refresh_frac"
- ",fee_refund_val"
- ",fee_refund_frac"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
- " $11, $12, $13, $14, $15, $16, $17);",
- 17),
- /* Used in #postgres_iterate_denomination_info() */
- GNUNET_PQ_make_prepare ("denomination_iterate",
- "SELECT"
- " master_sig"
- ",valid_from"
- ",expire_withdraw"
- ",expire_deposit"
- ",expire_legal"
- ",coin_val" /* value of this denom */
- ",coin_frac" /* fractional value of this denom */
- ",fee_withdraw_val"
- ",fee_withdraw_frac"
- ",fee_deposit_val"
- ",fee_deposit_frac"
- ",fee_refresh_val"
- ",fee_refresh_frac"
- ",fee_refund_val"
- ",fee_refund_frac"
- ",denom_pub"
- " FROM denominations;",
- 0),
- /* Used in #postgres_iterate_denominations() */
- GNUNET_PQ_make_prepare ("select_denominations",
- "SELECT"
- " denominations.master_sig"
- ",denom_revocations_serial_id IS NOT NULL AS revoked"
- ",valid_from"
- ",expire_withdraw"
- ",expire_deposit"
- ",expire_legal"
- ",coin_val" /* value of this denom */
- ",coin_frac" /* fractional value of this denom */
- ",fee_withdraw_val"
- ",fee_withdraw_frac"
- ",fee_deposit_val"
- ",fee_deposit_frac"
- ",fee_refresh_val"
- ",fee_refresh_frac"
- ",fee_refund_val"
- ",fee_refund_frac"
- ",denom_pub"
- " FROM denominations"
- " LEFT JOIN "
- " denomination_revocations USING (denominations_serial);",
- 0),
- /* Used in #postgres_iterate_active_signkeys() */
- GNUNET_PQ_make_prepare ("select_signkeys",
- "SELECT"
- " master_sig"
- ",exchange_pub"
- ",valid_from"
- ",expire_sign"
- ",expire_legal"
- " FROM exchange_sign_keys esk"
- " WHERE"
- " expire_sign > $1"
- " AND NOT EXISTS "
- " (SELECT esk_serial "
- " FROM signkey_revocations skr"
- " WHERE esk.esk_serial = skr.esk_serial);",
- 1),
- /* Used in #postgres_iterate_auditor_denominations() */
- GNUNET_PQ_make_prepare ("select_auditor_denoms",
- "SELECT"
- " auditors.auditor_pub"
- ",denominations.denom_pub_hash"
- ",auditor_denom_sigs.auditor_sig"
- " FROM auditor_denom_sigs"
- " JOIN auditors USING (auditor_uuid)"
- " JOIN denominations USING (denominations_serial)"
- " WHERE auditors.is_active;",
- 0),
- /* Used in #postgres_iterate_active_auditors() */
- GNUNET_PQ_make_prepare ("select_auditors",
- "SELECT"
- " auditor_pub"
- ",auditor_url"
- ",auditor_name"
- " FROM auditors"
- " WHERE"
- " is_active;",
- 0),
- /* Used in #postgres_get_denomination_info() */
- GNUNET_PQ_make_prepare ("denomination_get",
- "SELECT"
- " master_sig"
- ",valid_from"
- ",expire_withdraw"
- ",expire_deposit"
- ",expire_legal"
- ",coin_val" /* value of this denom */
- ",coin_frac" /* fractional value of this denom */
- ",fee_withdraw_val"
- ",fee_withdraw_frac"
- ",fee_deposit_val"
- ",fee_deposit_frac"
- ",fee_refresh_val"
- ",fee_refresh_frac"
- ",fee_refund_val"
- ",fee_refund_frac"
- " FROM denominations"
- " WHERE denom_pub_hash=$1;",
- 1),
- /* Used in #postgres_insert_denomination_revocation() */
- GNUNET_PQ_make_prepare ("denomination_revocation_insert",
- "INSERT INTO denomination_revocations "
- "(denominations_serial"
- ",master_sig"
- ") SELECT denominations_serial,$2"
- " FROM denominations"
- " WHERE denom_pub_hash=$1;",
- 2),
- /* Used in #postgres_get_denomination_revocation() */
- GNUNET_PQ_make_prepare ("denomination_revocation_get",
- "SELECT"
- " master_sig"
- ",denom_revocations_serial_id"
- " FROM denomination_revocations"
- " WHERE denominations_serial="
- " (SELECT denominations_serial"
- " FROM denominations"
- " WHERE denom_pub_hash=$1);",
- 1),
- /* Used in #postgres_reserves_get() */
- GNUNET_PQ_make_prepare ("reserves_get_with_kyc",
- "SELECT"
- " current_balance_val"
- ",current_balance_frac"
- ",expiration_date"
- ",gc_date"
- ",FALSE AS kyc_ok" // FIXME
- ",CAST (0 AS INT8) AS payment_target_uuid" // FIXME
- " FROM reserves"
- " WHERE reserve_pub=$1"
- " LIMIT 1;",
- 1),
-#if FIXME_DD23
- /* Used in #postgres_set_kyc_ok() */
- GNUNET_PQ_make_prepare ("set_kyc_ok",
- "UPDATE wire_targets"
- " SET kyc_ok=TRUE"
- " WHERE wire_target_serial_id=$1",
- 1),
- /* Used in #postgres_get_kyc_status() */
- GNUNET_PQ_make_prepare ("get_kyc_status",
- "SELECT"
- ",kyc_ok"
- ",wire_target_serial_id AS payment_target_uuid"
- " FROM wire_targets"
- " WHERE payto_uri=$1"
- " LIMIT 1;",
- 1),
- /* Used in #postgres_select_kyc_status() */
- GNUNET_PQ_make_prepare ("select_kyc_status",
- "SELECT"
- ",kyc_ok"
- ",h_payto"
- " FROM wire_targets"
- " WHERE"
- " wire_target_serial_id=$1",
- 1),
- /* Used in #postgres_inselect_wallet_kyc_status() */
- // FIXME: Note that this statement has not been debugged at all...
- // It just represents the _idea_.
- GNUNET_PQ_make_prepare ("inselect_wallet_kyc_status",
- "INSERT INTO wire_targets"
- "(payto_uri"
- ") VALUES "
- "($1)"
- " ON CONFLICT (wire_target_serial_id) DO "
- " (SELECT "
- " kyc_ok"
- " ,wire_target_serial_id"
- " )"
- " RETURNING "
- " FALSE AS kyc_ok"
- " wire_target_serial_id;",
- 1),
-#endif
- /* Used in #reserves_get() */
- GNUNET_PQ_make_prepare ("reserves_get",
- "SELECT"
- " current_balance_val"
- ",current_balance_frac"
- ",expiration_date"
- ",gc_date"
- " FROM reserves"
- " WHERE reserve_pub=$1"
- " LIMIT 1;",
- 1),
- GNUNET_PQ_make_prepare ("reserve_create",
- "INSERT INTO reserves "
- "(reserve_pub"
- ",account_details"
- ",current_balance_val"
- ",current_balance_frac"
- ",expiration_date"
- ",gc_date"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6)"
- " ON CONFLICT DO NOTHING"
- " RETURNING reserve_uuid;",
- 6),
- /* Used in #postgres_insert_reserve_closed() */
- GNUNET_PQ_make_prepare ("reserves_close_insert",
- "INSERT INTO reserves_close "
- "(reserve_uuid"
- ",execution_date"
- ",wtid"
- ",receiver_account"
- ",amount_val"
- ",amount_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ") SELECT reserve_uuid, $2, $3, $4, $5, $6, $7, $8"
- " FROM reserves"
- " WHERE reserve_pub=$1;",
- 8),
- /* Used in #reserves_update() when the reserve is updated */
- GNUNET_PQ_make_prepare ("reserve_update",
- "UPDATE reserves"
- " SET"
- " expiration_date=$1"
- ",gc_date=$2"
- ",current_balance_val=$3"
- ",current_balance_frac=$4"
- " WHERE reserve_pub=$5;",
- 5),
- /* Used in #postgres_reserves_in_insert() to store transaction details */
- GNUNET_PQ_make_prepare ("reserves_in_add_transaction",
- "INSERT INTO reserves_in "
- "(reserve_uuid"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",exchange_account_section"
- ",sender_account_details"
- ",execution_date"
- ") SELECT reserve_uuid, $2, $3, $4, $5, $6, $7"
- " FROM reserves"
- " WHERE reserve_pub=$1"
- " ON CONFLICT DO NOTHING;",
- 7),
- /* Used in #postgres_reserves_in_insert() to store transaction details */
- GNUNET_PQ_make_prepare ("reserves_in_add_by_uuid",
- "INSERT INTO reserves_in "
- "(reserve_uuid"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",exchange_account_section"
- ",sender_account_details"
- ",execution_date"
- ") VALUES ($1, $2, $3, $4, $5, $6, $7)"
- " ON CONFLICT DO NOTHING;",
- 7),
- /* Used in postgres_select_reserves_in_above_serial_id() to obtain inbound
- transactions for reserves with serial id '\geq' the given parameter */
- GNUNET_PQ_make_prepare ("reserves_in_get_latest_wire_reference",
- "SELECT"
- " wire_reference"
- " FROM reserves_in"
- " WHERE exchange_account_section=$1"
- " ORDER BY reserve_in_serial_id DESC"
- " LIMIT 1;",
- 1),
- /* Used in postgres_select_reserves_in_above_serial_id() to obtain inbound
- transactions for reserves with serial id '\geq' the given parameter */
- GNUNET_PQ_make_prepare ("audit_reserves_in_get_transactions_incr",
- "SELECT"
- " reserves.reserve_pub"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",execution_date"
- ",sender_account_details"
- ",reserve_in_serial_id"
- " FROM reserves_in"
- " JOIN reserves"
- " USING (reserve_uuid)"
- " WHERE reserve_in_serial_id>=$1"
- " ORDER BY reserve_in_serial_id;",
- 1),
- /* Used in postgres_select_reserves_in_above_serial_id() to obtain inbound
- transactions for reserves with serial id '\geq' the given parameter */
- GNUNET_PQ_make_prepare (
- "audit_reserves_in_get_transactions_incr_by_account",
- "SELECT"
- " reserves.reserve_pub"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",execution_date"
- ",sender_account_details"
- ",reserve_in_serial_id"
- " FROM reserves_in"
- " JOIN reserves "
- " USING (reserve_uuid)"
- " WHERE reserve_in_serial_id>=$1 AND exchange_account_section=$2"
- " ORDER BY reserve_in_serial_id;",
- 2),
- /* Used in #postgres_get_reserve_history() to obtain inbound transactions
- for a reserve */
- GNUNET_PQ_make_prepare ("reserves_in_get_transactions",
- "SELECT"
- " wire_reference"
- ",credit_val"
- ",credit_frac"
- ",execution_date"
- ",sender_account_details"
- " FROM reserves_in"
- " WHERE reserve_uuid="
- " (SELECT reserve_uuid "
- " FROM reserves"
- " WHERE reserve_pub=$1);",
- 1),
- /* Lock withdraw table; NOTE: we may want to eventually shard the
- deposit table to avoid this lock being the main point of
- contention limiting transaction performance. */
- GNUNET_PQ_make_prepare ("lock_withdraw",
- "LOCK TABLE reserves_out;",
- 0),
- /* Used in #postgres_insert_withdraw_info() to store
- the signature of a blinded coin with the blinded coin's
- details before returning it during /reserve/withdraw. We store
- the coin's denomination information (public key, signature)
- and the blinded message as well as the reserve that the coin
- is being withdrawn from and the signature of the message
- authorizing the withdrawal. */
- GNUNET_PQ_make_prepare ("insert_withdraw_info",
- "WITH ds AS"
- " (SELECT denominations_serial"
- " FROM denominations"
- " WHERE denom_pub_hash=$2)"
- "INSERT INTO reserves_out "
- "(h_blind_ev"
- ",denominations_serial"
- ",denom_sig"
- ",reserve_uuid"
- ",reserve_sig"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ") SELECT $1, ds.denominations_serial, $3, reserve_uuid, $5, $6, $7, $8"
- " FROM reserves"
- " CROSS JOIN ds"
- " WHERE reserve_pub=$4;",
- 8),
- /* Used in #postgres_get_withdraw_info() to
- locate the response for a /reserve/withdraw request
- using the hash of the blinded message. Used to
- make sure /reserve/withdraw requests are idempotent. */
- GNUNET_PQ_make_prepare ("get_withdraw_info",
- "SELECT"
- " denom.denom_pub_hash"
- ",denom_sig"
- ",reserve_sig"
- ",reserves.reserve_pub"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_withdraw_val"
- ",denom.fee_withdraw_frac"
- " FROM reserves_out"
- " JOIN reserves"
- " USING (reserve_uuid)"
- " JOIN denominations denom"
- " USING (denominations_serial)"
- " WHERE h_blind_ev=$1;",
- 1),
- /* Used during #postgres_get_reserve_history() to
- obtain all of the /reserve/withdraw operations that
- have been performed on a given reserve. (i.e. to
- demonstrate double-spending) */
- GNUNET_PQ_make_prepare ("get_reserves_out",
- "SELECT"
- " h_blind_ev"
- ",denom.denom_pub_hash"
- ",denom_sig"
- ",reserve_sig"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_withdraw_val"
- ",denom.fee_withdraw_frac"
- " FROM reserves_out"
- " JOIN denominations denom"
- " USING (denominations_serial)"
- " WHERE reserve_uuid="
- " (SELECT reserve_uuid"
- " FROM reserves"
- " WHERE reserve_pub=$1);",
- 1),
- /* Used in #postgres_select_withdrawals_above_serial_id() */
- GNUNET_PQ_make_prepare ("audit_get_reserves_out_incr",
- "SELECT"
- " h_blind_ev"
- ",denom.denom_pub"
- ",reserve_sig"
- ",reserves.reserve_pub"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",reserve_out_serial_id"
- " FROM reserves_out"
- " JOIN reserves"
- " USING (reserve_uuid)"
- " JOIN denominations denom"
- " USING (denominations_serial)"
- " WHERE reserve_out_serial_id>=$1"
- " ORDER BY reserve_out_serial_id ASC;",
- 1),
-
- /* Used in #postgres_count_known_coins() */
- GNUNET_PQ_make_prepare ("count_known_coins",
- "SELECT"
- " COUNT(*) AS count"
- " FROM known_coins"
- " WHERE denominations_serial="
- " (SELECT denominations_serial"
- " FROM denominations"
- " WHERE denom_pub_hash=$1);",
- 1),
- /* Used in #postgres_get_known_coin() to fetch
- the denomination public key and signature for
- a coin known to the exchange. */
- GNUNET_PQ_make_prepare ("get_known_coin",
- "SELECT"
- " denominations.denom_pub_hash"
- ",denom_sig"
- " FROM known_coins"
- " JOIN denominations USING (denominations_serial)"
- " WHERE coin_pub=$1;",
- 1),
- /* Used in #postgres_ensure_coin_known() */
- GNUNET_PQ_make_prepare ("get_known_coin_dh",
- "SELECT"
- " denominations.denom_pub_hash"
- " FROM known_coins"
- " JOIN denominations USING (denominations_serial)"
- " WHERE coin_pub=$1;",
- 1),
- /* Used in #postgres_get_coin_denomination() to fetch
- the denomination public key hash for
- a coin known to the exchange. */
- GNUNET_PQ_make_prepare ("get_coin_denomination",
- "SELECT"
- " denominations.denom_pub_hash"
- " FROM known_coins"
- " JOIN denominations USING (denominations_serial)"
- " WHERE coin_pub=$1"
- " FOR SHARE;",
- 1),
- /* Lock deposit table; NOTE: we may want to eventually shard the
- deposit table to avoid this lock being the main point of
- contention limiting transaction performance. */
- GNUNET_PQ_make_prepare ("lock_known_coins",
- "LOCK TABLE known_coins;",
- 0),
- /* Used in #postgres_insert_known_coin() to store
- the denomination public key and signature for
- a coin known to the exchange. */
- GNUNET_PQ_make_prepare ("insert_known_coin",
- "INSERT INTO known_coins "
- "(coin_pub"
- ",denominations_serial"
- ",denom_sig"
- ") SELECT $1, denominations_serial, $3 "
- " FROM denominations"
- " WHERE denom_pub_hash=$2;",
- 3),
-
- /* Used in #postgres_insert_melt() to store
- high-level information about a melt operation */
- GNUNET_PQ_make_prepare ("insert_melt",
- "INSERT INTO refresh_commitments "
- "(rc "
- ",old_known_coin_id "
- ",old_coin_sig "
- ",amount_with_fee_val "
- ",amount_with_fee_frac "
- ",noreveal_index "
- ") SELECT $1, known_coin_id, $3, $4, $5, $6"
- " FROM known_coins"
- " WHERE coin_pub=$2",
- 6),
- /* Used in #postgres_get_melt() to fetch
- high-level information about a melt operation */
- GNUNET_PQ_make_prepare ("get_melt",
- "SELECT"
- " denoms.denom_pub_hash"
- ",denoms.fee_refresh_val"
- ",denoms.fee_refresh_frac"
- ",kc.coin_pub AS old_coin_pub"
- ",old_coin_sig"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",noreveal_index"
- " FROM refresh_commitments"
- " JOIN known_coins kc"
- " ON (refresh_commitments.old_known_coin_id = kc.known_coin_id)"
- " JOIN denominations denoms"
- " ON (kc.denominations_serial = denoms.denominations_serial)"
- " WHERE rc=$1;",
- 1),
- /* Used in #postgres_get_melt_index() to fetch
- the noreveal index from a previous melt operation */
- GNUNET_PQ_make_prepare ("get_melt_index",
- "SELECT"
- " noreveal_index"
- " FROM refresh_commitments"
- " WHERE rc=$1;",
- 1),
- /* Used in #postgres_select_refreshes_above_serial_id() to fetch
- refresh session with id '\geq' the given parameter */
- GNUNET_PQ_make_prepare ("audit_get_refresh_commitments_incr",
- "SELECT"
- " denom.denom_pub"
- ",kc.coin_pub AS old_coin_pub"
- ",old_coin_sig"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",noreveal_index"
- ",melt_serial_id"
- ",rc"
- " FROM refresh_commitments"
- " JOIN known_coins kc"
- " ON (refresh_commitments.old_known_coin_id = kc.known_coin_id)"
- " JOIN denominations denom"
- " ON (kc.denominations_serial = denom.denominations_serial)"
- " WHERE melt_serial_id>=$1"
- " ORDER BY melt_serial_id ASC;",
- 1),
- /* Query the 'refresh_commitments' by coin public key */
- GNUNET_PQ_make_prepare ("get_refresh_session_by_coin",
- "SELECT"
- " rc"
- ",old_coin_sig"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denoms.denom_pub_hash"
- ",denoms.fee_refresh_val"
- ",denoms.fee_refresh_frac"
- ",melt_serial_id"
- " FROM refresh_commitments"
- " JOIN known_coins kc"
- " ON (refresh_commitments.old_known_coin_id = kc.known_coin_id)"
- " JOIN denominations denoms"
- " USING (denominations_serial)"
- " WHERE old_known_coin_id="
- "(SELECT known_coin_id"
- " FROM known_coins"
- " WHERE coin_pub=$1);",
- 1),
- /* Store information about the desired denominations for a
- refresh operation, used in #postgres_insert_refresh_reveal() */
- GNUNET_PQ_make_prepare ("insert_refresh_revealed_coin",
- "WITH rcx AS"
- " (SELECT melt_serial_id"
- " FROM refresh_commitments"
- " WHERE rc=$1)"
- "INSERT INTO refresh_revealed_coins "
- "(melt_serial_id "
- ",freshcoin_index "
- ",link_sig "
- ",denominations_serial "
- ",coin_ev"
- ",h_coin_ev"
- ",ev_sig"
- ") SELECT rcx.melt_serial_id, $2, $3, "
- " denominations_serial, $5, $6, $7"
- " FROM denominations"
- " CROSS JOIN rcx"
- " WHERE denom_pub_hash=$4;",
- 7),
- /* Obtain information about the coins created in a refresh
- operation, used in #postgres_get_refresh_reveal() */
- GNUNET_PQ_make_prepare ("get_refresh_revealed_coins",
- "SELECT "
- " freshcoin_index"
- ",denom.denom_pub"
- ",link_sig"
- ",coin_ev"
- ",ev_sig"
- " FROM refresh_revealed_coins"
- " JOIN denominations denom "
- " USING (denominations_serial)"
- " JOIN refresh_commitments"
- " USING (melt_serial_id)"
- " WHERE rc=$1"
- " ORDER BY freshcoin_index ASC;",
- 1),
-
- /* Used in #postgres_insert_refresh_reveal() to store the transfer
- keys we learned */
- GNUNET_PQ_make_prepare ("insert_refresh_transfer_keys",
- "INSERT INTO refresh_transfer_keys "
- "(melt_serial_id"
- ",transfer_pub"
- ",transfer_privs"
- ") SELECT melt_serial_id, $2, $3"
- " FROM refresh_commitments"
- " WHERE rc=$1",
- 3),
- /* Used in #postgres_get_refresh_reveal() to retrieve transfer
- keys from /refresh/reveal */
- GNUNET_PQ_make_prepare ("get_refresh_transfer_keys",
- "SELECT"
- " transfer_pub"
- ",transfer_privs"
- " FROM refresh_transfer_keys"
- " JOIN refresh_commitments"
- " USING (melt_serial_id)"
- " WHERE rc=$1;",
- 1),
- /* Used in #postgres_insert_refund() to store refund information */
- GNUNET_PQ_make_prepare ("insert_refund",
- "INSERT INTO refunds "
- "(deposit_serial_id "
- ",merchant_sig "
- ",rtransaction_id "
- ",amount_with_fee_val "
- ",amount_with_fee_frac "
- ") SELECT deposit_serial_id, $3, $5, $6, $7"
- " FROM deposits"
- " JOIN known_coins USING (known_coin_id)"
- " WHERE coin_pub=$1"
- " AND h_contract_terms=$4"
- " AND merchant_pub=$2",
- 7),
- /* Query the 'refunds' by coin public key */
- GNUNET_PQ_make_prepare ("get_refunds_by_coin",
- "SELECT"
- " merchant_pub"
- ",merchant_sig"
- ",h_contract_terms"
- ",rtransaction_id"
- ",refunds.amount_with_fee_val"
- ",refunds.amount_with_fee_frac"
- ",denom.fee_refund_val "
- ",denom.fee_refund_frac "
- ",refund_serial_id"
- " FROM refunds"
- " JOIN deposits USING (deposit_serial_id)"
- " JOIN known_coins USING (known_coin_id)"
- " JOIN denominations denom USING (denominations_serial)"
- " WHERE coin_pub=$1;",
- 1),
- /* Query the 'refunds' by coin public key, merchant_pub and contract hash */
- GNUNET_PQ_make_prepare ("get_refunds_by_coin_and_contract",
- "SELECT"
- " refunds.amount_with_fee_val"
- ",refunds.amount_with_fee_frac"
- " FROM refunds"
- " JOIN deposits USING (deposit_serial_id)"
- " JOIN known_coins USING (known_coin_id)"
- " WHERE coin_pub=$1"
- " AND merchant_pub=$2"
- " AND h_contract_terms=$3;",
- 3),
- /* Fetch refunds with rowid '\geq' the given parameter */
- GNUNET_PQ_make_prepare ("audit_get_refunds_incr",
- "SELECT"
- " merchant_pub"
- ",merchant_sig"
- ",h_contract_terms"
- ",rtransaction_id"
- ",denom.denom_pub"
- ",kc.coin_pub"
- ",refunds.amount_with_fee_val"
- ",refunds.amount_with_fee_frac"
- ",refund_serial_id"
- " FROM refunds"
- " JOIN deposits USING (deposit_serial_id)"
- " JOIN known_coins kc USING (known_coin_id)"
- " JOIN denominations denom ON (kc.denominations_serial = denom.denominations_serial)"
- " WHERE refund_serial_id>=$1"
- " ORDER BY refund_serial_id ASC;",
- 1),
- /* Lock deposit table; NOTE: we may want to eventually shard the
- deposit table to avoid this lock being the main point of
- contention limiting transaction performance. */
- GNUNET_PQ_make_prepare ("lock_deposit",
- "LOCK TABLE deposits;",
- 0),
- /* Store information about a /deposit the exchange is to execute.
- Used in #postgres_insert_deposit(). */
- GNUNET_PQ_make_prepare ("insert_deposit",
- "INSERT INTO deposits "
- "(known_coin_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",wallet_timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",merchant_pub"
- ",h_contract_terms"
- ",h_wire"
- ",coin_sig"
- ",wire"
- ",exchange_timestamp"
- ",shard"
- ") SELECT known_coin_id, $2, $3, $4, $5, $6, "
- " $7, $8, $9, $10, $11, $12, $13"
- " FROM known_coins"
- " WHERE coin_pub=$1;",
- 13),
- /* Fetch an existing deposit request, used to ensure idempotency
- during /deposit processing. Used in #postgres_have_deposit(). */
- GNUNET_PQ_make_prepare ("get_deposit",
- "SELECT"
- " amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denominations.fee_deposit_val"
- ",denominations.fee_deposit_frac"
- ",wallet_timestamp"
- ",exchange_timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",h_contract_terms"
- ",h_wire"
- " FROM deposits"
- " JOIN known_coins USING (known_coin_id)"
- " JOIN denominations USING (denominations_serial)"
- " WHERE ((coin_pub=$1)"
- " AND (merchant_pub=$3)"
- " AND (h_contract_terms=$2));",
- 3),
- /* Fetch deposits with rowid '\geq' the given parameter */
- GNUNET_PQ_make_prepare ("audit_get_deposits_incr",
- "SELECT"
- " amount_with_fee_val"
- ",amount_with_fee_frac"
- ",wallet_timestamp"
- ",exchange_timestamp"
- ",merchant_pub"
- ",denom.denom_pub"
- ",kc.coin_pub"
- ",coin_sig"
- ",refund_deadline"
- ",wire_deadline"
- ",h_contract_terms"
- ",wire"
- ",done"
- ",deposit_serial_id"
- " FROM deposits"
- " JOIN known_coins kc USING (known_coin_id)"
- " JOIN denominations denom USING (denominations_serial)"
- " WHERE ("
- " (deposit_serial_id>=$1)"
- " )"
- " ORDER BY deposit_serial_id ASC;",
- 1),
- /* Fetch an existing deposit request.
- Used in #postgres_lookup_transfer_by_deposit(). */
- GNUNET_PQ_make_prepare ("get_deposit_for_wtid",
- "SELECT"
- " FALSE AS kyc_ok" // FIXME
- ",CAST (0 AS INT8) AS payment_target_uuid" // FIXME
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- ",wire_deadline"
- " FROM deposits"
- " JOIN known_coins USING (known_coin_id)"
- " JOIN denominations denom USING (denominations_serial)"
- " WHERE ((coin_pub=$1)"
- " AND (merchant_pub=$4)"
- " AND (h_contract_terms=$2)"
- " AND (h_wire=$3)"
- " );",
- 4),
- /* Used in #postgres_get_ready_deposit() */
- GNUNET_PQ_make_prepare ("deposits_get_ready",
- "SELECT"
- " deposit_serial_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- ",h_contract_terms"
- ",wire"
- ",merchant_pub"
- ",kc.coin_pub"
- " FROM deposits"
- " JOIN known_coins kc USING (known_coin_id)"
- " JOIN denominations denom USING (denominations_serial)"
- " WHERE "
- " shard >= $2"
- " AND shard <= $3"
- " AND tiny=FALSE"
- " AND done=FALSE"
- " AND wire_deadline<=$1"
- " AND refund_deadline<$1"
- " ORDER BY "
- " shard ASC"
- " ,wire_deadline ASC"
- " LIMIT 1;",
- 3),
- /* Used in #postgres_iterate_matching_deposits() */
- GNUNET_PQ_make_prepare ("deposits_iterate_matching",
- "SELECT"
- " deposit_serial_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- ",h_contract_terms"
- ",kc.coin_pub"
- " FROM deposits"
- " JOIN known_coins kc USING (known_coin_id)"
- " JOIN denominations denom USING (denominations_serial)"
- " WHERE"
- " merchant_pub=$1 AND"
- " h_wire=$2 AND"
- " done=FALSE"
- " ORDER BY wire_deadline ASC"
- " LIMIT "
- TALER_QUOTE (
- TALER_EXCHANGEDB_MATCHING_DEPOSITS_LIMIT) ";",
- 2),
- /* Used in #postgres_mark_deposit_tiny() */
- GNUNET_PQ_make_prepare ("mark_deposit_tiny",
- "UPDATE deposits"
- " SET tiny=TRUE"
- " WHERE deposit_serial_id=$1",
- 1),
- /* Used in #postgres_mark_deposit_done() */
- GNUNET_PQ_make_prepare ("mark_deposit_done",
- "UPDATE deposits"
- " SET done=TRUE"
- " WHERE deposit_serial_id=$1;",
- 1),
- /* Used in #postgres_test_deposit_done() */
- GNUNET_PQ_make_prepare ("test_deposit_done",
- "SELECT done"
- " FROM deposits"
- " JOIN known_coins USING (known_coin_id)"
- " WHERE coin_pub=$1"
- " AND merchant_pub=$2"
- " AND h_contract_terms=$3"
- " AND h_wire=$4;",
- 5),
- /* Used in #postgres_get_coin_transactions() to obtain information
- about how a coin has been spend with /deposit requests. */
- GNUNET_PQ_make_prepare ("get_deposit_with_coin_pub",
- "SELECT"
- " amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denoms.fee_deposit_val"
- ",denoms.fee_deposit_frac"
- ",denoms.denom_pub_hash"
- ",wallet_timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",merchant_pub"
- ",h_contract_terms"
- ",h_wire"
- ",wire"
- ",coin_sig"
- ",deposit_serial_id"
- ",done"
- " FROM deposits"
- " JOIN known_coins kc"
- " USING (known_coin_id)"
- " JOIN denominations denoms"
- " USING (denominations_serial)"
- " WHERE coin_pub=$1;",
- 1),
-
- /* Used in #postgres_get_link_data(). */
- GNUNET_PQ_make_prepare ("get_link",
- "SELECT "
- " tp.transfer_pub"
- ",denoms.denom_pub"
- ",rrc.ev_sig"
- ",rrc.link_sig"
- " FROM refresh_commitments"
- " JOIN refresh_revealed_coins rrc"
- " USING (melt_serial_id)"
- " JOIN refresh_transfer_keys tp"
- " USING (melt_serial_id)"
- " JOIN denominations denoms"
- " ON (rrc.denominations_serial = denoms.denominations_serial)"
- " WHERE old_known_coin_id="
- " (SELECT known_coin_id "
- " FROM known_coins"
- " WHERE coin_pub=$1)"
- " ORDER BY tp.transfer_pub, rrc.freshcoin_index ASC",
- 1),
- /* Used in #postgres_lookup_wire_transfer */
- GNUNET_PQ_make_prepare ("lookup_transactions",
- "SELECT"
- " aggregation_serial_id"
- ",deposits.h_contract_terms"
- ",deposits.wire"
- ",deposits.h_wire"
- ",kc.coin_pub"
- ",deposits.merchant_pub"
- ",wire_out.execution_date"
- ",deposits.amount_with_fee_val"
- ",deposits.amount_with_fee_frac"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- ",denom.denom_pub"
- " FROM aggregation_tracking"
- " JOIN deposits"
- " USING (deposit_serial_id)"
- " JOIN known_coins kc"
- " USING (known_coin_id)"
- " JOIN denominations denom"
- " USING (denominations_serial)"
- " JOIN wire_out"
- " USING (wtid_raw)"
- " WHERE wtid_raw=$1;",
- 1),
- /* Used in #postgres_lookup_transfer_by_deposit */
- GNUNET_PQ_make_prepare ("lookup_deposit_wtid",
- "SELECT"
- " aggregation_tracking.wtid_raw"
- ",wire_out.execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",denom.fee_deposit_val"
- ",denom.fee_deposit_frac"
- " FROM deposits"
- " JOIN aggregation_tracking"
- " USING (deposit_serial_id)"
- " JOIN known_coins"
- " USING (known_coin_id)"
- " JOIN denominations denom"
- " USING (denominations_serial)"
- " JOIN wire_out"
- " USING (wtid_raw)"
- " WHERE coin_pub=$1"
- " AND h_contract_terms=$2"
- " AND h_wire=$3"
- " AND merchant_pub=$4;",
- 4),
- /* Used in #postgres_insert_aggregation_tracking */
- GNUNET_PQ_make_prepare ("insert_aggregation_tracking",
- "INSERT INTO aggregation_tracking "
- "(deposit_serial_id"
- ",wtid_raw"
- ") VALUES "
- "($1, $2);",
- 2),
- /* Used in #postgres_get_wire_fee() */
- GNUNET_PQ_make_prepare ("get_wire_fee",
- "SELECT "
- " start_date"
- ",end_date"
- ",wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",master_sig"
- " FROM wire_fee"
- " WHERE wire_method=$1"
- " AND start_date <= $2"
- " AND end_date > $2;",
- 2),
- /* Used in #postgres_insert_wire_fee */
- GNUNET_PQ_make_prepare ("insert_wire_fee",
- "INSERT INTO wire_fee "
- "(wire_method"
- ",start_date"
- ",end_date"
- ",wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",master_sig"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8);",
- 8),
- /* Used in #postgres_store_wire_transfer_out */
- GNUNET_PQ_make_prepare ("insert_wire_out",
- "INSERT INTO wire_out "
- "(execution_date"
- ",wtid_raw"
- ",wire_target"
- ",exchange_account_section"
- ",amount_val"
- ",amount_frac"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6);",
- 6),
- /* Used in #postgres_wire_prepare_data_insert() to store
- wire transfer information before actually committing it with the bank */
- GNUNET_PQ_make_prepare ("wire_prepare_data_insert",
- "INSERT INTO prewire "
- "(type"
- ",buf"
- ") VALUES "
- "($1, $2);",
- 2),
- /* Used in #postgres_wire_prepare_data_mark_finished() */
- GNUNET_PQ_make_prepare ("wire_prepare_data_mark_done",
- "UPDATE prewire"
- " SET finished=TRUE"
- " WHERE prewire_uuid=$1;",
- 1),
- /* Used in #postgres_wire_prepare_data_mark_failed() */
- GNUNET_PQ_make_prepare ("wire_prepare_data_mark_failed",
- "UPDATE prewire"
- " SET failed=TRUE"
- " WHERE prewire_uuid=$1;",
- 1),
- /* Used in #postgres_wire_prepare_data_get() */
- GNUNET_PQ_make_prepare ("wire_prepare_data_get",
- "SELECT"
- " prewire_uuid"
- ",type"
- ",buf"
- " FROM prewire"
- " WHERE prewire_uuid >= $1"
- " AND finished=FALSE"
- " AND failed=FALSE"
- " ORDER BY prewire_uuid ASC"
- " LIMIT $2;",
- 2),
- /* Used in #postgres_select_deposits_missing_wire */
- GNUNET_PQ_make_prepare ("deposits_get_overdue",
- "SELECT"
- " deposit_serial_id"
- ",coin_pub"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",wire"
- ",wire_deadline"
- ",tiny"
- ",done"
- " FROM deposits d"
- " JOIN known_coins USING (known_coin_id)"
- " WHERE wire_deadline >= $1"
- " AND wire_deadline < $2"
- " AND NOT (EXISTS (SELECT 1"
- " FROM refunds"
- " JOIN deposits dx USING (deposit_serial_id)"
- " WHERE (dx.known_coin_id = d.known_coin_id))"
- " OR EXISTS (SELECT 1"
- " FROM aggregation_tracking"
- " WHERE (aggregation_tracking.deposit_serial_id = d.deposit_serial_id)))"
- " ORDER BY wire_deadline ASC",
- 2),
- /* Used in #postgres_select_wire_out_above_serial_id() */
- GNUNET_PQ_make_prepare ("audit_get_wire_incr",
- "SELECT"
- " wireout_uuid"
- ",execution_date"
- ",wtid_raw"
- ",wire_target"
- ",amount_val"
- ",amount_frac"
- " FROM wire_out"
- " WHERE wireout_uuid>=$1"
- " ORDER BY wireout_uuid ASC;",
- 1),
- /* Used in #postgres_select_wire_out_above_serial_id_by_account() */
- GNUNET_PQ_make_prepare ("audit_get_wire_incr_by_account",
- "SELECT"
- " wireout_uuid"
- ",execution_date"
- ",wtid_raw"
- ",wire_target"
- ",amount_val"
- ",amount_frac"
- " FROM wire_out"
- " WHERE wireout_uuid>=$1 AND exchange_account_section=$2"
- " ORDER BY wireout_uuid ASC;",
- 2),
- /* Used in #postgres_insert_recoup_request() to store recoup
- information */
- GNUNET_PQ_make_prepare ("recoup_insert",
- "WITH rx AS"
- " (SELECT reserve_out_serial_id"
- " FROM reserves_out"
- " WHERE h_blind_ev=$7)"
- "INSERT INTO recoup "
- "(known_coin_id"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",reserve_out_serial_id"
- ") SELECT known_coin_id, $2, $3, $4, $5, $6, rx.reserve_out_serial_id"
- " FROM known_coins"
- " CROSS JOIN rx"
- " WHERE coin_pub=$1;",
- 7),
- /* Used in #postgres_insert_recoup_refresh_request() to store recoup-refresh
- information */
- GNUNET_PQ_make_prepare ("recoup_refresh_insert",
- "WITH rrx AS"
- " (SELECT rrc_serial"
- " FROM refresh_revealed_coins"
- " WHERE h_coin_ev=$7)"
- "INSERT INTO recoup_refresh "
- "(known_coin_id"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",rrc_serial"
- ") SELECT known_coin_id, $2, $3, $4, $5, $6, rrx.rrc_serial"
- " FROM known_coins"
- " CROSS JOIN rrx"
- " WHERE coin_pub=$1;",
- 7),
- /* Used in #postgres_select_recoup_above_serial_id() to obtain recoup transactions */
- GNUNET_PQ_make_prepare ("recoup_get_incr",
- "SELECT"
- " recoup_uuid"
- ",timestamp"
- ",reserves.reserve_pub"
- ",coins.coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",ro.h_blind_ev"
- ",denoms.denom_pub_hash"
- ",coins.denom_sig"
- ",denoms.denom_pub"
- ",amount_val"
- ",amount_frac"
- " FROM recoup"
- " JOIN known_coins coins"
- " USING (known_coin_id)"
- " JOIN reserves_out ro"
- " USING (reserve_out_serial_id)"
- " JOIN reserves"
- " USING (reserve_uuid)"
- " JOIN denominations denoms"
- " ON (coins.denominations_serial = denoms.denominations_serial)"
- " WHERE recoup_uuid>=$1"
- " ORDER BY recoup_uuid ASC;",
- 1),
- /* Used in #postgres_select_recoup_refresh_above_serial_id() to obtain
- recoup-refresh transactions */
- GNUNET_PQ_make_prepare ("recoup_refresh_get_incr",
- "SELECT"
- " recoup_refresh_uuid"
- ",timestamp"
- ",old_coins.coin_pub AS old_coin_pub"
- ",old_denoms.denom_pub_hash AS old_denom_pub_hash"
- ",new_coins.coin_pub As coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",new_denoms.denom_pub AS denom_pub"
- ",rrc.h_coin_ev AS h_blind_ev"
- ",new_denoms.denom_pub_hash"
- ",new_coins.denom_sig AS denom_sig"
- ",amount_val"
- ",amount_frac"
- " FROM recoup_refresh"
- " INNER JOIN refresh_revealed_coins rrc"
- " USING (rrc_serial)"
- " INNER JOIN refresh_commitments rfc"
- " ON (rrc.melt_serial_id = rfc.melt_serial_id)"
- " INNER JOIN known_coins old_coins"
- " ON (rfc.old_known_coin_id = old_coins.known_coin_id)"
- " INNER JOIN known_coins new_coins"
- " ON (new_coins.known_coin_id = recoup_refresh.known_coin_id)"
- " INNER JOIN denominations new_denoms"
- " ON (new_coins.denominations_serial = new_denoms.denominations_serial)"
- " INNER JOIN denominations old_denoms"
- " ON (old_coins.denominations_serial = old_denoms.denominations_serial)"
- " WHERE recoup_refresh_uuid>=$1"
- " ORDER BY recoup_refresh_uuid ASC;",
- 1),
- /* Used in #postgres_select_reserve_closed_above_serial_id() to
- obtain information about closed reserves */
- GNUNET_PQ_make_prepare ("reserves_close_get_incr",
- "SELECT"
- " close_uuid"
- ",reserves.reserve_pub"
- ",execution_date"
- ",wtid"
- ",receiver_account"
- ",amount_val"
- ",amount_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- " FROM reserves_close"
- " JOIN reserves"
- " USING (reserve_uuid)"
- " WHERE close_uuid>=$1"
- " ORDER BY close_uuid ASC;",
- 1),
- /* Used in #postgres_get_reserve_history() to obtain recoup transactions
- for a reserve */
- GNUNET_PQ_make_prepare ("recoup_by_reserve",
- "SELECT"
- " coins.coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",denoms.denom_pub_hash"
- ",coins.denom_sig"
- " FROM recoup"
- " JOIN known_coins coins"
- " USING (known_coin_id)"
- " JOIN denominations denoms"
- " USING (denominations_serial)"
- " JOIN reserves_out ro"
- " USING (reserve_out_serial_id)"
- " WHERE ro.reserve_uuid="
- " (SELECT reserve_uuid"
- " FROM reserves"
- " WHERE reserve_pub=$1);",
- 1),
- /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
- affecting old coins of refreshed coins */
- GNUNET_PQ_make_prepare ("recoup_by_old_coin",
- "SELECT"
- " coins.coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",denoms.denom_pub_hash"
- ",coins.denom_sig"
- ",recoup_refresh_uuid"
- " FROM recoup_refresh"
- " JOIN known_coins coins"
- " USING (known_coin_id)"
- " JOIN denominations denoms"
- " USING (denominations_serial)"
- " WHERE rrc_serial IN"
- " (SELECT rrc.rrc_serial"
- " FROM refresh_commitments"
- " JOIN refresh_revealed_coins rrc"
- " USING (melt_serial_id)"
- " WHERE old_known_coin_id="
- " (SELECT known_coin_id"
- " FROM known_coins"
- " WHERE coin_pub=$1));",
- 1),
- /* Used in #postgres_get_reserve_history() */
- GNUNET_PQ_make_prepare ("close_by_reserve",
- "SELECT"
- " amount_val"
- ",amount_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",execution_date"
- ",receiver_account"
- ",wtid"
- " FROM reserves_close"
- " WHERE reserve_uuid="
- " (SELECT reserve_uuid"
- " FROM reserves"
- " WHERE reserve_pub=$1);",
- 1),
- /* Used in #postgres_get_expired_reserves() */
- GNUNET_PQ_make_prepare ("get_expired_reserves",
- "SELECT"
- " expiration_date"
- ",account_details"
- ",reserve_pub"
- ",current_balance_val"
- ",current_balance_frac"
- " FROM reserves"
- " WHERE expiration_date<=$1"
- " AND (current_balance_val != 0 "
- " OR current_balance_frac != 0)"
- " ORDER BY expiration_date ASC"
- " LIMIT 1;",
- 1),
- /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
- for a coin */
- GNUNET_PQ_make_prepare ("recoup_by_coin",
- "SELECT"
- " reserves.reserve_pub"
- ",denoms.denom_pub_hash"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",recoup_uuid"
- " FROM recoup"
- " JOIN reserves_out ro"
- " USING (reserve_out_serial_id)"
- " JOIN reserves"
- " USING (reserve_uuid)"
- " JOIN known_coins coins"
- " USING (known_coin_id)"
- " JOIN denominations denoms"
- " ON (denoms.denominations_serial = coins.denominations_serial)"
- " WHERE coins.coin_pub=$1;",
- 1),
- /* Used in #postgres_get_coin_transactions() to obtain recoup transactions
- for a refreshed coin */
- GNUNET_PQ_make_prepare ("recoup_by_refreshed_coin",
- "SELECT"
- " old_coins.coin_pub AS old_coin_pub"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",denoms.denom_pub_hash"
- ",coins.denom_sig"
- ",recoup_refresh_uuid"
- " FROM recoup_refresh"
- " JOIN refresh_revealed_coins rrc"
- " USING (rrc_serial)"
- " JOIN refresh_commitments rfc"
- " ON (rrc.melt_serial_id = rfc.melt_serial_id)"
- " JOIN known_coins old_coins"
- " ON (rfc.old_known_coin_id = old_coins.known_coin_id)"
- " JOIN known_coins coins"
- " ON (recoup_refresh.known_coin_id = coins.known_coin_id)"
- " JOIN denominations denoms"
- " ON (denoms.denominations_serial = coins.denominations_serial)"
- " WHERE coins.coin_pub=$1;",
- 1),
- /* Used in #postgres_get_reserve_by_h_blind() */
- GNUNET_PQ_make_prepare ("reserve_by_h_blind",
- "SELECT"
- " reserves.reserve_pub"
- " FROM reserves_out"
- " JOIN reserves"
- " USING (reserve_uuid)"
- " WHERE h_blind_ev=$1"
- " LIMIT 1;",
- 1),
- /* Used in #postgres_get_old_coin_by_h_blind() */
- GNUNET_PQ_make_prepare ("old_coin_by_h_blind",
- "SELECT"
- " okc.coin_pub AS old_coin_pub"
- " FROM refresh_revealed_coins rrc"
- " JOIN refresh_commitments rcom USING (melt_serial_id)"
- " JOIN known_coins okc ON (rcom.old_known_coin_id = okc.known_coin_id)"
- " WHERE h_coin_ev=$1"
- " LIMIT 1;",
- 1),
- /* Used in #postgres_lookup_auditor_timestamp() */
- GNUNET_PQ_make_prepare ("lookup_auditor_timestamp",
- "SELECT"
- " last_change"
- " FROM auditors"
- " WHERE auditor_pub=$1;",
- 1),
- /* Used in #postgres_lookup_auditor_status() */
- GNUNET_PQ_make_prepare ("lookup_auditor_status",
- "SELECT"
- " auditor_url"
- ",is_active"
- " FROM auditors"
- " WHERE auditor_pub=$1;",
- 1),
- /* Used in #postgres_lookup_wire_timestamp() */
- GNUNET_PQ_make_prepare ("lookup_wire_timestamp",
- "SELECT"
- " last_change"
- " FROM wire_accounts"
- " WHERE payto_uri=$1;",
- 1),
- /* used in #postgres_insert_auditor() */
- GNUNET_PQ_make_prepare ("insert_auditor",
- "INSERT INTO auditors "
- "(auditor_pub"
- ",auditor_name"
- ",auditor_url"
- ",is_active"
- ",last_change"
- ") VALUES "
- "($1, $2, $3, true, $4);",
- 4),
- /* used in #postgres_update_auditor() */
- GNUNET_PQ_make_prepare ("update_auditor",
- "UPDATE auditors"
- " SET"
- " auditor_url=$2"
- " ,auditor_name=$3"
- " ,is_active=$4"
- " ,last_change=$5"
- " WHERE auditor_pub=$1",
- 5),
- /* used in #postgres_insert_wire() */
- GNUNET_PQ_make_prepare ("insert_wire",
- "INSERT INTO wire_accounts "
- "(payto_uri"
- ",master_sig"
- ",is_active"
- ",last_change"
- ") VALUES "
- "($1, $2, true, $3);",
- 3),
- /* used in #postgres_update_wire() */
- GNUNET_PQ_make_prepare ("update_wire",
- "UPDATE wire_accounts"
- " SET"
- " is_active=$2"
- " ,last_change=$3"
- " WHERE payto_uri=$1",
- 3),
- /* used in #postgres_update_wire() */
- GNUNET_PQ_make_prepare ("get_wire_accounts",
- "SELECT"
- " payto_uri"
- ",master_sig"
- " FROM wire_accounts"
- " WHERE is_active",
- 0),
- /* used in #postgres_update_wire() */
- GNUNET_PQ_make_prepare ("get_wire_fees",
- "SELECT"
- " wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",start_date"
- ",end_date"
- ",master_sig"
- " FROM wire_fee"
- " WHERE wire_method=$1",
- 1),
- /* used in #postgres_insert_signkey_revocation() */
- GNUNET_PQ_make_prepare ("insert_signkey_revocation",
- "INSERT INTO signkey_revocations "
- "(esk_serial"
- ",master_sig"
- ") SELECT esk_serial, $2 "
- " FROM exchange_sign_keys"
- " WHERE exchange_pub=$1;",
- 2),
- /* used in #postgres_insert_signkey_revocation() */
- GNUNET_PQ_make_prepare ("lookup_signkey_revocation",
- "SELECT "
- " master_sig"
- " FROM signkey_revocations"
- " WHERE esk_serial="
- " (SELECT esk_serial"
- " FROM exchange_sign_keys"
- " WHERE exchange_pub=$1);",
- 1),
- /* used in #postgres_insert_signkey() */
- GNUNET_PQ_make_prepare ("insert_signkey",
- "INSERT INTO exchange_sign_keys "
- "(exchange_pub"
- ",valid_from"
- ",expire_sign"
- ",expire_legal"
- ",master_sig"
- ") VALUES "
- "($1, $2, $3, $4, $5);",
- 5),
- /* used in #postgres_lookup_signing_key() */
- GNUNET_PQ_make_prepare ("lookup_signing_key",
- "SELECT"
- " valid_from"
- ",expire_sign"
- ",expire_legal"
- " FROM exchange_sign_keys"
- " WHERE exchange_pub=$1",
- 1),
- /* used in #postgres_lookup_denomination_key() */
- GNUNET_PQ_make_prepare ("lookup_denomination_key",
- "SELECT"
- " valid_from"
- ",expire_withdraw"
- ",expire_deposit"
- ",expire_legal"
- ",coin_val"
- ",coin_frac"
- ",fee_withdraw_val"
- ",fee_withdraw_frac"
- ",fee_deposit_val"
- ",fee_deposit_frac"
- ",fee_refresh_val"
- ",fee_refresh_frac"
- ",fee_refund_val"
- ",fee_refund_frac"
- " FROM denominations"
- " WHERE denom_pub_hash=$1;",
- 1),
- /* used in #postgres_insert_auditor_denom_sig() */
- GNUNET_PQ_make_prepare ("insert_auditor_denom_sig",
- "WITH ax AS"
- " (SELECT auditor_uuid"
- " FROM auditors"
- " WHERE auditor_pub=$1)"
- "INSERT INTO auditor_denom_sigs "
- "(auditor_uuid"
- ",denominations_serial"
- ",auditor_sig"
- ") SELECT ax.auditor_uuid, denominations_serial, $3 "
- " FROM denominations"
- " CROSS JOIN ax"
- " WHERE denom_pub_hash=$2;",
- 3),
- /* used in #postgres_select_auditor_denom_sig() */
- GNUNET_PQ_make_prepare ("select_auditor_denom_sig",
- "SELECT"
- " auditor_sig"
- " FROM auditor_denom_sigs"
- " WHERE auditor_uuid="
- " (SELECT auditor_uuid"
- " FROM auditors"
- " WHERE auditor_pub=$1)"
- " AND denominations_serial="
- " (SELECT denominations_serial"
- " FROM denominations"
- " WHERE denom_pub_hash=$2);",
- 2),
- /* used in #postgres_lookup_wire_fee_by_time() */
- GNUNET_PQ_make_prepare ("lookup_wire_fee_by_time",
- "SELECT"
- " wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- " FROM wire_fee"
- " WHERE wire_method=$1"
- " AND end_date > $2"
- " AND start_date < $3;",
- 1),
- /* used in #postgres_commit */
- GNUNET_PQ_make_prepare ("do_commit",
- "COMMIT",
- 0),
- /* used in #postgres_lookup_serial_by_table() */
- GNUNET_PQ_make_prepare ("select_serial_by_table_denominations",
- "SELECT"
- " denominations_serial AS serial"
- " FROM denominations"
- " ORDER BY denominations_serial DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_denomination_revocations",
- "SELECT"
- " denom_revocations_serial_id AS serial"
- " FROM denomination_revocations"
- " ORDER BY denom_revocations_serial_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_reserves",
- "SELECT"
- " reserve_uuid AS serial"
- " FROM reserves"
- " ORDER BY reserve_uuid DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_reserves_in",
- "SELECT"
- " reserve_in_serial_id AS serial"
- " FROM reserves_in"
- " ORDER BY reserve_in_serial_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_reserves_close",
- "SELECT"
- " close_uuid AS serial"
- " FROM reserves_close"
- " ORDER BY close_uuid DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_reserves_out",
- "SELECT"
- " reserve_out_serial_id AS serial"
- " FROM reserves_out"
- " ORDER BY reserve_out_serial_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_auditors",
- "SELECT"
- " auditor_uuid AS serial"
- " FROM auditors"
- " ORDER BY auditor_uuid DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_auditor_denom_sigs",
- "SELECT"
- " auditor_denom_serial AS serial"
- " FROM auditor_denom_sigs"
- " ORDER BY auditor_denom_serial DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_exchange_sign_keys",
- "SELECT"
- " esk_serial AS serial"
- " FROM exchange_sign_keys"
- " ORDER BY esk_serial DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_signkey_revocations",
- "SELECT"
- " signkey_revocations_serial_id AS serial"
- " FROM signkey_revocations"
- " ORDER BY signkey_revocations_serial_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_known_coins",
- "SELECT"
- " known_coin_id AS serial"
- " FROM known_coins"
- " ORDER BY known_coin_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_refresh_commitments",
- "SELECT"
- " melt_serial_id AS serial"
- " FROM refresh_commitments"
- " ORDER BY melt_serial_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_refresh_revealed_coins",
- "SELECT"
- " rrc_serial AS serial"
- " FROM refresh_revealed_coins"
- " ORDER BY rrc_serial DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_refresh_transfer_keys",
- "SELECT"
- " rtc_serial AS serial"
- " FROM refresh_transfer_keys"
- " ORDER BY rtc_serial DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_deposits",
- "SELECT"
- " deposit_serial_id AS serial"
- " FROM deposits"
- " ORDER BY deposit_serial_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_refunds",
- "SELECT"
- " refund_serial_id AS serial"
- " FROM refunds"
- " ORDER BY refund_serial_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_wire_out",
- "SELECT"
- " wireout_uuid AS serial"
- " FROM wire_out"
- " ORDER BY wireout_uuid DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_aggregation_tracking",
- "SELECT"
- " aggregation_serial_id AS serial"
- " FROM aggregation_tracking"
- " ORDER BY aggregation_serial_id DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_wire_fee",
- "SELECT"
- " wire_fee_serial AS serial"
- " FROM wire_fee"
- " ORDER BY wire_fee_serial DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_recoup",
- "SELECT"
- " recoup_uuid AS serial"
- " FROM recoup"
- " ORDER BY recoup_uuid DESC"
- " LIMIT 1;",
- 0),
- GNUNET_PQ_make_prepare ("select_serial_by_table_recoup_refresh",
- "SELECT"
- " recoup_refresh_uuid AS serial"
- " FROM recoup_refresh"
- " ORDER BY recoup_refresh_uuid DESC"
- " LIMIT 1;",
- 0),
- /* For postgres_lookup_records_by_table */
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_denominations",
- "SELECT"
- " denominations_serial AS serial"
- ",denom_pub"
- ",master_sig"
- ",valid_from"
- ",expire_withdraw"
- ",expire_deposit"
- ",expire_legal"
- ",coin_val"
- ",coin_frac"
- ",fee_withdraw_val"
- ",fee_withdraw_frac"
- ",fee_deposit_val"
- ",fee_deposit_frac"
- ",fee_refresh_val"
- ",fee_refresh_frac"
- ",fee_refund_val"
- ",fee_refund_frac"
- " FROM denominations"
- " WHERE denominations_serial > $1"
- " ORDER BY denominations_serial ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_denomination_revocations",
- "SELECT"
- " denom_revocations_serial_id AS serial"
- ",master_sig"
- ",denominations_serial"
- " FROM denomination_revocations"
- " WHERE denom_revocations_serial_id > $1"
- " ORDER BY denom_revocations_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_reserves",
- "SELECT"
- " reserve_uuid AS serial"
- ",reserve_pub"
- ",account_details"
- ",current_balance_val"
- ",current_balance_frac"
- ",expiration_date"
- ",gc_date"
- " FROM reserves"
- " WHERE reserve_uuid > $1"
- " ORDER BY reserve_uuid ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_reserves_in",
- "SELECT"
- " reserve_in_serial_id AS serial"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",sender_account_details"
- ",exchange_account_section"
- ",execution_date"
- ",reserve_uuid"
- " FROM reserves_in"
- " WHERE reserve_in_serial_id > $1"
- " ORDER BY reserve_in_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_reserves_close",
- "SELECT"
- " close_uuid AS serial"
- ",execution_date"
- ",wtid"
- ",receiver_account"
- ",amount_val"
- ",amount_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",reserve_uuid"
- " FROM reserves_close"
- " WHERE close_uuid > $1"
- " ORDER BY close_uuid ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_reserves_out",
- "SELECT"
- " reserve_out_serial_id AS serial"
- ",h_blind_ev"
- ",denom_sig"
- ",reserve_sig"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",reserve_uuid"
- ",denominations_serial"
- " FROM reserves_out"
- " WHERE reserve_out_serial_id > $1"
- " ORDER BY reserve_out_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_auditors",
- "SELECT"
- " auditor_uuid AS serial"
- ",auditor_pub"
- ",auditor_name"
- ",auditor_url"
- ",is_active"
- ",last_change"
- " FROM auditors"
- " WHERE auditor_uuid > $1"
- " ORDER BY auditor_uuid ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_auditor_denom_sigs",
- "SELECT"
- " auditor_denom_serial AS serial"
- ",auditor_uuid"
- ",denominations_serial"
- ",auditor_sig"
- " FROM auditor_denom_sigs"
- " WHERE auditor_denom_serial > $1"
- " ORDER BY auditor_denom_serial ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_exchange_sign_keys",
- "SELECT"
- " esk_serial AS serial"
- ",exchange_pub"
- ",master_sig"
- ",valid_from"
- ",expire_sign"
- ",expire_legal"
- " FROM exchange_sign_keys"
- " WHERE esk_serial > $1"
- " ORDER BY esk_serial ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_signkey_revocations",
- "SELECT"
- " signkey_revocations_serial_id AS serial"
- ",esk_serial"
- ",master_sig"
- " FROM signkey_revocations"
- " WHERE signkey_revocations_serial_id > $1"
- " ORDER BY signkey_revocations_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_known_coins",
- "SELECT"
- " known_coin_id AS serial"
- ",coin_pub"
- ",denom_sig"
- ",denominations_serial"
- " FROM known_coins"
- " WHERE known_coin_id > $1"
- " ORDER BY known_coin_id ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_refresh_commitments",
- "SELECT"
- " melt_serial_id AS serial"
- ",rc"
- ",old_known_coin_id"
- ",old_coin_sig"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",noreveal_index"
- " FROM refresh_commitments"
- " WHERE melt_serial_id > $1"
- " ORDER BY melt_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_refresh_revealed_coins",
- "SELECT"
- " rrc_serial AS serial"
- ",freshcoin_index"
- ",link_sig"
- ",coin_ev"
- ",h_coin_ev"
- ",ev_sig"
- ",melt_serial_id"
- ",denominations_serial"
- " FROM refresh_revealed_coins"
- " WHERE rrc_serial > $1"
- " ORDER BY rrc_serial ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_refresh_transfer_keys",
- "SELECT"
- " rtc_serial AS serial"
- ",transfer_pub"
- ",transfer_privs"
- ",melt_serial_id"
- " FROM refresh_transfer_keys"
- " WHERE rtc_serial > $1"
- " ORDER BY rtc_serial ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_deposits",
- "SELECT"
- " deposit_serial_id AS serial"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",wallet_timestamp"
- ",exchange_timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",merchant_pub"
- ",h_contract_terms"
- ",h_wire"
- ",coin_sig"
- ",wire"
- ",tiny"
- ",done"
- ",known_coin_id"
- " FROM deposits"
- " WHERE deposit_serial_id > $1"
- " ORDER BY deposit_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_refunds",
- "SELECT"
- " refund_serial_id AS serial"
- ",merchant_sig"
- ",rtransaction_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",deposit_serial_id"
- " FROM refunds"
- " WHERE refund_serial_id > $1"
- " ORDER BY refund_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_wire_out",
- "SELECT"
- " wireout_uuid AS serial"
- ",execution_date"
- ",wtid_raw"
- ",wire_target"
- ",exchange_account_section"
- ",amount_val"
- ",amount_frac"
- " FROM wire_out"
- " WHERE wireout_uuid > $1"
- " ORDER BY wireout_uuid ASC;",
- 1),
- GNUNET_PQ_make_prepare (
- "select_above_serial_by_table_aggregation_tracking",
- "SELECT"
- " aggregation_serial_id AS serial"
- ",deposit_serial_id"
- ",wtid_raw"
- " FROM aggregation_tracking"
- " WHERE aggregation_serial_id > $1"
- " ORDER BY aggregation_serial_id ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_wire_fee",
- "SELECT"
- " wire_fee_serial AS serial"
- ",wire_method"
- ",start_date"
- ",end_date"
- ",wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",master_sig"
- " FROM wire_fee"
- " WHERE wire_fee_serial > $1"
- " ORDER BY wire_fee_serial ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_recoup",
- "SELECT"
- " recoup_uuid AS serial"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",known_coin_id"
- ",reserve_out_serial_id"
- " FROM recoup"
- " WHERE recoup_uuid > $1"
- " ORDER BY recoup_uuid ASC;",
- 1),
- GNUNET_PQ_make_prepare ("select_above_serial_by_table_recoup_refresh",
- "SELECT"
- " recoup_refresh_uuid AS serial"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",known_coin_id"
- ",rrc_serial"
- " FROM recoup_refresh"
- " WHERE recoup_refresh_uuid > $1"
- " ORDER BY recoup_refresh_uuid ASC;",
- 1),
- /* For postgres_insert_records_by_table */
- GNUNET_PQ_make_prepare ("insert_into_table_denominations",
- "INSERT INTO denominations"
- "(denominations_serial"
- ",denom_pub_hash"
- ",denom_pub"
- ",master_sig"
- ",valid_from"
- ",expire_withdraw"
- ",expire_deposit"
- ",expire_legal"
- ",coin_val"
- ",coin_frac"
- ",fee_withdraw_val"
- ",fee_withdraw_frac"
- ",fee_deposit_val"
- ",fee_deposit_frac"
- ",fee_refresh_val"
- ",fee_refresh_frac"
- ",fee_refund_val"
- ",fee_refund_frac"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
- " $11, $12, $13, $14, $15, $16, $17, $18);",
- 18),
- GNUNET_PQ_make_prepare ("insert_into_table_denomination_revocations",
- "INSERT INTO denomination_revocations"
- "(denom_revocations_serial_id"
- ",master_sig"
- ",denominations_serial"
- ") VALUES "
- "($1, $2, $3);",
- 3),
- GNUNET_PQ_make_prepare ("insert_into_table_reserves",
- "INSERT INTO reserves"
- "(reserve_uuid"
- ",reserve_pub"
- ",account_details"
- ",current_balance_val"
- ",current_balance_frac"
- ",expiration_date"
- ",gc_date"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7);",
- 7),
- GNUNET_PQ_make_prepare ("insert_into_table_reserves_in",
- "INSERT INTO reserves_in"
- "(reserve_in_serial_id"
- ",wire_reference"
- ",credit_val"
- ",credit_frac"
- ",sender_account_details"
- ",exchange_account_section"
- ",execution_date"
- ",reserve_uuid"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8);",
- 8),
- GNUNET_PQ_make_prepare ("insert_into_table_reserves_close",
- "INSERT INTO reserves_close"
- "(close_uuid"
- ",execution_date"
- ",wtid"
- ",receiver_account"
- ",amount_val"
- ",amount_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",reserve_uuid"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9);",
- 9),
- GNUNET_PQ_make_prepare ("insert_into_table_reserves_out",
- "INSERT INTO reserves_out"
- "(reserve_out_serial_id"
- ",h_blind_ev"
- ",denom_sig"
- ",reserve_sig"
- ",execution_date"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",reserve_uuid"
- ",denominations_serial"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9);",
- 9),
- GNUNET_PQ_make_prepare ("insert_into_table_auditors",
- "INSERT INTO auditors"
- "(auditor_uuid"
- ",auditor_pub"
- ",auditor_name"
- ",auditor_url"
- ",is_active"
- ",last_change"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6);",
- 6),
- GNUNET_PQ_make_prepare ("insert_into_table_auditor_denom_sigs",
- "INSERT INTO auditor_denom_sigs"
- "(auditor_denom_serial"
- ",auditor_uuid"
- ",denominations_serial"
- ",auditor_sig"
- ") VALUES "
- "($1, $2, $3, $4);",
- 4),
- GNUNET_PQ_make_prepare ("insert_into_table_exchange_sign_keys",
- "INSERT INTO exchange_sign_keys"
- "(esk_serial"
- ",exchange_pub"
- ",master_sig"
- ",valid_from"
- ",expire_sign"
- ",expire_legal"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6);",
- 6),
- GNUNET_PQ_make_prepare ("insert_into_table_signkey_revocations",
- "INSERT INTO signkey_revocations"
- "(signkey_revocations_serial_id"
- ",esk_serial"
- ",master_sig"
- ") VALUES "
- "($1, $2, $3);",
- 3),
- GNUNET_PQ_make_prepare ("insert_into_table_known_coins",
- "INSERT INTO known_coins"
- "(known_coin_id"
- ",coin_pub"
- ",denom_sig"
- ",denominations_serial"
- ") VALUES "
- "($1, $2, $3, $4);",
- 4),
- GNUNET_PQ_make_prepare ("insert_into_table_refresh_commitments",
- "INSERT INTO refresh_commitments"
- "(melt_serial_id"
- ",rc"
- ",old_coin_sig"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",noreveal_index"
- ",old_known_coin_id"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7);",
- 7),
- GNUNET_PQ_make_prepare ("insert_into_table_refresh_revealed_coins",
- "INSERT INTO refresh_revealed_coins"
- "(rrc_serial"
- ",freshcoin_index"
- ",link_sig"
- ",coin_ev"
- ",h_coin_ev"
- ",ev_sig"
- ",denominations_serial"
- ",melt_serial_id"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8);",
- 8),
- GNUNET_PQ_make_prepare ("insert_into_table_refresh_transfer_keys",
- "INSERT INTO refresh_transfer_keys"
- "(rtc_serial"
- ",transfer_pub"
- ",transfer_privs"
- ",melt_serial_id"
- ") VALUES "
- "($1, $2, $3, $4);",
- 4),
- GNUNET_PQ_make_prepare ("insert_into_table_deposits",
- "INSERT INTO deposits"
- "(deposit_serial_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",wallet_timestamp"
- ",exchange_timestamp"
- ",refund_deadline"
- ",wire_deadline"
- ",merchant_pub"
- ",h_contract_terms"
- ",h_wire"
- ",coin_sig"
- ",wire"
- ",tiny"
- ",done"
- ",known_coin_id"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,"
- " $11, $12, $13, $14, $15);",
- 15),
- GNUNET_PQ_make_prepare ("insert_into_table_refunds",
- "INSERT INTO refunds"
- "(refund_serial_id"
- ",merchant_sig"
- ",rtransaction_id"
- ",amount_with_fee_val"
- ",amount_with_fee_frac"
- ",deposit_serial_id"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6);",
- 6),
- GNUNET_PQ_make_prepare ("insert_into_table_wire_out",
- "INSERT INTO wire_out"
- "(wireout_uuid"
- ",execution_date"
- ",wtid_raw"
- ",wire_target"
- ",exchange_account_section"
- ",amount_val"
- ",amount_frac"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7);",
- 7),
- GNUNET_PQ_make_prepare ("insert_into_table_aggregation_tracking",
- "INSERT INTO aggregation_tracking"
- "(aggregation_serial_id"
- ",deposit_serial_id"
- ",wtid_raw"
- ") VALUES "
- "($1, $2, $3);",
- 3),
- GNUNET_PQ_make_prepare ("insert_into_table_wire_fee",
- "INSERT INTO wire_fee"
- "(wire_fee_serial"
- ",wire_method"
- ",start_date"
- ",end_date"
- ",wire_fee_val"
- ",wire_fee_frac"
- ",closing_fee_val"
- ",closing_fee_frac"
- ",master_sig"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8, $9);",
- 9),
- GNUNET_PQ_make_prepare ("insert_into_table_recoup",
- "INSERT INTO recoup"
- "(recoup_uuid"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",known_coin_id"
- ",reserve_out_serial_id"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8);",
- 8),
- GNUNET_PQ_make_prepare ("insert_into_table_recoup_refresh",
- "INSERT INTO recoup_refresh"
- "(recoup_refresh_uuid"
- ",coin_sig"
- ",coin_blind"
- ",amount_val"
- ",amount_frac"
- ",timestamp"
- ",known_coin_id"
- ",rrc_serial"
- ") VALUES "
- "($1, $2, $3, $4, $5, $6, $7, $8);",
- 8),
-
- /* Used in #postgres_begin_shard() */
- GNUNET_PQ_make_prepare ("get_open_shard",
- "SELECT"
- " start_row"
- ",end_row"
- " FROM work_shards"
- " WHERE job_name=$1"
- " AND last_attempt<$2"
- " AND completed=FALSE"
- " ORDER BY last_attempt ASC"
- " LIMIT 1;",
- 2),
- /* Used in #postgres_begin_revolving_shard() */
- GNUNET_PQ_make_prepare ("get_open_revolving_shard",
- "SELECT"
- " start_row"
- ",end_row"
- " FROM revolving_work_shards"
- " WHERE job_name=$1"
- " AND active=FALSE"
- " ORDER BY last_attempt ASC"
- " LIMIT 1;",
- 2),
- /* Used in #postgres_begin_shard() */
- GNUNET_PQ_make_prepare ("reclaim_shard",
- "UPDATE work_shards"
- " SET last_attempt=$2"
- " WHERE job_name=$1"
- " AND start_row=$3"
- " AND end_row=$4",
- 4),
- /* Used in #postgres_begin_revolving_shard() */
- GNUNET_PQ_make_prepare ("reclaim_revolving_shard",
- "UPDATE revolving_work_shards"
- " SET last_attempt=$2"
- " ,active=TRUE"
- " WHERE job_name=$1"
- " AND start_row=$3"
- " AND end_row=$4",
- 4),
- /* Used in #postgres_begin_shard() */
- GNUNET_PQ_make_prepare ("get_last_shard",
- "SELECT"
- " end_row"
- " FROM work_shards"
- " WHERE job_name=$1"
- " ORDER BY end_row DESC"
- " LIMIT 1;",
- 1),
- /* Used in #postgres_begin_revolving_shard() */
- GNUNET_PQ_make_prepare ("get_last_revolving_shard",
- "SELECT"
- " end_row"
- " FROM revolving_work_shards"
- " WHERE job_name=$1"
- " ORDER BY end_row DESC"
- " LIMIT 1;",
- 1),
- /* Used in #postgres_begin_shard() */
- GNUNET_PQ_make_prepare ("claim_next_shard",
- "INSERT INTO work_shards"
- "(job_name"
- ",last_attempt"
- ",start_row"
- ",end_row"
- ") VALUES "
- "($1, $2, $3, $4);",
- 4),
- /* Used in #postgres_claim_revolving_shard() */
- GNUNET_PQ_make_prepare ("create_revolving_shard",
- "INSERT INTO revolving_work_shards"
- "(job_name"
- ",last_attempt"
- ",start_row"
- ",end_row"
- ",active"
- ") VALUES "
- "($1, $2, $3, $4, TRUE);",
- 4),
- /* Used in #postgres_complete_shard() */
- GNUNET_PQ_make_prepare ("complete_shard",
- "UPDATE work_shards"
- " SET completed=TRUE"
- " WHERE job_name=$1"
- " AND start_row=$2"
- " AND end_row=$3",
- 3),
- /* Used in #postgres_complete_shard() */
- GNUNET_PQ_make_prepare ("release_revolving_shard",
- "UPDATE revolving_work_shards"
- " SET active=FALSE"
- " WHERE job_name=$1"
- " AND start_row=$2"
- " AND end_row=$3",
- 3),
- /* Used in #postgres_delete_revolving_shards() */
- GNUNET_PQ_make_prepare ("delete_revolving_shards",
- "DELETE FROM revolving_work_shards",
- 0),
- GNUNET_PQ_PREPARED_STATEMENT_END
- };
-
- ret = GNUNET_PQ_prepare_statements (pg->conn,
- ps);
- if (GNUNET_OK != ret)
- return ret;
- pg->init = true;
- return GNUNET_OK;
-}
-
-
-/**
* Connect to the database if the connection does not exist yet.
*
* @param pg the plugin-specific state
- * @param skip_prepare true if we should skip prepared statement setup
* @return #GNUNET_OK on success
*/
-static enum GNUNET_GenericReturnValue
-internal_setup (struct PostgresClosure *pg,
- bool skip_prepare)
+enum GNUNET_GenericReturnValue
+TEH_PG_internal_setup (struct PostgresClosure *pg)
{
if (NULL == pg->conn)
{
@@ -2584,10 +272,22 @@ internal_setup (struct PostgresClosure *pg,
"SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
+ GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+ /* Mergejoin causes issues, see Postgres #18380 */
+ GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"),
GNUNET_PQ_EXECUTE_STATEMENT_END
};
#else
- struct GNUNET_PQ_ExecuteStatement *es = NULL;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_try_execute (
+ "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;"),
+ GNUNET_PQ_make_try_execute ("SET enable_sort=OFF;"),
+ GNUNET_PQ_make_try_execute ("SET enable_seqscan=OFF;"),
+ /* Mergejoin causes issues, see Postgres #18380 */
+ GNUNET_PQ_make_try_execute ("SET enable_mergejoin=OFF;"),
+ GNUNET_PQ_make_try_execute ("SET search_path TO exchange;"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
#endif
struct GNUNET_PQ_Context *db_conn;
@@ -2598,8615 +298,17 @@ internal_setup (struct PostgresClosure *pg,
NULL);
if (NULL == db_conn)
return GNUNET_SYSERR;
+
+ pg->prep_gen++;
pg->conn = db_conn;
}
if (NULL == pg->transaction_name)
GNUNET_PQ_reconnect_if_down (pg->conn);
- if (pg->init)
- return GNUNET_OK;
- if (skip_prepare)
- return GNUNET_OK;
- return prepare_statements (pg);
-}
-
-
-/**
- * Do a pre-flight check that we are not in an uncommitted transaction.
- * If we are, try to commit the previous transaction and output a warning.
- * Does not return anything, as we will continue regardless of the outcome.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @return #GNUNET_OK if everything is fine
- * #GNUNET_NO if a transaction was rolled back
- * #GNUNET_SYSERR on hard errors
- */
-static enum GNUNET_GenericReturnValue
-postgres_preflight (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("ROLLBACK"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- if (! pg->init)
- {
- if (GNUNET_OK !=
- internal_setup (pg,
- false))
- return GNUNET_SYSERR;
- }
- if (NULL == pg->transaction_name)
- return GNUNET_OK; /* all good */
- if (GNUNET_OK ==
- GNUNET_PQ_exec_statements (pg->conn,
- es))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "BUG: Preflight check rolled back transaction `%s'!\n",
- pg->transaction_name);
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "BUG: Preflight check failed to rollback transaction `%s'!\n",
- pg->transaction_name);
- }
- pg->transaction_name = NULL;
- return GNUNET_NO;
-}
-
-
-/**
- * Start a transaction.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param name unique name identifying the transaction (for debugging)
- * must point to a constant
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-postgres_start (void *cls,
- const char *name)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- if (GNUNET_SYSERR ==
- postgres_preflight (pg))
- return GNUNET_SYSERR;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Starting transaction named %s on %p\n",
- name,
- pg->conn);
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (pg->conn,
- es))
- {
- TALER_LOG_ERROR ("Failed to start transaction\n");
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- pg->transaction_name = name;
- return GNUNET_OK;
-}
-
-
-/**
- * Start a READ COMMITTED transaction.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param name unique name identifying the transaction (for debugging)
- * must point to a constant
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-postgres_start_read_committed (void *cls,
- const char *name)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ COMMITTED"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- if (GNUNET_SYSERR ==
- postgres_preflight (pg))
- return GNUNET_SYSERR;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Starting transaction named %s on %p\n",
- name,
- pg->conn);
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (pg->conn,
- es))
- {
- TALER_LOG_ERROR ("Failed to start transaction\n");
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- pg->transaction_name = name;
return GNUNET_OK;
}
/**
- * Roll back the current transaction of a database connection.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- */
-static void
-postgres_rollback (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("ROLLBACK"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Rolling back transaction on %p\n",
- pg->conn);
- GNUNET_break (GNUNET_OK ==
- GNUNET_PQ_exec_statements (pg->conn,
- es));
- pg->transaction_name = NULL;
-}
-
-
-/**
- * Commit the current transaction of a database connection.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @return final transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_commit (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "do_commit",
- params);
- pg->transaction_name = NULL;
- return qs;
-}
-
-
-/**
- * Register callback to be invoked on events of type @a es.
- *
- * @param cls database context to use
- * @param timeout how long until to generate a timeout event
- * @param es specification of the event to listen for
- * @param cb function to call when the event happens, possibly
- * multiple times (until cancel is invoked)
- * @param cb_cls closure for @a cb
- * @return handle useful to cancel the listener
- */
-static struct GNUNET_DB_EventHandler *
-postgres_event_listen (void *cls,
- struct GNUNET_TIME_Relative timeout,
- const struct GNUNET_DB_EventHeaderP *es,
- GNUNET_DB_EventCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
-
- return GNUNET_PQ_event_listen (pg->conn,
- es,
- timeout,
- cb,
- cb_cls);
-}
-
-
-/**
- * Stop notifications.
- *
- * @param cls the plugin's `struct PostgresClosure`
- * @param eh handle to unregister.
- */
-static void
-postgres_event_listen_cancel (void *cls,
- struct GNUNET_DB_EventHandler *eh)
-{
- (void) cls;
- GNUNET_PQ_event_listen_cancel (eh);
-}
-
-
-/**
- * Notify all that listen on @a es of an event.
- *
- * @param cls database context to use
- * @param es specification of the event to generate
- * @param extra additional event data provided
- * @param extra_size number of bytes in @a extra
- */
-static void
-postgres_event_notify (void *cls,
- const struct GNUNET_DB_EventHeaderP *es,
- const void *extra,
- size_t extra_size)
-{
- struct PostgresClosure *pg = cls;
-
- GNUNET_PQ_event_notify (pg->conn,
- es,
- extra,
- extra_size);
-}
-
-
-/**
- * Insert a denomination key's public information into the database for
- * reference by auditors and other consistency checks.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param denom_pub the public key used for signing coins of this denomination
- * @param issue issuing information with value, fees and other info about the coin
- * @return status of the query
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_denomination_info (
- void *cls,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&issue->properties.denom_hash),
- GNUNET_PQ_query_param_rsa_public_key (denom_pub->rsa_public_key),
- GNUNET_PQ_query_param_auto_from_type (&issue->signature),
- TALER_PQ_query_param_absolute_time_nbo (&issue->properties.start),
- TALER_PQ_query_param_absolute_time_nbo (&issue->properties.expire_withdraw),
- TALER_PQ_query_param_absolute_time_nbo (&issue->properties.expire_deposit),
- TALER_PQ_query_param_absolute_time_nbo (&issue->properties.expire_legal),
- TALER_PQ_query_param_amount_nbo (&issue->properties.value),
- TALER_PQ_query_param_amount_nbo (&issue->properties.fee_withdraw),
- TALER_PQ_query_param_amount_nbo (&issue->properties.fee_deposit),
- TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refresh),
- TALER_PQ_query_param_amount_nbo (&issue->properties.fee_refund),
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_assert (0 != GNUNET_TIME_absolute_ntoh (
- issue->properties.start).abs_value_us);
- GNUNET_assert (0 != GNUNET_TIME_absolute_ntoh (
- issue->properties.expire_withdraw).abs_value_us);
- GNUNET_assert (0 != GNUNET_TIME_absolute_ntoh (
- issue->properties.expire_deposit).abs_value_us);
- GNUNET_assert (0 != GNUNET_TIME_absolute_ntoh (
- issue->properties.expire_legal).abs_value_us);
- /* check fees match coin currency */
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency_nbo (&issue->properties.value,
- &issue->properties.fee_withdraw));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency_nbo (&issue->properties.value,
- &issue->properties.fee_deposit));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency_nbo (&issue->properties.value,
- &issue->properties.fee_refresh));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency_nbo (&issue->properties.value,
- &issue->properties.fee_refund));
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "denomination_insert",
- params);
-}
-
-
-/**
- * Fetch information about a denomination key.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param denom_pub_hash hash of the public key used for signing coins of this denomination
- * @param[out] issue set to issue information with value, fees and other info about the coin
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_denomination_info (
- void *cls,
- const struct GNUNET_HashCode *denom_pub_hash,
- struct TALER_EXCHANGEDB_DenominationKeyInformationP *issue)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &issue->signature),
- TALER_PQ_result_spec_absolute_time_nbo ("valid_from",
- &issue->properties.start),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_withdraw",
- &issue->properties.expire_withdraw),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_deposit",
- &issue->properties.expire_deposit),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_legal",
- &issue->properties.expire_legal),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("coin",
- &issue->properties.value),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_withdraw",
- &issue->properties.fee_withdraw),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_deposit",
- &issue->properties.fee_deposit),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refresh",
- &issue->properties.fee_refresh),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refund",
- &issue->properties.fee_refund),
- GNUNET_PQ_result_spec_end
- };
-
- memset (&issue->properties.master,
- 0,
- sizeof (issue->properties.master));
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "denomination_get",
- params,
- rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- return qs;
- issue->properties.purpose.size = htonl (sizeof (struct
- TALER_DenominationKeyValidityPS));
- issue->properties.purpose.purpose = htonl (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY);
- issue->properties.denom_hash = *denom_pub_hash;
- return qs;
-}
-
-
-/**
- * Closure for #domination_cb_helper()
- */
-struct DenomIteratorContext
-{
- /**
- * Function to call with the results.
- */
- TALER_EXCHANGEDB_DenominationCallback cb;
-
- /**
- * Closure to pass to @e cb
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-};
-
-
-/**
- * Helper function for #postgres_iterate_denomination_info().
- * Calls the callback with each denomination key.
- *
- * @param cls a `struct DenomIteratorContext`
- * @param result db results
- * @param num_results number of results in @a result
- */
-static void
-domination_cb_helper (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct DenomIteratorContext *dic = cls;
- struct PostgresClosure *pg = dic->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue;
- struct TALER_DenominationPublicKey denom_pub;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &issue.signature),
- TALER_PQ_result_spec_absolute_time_nbo ("valid_from",
- &issue.properties.start),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_withdraw",
- &issue.properties.expire_withdraw),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_deposit",
- &issue.properties.expire_deposit),
- TALER_PQ_result_spec_absolute_time_nbo ("expire_legal",
- &issue.properties.expire_legal),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("coin",
- &issue.properties.value),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_withdraw",
- &issue.properties.fee_withdraw),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_deposit",
- &issue.properties.fee_deposit),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refresh",
- &issue.properties.fee_refresh),
- TALER_PQ_RESULT_SPEC_AMOUNT_NBO ("fee_refund",
- &issue.properties.fee_refund),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_end
- };
-
- memset (&issue.properties.master,
- 0,
- sizeof (issue.properties.master));
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- return;
- }
- issue.properties.purpose.size
- = htonl (sizeof (struct TALER_DenominationKeyValidityPS));
- issue.properties.purpose.purpose
- = htonl (TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY);
- GNUNET_CRYPTO_rsa_public_key_hash (denom_pub.rsa_public_key,
- &issue.properties.denom_hash);
- dic->cb (dic->cb_cls,
- &denom_pub,
- &issue);
- GNUNET_CRYPTO_rsa_public_key_free (denom_pub.rsa_public_key);
- }
-}
-
-
-/**
- * Fetch information about all known denomination keys.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param cb function to call on each denomination key
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_iterate_denomination_info (void *cls,
- TALER_EXCHANGEDB_DenominationCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- struct DenomIteratorContext dic = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg
- };
-
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "denomination_iterate",
- params,
- &domination_cb_helper,
- &dic);
-}
-
-
-/**
- * Closure for #dominations_cb_helper()
- */
-struct DenomsIteratorContext
-{
- /**
- * Function to call with the results.
- */
- TALER_EXCHANGEDB_DenominationsCallback cb;
-
- /**
- * Closure to pass to @e cb
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-};
-
-
-/**
- * Helper function for #postgres_iterate_denominations().
- * Calls the callback with each denomination key.
- *
- * @param cls a `struct DenomsIteratorContext`
- * @param result db results
- * @param num_results number of results in @a result
- */
-static void
-dominations_cb_helper (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct DenomsIteratorContext *dic = cls;
- struct PostgresClosure *pg = dic->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
- struct TALER_DenominationPublicKey denom_pub;
- struct TALER_MasterSignatureP master_sig;
- struct GNUNET_HashCode h_denom_pub;
- uint8_t revoked;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &master_sig),
- GNUNET_PQ_result_spec_auto_from_type ("revoked",
- &revoked),
- TALER_PQ_result_spec_absolute_time ("valid_from",
- &meta.start),
- TALER_PQ_result_spec_absolute_time ("expire_withdraw",
- &meta.expire_withdraw),
- TALER_PQ_result_spec_absolute_time ("expire_deposit",
- &meta.expire_deposit),
- TALER_PQ_result_spec_absolute_time ("expire_legal",
- &meta.expire_legal),
- TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
- &meta.value),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
- &meta.fee_withdraw),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- &meta.fee_deposit),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
- &meta.fee_refresh),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
- &meta.fee_refund),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- return;
- }
- GNUNET_CRYPTO_rsa_public_key_hash (denom_pub.rsa_public_key,
- &h_denom_pub);
- dic->cb (dic->cb_cls,
- &denom_pub,
- &h_denom_pub,
- &meta,
- &master_sig,
- (0 != revoked));
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
-* Function called to invoke @a cb on every known denomination key (revoked
-* and non-revoked) that has been signed by the master key. Runs in its own
-* read-only transaction.
-*
-*
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param cb function to call on each denomination key
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_iterate_denominations (void *cls,
- TALER_EXCHANGEDB_DenominationsCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- struct DenomsIteratorContext dic = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg
- };
-
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "select_denominations",
- params,
- &dominations_cb_helper,
- &dic);
-}
-
-
-/**
- * Closure for #signkeys_cb_helper()
- */
-struct SignkeysIteratorContext
-{
- /**
- * Function to call with the results.
- */
- TALER_EXCHANGEDB_ActiveSignkeysCallback cb;
-
- /**
- * Closure to pass to @e cb
- */
- void *cb_cls;
-
-};
-
-
-/**
- * Helper function for #postgres_iterate_active_signkeys().
- * Calls the callback with each signkey.
- *
- * @param cls a `struct SignkeysIteratorContext`
- * @param result db results
- * @param num_results number of results in @a result
- */
-static void
-signkeys_cb_helper (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct SignkeysIteratorContext *dic = cls;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_SignkeyMetaData meta;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_MasterSignatureP master_sig;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &master_sig),
- GNUNET_PQ_result_spec_auto_from_type ("exchange_pub",
- &exchange_pub),
- TALER_PQ_result_spec_absolute_time ("valid_from",
- &meta.start),
- TALER_PQ_result_spec_absolute_time ("expire_sign",
- &meta.expire_sign),
- TALER_PQ_result_spec_absolute_time ("expire_legal",
- &meta.expire_legal),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- return;
- }
- dic->cb (dic->cb_cls,
- &exchange_pub,
- &meta,
- &master_sig);
- }
-}
-
-
-/**
- * Function called to invoke @a cb on every non-revoked exchange signing key
- * that has been signed by the master key. Revoked and (for signing!)
- * expired keys are skipped. Runs in its own read-only transaction.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param cb function to call on each signing key
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_iterate_active_signkeys (void *cls,
- TALER_EXCHANGEDB_ActiveSignkeysCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_end
- };
- struct SignkeysIteratorContext dic = {
- .cb = cb,
- .cb_cls = cb_cls,
- };
-
- now = GNUNET_TIME_absolute_get ();
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "select_signkeys",
- params,
- &signkeys_cb_helper,
- &dic);
-}
-
-
-/**
- * Closure for #auditors_cb_helper()
- */
-struct AuditorsIteratorContext
-{
- /**
- * Function to call with the results.
- */
- TALER_EXCHANGEDB_AuditorsCallback cb;
-
- /**
- * Closure to pass to @e cb
- */
- void *cb_cls;
-
-};
-
-
-/**
- * Helper function for #postgres_iterate_active_auditors().
- * Calls the callback with each auditor.
- *
- * @param cls a `struct SignkeysIteratorContext`
- * @param result db results
- * @param num_results number of results in @a result
- */
-static void
-auditors_cb_helper (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct AuditorsIteratorContext *dic = cls;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_AuditorPublicKeyP auditor_pub;
- char *auditor_url;
- char *auditor_name;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("auditor_pub",
- &auditor_pub),
- GNUNET_PQ_result_spec_string ("auditor_url",
- &auditor_url),
- GNUNET_PQ_result_spec_string ("auditor_name",
- &auditor_name),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- return;
- }
- dic->cb (dic->cb_cls,
- &auditor_pub,
- auditor_url,
- auditor_name);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called to invoke @a cb on every active auditor. Disabled
- * auditors are skipped. Runs in its own read-only transaction.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param cb function to call on each active auditor
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_iterate_active_auditors (void *cls,
- TALER_EXCHANGEDB_AuditorsCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- struct AuditorsIteratorContext dic = {
- .cb = cb,
- .cb_cls = cb_cls,
- };
-
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "select_auditors",
- params,
- &auditors_cb_helper,
- &dic);
-}
-
-
-/**
- * Closure for #auditor_denoms_cb_helper()
- */
-struct AuditorDenomsIteratorContext
-{
- /**
- * Function to call with the results.
- */
- TALER_EXCHANGEDB_AuditorDenominationsCallback cb;
-
- /**
- * Closure to pass to @e cb
- */
- void *cb_cls;
-};
-
-
-/**
- * Helper function for #postgres_iterate_auditor_denominations().
- * Calls the callback with each auditor and denomination pair.
- *
- * @param cls a `struct AuditorDenomsIteratorContext`
- * @param result db results
- * @param num_results number of results in @a result
- */
-static void
-auditor_denoms_cb_helper (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct AuditorDenomsIteratorContext *dic = cls;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_AuditorPublicKeyP auditor_pub;
- struct GNUNET_HashCode h_denom_pub;
- struct TALER_AuditorSignatureP auditor_sig;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("auditor_pub",
- &auditor_pub),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &h_denom_pub),
- GNUNET_PQ_result_spec_auto_from_type ("auditor_sig",
- &auditor_sig),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- return;
- }
- dic->cb (dic->cb_cls,
- &auditor_pub,
- &h_denom_pub,
- &auditor_sig);
- }
-}
-
-
-/**
- * Function called to invoke @a cb on every denomination with an active
- * auditor. Disabled auditors and denominations without auditor are
- * skipped. Runs in its own read-only transaction.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param cb function to call on each active auditor
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_iterate_auditor_denominations (
- void *cls,
- TALER_EXCHANGEDB_AuditorDenominationsCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- struct AuditorDenomsIteratorContext dic = {
- .cb = cb,
- .cb_cls = cb_cls,
- };
-
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "select_auditor_denoms",
- params,
- &auditor_denoms_cb_helper,
- &dic);
-}
-
-
-/**
- * Get the summary of a reserve.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param[in,out] reserve the reserve data. The public key of the reserve should be
- * set in this structure; it is used to query the database. The balance
- * and expiration are then filled accordingly.
- * @param[out] kyc set to the KYC status of the reserve
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_reserves_get (void *cls,
- struct TALER_EXCHANGEDB_Reserve *reserve,
- struct TALER_EXCHANGEDB_KycStatus *kyc)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
- GNUNET_PQ_query_param_end
- };
- uint8_t ok8;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
- &reserve->balance),
- TALER_PQ_result_spec_absolute_time ("expiration_date",
- &reserve->expiry),
- TALER_PQ_result_spec_absolute_time ("gc_date",
- &reserve->gc),
- GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
- &kyc->payment_target_uuid),
- GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
- &ok8),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "reserves_get_with_kyc",
- params,
- rs);
- kyc->type = TALER_EXCHANGEDB_KYC_WITHDRAW;
- kyc->ok = (0 != ok8);
- return qs;
-}
-
-
-/**
- * Set the KYC status to "OK" for a bank account.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param payment_target_uuid which account has been checked
- * @param ... possibly additional data to persist (TODO)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_set_kyc_ok (void *cls,
- uint64_t payment_target_uuid,
- ...)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&payment_target_uuid),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "set_kyc_ok",
- params);
-}
-
-
-/**
- * Get the KYC status for a bank account.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param payto_uri payto:// URI that identifies the bank account
- * @param[out] kyc set to the KYC status of the reserve
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_kyc_status (void *cls,
- const char *payto_uri,
- struct TALER_EXCHANGEDB_KycStatus *kyc)
-{
-#if FIXME_DD23
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (payto_uri),
- GNUNET_PQ_query_param_end
- };
-#endif
- uint8_t ok8;
-#if FIXME_DD23
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
- &kyc->payment_target_uuid),
- GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
- &ok8),
- GNUNET_PQ_result_spec_end
- };
-#endif
- enum GNUNET_DB_QueryStatus qs;
-
-#if FIXME_DD23
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_kyc_status",
- params,
- rs);
-#else
- qs = 1;
- ok8 = 0;
- kyc->payment_target_uuid = 0;
-#endif
- kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
- kyc->ok = (0 != ok8);
- return qs;
-}
-
-
-/**
- * Get the @a kyc status and @a h_payto by UUID.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param payment_target_uuid which account to get the KYC status for
- * @param[out] h_payto set to the hash of the account's payto URI (unsalted)
- * @param[out] kyc set to the KYC status of the account
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_kyc_status (void *cls,
- uint64_t payment_target_uuid,
- struct GNUNET_HashCode *h_payto,
- struct TALER_EXCHANGEDB_KycStatus *kyc)
-{
-#if FIXME_DD23
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (payment_target_uuid),
- GNUNET_PQ_query_param_end
- };
-#endif
- uint8_t ok8;
-#if FIXME_DD23
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_payto",
- h_payto),
- GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
- &ok8),
- GNUNET_PQ_result_spec_end
- };
-#endif
- enum GNUNET_DB_QueryStatus qs;
-
-#if FIXME_DD23
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_kyc_status",
- params,
- rs);
-#else
- qs = 1;
- ok8 = 0;
-#endif
- kyc->type = TALER_EXCHANGEDB_KYC_UNKNOWN;
- kyc->ok = (0 != ok8);
- kyc->payment_target_uuid = payment_target_uuid;
- return qs;
-}
-
-
-/**
- * Get the KYC status for a wallet. If the status is unknown,
- * inserts a new status record (hence INsertSELECT).
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param reserve_pub public key of the wallet
- * @param[out] kyc set to the KYC status of the wallet
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_inselect_wallet_kyc_status (
- void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct TALER_EXCHANGEDB_KycStatus *kyc)
-{
-#if FIXME_DD23
- struct PostgresClosure *pg = cls;
- /* FIXME: maybe prepared statement will take
- a payto:// URI instead of the reserve public key?
- => figure out once DB schema is stable! */
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- reserve_pub),
- GNUNET_PQ_query_param_end
- };
-#endif
- uint8_t ok8;
-#if FIXME_DD23
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
- &kyc->payment_target_uuid),
- GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
- &ok8),
- GNUNET_PQ_result_spec_end
- };
-#endif
- enum GNUNET_DB_QueryStatus qs;
-
-#if FIXME_DD23
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "inselect_wallet_kyc_status",
- params,
- rs);
-#else
- qs = 1;
- ok8 = 0;
- kyc->payment_target_uuid = 0;
-#endif
- kyc->type = TALER_EXCHANGEDB_KYC_BALANCE;
- kyc->ok = (0 != ok8);
- return qs;
-}
-
-
-/**
- * Get the summary of a reserve.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param[in,out] reserve the reserve data. The public key of the reserve should be
- * set in this structure; it is used to query the database. The balance
- * and expiration are then filled accordingly.
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-reserves_get_internal (void *cls,
- struct TALER_EXCHANGEDB_Reserve *reserve)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
- &reserve->balance),
- TALER_PQ_result_spec_absolute_time ("expiration_date",
- &reserve->expiry),
- TALER_PQ_result_spec_absolute_time ("gc_date",
- &reserve->gc),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "reserves_get",
- params,
- rs);
-}
-
-
-/**
- * Updates a reserve with the data from the given reserve structure.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param reserve the reserve structure whose data will be used to update the
- * corresponding record in the database.
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-reserves_update (void *cls,
- const struct TALER_EXCHANGEDB_Reserve *reserve)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_absolute_time (&reserve->expiry),
- TALER_PQ_query_param_absolute_time (&reserve->gc),
- TALER_PQ_query_param_amount (&reserve->balance),
- GNUNET_PQ_query_param_auto_from_type (&reserve->pub),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "reserve_update",
- params);
-}
-
-
-/**
- * Generate event notification for the reserve
- * change.
- *
- * @param pg plugin state
- * @param reserve_pub reserve to notfiy on
- */
-static void
-notify_on_reserve (struct PostgresClosure *pg,
- const struct TALER_ReservePublicKeyP *reserve_pub)
-{
- struct TALER_ReserveEventP rep = {
- .header.size = htons (sizeof (rep)),
- .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
- .reserve_pub = *reserve_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Notifying on reserve!\n");
- postgres_event_notify (pg,
- &rep.header,
- NULL,
- 0);
-}
-
-
-/**
- * Insert an incoming transaction into reserves. New reserves are also created
- * through this function.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param reserve_pub public key of the reserve
- * @param balance the amount that has to be added to the reserve
- * @param execution_time when was the amount added
- * @param sender_account_details account information for the sender (payto://-URL)
- * @param exchange_account_section name of the section in the configuration for the exchange's
- * account into which the deposit was made
- * @param wire_ref unique reference identifying the wire transfer
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_reserves_in_insert (void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_Amount *balance,
- struct GNUNET_TIME_Absolute execution_time,
- const char *sender_account_details,
- const char *exchange_account_section,
- uint64_t wire_ref)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs1;
- struct TALER_EXCHANGEDB_Reserve reserve;
- struct GNUNET_TIME_Absolute expiry;
- struct GNUNET_TIME_Absolute gc;
- struct GNUNET_TIME_Absolute now;
- uint64_t reserve_uuid;
-
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- reserve.pub = *reserve_pub;
- expiry = GNUNET_TIME_absolute_add (execution_time,
- pg->idle_reserve_expiration_time);
- (void) GNUNET_TIME_round_abs (&expiry);
- gc = GNUNET_TIME_absolute_add (now,
- pg->legal_reserve_expiration_time);
- (void) GNUNET_TIME_round_abs (&gc);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Creating reserve %s with expiration in %s\n",
- TALER_B2S (reserve_pub),
- GNUNET_STRINGS_relative_time_to_string (
- pg->idle_reserve_expiration_time,
- GNUNET_NO));
- /* Optimistically assume this is a new reserve, create balance for the first
- time; we do this before adding the actual transaction to "reserves_in",
- as for a new reserve it can't be a duplicate 'add' operation, and as
- the 'add' operation may need the reserve entry as a foreign key. */
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_string (sender_account_details),
- TALER_PQ_query_param_amount (balance),
- TALER_PQ_query_param_absolute_time (&expiry),
- TALER_PQ_query_param_absolute_time (&gc),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("reserve_uuid",
- &reserve_uuid),
- GNUNET_PQ_result_spec_end
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Reserve does not exist; creating a new one\n");
- /* Note: query uses 'on conflict do nothing' */
- qs1 = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "reserve_create",
- params,
- rs);
- if (qs1 < 0)
- return qs1;
- }
-
- /* Create new incoming transaction, "ON CONFLICT DO NOTHING"
- is again used to guard against duplicates. */
- {
- enum GNUNET_DB_QueryStatus qs2;
-
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs1)
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&reserve.pub),
- GNUNET_PQ_query_param_uint64 (&wire_ref),
- TALER_PQ_query_param_amount (balance),
- GNUNET_PQ_query_param_string (exchange_account_section),
- GNUNET_PQ_query_param_string (sender_account_details),
- TALER_PQ_query_param_absolute_time (&execution_time),
- GNUNET_PQ_query_param_end
- };
-
- qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "reserves_in_add_transaction",
- params);
- }
- else
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&reserve_uuid),
- GNUNET_PQ_query_param_uint64 (&wire_ref),
- TALER_PQ_query_param_amount (balance),
- GNUNET_PQ_query_param_string (exchange_account_section),
- GNUNET_PQ_query_param_string (sender_account_details),
- TALER_PQ_query_param_absolute_time (&execution_time),
- GNUNET_PQ_query_param_end
- };
-
- qs2 = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "reserves_in_add_by_uuid",
- params);
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs2)
- {
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs2);
- return qs2;
- }
- if (0 >= qs2)
- {
- if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs2) &&
- (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs1) )
- {
- GNUNET_break (0); /* should be impossible: reserve was fresh,
- but transaction already known */
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- /* Transaction was already known or error. We are finished. */
- return qs2;
- }
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs1)
- {
- notify_on_reserve (pg,
- reserve_pub);
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* new reserve, we are finished */
- }
-
- /* we were wrong with our optimistic assumption:
- reserve does exist, need to do an update instead */
- {
- enum GNUNET_DB_QueryStatus cs;
-
- cs = postgres_commit (pg);
- if (cs < 0)
- return cs;
- if (GNUNET_OK !=
- postgres_start (pg,
- "reserve-update-serializable"))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- {
- enum GNUNET_DB_QueryStatus reserve_exists;
-
- reserve_exists = reserves_get_internal (pg,
- &reserve);
- switch (reserve_exists)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return reserve_exists;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return reserve_exists;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* First we got a conflict, but then we cannot select? Very strange. */
- GNUNET_break (0);
- return GNUNET_DB_STATUS_SOFT_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* continued below */
- break;
- }
- }
-
- {
- struct TALER_EXCHANGEDB_Reserve updated_reserve;
- enum GNUNET_DB_QueryStatus qs3;
-
- /* If the reserve already existed, we need to still update the
- balance; we do this after checking for duplication, as
- otherwise we might have to actually pay the cost to roll this
- back for duplicate transactions; like this, we should virtually
- never actually have to rollback anything. */
- updated_reserve.pub = reserve.pub;
- if (0 >
- TALER_amount_add (&updated_reserve.balance,
- &reserve.balance,
- balance))
- {
- /* currency overflow or incompatible currency */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Attempt to deposit incompatible amount into reserve\n");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- updated_reserve.expiry = GNUNET_TIME_absolute_max (expiry,
- reserve.expiry);
- (void) GNUNET_TIME_round_abs (&updated_reserve.expiry);
- updated_reserve.gc = GNUNET_TIME_absolute_max (gc,
- reserve.gc);
- (void) GNUNET_TIME_round_abs (&updated_reserve.gc);
- qs3 = reserves_update (pg,
- &updated_reserve);
- switch (qs3)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return qs3;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return qs3;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* How can the UPDATE not work here? Very strange. */
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* continued below */
- break;
- }
- }
- notify_on_reserve (pg,
- reserve_pub);
- /* Go back to original transaction mode */
- {
- enum GNUNET_DB_QueryStatus cs;
-
- cs = postgres_commit (pg);
- if (cs < 0)
- return cs;
- if (GNUNET_OK !=
- postgres_start_read_committed (pg,
- "reserve-insert-continued"))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
-
-
-/**
- * Obtain the most recent @a wire_reference that was inserted via @e reserves_in_insert.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param exchange_account_name name of the section in the exchange's configuration
- * for the account that we are tracking here
- * @param[out] wire_reference set to unique reference identifying the wire transfer
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_latest_reserve_in_reference (
- void *cls,
- const char *exchange_account_name,
- uint64_t *wire_reference)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (exchange_account_name),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("wire_reference",
- wire_reference),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "reserves_in_get_latest_wire_reference",
- params,
- rs);
-}
-
-
-/**
- * Locate the response for a /reserve/withdraw request under the
- * key of the hash of the blinded message.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param h_blind hash of the blinded coin to be signed (will match
- * `h_coin_envelope` in the @a collectable to be returned)
- * @param collectable corresponding collectable coin (blind signature)
- * if a coin is found
- * @return statement execution status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_withdraw_info (
- void *cls,
- const struct GNUNET_HashCode *h_blind,
- struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (h_blind),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &collectable->denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &collectable->sig.rsa_signature),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
- &collectable->reserve_sig),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &collectable->reserve_pub),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &collectable->amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
- &collectable->withdraw_fee),
- GNUNET_PQ_result_spec_end
- };
-#if EXPLICIT_LOCKS
- struct GNUNET_PQ_QueryParam no_params[] = {
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- if (0 > (qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "lock_withdraw",
- no_params)))
- return qs;
-#endif
- collectable->h_coin_envelope = *h_blind;
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_withdraw_info",
- params,
- rs);
-}
-
-
-/**
- * Store collectable bit coin under the corresponding
- * hash of the blinded message.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param collectable corresponding collectable coin (blind signature)
- * if a coin is found
- * @return query execution status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_withdraw_info (
- void *cls,
- const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
-{
- struct PostgresClosure *pg = cls;
- struct TALER_EXCHANGEDB_Reserve reserve;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Absolute gc;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&collectable->h_coin_envelope),
- GNUNET_PQ_query_param_auto_from_type (&collectable->denom_pub_hash),
- GNUNET_PQ_query_param_rsa_signature (collectable->sig.rsa_signature),
- GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_pub),
- GNUNET_PQ_query_param_auto_from_type (&collectable->reserve_sig),
- TALER_PQ_query_param_absolute_time (&now),
- TALER_PQ_query_param_amount (&collectable->amount_with_fee),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_withdraw_info",
- params);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
-
- /* update reserve balance */
- reserve.pub = collectable->reserve_pub;
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- (qs = reserves_get_internal (pg,
- &reserve)))
- {
- /* Should have been checked before we got here... */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
- }
- if (0 >
- TALER_amount_subtract (&reserve.balance,
- &reserve.balance,
- &collectable->amount_with_fee))
- {
- /* The reserve history was checked to make sure there is enough of a balance
- left before we tried this; however, concurrent operations may have changed
- the situation by now, causing us to fail here. As reserves can no longer
- be topped up, retrying should not help either. */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Withdrawal from reserve `%s' refused due to insufficient balance.\n",
- TALER_B2S (&collectable->reserve_pub));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- gc = GNUNET_TIME_absolute_add (now,
- pg->legal_reserve_expiration_time);
- reserve.gc = GNUNET_TIME_absolute_max (gc,
- reserve.gc);
- (void) GNUNET_TIME_round_abs (&reserve.gc);
- qs = reserves_update (pg,
- &reserve);
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_break (0);
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- }
- return qs;
-}
-
-
-/**
- * Closure for callbacks invoked via #postgres_get_reserve_history.
- */
-struct ReserveHistoryContext
-{
-
- /**
- * Which reserve are we building the history for?
- */
- const struct TALER_ReservePublicKeyP *reserve_pub;
-
- /**
- * Where we build the history.
- */
- struct TALER_EXCHANGEDB_ReserveHistory *rh;
-
- /**
- * Tail of @e rh list.
- */
- struct TALER_EXCHANGEDB_ReserveHistory *rh_tail;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to #GNUNET_SYSERR on serious internal errors during
- * the callbacks.
- */
- int status;
-};
-
-
-/**
- * Append and return a fresh element to the reserve
- * history kept in @a rhc.
- *
- * @param rhc where the history is kept
- * @return the fresh element that was added
- */
-static struct TALER_EXCHANGEDB_ReserveHistory *
-append_rh (struct ReserveHistoryContext *rhc)
-{
- struct TALER_EXCHANGEDB_ReserveHistory *tail;
-
- tail = GNUNET_new (struct TALER_EXCHANGEDB_ReserveHistory);
- if (NULL != rhc->rh_tail)
- {
- rhc->rh_tail->next = tail;
- rhc->rh_tail = tail;
- }
- else
- {
- rhc->rh_tail = tail;
- rhc->rh = tail;
- }
- return tail;
-}
-
-
-/**
- * Add bank transfers to result set for #postgres_get_reserve_history.
- *
- * @param cls a `struct ReserveHistoryContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-add_bank_to_exchange (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReserveHistoryContext *rhc = cls;
- struct PostgresClosure *pg = rhc->pg;
-
- while (0 < num_results)
- {
- struct TALER_EXCHANGEDB_BankTransfer *bt;
- struct TALER_EXCHANGEDB_ReserveHistory *tail;
-
- bt = GNUNET_new (struct TALER_EXCHANGEDB_BankTransfer);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("wire_reference",
- &bt->wire_reference),
- TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
- &bt->amount),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &bt->execution_date),
- GNUNET_PQ_result_spec_string ("sender_account_details",
- &bt->sender_account_details),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- --num_results))
- {
- GNUNET_break (0);
- GNUNET_free (bt);
- rhc->status = GNUNET_SYSERR;
- return;
- }
- }
- bt->reserve_pub = *rhc->reserve_pub;
- tail = append_rh (rhc);
- tail->type = TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE;
- tail->details.bank = bt;
- } /* end of 'while (0 < rows)' */
-}
-
-
-/**
- * Add coin withdrawals to result set for #postgres_get_reserve_history.
- *
- * @param cls a `struct ReserveHistoryContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-add_withdraw_coin (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReserveHistoryContext *rhc = cls;
- struct PostgresClosure *pg = rhc->pg;
-
- while (0 < num_results)
- {
- struct TALER_EXCHANGEDB_CollectableBlindcoin *cbc;
- struct TALER_EXCHANGEDB_ReserveHistory *tail;
-
- cbc = GNUNET_new (struct TALER_EXCHANGEDB_CollectableBlindcoin);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
- &cbc->h_coin_envelope),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &cbc->denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &cbc->sig.rsa_signature),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
- &cbc->reserve_sig),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &cbc->amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
- &cbc->withdraw_fee),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- --num_results))
- {
- GNUNET_break (0);
- GNUNET_free (cbc);
- rhc->status = GNUNET_SYSERR;
- return;
- }
- }
- cbc->reserve_pub = *rhc->reserve_pub;
- tail = append_rh (rhc);
- tail->type = TALER_EXCHANGEDB_RO_WITHDRAW_COIN;
- tail->details.withdraw = cbc;
- }
-}
-
-
-/**
- * Add recoups to result set for #postgres_get_reserve_history.
- *
- * @param cls a `struct ReserveHistoryContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-add_recoup (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReserveHistoryContext *rhc = cls;
- struct PostgresClosure *pg = rhc->pg;
-
- while (0 < num_results)
- {
- struct TALER_EXCHANGEDB_Recoup *recoup;
- struct TALER_EXCHANGEDB_ReserveHistory *tail;
-
- recoup = GNUNET_new (struct TALER_EXCHANGEDB_Recoup);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &recoup->value),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &recoup->coin.coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &recoup->coin_blind),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &recoup->coin_sig),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &recoup->timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &recoup->coin.denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature (
- "denom_sig",
- &recoup->coin.denom_sig.rsa_signature),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- --num_results))
- {
- GNUNET_break (0);
- GNUNET_free (recoup);
- rhc->status = GNUNET_SYSERR;
- return;
- }
- }
- recoup->reserve_pub = *rhc->reserve_pub;
- tail = append_rh (rhc);
- tail->type = TALER_EXCHANGEDB_RO_RECOUP_COIN;
- tail->details.recoup = recoup;
- } /* end of 'while (0 < rows)' */
-}
-
-
-/**
- * Add exchange-to-bank transfers to result set for
- * #postgres_get_reserve_history.
- *
- * @param cls a `struct ReserveHistoryContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-add_exchange_to_bank (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReserveHistoryContext *rhc = cls;
- struct PostgresClosure *pg = rhc->pg;
-
- while (0 < num_results)
- {
- struct TALER_EXCHANGEDB_ClosingTransfer *closing;
- struct TALER_EXCHANGEDB_ReserveHistory *tail;
-
- closing = GNUNET_new (struct TALER_EXCHANGEDB_ClosingTransfer);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &closing->amount),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
- &closing->closing_fee),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &closing->execution_date),
- GNUNET_PQ_result_spec_string ("receiver_account",
- &closing->receiver_account_details),
- GNUNET_PQ_result_spec_auto_from_type ("wtid",
- &closing->wtid),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- --num_results))
- {
- GNUNET_break (0);
- GNUNET_free (closing);
- rhc->status = GNUNET_SYSERR;
- return;
- }
- }
- closing->reserve_pub = *rhc->reserve_pub;
- tail = append_rh (rhc);
- tail->type = TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK;
- tail->details.closing = closing;
- } /* end of 'while (0 < rows)' */
-}
-
-
-/**
- * Get all of the transaction history associated with the specified
- * reserve.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param reserve_pub public key of the reserve
- * @param[out] rhp set to known transaction history (NULL if reserve is unknown)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_reserve_history (void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct TALER_EXCHANGEDB_ReserveHistory **rhp)
-{
- struct PostgresClosure *pg = cls;
- struct ReserveHistoryContext rhc;
- struct
- {
- /**
- * Name of the prepared statement to run.
- */
- const char *statement;
- /**
- * Function to use to process the results.
- */
- GNUNET_PQ_PostgresResultHandler cb;
- } work[] = {
- /** #TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE */
- { "reserves_in_get_transactions",
- add_bank_to_exchange },
- /** #TALER_EXCHANGEDB_RO_WITHDRAW_COIN */
- { "get_reserves_out",
- &add_withdraw_coin },
- /** #TALER_EXCHANGEDB_RO_RECOUP_COIN */
- { "recoup_by_reserve",
- &add_recoup },
- /** #TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK */
- { "close_by_reserve",
- &add_exchange_to_bank },
- /* List terminator */
- { NULL,
- NULL }
- };
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_end
- };
-
- rhc.reserve_pub = reserve_pub;
- rhc.rh = NULL;
- rhc.rh_tail = NULL;
- rhc.pg = pg;
- rhc.status = GNUNET_OK;
- qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; /* make static analysis happy */
- for (unsigned int i = 0; NULL != work[i].cb; i++)
- {
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- work[i].statement,
- params,
- work[i].cb,
- &rhc);
- if ( (0 > qs) ||
- (GNUNET_OK != rhc.status) )
- break;
- }
- if ( (qs < 0) ||
- (rhc.status != GNUNET_OK) )
- {
- common_free_reserve_history (cls,
- rhc.rh);
- rhc.rh = NULL;
- if (qs >= 0)
- {
- /* status == SYSERR is a very hard error... */
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- *rhp = rhc.rh;
- return qs;
-}
-
-
-/**
- * Closure for withdraw_amount_by_account_cb()
- */
-struct WithdrawAmountByAccountContext
-{
- /**
- * Function to call on each amount.
- */
- TALER_EXCHANGEDB_WithdrawHistoryCallback cb;
-
- /**
- * Closure for @e cb
- */
- void *cb_cls;
-
- /**
- * Our plugin's context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to true on failures.
- */
- bool failed;
-};
-
-
-/**
- * Helper function for #postgres_select_withdraw_amounts_by_account().
- * To be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct WithdrawAmountByAccountContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-withdraw_amount_by_account_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct WithdrawAmountByAccountContext *wac = cls;
- struct PostgresClosure *pg = wac->pg;
-
- for (unsigned int i = 0; num_results; i++)
- {
- struct TALER_Amount val;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("val",
- &val),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- wac->failed = true;
- return;
- }
- wac->cb (wac->cb_cls,
- &val);
- }
-}
-
-
-/**
- * Find out all of the amounts that have been withdrawn
- * so far from the same bank account that created the
- * given reserve.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param reserve_pub reserve to select withdrawals by
- * @param duration how far back should we select withdrawals
- * @param cb function to call on each amount withdrawn
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_withdraw_amounts_by_account (
- void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct GNUNET_TIME_Relative duration,
- TALER_EXCHANGEDB_WithdrawHistoryCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct WithdrawAmountByAccountContext wac = {
- .pg = pg,
- .cb = cb,
- .cb_cls = cb_cls
- };
- struct GNUNET_TIME_Absolute start
- = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (),
- duration);
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- GNUNET_PQ_query_param_absolute_time (&start),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (
- pg->conn,
- "select_XXX",
- params,
- &withdraw_amount_by_account_cb,
- &wac);
-
- if (wac.failed)
- {
- GNUNET_break (0);
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- }
- return qs;
-}
-
-
-/**
- * Check if we have the specified deposit already in the database.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param deposit deposit to search for
- * @param check_extras whether to check extra fields match or not
- * @param[out] deposit_fee set to the deposit fee the exchange charged
- * @param[out] exchange_timestamp set to the time when the exchange received the deposit
- * @return 1 if we know this operation,
- * 0 if this exact deposit is unknown to us,
- * otherwise transaction error status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_have_deposit (void *cls,
- const struct TALER_EXCHANGEDB_Deposit *deposit,
- int check_extras,
- struct TALER_Amount *deposit_fee,
- struct GNUNET_TIME_Absolute *exchange_timestamp)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
- GNUNET_PQ_query_param_auto_from_type (&deposit->h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (&deposit->merchant_pub),
- GNUNET_PQ_query_param_end
- };
- struct TALER_EXCHANGEDB_Deposit deposit2;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &deposit2.amount_with_fee),
- TALER_PQ_result_spec_absolute_time ("wallet_timestamp",
- &deposit2.timestamp),
- TALER_PQ_result_spec_absolute_time ("exchange_timestamp",
- exchange_timestamp),
- TALER_PQ_result_spec_absolute_time ("refund_deadline",
- &deposit2.refund_deadline),
- TALER_PQ_result_spec_absolute_time ("wire_deadline",
- &deposit2.wire_deadline),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- deposit_fee),
- GNUNET_PQ_result_spec_auto_from_type ("h_wire",
- &deposit2.h_wire),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-#if EXPLICIT_LOCKS
- struct GNUNET_PQ_QueryParam no_params[] = {
- GNUNET_PQ_query_param_end
- };
-
- if (0 > (qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "lock_deposit",
- no_params)))
- return qs;
-#endif
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Getting deposits for coin %s\n",
- TALER_B2S (&deposit->coin.coin_pub));
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_deposit",
- params,
- rs);
- if (0 >= qs)
- return qs;
- /* Now we check that the other information in @a deposit
- also matches, and if not report inconsistencies. */
- if ( ( (check_extras) &&
- ( (0 != TALER_amount_cmp (&deposit->amount_with_fee,
- &deposit2.amount_with_fee)) ||
- (deposit->timestamp.abs_value_us !=
- deposit2.timestamp.abs_value_us) ) ) ||
- (deposit->refund_deadline.abs_value_us !=
- deposit2.refund_deadline.abs_value_us) ||
- (0 != GNUNET_memcmp (&deposit->h_wire,
- &deposit2.h_wire) ) )
- {
- /* Inconsistencies detected! Does not match! (We might want to
- expand the API with a 'get_deposit' function to return the
- original transaction details to be used for an error message
- in the future!) #3838 */
- return 0; /* Counts as if the transaction was not there */
- }
- return 1;
-}
-
-
-/**
- * Mark a deposit as tiny, thereby declaring that it cannot be
- * executed by itself and should no longer be returned by
- * @e iterate_ready_deposits()
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param rowid identifies the deposit row to modify
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_mark_deposit_tiny (void *cls,
- uint64_t rowid)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&rowid),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "mark_deposit_tiny",
- params);
-}
-
-
-/**
- * Test if a deposit was marked as done, thereby declaring that it cannot be
- * refunded anymore.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param coin_pub the coin to check for deposit
- * @param merchant_pub merchant to receive the deposit
- * @param h_contract_terms contract terms of the deposit
- * @param h_wire hash of the merchant's wire details
- * @return #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if is is marked done,
- * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if not,
- * otherwise transaction error status (incl. deposit unknown)
- */
-static enum GNUNET_DB_QueryStatus
-postgres_test_deposit_done (void *cls,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct GNUNET_HashCode *h_wire)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_auto_from_type (merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (h_wire),
- GNUNET_PQ_query_param_end
- };
- uint8_t done = 0;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("done",
- &done),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "test_deposit_done",
- params,
- rs);
- if (qs < 0)
- return qs;
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return GNUNET_DB_STATUS_HARD_ERROR; /* deposit MUST exist */
- return (done
- ? GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
- : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS);
-}
-
-
-/**
- * Mark a deposit as done, thereby declaring that it cannot be
- * executed at all anymore, and should no longer be returned by
- * @e iterate_ready_deposits() or @e iterate_matching_deposits().
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param rowid identifies the deposit row to modify
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_mark_deposit_done (void *cls,
- uint64_t rowid)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&rowid),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "mark_deposit_done",
- params);
-}
-
-
-/**
- * Obtain information about deposits that are ready to be executed.
- * Such deposits must not be marked as "tiny" or "done", and the
- * execution time must be in the past.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param start_shard_row minimum shard row to select
- * @param end_shard_row maximum shard row to select (inclusive)
- * @param deposit_cb function to call for ONE such deposit
- * @param deposit_cb_cls closure for @a deposit_cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_ready_deposit (void *cls,
- uint32_t start_shard_row,
- uint32_t end_shard_row,
- TALER_EXCHANGEDB_DepositIterator deposit_cb,
- void *deposit_cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_uint32 (&start_shard_row),
- GNUNET_PQ_query_param_uint32 (&end_shard_row),
- GNUNET_PQ_query_param_end
- };
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount deposit_fee;
- struct GNUNET_HashCode h_contract_terms;
- struct TALER_MerchantPublicKeyP merchant_pub;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- uint64_t serial_id;
- json_t *wire;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
- &serial_id),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- &deposit_fee),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &merchant_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin_pub),
- TALER_PQ_result_spec_json ("wire",
- &wire),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- (void) GNUNET_TIME_round_abs (&now);
- GNUNET_assert (start_shard_row < end_shard_row);
- GNUNET_assert (end_shard_row <= INT32_MAX);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Finding ready deposits by deadline %s (%llu)\n",
- GNUNET_STRINGS_absolute_time_to_string (now),
- (unsigned long long) now.abs_value_us);
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "deposits_get_ready",
- params,
- rs);
- if (qs <= 0)
- return qs;
-
- qs = deposit_cb (deposit_cb_cls,
- serial_id,
- &merchant_pub,
- &coin_pub,
- &amount_with_fee,
- &deposit_fee,
- &h_contract_terms,
- wire);
- GNUNET_PQ_cleanup_result (rs);
- return qs;
-}
-
-
-/**
- * Closure for #match_deposit_cb().
- */
-struct MatchingDepositContext
-{
- /**
- * Function to call for each result
- */
- TALER_EXCHANGEDB_MatchingDepositIterator deposit_cb;
-
- /**
- * Closure for @e deposit_cb.
- */
- void *deposit_cb_cls;
-
- /**
- * Public key of the merchant against which we are matching.
- */
- const struct TALER_MerchantPublicKeyP *merchant_pub;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Maximum number of results to return.
- */
- uint32_t limit;
-
- /**
- * Loop counter, actual number of results returned.
- */
- unsigned int i;
-
- /**
- * Set to #GNUNET_SYSERR on hard errors.
- */
- enum GNUNET_GenericReturnValue status;
-};
-
-
-/**
- * Helper function for #postgres_iterate_matching_deposits().
- * To be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct MatchingDepositContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-match_deposit_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct MatchingDepositContext *mdc = cls;
- struct PostgresClosure *pg = mdc->pg;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Found %u/%u matching deposits\n",
- num_results,
- mdc->limit);
- num_results = GNUNET_MIN (num_results,
- mdc->limit);
- for (mdc->i = 0; mdc->i<num_results; mdc->i++)
- {
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount deposit_fee;
- struct GNUNET_HashCode h_contract_terms;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- uint64_t serial_id;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
- &serial_id),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- &deposit_fee),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin_pub),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- mdc->i))
- {
- GNUNET_break (0);
- mdc->status = GNUNET_SYSERR;
- return;
- }
- qs = mdc->deposit_cb (mdc->deposit_cb_cls,
- serial_id,
- &coin_pub,
- &amount_with_fee,
- &deposit_fee,
- &h_contract_terms);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- break;
- }
-}
-
-
-/**
- * Obtain information about other pending deposits for the same
- * destination. Those deposits must not already be "done".
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param h_wire destination of the wire transfer
- * @param merchant_pub public key of the merchant
- * @param deposit_cb function to call for each deposit
- * @param deposit_cb_cls closure for @a deposit_cb
- * @param limit maximum number of matching deposits to return
- * @return transaction status code, if positive:
- * number of rows processed, 0 if none exist
- */
-static enum GNUNET_DB_QueryStatus
-postgres_iterate_matching_deposits (
- void *cls,
- const struct GNUNET_HashCode *h_wire,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- TALER_EXCHANGEDB_MatchingDepositIterator deposit_cb,
- void *deposit_cb_cls,
- uint32_t limit)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (h_wire),
- GNUNET_PQ_query_param_end
- };
- struct MatchingDepositContext mdc = {
- .deposit_cb = deposit_cb,
- .deposit_cb_cls = deposit_cb_cls,
- .merchant_pub = merchant_pub,
- .pg = pg,
- .limit = limit,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "deposits_iterate_matching",
- params,
- &match_deposit_cb,
- &mdc);
- if (GNUNET_OK != mdc.status)
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (qs >= 0)
- return mdc.i;
- return qs;
-}
-
-
-/**
- * Retrieve the record for a known coin.
- *
- * @param cls the plugin closure
- * @param coin_pub the public key of the coin to search for
- * @param coin_info place holder for the returned coin information object
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_known_coin (void *cls,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- struct TALER_CoinPublicInfo *coin_info)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &coin_info->denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &coin_info->denom_sig.rsa_signature),
- GNUNET_PQ_result_spec_end
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Getting known coin data for coin %s\n",
- TALER_B2S (coin_pub));
- coin_info->coin_pub = *coin_pub;
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_known_coin",
- params,
- rs);
-}
-
-
-/**
- * Retrieve the denomination of a known coin.
- *
- * @param cls the plugin closure
- * @param coin_pub the public key of the coin to search for
- * @param[out] denom_hash where to store the hash of the coins denomination
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_coin_denomination (
- void *cls,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- struct GNUNET_HashCode *denom_hash)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- denom_hash),
- GNUNET_PQ_result_spec_end
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Getting coin denomination of coin %s\n",
- TALER_B2S (coin_pub));
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_coin_denomination",
- params,
- rs);
-}
-
-
-/**
- * Insert a coin we know of into the DB. The coin can then be
- * referenced by tables for deposits, refresh and refund
- * functionality.
- *
- * @param cls plugin closure
- * @param coin_info the public coin info
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-insert_known_coin (void *cls,
- const struct TALER_CoinPublicInfo *coin_info)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&coin_info->coin_pub),
- GNUNET_PQ_query_param_auto_from_type (&coin_info->denom_pub_hash),
- GNUNET_PQ_query_param_rsa_signature (coin_info->denom_sig.rsa_signature),
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Creating known coin %s\n",
- TALER_B2S (&coin_info->coin_pub));
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_known_coin",
- params);
-}
-
-
-/**
- * Count the number of known coins by denomination.
- *
- * @param cls database connection plugin state
- * @param denom_pub_hash denomination to count by
- * @return number of coins if non-negative, otherwise an `enum GNUNET_DB_QueryStatus`
- */
-static long long
-postgres_count_known_coins (void *cls,
- const struct GNUNET_HashCode *denom_pub_hash)
-{
- struct PostgresClosure *pg = cls;
- uint64_t count;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("count",
- &count),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "count_known_coins",
- params,
- rs);
- if (0 > qs)
- return (long long) qs;
- return (long long) count;
-}
-
-
-/**
- * Make sure the given @a coin is known to the database.
- *
- * @param cls database connection plugin state
- * @param coin the coin that must be made known
- * @return database transaction status, non-negative on success
- */
-static enum TALER_EXCHANGEDB_CoinKnownStatus
-postgres_ensure_coin_known (void *cls,
- const struct TALER_CoinPublicInfo *coin)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_HashCode denom_pub_hash;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &denom_pub_hash),
- GNUNET_PQ_result_spec_end
- };
-#if EXPLICIT_LOCKS
- struct GNUNET_PQ_QueryParam no_params[] = {
- GNUNET_PQ_query_param_end
- };
-
- if (0 > (qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "lock_known_coins",
- no_params)))
- return qs;
-#endif
- /* check if the coin is already known */
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_known_coin_dh",
- params,
- rs);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- return TALER_EXCHANGEDB_CKS_HARD_FAIL;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return TALER_EXCHANGEDB_CKS_SOFT_FAIL;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- if (0 == GNUNET_memcmp (&denom_pub_hash,
- &coin->denom_pub_hash))
- return TALER_EXCHANGEDB_CKS_PRESENT;
- GNUNET_break_op (0);
- return TALER_EXCHANGEDB_CKS_CONFLICT;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- break;
- }
-
- /* if not known, insert it */
- qs = insert_known_coin (pg,
- coin);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- return TALER_EXCHANGEDB_CKS_SOFT_FAIL;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return TALER_EXCHANGEDB_CKS_HARD_FAIL;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_break (0);
- return TALER_EXCHANGEDB_CKS_HARD_FAIL;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- return TALER_EXCHANGEDB_CKS_ADDED;
-}
-
-
-/**
- * Compute the shard number of a given @a deposit
- *
- * @param deposit deposit to compute shard for
- * @return shard number
- */
-static uint32_t
-compute_shard (const struct TALER_EXCHANGEDB_Deposit *deposit)
-{
- uint32_t res;
-
- GNUNET_assert (GNUNET_YES ==
- GNUNET_CRYPTO_kdf (&res,
- sizeof (res),
- &deposit->h_wire,
- sizeof (deposit->h_wire),
- &deposit->merchant_pub,
- sizeof (deposit->merchant_pub),
- NULL, 0));
- /* interpret hash result as NBO for platform independence,
- convert to HBO and map to [0..2^31-1] range */
- res = ntohl (res);
- if (res > INT32_MAX)
- res += INT32_MIN;
- GNUNET_assert (res <= INT32_MAX);
- return res;
-}
-
-
-/**
- * Insert information about deposited coin into the database.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param exchange_timestamp time the exchange received the deposit request
- * @param deposit deposit information to store
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_deposit (void *cls,
- struct GNUNET_TIME_Absolute exchange_timestamp,
- const struct TALER_EXCHANGEDB_Deposit *deposit)
-{
- struct PostgresClosure *pg = cls;
- uint32_t shard = compute_shard (deposit);
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&deposit->coin.coin_pub),
- TALER_PQ_query_param_amount (&deposit->amount_with_fee),
- TALER_PQ_query_param_absolute_time (&deposit->timestamp),
- TALER_PQ_query_param_absolute_time (&deposit->refund_deadline),
- TALER_PQ_query_param_absolute_time (&deposit->wire_deadline),
- GNUNET_PQ_query_param_auto_from_type (&deposit->merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (&deposit->h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (&deposit->h_wire),
- GNUNET_PQ_query_param_auto_from_type (&deposit->csig),
- TALER_PQ_query_param_json (deposit->receiver_wire_account),
- TALER_PQ_query_param_absolute_time (&exchange_timestamp),
- GNUNET_PQ_query_param_uint32 (&shard),
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_assert (shard <= INT32_MAX);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Inserting deposit to be executed at %s (%llu/%llu)\n",
- GNUNET_STRINGS_absolute_time_to_string (deposit->wire_deadline),
- (unsigned long long) deposit->wire_deadline.abs_value_us,
- (unsigned long long) deposit->refund_deadline.abs_value_us);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_deposit",
- params);
-}
-
-
-/**
- * Insert information about refunded coin into the database.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param refund refund information to store
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_refund (void *cls,
- const struct TALER_EXCHANGEDB_Refund *refund)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&refund->coin.coin_pub),
- GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (&refund->details.merchant_sig),
- GNUNET_PQ_query_param_auto_from_type (&refund->details.h_contract_terms),
- GNUNET_PQ_query_param_uint64 (&refund->details.rtransaction_id),
- TALER_PQ_query_param_amount (&refund->details.refund_amount),
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (&refund->details.refund_amount,
- &refund->details.refund_fee));
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_refund",
- params);
-}
-
-
-/**
- * Closure for #get_refunds_cb().
- */
-struct SelectRefundContext
-{
- /**
- * Function to call on each result.
- */
- TALER_EXCHANGEDB_RefundCoinCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to #GNUNET_SYSERR on error.
- */
- int status;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct SelectRefundContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-get_refunds_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct SelectRefundContext *srctx = cls;
- struct PostgresClosure *pg = srctx->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_Amount amount_with_fee;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- srctx->status = GNUNET_SYSERR;
- return;
- }
- if (GNUNET_OK !=
- srctx->cb (srctx->cb_cls,
- &amount_with_fee))
- return;
- }
-}
-
-
-/**
- * Select refunds by @a coin_pub, @a merchant_pub and @a h_contract.
- *
- * @param cls closure of plugin
- * @param coin_pub coin to get refunds for
- * @param merchant_pub merchant to get refunds for
- * @param h_contract contract (hash) to get refunds for
- * @param cb function to call for each refund found
- * @param cb_cls closure for @a cb
- * @return query result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_refunds_by_coin (
- void *cls,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_contract,
- TALER_EXCHANGEDB_RefundCoinCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_auto_from_type (merchant_pub),
- GNUNET_PQ_query_param_auto_from_type (h_contract),
- GNUNET_PQ_query_param_end
- };
- struct SelectRefundContext srctx = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "get_refunds_by_coin_and_contract",
- params,
- &get_refunds_cb,
- &srctx);
- if (GNUNET_SYSERR == srctx.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Lookup refresh melt commitment data under the given @a rc.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param rc commitment hash to use to locate the operation
- * @param[out] melt where to store the result; note that
- * melt->session.coin.denom_sig will be set to NULL
- * and is not fetched by this routine (as it is not needed by the client)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_melt (void *cls,
- const struct TALER_RefreshCommitmentP *rc,
- struct TALER_EXCHANGEDB_Melt *melt)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (rc),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &melt->session.coin.
- denom_pub_hash),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
- &melt->melt_fee),
- GNUNET_PQ_result_spec_uint32 ("noreveal_index",
- &melt->session.noreveal_index),
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
- &melt->session.coin.coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
- &melt->session.coin_sig),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &melt->session.amount_with_fee),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- melt->session.coin.denom_sig.rsa_signature = NULL;
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_melt",
- params,
- rs);
- melt->session.rc = *rc;
- return qs;
-}
-
-
-/**
- * Lookup noreveal index of a previous melt operation under the given
- * @a rc.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param rc commitment hash to use to locate the operation
- * @param[out] noreveal_index returns the "gamma" value selected by the
- * exchange which is the index of the transfer key that is
- * not to be revealed to the exchange
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_melt_index (void *cls,
- const struct TALER_RefreshCommitmentP *rc,
- uint32_t *noreveal_index)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (rc),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint32 ("noreveal_index",
- noreveal_index),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_melt_index",
- params,
- rs);
-}
-
-
-/**
- * Store new refresh melt commitment data.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param refresh_session session data to store
- * @return query status for the transaction
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_melt (
- void *cls,
- const struct TALER_EXCHANGEDB_Refresh *refresh_session)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&refresh_session->rc),
- GNUNET_PQ_query_param_auto_from_type (&refresh_session->coin.coin_pub),
- GNUNET_PQ_query_param_auto_from_type (&refresh_session->coin_sig),
- TALER_PQ_query_param_amount (&refresh_session->amount_with_fee),
- GNUNET_PQ_query_param_uint32 (&refresh_session->noreveal_index),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_melt",
- params);
-}
-
-
-/**
- * Store in the database which coin(s) the wallet wanted to create
- * in a given refresh operation and all of the other information
- * we learned or created in the /refresh/reveal step.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param rc identify commitment and thus refresh operation
- * @param num_rrcs number of coins to generate, size of the @a rrcs array
- * @param rrcs information about the new coins
- * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
- * @param tprivs transfer private keys to store
- * @param tp public key to store
- * @return query status for the transaction
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_refresh_reveal (
- void *cls,
- const struct TALER_RefreshCommitmentP *rc,
- uint32_t num_rrcs,
- const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
- unsigned int num_tprivs,
- const struct TALER_TransferPrivateKeyP *tprivs,
- const struct TALER_TransferPublicKeyP *tp)
-{
- struct PostgresClosure *pg = cls;
-
- if (TALER_CNC_KAPPA != num_tprivs + 1)
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- for (uint32_t i = 0; i<num_rrcs; i++)
- {
- const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &rrcs[i];
- struct GNUNET_HashCode denom_pub_hash;
- struct GNUNET_HashCode h_coin_ev;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (rc),
- GNUNET_PQ_query_param_uint32 (&i),
- GNUNET_PQ_query_param_auto_from_type (&rrc->orig_coin_link_sig),
- GNUNET_PQ_query_param_auto_from_type (&denom_pub_hash),
- GNUNET_PQ_query_param_fixed_size (rrc->coin_ev,
- rrc->coin_ev_size),
- GNUNET_PQ_query_param_auto_from_type (&h_coin_ev),
- GNUNET_PQ_query_param_rsa_signature (rrc->coin_sig.rsa_signature),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- GNUNET_CRYPTO_rsa_public_key_hash (rrc->denom_pub.rsa_public_key,
- &denom_pub_hash);
- GNUNET_CRYPTO_hash (rrc->coin_ev,
- rrc->coin_ev_size,
- &h_coin_ev);
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_refresh_revealed_coin",
- params);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- return qs;
- }
-
- {
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (rc),
- GNUNET_PQ_query_param_auto_from_type (tp),
- GNUNET_PQ_query_param_fixed_size (tprivs,
- num_tprivs
- * sizeof (struct
- TALER_TransferPrivateKeyP)),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_refresh_transfer_keys",
- params);
- }
-}
-
-
-/**
- * Context where we aggregate data from the database.
- * Closure for #add_revealed_coins().
- */
-struct GetRevealContext
-{
- /**
- * Array of revealed coins we obtained from the DB.
- */
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs;
-
- /**
- * Length of the @a rrcs array.
- */
- unsigned int rrcs_len;
-
- /**
- * Set to an error code if we ran into trouble.
- */
- enum GNUNET_DB_QueryStatus qs;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct GetRevealContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_revealed_coins (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct GetRevealContext *grctx = cls;
-
- if (0 == num_results)
- return;
- grctx->rrcs = GNUNET_new_array (num_results,
- struct TALER_EXCHANGEDB_RefreshRevealedCoin);
- grctx->rrcs_len = num_results;
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &grctx->rrcs[i];
- uint32_t off;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint32 ("freshcoin_index",
- &off),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &rrc->denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("link_sig",
- &rrc->orig_coin_link_sig),
- GNUNET_PQ_result_spec_variable_size ("coin_ev",
- (void **) &rrc->coin_ev,
- &rrc->coin_ev_size),
- GNUNET_PQ_result_spec_rsa_signature ("ev_sig",
- &rrc->coin_sig.rsa_signature),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- if (off != i)
- {
- GNUNET_break (0);
- grctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
- return;
- }
- }
-}
-
-
-/**
- * Lookup in the database the coins that we want to
- * create in the given refresh operation.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param rc identify commitment and thus refresh operation
- * @param cb function to call with the results
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_refresh_reveal (void *cls,
- const struct TALER_RefreshCommitmentP *rc,
- TALER_EXCHANGEDB_RefreshCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GetRevealContext grctx;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_TransferPublicKeyP tp;
- void *tpriv;
- size_t tpriv_size;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (rc),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("transfer_pub",
- &tp),
- GNUNET_PQ_result_spec_variable_size ("transfer_privs",
- &tpriv,
- &tpriv_size),
- GNUNET_PQ_result_spec_end
- };
-
- /* First get the coins */
- memset (&grctx,
- 0,
- sizeof (grctx));
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "get_refresh_revealed_coins",
- params,
- &add_revealed_coins,
- &grctx);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- goto cleanup;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- default: /* can have more than one result */
- break;
- }
- switch (grctx.qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- goto cleanup;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* should be impossible */
- break;
- }
-
- /* now also get the transfer keys (public and private) */
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_refresh_transfer_keys",
- params,
- rs);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- goto cleanup;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- default:
- GNUNET_assert (0);
- }
- if ( (0 != tpriv_size % sizeof (struct TALER_TransferPrivateKeyP)) ||
- (TALER_CNC_KAPPA - 1 != tpriv_size / sizeof (struct
- TALER_TransferPrivateKeyP)) )
- {
- GNUNET_break (0);
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- GNUNET_PQ_cleanup_result (rs);
- goto cleanup;
- }
-
- /* Pass result back to application */
- cb (cb_cls,
- grctx.rrcs_len,
- grctx.rrcs,
- tpriv_size / sizeof (struct TALER_TransferPrivateKeyP),
- (const struct TALER_TransferPrivateKeyP *) tpriv,
- &tp);
- GNUNET_PQ_cleanup_result (rs);
-
-cleanup:
- for (unsigned int i = 0; i < grctx.rrcs_len; i++)
- {
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrc = &grctx.rrcs[i];
-
- if (NULL != rrc->denom_pub.rsa_public_key)
- GNUNET_CRYPTO_rsa_public_key_free (rrc->denom_pub.rsa_public_key);
- if (NULL != rrc->coin_sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (rrc->coin_sig.rsa_signature);
- GNUNET_free (rrc->coin_ev);
- }
- GNUNET_free (grctx.rrcs);
-
- return qs;
-}
-
-
-/**
- * Closure for #add_ldl().
- */
-struct LinkDataContext
-{
- /**
- * Function to call on each result.
- */
- TALER_EXCHANGEDB_LinkCallback ldc;
-
- /**
- * Closure for @e ldc.
- */
- void *ldc_cls;
-
- /**
- * Last transfer public key for which we have information in @e last.
- * Only valid if @e last is non-NULL.
- */
- struct TALER_TransferPublicKeyP transfer_pub;
-
- /**
- * Link data for @e transfer_pub
- */
- struct TALER_EXCHANGEDB_LinkList *last;
-
- /**
- * Status, set to #GNUNET_SYSERR on errors,
- */
- int status;
-};
-
-
-/**
- * Free memory of the link data list.
- *
- * @param cls the @e cls of this struct with the plugin-specific state (unused)
- * @param ldl link data list to release
- */
-static void
-free_link_data_list (void *cls,
- struct TALER_EXCHANGEDB_LinkList *ldl)
-{
- struct TALER_EXCHANGEDB_LinkList *next;
-
- while (NULL != ldl)
- {
- next = ldl->next;
- if (NULL != ldl->denom_pub.rsa_public_key)
- GNUNET_CRYPTO_rsa_public_key_free (ldl->denom_pub.rsa_public_key);
- if (NULL != ldl->ev_sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (ldl->ev_sig.rsa_signature);
- GNUNET_free (ldl);
- ldl = next;
- }
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct LinkDataContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_ldl (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct LinkDataContext *ldctx = cls;
-
- for (int i = num_results - 1; i >= 0; i--)
- {
- struct TALER_EXCHANGEDB_LinkList *pos;
- struct TALER_TransferPublicKeyP transfer_pub;
-
- pos = GNUNET_new (struct TALER_EXCHANGEDB_LinkList);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("transfer_pub",
- &transfer_pub),
- GNUNET_PQ_result_spec_auto_from_type ("link_sig",
- &pos->orig_coin_link_sig),
- GNUNET_PQ_result_spec_rsa_signature ("ev_sig",
- &pos->ev_sig.rsa_signature),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &pos->denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (pos);
- ldctx->status = GNUNET_SYSERR;
- return;
- }
- }
- if ( (NULL != ldctx->last) &&
- (0 == GNUNET_memcmp (&transfer_pub,
- &ldctx->transfer_pub)) )
- {
- pos->next = ldctx->last;
- }
- else
- {
- if (NULL != ldctx->last)
- {
- ldctx->ldc (ldctx->ldc_cls,
- &ldctx->transfer_pub,
- ldctx->last);
- free_link_data_list (cls,
- ldctx->last);
- }
- ldctx->transfer_pub = transfer_pub;
- }
- ldctx->last = pos;
- }
-}
-
-
-/**
- * Obtain the link data of a coin, that is the encrypted link
- * information, the denomination keys and the signatures.
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param coin_pub public key of the coin
- * @param ldc function to call for each session the coin was melted into
- * @param ldc_cls closure for @a tdc
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_link_data (void *cls,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- TALER_EXCHANGEDB_LinkCallback ldc,
- void *ldc_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
- struct LinkDataContext ldctx;
-
- ldctx.ldc = ldc;
- ldctx.ldc_cls = ldc_cls;
- ldctx.last = NULL;
- ldctx.status = GNUNET_OK;
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "get_link",
- params,
- &add_ldl,
- &ldctx);
- if (NULL != ldctx.last)
- {
- if (GNUNET_OK == ldctx.status)
- {
- /* call callback one more time! */
- ldc (ldc_cls,
- &ldctx.transfer_pub,
- ldctx.last);
- }
- free_link_data_list (cls,
- ldctx.last);
- ldctx.last = NULL;
- }
- if (GNUNET_OK != ldctx.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for callbacks called from #postgres_get_coin_transactions()
- */
-struct CoinHistoryContext
-{
- /**
- * Head of the coin's history list.
- */
- struct TALER_EXCHANGEDB_TransactionList *head;
-
- /**
- * Public key of the coin we are building the history for.
- */
- const struct TALER_CoinSpendPublicKeyP *coin_pub;
-
- /**
- * Closure for all callbacks of this database plugin.
- */
- void *db_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to 'true' if the transaction failed.
- */
- bool failed;
-
- /**
- * Set to 'true' if we found a deposit or melt (for invariant check).
- */
- bool have_deposit_or_melt;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct CoinHistoryContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_coin_deposit (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct CoinHistoryContext *chc = cls;
- struct PostgresClosure *pg = chc->pg;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_EXCHANGEDB_DepositListEntry *deposit;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- uint64_t serial_id;
-
- chc->have_deposit_or_melt = true;
- deposit = GNUNET_new (struct TALER_EXCHANGEDB_DepositListEntry);
- {
- uint8_t done = 0;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &deposit->amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- &deposit->deposit_fee),
- TALER_PQ_result_spec_absolute_time ("wallet_timestamp",
- &deposit->timestamp),
- TALER_PQ_result_spec_absolute_time ("refund_deadline",
- &deposit->refund_deadline),
- TALER_PQ_result_spec_absolute_time ("wire_deadline",
- &deposit->wire_deadline),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &deposit->h_denom_pub),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &deposit->merchant_pub),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &deposit->h_contract_terms),
- GNUNET_PQ_result_spec_auto_from_type ("h_wire",
- &deposit->h_wire),
- TALER_PQ_result_spec_json ("wire",
- &deposit->receiver_wire_account),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &deposit->csig),
- GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
- &serial_id),
- GNUNET_PQ_result_spec_auto_from_type ("done",
- &done),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (deposit);
- chc->failed = true;
- return;
- }
- deposit->done = (0 != done);
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_DEPOSIT;
- tl->details.deposit = deposit;
- tl->serial_id = serial_id;
- chc->head = tl;
- }
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct CoinHistoryContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_coin_melt (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct CoinHistoryContext *chc = cls;
- struct PostgresClosure *pg = chc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_MeltListEntry *melt;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- uint64_t serial_id;
-
- chc->have_deposit_or_melt = true;
- melt = GNUNET_new (struct TALER_EXCHANGEDB_MeltListEntry);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("rc",
- &melt->rc),
- /* oldcoin_index not needed */
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &melt->h_denom_pub),
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
- &melt->coin_sig),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &melt->amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
- &melt->melt_fee),
- GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
- &serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (melt);
- chc->failed = true;
- return;
- }
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_MELT;
- tl->details.melt = melt;
- tl->serial_id = serial_id;
- chc->head = tl;
- }
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct CoinHistoryContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_coin_refund (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct CoinHistoryContext *chc = cls;
- struct PostgresClosure *pg = chc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_RefundListEntry *refund;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- uint64_t serial_id;
-
- refund = GNUNET_new (struct TALER_EXCHANGEDB_RefundListEntry);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &refund->merchant_pub),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
- &refund->merchant_sig),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &refund->h_contract_terms),
- GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
- &refund->rtransaction_id),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &refund->refund_amount),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
- &refund->refund_fee),
- GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
- &serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (refund);
- chc->failed = true;
- return;
- }
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_REFUND;
- tl->details.refund = refund;
- tl->serial_id = serial_id;
- chc->head = tl;
- }
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct CoinHistoryContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_old_coin_recoup (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct CoinHistoryContext *chc = cls;
- struct PostgresClosure *pg = chc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- uint64_t serial_id;
-
- recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &recoup->coin.coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &recoup->coin_sig),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &recoup->coin_blind),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &recoup->value),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &recoup->timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &recoup->coin.denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &recoup->coin.denom_sig.
- rsa_signature),
- GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
- &serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (recoup);
- chc->failed = true;
- return;
- }
- recoup->old_coin_pub = *chc->coin_pub;
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP;
- tl->details.old_coin_recoup = recoup;
- tl->serial_id = serial_id;
- chc->head = tl;
- }
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct CoinHistoryContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_coin_recoup (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct CoinHistoryContext *chc = cls;
- struct PostgresClosure *pg = chc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_RecoupListEntry *recoup;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- uint64_t serial_id;
-
- recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupListEntry);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &recoup->reserve_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &recoup->coin_sig),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &recoup->h_denom_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &recoup->coin_blind),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &recoup->value),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &recoup->timestamp),
- GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
- &serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (recoup);
- chc->failed = true;
- return;
- }
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_RECOUP;
- tl->details.recoup = recoup;
- tl->serial_id = serial_id;
- chc->head = tl;
- }
-}
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct CoinHistoryContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-add_coin_recoup_refresh (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct CoinHistoryContext *chc = cls;
- struct PostgresClosure *pg = chc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_RecoupRefreshListEntry *recoup;
- struct TALER_EXCHANGEDB_TransactionList *tl;
- uint64_t serial_id;
-
- recoup = GNUNET_new (struct TALER_EXCHANGEDB_RecoupRefreshListEntry);
- {
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
- &recoup->old_coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &recoup->coin_sig),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &recoup->coin_blind),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &recoup->value),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &recoup->timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &recoup->coin.denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &recoup->coin.denom_sig.
- rsa_signature),
- GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
- &serial_id),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- GNUNET_free (recoup);
- chc->failed = true;
- return;
- }
- recoup->coin.coin_pub = *chc->coin_pub;
- }
- tl = GNUNET_new (struct TALER_EXCHANGEDB_TransactionList);
- tl->next = chc->head;
- tl->type = TALER_EXCHANGEDB_TT_RECOUP_REFRESH;
- tl->details.recoup_refresh = recoup;
- tl->serial_id = serial_id;
- chc->head = tl;
- }
-}
-
-
-/**
- * Work we need to do.
- */
-struct Work
-{
- /**
- * SQL prepared statement name.
- */
- const char *statement;
-
- /**
- * Function to call to handle the result(s).
- */
- GNUNET_PQ_PostgresResultHandler cb;
-};
-
-
-/**
- * Compile a list of all (historic) transactions performed with the given coin
- * (/refresh/melt, /deposit, /refund and /recoup operations).
- *
- * @param cls the `struct PostgresClosure` with the plugin-specific state
- * @param coin_pub coin to investigate
- * @param include_recoup should recoup transactions be included in the @a tlp
- * @param[out] tlp set to list of transactions, NULL if coin is fresh
- * @return database transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_coin_transactions (
- void *cls,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- int include_recoup,
- struct TALER_EXCHANGEDB_TransactionList **tlp)
-{
- struct PostgresClosure *pg = cls;
- static const struct Work work_op[] = {
- /** #TALER_EXCHANGEDB_TT_DEPOSIT */
- { "get_deposit_with_coin_pub",
- &add_coin_deposit },
- /** #TALER_EXCHANGEDB_TT_MELT */
- { "get_refresh_session_by_coin",
- &add_coin_melt },
- /** #TALER_EXCHANGEDB_TT_REFUND */
- { "get_refunds_by_coin",
- &add_coin_refund },
- { NULL, NULL }
- };
- static const struct Work work_wp[] = {
- /** #TALER_EXCHANGEDB_TT_DEPOSIT */
- { "get_deposit_with_coin_pub",
- &add_coin_deposit },
- /** #TALER_EXCHANGEDB_TT_MELT */
- { "get_refresh_session_by_coin",
- &add_coin_melt },
- /** #TALER_EXCHANGEDB_TT_REFUND */
- { "get_refunds_by_coin",
- &add_coin_refund },
- /** #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP */
- { "recoup_by_old_coin",
- &add_old_coin_recoup },
- /** #TALER_EXCHANGEDB_TT_RECOUP */
- { "recoup_by_coin",
- &add_coin_recoup },
- /** #TALER_EXCHANGEDB_TT_RECOUP_REFRESH */
- { "recoup_by_refreshed_coin",
- &add_coin_recoup_refresh },
- { NULL, NULL }
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
- const struct Work *work;
- struct CoinHistoryContext chc = {
- .head = NULL,
- .coin_pub = coin_pub,
- .pg = pg,
- .db_cls = cls
- };
-
- work = (GNUNET_YES == include_recoup) ? work_wp : work_op;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Getting transactions for coin %s\n",
- TALER_B2S (coin_pub));
- for (unsigned int i = 0; NULL != work[i].statement; i++)
- {
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- work[i].statement,
- params,
- work[i].cb,
- &chc);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Coin %s yielded %d transactions of type %s\n",
- TALER_B2S (coin_pub),
- qs,
- work[i].statement);
- if ( (0 > qs) ||
- (chc.failed) )
- {
- if (NULL != chc.head)
- common_free_coin_transaction_list (cls,
- chc.head);
- *tlp = NULL;
- if (chc.failed)
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
- }
- }
- *tlp = chc.head;
- if (NULL == chc.head)
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
-
-
-/**
- * Closure for #handle_wt_result.
- */
-struct WireTransferResultContext
-{
- /**
- * Function to call on each result.
- */
- TALER_EXCHANGEDB_AggregationDataCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to #GNUNET_SYSERR on serious errors.
- */
- int status;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results. Helper function
- * for #postgres_lookup_wire_transfer().
- *
- * @param cls closure of type `struct WireTransferResultContext *`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-handle_wt_result (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct WireTransferResultContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t rowid;
- struct GNUNET_HashCode h_contract_terms;
- struct GNUNET_HashCode h_wire;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_MerchantPublicKeyP merchant_pub;
- struct GNUNET_TIME_Absolute exec_time;
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount deposit_fee;
- struct TALER_DenominationPublicKey denom_pub;
- json_t *wire;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("aggregation_serial_id", &rowid),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &h_contract_terms),
- TALER_PQ_result_spec_json ("wire", &wire),
- GNUNET_PQ_result_spec_auto_from_type ("h_wire", &h_wire),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub", &coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub", &merchant_pub),
- TALER_PQ_result_spec_absolute_time ("execution_date", &exec_time),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee", &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit", &deposit_fee),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->status = GNUNET_SYSERR;
- return;
- }
- ctx->cb (ctx->cb_cls,
- rowid,
- &merchant_pub,
- &h_wire,
- wire,
- exec_time,
- &h_contract_terms,
- &denom_pub,
- &coin_pub,
- &amount_with_fee,
- &deposit_fee);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Lookup the list of Taler transactions that were aggregated
- * into a wire transfer by the respective @a wtid.
- *
- * @param cls closure
- * @param wtid the raw wire transfer identifier we used
- * @param cb function to call on each transaction found
- * @param cb_cls closure for @a cb
- * @return query status of the transaction
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_wire_transfer (
- void *cls,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- TALER_EXCHANGEDB_AggregationDataCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_end
- };
- struct WireTransferResultContext ctx;
- enum GNUNET_DB_QueryStatus qs;
-
- ctx.cb = cb;
- ctx.cb_cls = cb_cls;
- ctx.pg = pg;
- ctx.status = GNUNET_OK;
- /* check if the melt record exists and get it */
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_transactions",
- params,
- &handle_wt_result,
- &ctx);
- if (GNUNET_OK != ctx.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Try to find the wire transfer details for a deposit operation.
- * If we did not execute the deposit yet, return when it is supposed
- * to be executed.
- *
- * @param cls closure
- * @param h_contract_terms hash of the proposal data
- * @param h_wire hash of merchant wire details
- * @param coin_pub public key of deposited coin
- * @param merchant_pub merchant public key
- * @param[out] pending set to true if the transaction is still pending
- * @param[out] wtid wire transfer identifier, only set if @a pending is false
- * @param[out] coin_contribution how much did the coin we asked about
- * contribute to the total transfer value? (deposit value including fee)
- * @param[out] coin_fee how much did the exchange charge for the deposit fee
- * @param[out] execution_time when was the transaction done, or
- * when we expect it to be done (if @a pending is false)
- * @param[out] kyc set to the kyc status of the receiver (if @a pending)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_transfer_by_deposit (
- void *cls,
- const struct GNUNET_HashCode *h_contract_terms,
- const struct GNUNET_HashCode *h_wire,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- bool *pending,
- struct TALER_WireTransferIdentifierRawP *wtid,
- struct GNUNET_TIME_Absolute *exec_time,
- struct TALER_Amount *amount_with_fee,
- struct TALER_Amount *deposit_fee,
- struct TALER_EXCHANGEDB_KycStatus *kyc)
-{
- struct PostgresClosure *pg = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (coin_pub),
- GNUNET_PQ_query_param_auto_from_type (h_contract_terms),
- GNUNET_PQ_query_param_auto_from_type (h_wire),
- GNUNET_PQ_query_param_auto_from_type (merchant_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
- wtid),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- exec_time),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- deposit_fee),
- GNUNET_PQ_result_spec_end
- };
-
- /* check if the aggregation record exists and get it */
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_deposit_wtid",
- params,
- rs);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- *pending = false;
- memset (kyc,
- 0,
- sizeof (*kyc));
- kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
- kyc->ok = true;
- return qs;
- }
- if (0 > qs)
- return qs;
- *pending = true;
- memset (wtid,
- 0,
- sizeof (*wtid));
- GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "lookup_deposit_wtid returned 0 matching rows\n");
- {
- /* Check if transaction exists in deposits, so that we just
- do not have a WTID yet, if so, do call the CB with a NULL wtid
- and return #GNUNET_YES! */
- uint8_t ok8 = 0;
- struct GNUNET_PQ_ResultSpec rs2[] = {
- GNUNET_PQ_result_spec_uint64 ("payment_target_uuid",
- &kyc->payment_target_uuid),
- GNUNET_PQ_result_spec_auto_from_type ("kyc_ok",
- &ok8),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- deposit_fee),
- TALER_PQ_result_spec_absolute_time ("wire_deadline",
- exec_time),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_deposit_for_wtid",
- params,
- rs2);
- kyc->type = TALER_EXCHANGEDB_KYC_DEPOSIT;
- kyc->ok = (0 != ok8);
- return qs;
- }
-}
-
-
-/**
- * Function called to insert aggregation information into the DB.
- *
- * @param cls closure
- * @param wtid the raw wire transfer identifier we used
- * @param deposit_serial_id row in the deposits table for which this is aggregation data
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_aggregation_tracking (
- void *cls,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- unsigned long long deposit_serial_id)
-{
- struct PostgresClosure *pg = cls;
- uint64_t rid = deposit_serial_id;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&rid),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_aggregation_tracking",
- params);
-}
-
-
-/**
- * Obtain wire fee from database.
- *
- * @param cls closure
- * @param type type of wire transfer the fee applies for
- * @param date for which date do we want the fee?
- * @param[out] start_date when does the fee go into effect
- * @param[out] end_date when does the fee end being valid
- * @param[out] wire_fee how high is the wire transfer fee
- * @param[out] closing_fee how high is the closing fee
- * @param[out] master_sig signature over the above by the exchange master key
- * @return status of the transaction
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_wire_fee (void *cls,
- const char *type,
- struct GNUNET_TIME_Absolute date,
- struct GNUNET_TIME_Absolute *start_date,
- struct GNUNET_TIME_Absolute *end_date,
- struct TALER_Amount *wire_fee,
- struct TALER_Amount *closing_fee,
- struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (type),
- TALER_PQ_query_param_absolute_time (&date),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_absolute_time ("start_date", start_date),
- TALER_PQ_result_spec_absolute_time ("end_date", end_date),
- TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee", wire_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee", closing_fee),
- GNUNET_PQ_result_spec_auto_from_type ("master_sig", master_sig),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_wire_fee",
- params,
- rs);
-}
-
-
-/**
- * Insert wire transfer fee into database.
- *
- * @param cls closure
- * @param type type of wire transfer this fee applies for
- * @param start_date when does the fee go into effect
- * @param end_date when does the fee end being valid
- * @param wire_fee how high is the wire transfer fee
- * @param closing_fee how high is the closing fee
- * @param master_sig signature over the above by the exchange master key
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_wire_fee (void *cls,
- const char *type,
- struct GNUNET_TIME_Absolute start_date,
- struct GNUNET_TIME_Absolute end_date,
- const struct TALER_Amount *wire_fee,
- const struct TALER_Amount *closing_fee,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (type),
- TALER_PQ_query_param_absolute_time (&start_date),
- TALER_PQ_query_param_absolute_time (&end_date),
- TALER_PQ_query_param_amount (wire_fee),
- TALER_PQ_query_param_amount (closing_fee),
- GNUNET_PQ_query_param_auto_from_type (master_sig),
- GNUNET_PQ_query_param_end
- };
- struct TALER_Amount wf;
- struct TALER_Amount cf;
- struct TALER_MasterSignatureP sig;
- struct GNUNET_TIME_Absolute sd;
- struct GNUNET_TIME_Absolute ed;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = postgres_get_wire_fee (pg,
- type,
- start_date,
- &sd,
- &ed,
- &wf,
- &cf,
- &sig);
- if (qs < 0)
- return qs;
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- if (0 != GNUNET_memcmp (&sig,
- master_sig))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (0 != TALER_amount_cmp (wire_fee,
- &wf))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (0 != TALER_amount_cmp (closing_fee,
- &cf))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if ( (sd.abs_value_us != start_date.abs_value_us) ||
- (ed.abs_value_us != end_date.abs_value_us) )
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- /* equal record already exists */
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- }
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_wire_fee",
- params);
-}
-
-
-/**
- * Closure for #reserve_expired_cb().
- */
-struct ExpiredReserveContext
-{
- /**
- * Function to call for each expired reserve.
- */
- TALER_EXCHANGEDB_ReserveExpiredCallback rec;
-
- /**
- * Closure to give to @e rec.
- */
- void *rec_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to #GNUNET_SYSERR on error.
- */
- int status;
-};
-
-
-/**
- * Function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-reserve_expired_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ExpiredReserveContext *erc = cls;
- struct PostgresClosure *pg = erc->pg;
- int ret;
-
- ret = GNUNET_OK;
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_TIME_Absolute exp_date;
- char *account_details;
- struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_Amount remaining_balance;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_absolute_time ("expiration_date",
- &exp_date),
- GNUNET_PQ_result_spec_string ("account_details",
- &account_details),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- TALER_PQ_RESULT_SPEC_AMOUNT ("current_balance",
- &remaining_balance),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ret = GNUNET_SYSERR;
- break;
- }
- ret = erc->rec (erc->rec_cls,
- &reserve_pub,
- &remaining_balance,
- account_details,
- exp_date);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
- erc->status = ret;
-}
-
-
-/**
- * Obtain information about expired reserves and their
- * remaining balances.
- *
- * @param cls closure of the plugin
- * @param now timestamp based on which we decide expiration
- * @param rec function to call on expired reserves
- * @param rec_cls closure for @a rec
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_expired_reserves (void *cls,
- struct GNUNET_TIME_Absolute now,
- TALER_EXCHANGEDB_ReserveExpiredCallback rec,
- void *rec_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_end
- };
- struct ExpiredReserveContext ectx;
- enum GNUNET_DB_QueryStatus qs;
-
- ectx.rec = rec;
- ectx.rec_cls = rec_cls;
- ectx.pg = pg;
- ectx.status = GNUNET_OK;
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "get_expired_reserves",
- params,
- &reserve_expired_cb,
- &ectx);
- if (GNUNET_OK != ectx.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Insert reserve close operation into database.
- *
- * @param cls closure
- * @param reserve_pub which reserve is this about?
- * @param execution_date when did we perform the transfer?
- * @param receiver_account to which account do we transfer?
- * @param wtid wire transfer details
- * @param amount_with_fee amount we charged to the reserve
- * @param closing_fee how high is the closing fee
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_reserve_closed (
- void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- struct GNUNET_TIME_Absolute execution_date,
- const char *receiver_account,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *closing_fee)
-{
- struct PostgresClosure *pg = cls;
- struct TALER_EXCHANGEDB_Reserve reserve;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (reserve_pub),
- TALER_PQ_query_param_absolute_time (&execution_date),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- GNUNET_PQ_query_param_string (receiver_account),
- TALER_PQ_query_param_amount (amount_with_fee),
- TALER_PQ_query_param_amount (closing_fee),
- GNUNET_PQ_query_param_end
- };
- enum TALER_AmountArithmeticResult ret;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "reserves_close_insert",
- params);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- return qs;
-
- /* update reserve balance */
- reserve.pub = *reserve_pub;
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- (qs = reserves_get_internal (cls,
- &reserve)))
- {
- /* Existence should have been checked before we got here... */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- qs = GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
- }
- ret = TALER_amount_subtract (&reserve.balance,
- &reserve.balance,
- amount_with_fee);
- if (ret < 0)
- {
- /* The reserve history was checked to make sure there is enough of a balance
- left before we tried this; however, concurrent operations may have changed
- the situation by now. We should re-try the transaction. */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Closing of reserve `%s' refused due to balance mismatch. Retrying.\n",
- TALER_B2S (reserve_pub));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- GNUNET_break (TALER_AAR_RESULT_ZERO == ret);
- return reserves_update (cls,
- &reserve);
-}
-
-
-/**
- * Function called to insert wire transfer commit data into the DB.
- *
- * @param cls closure
- * @param type type of the wire transfer (i.e. "iban")
- * @param buf buffer with wire transfer preparation data
- * @param buf_size number of bytes in @a buf
- * @return query status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_wire_prepare_data_insert (void *cls,
- const char *type,
- const char *buf,
- size_t buf_size)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (type),
- GNUNET_PQ_query_param_fixed_size (buf, buf_size),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "wire_prepare_data_insert",
- params);
-}
-
-
-/**
- * Function called to mark wire transfer commit data as finished.
- *
- * @param cls closure
- * @param rowid which entry to mark as finished
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_wire_prepare_data_mark_finished (
- void *cls,
- uint64_t rowid)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&rowid),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "wire_prepare_data_mark_done",
- params);
-}
-
-
-/**
- * Function called to mark wire transfer commit data as failed.
- *
- * @param cls closure
- * @param rowid which entry to mark as failed
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_wire_prepare_data_mark_failed (
- void *cls,
- uint64_t rowid)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&rowid),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "wire_prepare_data_mark_failed",
- params);
-}
-
-
-/**
- * Closure for #prewire_cb().
- */
-struct PrewireContext
-{
- /**
- * Function to call on each result.
- */
- TALER_EXCHANGEDB_WirePreparationIterator cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * #GNUNET_OK if everything went fine.
- */
- enum GNUNET_GenericReturnValue status;
-};
-
-
-/**
- * Invoke the callback for each result.
- *
- * @param cls a `struct MissingWireContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-prewire_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct PrewireContext *pc = cls;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- uint64_t prewire_uuid;
- char *type;
- void *buf = NULL;
- size_t buf_size;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("prewire_uuid",
- &prewire_uuid),
- GNUNET_PQ_result_spec_string ("type",
- &type),
- GNUNET_PQ_result_spec_variable_size ("buf",
- &buf,
- &buf_size),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- pc->status = GNUNET_SYSERR;
- return;
- }
- pc->cb (pc->cb_cls,
- prewire_uuid,
- type,
- buf,
- buf_size);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Function called to get an unfinished wire transfer
- * preparation data. Fetches at most one item.
- *
- * @param cls closure
- * @param start_row offset to query table at
- * @param limit maximum number of results to return
- * @param cb function to call for ONE unfinished item
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_wire_prepare_data_get (void *cls,
- uint64_t start_row,
- uint64_t limit,
- TALER_EXCHANGEDB_WirePreparationIterator cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&start_row),
- GNUNET_PQ_query_param_uint64 (&limit),
- GNUNET_PQ_query_param_end
- };
- struct PrewireContext pc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "wire_prepare_data_get",
- params,
- &prewire_cb,
- &pc);
- if (GNUNET_OK != pc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Starts a READ COMMITTED transaction where we transiently violate the foreign
- * constraints on the "wire_out" table as we insert aggregations
- * and only add the wire transfer out at the end.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-postgres_start_deferred_wire_out (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_ExecuteStatement es[] = {
- GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL READ COMMITTED"),
- GNUNET_PQ_make_execute ("SET CONSTRAINTS wire_out_ref DEFERRED"),
- GNUNET_PQ_EXECUTE_STATEMENT_END
- };
-
- if (GNUNET_SYSERR ==
- postgres_preflight (pg))
- return GNUNET_SYSERR;
- if (GNUNET_OK !=
- GNUNET_PQ_exec_statements (pg->conn,
- es))
- {
- TALER_LOG_ERROR (
- "Failed to defer wire_out_ref constraint on transaction\n");
- GNUNET_break (0);
- postgres_rollback (pg);
- return GNUNET_SYSERR;
- }
- pg->transaction_name = "deferred wire out";
- return GNUNET_OK;
-}
-
-
-/**
- * Store information about an outgoing wire transfer that was executed.
- *
- * @param cls closure
- * @param date time of the wire transfer
- * @param wtid subject of the wire transfer
- * @param wire_account details about the receiver account of the wire transfer
- * @param exchange_account_section configuration section of the exchange specifying the
- * exchange's bank account being used
- * @param amount amount that was transmitted
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_store_wire_transfer_out (
- void *cls,
- struct GNUNET_TIME_Absolute date,
- const struct TALER_WireTransferIdentifierRawP *wtid,
- const json_t *wire_account,
- const char *exchange_account_section,
- const struct TALER_Amount *amount)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_absolute_time (&date),
- GNUNET_PQ_query_param_auto_from_type (wtid),
- TALER_PQ_query_param_json (wire_account),
- GNUNET_PQ_query_param_string (exchange_account_section),
- TALER_PQ_query_param_amount (amount),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_wire_out",
- params);
-}
-
-
-/**
- * Function called to perform "garbage collection" on the
- * database, expiring records we no longer require.
- *
- * @param cls closure
- * @return #GNUNET_OK on success,
- * #GNUNET_SYSERR on DB errors
- */
-static int
-postgres_gc (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Absolute long_ago;
- struct GNUNET_PQ_QueryParam params_none[] = {
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_QueryParam params_time[] = {
- TALER_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_QueryParam params_ancient_time[] = {
- TALER_PQ_query_param_absolute_time (&long_ago),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_Context *conn;
- int ret;
-
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- /* Keep wire fees for 10 years, that should always
- be enough _and_ they are tiny so it does not
- matter to make this tight */
- long_ago = GNUNET_TIME_absolute_subtract (now,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_YEARS,
- 10));
- {
- struct GNUNET_PQ_PreparedStatement ps[] = {
- /* Used in #postgres_gc() */
- GNUNET_PQ_make_prepare ("gc_prewire",
- "DELETE"
- " FROM prewire"
- " WHERE finished=true;",
- 0),
- GNUNET_PQ_make_prepare ("gc_reserves",
- "DELETE"
- " FROM reserves"
- " WHERE gc_date < $1"
- " AND current_balance_val = 0"
- " AND current_balance_frac = 0;",
- 1),
- GNUNET_PQ_make_prepare ("gc_wire_fee",
- "DELETE"
- " FROM wire_fee"
- " WHERE end_date < $1;",
- 1),
- GNUNET_PQ_make_prepare ("gc_denominations",
- "DELETE"
- " FROM denominations"
- " WHERE expire_legal < $1;",
- 1),
- GNUNET_PQ_PREPARED_STATEMENT_END
- };
-
- conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
- "exchangedb-postgres",
- NULL,
- NULL,
- ps);
- }
- if (NULL == conn)
- return GNUNET_SYSERR;
- ret = GNUNET_OK;
- if ( (0 > GNUNET_PQ_eval_prepared_non_select (conn,
- "gc_reserves",
- params_time)) ||
- (0 > GNUNET_PQ_eval_prepared_non_select (conn,
- "gc_prewire",
- params_none)) ||
- (0 > GNUNET_PQ_eval_prepared_non_select (conn,
- "gc_wire_fee",
- params_ancient_time)) )
- ret = GNUNET_SYSERR;
- /* This one may fail due to foreign key constraints from
- recoup and reserves_out tables to known_coins; these
- are NOT using 'ON DROP CASCADE' and might keep denomination
- keys alive for a bit longer, thus causing this statement
- to fail. */
- (void) GNUNET_PQ_eval_prepared_non_select (conn,
- "gc_denominations",
- params_time);
- GNUNET_PQ_disconnect (conn);
- return ret;
-}
-
-
-/**
- * Closure for #deposit_serial_helper_cb().
- */
-struct DepositSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_DepositCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct DepositSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-deposit_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct DepositSerialContext *dsc = cls;
- struct PostgresClosure *pg = dsc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_Deposit deposit;
- struct GNUNET_TIME_Absolute exchange_timestamp;
- struct TALER_DenominationPublicKey denom_pub;
- uint8_t done = 0;
- uint64_t rowid;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &deposit.amount_with_fee),
- TALER_PQ_result_spec_absolute_time ("wallet_timestamp",
- &deposit.timestamp),
- TALER_PQ_result_spec_absolute_time ("exchange_timestamp",
- &exchange_timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &deposit.merchant_pub),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &deposit.coin.coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &deposit.csig),
- TALER_PQ_result_spec_absolute_time ("refund_deadline",
- &deposit.refund_deadline),
- TALER_PQ_result_spec_absolute_time ("wire_deadline",
- &deposit.wire_deadline),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &deposit.h_contract_terms),
- TALER_PQ_result_spec_json ("wire",
- &deposit.receiver_wire_account),
- GNUNET_PQ_result_spec_auto_from_type ("done",
- &done),
- GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
- &rowid),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- dsc->status = GNUNET_SYSERR;
- return;
- }
- ret = dsc->cb (dsc->cb_cls,
- rowid,
- exchange_timestamp,
- deposit.timestamp,
- &deposit.merchant_pub,
- &denom_pub,
- &deposit.coin.coin_pub,
- &deposit.csig,
- &deposit.amount_with_fee,
- &deposit.h_contract_terms,
- deposit.refund_deadline,
- deposit.wire_deadline,
- deposit.receiver_wire_account,
- done);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Select deposits above @a serial_id in monotonically increasing
- * order.
- *
- * @param cls closure
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call on each result
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_deposits_above_serial_id (
- void *cls,
- uint64_t serial_id,
- TALER_EXCHANGEDB_DepositCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct DepositSerialContext dsc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "audit_get_deposits_incr",
- params,
- &deposit_serial_helper_cb,
- &dsc);
- if (GNUNET_OK != dsc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #refreshs_serial_helper_cb().
- */
-struct RefreshsSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_RefreshesCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct RefreshsSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-refreshs_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct RefreshsSerialContext *rsc = cls;
- struct PostgresClosure *pg = rsc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_DenominationPublicKey denom_pub;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_Amount amount_with_fee;
- uint32_t noreveal_index;
- uint64_t rowid;
- struct TALER_RefreshCommitmentP rc;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
- &coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_sig",
- &coin_sig),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- GNUNET_PQ_result_spec_uint32 ("noreveal_index",
- &noreveal_index),
- GNUNET_PQ_result_spec_uint64 ("melt_serial_id",
- &rowid),
- GNUNET_PQ_result_spec_auto_from_type ("rc",
- &rc),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- rsc->status = GNUNET_SYSERR;
- return;
- }
- ret = rsc->cb (rsc->cb_cls,
- rowid,
- &denom_pub,
- &coin_pub,
- &coin_sig,
- &amount_with_fee,
- noreveal_index,
- &rc);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Select refresh sessions above @a serial_id in monotonically increasing
- * order.
- *
- * @param cls closure
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call on each result
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_refreshes_above_serial_id (
- void *cls,
- uint64_t serial_id,
- TALER_EXCHANGEDB_RefreshesCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct RefreshsSerialContext rsc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "audit_get_refresh_commitments_incr",
- params,
- &refreshs_serial_helper_cb,
- &rsc);
- if (GNUNET_OK != rsc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #refunds_serial_helper_cb().
- */
-struct RefundsSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_RefundCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct RefundsSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-refunds_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct RefundsSerialContext *rsc = cls;
- struct PostgresClosure *pg = rsc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_EXCHANGEDB_Refund refund;
- struct TALER_DenominationPublicKey denom_pub;
- uint64_t rowid;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("merchant_pub",
- &refund.details.merchant_pub),
- GNUNET_PQ_result_spec_auto_from_type ("merchant_sig",
- &refund.details.merchant_sig),
- GNUNET_PQ_result_spec_auto_from_type ("h_contract_terms",
- &refund.details.h_contract_terms),
- GNUNET_PQ_result_spec_uint64 ("rtransaction_id",
- &refund.details.rtransaction_id),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &refund.coin.coin_pub),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &refund.details.refund_amount),
- GNUNET_PQ_result_spec_uint64 ("refund_serial_id",
- &rowid),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- rsc->status = GNUNET_SYSERR;
- return;
- }
- ret = rsc->cb (rsc->cb_cls,
- rowid,
- &denom_pub,
- &refund.coin.coin_pub,
- &refund.details.merchant_pub,
- &refund.details.merchant_sig,
- &refund.details.h_contract_terms,
- refund.details.rtransaction_id,
- &refund.details.refund_amount);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Select refunds above @a serial_id in monotonically increasing
- * order.
- *
- * @param cls closure
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call on each result
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_refunds_above_serial_id (
- void *cls,
- uint64_t serial_id,
- TALER_EXCHANGEDB_RefundCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct RefundsSerialContext rsc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "audit_get_refunds_incr",
- params,
- &refunds_serial_helper_cb,
- &rsc);
- if (GNUNET_OK != rsc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #reserves_in_serial_helper_cb().
- */
-struct ReservesInSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_ReserveInCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct ReservesInSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-reserves_in_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReservesInSerialContext *risc = cls;
- struct PostgresClosure *pg = risc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_Amount credit;
- char *sender_account_details;
- struct GNUNET_TIME_Absolute execution_date;
- uint64_t rowid;
- uint64_t wire_reference;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- GNUNET_PQ_result_spec_uint64 ("wire_reference",
- &wire_reference),
- TALER_PQ_RESULT_SPEC_AMOUNT ("credit",
- &credit),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &execution_date),
- GNUNET_PQ_result_spec_string ("sender_account_details",
- &sender_account_details),
- GNUNET_PQ_result_spec_uint64 ("reserve_in_serial_id",
- &rowid),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- risc->status = GNUNET_SYSERR;
- return;
- }
- ret = risc->cb (risc->cb_cls,
- rowid,
- &reserve_pub,
- &credit,
- sender_account_details,
- wire_reference,
- execution_date);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Select inbound wire transfers into reserves_in above @a serial_id
- * in monotonically increasing order.
- *
- * @param cls closure
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call on each result
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_reserves_in_above_serial_id (
- void *cls,
- uint64_t serial_id,
- TALER_EXCHANGEDB_ReserveInCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct ReservesInSerialContext risc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "audit_reserves_in_get_transactions_incr",
- params,
- &reserves_in_serial_helper_cb,
- &risc);
- if (GNUNET_OK != risc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Select inbound wire transfers into reserves_in above @a serial_id
- * in monotonically increasing order by account.
- *
- * @param cls closure
- * @param account_name name of the account to select by
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call on each result
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_reserves_in_above_serial_id_by_account (
- void *cls,
- const char *account_name,
- uint64_t serial_id,
- TALER_EXCHANGEDB_ReserveInCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_string (account_name),
- GNUNET_PQ_query_param_end
- };
- struct ReservesInSerialContext risc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "audit_reserves_in_get_transactions_incr_by_account",
- params,
- &reserves_in_serial_helper_cb,
- &risc);
- if (GNUNET_OK != risc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #reserves_out_serial_helper_cb().
- */
-struct ReservesOutSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_WithdrawCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct ReservesOutSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-reserves_out_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReservesOutSerialContext *rosc = cls;
- struct PostgresClosure *pg = rosc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct GNUNET_HashCode h_blind_ev;
- struct TALER_DenominationPublicKey denom_pub;
- struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_ReserveSignatureP reserve_sig;
- struct GNUNET_TIME_Absolute execution_date;
- struct TALER_Amount amount_with_fee;
- uint64_t rowid;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
- &h_blind_ev),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_sig",
- &reserve_sig),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &execution_date),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount_with_fee),
- GNUNET_PQ_result_spec_uint64 ("reserve_out_serial_id",
- &rowid),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- rosc->status = GNUNET_SYSERR;
- return;
- }
- ret = rosc->cb (rosc->cb_cls,
- rowid,
- &h_blind_ev,
- &denom_pub,
- &reserve_pub,
- &reserve_sig,
- execution_date,
- &amount_with_fee);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Select withdraw operations from reserves_out above @a serial_id
- * in monotonically increasing order.
- *
- * @param cls closure
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call on each result
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_withdrawals_above_serial_id (
- void *cls,
- uint64_t serial_id,
- TALER_EXCHANGEDB_WithdrawCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct ReservesOutSerialContext rosc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "audit_get_reserves_out_incr",
- params,
- &reserves_out_serial_helper_cb,
- &rosc);
- if (GNUNET_OK != rosc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #wire_out_serial_helper_cb().
- */
-struct WireOutSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_WireTransferOutCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct WireOutSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-wire_out_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct WireOutSerialContext *wosc = cls;
- struct PostgresClosure *pg = wosc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t rowid;
- struct GNUNET_TIME_Absolute date;
- struct TALER_WireTransferIdentifierRawP wtid;
- json_t *wire;
- struct TALER_Amount amount;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("wireout_uuid",
- &rowid),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &date),
- GNUNET_PQ_result_spec_auto_from_type ("wtid_raw",
- &wtid),
- TALER_PQ_result_spec_json ("wire_target",
- &wire),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &amount),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- wosc->status = GNUNET_SYSERR;
- return;
- }
- ret = wosc->cb (wosc->cb_cls,
- rowid,
- date,
- &wtid,
- wire,
- &amount);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Function called to select all wire transfers the exchange
- * executed.
- *
- * @param cls closure
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call for ONE unfinished item
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_wire_out_above_serial_id (
- void *cls,
- uint64_t serial_id,
- TALER_EXCHANGEDB_WireTransferOutCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct WireOutSerialContext wosc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "audit_get_wire_incr",
- params,
- &wire_out_serial_helper_cb,
- &wosc);
- if (GNUNET_OK != wosc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Function called to select all wire transfers the exchange
- * executed by account.
- *
- * @param cls closure
- * @param account_name account to select
- * @param serial_id highest serial ID to exclude (select strictly larger)
- * @param cb function to call for ONE unfinished item
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_wire_out_above_serial_id_by_account (
- void *cls,
- const char *account_name,
- uint64_t serial_id,
- TALER_EXCHANGEDB_WireTransferOutCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_string (account_name),
- GNUNET_PQ_query_param_end
- };
- struct WireOutSerialContext wosc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "audit_get_wire_incr_by_account",
- params,
- &wire_out_serial_helper_cb,
- &wosc);
- if (GNUNET_OK != wosc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #recoup_serial_helper_cb().
- */
-struct RecoupSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_RecoupCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- enum GNUNET_GenericReturnValue status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct RecoupSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-recoup_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct RecoupSerialContext *psc = cls;
- struct PostgresClosure *pg = psc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t rowid;
- struct TALER_ReservePublicKeyP reserve_pub;
- struct TALER_CoinPublicInfo coin;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_DenominationBlindingKeyP coin_blind;
- struct TALER_Amount amount;
- struct TALER_DenominationPublicKey denom_pub;
- struct GNUNET_HashCode h_blind_ev;
- struct GNUNET_TIME_Absolute timestamp;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("recoup_uuid",
- &rowid),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin.coin_pub),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &coin_sig),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &coin_blind),
- GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
- &h_blind_ev),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &coin.denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &coin.denom_sig.rsa_signature),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &amount),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- psc->status = GNUNET_SYSERR;
- return;
- }
- ret = psc->cb (psc->cb_cls,
- rowid,
- timestamp,
- &amount,
- &reserve_pub,
- &coin,
- &denom_pub,
- &coin_sig,
- &coin_blind);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Function called to select recoup requests the exchange
- * received, ordered by serial ID (monotonically increasing).
- *
- * @param cls closure
- * @param serial_id lowest serial ID to include (select larger or equal)
- * @param cb function to call for ONE unfinished item
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_recoup_above_serial_id (
- void *cls,
- uint64_t serial_id,
- TALER_EXCHANGEDB_RecoupCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct RecoupSerialContext psc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "recoup_get_incr",
- params,
- &recoup_serial_helper_cb,
- &psc);
- if (GNUNET_OK != psc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #recoup_refresh_serial_helper_cb().
- */
-struct RecoupRefreshSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_RecoupRefreshCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct RecoupRefreshSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-recoup_refresh_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct RecoupRefreshSerialContext *psc = cls;
- struct PostgresClosure *pg = psc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t rowid;
- struct TALER_CoinSpendPublicKeyP old_coin_pub;
- struct TALER_CoinPublicInfo coin;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_DenominationBlindingKeyP coin_blind;
- struct TALER_DenominationPublicKey denom_pub;
- struct GNUNET_HashCode old_denom_pub_hash;
- struct TALER_Amount amount;
- struct GNUNET_HashCode h_blind_ev;
- struct GNUNET_TIME_Absolute timestamp;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("recoup_refresh_uuid",
- &rowid),
- TALER_PQ_result_spec_absolute_time ("timestamp",
- &timestamp),
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
- &old_coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("old_denom_pub_hash",
- &old_denom_pub_hash),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin.coin_pub),
- GNUNET_PQ_result_spec_auto_from_type ("coin_sig",
- &coin_sig),
- GNUNET_PQ_result_spec_auto_from_type ("coin_blind",
- &coin_blind),
- GNUNET_PQ_result_spec_rsa_public_key ("denom_pub",
- &denom_pub.rsa_public_key),
- GNUNET_PQ_result_spec_auto_from_type ("h_blind_ev",
- &h_blind_ev),
- GNUNET_PQ_result_spec_auto_from_type ("denom_pub_hash",
- &coin.denom_pub_hash),
- GNUNET_PQ_result_spec_rsa_signature ("denom_sig",
- &coin.denom_sig.rsa_signature),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &amount),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- psc->status = GNUNET_SYSERR;
- return;
- }
- ret = psc->cb (psc->cb_cls,
- rowid,
- timestamp,
- &amount,
- &old_coin_pub,
- &old_denom_pub_hash,
- &coin,
- &denom_pub,
- &coin_sig,
- &coin_blind);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Function called to select recoup requests the exchange received for
- * refreshed coins, ordered by serial ID (monotonically increasing).
- *
- * @param cls closure
- * @param serial_id lowest serial ID to include (select larger or equal)
- * @param cb function to call for ONE unfinished item
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_recoup_refresh_above_serial_id (
- void *cls,
- uint64_t serial_id,
- TALER_EXCHANGEDB_RecoupRefreshCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct RecoupRefreshSerialContext psc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "recoup_refresh_get_incr",
- params,
- &recoup_refresh_serial_helper_cb,
- &psc);
- if (GNUNET_OK != psc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Closure for #reserve_closed_serial_helper_cb().
- */
-struct ReserveClosedSerialContext
-{
-
- /**
- * Callback to call.
- */
- TALER_EXCHANGEDB_ReserveClosedCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin's context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Status code, set to #GNUNET_SYSERR on hard errors.
- */
- int status;
-};
-
-
-/**
- * Helper function to be called with the results of a SELECT statement
- * that has returned @a num_results results.
- *
- * @param cls closure of type `struct ReserveClosedSerialContext`
- * @param result the postgres result
- * @param num_results the number of results in @a result
- */
-static void
-reserve_closed_serial_helper_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct ReserveClosedSerialContext *rcsc = cls;
- struct PostgresClosure *pg = rcsc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- uint64_t rowid;
- struct TALER_ReservePublicKeyP reserve_pub;
- char *receiver_account;
- struct TALER_WireTransferIdentifierRawP wtid;
- struct TALER_Amount amount_with_fee;
- struct TALER_Amount closing_fee;
- struct GNUNET_TIME_Absolute execution_date;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("close_uuid",
- &rowid),
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- &reserve_pub),
- TALER_PQ_result_spec_absolute_time ("execution_date",
- &execution_date),
- GNUNET_PQ_result_spec_auto_from_type ("wtid",
- &wtid),
- GNUNET_PQ_result_spec_string ("receiver_account",
- &receiver_account),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount",
- &amount_with_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
- &closing_fee),
- GNUNET_PQ_result_spec_end
- };
- int ret;
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- rcsc->status = GNUNET_SYSERR;
- return;
- }
- ret = rcsc->cb (rcsc->cb_cls,
- rowid,
- execution_date,
- &amount_with_fee,
- &closing_fee,
- &reserve_pub,
- receiver_account,
- &wtid);
- GNUNET_PQ_cleanup_result (rs);
- if (GNUNET_OK != ret)
- break;
- }
-}
-
-
-/**
- * Function called to select reserve close operations the aggregator
- * triggered, ordered by serial ID (monotonically increasing).
- *
- * @param cls closure
- * @param serial_id lowest serial ID to include (select larger or equal)
- * @param cb function to call for ONE unfinished item
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_reserve_closed_above_serial_id (
- void *cls,
- uint64_t serial_id,
- TALER_EXCHANGEDB_ReserveClosedCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial_id),
- GNUNET_PQ_query_param_end
- };
- struct ReserveClosedSerialContext rcsc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "reserves_close_get_incr",
- params,
- &reserve_closed_serial_helper_cb,
- &rcsc);
- if (GNUNET_OK != rcsc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Function called to add a request for an emergency recoup for a
- * coin. The funds are to be added back to the reserve. The function
- * should return the @a deadline by which the exchange will trigger a
- * wire transfer back to the customer's account for the reserve.
- *
- * @param cls closure
- * @param reserve_pub public key of the reserve that is being refunded
- * @param coin information about the coin
- * @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
- * @param coin_blind blinding key of the coin
- * @param amount total amount to be paid back
- * @param h_blind_ev hash of the blinded coin's envelope (must match reserves_out entry)
- * @param timestamp current time (rounded)
- * @return transaction result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_recoup_request (
- void *cls,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const struct TALER_CoinPublicInfo *coin,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind,
- const struct TALER_Amount *amount,
- const struct GNUNET_HashCode *h_blind_ev,
- struct GNUNET_TIME_Absolute timestamp)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_TIME_Absolute expiry;
- struct GNUNET_TIME_Absolute gc;
- struct TALER_EXCHANGEDB_Reserve reserve;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),
- GNUNET_PQ_query_param_auto_from_type (coin_sig),
- GNUNET_PQ_query_param_auto_from_type (coin_blind),
- TALER_PQ_query_param_amount (amount),
- TALER_PQ_query_param_absolute_time (&timestamp),
- GNUNET_PQ_query_param_auto_from_type (h_blind_ev),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- /* now store actual recoup information */
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "recoup_insert",
- params);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
-
- /* Update reserve balance */
- reserve.pub = *reserve_pub;
- qs = reserves_get_internal (pg,
- &reserve);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- if (0 >
- TALER_amount_add (&reserve.balance,
- &reserve.balance,
- amount))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Inserting recoup for coin %s\n",
- TALER_B2S (&coin->coin_pub));
- gc = GNUNET_TIME_absolute_add (timestamp,
- pg->legal_reserve_expiration_time);
- reserve.gc = GNUNET_TIME_absolute_max (gc,
- reserve.gc);
- (void) GNUNET_TIME_round_abs (&reserve.gc);
- expiry = GNUNET_TIME_absolute_add (timestamp,
- pg->idle_reserve_expiration_time);
- reserve.expiry = GNUNET_TIME_absolute_max (expiry,
- reserve.expiry);
- (void) GNUNET_TIME_round_abs (&reserve.expiry);
- qs = reserves_update (pg,
- &reserve);
- if (0 >= qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- return qs;
-}
-
-
-/**
- * Function called to add a request for an emergency recoup for a
- * refreshed coin. The funds are to be added back to the original coin
- * (which is implied via @a h_blind_ev, see the prepared statement
- * "recoup_by_old_coin" used in #postgres_get_coin_transactions()).
- *
- * @param cls closure
- * @param coin public information about the refreshed coin
- * @param coin_sig signature of the coin of type #TALER_SIGNATURE_WALLET_COIN_RECOUP
- * @param coin_blind blinding key of the coin
- * @param h_blind_ev blinded envelope, as calculated by the exchange
- * @param amount total amount to be paid back
- * @param h_blind_ev hash of the blinded coin's envelope (must match reserves_out entry)
- * @param timestamp a timestamp to store
- * @return transaction result status
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_recoup_refresh_request (
- void *cls,
- const struct TALER_CoinPublicInfo *coin,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind,
- const struct TALER_Amount *amount,
- const struct GNUNET_HashCode *h_blind_ev,
- struct GNUNET_TIME_Absolute timestamp)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (&coin->coin_pub),
- GNUNET_PQ_query_param_auto_from_type (coin_sig),
- GNUNET_PQ_query_param_auto_from_type (coin_blind),
- TALER_PQ_query_param_amount (amount),
- TALER_PQ_query_param_absolute_time (&timestamp),
- GNUNET_PQ_query_param_auto_from_type (h_blind_ev),
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- /* now store actual recoup information */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Inserting recoup-refresh for coin %s\n",
- TALER_B2S (&coin->coin_pub));
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "recoup_refresh_insert",
- params);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- return qs;
-}
-
-
-/**
- * Obtain information about which reserve a coin was generated
- * from given the hash of the blinded coin.
- *
- * @param cls closure
- * @param h_blind_ev hash of the blinded coin
- * @param[out] reserve_pub set to information about the reserve (on success only)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_reserve_by_h_blind (void *cls,
- const struct GNUNET_HashCode *h_blind_ev,
- struct TALER_ReservePublicKeyP *reserve_pub)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (h_blind_ev),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("reserve_pub",
- reserve_pub),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "reserve_by_h_blind",
- params,
- rs);
-}
-
-
-/**
- * Obtain information about which old coin a coin was refreshed
- * given the hash of the blinded (fresh) coin.
- *
- * @param cls closure
- * @param h_blind_ev hash of the blinded coin
- * @param[out] old_coin_pub set to information about the old coin (on success only)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_old_coin_by_h_blind (void *cls,
- const struct GNUNET_HashCode *h_blind_ev,
- struct TALER_CoinSpendPublicKeyP *old_coin_pub)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (h_blind_ev),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("old_coin_pub",
- old_coin_pub),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "old_coin_by_h_blind",
- params,
- rs);
-}
-
-
-/**
- * Store information that a denomination key was revoked
- * in the database.
- *
- * @param cls closure
- * @param denom_pub_hash hash of the revoked denomination key
- * @param master_sig signature affirming the revocation
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_denomination_revocation (
- void *cls,
- const struct GNUNET_HashCode *denom_pub_hash,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- GNUNET_PQ_query_param_auto_from_type (master_sig),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "denomination_revocation_insert",
- params);
-}
-
-
-/**
- * Obtain information about a denomination key's revocation from
- * the database.
- *
- * @param cls closure
- * @param denom_pub_hash hash of the revoked denomination key
- * @param[out] master_sig signature affirming the revocation
- * @param[out] rowid row where the information is stored
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_denomination_revocation (
- void *cls,
- const struct GNUNET_HashCode *denom_pub_hash,
- struct TALER_MasterSignatureP *master_sig,
- uint64_t *rowid)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (denom_pub_hash),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("master_sig", master_sig),
- GNUNET_PQ_result_spec_uint64 ("denom_revocations_serial_id", rowid),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "denomination_revocation_get",
- params,
- rs);
-}
-
-
-/**
- * Closure for #missing_wire_cb().
- */
-struct MissingWireContext
-{
- /**
- * Function to call per result.
- */
- TALER_EXCHANGEDB_WireMissingCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Set to #GNUNET_SYSERR on error.
- */
- int status;
-};
-
-
-/**
- * Invoke the callback for each result.
- *
- * @param cls a `struct MissingWireContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-missing_wire_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct MissingWireContext *mwc = cls;
- struct PostgresClosure *pg = mwc->pg;
-
- while (0 < num_results)
- {
- uint64_t rowid;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_Amount amount;
- json_t *wire;
- struct GNUNET_TIME_Absolute deadline;
- uint8_t tiny;
- uint8_t done;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("deposit_serial_id",
- &rowid),
- GNUNET_PQ_result_spec_auto_from_type ("coin_pub",
- &coin_pub),
- TALER_PQ_RESULT_SPEC_AMOUNT ("amount_with_fee",
- &amount),
- TALER_PQ_result_spec_json ("wire",
- &wire),
- TALER_PQ_result_spec_absolute_time ("wire_deadline",
- &deadline),
- GNUNET_PQ_result_spec_auto_from_type ("tiny",
- &tiny),
- GNUNET_PQ_result_spec_auto_from_type ("done",
- &done),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- --num_results))
- {
- GNUNET_break (0);
- mwc->status = GNUNET_SYSERR;
- return;
- }
- mwc->cb (mwc->cb_cls,
- rowid,
- &coin_pub,
- &amount,
- wire,
- deadline,
- tiny,
- done);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Select all of those deposits in the database for which we do
- * not have a wire transfer (or a refund) and which should have
- * been deposited between @a start_date and @a end_date.
- *
- * @param cls closure
- * @param start_date lower bound on the requested wire execution date
- * @param end_date upper bound on the requested wire execution date
- * @param cb function to call on all such deposits
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_deposits_missing_wire (void *cls,
- struct GNUNET_TIME_Absolute start_date,
- struct GNUNET_TIME_Absolute end_date,
- TALER_EXCHANGEDB_WireMissingCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- TALER_PQ_query_param_absolute_time (&start_date),
- TALER_PQ_query_param_absolute_time (&end_date),
- GNUNET_PQ_query_param_end
- };
- struct MissingWireContext mwc = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "deposits_get_overdue",
- params,
- &missing_wire_cb,
- &mwc);
- if (GNUNET_OK != mwc.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Check the last date an auditor was modified.
- *
- * @param cls closure
- * @param auditor_pub key to look up information for
- * @param[out] last_date last modification date to auditor status
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_auditor_timestamp (
- void *cls,
- const struct TALER_AuditorPublicKeyP *auditor_pub,
- struct GNUNET_TIME_Absolute *last_date)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (auditor_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_absolute_time ("last_change",
- last_date),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_auditor_timestamp",
- params,
- rs);
-}
-
-
-/**
- * Lookup current state of an auditor.
- *
- * @param cls closure
- * @param auditor_pub key to look up information for
- * @param[out] auditor_url set to the base URL of the auditor's REST API; memory to be
- * released by the caller!
- * @param[out] enabled set if the auditor is currently in use
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_auditor_status (
- void *cls,
- const struct TALER_AuditorPublicKeyP *auditor_pub,
- char **auditor_url,
- bool *enabled)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (auditor_pub),
- GNUNET_PQ_query_param_end
- };
- uint8_t enabled8 = 0;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("auditor_url",
- auditor_url),
- GNUNET_PQ_result_spec_auto_from_type ("is_active",
- &enabled8),
- GNUNET_PQ_result_spec_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_auditor_status",
- params,
- rs);
- *enabled = (0 != enabled8);
- return qs;
-}
-
-
-/**
- * Insert information about an auditor that will audit this exchange.
- *
- * @param cls closure
- * @param auditor_pub key of the auditor
- * @param auditor_url base URL of the auditor's REST service
- * @param auditor_name name of the auditor (for humans)
- * @param start_date date when the auditor was added by the offline system
- * (only to be used for replay detection)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_auditor (void *cls,
- const struct TALER_AuditorPublicKeyP *auditor_pub,
- const char *auditor_url,
- const char *auditor_name,
- struct GNUNET_TIME_Absolute start_date)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (auditor_pub),
- GNUNET_PQ_query_param_string (auditor_name),
- GNUNET_PQ_query_param_string (auditor_url),
- GNUNET_PQ_query_param_absolute_time (&start_date),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_auditor",
- params);
-}
-
-
-/**
- * Update information about an auditor that will audit this exchange.
- *
- * @param cls closure
- * @param auditor_pub key of the auditor (primary key for the existing record)
- * @param auditor_url base URL of the auditor's REST service, to be updated
- * @param auditor_name name of the auditor (for humans)
- * @param change_date date when the auditor status was last changed
- * (only to be used for replay detection)
- * @param enabled true to enable, false to disable
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_auditor (void *cls,
- const struct TALER_AuditorPublicKeyP *auditor_pub,
- const char *auditor_url,
- const char *auditor_name,
- struct GNUNET_TIME_Absolute change_date,
- bool enabled)
-{
- struct PostgresClosure *pg = cls;
- uint8_t enabled8 = enabled ? 1 : 0;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (auditor_pub),
- GNUNET_PQ_query_param_string (auditor_url),
- GNUNET_PQ_query_param_string (auditor_name),
- GNUNET_PQ_query_param_auto_from_type (&enabled8),
- GNUNET_PQ_query_param_absolute_time (&change_date),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_auditor",
- params);
-}
-
-
-/**
- * Check the last date an exchange wire account was modified.
- *
- * @param cls closure
- * @param payto_uri key to look up information for
- * @param[out] last_date last modification date to auditor status
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_wire_timestamp (void *cls,
- const char *payto_uri,
- struct GNUNET_TIME_Absolute *last_date)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (payto_uri),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_absolute_time ("last_change",
- last_date),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_wire_timestamp",
- params,
- rs);
-}
-
-
-/**
- * Insert information about an wire account used by this exchange.
- *
- * @param cls closure
- * @param payto_uri wire account of the exchange
- * @param start_date date when the account was added by the offline system
- * (only to be used for replay detection)
- * @param master_sig public signature affirming the existence of the account,
- * must be of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_wire (void *cls,
- const char *payto_uri,
- struct GNUNET_TIME_Absolute start_date,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (payto_uri),
- GNUNET_PQ_query_param_auto_from_type (master_sig),
- GNUNET_PQ_query_param_absolute_time (&start_date),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_wire",
- params);
-}
-
-
-/**
- * Update information about a wire account of the exchange.
- *
- * @param cls closure
- * @param payto_uri account the update is about
- * @param change_date date when the account status was last changed
- * (only to be used for replay detection)
- * @param enabled true to enable, false to disable (the actual change)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_update_wire (void *cls,
- const char *payto_uri,
- struct GNUNET_TIME_Absolute change_date,
- bool enabled)
-{
- struct PostgresClosure *pg = cls;
- uint8_t enabled8 = enabled ? 1 : 0;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (payto_uri),
- GNUNET_PQ_query_param_auto_from_type (&enabled8),
- GNUNET_PQ_query_param_absolute_time (&change_date),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_wire",
- params);
-}
-
-
-/**
- * Closure for #get_wire_accounts_cb().
- */
-struct GetWireAccountsContext
-{
- /**
- * Function to call per result.
- */
- TALER_EXCHANGEDB_WireAccountCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Flag set to #GNUNET_OK as long as everything is fine.
- */
- int status;
-
-};
-
-
-/**
- * Invoke the callback for each result.
- *
- * @param cls a `struct MissingWireContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-get_wire_accounts_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct GetWireAccountsContext *ctx = cls;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- char *payto_uri;
- struct TALER_MasterSignatureP master_sig;
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_string ("payto_uri",
- &payto_uri),
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &master_sig),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->status = GNUNET_SYSERR;
- return;
- }
- ctx->cb (ctx->cb_cls,
- payto_uri,
- &master_sig);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Obtain information about the enabled wire accounts of the exchange.
- *
- * @param cls closure
- * @param cb function to call on each account
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_wire_accounts (void *cls,
- TALER_EXCHANGEDB_WireAccountCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GetWireAccountsContext ctx = {
- .cb = cb,
- .cb_cls = cb_cls,
- .status = GNUNET_OK
- };
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "get_wire_accounts",
- params,
- &get_wire_accounts_cb,
- &ctx);
- if (GNUNET_OK != ctx.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-
-}
-
-
-/**
- * Closure for #get_wire_fees_cb().
- */
-struct GetWireFeesContext
-{
- /**
- * Function to call per result.
- */
- TALER_EXCHANGEDB_WireFeeCallback cb;
-
- /**
- * Closure for @e cb.
- */
- void *cb_cls;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Flag set to #GNUNET_OK as long as everything is fine.
- */
- int status;
-
-};
-
-
-/**
- * Invoke the callback for each result.
- *
- * @param cls a `struct MissingWireContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-get_wire_fees_cb (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct GetWireFeesContext *ctx = cls;
- struct PostgresClosure *pg = ctx->pg;
-
- for (unsigned int i = 0; i < num_results; i++)
- {
- struct TALER_MasterSignatureP master_sig;
- struct TALER_Amount wire_fee;
- struct TALER_Amount closing_fee;
- struct GNUNET_TIME_Absolute start_date;
- struct GNUNET_TIME_Absolute end_date;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
- &wire_fee),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
- &closing_fee),
- TALER_PQ_result_spec_absolute_time ("start_date",
- &start_date),
- TALER_PQ_result_spec_absolute_time ("end_date",
- &end_date),
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- &master_sig),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- ctx->status = GNUNET_SYSERR;
- return;
- }
- ctx->cb (ctx->cb_cls,
- &wire_fee,
- &closing_fee,
- start_date,
- end_date,
- &master_sig);
- GNUNET_PQ_cleanup_result (rs);
- }
-}
-
-
-/**
- * Obtain information about the fee structure of the exchange for
- * a given @a wire_method
- *
- * @param cls closure
- * @param wire_method which wire method to obtain fees for
- * @param cb function to call on each account
- * @param cb_cls closure for @a cb
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_get_wire_fees (void *cls,
- const char *wire_method,
- TALER_EXCHANGEDB_WireFeeCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (wire_method),
- GNUNET_PQ_query_param_end
- };
- struct GetWireFeesContext ctx = {
- .cb = cb,
- .cb_cls = cb_cls,
- .pg = pg,
- .status = GNUNET_OK
- };
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "get_wire_fees",
- params,
- &get_wire_fees_cb,
- &ctx);
- if (GNUNET_OK != ctx.status)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Store information about a revoked online signing key.
- *
- * @param cls closure
- * @param exchange_pub exchange online signing key that was revoked
- * @param master_sig signature affirming the revocation
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_signkey_revocation (
- void *cls,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (exchange_pub),
- GNUNET_PQ_query_param_auto_from_type (master_sig),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_signkey_revocation",
- params);
-}
-
-
-/**
- * Obtain information about a revoked online signing key.
- *
- * @param cls closure
- * @param exchange_pub exchange online signing key
- * @param[out] master_sig set to signature affirming the revocation (if revoked)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_signkey_revocation (
- void *cls,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (exchange_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("master_sig",
- master_sig),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_signkey_revocation",
- params,
- rs);
-}
-
-
-/**
- * Lookup information about current denomination key.
- *
- * @param cls closure
- * @param h_denom_pub hash of the denomination public key
- * @param[out] meta set to various meta data about the key
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_denomination_key (
- void *cls,
- const struct GNUNET_HashCode *h_denom_pub,
- struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_absolute_time ("valid_from",
- &meta->start),
- TALER_PQ_result_spec_absolute_time ("expire_withdraw",
- &meta->expire_withdraw),
- TALER_PQ_result_spec_absolute_time ("expire_deposit",
- &meta->expire_deposit),
- TALER_PQ_result_spec_absolute_time ("expire_legal",
- &meta->expire_legal),
- TALER_PQ_RESULT_SPEC_AMOUNT ("coin",
- &meta->value),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_withdraw",
- &meta->fee_withdraw),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_deposit",
- &meta->fee_deposit),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refresh",
- &meta->fee_refresh),
- TALER_PQ_RESULT_SPEC_AMOUNT ("fee_refund",
- &meta->fee_refund),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_denomination_key",
- params,
- rs);
-}
-
-
-/**
- * Activate denomination key, turning it into a "current" or "valid"
- * denomination key by adding the master signature.
- *
- * @param cls closure
- * @param h_denom_pub hash of the denomination public key
- * @param denom_pub the actual denomination key
- * @param meta meta data about the denomination
- * @param master_sig master signature to add
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_add_denomination_key (
- void *cls,
- const struct GNUNET_HashCode *h_denom_pub,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam iparams[] = {
- GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
- GNUNET_PQ_query_param_rsa_public_key (denom_pub->rsa_public_key),
- GNUNET_PQ_query_param_auto_from_type (master_sig),
- TALER_PQ_query_param_absolute_time (&meta->start),
- TALER_PQ_query_param_absolute_time (&meta->expire_withdraw),
- TALER_PQ_query_param_absolute_time (&meta->expire_deposit),
- TALER_PQ_query_param_absolute_time (&meta->expire_legal),
- TALER_PQ_query_param_amount (&meta->value),
- TALER_PQ_query_param_amount (&meta->fee_withdraw),
- TALER_PQ_query_param_amount (&meta->fee_deposit),
- TALER_PQ_query_param_amount (&meta->fee_refresh),
- TALER_PQ_query_param_amount (&meta->fee_refund),
- GNUNET_PQ_query_param_end
- };
-
- /* Sanity check: ensure fees match coin currency */
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (&meta->value,
- &meta->fee_withdraw));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (&meta->value,
- &meta->fee_deposit));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (&meta->value,
- &meta->fee_refresh));
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (&meta->value,
- &meta->fee_refund));
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "denomination_insert",
- iparams);
-}
-
-
-/**
- * Add signing key.
- *
- * @param cls closure
- * @param exchange_pub the exchange online signing public key
- * @param meta meta data about @a exchange_pub
- * @param master_sig master signature to add
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_activate_signing_key (
- void *cls,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam iparams[] = {
- GNUNET_PQ_query_param_auto_from_type (exchange_pub),
- TALER_PQ_query_param_absolute_time (&meta->start),
- TALER_PQ_query_param_absolute_time (&meta->expire_sign),
- TALER_PQ_query_param_absolute_time (&meta->expire_legal),
- GNUNET_PQ_query_param_auto_from_type (master_sig),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_signkey",
- iparams);
-}
-
-
-/**
- * Lookup signing key meta data.
- *
- * @param cls closure
- * @param exchange_pub the exchange online signing public key
- * @param[out] meta meta data about @a exchange_pub
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_signing_key (
- void *cls,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- struct TALER_EXCHANGEDB_SignkeyMetaData *meta)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (exchange_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_result_spec_absolute_time ("valid_from",
- &meta->start),
- TALER_PQ_result_spec_absolute_time ("expire_sign",
- &meta->expire_sign),
- TALER_PQ_result_spec_absolute_time ("expire_legal",
- &meta->expire_legal),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "lookup_signing_key",
- params,
- rs);
-}
-
-
-/**
- * Insert information about an auditor auditing a denomination key.
- *
- * @param cls closure
- * @param h_denom_pub the audited denomination
- * @param auditor_pub the auditor's key
- * @param auditor_sig signature affirming the auditor's audit activity
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_auditor_denom_sig (
- void *cls,
- const struct GNUNET_HashCode *h_denom_pub,
- const struct TALER_AuditorPublicKeyP *auditor_pub,
- const struct TALER_AuditorSignatureP *auditor_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (auditor_pub),
- GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
- GNUNET_PQ_query_param_auto_from_type (auditor_sig),
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_auditor_denom_sig",
- params);
-}
-
-
-/**
- * Select information about an auditor auditing a denomination key.
- *
- * @param cls closure
- * @param h_denom_pub the audited denomination
- * @param auditor_pub the auditor's key
- * @param[out] auditor_sig set to signature affirming the auditor's audit activity
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_select_auditor_denom_sig (
- void *cls,
- const struct GNUNET_HashCode *h_denom_pub,
- const struct TALER_AuditorPublicKeyP *auditor_pub,
- struct TALER_AuditorSignatureP *auditor_sig)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_auto_from_type (auditor_pub),
- GNUNET_PQ_query_param_auto_from_type (h_denom_pub),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_auto_from_type ("auditor_sig",
- auditor_sig),
- GNUNET_PQ_result_spec_end
- };
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "select_auditor_denom_sig",
- params,
- rs);
-}
-
-
-/**
- * Closure for #wire_fee_by_time_helper()
- */
-struct WireFeeLookupContext
-{
-
- /**
- * Set to the wire fee. Set to invalid if fees conflict over
- * the given time period.
- */
- struct TALER_Amount *wire_fee;
-
- /**
- * Set to the closing fee. Set to invalid if fees conflict over
- * the given time period.
- */
- struct TALER_Amount *closing_fee;
-
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-};
-
-
-/**
- * Helper function for #postgres_iterate_denomination_info().
- * Calls the callback with each denomination key.
- *
- * @param cls a `struct DenomIteratorContext`
- * @param result db results
- * @param num_results number of results in @a result
- */
-static void
-wire_fee_by_time_helper (void *cls,
- PGresult *result,
- unsigned int num_results)
-{
- struct WireFeeLookupContext *wlc = cls;
- struct PostgresClosure *pg = wlc->pg;
-
- for (unsigned int i = 0; i<num_results; i++)
- {
- struct TALER_Amount wf;
- struct TALER_Amount cf;
- struct GNUNET_PQ_ResultSpec rs[] = {
- TALER_PQ_RESULT_SPEC_AMOUNT ("wire_fee",
- &wf),
- TALER_PQ_RESULT_SPEC_AMOUNT ("closing_fee",
- &cf),
- GNUNET_PQ_result_spec_end
- };
-
- if (GNUNET_OK !=
- GNUNET_PQ_extract_result (result,
- rs,
- i))
- {
- GNUNET_break (0);
- /* invalidate */
- memset (wlc->wire_fee,
- 0,
- sizeof (struct TALER_Amount));
- memset (wlc->closing_fee,
- 0,
- sizeof (struct TALER_Amount));
- return;
- }
- if (0 == i)
- {
- *wlc->wire_fee = wf;
- *wlc->closing_fee = cf;
- continue;
- }
- if ( (GNUNET_YES !=
- TALER_amount_cmp_currency (&wf,
- wlc->wire_fee)) ||
- (GNUNET_YES !=
- TALER_amount_cmp_currency (&cf,
- wlc->closing_fee)) ||
- (0 !=
- TALER_amount_cmp (&wf,
- wlc->wire_fee)) ||
- (0 !=
- TALER_amount_cmp (&cf,
- wlc->closing_fee)) )
- {
- /* invalidate */
- memset (wlc->wire_fee,
- 0,
- sizeof (struct TALER_Amount));
- memset (wlc->closing_fee,
- 0,
- sizeof (struct TALER_Amount));
- return;
- }
- }
-}
-
-
-/**
- * Lookup information about known wire fees. Finds all applicable
- * fees in the given range. If they are identical, returns the
- * respective @a wire_fee and @a closing_fee. If any of the fees
- * differ between @a start_time and @a end_time, the transaction
- * succeeds BUT returns an invalid amount for both fees.
- *
- * @param cls closure
- * @param wire_method the wire method to lookup fees for
- * @param start_time starting time of fee
- * @param end_time end time of fee
- * @param[out] wire_fee wire fee for that time period; if
- * different wire fee exists within this time
- * period, an 'invalid' amount is returned.
- * @param[out] closing_fee wire fee for that time period; if
- * different wire fee exists within this time
- * period, an 'invalid' amount is returned.
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_wire_fee_by_time (
- void *cls,
- const char *wire_method,
- struct GNUNET_TIME_Absolute start_time,
- struct GNUNET_TIME_Absolute end_time,
- struct TALER_Amount *wire_fee,
- struct TALER_Amount *closing_fee)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (wire_method),
- GNUNET_PQ_query_param_absolute_time (&start_time),
- GNUNET_PQ_query_param_absolute_time (&end_time),
- GNUNET_PQ_query_param_end
- };
- struct WireFeeLookupContext wlc = {
- .wire_fee = wire_fee,
- .closing_fee = closing_fee,
- .pg = pg,
- };
-
- return GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- "lookup_wire_fee_by_time",
- params,
- &wire_fee_by_time_helper,
- &wlc);
-}
-
-
-/**
- * Lookup the latest serial number of @a table. Used in
- * exchange-auditor database replication.
- *
- * @param cls closure
- * @param table table for which we should return the serial
- * @param[out] serial latest serial number in use
- * @return transaction status code, GNUNET_DB_STATUS_HARD_ERROR if
- * @a table does not have a serial number
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_serial_by_table (void *cls,
- enum TALER_EXCHANGEDB_ReplicatedTable table,
- uint64_t *serial)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("serial",
- serial),
- GNUNET_PQ_result_spec_end
- };
- const char *statement;
-
- switch (table)
- {
- case TALER_EXCHANGEDB_RT_DENOMINATIONS:
- statement = "select_serial_by_table_denominations";
- break;
- case TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS:
- statement = "select_serial_by_table_denomination_revocations";
- break;
- case TALER_EXCHANGEDB_RT_RESERVES:
- statement = "select_serial_by_table_reserves";
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_IN:
- statement = "select_serial_by_table_reserves_in";
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_CLOSE:
- statement = "select_serial_by_table_reserves_close";
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_OUT:
- statement = "select_serial_by_table_reserves_out";
- break;
- case TALER_EXCHANGEDB_RT_AUDITORS:
- statement = "select_serial_by_table_auditors";
- break;
- case TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS:
- statement = "select_serial_by_table_auditor_denom_sigs";
- break;
- case TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS:
- statement = "select_serial_by_table_exchange_sign_keys";
- break;
- case TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS:
- statement = "select_serial_by_table_signkey_revocations";
- break;
- case TALER_EXCHANGEDB_RT_KNOWN_COINS:
- statement = "select_serial_by_table_known_coins";
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS:
- statement = "select_serial_by_table_refresh_commitments";
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS:
- statement = "select_serial_by_table_refresh_revealed_coins";
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS:
- statement = "select_serial_by_table_refresh_transfer_keys";
- break;
- case TALER_EXCHANGEDB_RT_DEPOSITS:
- statement = "select_serial_by_table_deposits";
- break;
- case TALER_EXCHANGEDB_RT_REFUNDS:
- statement = "select_serial_by_table_refunds";
- break;
- case TALER_EXCHANGEDB_RT_WIRE_OUT:
- statement = "select_serial_by_table_wire_out";
- break;
- case TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING:
- statement = "select_serial_by_table_aggregation_tracking";
- break;
- case TALER_EXCHANGEDB_RT_WIRE_FEE:
- statement = "select_serial_by_table_wire_fee";
- break;
- case TALER_EXCHANGEDB_RT_RECOUP:
- statement = "select_serial_by_table_recoup";
- break;
- case TALER_EXCHANGEDB_RT_RECOUP_REFRESH:
- statement = "select_serial_by_table_recoup_refresh";
- break;
- default:
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- statement,
- params,
- rs);
-}
-
-
-/**
- * Closure for callbacks used by #postgres_lookup_records_by_table.
- */
-struct LookupRecordsByTableContext
-{
- /**
- * Plugin context.
- */
- struct PostgresClosure *pg;
-
- /**
- * Function to call with the results.
- */
- TALER_EXCHANGEDB_ReplicationCallback cb;
-
- /**
- * Closure for @a cb.
- */
- void *cb_cls;
-
- /**
- * Set to true on errors.
- */
- bool error;
-};
-
-
-#include "lrbt_callbacks.c"
-
-
-/**
- * Lookup records above @a serial number in @a table. Used in
- * exchange-auditor database replication.
- *
- * @param cls closure
- * @param table table for which we should return the serial
- * @param serial largest serial number to exclude
- * @param cb function to call on the records
- * @param cb_cls closure for @a cb
- * @return transaction status code, GNUNET_DB_STATUS_HARD_ERROR if
- * @a table does not have a serial number
- */
-static enum GNUNET_DB_QueryStatus
-postgres_lookup_records_by_table (void *cls,
- enum TALER_EXCHANGEDB_ReplicatedTable table,
- uint64_t serial,
- TALER_EXCHANGEDB_ReplicationCallback cb,
- void *cb_cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_uint64 (&serial),
- GNUNET_PQ_query_param_end
- };
- struct LookupRecordsByTableContext ctx = {
- .pg = pg,
- .cb = cb,
- .cb_cls = cb_cls
- };
- GNUNET_PQ_PostgresResultHandler rh;
- const char *statement;
- enum GNUNET_DB_QueryStatus qs;
-
- switch (table)
- {
- case TALER_EXCHANGEDB_RT_DENOMINATIONS:
- statement = "select_above_serial_by_table_denominations";
- rh = &lrbt_cb_table_denominations;
- break;
- case TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS:
- statement = "select_above_serial_by_table_denomination_revocations";
- rh = &lrbt_cb_table_denomination_revocations;
- break;
- case TALER_EXCHANGEDB_RT_RESERVES:
- statement = "select_above_serial_by_table_reserves";
- rh = &lrbt_cb_table_reserves;
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_IN:
- statement = "select_above_serial_by_table_reserves_in";
- rh = &lrbt_cb_table_reserves_in;
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_CLOSE:
- statement = "select_above_serial_by_table_reserves_close";
- rh = &lrbt_cb_table_reserves_close;
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_OUT:
- statement = "select_above_serial_by_table_reserves_out";
- rh = &lrbt_cb_table_reserves_out;
- break;
- case TALER_EXCHANGEDB_RT_AUDITORS:
- statement = "select_above_serial_by_table_auditors";
- rh = &lrbt_cb_table_auditors;
- break;
- case TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS:
- statement = "select_above_serial_by_table_auditor_denom_sigs";
- rh = &lrbt_cb_table_auditor_denom_sigs;
- break;
- case TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS:
- statement = "select_above_serial_by_table_exchange_sign_keys";
- rh = &lrbt_cb_table_exchange_sign_keys;
- break;
- case TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS:
- statement = "select_above_serial_by_table_signkey_revocations";
- rh = &lrbt_cb_table_signkey_revocations;
- break;
- case TALER_EXCHANGEDB_RT_KNOWN_COINS:
- statement = "select_above_serial_by_table_known_coins";
- rh = &lrbt_cb_table_known_coins;
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS:
- statement = "select_above_serial_by_table_refresh_commitments";
- rh = &lrbt_cb_table_refresh_commitments;
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS:
- statement = "select_above_serial_by_table_refresh_revealed_coins";
- rh = &lrbt_cb_table_refresh_revealed_coins;
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS:
- statement = "select_above_serial_by_table_refresh_transfer_keys";
- rh = &lrbt_cb_table_refresh_transfer_keys;
- break;
- case TALER_EXCHANGEDB_RT_DEPOSITS:
- statement = "select_above_serial_by_table_deposits";
- rh = &lrbt_cb_table_deposits;
- break;
- case TALER_EXCHANGEDB_RT_REFUNDS:
- statement = "select_above_serial_by_table_refunds";
- rh = &lrbt_cb_table_refunds;
- break;
- case TALER_EXCHANGEDB_RT_WIRE_OUT:
- statement = "select_above_serial_by_table_wire_out";
- rh = &lrbt_cb_table_wire_out;
- break;
- case TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING:
- statement = "select_above_serial_by_table_aggregation_tracking";
- rh = &lrbt_cb_table_aggregation_tracking;
- break;
- case TALER_EXCHANGEDB_RT_WIRE_FEE:
- statement = "select_above_serial_by_table_wire_fee";
- rh = &lrbt_cb_table_wire_fee;
- break;
- case TALER_EXCHANGEDB_RT_RECOUP:
- statement = "select_above_serial_by_table_recoup";
- rh = &lrbt_cb_table_recoup;
- break;
- case TALER_EXCHANGEDB_RT_RECOUP_REFRESH:
- statement = "select_above_serial_by_table_recoup_refresh";
- rh = &lrbt_cb_table_recoup_refresh;
- break;
- default:
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
- statement,
- params,
- rh,
- &ctx);
- if (qs < 0)
- return qs;
- if (ctx.error)
- return GNUNET_DB_STATUS_HARD_ERROR;
- return qs;
-}
-
-
-/**
- * Signature of helper functions of #postgres_insert_records_by_table.
- *
- * @param pg plugin context
- * @param td record to insert
- * @return transaction status code
- */
-typedef enum GNUNET_DB_QueryStatus
-(*InsertRecordCallback)(struct PostgresClosure *pg,
- const struct TALER_EXCHANGEDB_TableData *td);
-
-
-#include "irbt_callbacks.c"
-
-
-/**
- * Insert record set into @a table. Used in exchange-auditor database
- * replication.
- *
- * @param cls closure
- * @param td table data to insert
- * @return transaction status code, #GNUNET_DB_STATUS_HARD_ERROR if
- * @e table in @a tr is not supported
- */
-static enum GNUNET_DB_QueryStatus
-postgres_insert_records_by_table (void *cls,
- const struct TALER_EXCHANGEDB_TableData *td)
-{
- struct PostgresClosure *pg = cls;
- InsertRecordCallback rh;
-
- switch (td->table)
- {
- case TALER_EXCHANGEDB_RT_DENOMINATIONS:
- rh = &irbt_cb_table_denominations;
- break;
- case TALER_EXCHANGEDB_RT_DENOMINATION_REVOCATIONS:
- rh = &irbt_cb_table_denomination_revocations;
- break;
- case TALER_EXCHANGEDB_RT_RESERVES:
- rh = &irbt_cb_table_reserves;
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_IN:
- rh = &irbt_cb_table_reserves_in;
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_CLOSE:
- rh = &irbt_cb_table_reserves_close;
- break;
- case TALER_EXCHANGEDB_RT_RESERVES_OUT:
- rh = &irbt_cb_table_reserves_out;
- break;
- case TALER_EXCHANGEDB_RT_AUDITORS:
- rh = &irbt_cb_table_auditors;
- break;
- case TALER_EXCHANGEDB_RT_AUDITOR_DENOM_SIGS:
- rh = &irbt_cb_table_auditor_denom_sigs;
- break;
- case TALER_EXCHANGEDB_RT_EXCHANGE_SIGN_KEYS:
- rh = &irbt_cb_table_exchange_sign_keys;
- break;
- case TALER_EXCHANGEDB_RT_SIGNKEY_REVOCATIONS:
- rh = &irbt_cb_table_signkey_revocations;
- break;
- case TALER_EXCHANGEDB_RT_KNOWN_COINS:
- rh = &irbt_cb_table_known_coins;
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_COMMITMENTS:
- rh = &irbt_cb_table_refresh_commitments;
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_REVEALED_COINS:
- rh = &irbt_cb_table_refresh_revealed_coins;
- break;
- case TALER_EXCHANGEDB_RT_REFRESH_TRANSFER_KEYS:
- rh = &irbt_cb_table_refresh_transfer_keys;
- break;
- case TALER_EXCHANGEDB_RT_DEPOSITS:
- rh = &irbt_cb_table_deposits;
- break;
- case TALER_EXCHANGEDB_RT_REFUNDS:
- rh = &irbt_cb_table_refunds;
- break;
- case TALER_EXCHANGEDB_RT_WIRE_OUT:
- rh = &irbt_cb_table_wire_out;
- break;
- case TALER_EXCHANGEDB_RT_AGGREGATION_TRACKING:
- rh = &irbt_cb_table_aggregation_tracking;
- break;
- case TALER_EXCHANGEDB_RT_WIRE_FEE:
- rh = &irbt_cb_table_wire_fee;
- break;
- case TALER_EXCHANGEDB_RT_RECOUP:
- rh = &irbt_cb_table_recoup;
- break;
- case TALER_EXCHANGEDB_RT_RECOUP_REFRESH:
- rh = &irbt_cb_table_recoup_refresh;
- break;
- default:
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- return rh (pg,
- td);
-}
-
-
-/**
- * Function called to grab a work shard on an operation @a op. Runs in its
- * own transaction.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param job_name name of the operation to grab a word shard for
- * @param delay minimum age of a shard to grab
- * @param shard_size desired shard size
- * @param[out] start_row inclusive start row of the shard (returned)
- * @param[out] end_row exclusive end row of the shard (returned)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_begin_shard (void *cls,
- const char *job_name,
- struct GNUNET_TIME_Relative delay,
- uint64_t shard_size,
- uint64_t *start_row,
- uint64_t *end_row)
-{
- struct PostgresClosure *pg = cls;
-
- for (unsigned int retries = 0; retries<3; retries++)
- {
- if (GNUNET_OK !=
- postgres_start (pg,
- "begin_shard"))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- {
- struct GNUNET_TIME_Absolute past;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (job_name),
- GNUNET_PQ_query_param_absolute_time (&past),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("start_row",
- start_row),
- GNUNET_PQ_result_spec_uint64 ("end_row",
- end_row),
- GNUNET_PQ_result_spec_end
- };
-
- past = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (),
- delay);
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_open_shard",
- params,
- rs);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- {
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (job_name),
- GNUNET_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_uint64 (start_row),
- GNUNET_PQ_query_param_uint64 (end_row),
- GNUNET_PQ_query_param_end
- };
-
- now = GNUNET_TIME_absolute_get ();
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "reclaim_shard",
- params);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- goto commit;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_break (0); /* logic error, should be impossible */
- postgres_rollback (pg);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- break; /* actually unreachable */
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- break; /* continued below */
- }
- } /* get_open_shard */
-
- /* No open shard, find last 'end_row' */
- {
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (job_name),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint64 ("end_row",
- start_row),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_last_shard",
- params,
- rs);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- *start_row = 0; /* base-case: no shards yet */
- break; /* continued below */
- }
- *end_row = *start_row + shard_size;
- } /* get_last_shard */
-
- /* Claim fresh shard */
- {
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (job_name),
- GNUNET_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_uint64 (start_row),
- GNUNET_PQ_query_param_uint64 (end_row),
- GNUNET_PQ_query_param_end
- };
-
- now = GNUNET_TIME_absolute_get ();
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Trying to claim shard %llu-%llu\n",
- (unsigned long long) *start_row,
- (unsigned long long) *end_row);
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "claim_next_shard",
- params);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* continued below */
- break;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* someone else got this shard already,
- try again */
- postgres_rollback (pg);
- continue;
- }
- } /* claim_next_shard */
-
- /* commit */
-commit:
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = postgres_commit (pg);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
- }
- }
- } /* retry 'for' loop */
- return GNUNET_DB_STATUS_SOFT_ERROR;
-}
-
-
-/**
- * Function called to persist that work on a shard was completed.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param job_name name of the operation to grab a word shard for
- * @param start_row inclusive start row of the shard
- * @param end_row exclusive end row of the shard
- * @return transaction status code
- */
-enum GNUNET_DB_QueryStatus
-postgres_complete_shard (void *cls,
- const char *job_name,
- uint64_t start_row,
- uint64_t end_row)
-{
- struct PostgresClosure *pg = cls;
-
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (job_name),
- GNUNET_PQ_query_param_uint64 (&start_row),
- GNUNET_PQ_query_param_uint64 (&end_row),
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Completing shard %llu-%llu\n",
- (unsigned long long) start_row,
- (unsigned long long) end_row);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "complete_shard",
- params);
-}
-
-
-/**
- * Function called to grab a revolving work shard on an operation @a op. Runs
- * in its own transaction. Returns the oldest inactive shard.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param job_name name of the operation to grab a revolving shard for
- * @param shard_size desired shard size
- * @param shard_limit exclusive end of the shard range
- * @param[out] start_row inclusive start row of the shard (returned)
- * @param[out] end_row inclusive end row of the shard (returned)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-postgres_begin_revolving_shard (void *cls,
- const char *job_name,
- uint32_t shard_size,
- uint32_t shard_limit,
- uint32_t *start_row,
- uint32_t *end_row)
-{
- struct PostgresClosure *pg = cls;
-
- GNUNET_assert (shard_limit <= 1U + (uint32_t) INT32_MAX);
- GNUNET_assert (shard_limit > 0);
- GNUNET_assert (shard_size > 0);
- for (unsigned int retries = 0; retries<3; retries++)
- {
- if (GNUNET_OK !=
- postgres_start (pg,
- "begin_revolving_shard"))
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- /* First, find last 'end_row' */
- {
- enum GNUNET_DB_QueryStatus qs;
- uint32_t last_end;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (job_name),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint32 ("end_row",
- &last_end),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_last_revolving_shard",
- params,
- rs);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- *start_row = 1U + last_end;
- break;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- *start_row = 0; /* base-case: no shards yet */
- break; /* continued below */
- }
- } /* get_last_shard */
-
- if (*start_row < shard_limit)
- {
- /* Claim fresh shard */
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (job_name),
- GNUNET_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_uint32 (start_row),
- GNUNET_PQ_query_param_uint32 (end_row),
- GNUNET_PQ_query_param_end
- };
-
- *end_row = GNUNET_MIN (shard_limit,
- *start_row + shard_size - 1);
- now = GNUNET_TIME_absolute_get ();
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Trying to claim shard %llu-%llu\n",
- (unsigned long long) *start_row,
- (unsigned long long) *end_row);
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "create_revolving_shard",
- params);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* continued below (with commit) */
- break;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* someone else got this shard already,
- try again */
- postgres_rollback (pg);
- continue;
- }
- } /* end create fresh reovlving shard */
- else
- {
- /* claim oldest existing shard */
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (job_name),
- GNUNET_PQ_query_param_end
- };
- struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_uint32 ("start_row",
- start_row),
- GNUNET_PQ_result_spec_uint32 ("end_row",
- end_row),
- GNUNET_PQ_result_spec_end
- };
-
- qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
- "get_open_revolving_shard",
- params,
- rs);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* no open shards available */
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- {
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (job_name),
- GNUNET_PQ_query_param_absolute_time (&now),
- GNUNET_PQ_query_param_uint32 (start_row),
- GNUNET_PQ_query_param_uint32 (end_row),
- GNUNET_PQ_query_param_end
- };
-
- qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "reclaim_revolving_shard",
- params);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break; /* continue with commit */
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_break (0); /* logic error, should be impossible */
- postgres_rollback (pg);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- break; /* continue with commit */
- }
- } /* end claim oldest existing shard */
-
- /* commit */
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = postgres_commit (pg);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- postgres_rollback (pg);
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- postgres_rollback (pg);
- continue;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
- }
- }
- } /* retry 'for' loop */
- return GNUNET_DB_STATUS_SOFT_ERROR;
-}
-
-
-/**
- * Function called to release a revolving shard
- * back into the work pool. Clears the
- * "completed" flag.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param job_name name of the operation to grab a word shard for
- * @param start_row inclusive start row of the shard
- * @param end_row exclusive end row of the shard
- * @return transaction status code
- */
-enum GNUNET_DB_QueryStatus
-postgres_release_revolving_shard (void *cls,
- const char *job_name,
- uint32_t start_row,
- uint32_t end_row)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_string (job_name),
- GNUNET_PQ_query_param_uint32 (&start_row),
- GNUNET_PQ_query_param_uint32 (&end_row),
- GNUNET_PQ_query_param_end
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Releasing revolving shard %s %u-%u\n",
- job_name,
- (unsigned int) start_row,
- (unsigned int) end_row);
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "release_revolving_shard",
- params);
-}
-
-
-/**
- * Function called to delete all revolving shards.
- * To be used after a crash or when the shard size is
- * changed.
- *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @return transaction status code
- */
-enum GNUNET_DB_QueryStatus
-postgres_delete_revolving_shards (void *cls)
-{
- struct PostgresClosure *pg = cls;
- struct GNUNET_PQ_QueryParam params[] = {
- GNUNET_PQ_query_param_end
- };
-
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "delete_revolving_shards",
- params);
-}
-
-
-/**
* Initialize Postgres database subsystem.
*
* @param cls a configuration instance
@@ -11219,6 +321,7 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
struct PostgresClosure *pg;
struct TALER_EXCHANGEDB_Plugin *plugin;
+ unsigned long long dpl;
pg = GNUNET_new (struct PostgresClosure);
pg->cfg = cfg;
@@ -11230,206 +333,467 @@ libtaler_plugin_exchangedb_postgres_init (void *cls)
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchangedb-postgres",
- "CONFIG");
+ "SQL_DIR");
GNUNET_free (pg);
return NULL;
}
- if ( (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- "exchangedb",
- "IDLE_RESERVE_EXPIRATION_TIME",
- &pg->idle_reserve_expiration_time))
- ||
- (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (cfg,
- "exchangedb",
- "LEGAL_RESERVE_EXPIRATION_TIME",
- &pg->legal_reserve_expiration_time)) )
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "exchange",
+ "BASE_URL",
+ &pg->exchange_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "BASE_URL");
+ GNUNET_free (pg->sql_dir);
+ GNUNET_free (pg);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ "exchangedb",
+ "IDLE_RESERVE_EXPIRATION_TIME",
+ &pg->idle_reserve_expiration_time))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"exchangedb",
- "LEGAL/IDLE_RESERVE_EXPIRATION_TIME");
+ "IDLE_RESERVE_EXPIRATION_TIME");
+ GNUNET_free (pg->exchange_url);
GNUNET_free (pg->sql_dir);
GNUNET_free (pg);
return NULL;
}
if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ "exchangedb",
+ "LEGAL_RESERVE_EXPIRATION_TIME",
+ &pg->legal_reserve_expiration_time))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchangedb",
+ "LEGAL_RESERVE_EXPIRATION_TIME");
+ GNUNET_free (pg->exchange_url);
+ GNUNET_free (pg->sql_dir);
+ GNUNET_free (pg);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ "exchangedb",
+ "AGGREGATOR_SHIFT",
+ &pg->aggregator_shift))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ "exchangedb",
+ "AGGREGATOR_SHIFT");
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ "exchangedb",
+ "DEFAULT_PURSE_LIMIT",
+ &dpl))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ "exchangedb",
+ "DEFAULT_PURSE_LIMIT");
+ pg->def_purse_limit = 1;
+ }
+ else
+ {
+ pg->def_purse_limit = (uint32_t) dpl;
+ }
+
+ if (GNUNET_OK !=
TALER_config_get_currency (cfg,
&pg->currency))
{
+ GNUNET_free (pg->exchange_url);
GNUNET_free (pg->sql_dir);
GNUNET_free (pg);
return NULL;
}
if (GNUNET_OK !=
- internal_setup (pg,
- true))
+ TEH_PG_internal_setup (pg))
{
+ GNUNET_free (pg->exchange_url);
GNUNET_free (pg->currency);
GNUNET_free (pg->sql_dir);
GNUNET_free (pg);
return NULL;
}
-
plugin = GNUNET_new (struct TALER_EXCHANGEDB_Plugin);
plugin->cls = pg;
- plugin->drop_tables = &postgres_drop_tables;
- plugin->create_tables = &postgres_create_tables;
- plugin->start = &postgres_start;
- plugin->start_read_committed = &postgres_start_read_committed;
- plugin->commit = &postgres_commit;
- plugin->preflight = &postgres_preflight;
- plugin->rollback = &postgres_rollback;
- plugin->event_listen = &postgres_event_listen;
- plugin->event_listen_cancel = &postgres_event_listen_cancel;
- plugin->event_notify = &postgres_event_notify;
- plugin->insert_denomination_info = &postgres_insert_denomination_info;
- plugin->get_denomination_info = &postgres_get_denomination_info;
- plugin->iterate_denomination_info = &postgres_iterate_denomination_info;
- plugin->iterate_denominations = &postgres_iterate_denominations;
- plugin->iterate_active_signkeys = &postgres_iterate_active_signkeys;
- plugin->iterate_active_auditors = &postgres_iterate_active_auditors;
- plugin->iterate_auditor_denominations =
- &postgres_iterate_auditor_denominations;
- plugin->reserves_get = &postgres_reserves_get;
- plugin->set_kyc_ok = &postgres_set_kyc_ok;
- plugin->get_kyc_status = &postgres_get_kyc_status;
- plugin->select_kyc_status = &postgres_select_kyc_status;
- plugin->inselect_wallet_kyc_status = &postgres_inselect_wallet_kyc_status;
- plugin->reserves_in_insert = &postgres_reserves_in_insert;
- plugin->get_latest_reserve_in_reference =
- &postgres_get_latest_reserve_in_reference;
- plugin->get_withdraw_info = &postgres_get_withdraw_info;
- plugin->insert_withdraw_info = &postgres_insert_withdraw_info;
- plugin->get_reserve_history = &postgres_get_reserve_history;
- plugin->select_withdraw_amounts_by_account
- = &postgres_select_withdraw_amounts_by_account;
- plugin->free_reserve_history = &common_free_reserve_history;
- plugin->count_known_coins = &postgres_count_known_coins;
- plugin->ensure_coin_known = &postgres_ensure_coin_known;
- plugin->get_known_coin = &postgres_get_known_coin;
- plugin->get_coin_denomination = &postgres_get_coin_denomination;
- plugin->have_deposit = &postgres_have_deposit;
- plugin->mark_deposit_tiny = &postgres_mark_deposit_tiny;
- plugin->test_deposit_done = &postgres_test_deposit_done;
- plugin->mark_deposit_done = &postgres_mark_deposit_done;
- plugin->get_ready_deposit = &postgres_get_ready_deposit;
- plugin->iterate_matching_deposits = &postgres_iterate_matching_deposits;
- plugin->insert_deposit = &postgres_insert_deposit;
- plugin->insert_refund = &postgres_insert_refund;
- plugin->select_refunds_by_coin = &postgres_select_refunds_by_coin;
- plugin->insert_melt = &postgres_insert_melt;
- plugin->get_melt = &postgres_get_melt;
- plugin->get_melt_index = &postgres_get_melt_index;
- plugin->insert_refresh_reveal = &postgres_insert_refresh_reveal;
- plugin->get_refresh_reveal = &postgres_get_refresh_reveal;
- plugin->get_link_data = &postgres_get_link_data;
- plugin->get_coin_transactions = &postgres_get_coin_transactions;
- plugin->free_coin_transaction_list = &common_free_coin_transaction_list;
- plugin->lookup_wire_transfer = &postgres_lookup_wire_transfer;
- plugin->lookup_transfer_by_deposit = &postgres_lookup_transfer_by_deposit;
- plugin->insert_aggregation_tracking = &postgres_insert_aggregation_tracking;
- plugin->insert_wire_fee = &postgres_insert_wire_fee;
- plugin->get_wire_fee = &postgres_get_wire_fee;
- plugin->get_expired_reserves = &postgres_get_expired_reserves;
- plugin->insert_reserve_closed = &postgres_insert_reserve_closed;
- plugin->wire_prepare_data_insert = &postgres_wire_prepare_data_insert;
- plugin->wire_prepare_data_mark_finished =
- &postgres_wire_prepare_data_mark_finished;
- plugin->wire_prepare_data_mark_failed =
- &postgres_wire_prepare_data_mark_failed;
- plugin->wire_prepare_data_get = &postgres_wire_prepare_data_get;
- plugin->start_deferred_wire_out = &postgres_start_deferred_wire_out;
- plugin->store_wire_transfer_out = &postgres_store_wire_transfer_out;
- plugin->gc = &postgres_gc;
- plugin->select_deposits_above_serial_id
- = &postgres_select_deposits_above_serial_id;
+ plugin->do_reserve_open
+ = &TEH_PG_do_reserve_open;
+ plugin->drop_tables
+ = &TEH_PG_drop_tables;
+ plugin->free_coin_transaction_list
+ = &TEH_COMMON_free_coin_transaction_list;
+ plugin->free_reserve_history
+ = &TEH_COMMON_free_reserve_history;
+ plugin->get_coin_transactions
+ = &TEH_PG_get_coin_transactions;
+ plugin->get_expired_reserves
+ = &TEH_PG_get_expired_reserves;
+ plugin->get_purse_request
+ = &TEH_PG_get_purse_request;
+ plugin->get_reserve_history
+ = &TEH_PG_get_reserve_history;
+ plugin->get_unfinished_close_requests
+ = &TEH_PG_get_unfinished_close_requests;
+ plugin->insert_records_by_table
+ = &TEH_PG_insert_records_by_table;
+ plugin->insert_reserve_open_deposit
+ = &TEH_PG_insert_reserve_open_deposit;
+ plugin->insert_close_request
+ = &TEH_PG_insert_close_request;
+ plugin->delete_aggregation_transient
+ = &TEH_PG_delete_aggregation_transient;
+ plugin->get_link_data
+ = &TEH_PG_get_link_data;
+ plugin->iterate_reserve_close_info
+ = &TEH_PG_iterate_reserve_close_info;
+ plugin->iterate_kyc_reference
+ = &TEH_PG_iterate_kyc_reference;
+ plugin->lookup_records_by_table
+ = &TEH_PG_lookup_records_by_table;
+ plugin->lookup_serial_by_table
+ = &TEH_PG_lookup_serial_by_table;
+ plugin->select_account_merges_above_serial_id
+ = &TEH_PG_select_account_merges_above_serial_id;
+ plugin->select_all_purse_decisions_above_serial_id
+ = &TEH_PG_select_all_purse_decisions_above_serial_id;
+ plugin->select_aml_threshold
+ = &TEH_PG_select_aml_threshold;
+ plugin->select_purse
+ = &TEH_PG_select_purse;
+ plugin->select_purse_deposits_above_serial_id
+ = &TEH_PG_select_purse_deposits_above_serial_id;
+ plugin->select_purse_merges_above_serial_id
+ = &TEH_PG_select_purse_merges_above_serial_id;
+ plugin->select_purse_requests_above_serial_id
+ = &TEH_PG_select_purse_requests_above_serial_id;
+ plugin->select_reserve_close_info
+ = &TEH_PG_select_reserve_close_info;
+ plugin->select_reserve_closed_above_serial_id
+ = &TEH_PG_select_reserve_closed_above_serial_id;
+ plugin->select_reserve_open_above_serial_id
+ = &TEH_PG_select_reserve_open_above_serial_id;
+ plugin->insert_purse_request
+ = &TEH_PG_insert_purse_request;
+ plugin->iterate_active_signkeys
+ = &TEH_PG_iterate_active_signkeys;
+ plugin->commit
+ = &TEH_PG_commit;
+ plugin->preflight
+ = &TEH_PG_preflight;
+ plugin->select_aggregation_amounts_for_kyc_check
+ = &TEH_PG_select_aggregation_amounts_for_kyc_check;
+ plugin->select_satisfied_kyc_processes
+ = &TEH_PG_select_satisfied_kyc_processes;
+ plugin->kyc_provider_account_lookup
+ = &TEH_PG_kyc_provider_account_lookup;
+ plugin->lookup_kyc_requirement_by_row
+ = &TEH_PG_lookup_kyc_requirement_by_row;
+ plugin->insert_kyc_requirement_for_account
+ = &TEH_PG_insert_kyc_requirement_for_account;
+ plugin->lookup_kyc_process_by_account
+ = &TEH_PG_lookup_kyc_process_by_account;
+ plugin->update_kyc_process_by_row
+ = &TEH_PG_update_kyc_process_by_row;
+ plugin->insert_kyc_requirement_process
+ = &TEH_PG_insert_kyc_requirement_process;
+ plugin->select_withdraw_amounts_for_kyc_check
+ = &TEH_PG_select_withdraw_amounts_for_kyc_check;
+ plugin->select_merge_amounts_for_kyc_check
+ = &TEH_PG_select_merge_amounts_for_kyc_check;
+ plugin->profit_drains_set_finished
+ = &TEH_PG_profit_drains_set_finished;
+ plugin->profit_drains_get_pending
+ = &TEH_PG_profit_drains_get_pending;
+ plugin->get_drain_profit
+ = &TEH_PG_get_drain_profit;
+ plugin->get_purse_deposit
+ = &TEH_PG_get_purse_deposit;
+ plugin->insert_contract
+ = &TEH_PG_insert_contract;
+ plugin->select_contract
+ = &TEH_PG_select_contract;
+ plugin->select_purse_merge
+ = &TEH_PG_select_purse_merge;
+ plugin->select_contract_by_purse
+ = &TEH_PG_select_contract_by_purse;
+ plugin->insert_drain_profit
+ = &TEH_PG_insert_drain_profit;
+ plugin->do_reserve_purse
+ = &TEH_PG_do_reserve_purse;
+ plugin->lookup_global_fee_by_time
+ = &TEH_PG_lookup_global_fee_by_time;
+ plugin->do_purse_deposit
+ = &TEH_PG_do_purse_deposit;
+ plugin->activate_signing_key
+ = &TEH_PG_activate_signing_key;
+ plugin->update_auditor
+ = &TEH_PG_update_auditor;
+ plugin->begin_revolving_shard
+ = &TEH_PG_begin_revolving_shard;
+ plugin->get_extension_manifest
+ = &TEH_PG_get_extension_manifest;
+ plugin->do_purse_merge
+ = &TEH_PG_do_purse_merge;
+ plugin->do_purse_delete
+ = &TEH_PG_do_purse_delete;
+ plugin->start_read_committed
+ = &TEH_PG_start_read_committed;
+ plugin->start_read_only
+ = &TEH_PG_start_read_only;
+ plugin->insert_denomination_info
+ = &TEH_PG_insert_denomination_info;
+ plugin->do_batch_withdraw_insert
+ = &TEH_PG_do_batch_withdraw_insert;
+ plugin->lookup_wire_fee_by_time
+ = &TEH_PG_lookup_wire_fee_by_time;
+ plugin->start
+ = &TEH_PG_start;
+ plugin->rollback
+ = &TEH_PG_rollback;
+ plugin->create_tables
+ = &TEH_PG_create_tables;
+ plugin->event_listen
+ = &TEH_PG_event_listen;
+ plugin->event_listen_cancel
+ = &TEH_PG_event_listen_cancel;
+ plugin->event_notify
+ = &TEH_PG_event_notify;
+ plugin->get_denomination_info
+ = &TEH_PG_get_denomination_info;
+ plugin->iterate_denomination_info
+ = &TEH_PG_iterate_denomination_info;
+ plugin->iterate_denominations
+ = &TEH_PG_iterate_denominations;
+ plugin->iterate_active_auditors
+ = &TEH_PG_iterate_active_auditors;
+ plugin->iterate_auditor_denominations
+ = &TEH_PG_iterate_auditor_denominations;
+ plugin->reserves_get
+ = &TEH_PG_reserves_get;
+ plugin->reserves_get_origin
+ = &TEH_PG_reserves_get_origin;
+ plugin->drain_kyc_alert
+ = &TEH_PG_drain_kyc_alert;
+ plugin->reserves_in_insert
+ = &TEH_PG_reserves_in_insert;
+ plugin->get_withdraw_info
+ = &TEH_PG_get_withdraw_info;
+ plugin->do_batch_withdraw
+ = &TEH_PG_do_batch_withdraw;
+ plugin->do_age_withdraw
+ = &TEH_PG_do_age_withdraw;
+ plugin->get_age_withdraw
+ = &TEH_PG_get_age_withdraw;
+ plugin->get_policy_details
+ = &TEH_PG_get_policy_details;
+ plugin->persist_policy_details
+ = &TEH_PG_persist_policy_details;
+ plugin->do_deposit
+ = &TEH_PG_do_deposit;
+ plugin->get_wire_hash_for_contract
+ = &TEH_PG_get_wire_hash_for_contract;
+ plugin->add_policy_fulfillment_proof
+ = &TEH_PG_add_policy_fulfillment_proof;
+ plugin->do_melt
+ = &TEH_PG_do_melt;
+ plugin->do_refund
+ = &TEH_PG_do_refund;
+ plugin->do_recoup
+ = &TEH_PG_do_recoup;
+ plugin->do_recoup_refresh
+ = &TEH_PG_do_recoup_refresh;
+ plugin->get_reserve_balance
+ = &TEH_PG_get_reserve_balance;
+ plugin->count_known_coins
+ = &TEH_PG_count_known_coins;
+ plugin->ensure_coin_known
+ = &TEH_PG_ensure_coin_known;
+ plugin->get_known_coin
+ = &TEH_PG_get_known_coin;
+ plugin->get_signature_for_known_coin
+ = &TEH_PG_get_signature_for_known_coin;
+ plugin->get_coin_denomination
+ = &TEH_PG_get_coin_denomination;
+ plugin->have_deposit2
+ = &TEH_PG_have_deposit2;
+ plugin->aggregate
+ = &TEH_PG_aggregate;
+ plugin->create_aggregation_transient
+ = &TEH_PG_create_aggregation_transient;
+ plugin->select_aggregation_transient
+ = &TEH_PG_select_aggregation_transient;
+ plugin->find_aggregation_transient
+ = &TEH_PG_find_aggregation_transient;
+ plugin->update_aggregation_transient
+ = &TEH_PG_update_aggregation_transient;
+ plugin->get_ready_deposit
+ = &TEH_PG_get_ready_deposit;
+ plugin->insert_refund
+ = &TEH_PG_insert_refund;
+ plugin->select_refunds_by_coin
+ = &TEH_PG_select_refunds_by_coin;
+ plugin->get_melt
+ = &TEH_PG_get_melt;
+ plugin->insert_refresh_reveal
+ = &TEH_PG_insert_refresh_reveal;
+ plugin->get_refresh_reveal
+ = &TEH_PG_get_refresh_reveal;
+ plugin->lookup_wire_transfer
+ = &TEH_PG_lookup_wire_transfer;
+ plugin->lookup_transfer_by_deposit
+ = &TEH_PG_lookup_transfer_by_deposit;
+ plugin->insert_wire_fee
+ = &TEH_PG_insert_wire_fee;
+ plugin->insert_global_fee
+ = &TEH_PG_insert_global_fee;
+ plugin->get_wire_fee
+ = &TEH_PG_get_wire_fee;
+ plugin->get_global_fee
+ = &TEH_PG_get_global_fee;
+ plugin->get_global_fees
+ = &TEH_PG_get_global_fees;
+ plugin->insert_reserve_closed
+ = &TEH_PG_insert_reserve_closed;
+ plugin->wire_prepare_data_insert
+ = &TEH_PG_wire_prepare_data_insert;
+ plugin->wire_prepare_data_mark_finished
+ = &TEH_PG_wire_prepare_data_mark_finished;
+ plugin->wire_prepare_data_mark_failed
+ = &TEH_PG_wire_prepare_data_mark_failed;
+ plugin->wire_prepare_data_get
+ = &TEH_PG_wire_prepare_data_get;
+ plugin->start_deferred_wire_out
+ = &TEH_PG_start_deferred_wire_out;
+ plugin->store_wire_transfer_out
+ = &TEH_PG_store_wire_transfer_out;
+ plugin->gc
+ = &TEH_PG_gc;
+ plugin->select_coin_deposits_above_serial_id
+ = &TEH_PG_select_coin_deposits_above_serial_id;
+ plugin->select_purse_decisions_above_serial_id
+ = &TEH_PG_select_purse_decisions_above_serial_id;
+ plugin->select_purse_deposits_by_purse
+ = &TEH_PG_select_purse_deposits_by_purse;
plugin->select_refreshes_above_serial_id
- = &postgres_select_refreshes_above_serial_id;
+ = &TEH_PG_select_refreshes_above_serial_id;
plugin->select_refunds_above_serial_id
- = &postgres_select_refunds_above_serial_id;
+ = &TEH_PG_select_refunds_above_serial_id;
plugin->select_reserves_in_above_serial_id
- = &postgres_select_reserves_in_above_serial_id;
+ = &TEH_PG_select_reserves_in_above_serial_id;
plugin->select_reserves_in_above_serial_id_by_account
- = &postgres_select_reserves_in_above_serial_id_by_account;
+ = &TEH_PG_select_reserves_in_above_serial_id_by_account;
plugin->select_withdrawals_above_serial_id
- = &postgres_select_withdrawals_above_serial_id;
+ = &TEH_PG_select_withdrawals_above_serial_id;
plugin->select_wire_out_above_serial_id
- = &postgres_select_wire_out_above_serial_id;
+ = &TEH_PG_select_wire_out_above_serial_id;
plugin->select_wire_out_above_serial_id_by_account
- = &postgres_select_wire_out_above_serial_id_by_account;
+ = &TEH_PG_select_wire_out_above_serial_id_by_account;
plugin->select_recoup_above_serial_id
- = &postgres_select_recoup_above_serial_id;
+ = &TEH_PG_select_recoup_above_serial_id;
plugin->select_recoup_refresh_above_serial_id
- = &postgres_select_recoup_refresh_above_serial_id;
- plugin->select_reserve_closed_above_serial_id
- = &postgres_select_reserve_closed_above_serial_id;
- plugin->insert_recoup_request
- = &postgres_insert_recoup_request;
- plugin->insert_recoup_refresh_request
- = &postgres_insert_recoup_refresh_request;
+ = &TEH_PG_select_recoup_refresh_above_serial_id;
plugin->get_reserve_by_h_blind
- = &postgres_get_reserve_by_h_blind;
+ = &TEH_PG_get_reserve_by_h_blind;
plugin->get_old_coin_by_h_blind
- = &postgres_get_old_coin_by_h_blind;
+ = &TEH_PG_get_old_coin_by_h_blind;
plugin->insert_denomination_revocation
- = &postgres_insert_denomination_revocation;
+ = &TEH_PG_insert_denomination_revocation;
plugin->get_denomination_revocation
- = &postgres_get_denomination_revocation;
- plugin->select_deposits_missing_wire
- = &postgres_select_deposits_missing_wire;
+ = &TEH_PG_get_denomination_revocation;
+ plugin->select_batch_deposits_missing_wire
+ = &TEH_PG_select_batch_deposits_missing_wire;
+ plugin->select_justification_for_missing_wire
+ = &TEH_PG_select_justification_for_missing_wire;
+ plugin->select_aggregations_above_serial
+ = &TEH_PG_select_aggregations_above_serial;
plugin->lookup_auditor_timestamp
- = &postgres_lookup_auditor_timestamp;
+ = &TEH_PG_lookup_auditor_timestamp;
plugin->lookup_auditor_status
- = &postgres_lookup_auditor_status;
+ = &TEH_PG_lookup_auditor_status;
plugin->insert_auditor
- = &postgres_insert_auditor;
- plugin->update_auditor
- = &postgres_update_auditor;
+ = &TEH_PG_insert_auditor;
plugin->lookup_wire_timestamp
- = &postgres_lookup_wire_timestamp;
+ = &TEH_PG_lookup_wire_timestamp;
plugin->insert_wire
- = &postgres_insert_wire;
+ = &TEH_PG_insert_wire;
plugin->update_wire
- = &postgres_update_wire;
+ = &TEH_PG_update_wire;
plugin->get_wire_accounts
- = &postgres_get_wire_accounts;
+ = &TEH_PG_get_wire_accounts;
plugin->get_wire_fees
- = &postgres_get_wire_fees;
+ = &TEH_PG_get_wire_fees;
plugin->insert_signkey_revocation
- = &postgres_insert_signkey_revocation;
+ = &TEH_PG_insert_signkey_revocation;
plugin->lookup_signkey_revocation
- = &postgres_lookup_signkey_revocation;
+ = &TEH_PG_lookup_signkey_revocation;
plugin->lookup_denomination_key
- = &postgres_lookup_denomination_key;
+ = &TEH_PG_lookup_denomination_key;
plugin->insert_auditor_denom_sig
- = &postgres_insert_auditor_denom_sig;
+ = &TEH_PG_insert_auditor_denom_sig;
plugin->select_auditor_denom_sig
- = &postgres_select_auditor_denom_sig;
- plugin->lookup_wire_fee_by_time
- = &postgres_lookup_wire_fee_by_time;
+ = &TEH_PG_select_auditor_denom_sig;
plugin->add_denomination_key
- = &postgres_add_denomination_key;
- plugin->activate_signing_key
- = &postgres_activate_signing_key;
+ = &TEH_PG_add_denomination_key;
plugin->lookup_signing_key
- = &postgres_lookup_signing_key;
- plugin->lookup_serial_by_table
- = &postgres_lookup_serial_by_table;
- plugin->lookup_records_by_table
- = &postgres_lookup_records_by_table;
- plugin->insert_records_by_table
- = &postgres_insert_records_by_table;
+ = &TEH_PG_lookup_signing_key;
plugin->begin_shard
- = &postgres_begin_shard;
+ = &TEH_PG_begin_shard;
+ plugin->abort_shard
+ = &TEH_PG_abort_shard;
+ plugin->insert_kyc_failure
+ = &TEH_PG_insert_kyc_failure;
plugin->complete_shard
- = &postgres_complete_shard;
- plugin->begin_revolving_shard
- = &postgres_begin_revolving_shard;
+ = &TEH_PG_complete_shard;
plugin->release_revolving_shard
- = &postgres_release_revolving_shard;
- plugin->delete_revolving_shards
- = &postgres_delete_revolving_shards;
+ = &TEH_PG_release_revolving_shard;
+ plugin->delete_shard_locks
+ = &TEH_PG_delete_shard_locks;
+ plugin->set_extension_manifest
+ = &TEH_PG_set_extension_manifest;
+ plugin->insert_partner
+ = &TEH_PG_insert_partner;
+ plugin->expire_purse
+ = &TEH_PG_expire_purse;
+ plugin->select_purse_by_merge_pub
+ = &TEH_PG_select_purse_by_merge_pub;
+ plugin->set_purse_balance
+ = &TEH_PG_set_purse_balance;
+ plugin->get_pending_kyc_requirement_process
+ = &TEH_PG_get_pending_kyc_requirement_process;
+ plugin->insert_kyc_attributes
+ = &TEH_PG_insert_kyc_attributes;
+ plugin->select_similar_kyc_attributes
+ = &TEH_PG_select_similar_kyc_attributes;
+ plugin->select_kyc_attributes
+ = &TEH_PG_select_kyc_attributes;
+ plugin->insert_aml_officer
+ = &TEH_PG_insert_aml_officer;
+ plugin->test_aml_officer
+ = &TEH_PG_test_aml_officer;
+ plugin->lookup_aml_officer
+ = &TEH_PG_lookup_aml_officer;
+ plugin->trigger_aml_process
+ = &TEH_PG_trigger_aml_process;
+ plugin->select_aml_process
+ = &TEH_PG_select_aml_process;
+ plugin->select_aml_history
+ = &TEH_PG_select_aml_history;
+ plugin->insert_aml_decision
+ = &TEH_PG_insert_aml_decision;
+
+ plugin->batch_ensure_coin_known
+ = &TEH_PG_batch_ensure_coin_known;
+ plugin->inject_auditor_triggers
+ = &TEH_PG_inject_auditor_triggers;
+
return plugin;
}
@@ -11447,7 +811,11 @@ libtaler_plugin_exchangedb_postgres_done (void *cls)
struct PostgresClosure *pg = plugin->cls;
if (NULL != pg->conn)
+ {
GNUNET_PQ_disconnect (pg->conn);
+ pg->conn = NULL;
+ }
+ GNUNET_free (pg->exchange_url);
GNUNET_free (pg->sql_dir);
GNUNET_free (pg->currency);
GNUNET_free (pg);
diff --git a/src/exchangedb/procedures.sql.in b/src/exchangedb/procedures.sql.in
new file mode 100644
index 000000000..7afb01f0b
--- /dev/null
+++ b/src/exchangedb/procedures.sql.in
@@ -0,0 +1,49 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2014--2022 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+BEGIN;
+
+SET search_path TO exchange;
+
+#include "exchange_do_amount_specific.sql"
+#include "exchange_do_batch_withdraw.sql"
+#include "exchange_do_batch_withdraw_insert.sql"
+#include "exchange_do_age_withdraw.sql"
+#include "exchange_do_deposit.sql"
+#include "exchange_do_melt.sql"
+#include "exchange_do_select_deposits_missing_wire.sql"
+#include "exchange_do_select_justification_for_missing_wire.sql"
+#include "exchange_do_refund.sql"
+#include "exchange_do_recoup_to_reserve.sql"
+#include "exchange_do_recoup_to_coin.sql"
+#include "exchange_do_gc.sql"
+#include "exchange_do_purse_delete.sql"
+#include "exchange_do_purse_deposit.sql"
+#include "exchange_do_purse_merge.sql"
+#include "exchange_do_reserve_purse.sql"
+#include "exchange_do_expire_purse.sql"
+#include "exchange_do_reserve_open_deposit.sql"
+#include "exchange_do_reserve_open.sql"
+#include "exchange_do_insert_or_update_policy_details.sql"
+#include "exchange_do_insert_aml_decision.sql"
+#include "exchange_do_insert_aml_officer.sql"
+#include "exchange_do_insert_kyc_attributes.sql"
+#include "exchange_do_reserves_in_insert.sql"
+#include "exchange_do_batch_reserves_update.sql"
+#include "exchange_do_get_link_data.sql"
+#include "exchange_do_batch_coin_known.sql"
+
+COMMIT;
diff --git a/src/exchangedb/spi/Makefile b/src/exchangedb/spi/Makefile
new file mode 100644
index 000000000..d654d91e9
--- /dev/null
+++ b/src/exchangedb/spi/Makefile
@@ -0,0 +1,9 @@
+EXTENSION = own_test
+MODULES = own_test
+DATA = own_test.sql
+PG_CPPFLAGS = -I /usr/include/postgresql
+
+# postgresql build stuff
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
diff --git a/src/exchangedb/spi/README.md b/src/exchangedb/spi/README.md
new file mode 100644
index 000000000..47eb37b94
--- /dev/null
+++ b/src/exchangedb/spi/README.md
@@ -0,0 +1,37 @@
+ Server Programming Interface (SPI)
+
+
+Overview
+========
+
+This folder contains results from an experiment by Joseph Xu
+to use the Postgres SPI. They are not currently used at all
+by the GNU Taler exchange.
+
+
+Dependencies
+============
+
+These are the direct dependencies for compiling the code:
+
+# apt-get install libpq-dev postgresql-server-dev-13
+# apt-get install libkrb5-dev
+# apt-get install libssl-dev
+
+
+Compilation
+===========
+
+$ make
+
+Loading functions
+=================
+
+# make install
+$ psql "$DB_NAME" < own_test.sql
+
+
+Calling functions
+==================
+
+$ psql -c "SELECT $FUNCTION_NAME($ARGS);" "$DB_NAME"
diff --git a/src/exchangedb/spi/own_test.c b/src/exchangedb/spi/own_test.c
new file mode 100644
index 000000000..ac72fad7b
--- /dev/null
+++ b/src/exchangedb/spi/own_test.c
@@ -0,0 +1,873 @@
+#include "postgres.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <postgresql/libpq-fe.h>
+#include <libpq-int.h>
+#include <catalog/pg_type.h>
+#include <executor/spi.h>
+#include <funcapi.h>
+#include <fmgr.h>
+#include <utils/builtins.h>
+#include <utils/array.h>
+#include <sys/time.h>
+#include <utils/numeric.h>
+#include <utils/timestamp.h>
+#include <utils/bytea.h>
+
+#ifdef PG_MODULE_MAGIC
+PG_MODULE_MAGIC;
+#endif
+
+typedef struct
+{
+ Datum col1;
+ Datum col2;
+} valuest;
+
+void _PG_init (void);
+
+void _PG_fini (void);
+
+void
+_PG_init (void)
+{
+}
+
+
+PG_FUNCTION_INFO_V1 (pg_spi_insert_int);
+PG_FUNCTION_INFO_V1 (pg_spi_select_from_x);
+PG_FUNCTION_INFO_V1 (pg_spi_select_pair_from_y);
+// PG_FUNCTION_INFO_V1(pg_spi_select_with_cond);
+PG_FUNCTION_INFO_V1 (pg_spi_update_y);
+PG_FUNCTION_INFO_V1 (pg_spi_prepare_example);
+PG_FUNCTION_INFO_V1 (pg_spi_prepare_example_without_saveplan);
+PG_FUNCTION_INFO_V1 (pg_spi_prepare_insert);
+PG_FUNCTION_INFO_V1 (pg_spi_prepare_insert_without_saveplan);
+// PG_FUNCTION_INFO_V1(pg_spi_prepare_select_with_cond);
+PG_FUNCTION_INFO_V1 (pg_spi_prepare_select_with_cond_without_saveplan);
+PG_FUNCTION_INFO_V1 (pg_spi_prepare_update);
+PG_FUNCTION_INFO_V1 (pg_spi_get_dep_ref_fees);
+// SIMPLE SELECT
+Datum
+pg_spi_prepare_example (PG_FUNCTION_ARGS)
+{
+ static SPIPlanPtr prepared_plan;
+ int ret;
+ int64 result;
+ char *value;
+ SPIPlanPtr new_plan;
+
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "DB connection failed ! \n");
+ }
+ {
+ if (prepared_plan == NULL)
+ {
+ new_plan = SPI_prepare ("SELECT 1 FROM X", 0, NULL);
+ prepared_plan = SPI_saveplan (new_plan);
+
+ if (prepared_plan == NULL)
+ {
+ elog (ERROR, "FAIL TO SAVE !\n");
+ }
+ }
+
+ ret = SPI_execute_plan (prepared_plan, NULL, 0,false, 0);
+ if (ret != SPI_OK_SELECT)
+ {
+ elog (ERROR, "SELECT FAILED %d !\n", ret);
+ }
+
+ if (SPI_tuptable != NULL && SPI_tuptable->vals != NULL &&
+ SPI_tuptable->tupdesc != NULL)
+ {
+ value = SPI_getvalue (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1);
+ result = atoi (value);
+ }
+ else
+ {
+ elog (ERROR, "EMPTY TABLE !\n");
+ }
+ }
+ SPI_finish ();
+ PG_RETURN_INT64 (result);
+}
+
+
+Datum
+pg_spi_prepare_example_without_saveplan (PG_FUNCTION_ARGS)
+{
+ int ret;
+ int64 result;
+ char *value;
+ SPIPlanPtr new_plan;
+
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "DB connection failed ! \n");
+ }
+
+ {
+ new_plan = SPI_prepare ("SELECT 1 FROM X", 0, NULL);
+ ret = SPI_execute_plan (new_plan, NULL, 0,false, 0);
+ if (ret != SPI_OK_SELECT)
+ {
+ elog (ERROR, "SELECT FAILED %d !\n", ret);
+ }
+
+ if (SPI_tuptable != NULL
+ && SPI_tuptable->vals != NULL
+ && SPI_tuptable->tupdesc != NULL)
+ {
+ value = SPI_getvalue (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1);
+ result = atoi (value);
+ }
+ else
+ {
+ elog (ERROR, "EMPTY TABLE !\n");
+ }
+ }
+ SPI_finish ();
+ PG_RETURN_INT64 (result);// PG_RETURN_INT64(result);
+}
+
+
+// SELECT 1 FROM X
+// V1
+Datum
+pg_spi_select_from_x (PG_FUNCTION_ARGS)
+{
+ int ret;
+ char *query = "SELECT 1 FROM X";
+ uint64 proc;
+ ret = SPI_connect ();
+
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "SPI_connect failed");
+ }
+
+ ret = SPI_exec (query, 10);
+ proc = SPI_processed;
+ if (ret != SPI_OK_SELECT)
+ {
+ elog (ERROR, "SPI_exec failed");
+ }
+
+ SPI_finish ();
+
+ PG_RETURN_INT64 (proc);
+}
+
+
+// INSERT INTO X VALUES (1)
+Datum
+pg_spi_insert_int (PG_FUNCTION_ARGS)
+{
+ int ret;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ char *query = "INSERT INTO X (a) VALUES ($1)";
+
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "SPI_connect failed");
+ }
+
+ nargs = 1;
+ argtypes[0] = INT4OID;
+ values[0] = Int32GetDatum (3);
+
+ ret = SPI_execute_with_args (query, nargs, argtypes, values, NULL, false, 0);
+ if (ret != SPI_OK_INSERT)
+ {
+ elog (ERROR, "SPI_execute_with_args failed");
+ }
+
+ SPI_finish ();
+
+ PG_RETURN_VOID ();
+}
+
+
+Datum
+pg_spi_prepare_insert (PG_FUNCTION_ARGS)
+{
+ static SPIPlanPtr prepared_plan = NULL;
+ int ret;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ const char *query = "INSERT INTO X (a) VALUES ($1)";
+ SPIPlanPtr new_plan;
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "SPI_connect failed ! \n");
+ }
+ if (prepared_plan == NULL)
+ {
+
+ argtypes[0] = INT4OID;
+ nargs = 1;
+ values[0] = Int32GetDatum (3);
+ new_plan = SPI_prepare (query, nargs, argtypes);
+ if (new_plan== NULL)
+ {
+ elog (ERROR, "SPI_prepare failed ! \n");
+ }
+ prepared_plan = SPI_saveplan (new_plan);
+ if (prepared_plan == NULL)
+ {
+ elog (ERROR, "SPI_saveplan failed ! \n");
+ }
+ }
+
+ ret = SPI_execute_plan (prepared_plan, values, NULL, false, 0);
+ if (ret != SPI_OK_INSERT)
+ {
+ elog (ERROR, "SPI_execute_plan failed ! \n");
+ }
+
+ SPI_finish ();
+
+ PG_RETURN_VOID ();
+}
+
+
+/*
+Datum
+pg_spi_prepare_insert_bytea(PG_FUNCTION_ARGS)
+{
+ static SPIPlanPtr prepared_plan = NULL;
+ int ret;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ Oid argtypes2[1];
+ Datum val[1];
+ char *query = "INSERT INTO X (a) VALUES ($1)";
+ SPIPlanPtr new_plan;
+ ret = SPI_connect();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog(ERROR, "SPI_connect failed ! \n");
+ }
+ if (prepared_plan == NULL) {
+ argtypes2[0] = BOOLOID;
+ val[0] = BoolGetDatum();
+ argtypes[0] = BYTEAOID;
+ nargs = 1;
+ values[0] = Int32GetDatum(3);
+ new_plan = SPI_prepare(query, nargs, argtypes);
+ if (new_plan== NULL)
+ {
+ elog(ERROR, "SPI_prepare failed ! \n");
+ }
+ prepared_plan = SPI_saveplan(new_plan);
+ if (prepared_plan == NULL)
+ {
+ elog(ERROR, "SPI_saveplan failed ! \n");
+ }
+ }
+
+ ret = SPI_execute_plan(prepared_plan, values, NULL, false, 0);
+ if (ret != SPI_OK_INSERT)
+ {
+ elog(ERROR, "SPI_execute_plan failed ! \n");
+ }
+
+ SPI_finish();
+
+ PG_RETURN_VOID();
+}
+*/
+
+Datum
+pg_spi_prepare_insert_without_saveplan (PG_FUNCTION_ARGS)
+{
+ int ret;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ const char *query = "INSERT INTO X (a) VALUES ($1)";
+ SPIPlanPtr new_plan;
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "SPI_connect failed");
+ }
+ {
+ argtypes[0] = INT4OID;
+ nargs = 1;
+ values[0] = Int32GetDatum (3);
+ new_plan = SPI_prepare (query, nargs, argtypes);
+ if (new_plan== NULL)
+ {
+ elog (ERROR, "SPI_prepare failed");
+ }
+ }
+
+ ret = SPI_execute_plan (new_plan, values, NULL, false, 0);
+ if (ret != SPI_OK_INSERT)
+ {
+ elog (ERROR, "SPI_execute_plan failed");
+ }
+
+ SPI_finish ();
+
+ PG_RETURN_VOID ();
+}
+
+
+/*
+Datum
+pg_spi_select_pair_from_y(PG_FUNCTION_ARGS)
+{
+ int ret;
+ valuest result;
+ bool isnull;
+ char *query = "SELECT 1,1 FROM Y";
+ result.col1 = 0;
+ result.col2 = 0;
+
+ if ((ret = SPI_connect()) < 0) {
+ fprintf(stderr, "SPI_connect returned %d\n", ret);
+ exit(1);
+ }
+ ret = SPI_exec(query, 0);
+ if (ret == SPI_OK_SELECT && SPI_processed > 0) {
+ int i;
+ SPITupleTable *tuptable = SPI_tuptable;
+ TupleDesc tupdesc = tuptable->tupdesc;
+ for (i = 0; i < SPI_processed; i++) {
+ HeapTuple tuple = tuptable->vals[i];
+ result.col1 = SPI_getbinval(tuple, tupdesc, 1, &isnull);
+ result.col2 = SPI_getbinval(tuple, tupdesc, 2, &isnull);
+ }
+ }
+ SPI_finish();
+ PG_RETURN_TEXT_P(result);
+}
+*/
+
+// SELECT X FROM Y WHERE Z=$1
+/*
+Datum
+pg_spi_select_with_cond(PG_FUNCTION_ARGS)
+{
+ int ret;
+ char *query;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ uint64 proc;
+ query = "SELECT col1 FROM Y WHERE col2 = $1";
+
+ ret = SPI_connect();
+ if (ret != SPI_OK_CONNECT) {
+ elog(ERROR, "SPI_connect failed: %d", ret);
+ }
+ nargs = 1;
+ argtypes[0] = INT4OID;
+ values[0] = Int32GetDatum(2);
+
+ ret = SPI_execute_with_args(query, nargs, argtypes, values, NULL, false, 0);
+ proc = SPI_processed;
+ if (ret != SPI_OK_SELECT)
+ {
+ elog(ERROR, "SPI_execute_with_args failed");
+ }
+
+ SPI_finish();
+
+
+ PG_RETURN_INT64(proc);
+ }*/
+
+////////SELECT WITH COND
+/*
+Datum pg_spi_prepare_select_with_cond(PG_FUNCTION_ARGS) {
+ static SPIPlanPtr prepared_plan = NULL;
+ SPIPlanPtr new_plan;
+ int ret;
+ Datum values[1];
+ uint64 proc;
+ int nargs;
+ Oid argtypes[1];
+ char *query = "SELECT col1 FROM Y WHERE col1 = $1";
+ int result = 0;
+
+ ret = SPI_connect();
+ if (ret != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed ! \n");
+
+ if (prepared_plan == NULL) {
+
+ argtypes[0] = INT4OID;
+ nargs = 1;
+ values[0] = DatumGetByteaP(SPI_getbinval(tuptable->vals[0], tupdesc, 1, &isnull)); //Value col2
+
+ new_plan = SPI_prepare(query, nargs, argtypes);
+ if (new_plan == NULL)
+ elog(ERROR, "SPI_prepare failed ! \n");
+
+ prepared_plan = SPI_saveplan(new_plan);
+ if (prepared_plan == NULL)
+ elog(ERROR, "SPI_saveplan failed ! \n");
+ }
+
+
+ ret = SPI_execute_plan(prepared_plan, values, NULL, false, 0);
+
+ if (ret != SPI_OK_SELECT) {
+ elog(ERROR, "SPI_execute_plan failed: %d \n", ret);
+ }
+
+ proc = SPI_processed;
+
+ if (proc > 0) {
+ SPITupleTable *tuptable = SPI_tuptable;
+ TupleDesc tupdesc = tuptable->tupdesc;
+ HeapTuple tuple;
+ int i;
+
+ for (i = 0; i < proc; i++) {
+ tuple = tuptable->vals[i];
+ for (int j = 1; j <= tupdesc->natts; j++) {
+ char * value = SPI_getvalue(tuple, tupdesc, j);
+ result += atoi(value);
+ }
+ }
+ }
+ SPI_finish();
+ PG_RETURN_INT64(result);
+}
+*/
+
+Datum
+pg_spi_prepare_select_with_cond_without_saveplan (PG_FUNCTION_ARGS)
+{
+
+ SPIPlanPtr new_plan;
+ int ret;
+ Datum values[1];
+ uint64 proc;
+ int nargs;
+ Oid argtypes[1];
+ char *query = "SELECT col1 FROM Y WHERE col2 = $1";
+ int result = 0;
+
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ elog (ERROR, "SPI_connect failed ! \n");
+
+ {
+
+ argtypes[0] = INT4OID;
+ nargs = 1;
+ values[0] = Int32GetDatum (2); // Value col2
+
+ new_plan = SPI_prepare (query, nargs, argtypes);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare failed ! \n");
+
+ }
+
+
+ ret = SPI_execute_plan (new_plan, values, NULL, false, 0);
+
+ if (ret != SPI_OK_SELECT)
+ {
+ elog (ERROR, "SPI_execute_plan failed: %d \n", ret);
+ }
+
+ proc = SPI_processed;
+
+ if (proc > 0)
+ {
+ SPITupleTable *tuptable = SPI_tuptable;
+ TupleDesc tupdesc = tuptable->tupdesc;
+ HeapTuple tuple;
+ int i;
+
+ for (i = 0; i < proc; i++)
+ {
+ tuple = tuptable->vals[i];
+ for (int j = 1; j <= tupdesc->natts; j++)
+ {
+ char *value = SPI_getvalue (tuple, tupdesc, j);
+ result += atoi (value);
+ }
+ }
+ }
+ SPI_finish ();
+ PG_RETURN_INT64 (result);
+}
+
+
+Datum
+pg_spi_update_y (PG_FUNCTION_ARGS)
+{
+ int ret;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ const char *query = "UPDATE Y SET col1 = 4 WHERE (col2 = $1)";
+
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "SPI_connect failed ! \n");
+ }
+
+ nargs = 1;
+ argtypes[0] = INT4OID;
+ values[0] = Int32GetDatum (0);
+
+ ret = SPI_execute_with_args (query, nargs, argtypes, values, NULL, false, 0);
+ if (ret != SPI_OK_UPDATE)
+ {
+ elog (ERROR, "SPI_execute_with_args failed ! \n");
+ }
+
+ SPI_finish ();
+
+ PG_RETURN_VOID ();
+}
+
+
+Datum
+pg_spi_prepare_update (PG_FUNCTION_ARGS)
+{
+ static SPIPlanPtr prepared_plan = NULL;
+ SPIPlanPtr new_plan;
+ int ret;
+ int nargs;
+ Oid argtypes[1];
+ Datum values[1];
+ const char *query = "UPDATE Y SET col1 = 4 WHERE (col2 = $1)";
+
+ ret = SPI_connect ();
+ if (ret != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "SPI_connect failed ! \n");
+ }
+
+ if (prepared_plan == NULL)
+ {
+ argtypes[0] = INT4OID;
+ nargs = 1;
+ values[0] = Int32GetDatum (3);
+ // PREPARE
+ new_plan = SPI_prepare (query, nargs, argtypes);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare failed ! \n");
+ // SAVEPLAN
+ prepared_plan = SPI_saveplan (new_plan);
+ if (prepared_plan == NULL)
+ elog (ERROR, "SPI_saveplan failed ! \n");
+ }
+ ret = SPI_execute_plan (prepared_plan, values, NULL, false, 0);
+ if (ret != SPI_OK_UPDATE)
+ elog (ERROR, "SPI_execute_plan failed ! \n");
+
+ SPI_finish ();
+ PG_RETURN_VOID ();
+}
+
+
+/*
+Datum
+pg_spi_prepare_update_without_saveplan(PG_FUNCTION_ARGS)
+{}*/
+void
+_PG_fini (void)
+{
+}
+
+
+/*
+
+*/
+
+
+Datum
+pg_spi_get_dep_ref_fees (PG_FUNCTION_ARGS)
+{
+ /* Define plan to save */
+ static SPIPlanPtr deposit_plan;
+ static SPIPlanPtr ref_plan;
+ static SPIPlanPtr fees_plan;
+ static SPIPlanPtr dummy_plan;
+ /* Define variables to update */
+ Timestamp refund_deadline = PG_GETARG_TIMESTAMP (0);
+ bytea *merchant_pub = PG_GETARG_BYTEA_P (1);
+ bytea *wire_target_h_payto = PG_GETARG_BYTEA_P (2);
+ bytea *wtid_raw = PG_GETARG_BYTEA_P (3);
+ bool is_null;
+ /* Define variables to store the results of each SPI query */
+ uint64_t sum_deposit_val = 0;
+ uint32_t sum_deposit_frac = 0;
+ uint64_t s_refund_val = 0;
+ uint32_t s_refund_frac = 0;
+ uint64_t sum_dep_fee_val = 0;
+ uint32_t sum_dep_fee_frac = 0;
+ uint64_t norm_refund_val = 0;
+ uint32_t norm_refund_frac = 0;
+ uint64_t sum_refund_val = 0;
+ uint32_t sum_refund_frac = 0;
+ /* Define variables to store the Tuptable */
+ SPITupleTable *dep_res;
+ SPITupleTable *ref_res;
+ SPITupleTable *ref_by_coin_res;
+ SPITupleTable *norm_ref_by_coin_res;
+ SPITupleTable *fully_refunded_coins_res;
+ SPITupleTable *fees_res;
+ SPITupleTable *dummys_res;
+ /* Define variable to update */
+ Datum values_refund[2];
+ Datum values_deposit[3];
+ Datum values_fees[2];
+ Datum values_dummys[2];
+ TupleDesc tupdesc;
+ /* Define variables to replace some tables */
+ bytea *ref_by_coin_coin_pub;
+ int64 ref_by_coin_deposit_serial_id = 0;
+ bytea *norm_ref_by_coin_coin_pub;
+ int64_t norm_ref_by_coin_deposit_serial_id = 0;
+ bytea *new_dep_coin_pub = NULL;
+ int res = SPI_connect ();
+
+ /* Connect to SPI */
+ if (res < 0)
+ {
+ elog (ERROR, "Could not connect to SPI manager");
+ }
+ if (deposit_plan == NULL)
+ {
+ const char *dep_sql;
+ SPIPlanPtr new_plan;
+
+ // Execute first query and store results in variables
+ dep_sql =
+ "UPDATE deposits SET done=TRUE "
+ "WHERE NOT (done OR policy_blocked) "
+ "AND refund_deadline=$1 "
+ "AND merchant_pub=$2 "
+ "AND wire_target_h_payto=$3 "
+ "RETURNING "
+ "deposit_serial_id,"
+ "coin_pub,"
+ "amount_with_fee_val,"
+ "amount_with_fee_frac;";
+ fprintf (stderr, "dep sql %d\n", 1);
+ new_plan =
+ SPI_prepare (dep_sql, 4,(Oid[]){INT8OID, BYTEAOID, BYTEAOID});
+ fprintf (stderr, "dep sql %d\n", 2);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare failed for dep \n");
+ deposit_plan = SPI_saveplan (new_plan);
+ if (deposit_plan == NULL)
+ elog (ERROR, "SPI_saveplan failed for dep \n");
+ }
+ fprintf (stdout, "dep sql %d\n", 3);
+
+ values_deposit[0] = Int64GetDatum (refund_deadline);
+ values_deposit[1] = PointerGetDatum (merchant_pub);
+ values_deposit[2] = PointerGetDatum (wire_target_h_payto);
+
+ res = SPI_execute_plan (deposit_plan,
+ values_deposit,
+ NULL,
+ true,
+ 0);
+ fprintf (stdout, "dep sql %d\n", 4);
+ if (res != SPI_OK_UPDATE)
+ {
+ elog (ERROR, "Failed to execute subquery 1 \n");
+ }
+ // STORE TUPTABLE deposit
+ dep_res = SPI_tuptable;
+
+ for (unsigned int i = 0; i < SPI_processed; i++)
+ {
+ int64 dep_deposit_serial_ids = DatumGetInt64 (SPI_getbinval (
+ SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc, 1,
+ &is_null));
+ bytea *dep_coin_pub = DatumGetByteaP (SPI_getbinval (SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc,
+ 2, &is_null));
+ int64 dep_amount_val = DatumGetInt64 (SPI_getbinval (SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc,
+ 3, &is_null));
+ int32 dep_amount_frac = DatumGetInt32 (SPI_getbinval (SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc,
+ 4, &is_null));
+
+ if (is_null)
+ elog (ERROR, "Failed to retrieve data from deposit \n");
+ if (ref_plan == NULL)
+ {
+ // Execute second query with parameters from first query and store results in variables
+ const char *ref_sql =
+ "SELECT amount_with_fee_val, amount_with_fee_frac, coin_pub, deposit_serial_id "
+ "FROM refunds "
+ "WHERE coin_pub=$1 "
+ "AND deposit_serial_id=$2;";
+ SPIPlanPtr new_plan = SPI_prepare (ref_sql, 3, (Oid[]){BYTEAOID,
+ INT8OID});
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare failed for refund\n");
+ ref_plan = SPI_saveplan (new_plan);
+ if (ref_plan == NULL)
+ elog (ERROR, "SPI_saveplan failed for refund\n");
+ }
+ values_refund[0] = PointerGetDatum (dep_coin_pub);
+ values_refund[1] = Int64GetDatum (dep_deposit_serial_ids);
+ res = SPI_execute_plan (ref_plan,
+ values_refund,
+ NULL,
+ false,
+ 0);
+ if (res != SPI_OK_SELECT)
+ elog (ERROR, "Failed to execute subquery 2\n");
+ // STORE TUPTABLE refund
+ ref_res = SPI_tuptable;
+ for (unsigned int j = 0; j < SPI_processed; j++)
+ {
+ int64 ref_refund_val = DatumGetInt64 (SPI_getbinval (
+ SPI_tuptable->vals[j],
+ SPI_tuptable->tupdesc, 1,
+ &is_null));
+ int32 ref_refund_frac = DatumGetInt32 (SPI_getbinval (
+ SPI_tuptable->vals[j],
+ SPI_tuptable->tupdesc, 2,
+ &is_null));
+ bytea *ref_coin_pub = DatumGetByteaP (SPI_getbinval (
+ SPI_tuptable->vals[j],
+ SPI_tuptable->tupdesc, 3,
+ &is_null));
+ int64 ref_deposit_serial_id = DatumGetInt64 (SPI_getbinval (
+ SPI_tuptable->vals[j],
+ SPI_tuptable->tupdesc, 4,
+ &is_null));
+ // Execute third query with parameters from second query and store results in variables
+ ref_by_coin_coin_pub = ref_coin_pub;
+ ref_by_coin_deposit_serial_id = ref_deposit_serial_id;
+ // LOOP TO GET THE SUM FROM REFUND BY COIN
+ for (unsigned int i = 0; i<SPI_processed; i++)
+ {
+ if ((ref_by_coin_coin_pub ==
+ DatumGetByteaP (SPI_getbinval (SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc, 1,
+ &is_null)))
+ &&
+ (ref_by_coin_deposit_serial_id ==
+ DatumGetUInt64 (SPI_getbinval (SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc, 2,
+ &is_null)))
+ )
+ {
+ sum_refund_val += ref_refund_val;
+ sum_refund_frac += ref_refund_frac;
+ norm_ref_by_coin_coin_pub = ref_by_coin_coin_pub;
+ norm_ref_by_coin_deposit_serial_id = ref_by_coin_deposit_serial_id;
+ }
+ }// END SUM CALCULATION
+ // NORMALIZE REFUND VAL FRAC
+ norm_refund_val =
+ (sum_refund_val + sum_refund_frac) / 100000000;
+ norm_refund_frac =
+ sum_refund_frac % 100000000;
+ // Get refund values
+ s_refund_val += sum_refund_val;
+ s_refund_frac = sum_refund_frac;
+ }// END REFUND
+ if (norm_ref_by_coin_coin_pub == dep_coin_pub
+ && ref_by_coin_deposit_serial_id == dep_deposit_serial_ids
+ && norm_refund_val == dep_amount_val
+ && norm_refund_frac == dep_amount_frac)
+ {
+ new_dep_coin_pub = dep_coin_pub;
+ }
+ // Ensure we get the fee for each coin and not only once per denomination
+ if (fees_plan == NULL)
+ {
+ const char *fees_sql =
+ "SELECT "
+ " denom.fee_deposit_val AS fee_val, "
+ " denom.fee_deposit_frac AS fee_frac, "
+ "FROM known_coins kc"
+ "JOIN denominations denom USING (denominations_serial) "
+ "WHERE kc.coin_pub = $1 AND kc.coin_pub != $2;";
+ SPIPlanPtr new_plan = SPI_prepare (fees_sql, 3, (Oid[]){BYTEAOID,
+ BYTEAOID});
+ if (new_plan == NULL)
+ {
+ elog (ERROR, "SPI_prepare for fees failed ! \n");
+ }
+ fees_plan = SPI_saveplan (new_plan);
+ if (fees_plan == NULL)
+ {
+ elog (ERROR, "SPI_saveplan for fees failed ! \n");
+ }
+ }
+ values_fees[0] = PointerGetDatum (dep_coin_pub);
+ values_fees[1] = PointerGetDatum (new_dep_coin_pub);
+ res = SPI_execute_plan (fees_plan, values_fees, NULL, false, 0);
+ if (res != SPI_OK_SELECT)
+ elog (ERROR, "SPI_execute_plan failed for fees \n");
+ fees_res = SPI_tuptable;
+ tupdesc = fees_res->tupdesc;
+ for (unsigned int i = 0; i<SPI_processed; i++)
+ {
+ HeapTuple tuple = fees_res->vals[i];
+ bool is_null;
+ uint64_t fee_val = DatumGetUInt64 (SPI_getbinval (tuple, tupdesc, 1,
+ &is_null));
+ uint32_t fee_frac = DatumGetUInt32 (SPI_getbinval (tuple, tupdesc, 2,
+ &is_null));
+ uint64_t fees_deposit_serial_id = DatumGetUInt64 (SPI_getbinval (tuple,
+ tupdesc,
+ 3,
+ &is_null));
+ if (dummy_plan == NULL)
+ {
+ const char *insert_dummy_sql =
+ "INSERT INTO "
+ "aggregation_tracking(deposit_serial_id, wtid_raw)"
+ " VALUES ($1, $2)";
+
+ SPIPlanPtr new_plan = SPI_prepare (insert_dummy_sql, 2, (Oid[]){INT8OID,
+ BYTEAOID});
+ if (new_plan == NULL)
+ elog (ERROR, "FAILED to prepare aggregation tracking \n");
+ dummy_plan = SPI_saveplan (new_plan);
+ if (dummy_plan == NULL)
+ elog (ERROR, "FAILED to saveplan aggregation tracking\n");
+ }
+ values_dummys[0] = Int64GetDatum (dep_deposit_serial_ids);
+ values_dummys[1] = PointerGetDatum (wtid_raw);
+ res = SPI_execute_plan (dummy_plan, values_dummys, NULL, false, 0);
+ if (res != SPI_OK_INSERT)
+ elog (ERROR, "Failed to insert dummy\n");
+ dummys_res = SPI_tuptable;
+ // Calculation of deposit fees for not fully refunded deposits
+ sum_dep_fee_val += fee_val;
+ sum_dep_fee_frac += fee_frac;
+ }
+ // Get deposit values
+ sum_deposit_val += dep_amount_val;
+ sum_deposit_frac += dep_amount_frac;
+ }// END DEPOSIT
+ SPI_finish ();
+ PG_RETURN_VOID ();
+}
diff --git a/src/exchangedb/spi/own_test.control b/src/exchangedb/spi/own_test.control
new file mode 100644
index 000000000..4e73e207f
--- /dev/null
+++ b/src/exchangedb/spi/own_test.control
@@ -0,0 +1,4 @@
+comment = 'Example extension for testing purposes'
+default_version = '1.0'
+module_pathname = '$libdir/own_test'
+relocatable = true
diff --git a/src/exchangedb/spi/own_test.sql b/src/exchangedb/spi/own_test.sql
new file mode 100644
index 000000000..12729d068
--- /dev/null
+++ b/src/exchangedb/spi/own_test.sql
@@ -0,0 +1,201 @@
+DROP TABLE IF EXISTS X;
+CREATE TABLE X (
+ a integer
+);
+
+INSERT INTO X (a)
+ VALUES (1), (2), (3), (4), (5), (6), (7);
+
+DROP TABLE IF EXISTS Y;
+CREATE TABLE Y (col1 INT, col2 INT);
+INSERT INTO Y (col1,col2)
+ VALUES (1,2), (2,0), (0,4), (4,0), (0,6), (6,7), (7,8);
+
+DROP TABLE IF EXISTS Z;
+CREATE TABLE Z (col1 BYTEA);
+
+DROP TABLE IF EXISTS deposits;
+CREATE TABLE deposits(
+ deposit_serial_id BIGINT GENERATED BY DEFAULT AS IDENTITY
+ ,shard INT8 NOT NULL
+ ,coin_pub BYTEA NOT NULL CHECK (LENGTH(coin_pub)=32)
+ ,known_coin_id INT8 NOT NULL
+ ,amount_with_fee_val INT8 NOT NULL
+ ,amount_with_fee_frac INT4 NOT NULL
+ ,wallet_timestamp INT8 NOT NULL
+ ,exchange_timestamp INT8 NOT NULL
+ ,refund_deadline INT8 NOT NULL
+ ,wire_deadline INT8 NOT NULL
+ ,merchant_pub BYTEA NOT NULL CHECK (LENGTH(merchant_pub)=32)
+ ,h_contract_terms BYTEA NOT NULL CHECK (LENGTH(h_contract_terms)=64)
+ ,coin_sig BYTEA NOT NULL CHECK (LENGTH(coin_sig)=64)
+ ,wire_salt BYTEA NOT NULL CHECK (LENGTH(wire_salt)=16)
+ ,wire_target_h_payto BYTEA CHECK (LENGTH(wire_target_h_payto)=32)
+ ,done BOOLEAN NOT NULL DEFAULT FALSE
+ ,policy_blocked BOOLEAN NOT NULL DEFAULT FALSE
+ ,policy_details_serial_id INT8);
+
+
+DROP FUNCTION IF EXISTS pg_spi_insert_int;
+CREATE FUNCTION pg_spi_insert_int()
+ RETURNS VOID
+ LANGUAGE c VOLATILE COST 100
+AS '$libdir/own_test', 'pg_spi_insert_int';
+
+DROP FUNCTION IF EXISTS pg_spi_select_from_x;
+CREATE FUNCTION pg_spi_select_from_x()
+ RETURNS INT8
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_select_from_x';
+
+/*
+CREATE FUNCTION pg_spi_select_pair_from_y()
+ RETURNS valuest
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_select_pair_from_y';
+*/
+/*CREATE FUNCTION pg_spi_select_with_cond()
+ RETURNS INT8
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_select_with_cond';
+*/
+
+DROP FUNCTION IF EXISTS pg_spi_update_y;
+CREATE FUNCTION pg_spi_update_y()
+ RETURNS VOID
+ LANGUAGE c VOLATILE COST 100
+AS '$libdir/own_test', 'pg_spi_update_y';
+
+DROP FUNCTION IF EXISTS pg_spi_prepare_example;
+CREATE FUNCTION pg_spi_prepare_example()
+ RETURNS INT8
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_example';
+
+DROP FUNCTION IF EXISTS pg_spi_prepare_example_without_saveplan;
+CREATE FUNCTION pg_spi_prepare_example_without_saveplan()
+ RETURNS INT8
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_example_without_saveplan';
+
+DROP FUNCTION IF EXISTS pg_spi_prepare_insert;
+CREATE FUNCTION pg_spi_prepare_insert()
+ RETURNS VOID
+ LANGUAGE c VOLATILE COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_insert';
+
+DROP FUNCTION IF EXISTS pg_spi_prepare_insert_without_saveplan;
+CREATE FUNCTION pg_spi_prepare_insert_without_saveplan()
+ RETURNS VOID
+ LANGUAGE c VOLATILE COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_insert_without_saveplan';
+
+/*
+CREATE FUNCTION pg_spi_prepare_select_with_cond()
+ RETURNS INT8
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_select_with_cond';
+*/
+
+DROP FUNCTION IF EXISTS pg_spi_prepare_select_with_cond_without_saveplan;
+CREATE FUNCTION pg_spi_prepare_select_with_cond_without_saveplan()
+ RETURNS INT8
+ LANGUAGE c COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_select_with_cond_without_saveplan';
+
+DROP FUNCTION IF EXISTS pg_spi_prepare_update;
+CREATE FUNCTION pg_spi_prepare_update()
+ RETURNS VOID
+ LANGUAGE c VOLATILE COST 100
+AS '$libdir/own_test', 'pg_spi_prepare_update';
+
+DROP FUNCTION IF EXISTS pg_spi_get_dep_ref_fees;
+CREATE FUNCTION pg_spi_get_dep_ref_fees(
+ IN in_timestamp INT8
+ ,IN merchant_pub BYTEA
+ ,IN wire_target_h_payto BYTEA
+ ,IN wtid BYTEA
+)
+ RETURNS VOID
+ LANGUAGE c VOLATILE COST 100
+AS '$libdir/own_test', 'pg_spi_get_dep_ref_fees';
+
+DROP FUNCTION IF EXISTS update_pg_spi_get_dep_ref_fees;
+CREATE FUNCTION update_pg_spi_get_dep_ref_fees(
+ IN in_refund_deadline INT8,
+ IN in_merchant_pub BYTEA,
+ IN in_wire_target_h_payto BYTEA
+)
+RETURNS SETOF record
+LANGUAGE plpgsql VOLATILE
+AS $$
+DECLARE
+
+BEGIN
+RETURN QUERY
+ UPDATE deposits
+ SET done = TRUE
+ WHERE NOT (done OR policy_blocked)
+ AND refund_deadline < in_refund_deadline
+ AND merchant_pub = in_merchant_pub
+ AND wire_target_h_payto = in_wire_target_h_payto
+ RETURNING
+ deposit_serial_id,
+ coin_pub,
+ amount_with_fee_val,
+ amount_with_fee_frac;
+END $$;
+
+DROP FUNCTION IF EXISTS stored_procedure_update;
+CREATE FUNCTION stored_procedure_update(
+IN in_number INT8
+)
+RETURNS VOID
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ UPDATE Y
+ SET col1 = 4
+ WHERE col2 = in_number;
+END $$;
+
+DROP FUNCTION IF EXISTS stored_procedure_select;
+CREATE FUNCTION stored_procedure_select(OUT out_value INT8)
+RETURNS INT8
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ SELECT 1
+ INTO out_value
+ FROM X;
+ RETURN;
+END $$;
+
+
+DROP FUNCTION IF EXISTS stored_procedure_insert;
+CREATE FUNCTION stored_procedure_insert(
+IN in_number INT8,
+OUT out_number INT8)
+RETURNS INT8
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ INSERT INTO X (a)
+ VALUES (in_number)
+ RETURNING a INTO out_number;
+END $$;
+
+DROP FUNCTION IF EXISTS stored_procedure_select_with_cond;
+CREATE FUNCTION stored_procedure_select_with_cond(
+IN in_number INT8,
+OUT out_number INT8
+)
+RETURNS INT8
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ SELECT col1 INTO out_number
+ FROM Y
+ WHERE col2 = in_number;
+ RETURN;
+END $$;
diff --git a/src/exchangedb/spi/perf_own_test.c b/src/exchangedb/spi/perf_own_test.c
new file mode 100644
index 000000000..92be2235e
--- /dev/null
+++ b/src/exchangedb/spi/perf_own_test.c
@@ -0,0 +1,25 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2023 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchangedb/spi/perf_own_test.c
+ * @brief benchmark for 'own_test'
+ * @author Joseph Xu
+ */
+#include "exchangedb/platform.h"
+#include "exchangedb/taler_exchangedb_lib.h"
+#include "exchangedb/taler_json_lib.h"
+#include "exchangedb/taler_exchangedb_plugin.h"
+#include "own_test.sql"
diff --git a/src/exchangedb/spi/pg_aggregate.c b/src/exchangedb/spi/pg_aggregate.c
new file mode 100644
index 000000000..721f247c7
--- /dev/null
+++ b/src/exchangedb/spi/pg_aggregate.c
@@ -0,0 +1,411 @@
+#include "postgres.h"
+#include "fmgr.h"
+#include "utils/numeric.h"
+#include "utils/builtins.h"
+#include "executor/spi.h"
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1 (get_deposit_summary);
+
+Datum
+get_deposit_summary (PG_FUNCTION_ARGS)
+{
+
+ static SPIPlanPtr deposit_plan;
+ static SPIPlanPtr refund_plan;
+ static SPIPlanPtr refund_by_coin_plan;
+ static SPIPlanPtr norm_refund_by_coin_plan;
+ static SPIPlanPtr fully_refunded_by_coins_plan;
+ static SPIPlanPtr fees_plan;
+
+ int shard = PG_GETARG_INT32 (0);
+ char *sql;
+ char *merchant_pub = text_to_cstring (PG_GETARG_TEXT_P (1));
+ char *wire_target_h_payto = text_to_cstring (PG_GETARG_TEXT_P (2));
+ char *wtid_raw = text_to_cstring (PG_GETARG_TEXT_P (3));
+ int refund_deadline = PG_GETARG_INT32 (4);
+ int conn = SPI_connect ();
+ if (conn != SPI_OK_CONNECT)
+ {
+ elog (ERROR, "DB connection failed ! \n");
+ }
+
+ if (deposit_plan == NULL
+ || refund_plan == NULL
+ || refund_by_coin_plan == NULL
+ || norm_refund_by_coin_plan = NULL
+ || fully_refunded_coins_plan = NULL
+ || fees_plan
+ == NULL)
+ {
+ if (deposit_plan == NULL)
+ {
+ int nargs = 3;
+ Oid argtypes[3];
+ argtypes[0] = INT8OID;
+ argtypes[1] = BYTEAOID;
+ argtypes[2] = BYTEAOID;
+ const char *dep_sql =
+ " UPDATE deposits"
+ " SET done=TRUE"
+ " WHERE NOT (done OR policy_blocked)"
+ " AND refund_deadline < $1"
+ " AND merchant_pub = $2"
+ " AND wire_target_h_payto = $3"
+ " RETURNING"
+ " deposit_serial_id"
+ " ,coin_pub"
+ " ,amount_with_fee_val AS amount_val"
+ " ,amount_with_fee_frac AS amount_frac";
+ SPIPlanPtr new_plan =
+ SPI_prepare (dep_sql, 4, argtypes);
+ if (new_plan == NULL)
+ {
+ elog (ERROR, "SPI_prepare for deposit failed ! \n");
+ }
+ deposit_plan = SPI_saveplan (new_plan);
+ if (deposit_plan == NULL)
+ {
+ elog (ERROR, "SPI_saveplan for deposit failed ! \n");
+ }
+ }
+
+ Datum values[4];
+ values[0] = Int64GetDatum (refund_deadline);
+ values[1] = CStringGetDatum (merchant_pub);
+ values[2] = CStringGetDatum (wire_target_h_payto);
+ int ret = SPI_execute_plan (deposit_plan,
+ values,
+ NULL,
+ true,
+ 0);
+ if (ret != SPI_OK_UPDATE)
+ {
+ elog (ERROR, "Failed to execute subquery 1\n");
+ }
+ uint64_t *dep_deposit_serial_ids = palloc (sizeof(uint64_t)
+ * SPI_processed);
+ BYTEA **dep_coin_pubs = palloc (sizeof(BYTEA *) * SPI_processed);
+ uint64_t *dep_amount_vals = palloc (sizeof(uint64_t) * SPI_processed);
+ uint32_t *dep_amount_fracs = palloc (sizeof(uint32_t) * SPI_processed);
+ for (unsigned int i = 0; i < SPI_processed; i++)
+ {
+ HeapTuple tuple = SPI_tuptable->vals[i];
+ dep_deposit_serial_ids[i] =
+ DatumGetInt64 (SPI_getbinval (tuple, SPI_tuptable->tupdesc, 1, &ret));
+ dep_coin_pubs[i] =
+ DatumGetByteaP (SPI_getbinval (tuple, SPI_tuptable->tupdesc, 2, &ret));
+ dep_amount_vals[i] =
+ DatumGetInt64 (SPI_getbinval (tuple, SPI_tuptable->tupdesc, 3, &ret));
+ dep_amount_fracs[i] =
+ DatumGetInt32 (SPI_getbinval (tuple, SPI_tuptable->tupdesc, 4, &ret));
+ }
+
+
+ if (refund_plan == NULL)
+ {
+ const char *ref_sql =
+ "ref AS ("
+ " SELECT"
+ " amount_with_fee_val AS refund_val"
+ " ,amount_with_fee_frac AS refund_frac"
+ " ,coin_pub"
+ " ,deposit_serial_id"
+ " FROM refunds"
+ " WHERE coin_pub IN (SELECT coin_pub FROM dep)"
+ " AND deposit_serial_id IN (SELECT deposit_serial_id FROM dep)) ";
+ SPIPlanPtr new_plan = SPI_prepare (ref_sql, 0, NULL);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare for refund failed ! \n");
+ refund_plan = SPI_saveplan (new_plan);
+ if (refund_plan == NULL)
+ {
+ elog (ERROR, "SPI_saveplan for refund failed ! \n");
+ }
+ }
+
+ int64t_t *ref_deposit_serial_ids = palloc (sizeof(int64_t) * SPI_processed);
+
+ int res = SPI_execute_plan (refund_plan, NULL, NULL, false, 0);
+ if (res != SPI_OK_SELECT)
+ {
+ elog (ERROR, "Failed to execute subquery 2\n");
+ }
+ SPITupleTable *tuptable = SPI_tuptable;
+ TupleDesc tupdesc = tuptable->tupdesc;
+ for (unsigned int i = 0; i < SPI_processed; i++)
+ {
+ HeapTuple tuple = tuptable->vals[i];
+ Datum refund_val = SPI_getbinval (tuple, tupdesc, 1, &refund_val_isnull);
+ Datum refund_frac = SPI_getbinval (tuple, tupdesc, 2,
+ &refund_frac_isnull);
+ Datum coin_pub = SPI_getbinval (tuple, tupdesc, 3, &coin_pub_isnull);
+ Datum deposit_serial_id = SPI_getbinval (tuple, tupdesc, 4,
+ &deposit_serial_id_isnull);
+ if (refund_val_isnull
+ || refund_frac_isnull
+ || coin_pub_isnull
+ || deposit_serial_id_isnull)
+ {
+ elog (ERROR, "Failed to retrieve data from subquery 2");
+ }
+ uint64_t refund_val_int = DatumGetUInt64 (refund_val);
+ uint32_t refund_frac_int = DatumGetUInt32 (refund_frac);
+ BYTEA coin_pub = DatumGetByteaP (coin_pub);
+ ref_deposit_serial_ids = DatumGetInt64 (deposit_serial_id);
+
+ refund *new_refund = (refund*) palloc (sizeof(refund));
+ new_refund->coin_pub = coin_pub_str;
+ new_refund->deposit_serial_id = deposit_serial_id_int;
+ new_refund->amount_with_fee_val = refund_val_int;
+ new_refund->amount_with_fee_frac = refund_frac_int;
+ }
+
+
+ if (refund_by_coin_plan == NULL)
+ {
+ const char *ref_by_coin_sql =
+ "ref_by_coin AS ("
+ " SELECT"
+ " SUM(refund_val) AS sum_refund_val"
+ " ,SUM(refund_frac) AS sum_refund_frac"
+ " ,coin_pub"
+ " ,deposit_serial_id"
+ " FROM ref"
+ " GROUP BY coin_pub, deposit_serial_id) ";
+ SPIPlanPtr new_plan = SPI_prepare (ref_by_coin_sql, 0, NULL);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare for refund by coin failed ! \n");
+ refund_by_coin_plan = SPI_saveplan (new_plan);
+ if (refund_by_coin_plan == NULL)
+ elog (ERROR, "SPI_saveplan for refund failed");
+ }
+
+
+ int res = SPI_execute_plan (refund_by_coin_plan, NULL, NULL, false, 0);
+ if (res != SPI_OK_SELECT)
+ {
+ elog (ERROR, "Failed to execute subquery 2\n");
+ }
+
+ SPITupleTable *tuptable = SPI_tuptable;
+ TupleDesc tupdesc = tuptable->tupdesc;
+ for (unsigned int i = 0; i < SPI_processed; i++)
+ {
+ HeapTuple tuple = tuptable->vals[i];
+ Datum sum_refund_val = SPI_getbinval (tuple, tupdesc, 1,
+ &refund_val_isnull);
+ Datum sum_refund_frac = SPI_getbinval (tuple, tupdesc, 2,
+ &refund_frac_isnull);
+ Datum coin_pub = SPI_getbinval (tuple, tupdesc, 3, &coin_pub_isnull);
+ Datum deposit_serial_id_int = SPI_getbinval (tuple, tupdesc, 4,
+ &deposit_serial_id_isnull);
+ if (refund_val_isnull
+ || refund_frac_isnull
+ || coin_pub_isnull
+ || deposit_serial_id_isnull)
+ {
+ elog (ERROR, "Failed to retrieve data from subquery 2");
+ }
+ uint64_t s_refund_val_int = DatumGetUInt64 (sum_refund_val);
+ uint32_t s_refund_frac_int = DatumGetUInt32 (sum_refund_frac);
+ BYTEA coin_pub = DatumGetByteaP (coin_pub);
+ uint64_t deposit_serial_id_int = DatumGetInt64 (deposit_serial_id_int);
+ refund *new_refund_by_coin = (refund*) palloc (sizeof(refund));
+ new_refund_by_coin->coin_pub = coin_pub;
+ new_refund_by_coin->deposit_serial_id = deposit_serial_id_int;
+ new_refund_by_coin->refund_amount_with_fee_val = s_refund_val_int;
+ new_refund_by_coin->refund_amount_with_fee_frac = s_refund_frac_int;
+ }
+
+
+ if (norm_refund_by_coin_plan == NULL)
+ {
+ const char *norm_ref_by_coin_sql =
+ "norm_ref_by_coin AS ("
+ " SELECT"
+ " coin_pub"
+ " ,deposit_serial_id"
+ " FROM ref_by_coin) ";
+ SPIPlanPtr new_plan = SPI_prepare (norm_ref_by_coin_sql, 0, NULL);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare for norm refund by coin failed ! \n");
+ norm_refund_by_coin_plan = SPI_saveplan (new_plan);
+ if (norm_refund_by_coin_plan == NULL)
+ elog (ERROR, "SPI_saveplan for norm refund by coin failed ! \n");
+ }
+
+ double norm_refund_val =
+ ((double) new_refund_by_coin->refund_amount_with_fee_val
+ + (double) new_refund_by_coin->refund_amount_with_fee_frac) / 100000000;
+ double norm_refund_frac =
+ (double) new_refund_by_coin->refund_amount_with_fee_frac % 100000000;
+
+ if (fully_refunded_coins_plan == NULL)
+ {
+ const char *fully_refunded_coins_sql =
+ "fully_refunded_coins AS ("
+ " SELECT"
+ " dep.coin_pub"
+ " FROM norm_ref_by_coin norm"
+ " JOIN dep"
+ " ON (norm.coin_pub = dep.coin_pub"
+ " AND norm.deposit_serial_id = dep.deposit_serial_id"
+ " AND norm.norm_refund_val = dep.amount_val"
+ " AND norm.norm_refund_frac = dep.amount_frac)) ";
+ SPIPlanPtr new_plan =
+ SPI_prepare (fully_refunded_coins_sql, 0, NULL);
+ if (new_plan == NULL)
+ elog (ERROR, "SPI_prepare for fully refunded coins failed ! \n");
+ fully_refunded_coins_plan = SPI_saveplan (new_plan);
+ if (fully_refunded_coins_plan == NULL)
+ elog (ERROR, "SPI_saveplan for fully refunded coins failed ! \n");
+ }
+
+ int res = SPI_execute_plan (fully_refunded_coins_sql);
+ if (res != SPI_OK_SELECT)
+ elog (ERROR, "Failed to execute subquery 4\n");
+ SPITupleTable *tuptable = SPI_tuptable;
+ TupleDesc tupdesc = tuptable->tupdesc;
+
+ BYTEA coin_pub = SPI_getbinval (tuple, tupdesc, 1, &coin_pub_isnull);
+ if (fees_plan == NULL)
+ {
+ const char *fees_sql =
+ "SELECT "
+ " denom.fee_deposit_val AS fee_val, "
+ " denom.fee_deposit_frac AS fee_frac, "
+ " cs.deposit_serial_id "
+ "FROM dep cs "
+ "JOIN known_coins kc USING (coin_pub) "
+ "JOIN denominations denom USING (denominations_serial) "
+ "WHERE coin_pub NOT IN (SELECT coin_pub FROM fully_refunded_coins)";
+ SPIPlanPtr new_plan =
+ SPI_prepare (fees_sql, 0, NULL);
+ if (new_plan == NULL)
+ {
+ elog (ERROR, "SPI_prepare for fees failed ! \n");
+ }
+ fees_plan = SPI_saveplan (new_plan);
+ if (fees_plan == NULL)
+ {
+ elog (ERROR, "SPI_saveplan for fees failed ! \n");
+ }
+ }
+ }
+ int fees_ntuples;
+ SPI_execute (fees_sql, true, 0);
+ if (SPI_result_code () != SPI_OK_SELECT)
+ {
+ ereport (
+ ERROR,
+ (errcode (ERRCODE_INTERNAL_ERROR),
+ errmsg ("deposit fee query failed: error code %d \n",
+ SPI_result_code ())));
+ }
+ fees_ntuples = SPI_processed;
+
+ if (fees_ntuples > 0)
+ {
+ for (i = 0; i < fees_ntuples; i++)
+ {
+ Datum fee_val_datum =
+ SPI_getbinval (SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1,
+ &fee_null);
+ Datum fee_frac_datum =
+ SPI_getbinval (SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2,
+ &fee_null);
+ Datum deposit_id_datum =
+ SPI_getbinval (SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 3,
+ &deposit_null);
+ if (! fee_null && ! deposit_null)
+ {
+ int64 fee_val = DatumGetInt64 (fee_val_datum);
+ int32 fee_frac = DatumGetInt32 (fee_frac_datum);
+ int64 deposit_id = DatumGetInt64 (deposit_id_datum);
+ sum_fee_value += fee_val;
+ sum_fee_fraction += fee_frac;
+ char *insert_agg_sql =
+ psprintf (
+ "INSERT INTO "
+ "aggregation_tracking(deposit_serial_id, wtid_raw)"
+ " VALUES (%lld, '%s')",
+ deposit_id, wtid_raw);
+ SPI_execute (insert_agg_sql, false, 0);
+ }
+ }
+ }
+
+ TupleDesc tupdesc;
+ SPITupleTable *tuptable = SPI_tuptable;
+ HeapTuple tuple;
+ Datum result;
+
+ if (tuptable == NULL || SPI_processed != 1)
+ {
+ ereport (
+ ERROR,
+ (errcode (ERRCODE_INTERNAL_ERROR),
+ errmsg ("Unexpected result \n")));
+ }
+ tupdesc = SPI_tuptable->tupdesc;
+ tuple = SPI_tuptable->vals[0];
+ result = HeapTupleGetDatum (tuple);
+
+ TupleDesc result_desc = CreateTemplateTupleDesc (6, false);
+ TupleDescInitEntry (result_desc, (AttrNumber) 1, "sum_deposit_value", INT8OID,
+ -1, 0);
+ TupleDescInitEntry (result_desc, (AttrNumber) 2, "sum_deposit_fraction",
+ INT4OID, -1, 0);
+ TupleDescInitEntry (result_desc, (AttrNumber) 3, "sum_refund_value", INT8OID,
+ -1, 0);
+ TupleDescInitEntry (result_desc, (AttrNumber) 4, "sum_refund_fraction",
+ INT4OID, -1, 0);
+ TupleDescInitEntry (result_desc, (AttrNumber) 5, "sum_fee_value", INT8OID, -1,
+ 0);
+ TupleDescInitEntry (result_desc, (AttrNumber) 6, "sum_fee_fraction", INT4OID,
+ -1, 0);
+
+ int ret = SPI_prepare (sql, 4, argtypes);
+ if (ret != SPI_OK_PREPARE)
+ {
+ elog (ERROR, "Failed to prepare statement: %s \n", sql);
+ }
+
+ ret = SPI_execute_plan (plan, args, nulls, true, 0);
+ if (ret != SPI_OK_SELECT)
+ {
+ elog (ERROR, "Failed to execute statement: %s \n", sql);
+ }
+
+ if (SPI_processed > 0)
+ {
+ HeapTuple tuple;
+ Datum values[6];
+ bool nulls[6] = {false};
+ values[0] =
+ SPI_getbinval (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1,
+ &nulls[0]);
+ values[1] =
+ SPI_getbinval (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2,
+ &nulls[1]);
+ values[2] =
+ SPI_getbinval (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 3,
+ &nulls[2]);
+ values[3] =
+ SPI_getbinval (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 4,
+ &nulls[3]);
+ values[4] =
+ SPI_getbinval (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 5,
+ &nulls[4]);
+ values[5] =
+ SPI_getbinval (SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 6,
+ &nulls[5]);
+ tuple = heap_form_tuple (result_desc, values, nulls);
+ PG_RETURN_DATUM (HeapTupleGetDatum (tuple));
+ }
+ SPI_finish ();
+
+ PG_RETURN_NULL ();
+}
diff --git a/src/exchangedb/test-exchange-db-postgres.conf b/src/exchangedb/test-exchange-db-postgres.conf
index f6db76942..7f0332686 100644
--- a/src/exchangedb/test-exchange-db-postgres.conf
+++ b/src/exchangedb/test-exchange-db-postgres.conf
@@ -2,6 +2,8 @@
#The DB plugin to use
DB = postgres
+BASE_URL = http://localhost/
+
[exchangedb-postgres]
#The connection string the plugin has to use for connecting to the database
@@ -26,3 +28,9 @@ IDLE_RESERVE_EXPIRATION_TIME = 4 weeks
# After how long do we forget about reserves? Should be above
# the legal expiration timeframe of withdrawn coins.
LEGAL_RESERVE_EXPIRATION_TIME = 7 years
+
+# Shift to apply before aggregating.
+AGGREGATOR_SHIFT = 1s
+
+# Number of purses per account by default.
+DEFAULT_PURSE_LIMIT = 1
diff --git a/src/exchangedb/test-exchangedb-batch-reserves-in-insert-postgres b/src/exchangedb/test-exchangedb-batch-reserves-in-insert-postgres
new file mode 100755
index 000000000..bc044232a
--- /dev/null
+++ b/src/exchangedb/test-exchangedb-batch-reserves-in-insert-postgres
@@ -0,0 +1,210 @@
+#! /bin/bash
+
+# test-exchangedb-batch-reserves-in-insert-postgres - temporary wrapper script for .libs/test-exchangedb-batch-reserves-in-insert-postgres
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
+#
+# The test-exchangedb-batch-reserves-in-insert-postgres program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' libtalerexchangedb.la ../../src/json/libtalerjson.la ../../src/util/libtalerutil.la ../../src/pq/libtalerpq.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "test-exchangedb-batch-reserves-in-insert-postgres:test-exchangedb-batch-reserves-in-insert-postgres:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "test-exchangedb-batch-reserves-in-insert-postgres:test-exchangedb-batch-reserves-in-insert-postgres:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "test-exchangedb-batch-reserves-in-insert-postgres:test-exchangedb-batch-reserves-in-insert-postgres:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='test-exchangedb-batch-reserves-in-insert-postgres'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/priscilla/exchange/src/exchangedb/.libs:/home/priscilla/exchange/src/json/.libs:/home/priscilla/exchange/src/util/.libs:/home/priscilla/exchange/src/pq/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/exchangedb/test-exchangedb-by-j-postgres b/src/exchangedb/test-exchangedb-by-j-postgres
new file mode 100755
index 000000000..11d295cc2
--- /dev/null
+++ b/src/exchangedb/test-exchangedb-by-j-postgres
@@ -0,0 +1,210 @@
+#! /bin/bash
+
+# test-exchangedb-by-j-postgres - temporary wrapper script for .libs/test-exchangedb-by-j-postgres
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
+#
+# The test-exchangedb-by-j-postgres program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' libtalerexchangedb.la ../../src/json/libtalerjson.la ../../src/util/libtalerutil.la ../../src/pq/libtalerpq.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "test-exchangedb-by-j-postgres:test-exchangedb-by-j-postgres:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "test-exchangedb-by-j-postgres:test-exchangedb-by-j-postgres:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "test-exchangedb-by-j-postgres:test-exchangedb-by-j-postgres:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='test-exchangedb-by-j-postgres'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/priscilla/exchange/src/exchangedb/.libs:/home/priscilla/exchange/src/json/.libs:/home/priscilla/exchange/src/util/.libs:/home/priscilla/exchange/src/pq/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/exchangedb/test-exchangedb-populate-link-data-postgres b/src/exchangedb/test-exchangedb-populate-link-data-postgres
new file mode 100755
index 000000000..f3d673519
--- /dev/null
+++ b/src/exchangedb/test-exchangedb-populate-link-data-postgres
@@ -0,0 +1,210 @@
+#! /bin/bash
+
+# test-exchangedb-populate-link-data-postgres - temporary wrapper script for .libs/test-exchangedb-populate-link-data-postgres
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
+#
+# The test-exchangedb-populate-link-data-postgres program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' libtalerexchangedb.la ../../src/json/libtalerjson.la ../../src/util/libtalerutil.la ../../src/pq/libtalerpq.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "test-exchangedb-populate-link-data-postgres:test-exchangedb-populate-link-data-postgres:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "test-exchangedb-populate-link-data-postgres:test-exchangedb-populate-link-data-postgres:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "test-exchangedb-populate-link-data-postgres:test-exchangedb-populate-link-data-postgres:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='test-exchangedb-populate-link-data-postgres'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/priscilla/exchange/src/exchangedb/.libs:/home/priscilla/exchange/src/json/.libs:/home/priscilla/exchange/src/util/.libs:/home/priscilla/exchange/src/pq/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/exchangedb/test-exchangedb-populate-ready-deposit-postgres b/src/exchangedb/test-exchangedb-populate-ready-deposit-postgres
new file mode 100755
index 000000000..7747f381c
--- /dev/null
+++ b/src/exchangedb/test-exchangedb-populate-ready-deposit-postgres
@@ -0,0 +1,210 @@
+#! /bin/bash
+
+# test-exchangedb-populate-ready-deposit-postgres - temporary wrapper script for .libs/test-exchangedb-populate-ready-deposit-postgres
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
+#
+# The test-exchangedb-populate-ready-deposit-postgres program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' libtalerexchangedb.la ../../src/json/libtalerjson.la ../../src/util/libtalerutil.la ../../src/pq/libtalerpq.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "test-exchangedb-populate-ready-deposit-postgres:test-exchangedb-populate-ready-deposit-postgres:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "test-exchangedb-populate-ready-deposit-postgres:test-exchangedb-populate-ready-deposit-postgres:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "test-exchangedb-populate-ready-deposit-postgres:test-exchangedb-populate-ready-deposit-postgres:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='test-exchangedb-populate-ready-deposit-postgres'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/priscilla/exchange/src/exchangedb/.libs:/home/priscilla/exchange/src/json/.libs:/home/priscilla/exchange/src/util/.libs:/home/priscilla/exchange/src/pq/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/exchangedb/test-exchangedb-populate-select-refunds-by-coin-postgres b/src/exchangedb/test-exchangedb-populate-select-refunds-by-coin-postgres
new file mode 100755
index 000000000..ce7ebb712
--- /dev/null
+++ b/src/exchangedb/test-exchangedb-populate-select-refunds-by-coin-postgres
@@ -0,0 +1,210 @@
+#! /bin/bash
+
+# test-exchangedb-populate-select-refunds-by-coin-postgres - temporary wrapper script for .libs/test-exchangedb-populate-select-refunds-by-coin-postgres
+# Generated by libtool (GNU libtool) 2.4.6 Debian-2.4.6-15
+#
+# The test-exchangedb-populate-select-refunds-by-coin-postgres program cannot be directly executed until all the libtool
+# libraries that it depends on are installed.
+#
+# This wrapper script should never be moved out of the build directory.
+# If it is, it will not operate correctly.
+
+# Sed substitution that helps us do robust quoting. It backslashifies
+# metacharacters that are still active within double-quoted strings.
+sed_quote_subst='s|\([`"$\\]\)|\\\1|g'
+
+# Be Bourne compatible
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
+ emulate sh
+ NULLCMD=:
+ # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
+ # is contrary to our usage. Disable this feature.
+ alias -g '${1+"$@"}'='"$@"'
+ setopt NO_GLOB_SUBST
+else
+ case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
+fi
+BIN_SH=xpg4; export BIN_SH # for Tru64
+DUALCASE=1; export DUALCASE # for MKS sh
+
+# The HP-UX ksh and POSIX shell print the target directory to stdout
+# if CDPATH is set.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+relink_command=""
+
+# This environment variable determines our operation mode.
+if test "$libtool_install_magic" = "%%%MAGIC variable%%%"; then
+ # install mode needs the following variables:
+ generated_by_libtool_version='2.4.6'
+ notinst_deplibs=' libtalerexchangedb.la ../../src/json/libtalerjson.la ../../src/util/libtalerutil.la ../../src/pq/libtalerpq.la'
+else
+ # When we are sourced in execute mode, $file and $ECHO are already set.
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ file="$0"
+
+# A function that is used when there is no print builtin or printf.
+func_fallback_echo ()
+{
+ eval 'cat <<_LTECHO_EOF
+$1
+_LTECHO_EOF'
+}
+ ECHO="printf %s\\n"
+ fi
+
+# Very basic option parsing. These options are (a) specific to
+# the libtool wrapper, (b) are identical between the wrapper
+# /script/ and the wrapper /executable/ that is used only on
+# windows platforms, and (c) all begin with the string --lt-
+# (application programs are unlikely to have options that match
+# this pattern).
+#
+# There are only two supported options: --lt-debug and
+# --lt-dump-script. There is, deliberately, no --lt-help.
+#
+# The first argument to this parsing function should be the
+# script's ../../libtool value, followed by no.
+lt_option_debug=
+func_parse_lt_options ()
+{
+ lt_script_arg0=$0
+ shift
+ for lt_opt
+ do
+ case "$lt_opt" in
+ --lt-debug) lt_option_debug=1 ;;
+ --lt-dump-script)
+ lt_dump_D=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%/[^/]*$%%'`
+ test "X$lt_dump_D" = "X$lt_script_arg0" && lt_dump_D=.
+ lt_dump_F=`$ECHO "X$lt_script_arg0" | /usr/bin/sed -e 's/^X//' -e 's%^.*/%%'`
+ cat "$lt_dump_D/$lt_dump_F"
+ exit 0
+ ;;
+ --lt-*)
+ $ECHO "Unrecognized --lt- option: '$lt_opt'" 1>&2
+ exit 1
+ ;;
+ esac
+ done
+
+ # Print the debug banner immediately:
+ if test -n "$lt_option_debug"; then
+ echo "test-exchangedb-populate-select-refunds-by-coin-postgres:test-exchangedb-populate-select-refunds-by-coin-postgres:$LINENO: libtool wrapper (GNU libtool) 2.4.6 Debian-2.4.6-15" 1>&2
+ fi
+}
+
+# Used when --lt-debug. Prints its arguments to stdout
+# (redirection is the responsibility of the caller)
+func_lt_dump_args ()
+{
+ lt_dump_args_N=1;
+ for lt_arg
+ do
+ $ECHO "test-exchangedb-populate-select-refunds-by-coin-postgres:test-exchangedb-populate-select-refunds-by-coin-postgres:$LINENO: newargv[$lt_dump_args_N]: $lt_arg"
+ lt_dump_args_N=`expr $lt_dump_args_N + 1`
+ done
+}
+
+# Core function for launching the target application
+func_exec_program_core ()
+{
+
+ if test -n "$lt_option_debug"; then
+ $ECHO "test-exchangedb-populate-select-refunds-by-coin-postgres:test-exchangedb-populate-select-refunds-by-coin-postgres:$LINENO: newargv[0]: $progdir/$program" 1>&2
+ func_lt_dump_args ${1+"$@"} 1>&2
+ fi
+ exec "$progdir/$program" ${1+"$@"}
+
+ $ECHO "$0: cannot exec $program $*" 1>&2
+ exit 1
+}
+
+# A function to encapsulate launching the target application
+# Strips options in the --lt-* namespace from $@ and
+# launches target application with the remaining arguments.
+func_exec_program ()
+{
+ case " $* " in
+ *\ --lt-*)
+ for lt_wr_arg
+ do
+ case $lt_wr_arg in
+ --lt-*) ;;
+ *) set x "$@" "$lt_wr_arg"; shift;;
+ esac
+ shift
+ done ;;
+ esac
+ func_exec_program_core ${1+"$@"}
+}
+
+ # Parse options
+ func_parse_lt_options "$0" ${1+"$@"}
+
+ # Find the directory that this script lives in.
+ thisdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+ test "x$thisdir" = "x$file" && thisdir=.
+
+ # Follow symbolic links until we get to the real thisdir.
+ file=`ls -ld "$file" | /usr/bin/sed -n 's/.*-> //p'`
+ while test -n "$file"; do
+ destdir=`$ECHO "$file" | /usr/bin/sed 's%/[^/]*$%%'`
+
+ # If there was a directory component, then change thisdir.
+ if test "x$destdir" != "x$file"; then
+ case "$destdir" in
+ [\\/]* | [A-Za-z]:[\\/]*) thisdir="$destdir" ;;
+ *) thisdir="$thisdir/$destdir" ;;
+ esac
+ fi
+
+ file=`$ECHO "$file" | /usr/bin/sed 's%^.*/%%'`
+ file=`ls -ld "$thisdir/$file" | /usr/bin/sed -n 's/.*-> //p'`
+ done
+
+ # Usually 'no', except on cygwin/mingw when embedded into
+ # the cwrapper.
+ WRAPPER_SCRIPT_BELONGS_IN_OBJDIR=no
+ if test "$WRAPPER_SCRIPT_BELONGS_IN_OBJDIR" = "yes"; then
+ # special case for '.'
+ if test "$thisdir" = "."; then
+ thisdir=`pwd`
+ fi
+ # remove .libs from thisdir
+ case "$thisdir" in
+ *[\\/].libs ) thisdir=`$ECHO "$thisdir" | /usr/bin/sed 's%[\\/][^\\/]*$%%'` ;;
+ .libs ) thisdir=. ;;
+ esac
+ fi
+
+ # Try to get the absolute directory name.
+ absdir=`cd "$thisdir" && pwd`
+ test -n "$absdir" && thisdir="$absdir"
+
+ program='test-exchangedb-populate-select-refunds-by-coin-postgres'
+ progdir="$thisdir/.libs"
+
+
+ if test -f "$progdir/$program"; then
+ # Add our own library path to LD_LIBRARY_PATH
+ LD_LIBRARY_PATH="/home/priscilla/exchange/src/exchangedb/.libs:/home/priscilla/exchange/src/json/.libs:/home/priscilla/exchange/src/util/.libs:/home/priscilla/exchange/src/pq/.libs:$LD_LIBRARY_PATH"
+
+ # Some systems cannot cope with colon-terminated LD_LIBRARY_PATH
+ # The second colon is a workaround for a bug in BeOS R4 sed
+ LD_LIBRARY_PATH=`$ECHO "$LD_LIBRARY_PATH" | /usr/bin/sed 's/::*$//'`
+
+ export LD_LIBRARY_PATH
+
+ if test "$libtool_execute_magic" != "%%%MAGIC variable%%%"; then
+ # Run the actual program with our arguments.
+ func_exec_program ${1+"$@"}
+ fi
+ else
+ # The program doesn't exist.
+ $ECHO "$0: error: '$progdir/$program' does not exist" 1>&2
+ $ECHO "This script is just a wrapper for $program." 1>&2
+ $ECHO "See the libtool documentation for more information." 1>&2
+ exit 1
+ fi
+fi
diff --git a/src/exchangedb/test_exchangedb.c b/src/exchangedb/test_exchangedb.c
index c68cba816..22788a562 100644
--- a/src/exchangedb/test_exchangedb.c
+++ b/src/exchangedb/test_exchangedb.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 2014-2023 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -33,9 +33,9 @@ static int result;
/**
* Report line of error if @a cond is true, and jump to label "drop".
*/
-#define FAILIF(cond) \
+#define FAILIF(cond) \
do { \
- if (! (cond)) { break;} \
+ if (! (cond)) { break;} \
GNUNET_break (0); \
goto drop; \
} while (0)
@@ -45,7 +45,8 @@ static int result;
* Initializes @a ptr with random data.
*/
#define RND_BLK(ptr) \
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, \
+ sizeof (*ptr))
/**
* Initializes @a ptr with zeros.
@@ -95,6 +96,7 @@ mark_prepare_cb (void *cls,
const char *buf,
size_t buf_size)
{
+ (void) cls;
GNUNET_assert (11 == buf_size);
GNUNET_assert (0 == strcasecmp (wire_method,
"testcase"));
@@ -108,11 +110,69 @@ mark_prepare_cb (void *cls,
/**
+ * Simple check that config retrieval and setting for extensions work
+ */
+static enum GNUNET_GenericReturnValue
+test_extension_manifest (void)
+{
+ char *manifest;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->get_extension_manifest (plugin->cls,
+ "fnord",
+ &manifest));
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->set_extension_manifest (plugin->cls,
+ "fnord",
+ "bar"));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_extension_manifest (plugin->cls,
+ "fnord",
+ &manifest));
+
+ FAILIF (0 != strcmp ("bar", manifest));
+ GNUNET_free (manifest);
+
+ /* let's do this again! */
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->set_extension_manifest (plugin->cls,
+ "fnord",
+ "buzz"));
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_extension_manifest (plugin->cls,
+ "fnord",
+ &manifest));
+
+ FAILIF (0 != strcmp ("buzz", manifest));
+ GNUNET_free (manifest);
+
+ /* let's do this again, with NULL */
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->set_extension_manifest (plugin->cls,
+ "fnord",
+ NULL));
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_extension_manifest (plugin->cls,
+ "fnord",
+ &manifest));
+
+ FAILIF (NULL != manifest);
+
+ return GNUNET_OK;
+drop:
+ return GNUNET_SYSERR;
+}
+
+
+/**
* Test API relating to persisting the wire plugins preparation data.
*
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
test_wire_prepare (void)
{
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
@@ -161,18 +221,15 @@ check_reserve (const struct TALER_ReservePublicKeyP *pub,
const char *currency)
{
struct TALER_EXCHANGEDB_Reserve reserve;
- struct TALER_EXCHANGEDB_KycStatus kyc;
reserve.pub = *pub;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->reserves_get (plugin->cls,
- &reserve,
- &kyc));
+ &reserve));
FAILIF (value != reserve.balance.value);
FAILIF (fraction != reserve.balance.fraction);
FAILIF (0 != strcmp (currency,
reserve.balance.currency));
- FAILIF (kyc.ok);
return GNUNET_OK;
drop:
return GNUNET_SYSERR;
@@ -194,75 +251,64 @@ struct DenomKeyPair
static void
destroy_denom_key_pair (struct DenomKeyPair *dkp)
{
- GNUNET_CRYPTO_rsa_public_key_free (dkp->pub.rsa_public_key);
- GNUNET_CRYPTO_rsa_private_key_free (dkp->priv.rsa_private_key);
+ TALER_denom_pub_free (&dkp->pub);
+ TALER_denom_priv_free (&dkp->priv);
GNUNET_free (dkp);
}
/**
- * Create a denominaiton key pair by registering the denomination in the DB.
+ * Create a denomination key pair by registering the denomination in the DB.
*
* @param size the size of the denomination key
* @param now time to use for key generation, legal expiration will be 3h later.
- * @param fee_withdraw withdraw fee to use
- * @param fee_deposit deposit fee to use
- * @param fee_refresh refresh fee to use
- * @param fee_refund refund fee to use
+ * @param fees fees to use
* @return the denominaiton key pair; NULL upon error
*/
static struct DenomKeyPair *
create_denom_key_pair (unsigned int size,
- struct GNUNET_TIME_Absolute now,
+ struct GNUNET_TIME_Timestamp now,
const struct TALER_Amount *value,
- const struct TALER_Amount *fee_withdraw,
- const struct TALER_Amount *fee_deposit,
- const struct TALER_Amount *fee_refresh,
- const struct TALER_Amount *fee_refund)
+ const struct TALER_DenomFeeSet *fees)
{
struct DenomKeyPair *dkp;
struct TALER_EXCHANGEDB_DenominationKey dki;
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue2;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
dkp = GNUNET_new (struct DenomKeyPair);
- dkp->priv.rsa_private_key = GNUNET_CRYPTO_rsa_private_key_create (size);
- GNUNET_assert (NULL != dkp->priv.rsa_private_key);
- dkp->pub.rsa_public_key
- = GNUNET_CRYPTO_rsa_private_key_get_public (dkp->priv.rsa_private_key);
-
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_priv_create (&dkp->priv,
+ &dkp->pub,
+ GNUNET_CRYPTO_BSA_RSA,
+ size));
/* Using memset() as fields like master key and signature
are not properly initialized for this test. */
memset (&dki,
0,
sizeof (struct TALER_EXCHANGEDB_DenominationKey));
dki.denom_pub = dkp->pub;
- GNUNET_TIME_round_abs (&now);
- dki.issue.properties.start = GNUNET_TIME_absolute_hton (now);
- dki.issue.properties.expire_withdraw = GNUNET_TIME_absolute_hton
- (GNUNET_TIME_absolute_add (now,
- GNUNET_TIME_UNIT_HOURS));
- dki.issue.properties.expire_deposit = GNUNET_TIME_absolute_hton
- (GNUNET_TIME_absolute_add
- (now,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_HOURS, 2)));
- dki.issue.properties.expire_legal = GNUNET_TIME_absolute_hton
- (GNUNET_TIME_absolute_add
- (now,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_HOURS, 3)));
- TALER_amount_hton (&dki.issue.properties.value, value);
- TALER_amount_hton (&dki.issue.properties.fee_withdraw, fee_withdraw);
- TALER_amount_hton (&dki.issue.properties.fee_deposit, fee_deposit);
- TALER_amount_hton (&dki.issue.properties.fee_refresh, fee_refresh);
- TALER_amount_hton (&dki.issue.properties.fee_refund, fee_refund);
- GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
- &dki.issue.properties.denom_hash);
-
- dki.issue.properties.purpose.size = htonl (sizeof (struct
- TALER_DenominationKeyValidityPS));
- dki.issue.properties.purpose.purpose = htonl (
- TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY);
+ dki.issue.start = now;
+ dki.issue.expire_withdraw
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_UNIT_HOURS));
+ dki.issue.expire_deposit
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 2)));
+ dki.issue.expire_legal
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (
+ now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS, 3)));
+ dki.issue.value = *value;
+ dki.issue.fees = *fees;
+ TALER_denom_pub_hash (&dkp->pub,
+ &dki.issue.denom_hash);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_denomination_info (plugin->cls,
&dki.denom_pub,
@@ -272,9 +318,10 @@ create_denom_key_pair (unsigned int size,
destroy_denom_key_pair (dkp);
return NULL;
}
+ memset (&issue2, 0, sizeof (issue2));
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->get_denomination_info (plugin->cls,
- &dki.issue.properties.denom_hash,
+ &dki.issue.denom_hash,
&issue2))
{
GNUNET_break (0);
@@ -293,10 +340,7 @@ create_denom_key_pair (unsigned int size,
static struct TALER_Amount value;
-static struct TALER_Amount fee_withdraw;
-static struct TALER_Amount fee_deposit;
-static struct TALER_Amount fee_refresh;
-static struct TALER_Amount fee_refund;
+static struct TALER_DenomFeeSet fees;
static struct TALER_Amount fee_closing;
static struct TALER_Amount amount_with_fee;
@@ -312,9 +356,9 @@ static struct TALER_Amount amount_with_fee;
#define MELT_NOREVEAL_INDEX 1
/**
- * How big do we make the coin envelopes?
+ * How big do we make the RSA keys?
*/
-#define COIN_ENC_MAX_SIZE 512
+#define RSA_KEY_SIZE 1024
static struct TALER_EXCHANGEDB_RefreshRevealedCoin *revealed_coins;
@@ -331,24 +375,15 @@ static struct TALER_TransferPublicKeyP tpub;
* @param rowid unique serial ID for the row in our database
* @param num_freshcoins size of the @a rrcs array
* @param rrcs array of @a num_freshcoins information about coins to be created
- * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
- * @param tprivs array of @e num_tprivs transfer private keys
- * @param tp transfer public key information
*/
static void
never_called_cb (void *cls,
uint32_t num_freshcoins,
- const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
- unsigned int num_tprivs,
- const struct TALER_TransferPrivateKeyP *tprivs,
- const struct TALER_TransferPublicKeyP *tp)
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs)
{
(void) cls;
(void) num_freshcoins;
(void) rrcs;
- (void) num_tprivs;
- (void) tprivs;
- (void) tp;
GNUNET_assert (0); /* should never be called! */
}
@@ -361,18 +396,12 @@ never_called_cb (void *cls,
* @param rowid unique serial ID for the row in our database
* @param num_freshcoins size of the @a rrcs array
* @param rrcs array of @a num_freshcoins information about coins to be created
- * @param num_tprivs number of entries in @a tprivs, should be #TALER_CNC_KAPPA - 1
- * @param tprivsr array of @e num_tprivs transfer private keys
- * @param tpr transfer public key information
*/
static void
check_refresh_reveal_cb (
void *cls,
uint32_t num_freshcoins,
- const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs,
- unsigned int num_tprivs,
- const struct TALER_TransferPrivateKeyP *tprivsr,
- const struct TALER_TransferPublicKeyP *tpr)
+ const struct TALER_EXCHANGEDB_RefreshRevealedCoin *rrcs)
{
(void) cls;
/* compare the refresh commit coin arrays */
@@ -382,20 +411,13 @@ check_refresh_reveal_cb (
&revealed_coins[cnt];
const struct TALER_EXCHANGEDB_RefreshRevealedCoin *bcoin = &rrcs[cnt];
- GNUNET_assert (acoin->coin_ev_size == bcoin->coin_ev_size);
GNUNET_assert (0 ==
- GNUNET_memcmp (acoin->coin_ev,
- bcoin->coin_ev));
+ TALER_blinded_planchet_cmp (&acoin->blinded_planchet,
+ &bcoin->blinded_planchet));
GNUNET_assert (0 ==
- GNUNET_CRYPTO_rsa_public_key_cmp (
- acoin->denom_pub.rsa_public_key,
- bcoin->denom_pub.
- rsa_public_key));
+ GNUNET_memcmp (&acoin->h_denom_pub,
+ &bcoin->h_denom_pub));
}
- GNUNET_assert (0 == GNUNET_memcmp (&tpub, tpr));
- GNUNET_assert (0 == memcmp (tprivs, tprivsr,
- sizeof(struct TALER_TransferPrivateKeyP)
- * (TALER_CNC_KAPPA - 1)));
}
@@ -414,6 +436,7 @@ static unsigned int auditor_row_cnt;
* @param cls closure
* @param rowid unique serial ID for the refresh session in our DB
* @param denom_pub denomination of the @a coin_pub
+ * @param h_age_commitment hash of age commitment that went into the minting, may be NULL
* @param coin_pub public key of the coin
* @param coin_sig signature from the coin
* @param amount_with_fee amount that was deposited including fee
@@ -422,23 +445,27 @@ static unsigned int auditor_row_cnt;
* @param rc what is the session hash
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
-audit_refresh_session_cb (void *cls,
- uint64_t rowid,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_Amount *amount_with_fee,
- uint32_t noreveal_index,
- const struct TALER_RefreshCommitmentP *rc)
+static enum GNUNET_GenericReturnValue
+audit_refresh_session_cb (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_AgeCommitmentHash *h_age_commitment,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_CoinSpendSignatureP *coin_sig,
+ const struct TALER_Amount *amount_with_fee,
+ uint32_t noreveal_index,
+ const struct TALER_RefreshCommitmentP *rc)
{
(void) cls;
(void) rowid;
(void) denom_pub;
+ (void) coin_pub;
(void) coin_sig;
(void) amount_with_fee;
(void) noreveal_index;
(void) rc;
+ (void) h_age_commitment;
auditor_row_cnt++;
return GNUNET_OK;
}
@@ -469,22 +496,19 @@ handle_link_data_cb (void *cls,
NULL != ldlp;
ldlp = ldlp->next)
{
- int found;
+ bool found;
- found = GNUNET_NO;
+ found = false;
for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
{
- GNUNET_assert (NULL != ldlp->ev_sig.rsa_signature);
if ( (0 ==
- GNUNET_CRYPTO_rsa_public_key_cmp (ldlp->denom_pub.rsa_public_key,
- new_dkp[cnt]->pub.rsa_public_key))
- &&
+ TALER_denom_pub_cmp (&ldlp->denom_pub,
+ &new_dkp[cnt]->pub)) &&
(0 ==
- GNUNET_CRYPTO_rsa_signature_cmp (ldlp->ev_sig.rsa_signature,
- revealed_coins[cnt].coin_sig.
- rsa_signature)) )
+ TALER_blinded_denom_sig_cmp (&ldlp->ev_sig,
+ &revealed_coins[cnt].coin_sig)) )
{
- found = GNUNET_YES;
+ found = true;
break;
}
}
@@ -494,236 +518,44 @@ handle_link_data_cb (void *cls,
/**
- * Function to test melting of coins as part of a refresh session
- *
- * @return #GNUNET_OK if everything went well; #GNUNET_SYSERR if not
- */
-static int
-test_melting (void)
-{
- struct TALER_EXCHANGEDB_Refresh refresh_session;
- struct TALER_EXCHANGEDB_Melt ret_refresh_session;
- struct DenomKeyPair *dkp;
- struct TALER_DenominationPublicKey *new_denom_pubs;
- int ret;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute now;
-
- ret = GNUNET_SYSERR;
- RND_BLK (&refresh_session);
- dkp = NULL;
- new_dkp = NULL;
- new_denom_pubs = NULL;
- /* create and test a refresh session */
- refresh_session.noreveal_index = MELT_NOREVEAL_INDEX;
- /* create a denomination (value: 1; fraction: 100) */
- now = GNUNET_TIME_absolute_get ();
- GNUNET_TIME_round_abs (&now);
- dkp = create_denom_key_pair (512,
- now,
- &value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund);
- GNUNET_assert (NULL != dkp);
- /* initialize refresh session melt data */
- {
- struct GNUNET_HashCode hc;
-
- RND_BLK (&refresh_session.coin.coin_pub);
- GNUNET_CRYPTO_hash (&refresh_session.coin.coin_pub,
- sizeof (refresh_session.coin.coin_pub),
- &hc);
- refresh_session.coin.denom_sig.rsa_signature =
- GNUNET_CRYPTO_rsa_sign_fdh (dkp->priv.rsa_private_key,
- &hc);
- GNUNET_assert (NULL != refresh_session.coin.denom_sig.rsa_signature);
- GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
- &refresh_session.coin.denom_pub_hash);
- refresh_session.amount_with_fee = amount_with_fee;
- }
-
- /* test insert_melt & get_melt */
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->get_melt (plugin->cls,
- &refresh_session.rc,
- &ret_refresh_session));
- FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
- plugin->ensure_coin_known (plugin->cls,
- &refresh_session.coin));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_melt (plugin->cls,
- &refresh_session));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_melt (plugin->cls,
- &refresh_session.rc,
- &ret_refresh_session));
- FAILIF (refresh_session.noreveal_index !=
- ret_refresh_session.session.noreveal_index);
- FAILIF (0 !=
- TALER_amount_cmp (&refresh_session.amount_with_fee,
- &ret_refresh_session.session.amount_with_fee));
- FAILIF (0 !=
- TALER_amount_cmp (&fee_refresh,
- &ret_refresh_session.melt_fee));
- FAILIF (0 !=
- GNUNET_memcmp (&refresh_session.rc, &ret_refresh_session.session.rc));
- FAILIF (0 != GNUNET_memcmp (&refresh_session.coin_sig,
- &ret_refresh_session.session.coin_sig));
- FAILIF (NULL !=
- ret_refresh_session.session.coin.denom_sig.rsa_signature);
- FAILIF (0 != memcmp (&refresh_session.coin.coin_pub,
- &ret_refresh_session.session.coin.coin_pub,
- sizeof (refresh_session.coin.coin_pub)));
- FAILIF (0 !=
- GNUNET_memcmp (&refresh_session.coin.denom_pub_hash,
- &ret_refresh_session.session.coin.denom_pub_hash));
-
- /* test 'select_refreshes_above_serial_id' */
- auditor_row_cnt = 0;
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->select_refreshes_above_serial_id (plugin->cls,
- 0,
- &audit_refresh_session_cb,
- NULL));
- FAILIF (1 != auditor_row_cnt);
-
- new_dkp = GNUNET_new_array (MELT_NEW_COINS,
- struct DenomKeyPair *);
- new_denom_pubs = GNUNET_new_array (MELT_NEW_COINS,
- struct TALER_DenominationPublicKey);
- revealed_coins
- = GNUNET_new_array (MELT_NEW_COINS,
- struct TALER_EXCHANGEDB_RefreshRevealedCoin);
- for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
- {
- struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
- struct GNUNET_HashCode hc;
- struct GNUNET_TIME_Absolute now;
-
- now = GNUNET_TIME_absolute_get ();
- GNUNET_TIME_round_abs (&now);
- new_dkp[cnt] = create_denom_key_pair (1024,
- now,
- &value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund);
- GNUNET_assert (NULL != new_dkp[cnt]);
- new_denom_pubs[cnt] = new_dkp[cnt]->pub;
- ccoin = &revealed_coins[cnt];
- ccoin->coin_ev_size = (size_t) GNUNET_CRYPTO_random_u64 (
- GNUNET_CRYPTO_QUALITY_WEAK,
- COIN_ENC_MAX_SIZE);
- ccoin->coin_ev = GNUNET_malloc (ccoin->coin_ev_size);
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
- ccoin->coin_ev,
- ccoin->coin_ev_size);
- RND_BLK (&hc);
- ccoin->denom_pub = new_dkp[cnt]->pub;
- ccoin->coin_sig.rsa_signature
- = GNUNET_CRYPTO_rsa_sign_fdh (new_dkp[cnt]->priv.rsa_private_key,
- &hc);
- }
- RND_BLK (&tprivs);
- RND_BLK (&tpub);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->get_refresh_reveal (plugin->cls,
- &refresh_session.rc,
- &never_called_cb,
- NULL));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_refresh_reveal (plugin->cls,
- &refresh_session.rc,
- MELT_NEW_COINS,
- revealed_coins,
- TALER_CNC_KAPPA - 1,
- tprivs,
- &tpub));
- FAILIF (0 >=
- plugin->get_refresh_reveal (plugin->cls,
- &refresh_session.rc,
- &check_refresh_reveal_cb,
- NULL));
-
-
- qs = plugin->get_link_data (plugin->cls,
- &refresh_session.coin.coin_pub,
- &handle_link_data_cb,
- NULL);
- FAILIF (0 >= qs);
- {
- /* Just to test fetching a coin with melt history */
- struct TALER_EXCHANGEDB_TransactionList *tl;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = plugin->get_coin_transactions (plugin->cls,
- &refresh_session.coin.coin_pub,
- GNUNET_YES,
- &tl);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
- plugin->free_coin_transaction_list (plugin->cls,
- tl);
- }
-
-
- ret = GNUNET_OK;
-drop:
- if (NULL != revealed_coins)
- {
- for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
- {
- if (NULL != revealed_coins[cnt].coin_sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (
- revealed_coins[cnt].coin_sig.rsa_signature);
- GNUNET_free (revealed_coins[cnt].coin_ev);
- }
- GNUNET_free (revealed_coins);
- revealed_coins = NULL;
- }
- destroy_denom_key_pair (dkp);
- GNUNET_CRYPTO_rsa_signature_free (
- refresh_session.coin.denom_sig.rsa_signature);
- GNUNET_free (new_denom_pubs);
- for (unsigned int cnt = 0;
- (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]);
- cnt++)
- destroy_denom_key_pair (new_dkp[cnt]);
- GNUNET_free (new_dkp);
- return ret;
-}
-
-
-/**
* Callback that should never be called.
*/
static void
cb_wt_never (void *cls,
uint64_t serial_id,
const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_wire,
- const json_t *wire,
- struct GNUNET_TIME_Absolute exec_time,
- const struct GNUNET_HashCode *h_contract_terms,
+ const char *account_payto_uri,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp exec_time,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *coin_value,
const struct TALER_Amount *coin_fee)
{
+ (void) cls;
+ (void) serial_id;
+ (void) merchant_pub;
+ (void) account_payto_uri;
+ (void) h_payto;
+ (void) exec_time;
+ (void) h_contract_terms;
+ (void) denom_pub;
+ (void) coin_pub;
+ (void) coin_value;
+ (void) coin_fee;
GNUNET_assert (0); /* this statement should be unreachable */
}
static struct TALER_MerchantPublicKeyP merchant_pub_wt;
-static struct GNUNET_HashCode h_wire_wt;
-static struct GNUNET_HashCode h_contract_terms_wt;
+static struct TALER_MerchantWireHashP h_wire_wt;
+static struct TALER_PrivateContractHashP h_contract_terms_wt;
static struct TALER_CoinSpendPublicKeyP coin_pub_wt;
static struct TALER_Amount coin_value_wt;
static struct TALER_Amount coin_fee_wt;
static struct TALER_Amount transfer_value_wt;
-static struct GNUNET_TIME_Absolute wire_out_date;
+static struct GNUNET_TIME_Timestamp wire_out_date;
static struct TALER_WireTransferIdentifierRawP wire_out_wtid;
@@ -734,24 +566,26 @@ static void
cb_wt_check (void *cls,
uint64_t rowid,
const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct GNUNET_HashCode *h_wire,
- const json_t *wire,
- struct GNUNET_TIME_Absolute exec_time,
- const struct GNUNET_HashCode *h_contract_terms,
+ const char *account_payto_uri,
+ const struct TALER_PaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp exec_time,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *coin_value,
const struct TALER_Amount *coin_fee)
{
+ (void) rowid;
+ (void) denom_pub;
+ (void) h_payto;
GNUNET_assert (cls == &cb_wt_never);
GNUNET_assert (0 == GNUNET_memcmp (merchant_pub,
&merchant_pub_wt));
- GNUNET_assert (0 == strcmp (json_string_value (json_object_get (wire,
- "payto_uri")),
+ GNUNET_assert (0 == strcmp (account_payto_uri,
"payto://iban/DE67830654080004822650?receiver-name=Test"));
- GNUNET_assert (0 == GNUNET_memcmp (h_wire,
- &h_wire_wt));
- GNUNET_assert (exec_time.abs_value_us == wire_out_date.abs_value_us);
+ GNUNET_assert (GNUNET_TIME_timestamp_cmp (exec_time,
+ ==,
+ wire_out_date));
GNUNET_assert (0 == GNUNET_memcmp (h_contract_terms,
&h_contract_terms_wt));
GNUNET_assert (0 == GNUNET_memcmp (coin_pub,
@@ -764,146 +598,36 @@ cb_wt_check (void *cls,
/**
- * Here #deposit_cb() will store the row ID of the deposit.
- */
-static uint64_t deposit_rowid;
-
-
-/**
- * Function called with details about deposits that
- * have been made. Called in the test on the
- * deposit given in @a cls.
- *
- * @param cls closure a `struct TALER_EXCHANGEDB_Deposit *`
- * @param rowid unique ID for the deposit in our DB, used for marking
- * it as 'tiny' or 'done'
- * @param merchant_pub public key of the merchant
- * @param coin_pub public key of the coin
- * @param amount_with_fee amount that was deposited including fee
- * @param deposit_fee amount the exchange gets to keep as transaction fees
- * @param h_contract_terms hash of the proposal data known to merchant and customer
- * @param wire wire details for the merchant
- * @return transaction status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT to continue to iterate
- */
-static enum GNUNET_DB_QueryStatus
-deposit_cb (void *cls,
- uint64_t rowid,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- const json_t *wire)
-{
- struct TALER_EXCHANGEDB_Deposit *deposit = cls;
- struct GNUNET_HashCode h_wire;
-
- deposit_rowid = rowid;
- GNUNET_assert (GNUNET_OK ==
- TALER_JSON_merchant_wire_signature_hash (wire,
- &h_wire));
- if ( (0 != GNUNET_memcmp (merchant_pub,
- &deposit->merchant_pub)) ||
- (0 != TALER_amount_cmp (amount_with_fee,
- &deposit->amount_with_fee)) ||
- (0 != TALER_amount_cmp (deposit_fee,
- &deposit->deposit_fee)) ||
- (0 != GNUNET_memcmp (h_contract_terms,
- &deposit->h_contract_terms)) ||
- (0 != memcmp (coin_pub,
- &deposit->coin.coin_pub,
- sizeof (struct TALER_CoinSpendPublicKeyP))) ||
- (0 != GNUNET_memcmp (&h_wire,
- &deposit->h_wire)) )
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
-
-
-/**
- * Function called with details about deposits that
- * have been made. Called in the test on the
- * deposit given in @a cls.
- *
- * @param cls closure a `struct TALER_EXCHANGEDB_Deposit *`
- * @param rowid unique ID for the deposit in our DB, used for marking
- * it as 'tiny' or 'done'
- * @param coin_pub public key of the coin
- * @param amount_with_fee amount that was deposited including fee
- * @param deposit_fee amount the exchange gets to keep as transaction fees
- * @param h_contract_terms hash of the proposal data known to merchant and customer
- * @return transaction status code, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT to continue to iterate
+ * Here we store the hash of the payto URI.
*/
-static enum GNUNET_DB_QueryStatus
-matching_deposit_cb (void *cls,
- uint64_t rowid,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct GNUNET_HashCode *h_contract_terms)
-{
- struct TALER_EXCHANGEDB_Deposit *deposit = cls;
-
- deposit_rowid = rowid;
- if ( (0 != TALER_amount_cmp (amount_with_fee,
- &deposit->amount_with_fee)) ||
- (0 != TALER_amount_cmp (deposit_fee,
- &deposit->deposit_fee)) ||
- (0 != GNUNET_memcmp (h_contract_terms,
- &deposit->h_contract_terms)) ||
- (0 != memcmp (coin_pub,
- &deposit->coin.coin_pub,
- sizeof (struct TALER_CoinSpendPublicKeyP))) )
- {
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
+static struct TALER_PaytoHashP wire_target_h_payto;
/**
- * Callback for #select_deposits_above_serial_id ()
+ * Callback for #select_coin_deposits_above_serial_id ()
*
* @param cls closure
* @param rowid unique serial ID for the deposit in our DB
* @param exchange_timestamp when did the deposit happen
- * @param wallet_timestamp when did the wallet sign the contract
- * @param merchant_pub public key of the merchant
+ * @param deposit deposit details
* @param denom_pub denomination of the @a coin_pub
- * @param coin_pub public key of the coin
- * @param coin_sig signature from the coin
- * @param amount_with_fee amount that was deposited including fee
- * @param h_contract_terms hash of the proposal data known to merchant and customer
- * @param refund_deadline by which the merchant advised that he might want
- * to get a refund
- * @param wire_deadline by which the merchant advised that he would like the
- * wire transfer to be executed
- * @param receiver_wire_account wire details for the merchant, NULL from iterate_matching_deposits()
* @param done flag set if the deposit was already executed (or not)
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
audit_deposit_cb (void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute exchange_timestamp,
- struct GNUNET_TIME_Absolute wallet_timestamp,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
+ struct GNUNET_TIME_Timestamp exchange_timestamp,
+ const struct TALER_EXCHANGEDB_Deposit *deposit,
const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_Amount *amount_with_fee,
- const struct GNUNET_HashCode *h_contract_terms,
- struct GNUNET_TIME_Absolute refund_deadline,
- struct GNUNET_TIME_Absolute wire_deadline,
- const json_t *receiver_wire_account,
- int done)
+ bool done)
{
+ (void) cls;
+ (void) rowid;
+ (void) exchange_timestamp;
+ (void) deposit;
+ (void) denom_pub;
+ (void) done;
auditor_row_cnt++;
return GNUNET_OK;
}
@@ -922,18 +646,20 @@ audit_deposit_cb (void *cls,
* @param h_contract_terms hash of the proposal data in
* the contract between merchant and customer
* @param rtransaction_id refund transaction ID chosen by the merchant
+ * @param full_refund the deposit
* @param amount_with_fee amount that was deposited including fee
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
audit_refund_cb (void *cls,
uint64_t rowid,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantSignatureP *merchant_sig,
- const struct GNUNET_HashCode *h_contract_terms,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
uint64_t rtransaction_id,
+ bool full_refund,
const struct TALER_Amount *amount_with_fee)
{
(void) cls;
@@ -945,6 +671,7 @@ audit_refund_cb (void *cls,
(void) h_contract_terms;
(void) rtransaction_id;
(void) amount_with_fee;
+ (void) full_refund;
auditor_row_cnt++;
return GNUNET_OK;
}
@@ -962,14 +689,14 @@ audit_refund_cb (void *cls,
* @param execution_date when did we receive the funds
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
audit_reserve_in_cb (void *cls,
uint64_t rowid,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_Amount *credit,
const char *sender_account_details,
uint64_t wire_reference,
- struct GNUNET_TIME_Absolute execution_date)
+ struct GNUNET_TIME_Timestamp execution_date)
{
(void) cls;
(void) rowid;
@@ -996,14 +723,14 @@ audit_reserve_in_cb (void *cls,
* @param amount_with_fee amount that was withdrawn
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
audit_reserve_out_cb (void *cls,
uint64_t rowid,
- const struct GNUNET_HashCode *h_blind_ev,
+ const struct TALER_BlindedCoinHashP *h_blind_ev,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_ReserveSignatureP *reserve_sig,
- struct GNUNET_TIME_Absolute execution_date,
+ struct GNUNET_TIME_Timestamp execution_date,
const struct TALER_Amount *amount_with_fee)
{
(void) cls;
@@ -1024,28 +751,25 @@ audit_reserve_out_cb (void *cls,
*
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
test_gc (void)
{
struct DenomKeyPair *dkp;
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Absolute past;
- struct TALER_EXCHANGEDB_DenominationKeyInformationP issue2;
- struct GNUNET_HashCode denom_hash;
-
- now = GNUNET_TIME_absolute_get ();
- GNUNET_TIME_round_abs (&now);
- past = GNUNET_TIME_absolute_subtract (now,
- GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_HOURS,
- 4));
- dkp = create_denom_key_pair (1024,
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_TIME_Timestamp past;
+ struct TALER_EXCHANGEDB_DenominationKeyInformation issue2;
+ struct TALER_DenominationHashP denom_hash;
+
+ now = GNUNET_TIME_timestamp_get ();
+ past = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_subtract (now.abs_time,
+ GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_HOURS,
+ 4)));
+ dkp = create_denom_key_pair (RSA_KEY_SIZE,
past,
&value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund);
+ &fees);
GNUNET_assert (NULL != dkp);
if (GNUNET_OK !=
plugin->gc (plugin->cls))
@@ -1054,8 +778,8 @@ test_gc (void)
destroy_denom_key_pair (dkp);
return GNUNET_SYSERR;
}
- GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
- &denom_hash);
+ TALER_denom_pub_hash (&dkp->pub,
+ &denom_hash);
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->get_denomination_info (plugin->cls,
@@ -1076,30 +800,26 @@ test_gc (void)
*
* @return #GNUNET_OK on success
*/
-static int
+static enum GNUNET_GenericReturnValue
test_wire_fees (void)
{
- struct GNUNET_TIME_Absolute start_date;
- struct GNUNET_TIME_Absolute end_date;
- struct TALER_Amount wire_fee;
- struct TALER_Amount closing_fee;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct TALER_WireFeeSet fees;
struct TALER_MasterSignatureP master_sig;
- struct GNUNET_TIME_Absolute sd;
- struct GNUNET_TIME_Absolute ed;
- struct TALER_Amount fee;
- struct TALER_Amount fee2;
+ struct GNUNET_TIME_Timestamp sd;
+ struct GNUNET_TIME_Timestamp ed;
+ struct TALER_WireFeeSet fees2;
struct TALER_MasterSignatureP ms;
- start_date = GNUNET_TIME_absolute_get ();
- GNUNET_TIME_round_abs (&start_date);
- end_date = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
- GNUNET_TIME_round_abs (&end_date);
+ start_date = GNUNET_TIME_timestamp_get ();
+ end_date = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MINUTES);
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":1.424242",
- &wire_fee));
+ &fees.wire));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":2.424242",
- &closing_fee));
+ &fees.closing));
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
&master_sig,
sizeof (master_sig));
@@ -1108,8 +828,7 @@ test_wire_fees (void)
"wire-method",
start_date,
end_date,
- &wire_fee,
- &closing_fee,
+ &fees,
&master_sig))
{
GNUNET_break (0);
@@ -1120,8 +839,7 @@ test_wire_fees (void)
"wire-method",
start_date,
end_date,
- &wire_fee,
- &closing_fee,
+ &fees,
&master_sig))
{
GNUNET_break (0);
@@ -1135,8 +853,7 @@ test_wire_fees (void)
end_date,
&sd,
&ed,
- &fee,
- &fee2,
+ &fees2,
&ms))
{
GNUNET_break (0);
@@ -1148,19 +865,20 @@ test_wire_fees (void)
start_date,
&sd,
&ed,
- &fee,
- &fee2,
+ &fees2,
&ms))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
- if ( (sd.abs_value_us != start_date.abs_value_us) ||
- (ed.abs_value_us != end_date.abs_value_us) ||
- (0 != TALER_amount_cmp (&fee,
- &wire_fee)) ||
- (0 != TALER_amount_cmp (&fee2,
- &closing_fee)) ||
+ if ( (GNUNET_TIME_timestamp_cmp (sd,
+ !=,
+ start_date)) ||
+ (GNUNET_TIME_timestamp_cmp (ed,
+ !=,
+ end_date)) ||
+ (0 != TALER_wire_fee_set_cmp (&fees,
+ &fees2)) ||
(0 != GNUNET_memcmp (&ms,
&master_sig)) )
{
@@ -1185,14 +903,17 @@ static struct TALER_Amount wire_out_amount;
* @param amount amount that was wired
* @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop iteration
*/
-static int
+static enum GNUNET_GenericReturnValue
audit_wire_cb (void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute date,
+ struct GNUNET_TIME_Timestamp date,
const struct TALER_WireTransferIdentifierRawP *wtid,
- const json_t *wire,
+ const char *payto_uri,
const struct TALER_Amount *amount)
{
+ (void) cls;
+ (void) rowid;
+ (void) payto_uri;
auditor_row_cnt++;
GNUNET_assert (0 ==
TALER_amount_cmp (amount,
@@ -1200,7 +921,9 @@ audit_wire_cb (void *cls,
GNUNET_assert (0 ==
GNUNET_memcmp (wtid,
&wire_out_wtid));
- GNUNET_assert (date.abs_value_us == wire_out_date.abs_value_us);
+ GNUNET_assert (GNUNET_TIME_timestamp_cmp (date,
+ ==,
+ wire_out_date));
return GNUNET_OK;
}
@@ -1208,17 +931,23 @@ audit_wire_cb (void *cls,
/**
* Test API relating to wire_out handling.
*
+ * @param bd batch deposit to test
* @return #GNUNET_OK on success
*/
-static int
-test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
+static enum GNUNET_GenericReturnValue
+test_wire_out (const struct TALER_EXCHANGEDB_BatchDeposit *bd)
{
+ const struct TALER_EXCHANGEDB_CoinDepositInformation *deposit = &bd->cdis[0];
+ struct TALER_PaytoHashP h_payto;
+
+ GNUNET_assert (0 < bd->num_cdis);
+ TALER_payto_hash (bd->receiver_wire_account,
+ &h_payto);
auditor_row_cnt = 0;
memset (&wire_out_wtid,
- 42,
+ 41,
sizeof (wire_out_wtid));
- wire_out_date = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&wire_out_date);
+ wire_out_date = GNUNET_TIME_timestamp_get ();
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":1",
&wire_out_amount));
@@ -1230,13 +959,12 @@ test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
plugin->start_deferred_wire_out (plugin->cls));
/* setup values for wire transfer aggregation data */
- merchant_pub_wt = deposit->merchant_pub;
- h_wire_wt = deposit->h_wire;
- h_contract_terms_wt = deposit->h_contract_terms;
+ merchant_pub_wt = bd->merchant_pub;
+ h_contract_terms_wt = bd->h_contract_terms;
coin_pub_wt = deposit->coin.coin_pub;
coin_value_wt = deposit->amount_with_fee;
- coin_fee_wt = fee_deposit;
+ coin_fee_wt = fees.deposit;
GNUNET_assert (0 <
TALER_amount_subtract (&transfer_value_wt,
&coin_value_wt,
@@ -1248,15 +976,17 @@ test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
NULL));
{
- struct GNUNET_HashCode h_contract_terms_wt2 = h_contract_terms_wt;
+ struct TALER_PrivateContractHashP h_contract_terms_wt2 =
+ h_contract_terms_wt;
bool pending;
struct TALER_WireTransferIdentifierRawP wtid2;
struct TALER_Amount coin_contribution2;
struct TALER_Amount coin_fee2;
- struct GNUNET_TIME_Absolute execution_time2;
+ struct GNUNET_TIME_Timestamp execution_time2;
struct TALER_EXCHANGEDB_KycStatus kyc;
+ enum TALER_AmlDecisionState aml;
- h_contract_terms_wt2.bits[0]++;
+ h_contract_terms_wt2.hash.bits[0]++;
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_transfer_by_deposit (plugin->cls,
&h_contract_terms_wt2,
@@ -1268,40 +998,22 @@ test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
&execution_time2,
&coin_contribution2,
&coin_fee2,
- &kyc));
+ &kyc,
+ &aml));
}
- /* insert WT data */
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_aggregation_tracking (plugin->cls,
- &wire_out_wtid,
- deposit_rowid));
-
- /* Now let's fix the transient constraint violation by
- putting in the WTID into the wire_out table */
{
- json_t *wire_out_account;
- struct TALER_WireSalt salt;
+ struct TALER_ReservePublicKeyP rpub;
- memset (&salt,
+ memset (&rpub,
44,
- sizeof (salt));
- wire_out_account = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("payto_uri",
- "payto://x-taler-bank/localhost:8080/1"),
- GNUNET_JSON_pack_data_auto ("salt",
- &salt));
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->store_wire_transfer_out (plugin->cls,
- wire_out_date,
- &wire_out_wtid,
- wire_out_account,
- "my-config-section",
- &wire_out_amount))
- {
- json_decref (wire_out_account);
- FAILIF (1);
- }
- json_decref (wire_out_account);
+ sizeof (rpub));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->store_wire_transfer_out (plugin->cls,
+ wire_out_date,
+ &wire_out_wtid,
+ &h_payto,
+ "my-config-section",
+ &wire_out_amount));
}
/* And now the commit should still succeed! */
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
@@ -1317,8 +1029,9 @@ test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
struct TALER_WireTransferIdentifierRawP wtid2;
struct TALER_Amount coin_contribution2;
struct TALER_Amount coin_fee2;
- struct GNUNET_TIME_Absolute execution_time2;
+ struct GNUNET_TIME_Timestamp execution_time2;
struct TALER_EXCHANGEDB_KycStatus kyc;
+ enum TALER_AmlDecisionState aml = TALER_AML_FROZEN;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->lookup_transfer_by_deposit (plugin->cls,
@@ -1331,11 +1044,14 @@ test_wire_out (const struct TALER_EXCHANGEDB_Deposit *deposit)
&execution_time2,
&coin_contribution2,
&coin_fee2,
- &kyc));
+ &kyc,
+ &aml));
+ FAILIF (TALER_AML_NORMAL != aml);
GNUNET_assert (0 == GNUNET_memcmp (&wtid2,
&wire_out_wtid));
- GNUNET_assert (execution_time2.abs_value_us ==
- wire_out_date.abs_value_us);
+ GNUNET_assert (GNUNET_TIME_timestamp_cmp (execution_time2,
+ ==,
+ wire_out_date));
GNUNET_assert (0 == TALER_amount_cmp (&coin_contribution2,
&coin_value_wt));
GNUNET_assert (0 == TALER_amount_cmp (&coin_fee2,
@@ -1368,19 +1084,26 @@ drop:
* @param coin_blind blinding factor used to blind the coin
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
recoup_cb (void *cls,
uint64_t rowid,
- struct GNUNET_TIME_Absolute timestamp,
+ struct GNUNET_TIME_Timestamp timestamp,
const struct TALER_Amount *amount,
const struct TALER_ReservePublicKeyP *reserve_pub,
const struct TALER_CoinPublicInfo *coin,
const struct TALER_DenominationPublicKey *denom_pub,
const struct TALER_CoinSpendSignatureP *coin_sig,
- const struct TALER_DenominationBlindingKeyP *coin_blind)
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_blind)
{
- const struct TALER_DenominationBlindingKeyP *cb = cls;
+ const union GNUNET_CRYPTO_BlindingSecretP *cb = cls;
+ (void) rowid;
+ (void) timestamp;
+ (void) amount;
+ (void) reserve_pub;
+ (void) coin_sig;
+ (void) coin;
+ (void) denom_pub;
FAILIF (NULL == cb);
FAILIF (0 != GNUNET_memcmp (cb,
coin_blind));
@@ -1391,68 +1114,32 @@ drop:
/**
- * Function called on deposits that are past their due date
- * and have not yet seen a wire transfer.
+ * Function called on batch deposits that may require a
+ * wire transfer.
*
* @param cls closure a `struct TALER_EXCHANGEDB_Deposit *`
- * @param rowid deposit table row of the coin's deposit
- * @param coin_pub public key of the coin
- * @param amount value of the deposit, including fee
- * @param wire where should the funds be wired
- * @param deadline what was the requested wire transfer deadline
- * @param tiny did the exchange defer this transfer because it is too small?
- * @param done did the exchange claim that it made a transfer?
+ * @param batch_deposit_serial_id where in the table are we
+ * @param total_amount value of all missing deposits, including fees
+ * @param wire_target_h_payto hash of the recipient account's payto URI
+ * @param deadline what was the earliest requested wire transfer deadline
*/
static void
-wire_missing_cb (void *cls,
- uint64_t rowid,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount,
- const json_t *wire,
- struct GNUNET_TIME_Absolute deadline,
- /* bool? */ int tiny,
- /* bool? */ int done)
+wire_missing_cb (
+ void *cls,
+ uint64_t batch_deposit_serial_id,
+ const struct TALER_Amount *total_amount,
+ const struct TALER_PaytoHashP *wire_target_h_payto,
+ struct GNUNET_TIME_Timestamp deadline)
{
- const struct TALER_EXCHANGEDB_Deposit *deposit = cls;
- struct GNUNET_HashCode h_wire;
-
- (void) done;
- if (NULL != wire)
- GNUNET_assert (GNUNET_OK ==
- TALER_JSON_merchant_wire_signature_hash (wire,
- &h_wire));
- else
- memset (&h_wire,
- 0,
- sizeof (h_wire));
- if (GNUNET_NO != tiny)
- {
- GNUNET_break (0);
- result = 66;
- }
- if (GNUNET_NO != done)
- {
- GNUNET_break (0);
- result = 66;
- }
- if (0 != TALER_amount_cmp (amount,
- &deposit->amount_with_fee))
- {
- GNUNET_break (0);
- result = 66;
- }
- if (0 != GNUNET_memcmp (coin_pub,
- &deposit->coin.coin_pub))
- {
- GNUNET_break (0);
- result = 66;
- }
- if (0 != GNUNET_memcmp (&h_wire,
- &deposit->h_wire))
- {
- GNUNET_break (0);
- result = 66;
- }
+ const struct TALER_EXCHANGEDB_CoinDepositInformation *deposit = cls;
+
+ (void) batch_deposit_serial_id;
+ (void) deadline;
+ (void) wire_target_h_payto;
+ if (0 ==
+ TALER_amount_cmp (total_amount,
+ &deposit->amount_with_fee))
+ result = 8;
}
@@ -1464,7 +1151,7 @@ wire_missing_cb (void *cls,
* @param amount_with_fee amount being refunded
* @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop
*/
-static int
+static enum GNUNET_GenericReturnValue
check_refund_cb (void *cls,
const struct TALER_Amount *amount_with_fee)
{
@@ -1490,44 +1177,61 @@ run (void *cls)
{
struct GNUNET_CONFIGURATION_Handle *cfg = cls;
struct TALER_CoinSpendSignatureP coin_sig;
- struct GNUNET_TIME_Absolute deadline;
- struct TALER_DenominationBlindingKeyP coin_blind;
+ struct GNUNET_TIME_Timestamp deadline;
+ union GNUNET_CRYPTO_BlindingSecretP coin_blind;
struct TALER_ReservePublicKeyP reserve_pub;
struct TALER_ReservePublicKeyP reserve_pub2;
- struct DenomKeyPair *dkp;
- struct GNUNET_HashCode dkp_pub_hash;
+ struct TALER_ReservePublicKeyP reserve_pub3;
+ struct DenomKeyPair *dkp = NULL;
struct TALER_MasterSignatureP master_sig;
struct TALER_EXCHANGEDB_CollectableBlindcoin cbc;
struct TALER_EXCHANGEDB_CollectableBlindcoin cbc2;
- struct TALER_EXCHANGEDB_ReserveHistory *rh;
+ struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL;
struct TALER_EXCHANGEDB_ReserveHistory *rh_head;
struct TALER_EXCHANGEDB_BankTransfer *bt;
struct TALER_EXCHANGEDB_CollectableBlindcoin *withdraw;
- struct TALER_EXCHANGEDB_Deposit deposit;
- struct TALER_EXCHANGEDB_Deposit deposit2;
+ struct TALER_EXCHANGEDB_CoinDepositInformation deposit;
+ struct TALER_EXCHANGEDB_BatchDeposit bd;
+ struct TALER_CoinSpendPublicKeyP cpub2;
+ struct TALER_MerchantPublicKeyP mpub2;
struct TALER_EXCHANGEDB_Refund refund;
struct TALER_EXCHANGEDB_TransactionList *tl;
struct TALER_EXCHANGEDB_TransactionList *tlp;
- json_t *wire;
const char *sndr = "payto://x-taler-bank/localhost:8080/1";
+ const char *rcvr = "payto://x-taler-bank/localhost:8080/2";
+ const uint32_t num_partitions = 10;
unsigned int matched;
unsigned int cnt;
- uint64_t rr;
enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute now;
- struct TALER_WireSalt salt;
+ struct GNUNET_TIME_Timestamp now;
+ struct TALER_WireSaltP salt;
+ struct TALER_CoinPubHashP c_hash;
+ uint64_t known_coin_id;
+ uint64_t rrc_serial;
+ struct TALER_EXCHANGEDB_Refresh refresh;
+ struct TALER_DenominationPublicKey *new_denom_pubs = NULL;
+ uint64_t reserve_out_serial_id;
+ uint64_t melt_serial_id;
+ struct TALER_PlanchetMasterSecretP ps;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ const struct TALER_ExchangeWithdrawValues *alg_values
+ = TALER_denom_ewv_rsa_singleton ();
- dkp = NULL;
- rh = NULL;
- deposit.coin.denom_sig.rsa_signature = NULL;
+ memset (&deposit,
+ 0,
+ sizeof (deposit));
+ memset (&bd,
+ 0,
+ sizeof (bd));
+ bd.receiver_wire_account = (char *) rcvr;
+ bd.cdis = &deposit;
+ bd.num_cdis = 1;
memset (&salt,
45,
sizeof (salt));
- wire = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("payto_uri",
- "payto://iban/DE67830654080004822650?receiver-name=Test"),
- GNUNET_JSON_pack_data_auto ("salt",
- &salt));
+ memset (&refresh,
+ 0,
+ sizeof (refresh));
ZR_BLK (&cbc);
ZR_BLK (&cbc2);
if (NULL ==
@@ -1538,10 +1242,12 @@ run (void *cls)
}
(void) plugin->drop_tables (plugin->cls);
if (GNUNET_OK !=
- plugin->create_tables (plugin->cls))
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
{
result = 77;
- goto drop;
+ goto cleanup;
}
plugin->preflight (plugin->cls);
FAILIF (GNUNET_OK !=
@@ -1554,163 +1260,572 @@ run (void *cls)
0,
&recoup_cb,
NULL));
+ /* simple extension check */
+ FAILIF (GNUNET_OK !=
+ test_extension_manifest ());
+
RND_BLK (&reserve_pub);
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":1.000010",
&value));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":0.000010",
- &fee_withdraw));
+ &fees.withdraw));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":0.000010",
- &fee_deposit));
+ &fees.deposit));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":0.000010",
- &fee_refresh));
+ &fees.refresh));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":0.000010",
- &fee_refund));
+ &fees.refund));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":1.000010",
&amount_with_fee));
-
result = 4;
FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->get_latest_reserve_in_reference (plugin->cls,
- "exchange-account-1",
- &rr));
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->reserves_in_insert (plugin->cls,
- &reserve_pub,
- &value,
- now,
- sndr,
- "exchange-account-1",
- 4));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_latest_reserve_in_reference (plugin->cls,
- "exchange-account-1",
- &rr));
- FAILIF (4 != rr);
+ plugin->commit (plugin->cls));
+ now = GNUNET_TIME_timestamp_get ();
+ {
+ struct TALER_EXCHANGEDB_ReserveInInfo reserve = {
+ .reserve_pub = &reserve_pub,
+ .balance = &value,
+ .execution_time = now,
+ .sender_account_details = sndr,
+ .exchange_account_name = "exchange-account-1",
+ .wire_reference = 4
+ };
+ enum GNUNET_DB_QueryStatus qsr;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->reserves_in_insert (plugin->cls,
+ &reserve,
+ 1,
+ &qsr));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ qsr);
+ }
FAILIF (GNUNET_OK !=
check_reserve (&reserve_pub,
value.value,
value.fraction,
value.currency));
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->reserves_in_insert (plugin->cls,
- &reserve_pub,
- &value,
- now,
- sndr,
- "exchange-account-1",
- 5));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_latest_reserve_in_reference (plugin->cls,
- "exchange-account-1",
- &rr));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_latest_reserve_in_reference (plugin->cls,
- "exchange-account-1",
- &rr));
- FAILIF (5 != rr);
+ now = GNUNET_TIME_timestamp_get ();
+ RND_BLK (&reserve_pub2);
+ {
+ struct TALER_EXCHANGEDB_ReserveInInfo reserve = {
+ .reserve_pub = &reserve_pub2,
+ .balance = &value,
+ .execution_time = now,
+ .sender_account_details = sndr,
+ .exchange_account_name = "exchange-account-1",
+ .wire_reference = 5
+ };
+ enum GNUNET_DB_QueryStatus qsr;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->reserves_in_insert (plugin->cls,
+ &reserve,
+ 1,
+ &qsr));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ qsr);
+ }
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-2"));
FAILIF (GNUNET_OK !=
check_reserve (&reserve_pub,
- value.value * 2,
- value.fraction * 2,
+ value.value,
+ value.fraction,
+ value.currency));
+ FAILIF (GNUNET_OK !=
+ check_reserve (&reserve_pub2,
+ value.value,
+ value.fraction,
value.currency));
result = 5;
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
- dkp = create_denom_key_pair (1024,
+ now = GNUNET_TIME_timestamp_get ();
+ dkp = create_denom_key_pair (RSA_KEY_SIZE,
now,
&value,
- &fee_withdraw,
- &fee_deposit,
- &fee_refresh,
- &fee_refund);
+ &fees);
GNUNET_assert (NULL != dkp);
- GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
- &dkp_pub_hash);
- RND_BLK (&cbc.h_coin_envelope);
+ TALER_denom_pub_hash (&dkp->pub,
+ &cbc.denom_pub_hash);
RND_BLK (&cbc.reserve_sig);
- cbc.denom_pub_hash = dkp_pub_hash;
- cbc.sig.rsa_signature
- = GNUNET_CRYPTO_rsa_sign_fdh (dkp->priv.rsa_private_key,
- &cbc.h_coin_envelope);
+ RND_BLK (&ps);
+ TALER_planchet_blinding_secret_create (&ps,
+ alg_values,
+ &bks);
+ {
+ struct TALER_PlanchetDetail pd;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_AgeCommitmentHash age_hash;
+ struct TALER_AgeCommitmentHash *p_ah[2] = {
+ NULL,
+ &age_hash
+ };
+
+ /* Call TALER_denom_blind()/TALER_denom_sign_blinded() twice, once without
+ * age_hash, once with age_hash */
+ RND_BLK (&age_hash);
+ for (size_t i = 0; i < sizeof(p_ah) / sizeof(p_ah[0]); i++)
+ {
+
+ RND_BLK (&coin_pub);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_blind (&dkp->pub,
+ &bks,
+ NULL,
+ p_ah[i],
+ &coin_pub,
+ alg_values,
+ &c_hash,
+ &pd.blinded_planchet));
+ TALER_coin_ev_hash (&pd.blinded_planchet,
+ &cbc.denom_pub_hash,
+ &cbc.h_coin_envelope);
+ if (i != 0)
+ TALER_blinded_denom_sig_free (&cbc.sig);
+ GNUNET_assert (
+ GNUNET_OK ==
+ TALER_denom_sign_blinded (
+ &cbc.sig,
+ &dkp->priv,
+ false,
+ &pd.blinded_planchet));
+ TALER_blinded_planchet_free (&pd.blinded_planchet);
+ }
+ }
+
cbc.reserve_pub = reserve_pub;
cbc.amount_with_fee = value;
GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (CURRENCY, &cbc.withdraw_fee));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_withdraw_info (plugin->cls,
- &cbc));
+ TALER_amount_set_zero (CURRENCY,
+ &cbc.withdraw_fee));
+
+ {
+ bool found;
+ bool nonce_reuse;
+ bool balance_ok;
+ bool age_ok;
+ bool conflict;
+ bool denom_unknown;
+ uint16_t maximum_age;
+ uint64_t ruuid;
+ struct TALER_Amount reserve_balance;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_batch_withdraw (plugin->cls,
+ now,
+ &reserve_pub,
+ &value,
+ true,
+ &found,
+ &balance_ok,
+ &reserve_balance,
+ &age_ok,
+ &maximum_age,
+ &ruuid));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_batch_withdraw_insert (plugin->cls,
+ NULL,
+ &cbc,
+ now,
+ ruuid,
+ &denom_unknown,
+ &conflict,
+ &nonce_reuse));
+ GNUNET_assert (found);
+ GNUNET_assert (! nonce_reuse);
+ GNUNET_assert (! denom_unknown);
+ GNUNET_assert (balance_ok);
+ }
+
+
FAILIF (GNUNET_OK !=
check_reserve (&reserve_pub,
+ 0,
+ 0,
+ value.currency));
+ FAILIF (GNUNET_OK !=
+ check_reserve (&reserve_pub2,
value.value,
value.fraction,
value.currency));
-
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->get_reserve_by_h_blind (plugin->cls,
&cbc.h_coin_envelope,
- &reserve_pub2));
+ &reserve_pub3,
+ &reserve_out_serial_id));
FAILIF (0 != GNUNET_memcmp (&reserve_pub,
- &reserve_pub2));
+ &reserve_pub3));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->get_withdraw_info (plugin->cls,
&cbc.h_coin_envelope,
&cbc2));
- FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_sig, &cbc.reserve_sig));
- FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_pub, &cbc.reserve_pub));
+ FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_sig,
+ &cbc.reserve_sig));
+ FAILIF (0 != GNUNET_memcmp (&cbc2.reserve_pub,
+ &cbc.reserve_pub));
result = 6;
- FAILIF (GNUNET_OK !=
- GNUNET_CRYPTO_rsa_verify (&cbc.h_coin_envelope,
- cbc2.sig.rsa_signature,
- dkp->pub.rsa_public_key));
+ {
+ struct TALER_DenominationSignature ds;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&ds,
+ &cbc2.sig,
+ &bks,
+ &c_hash,
+ alg_values,
+ &dkp->pub));
+ FAILIF (GNUNET_OK !=
+ TALER_denom_pub_verify (&dkp->pub,
+ &ds,
+ &c_hash));
+ TALER_denom_sig_free (&ds);
+ }
RND_BLK (&coin_sig);
RND_BLK (&coin_blind);
RND_BLK (&deposit.coin.coin_pub);
- GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
- &deposit.coin.denom_pub_hash);
- deposit.coin.denom_sig = cbc.sig;
- deadline = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&deadline);
- FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
- plugin->ensure_coin_known (plugin->cls,
- &deposit.coin));
+ TALER_denom_pub_hash (&dkp->pub,
+ &deposit.coin.denom_pub_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&deposit.coin.denom_sig,
+ &cbc.sig,
+ &bks,
+ &c_hash,
+ alg_values,
+ &dkp->pub));
+ deadline = GNUNET_TIME_timestamp_get ();
+ {
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+
+ FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+ plugin->ensure_coin_known (plugin->cls,
+ &deposit.coin,
+ &known_coin_id,
+ &dph,
+ &agh));
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+ {
+ struct GNUNET_TIME_Timestamp deposit_timestamp
+ = GNUNET_TIME_timestamp_get ();
+ bool balance_ok;
+ uint32_t bad_balance_idx;
+ bool in_conflict;
+ struct TALER_PaytoHashP h_payto;
+
+ RND_BLK (&h_payto);
+ bd.refund_deadline
+ = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MONTHS);
+ bd.wire_deadline
+ = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_MONTHS);
+ deposit.amount_with_fee = value;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_deposit (plugin->cls,
+ &bd,
+ &deposit_timestamp,
+ &balance_ok,
+ &bad_balance_idx,
+ &in_conflict));
+ FAILIF (! balance_ok);
+ FAILIF (in_conflict);
+ }
+
+ {
+ bool not_found;
+ bool refund_ok;
+ bool gone;
+ bool conflict;
+
+ refund.coin = deposit.coin;
+ refund.details.merchant_pub = bd.merchant_pub;
+ RND_BLK (&refund.details.merchant_sig);
+ refund.details.h_contract_terms = bd.h_contract_terms;
+ refund.details.rtransaction_id = 1;
+ refund.details.refund_amount = value;
+ refund.details.refund_fee = fees.refund;
+ RND_BLK (&refund.details.merchant_sig);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_refund (plugin->cls,
+ &refund,
+ &fees.deposit,
+ known_coin_id,
+ &not_found,
+ &refund_ok,
+ &gone,
+ &conflict));
+ FAILIF (not_found);
+ FAILIF (! refund_ok);
+ FAILIF (gone);
+ FAILIF (conflict);
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->select_refunds_by_coin (plugin->cls,
+ &refund.coin.coin_pub,
+ &refund.details.merchant_pub,
+ &refund.details.h_contract_terms,
+ &check_refund_cb,
+ &refund));
+ }
+
+ /* test do_melt */
+ {
+ bool zombie_required = false;
+ bool balance_ok;
+
+ refresh.coin = deposit.coin;
+ RND_BLK (&refresh.coin_sig);
+ RND_BLK (&refresh.rc);
+ refresh.amount_with_fee = value;
+ refresh.noreveal_index = MELT_NOREVEAL_INDEX;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_melt (plugin->cls,
+ NULL,
+ &refresh,
+ known_coin_id,
+ &zombie_required,
+ &balance_ok));
+ FAILIF (! balance_ok);
+ FAILIF (zombie_required);
+ }
+
+ /* test get_melt */
+ {
+ struct TALER_EXCHANGEDB_Melt ret_refresh_session;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_melt (plugin->cls,
+ &refresh.rc,
+ &ret_refresh_session,
+ &melt_serial_id));
+ FAILIF (refresh.noreveal_index !=
+ ret_refresh_session.session.noreveal_index);
+ FAILIF (0 !=
+ TALER_amount_cmp (&refresh.amount_with_fee,
+ &ret_refresh_session.session.amount_with_fee));
+ FAILIF (0 !=
+ TALER_amount_cmp (&fees.refresh,
+ &ret_refresh_session.melt_fee));
+ FAILIF (0 !=
+ GNUNET_memcmp (&refresh.rc,
+ &ret_refresh_session.session.rc));
+ FAILIF (0 != GNUNET_memcmp (&refresh.coin_sig,
+ &ret_refresh_session.session.coin_sig));
+ FAILIF (0 !=
+ GNUNET_memcmp (&refresh.coin.coin_pub,
+ &ret_refresh_session.session.coin.coin_pub));
+ FAILIF (0 !=
+ GNUNET_memcmp (&refresh.coin.denom_pub_hash,
+ &ret_refresh_session.session.coin.denom_pub_hash));
+ }
+
+ {
+ /* test 'select_refreshes_above_serial_id' */
+ auditor_row_cnt = 0;
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->select_refreshes_above_serial_id (plugin->cls,
+ 0,
+ &audit_refresh_session_cb,
+ NULL));
+ FAILIF (1 != auditor_row_cnt);
+ }
+
+ /* do refresh-reveal */
+ {
+ new_dkp = GNUNET_new_array (MELT_NEW_COINS,
+ struct DenomKeyPair *);
+ new_denom_pubs = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_DenominationPublicKey);
+ revealed_coins
+ = GNUNET_new_array (MELT_NEW_COINS,
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin);
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ struct TALER_EXCHANGEDB_RefreshRevealedCoin *ccoin;
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_CRYPTO_BlindedMessage *rp;
+ struct GNUNET_CRYPTO_RsaBlindedMessage *rsa;
+ struct TALER_BlindedPlanchet *bp;
+
+ now = GNUNET_TIME_timestamp_get ();
+ new_dkp[cnt] = create_denom_key_pair (RSA_KEY_SIZE,
+ now,
+ &value,
+ &fees);
+ GNUNET_assert (NULL != new_dkp[cnt]);
+ new_denom_pubs[cnt] = new_dkp[cnt]->pub;
+ ccoin = &revealed_coins[cnt];
+ bp = &ccoin->blinded_planchet;
+ rp = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage);
+ bp->blinded_message = rp;
+ rp->cipher = GNUNET_CRYPTO_BSA_RSA;
+ rp->rc = 1;
+ rsa = &rp->details.rsa_blinded_message;
+ rsa->blinded_msg_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 (
+ GNUNET_CRYPTO_QUALITY_WEAK,
+ (RSA_KEY_SIZE / 8) - 1);
+ rsa->blinded_msg = GNUNET_malloc (rsa->blinded_msg_size);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
+ rsa->blinded_msg,
+ rsa->blinded_msg_size);
+ TALER_denom_pub_hash (&new_dkp[cnt]->pub,
+ &ccoin->h_denom_pub);
+ TALER_denom_ewv_copy (&ccoin->exchange_vals,
+ alg_values);
+ TALER_coin_ev_hash (bp,
+ &ccoin->h_denom_pub,
+ &ccoin->coin_envelope_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sign_blinded (&ccoin->coin_sig,
+ &new_dkp[cnt]->priv,
+ true,
+ bp));
+ }
+ RND_BLK (&tprivs);
+ RND_BLK (&tpub);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->get_refresh_reveal (plugin->cls,
+ &refresh.rc,
+ &never_called_cb,
+ NULL));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_refresh_reveal (plugin->cls,
+ melt_serial_id,
+ MELT_NEW_COINS,
+ revealed_coins,
+ TALER_CNC_KAPPA - 1,
+ tprivs,
+ &tpub));
+ {
+ struct TALER_BlindedCoinHashP h_coin_ev;
+ struct TALER_CoinSpendPublicKeyP ocp;
+ struct TALER_DenominationHashP denom_hash;
+
+ TALER_denom_pub_hash (&new_denom_pubs[0],
+ &denom_hash);
+ TALER_coin_ev_hash (&revealed_coins[0].blinded_planchet,
+ &denom_hash,
+ &h_coin_ev);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_old_coin_by_h_blind (plugin->cls,
+ &h_coin_ev,
+ &ocp,
+ &rrc_serial));
+ FAILIF (0 !=
+ GNUNET_memcmp (&ocp,
+ &refresh.coin.coin_pub));
+ }
+ FAILIF (0 >=
+ plugin->get_refresh_reveal (plugin->cls,
+ &refresh.rc,
+ &check_refresh_reveal_cb,
+ NULL));
+ qs = plugin->get_link_data (plugin->cls,
+ &refresh.coin.coin_pub,
+ &handle_link_data_cb,
+ NULL);
+ FAILIF (0 >= qs);
+ {
+ /* Just to test fetching a coin with melt history */
+ struct TALER_EXCHANGEDB_TransactionList *tl;
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t etag;
+ struct TALER_Amount balance;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ qs = plugin->get_coin_transactions (plugin->cls,
+ &refresh.coin.coin_pub,
+ 0,
+ 0,
+ &etag,
+ &balance,
+ &h_denom_pub,
+ &tl);
+ FAILIF (0 >= qs);
+ FAILIF (NULL == tl);
+ plugin->free_coin_transaction_list (plugin->cls,
+ tl);
+ }
+ }
+
+ /* do recoup-refresh */
+ {
+ struct GNUNET_TIME_Timestamp recoup_timestamp
+ = GNUNET_TIME_timestamp_get ();
+ union GNUNET_CRYPTO_BlindingSecretP coin_bks;
+ uint64_t new_known_coin_id;
+ struct TALER_CoinPublicInfo new_coin;
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+ bool recoup_ok;
+ bool internal_failure;
+
+ new_coin = deposit.coin; /* steal basic data */
+ RND_BLK (&new_coin.coin_pub);
+ FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+ plugin->ensure_coin_known (plugin->cls,
+ &new_coin,
+ &new_known_coin_id,
+ &dph,
+ &agh));
+ RND_BLK (&coin_bks);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_recoup_refresh (plugin->cls,
+ &deposit.coin.coin_pub,
+ rrc_serial,
+ &coin_bks,
+ &new_coin.coin_pub,
+ new_known_coin_id,
+ &coin_sig,
+ &recoup_timestamp,
+ &recoup_ok,
+ &internal_failure));
+ FAILIF (! recoup_ok);
+ FAILIF (internal_failure);
+ }
+
+ /* do recoup */
{
struct TALER_EXCHANGEDB_Reserve pre_reserve;
struct TALER_EXCHANGEDB_Reserve post_reserve;
struct TALER_Amount delta;
- struct TALER_EXCHANGEDB_KycStatus kyc;
+ bool recoup_ok;
+ bool internal_failure;
+ struct GNUNET_TIME_Timestamp recoup_timestamp
+ = GNUNET_TIME_timestamp_get ();
pre_reserve.pub = reserve_pub;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->reserves_get (plugin->cls,
- &pre_reserve,
- &kyc));
+ &pre_reserve));
+ FAILIF (! TALER_amount_is_zero (&pre_reserve.balance));
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_recoup_request (plugin->cls,
- &reserve_pub,
- &deposit.coin,
- &coin_sig,
- &coin_blind,
- &value,
- &cbc.h_coin_envelope,
- deadline));
+ plugin->do_recoup (plugin->cls,
+ &reserve_pub,
+ reserve_out_serial_id,
+ &coin_blind,
+ &deposit.coin.coin_pub,
+ known_coin_id,
+ &coin_sig,
+ &recoup_timestamp,
+ &recoup_ok,
+ &internal_failure));
+ FAILIF (internal_failure);
+ FAILIF (! recoup_ok);
post_reserve.pub = reserve_pub;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->reserves_get (plugin->cls,
- &post_reserve,
- &kyc));
+ &post_reserve));
FAILIF (0 >=
TALER_amount_subtract (&delta,
&post_reserve.balance,
@@ -1719,39 +1834,67 @@ run (void *cls)
TALER_amount_cmp (&delta,
&value));
}
+
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-3"));
+
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->select_recoup_above_serial_id (plugin->cls,
0,
&recoup_cb,
&coin_blind));
-
- GNUNET_assert (0 <=
- TALER_amount_add (&amount_with_fee,
- &value,
- &value));
+ /* Do reserve close */
+ now = GNUNET_TIME_timestamp_get ();
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":0.000010",
&fee_closing));
- now = GNUNET_TIME_absolute_get ();
- (void) GNUNET_TIME_round_abs (&now);
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->insert_reserve_closed (plugin->cls,
- &reserve_pub,
+ &reserve_pub2,
now,
sndr,
&wire_out_wtid,
&amount_with_fee,
- &fee_closing));
+ &fee_closing,
+ 0));
+ FAILIF (GNUNET_OK !=
+ check_reserve (&reserve_pub2,
+ 0,
+ 0,
+ value.currency));
+ now = GNUNET_TIME_timestamp_get ();
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_reserve_closed (plugin->cls,
+ &reserve_pub,
+ now,
+ sndr,
+ &wire_out_wtid,
+ &value,
+ &fee_closing,
+ 0));
FAILIF (GNUNET_OK !=
check_reserve (&reserve_pub,
0,
0,
value.currency));
-
result = 7;
- qs = plugin->get_reserve_history (plugin->cls,
- &reserve_pub,
- &rh);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+
+ /* check reserve history */
+ {
+ struct TALER_Amount balance;
+ uint64_t etag_out;
+
+ qs = plugin->get_reserve_history (plugin->cls,
+ &reserve_pub,
+ 0,
+ 0,
+ &etag_out,
+ &balance,
+ &rh);
+ }
FAILIF (0 > qs);
FAILIF (NULL == rh);
rh_head = rh;
@@ -1761,9 +1904,9 @@ run (void *cls)
{
case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
bt = rh_head->details.bank;
- FAILIF (0 != memcmp (&bt->reserve_pub,
- &reserve_pub,
- sizeof (reserve_pub)));
+ FAILIF (0 !=
+ GNUNET_memcmp (&bt->reserve_pub,
+ &reserve_pub));
/* this is the amount we transferred twice*/
FAILIF (1 != bt->amount.value);
FAILIF (1000 != bt->amount.fraction);
@@ -1772,31 +1915,32 @@ run (void *cls)
break;
case TALER_EXCHANGEDB_RO_WITHDRAW_COIN:
withdraw = rh_head->details.withdraw;
- FAILIF (0 != memcmp (&withdraw->reserve_pub,
- &reserve_pub,
- sizeof (reserve_pub)));
- FAILIF (0 != memcmp (&withdraw->h_coin_envelope,
- &cbc.h_coin_envelope,
- sizeof (cbc.h_coin_envelope)));
+ FAILIF (0 !=
+ GNUNET_memcmp (&withdraw->reserve_pub,
+ &reserve_pub));
+ FAILIF (0 !=
+ GNUNET_memcmp (&withdraw->h_coin_envelope,
+ &cbc.h_coin_envelope));
break;
case TALER_EXCHANGEDB_RO_RECOUP_COIN:
{
struct TALER_EXCHANGEDB_Recoup *recoup = rh_head->details.recoup;
- FAILIF (0 != memcmp (&recoup->coin_sig,
- &coin_sig,
- sizeof (coin_sig)));
- FAILIF (0 != memcmp (&recoup->coin_blind,
- &coin_blind,
- sizeof (coin_blind)));
- FAILIF (0 != memcmp (&recoup->reserve_pub,
- &reserve_pub,
- sizeof (reserve_pub)));
- FAILIF (0 != memcmp (&recoup->coin.coin_pub,
- &deposit.coin.coin_pub,
- sizeof (deposit.coin.coin_pub)));
- FAILIF (0 != TALER_amount_cmp (&recoup->value,
- &value));
+ FAILIF (0 !=
+ GNUNET_memcmp (&recoup->coin_sig,
+ &coin_sig));
+ FAILIF (0 !=
+ GNUNET_memcmp (&recoup->coin_blind,
+ &coin_blind));
+ FAILIF (0 !=
+ GNUNET_memcmp (&recoup->reserve_pub,
+ &reserve_pub));
+ FAILIF (0 !=
+ GNUNET_memcmp (&recoup->coin.coin_pub,
+ &deposit.coin.coin_pub));
+ FAILIF (0 !=
+ TALER_amount_cmp (&recoup->value,
+ &value));
}
break;
case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
@@ -1804,18 +1948,39 @@ run (void *cls)
struct TALER_EXCHANGEDB_ClosingTransfer *closing
= rh_head->details.closing;
- FAILIF (0 != memcmp (&closing->reserve_pub,
- &reserve_pub,
- sizeof (reserve_pub)));
+ FAILIF (0 !=
+ GNUNET_memcmp (&closing->reserve_pub,
+ &reserve_pub));
FAILIF (0 != TALER_amount_cmp (&closing->amount,
&amount_with_fee));
FAILIF (0 != TALER_amount_cmp (&closing->closing_fee,
&fee_closing));
}
break;
+ case TALER_EXCHANGEDB_RO_PURSE_MERGE:
+ {
+ /* FIXME: not yet tested */
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
+ {
+ /* FIXME: not yet tested */
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
+ {
+ /* FIXME: not yet tested */
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
+ {
+ /* FIXME: not yet tested */
+ break;
+ }
}
}
- FAILIF (5 != cnt);
+ GNUNET_assert (4 == cnt);
+ FAILIF (4 != cnt);
auditor_row_cnt = 0;
FAILIF (0 >=
@@ -1830,227 +1995,6 @@ run (void *cls)
NULL));
FAILIF (3 != auditor_row_cnt);
- /* Tests for deposits */
- memset (&deposit,
- 0,
- sizeof (deposit));
- RND_BLK (&deposit.coin.coin_pub);
- GNUNET_CRYPTO_rsa_public_key_hash (dkp->pub.rsa_public_key,
- &deposit.coin.denom_pub_hash);
- deposit.coin.denom_sig = cbc.sig;
- RND_BLK (&deposit.csig);
- RND_BLK (&deposit.merchant_pub);
- RND_BLK (&deposit.h_contract_terms);
- GNUNET_assert (GNUNET_OK ==
- TALER_JSON_merchant_wire_signature_hash (wire,
- &deposit.h_wire));
- deposit.receiver_wire_account = wire;
- deposit.amount_with_fee = value;
- deposit.deposit_fee = fee_deposit;
-
- deposit.refund_deadline = deadline;
- deposit.wire_deadline = deadline;
- result = 8;
- FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
- plugin->ensure_coin_known (plugin->cls,
- &deposit.coin));
- {
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Absolute r;
- struct TALER_Amount deposit_fee;
-
- now = GNUNET_TIME_absolute_get ();
- GNUNET_TIME_round_abs (&now);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_deposit (plugin->cls,
- now,
- &deposit));
- FAILIF (1 !=
- plugin->have_deposit (plugin->cls,
- &deposit,
- GNUNET_YES,
- &deposit_fee,
- &r));
- FAILIF (now.abs_value_us != r.abs_value_us);
- }
- {
- struct GNUNET_TIME_Absolute start_range;
- struct GNUNET_TIME_Absolute end_range;
-
- start_range = GNUNET_TIME_absolute_subtract (deadline,
- GNUNET_TIME_UNIT_SECONDS);
- end_range = GNUNET_TIME_absolute_add (deadline,
- GNUNET_TIME_UNIT_SECONDS);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->select_deposits_missing_wire (plugin->cls,
- start_range,
- end_range,
- &wire_missing_cb,
- &deposit));
- FAILIF (8 != result);
- }
- auditor_row_cnt = 0;
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->select_deposits_above_serial_id (plugin->cls,
- 0,
- &audit_deposit_cb,
- NULL));
- FAILIF (1 != auditor_row_cnt);
- result = 9;
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->iterate_matching_deposits (plugin->cls,
- &deposit.h_wire,
- &deposit.merchant_pub,
- &matching_deposit_cb,
- &deposit,
- 2));
- sleep (2); /* give deposit time to be ready */
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_ready_deposit (plugin->cls,
- 0,
- INT32_MAX,
- &deposit_cb,
- &deposit));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->commit (plugin->cls));
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
- "test-2"));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->mark_deposit_tiny (plugin->cls,
- deposit_rowid));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->get_ready_deposit (plugin->cls,
- 0,
- INT32_MAX,
- &deposit_cb,
- &deposit));
- plugin->rollback (plugin->cls);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_ready_deposit (plugin->cls,
- 0,
- INT32_MAX,
- &deposit_cb,
- &deposit));
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
- "test-3"));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->test_deposit_done (plugin->cls,
- &deposit.coin.coin_pub,
- &deposit.merchant_pub,
- &deposit.h_contract_terms,
- &deposit.h_wire));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->mark_deposit_done (plugin->cls,
- deposit_rowid));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->commit (plugin->cls));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->test_deposit_done (plugin->cls,
- &deposit.coin.coin_pub,
- &deposit.merchant_pub,
- &deposit.h_contract_terms,
- &deposit.h_wire));
-
- result = 10;
- deposit2 = deposit;
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
- "test-2"));
- RND_BLK (&deposit2.merchant_pub); /* should fail if merchant is different */
- {
- struct GNUNET_TIME_Absolute r;
- struct TALER_Amount deposit_fee;
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->have_deposit (plugin->cls,
- &deposit2,
- GNUNET_YES,
- &deposit_fee,
- &r));
- deposit2.merchant_pub = deposit.merchant_pub;
- RND_BLK (&deposit2.coin.coin_pub); /* should fail if coin is different */
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->have_deposit (plugin->cls,
- &deposit2,
- GNUNET_YES,
- &deposit_fee,
- &r));
- }
- FAILIF (GNUNET_OK !=
- test_melting ());
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->commit (plugin->cls));
-
-
- /* test insert_refund! */
- refund.coin = deposit.coin;
- refund.details.merchant_pub = deposit.merchant_pub;
- RND_BLK (&refund.details.merchant_sig);
- refund.details.h_contract_terms = deposit.h_contract_terms;
- refund.details.rtransaction_id
- = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
- UINT64_MAX);
- refund.details.refund_amount = deposit.amount_with_fee;
- refund.details.refund_fee = fee_refund;
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_refund (plugin->cls,
- &refund));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->select_refunds_by_coin (plugin->cls,
- &refund.coin.coin_pub,
- &refund.details.merchant_pub,
- &refund.details.h_contract_terms,
- &check_refund_cb,
- &refund));
-
- /* test recoup / revocation */
- RND_BLK (&master_sig);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_denomination_revocation (plugin->cls,
- &dkp_pub_hash,
- &master_sig));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->commit (plugin->cls));
- plugin->preflight (plugin->cls);
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
- "test-4"));
- FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->insert_denomination_revocation (plugin->cls,
- &dkp_pub_hash,
- &master_sig));
- plugin->rollback (plugin->cls);
- plugin->preflight (plugin->cls);
- FAILIF (GNUNET_OK !=
- plugin->start (plugin->cls,
- "test-5"));
- {
- struct TALER_MasterSignatureP msig;
- uint64_t rev_rowid;
-
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->get_denomination_revocation (plugin->cls,
- &dkp_pub_hash,
- &msig,
- &rev_rowid));
- FAILIF (0 != GNUNET_memcmp (&msig,
- &master_sig));
- }
-
-
- RND_BLK (&coin_sig);
- RND_BLK (&coin_blind);
- FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- plugin->insert_recoup_request (plugin->cls,
- &reserve_pub,
- &deposit.coin,
- &coin_sig,
- &coin_blind,
- &value,
- &cbc.h_coin_envelope,
- deadline));
auditor_row_cnt = 0;
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
@@ -2058,12 +2002,21 @@ run (void *cls)
0,
&audit_refund_cb,
NULL));
-
FAILIF (1 != auditor_row_cnt);
- qs = plugin->get_coin_transactions (plugin->cls,
- &refund.coin.coin_pub,
- GNUNET_YES,
- &tl);
+ {
+ uint64_t etag = 0;
+ struct TALER_Amount balance;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ qs = plugin->get_coin_transactions (plugin->cls,
+ &refund.coin.coin_pub,
+ 0,
+ 0,
+ &etag,
+ &balance,
+ &h_denom_pub,
+ &tl);
+ }
FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs);
GNUNET_assert (NULL != tl);
matched = 0;
@@ -2078,40 +2031,39 @@ run (void *cls)
/* Note: we're not comparing the denomination keys, as there is
still the question of whether we should even bother exporting
them here. */
- FAILIF (0 != memcmp (&have->csig,
- &deposit.csig,
- sizeof (struct TALER_CoinSpendSignatureP)));
- FAILIF (0 != memcmp (&have->merchant_pub,
- &deposit.merchant_pub,
- sizeof (struct TALER_MerchantPublicKeyP)));
- FAILIF (0 != memcmp (&have->h_contract_terms,
- &deposit.h_contract_terms,
- sizeof (struct GNUNET_HashCode)));
- FAILIF (0 != memcmp (&have->h_wire,
- &deposit.h_wire,
- sizeof (struct GNUNET_HashCode)));
- /* Note: not comparing 'wire', seems truly redundant and would be tricky */
- FAILIF (have->timestamp.abs_value_us != deposit.timestamp.abs_value_us);
- FAILIF (have->refund_deadline.abs_value_us !=
- deposit.refund_deadline.abs_value_us);
- FAILIF (have->wire_deadline.abs_value_us !=
- deposit.wire_deadline.abs_value_us);
+ FAILIF (0 !=
+ GNUNET_memcmp (&have->csig,
+ &deposit.csig));
+ FAILIF (0 !=
+ GNUNET_memcmp (&have->merchant_pub,
+ &bd.merchant_pub));
+ FAILIF (0 !=
+ GNUNET_memcmp (&have->h_contract_terms,
+ &bd.h_contract_terms));
+ FAILIF (0 !=
+ GNUNET_memcmp (&have->wire_salt,
+ &bd.wire_salt));
+ FAILIF (GNUNET_TIME_timestamp_cmp (have->timestamp,
+ !=,
+ bd.wallet_timestamp));
+ FAILIF (GNUNET_TIME_timestamp_cmp (have->refund_deadline,
+ !=,
+ bd.refund_deadline));
+ FAILIF (GNUNET_TIME_timestamp_cmp (have->wire_deadline,
+ !=,
+ bd.wire_deadline));
FAILIF (0 != TALER_amount_cmp (&have->amount_with_fee,
&deposit.amount_with_fee));
- FAILIF (0 != TALER_amount_cmp (&have->deposit_fee,
- &deposit.deposit_fee));
matched |= 1;
break;
}
-#if 0
/* this coin pub was actually never melted... */
case TALER_EXCHANGEDB_TT_MELT:
- FAILIF (0 != memcmp (&melt,
- &tlp->details.melt,
- sizeof (struct TALER_EXCHANGEDB_Melt)));
+ FAILIF (0 !=
+ GNUNET_memcmp (&refresh.rc,
+ &tlp->details.melt->rc));
matched |= 2;
break;
-#endif
case TALER_EXCHANGEDB_TT_REFUND:
{
struct TALER_EXCHANGEDB_RefundListEntry *have = tlp->details.refund;
@@ -2149,21 +2101,316 @@ run (void *cls)
matched |= 8;
break;
}
+ case TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP:
+ /* TODO: check fields better... */
+ matched |= 16;
+ break;
default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected coin history transaction type: %d\n",
+ tlp->type);
FAILIF (1);
break;
}
}
- FAILIF (13 != matched);
+ FAILIF (31 != matched);
plugin->free_coin_transaction_list (plugin->cls,
tl);
+
+ /* Tests for deposits+wire */
+ TALER_denom_sig_free (&deposit.coin.denom_sig);
+ memset (&deposit,
+ 0,
+ sizeof (deposit));
+ RND_BLK (&deposit.coin.coin_pub);
+ TALER_denom_pub_hash (&dkp->pub,
+ &deposit.coin.denom_pub_hash);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_denom_sig_unblind (&deposit.coin.denom_sig,
+ &cbc.sig,
+ &bks,
+ &c_hash,
+ alg_values,
+ &dkp->pub));
+ RND_BLK (&deposit.csig);
+ RND_BLK (&bd.merchant_pub);
+ RND_BLK (&bd.h_contract_terms);
+ RND_BLK (&bd.wire_salt);
+ bd.receiver_wire_account =
+ "payto://iban/DE67830654080004822650?receiver-name=Test";
+ TALER_merchant_wire_signature_hash (
+ "payto://iban/DE67830654080004822650?receiver-name=Test",
+ &bd.wire_salt,
+ &h_wire_wt);
+ deposit.amount_with_fee = value;
+ bd.refund_deadline = deadline;
+ bd.wire_deadline = deadline;
+ result = 8;
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-3"));
+ {
+ uint64_t known_coin_id;
+ struct TALER_DenominationHashP dph;
+ struct TALER_AgeCommitmentHash agh;
+
+ FAILIF (TALER_EXCHANGEDB_CKS_ADDED !=
+ plugin->ensure_coin_known (plugin->cls,
+ &deposit.coin,
+ &known_coin_id,
+ &dph,
+ &agh));
+ }
+ {
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_TIME_Timestamp r;
+ struct TALER_Amount deposit_fee;
+ struct TALER_MerchantWireHashP h_wire;
+ bool balance_ok;
+ uint32_t bad_idx;
+ bool ctr_conflict;
+
+ now = GNUNET_TIME_timestamp_get ();
+ TALER_payto_hash (bd.receiver_wire_account,
+ &bd.wire_target_h_payto);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->do_deposit (plugin->cls,
+ &bd,
+ &now,
+ &balance_ok,
+ &bad_idx,
+ &ctr_conflict));
+ TALER_merchant_wire_signature_hash (bd.receiver_wire_account,
+ &bd.wire_salt,
+ &h_wire);
+ FAILIF (1 !=
+ plugin->have_deposit2 (plugin->cls,
+ &bd.h_contract_terms,
+ &h_wire,
+ &deposit.coin.coin_pub,
+ &bd.merchant_pub,
+ bd.refund_deadline,
+ &deposit_fee,
+ &r));
+ FAILIF (GNUNET_TIME_timestamp_cmp (now,
+ !=,
+ r));
+ }
+ {
+ result = 66;
+ FAILIF (0 >=
+ plugin->select_batch_deposits_missing_wire (plugin->cls,
+ 0,
+ &wire_missing_cb,
+ &deposit));
+ FAILIF (8 != result);
+ }
+ auditor_row_cnt = 0;
+ FAILIF (0 >=
+ plugin->select_coin_deposits_above_serial_id (plugin->cls,
+ 0,
+ &audit_deposit_cb,
+ NULL));
+ FAILIF (0 == auditor_row_cnt);
+ result = 8;
+ sleep (2); /* give deposit time to be ready */
+ {
+ struct TALER_MerchantPublicKeyP merchant_pub2;
+ char *payto_uri2;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_ready_deposit (plugin->cls,
+ 0,
+ INT32_MAX,
+ &merchant_pub2,
+ &payto_uri2));
+ FAILIF (0 != GNUNET_memcmp (&merchant_pub2,
+ &bd.merchant_pub));
+ FAILIF (0 != strcmp (payto_uri2,
+ bd.receiver_wire_account));
+ TALER_payto_hash (payto_uri2,
+ &wire_target_h_payto);
+ GNUNET_free (payto_uri2);
+ }
+
+ {
+ struct TALER_Amount total;
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ memset (&wtid,
+ 41,
+ sizeof (wtid));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->aggregate (plugin->cls,
+ &wire_target_h_payto,
+ &bd.merchant_pub,
+ &wtid,
+ &total));
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-3"));
+ {
+ struct TALER_WireTransferIdentifierRawP wtid;
+ struct TALER_Amount total;
+ struct TALER_WireTransferIdentifierRawP wtid2;
+ struct TALER_Amount total2;
+
+ memset (&wtid,
+ 42,
+ sizeof (wtid));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":42",
+ &total));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->select_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ &bd.merchant_pub,
+ "x-bank",
+ &wtid2,
+ &total2));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->create_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ "x-bank",
+ &bd.merchant_pub,
+ &wtid,
+ 0,
+ &total));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->select_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ &bd.merchant_pub,
+ "x-bank",
+ &wtid2,
+ &total2));
+ FAILIF (0 !=
+ GNUNET_memcmp (&wtid2,
+ &wtid));
+ FAILIF (0 !=
+ TALER_amount_cmp (&total2,
+ &total));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":43",
+ &total));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->update_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ &wtid,
+ 0,
+ &total));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->select_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ &bd.merchant_pub,
+ "x-bank",
+ &wtid2,
+ &total2));
+ FAILIF (0 !=
+ GNUNET_memcmp (&wtid2,
+ &wtid));
+ FAILIF (0 !=
+ TALER_amount_cmp (&total2,
+ &total));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->delete_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ &wtid));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->select_aggregation_transient (plugin->cls,
+ &wire_target_h_payto,
+ &bd.merchant_pub,
+ "x-bank",
+ &wtid2,
+ &total2));
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+
+ result = 10;
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-2"));
+ RND_BLK (&mpub2); /* should fail if merchant is different */
+ {
+ struct TALER_MerchantWireHashP h_wire;
+ struct GNUNET_TIME_Timestamp r;
+ struct TALER_Amount deposit_fee;
+
+ TALER_merchant_wire_signature_hash (bd.receiver_wire_account,
+ &bd.wire_salt,
+ &h_wire);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->have_deposit2 (plugin->cls,
+ &bd.h_contract_terms,
+ &h_wire,
+ &deposit.coin.coin_pub,
+ &mpub2,
+ bd.refund_deadline,
+ &deposit_fee,
+ &r));
+ RND_BLK (&cpub2); /* should fail if coin is different */
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->have_deposit2 (plugin->cls,
+ &bd.h_contract_terms,
+ &h_wire,
+ &cpub2,
+ &bd.merchant_pub,
+ bd.refund_deadline,
+ &deposit_fee,
+ &r));
+ }
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+
+
+ /* test revocation */
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-3b"));
+ RND_BLK (&master_sig);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->insert_denomination_revocation (plugin->cls,
+ &cbc.denom_pub_hash,
+ &master_sig));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->commit (plugin->cls));
+ plugin->preflight (plugin->cls);
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-4"));
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->insert_denomination_revocation (plugin->cls,
+ &cbc.denom_pub_hash,
+ &master_sig));
+ plugin->rollback (plugin->cls);
+ plugin->preflight (plugin->cls);
+ FAILIF (GNUNET_OK !=
+ plugin->start (plugin->cls,
+ "test-5"));
+ {
+ struct TALER_MasterSignatureP msig;
+ uint64_t rev_rowid;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ plugin->get_denomination_revocation (plugin->cls,
+ &cbc.denom_pub_hash,
+ &msig,
+ &rev_rowid));
+ FAILIF (0 != GNUNET_memcmp (&msig,
+ &master_sig));
+ }
+
+
plugin->rollback (plugin->cls);
FAILIF (GNUNET_OK !=
test_wire_prepare ());
FAILIF (GNUNET_OK !=
- test_wire_out (&deposit));
+ test_wire_out (&bd));
FAILIF (GNUNET_OK !=
test_gc ());
FAILIF (GNUNET_OK !=
@@ -2182,14 +2429,29 @@ drop:
rh = NULL;
GNUNET_break (GNUNET_OK ==
plugin->drop_tables (plugin->cls));
+cleanup:
if (NULL != dkp)
destroy_denom_key_pair (dkp);
- if (NULL != cbc.sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (cbc.sig.rsa_signature);
- if (NULL != cbc2.sig.rsa_signature)
- GNUNET_CRYPTO_rsa_signature_free (cbc2.sig.rsa_signature);
+ if (NULL != revealed_coins)
+ {
+ for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++)
+ {
+ TALER_blinded_denom_sig_free (&revealed_coins[cnt].coin_sig);
+ TALER_blinded_planchet_free (&revealed_coins[cnt].blinded_planchet);
+ }
+ GNUNET_free (revealed_coins);
+ revealed_coins = NULL;
+ }
+ GNUNET_free (new_denom_pubs);
+ for (unsigned int cnt = 0;
+ (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]);
+ cnt++)
+ destroy_denom_key_pair (new_dkp[cnt]);
+ GNUNET_free (new_dkp);
+ TALER_denom_sig_free (&deposit.coin.denom_sig);
+ TALER_blinded_denom_sig_free (&cbc.sig);
+ TALER_blinded_denom_sig_free (&cbc2.sig);
dkp = NULL;
- json_decref (wire);
TALER_EXCHANGEDB_plugin_unload (plugin);
plugin = NULL;
}
@@ -2204,6 +2466,7 @@ main (int argc,
char *testname;
struct GNUNET_CONFIGURATION_Handle *cfg;
+ (void) argc;
result = -1;
if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
{
@@ -2211,8 +2474,9 @@ main (int argc,
return -1;
}
GNUNET_log_setup (argv[0],
- "WARNING",
+ "INFO",
NULL);
+ TALER_OS_init ();
plugin_name++;
(void) GNUNET_asprintf (&testname,
"test-exchange-db-%s",
diff --git a/src/exchangedb/test_exchangedb_by_j.c b/src/exchangedb/test_exchangedb_by_j.c
new file mode 100644
index 000000000..24b24d5b0
--- /dev/null
+++ b/src/exchangedb/test_exchangedb_by_j.c
@@ -0,0 +1,232 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchangedb/test_exchangedb_by_j.c
+ * @brief test cases for DB interaction functions
+ * @author Joseph Xu
+ */
+#include "platform.h"
+#include "taler_exchangedb_lib.h"
+#include "taler_json_lib.h"
+#include "taler_exchangedb_plugin.h"
+#include "math.h"
+#define ROUNDS 10
+
+/**
+ * Global result from the testcase.
+ */
+static int result;
+
+/**
+ * Report line of error if @a cond is true, and jump to label "drop".
+ */
+#define FAILIF(cond) \
+ do { \
+ if (! (cond)) {break;} \
+ GNUNET_break (0); \
+ goto drop; \
+ } while (0)
+
+
+/**
+ * Initializes @a ptr with random data.
+ */
+#define RND_BLK(ptr) \
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
+
+/**
+ * Initializes @a ptr with zeros.
+ */
+#define ZR_BLK(ptr) \
+ memset (ptr, 0, sizeof (*ptr))
+
+
+/**
+ * Currency we use. Must match test-exchange-db-*.conf.
+ */
+#define CURRENCY "EUR"
+
+/**
+ * Database plugin under test.
+ */
+static struct TALER_EXCHANGEDB_Plugin *plugin;
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure with config
+ */
+static void
+run (void *cls)
+{
+ static const unsigned int batches[] = {1, 2, 3, 4, 8, 16 };
+ struct GNUNET_TIME_Relative times[sizeof (batches) / sizeof(*batches)];
+ unsigned long long sqrs[sizeof (batches) / sizeof(*batches)];
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ const uint32_t num_partitions = 10;
+
+ if (NULL ==
+ (plugin = TALER_EXCHANGEDB_plugin_load (cfg)))
+ {
+ GNUNET_break (0);
+ result = 77;
+ return;
+ }
+
+
+ if (GNUNET_OK !=
+ plugin->create_tables (plugin->cls,
+ true,
+ num_partitions))
+ {
+ GNUNET_break (0);
+ result = 77;
+ goto cleanup;
+ }
+
+ memset (times, 0, sizeof (times));
+ memset (sqrs, 0, sizeof (sqrs));
+ for (unsigned int r = 0; r < ROUNDS; r++)
+ {
+ for (unsigned int i = 0; i< 6; i++)
+ {
+ const char *sndr = "payto://x-taler-bank/localhost:8080/1";
+ struct TALER_Amount value;
+ unsigned int batch_size = batches[i];
+ unsigned int iterations = 16; // 1024*10;
+ struct TALER_ReservePublicKeyP reserve_pubs[iterations];
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp ts;
+ struct GNUNET_TIME_Relative duration;
+ struct TALER_EXCHANGEDB_ReserveInInfo reserves[iterations];
+ enum GNUNET_DB_QueryStatus results[iterations];
+ unsigned long long duration_sq;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (CURRENCY ":1.000010",
+ &value));
+ now = GNUNET_TIME_absolute_get ();
+ ts = GNUNET_TIME_timestamp_get ();
+ for (unsigned int r = 0; r<iterations; r++)
+ {
+ RND_BLK (&reserve_pubs[r]);
+ reserves[r].reserve_pub = &reserve_pubs[r];
+ reserves[r].balance = &value;
+ reserves[r].execution_time = ts;
+ reserves[r].sender_account_details = sndr;
+ reserves[r].exchange_account_name = "name";
+ reserves[r].wire_reference = r;
+ }
+ FAILIF (iterations !=
+ plugin->batch2_reserves_in_insert (plugin->cls,
+ reserves,
+ iterations,
+ batch_size,
+ results));
+ duration = GNUNET_TIME_absolute_get_duration (now);
+ times[i] = GNUNET_TIME_relative_add (times[i],
+ duration);
+ duration_sq = duration.rel_value_us * duration.rel_value_us;
+ GNUNET_assert (duration_sq / duration.rel_value_us ==
+ duration.rel_value_us);
+ GNUNET_assert (sqrs[i] + duration_sq >= sqrs[i]);
+ sqrs[i] += duration_sq;
+ fprintf (stdout,
+ "for a batchsize equal to %d it took %s\n",
+ batch_size,
+ GNUNET_STRINGS_relative_time_to_string (duration,
+ GNUNET_NO) );
+
+ system ("./test.sh"); // DELETE AFTER TIMER
+ }
+ }
+ for (unsigned int i = 0; i< 6; i++)
+ {
+ struct GNUNET_TIME_Relative avg;
+ double avg_dbl;
+ double variance;
+
+ avg = GNUNET_TIME_relative_divide (times[i],
+ ROUNDS);
+ avg_dbl = avg.rel_value_us;
+ variance = sqrs[i] - (avg_dbl * avg_dbl * ROUNDS);
+ fprintf (stdout,
+ "Batch[%2u]: %8llu ± %6.0f\n",
+ batches[i],
+ (unsigned long long) avg.rel_value_us,
+ sqrt (variance / (ROUNDS - 1)));
+ }
+
+ result = 0;
+drop:
+ GNUNET_break (GNUNET_OK ==
+ plugin->drop_tables (plugin->cls));
+cleanup:
+ TALER_EXCHANGEDB_plugin_unload (plugin);
+ plugin = NULL;
+}
+
+
+int
+main (int argc,
+ char *const argv[])
+{
+ const char *plugin_name;
+ char *config_filename;
+ char *testname;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
+ (void) argc;
+ result = -1;
+ if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
+ {
+ GNUNET_break (0);
+ return -1;
+ }
+
+ GNUNET_log_setup (argv[0],
+ "WARNING",
+ NULL);
+ plugin_name++;
+ (void) GNUNET_asprintf (&testname,
+ "test-exchange-db-%s",
+ plugin_name);
+ (void) GNUNET_asprintf (&config_filename,
+ "%s.conf",
+ testname);
+ fprintf (stdout,
+ "Using config: %s\n",
+ config_filename);
+ cfg = GNUNET_CONFIGURATION_create ();
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_parse (cfg,
+ config_filename))
+ {
+ GNUNET_break (0);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return 2;
+ }
+ GNUNET_SCHEDULER_run (&run,
+ cfg);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ GNUNET_free (config_filename);
+ GNUNET_free (testname);
+ return result;
+}
+
+
+/* end of test_exchangedb_by_j.c */
diff --git a/src/exchangedb/test_idempotency.sh b/src/exchangedb/test_idempotency.sh
new file mode 100755
index 000000000..7314b8c3f
--- /dev/null
+++ b/src/exchangedb/test_idempotency.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+# This file is in the public domain.
+set -eu
+psql talercheck < /dev/null || exit 77
+echo "Initializing DB"
+taler-exchange-dbinit -r -c test-exchange-db-postgres.conf
+echo "Re-initializing DB"
+taler-exchange-dbinit -c test-exchange-db-postgres.conf
+echo "Re-loading procedures"
+psql talercheck < procedures.sql
+echo "Test PASSED"
+exit 0
diff --git a/src/exchangedb/exchange-0000.sql b/src/exchangedb/versioning.sql
index 116f409b7..444cf95ed 100644
--- a/src/exchangedb/exchange-0000.sql
+++ b/src/exchangedb/versioning.sql
@@ -146,12 +146,13 @@
BEGIN;
+
-- This file adds versioning support to database it will be loaded to.
-- It requires that PL/pgSQL is already loaded - will raise exception otherwise.
-- All versioning "stuff" (tables, functions) is in "_v" schema.
-- All functions are defined as 'RETURNS SETOF INT4' to be able to make them to RETURN literally nothing (0 rows).
--- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling.
+-- >> RETURNS VOID<< IS similar, but it still outputs "empty line" in psql when calling
CREATE SCHEMA IF NOT EXISTS _v;
COMMENT ON SCHEMA _v IS 'Schema for versioning data and functionality.';