From 9cce35d27027e1e72e44ac8965ae687d75f1c93d Mon Sep 17 00:00:00 2001 From: Joseph Date: Wed, 29 Mar 2023 11:18:20 -0400 Subject: [PATCH] New sql code for batch ensure coin known --- contrib/gana | 2 +- src/exchangedb/pg_batch_ensure_coin_known.c | 483 ++++++++++++++++++++ src/exchangedb/pg_batch_ensure_coin_known.h | 35 ++ src/exchangedb/plugin_exchangedb_postgres.c | 5 +- src/include/taler_exchangedb_plugin.h | 36 +- 5 files changed, 558 insertions(+), 3 deletions(-) create mode 100644 src/exchangedb/pg_batch_ensure_coin_known.c create mode 100644 src/exchangedb/pg_batch_ensure_coin_known.h diff --git a/contrib/gana b/contrib/gana index 3a616a04f..59de2acb7 160000 --- a/contrib/gana +++ b/contrib/gana @@ -1 +1 @@ -Subproject commit 3a616a04f1cd946bf0641b54cd71f1b858174f74 +Subproject commit 59de2acb7c716c816ed15786b5369e56c325770c 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..d246e6228 --- /dev/null +++ b/src/exchangedb/pg_batch_ensure_coin_known.c @@ -0,0 +1,483 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + */ +/** + * @file exchangedb/pg_batch_ensure_coin_known.c + * @brief Implementation of the batch_ensure_coin_known 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_batch_ensure_coin_known.h" +#include "pg_helper.h" + + + + + + +static enum TALER_EXCHANGEDB_CoinKnownStatus +insert1 (struct PosgresClosure *pg, + const struct TALER_CoinPublicInfo *coin[1], + const 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 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) && + (0 != GNUNET_memcmp (h_age_commitment, + &coin->h_age_commitment)) ) + { + GNUNET_break (GNUNET_is_zero (h_age_commitment)); + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_AGE_CONFLICT; + } + return TALER_EXCHANGEDB_CKS_PRESENT; +} + +static enum TALER_EXCHANGEDB_CoinKnownStatus +insert2 (struct PosgresClosure *pg, + const struct TALER_CoinPublicInfo *coin[2], + const struct TALER_EXCHANGEDB_CoinInfo *result[2]) +{ + enum GNUNET_DB_QueryStatus qs; + bool is_denom_pub_hash_null = false; + bool is_age_hash_null = false; + bool is_denom_pub_hash_null2 = false; + bool is_age_hash_null2 = 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), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash", + result[0].h_age_commitment[0]), + &is_age_hash_null), + 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_null2), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash2", + &result[1].h_age_commitment), + &is_age_hash_null2), + 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 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[0].hash, + &coin[0].denom_pub_hash.hash)) ) + { + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT; + } + + if ( (! is_age_hash_null) && + (0 != GNUNET_memcmp (h_age_commitment[0], + &coin[0].h_age_commitment)) ) + { + GNUNET_break (GNUNET_is_zero (h_age_commitment[0])); + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_AGE_CONFLICT; + } + if ( (! is_denom_pub_hash_null2) && + (0 != GNUNET_memcmp (&denom_hash[1].hash, + &coin[1].denom_pub_hash.hash)) ) + { + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT; + } + + if ( (! is_age_hash_null) && + (0 != GNUNET_memcmp (h_age_commitment[1], + &coin[1].h_age_commitment)) ) + { + GNUNET_break (GNUNET_is_zero (h_age_commitment[1])); + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_AGE_CONFLICT; + } + return TALER_EXCHANGEDB_CKS_PRESENT; +} + +static enum TALER_EXCHANGEDB_CoinKnownStatus +insert4 (struct PosgresClosure *pg, + const struct TALER_CoinPublicInfo *coin[4], + const struct TALER_EXCHANGEDB_CoinInfo *result[4]) +{ + enum GNUNET_DB_QueryStatus qs; + bool is_denom_pub_hash_null = false; + bool is_age_hash_null = false; + bool is_denom_pub_hash_null2 = false; + bool is_age_hash_null2 = false; + bool is_denom_pub_hash_null3 = false; + bool is_age_hash_null3 = false; + bool is_denom_pub_hash_null4 = false; + bool is_age_hash_null4 = 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), + 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_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_null2), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash2", + result[1].h_age_commitment), + &is_age_hash_null2), + 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_null3), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash3", + result[2].h_age_commitment), + &is_age_hash_null3), + 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_null4), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("age_commitment_hash4", + result[3].h_age_commitment), + &is_age_hash_null4), + 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 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 (result[0].denom_hash.hash, + &coin[0].denom_pub_hash.hash)) ) + { + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT; + } + if ( (! is_age_hash_null) && + (0 != GNUNET_memcmp (h_age_commitment[0], + &coin[0].h_age_commitment)) ) + { + GNUNET_break (GNUNET_is_zero (h_age_commitment[0])); + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_AGE_CONFLICT; + } + + if ( (! is_denom_pub_hash_null2) && + (0 != GNUNET_memcmp (&denom_hash[1].hash, + &coin[1].denom_pub_hash.hash)) ) + { + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT; + } + if ( (! is_age_hash_null2) && + (0 != GNUNET_memcmp (h_age_commitment[1], + &coin[1].h_age_commitment)) ) + { + GNUNET_break (GNUNET_is_zero (h_age_commitment[1])); + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_AGE_CONFLICT; + } + + if ( (! is_denom_pub_hash_null3) && + (0 != GNUNET_memcmp (&denom_hash[2].hash, + &coin[2].denom_pub_hash.hash)) ) + { + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT; + } + if ( (! is_age_hash_null3) && + (0 != GNUNET_memcmp (h_age_commitment[2], + &coin[2].h_age_commitment)) ) + { + GNUNET_break (GNUNET_is_zero (h_age_commitment[2])); + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_AGE_CONFLICT; + } + + if ( (! is_denom_pub_hash_null4) && + (0 != GNUNET_memcmp (&denom_hash[3].hash, + &coin[3].denom_pub_hash.hash)) ) + { + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_DENOM_CONFLICT; + } + if ( (! is_age_hash_null4) && + (0 != GNUNET_memcmp (h_age_commitment[3], + &coin[3].h_age_commitment)) ) + { + GNUNET_break (GNUNET_is_zero (h_age_commitment[3])); + GNUNET_break_op (0); + return TALER_EXCHANGEDB_CKS_AGE_CONFLICT; + } + + return TALER_EXCHANGEDB_CKS_PRESENT; +} + + + +enum TALER_EXCHANGEDB_CoinKnownStatus +TEH_PG_batch_ensure_coin_known (void *cls, + const struct + TALER_CoinPublicInfo *coin, + const struct + TALER_EXCHANGEDB_CoinInfo *result, + unsigned int coin_length, + unsigned int batch_size) +{ + struct PostgresClosure *pg = cls; + enum TALER_EXCHANGEDB_CoinKnownStatus qs1; + enum TALER_EXCHANGEDB_CoinKnownStatus qs2; + enum TALER_EXCHANGEDB_CoinKnownStatus qs4; + unsigned int i = 0; + + while (i < coin_length) + { + unsigned int bs = GNUNET_MIN (batch_size, + coin_length - i); + bs = 1; + if (bs >= 4) + { + qs4 = insert4 (pg, + &coin[i], + &result[i] + ); + i += 4; + continue; + } + switch (bs) + { + case 3: + case 2: + qs2 = insert2 (pg, + &coin[i], + &result[i] + ); + i += 2; + break; + case 1: + qs1 = insert1 (pg, + &coin[i], + &result[i] + ); + i += 1; + break; + case 0: + GNUNET_assert (0); + break; + } + } /* end while */ + return TALER_EXCHANGEDB_CKS_PRESENT; +} 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..ce2b2f4ce --- /dev/null +++ b/src/exchangedb/pg_batch_ensure_coin_known.h @@ -0,0 +1,35 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see + */ +/** + * @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" +enum TALER_EXCHANGEDB_CoinKnownStatus +TEH_PG_batch_ensure_coin_known (void *cls, + const struct + TALER_CoinPublicInfo *coin, + const struct + TALER_EXCHANGEDB_CoinInfo *result, + unsigned int coin_length, + unsigned int batch_size); +#endif diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 2f3318184..7e3aa2b1e 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -216,7 +216,7 @@ #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 @@ -774,6 +774,9 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) plugin->insert_aml_decision = &TEH_PG_insert_aml_decision; + plugin->batch_ensure_coin_known + = &TEH_PG_batch_ensure_coin_known; + return plugin; } diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index 962bccaa2..cee8fb883 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -29,6 +29,19 @@ #include "taler_extensions_policy.h" + + +struct TALER_EXCHANGEDB_CoinInfo +{ + uint64_t *known_coin_id; + struct TALER_DenominationHashP *denom_hash; + struct TALER_AgeCommitmentHash *h_age_commitment; + bool *existed; +}; + + + + /** * Information about a denomination key. */ @@ -887,6 +900,21 @@ struct TALER_EXCHANGEDB_DenominationKeyMetaData }; + + + + + + + + + + + + + + + /** * Signature of a function called with information about the exchange's * denomination keys. @@ -4027,7 +4055,13 @@ struct TALER_EXCHANGEDB_Plugin struct TALER_DenominationHashP *denom_pub_hash, struct TALER_AgeCommitmentHash *age_hash); - + enum TALER_EXCHANGEDB_CoinKnownStatus + (*batch_ensure_coin_known)(void *cls, + const struct TALER_CoinPublicInfo *coin, + const struct + TALER_EXCHANGEDB_CoinInfo *result, + unsigned int coin_length, + unsigned int batch_size); /** * Retrieve information about the given @a coin from the database. *