What changed, and why it matters
This commit fixes a build problem on 32-bit ARM computers by removing the use of a 128-bit integer type that those systems do not support. It changes one currency-conversion calculation in the bookkeeper plugin to use ordinary 64-bit math with a floating-point fallback, and adds a test file to check the results. There is no direct security vulnerability being patched; it is primarily a portability/build fix.
Treat as a routine build/portability fix. Review the double fallback path if exact accounting precision is required for extremely large msat*rate products, but no urgent security action is indicated.
Security signals we found
Use of non-portable 128-bit integer type removed
Currency conversion now uses checked 64-bit multiplication with floating-point fallback on overflow
New unit tests cover normal, fractional, small-amount, and overflow conversion cases
Evidence from the diff
The patch replaces an unsigned __int128 multiplication in plugins/bkpr/bookkeeper.c’s currencyrate_str() with amount_msat_mul() (a 64-bit-safe helper) and a double fallback when overflow occurs. The change is framed by the author as restoring builds on 32-bit ARM, where __int128 is unavailable. A new unit-test file exercises the conversion logic including overflow cases. The diff does not show any memory-safety bug, remote attack surface, or authentication bypass; the only runtime risk is a minor precision change when the double path is taken for very large values.
Changed components
plugins/bkpr/bookkeeper.cplugins/bkpr/test/run-currencyrate_str.cInspect captured patch +556 / −3
diff --git a/plugins/bkpr/bookkeeper.c b/plugins/bkpr/bookkeeper.c
index 41c6e34f..8f61df17 100644
--- a/plugins/bkpr/bookkeeper.c
+++ b/plugins/bkpr/bookkeeper.c
@@ -83,9 +83,14 @@ const char *currencyrate_str(const tal_t *ctx,
mul = ratefactor(bkpr->currency);
if (msat) {
- unsigned __int128 v;
- v = (unsigned __int128)msat->millisatoshis * crate->raw_rate /* Raw: 128-bit math */;
- raw_rate = v / MSAT_PER_BTC;
+ struct amount_msat res;
+
+ /* If multiply overflows, use floating point */
+ if (amount_msat_mul(&res, *msat, crate->raw_rate)) {
+ raw_rate = res.millisatoshis / MSAT_PER_BTC; /* Raw: divide */
+ } else {
+ raw_rate = (double)msat->millisatoshis * crate->raw_rate / MSAT_PER_BTC; /* Raw: double */
+ }
} else {
raw_rate = crate->raw_rate;
}
diff --git a/plugins/bkpr/test/run-currencyrate_str.c b/plugins/bkpr/test/run-currencyrate_str.c
new file mode 100644
index 00000000..b19a5ba3
--- /dev/null
+++ b/plugins/bkpr/test/run-currencyrate_str.c
@@ -0,0 +1,548 @@
+#include "config.h"
+#define main bookkeeper_main
+int bookkeeper_main(int argc, char *argv[]);
+#include "../bookkeeper.c"
+#undef main
+
+#include <assert.h>
+#include <ccan/str/str.h>
+#include <ccan/tal/str/str.h>
+#include <common/amount.h>
+#include <common/json_parse.h>
+#include <common/setup.h>
+
+/* AUTOGENERATED MOCKS START */
+/* Generated stub for account_get_chain_events */
+struct chain_event **account_get_chain_events(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED,
+ struct account *acct UNNEEDED)
+{ fprintf(stderr, "account_get_chain_events called!\n"); abort(); }
+/* Generated stub for account_get_chain_fees */
+struct onchain_fee **account_get_chain_fees(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ const char *acct_name UNNEEDED)
+{ fprintf(stderr, "account_get_chain_fees called!\n"); abort(); }
+/* Generated stub for account_get_channel_events */
+struct channel_event **account_get_channel_events(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED,
+ struct account *acct UNNEEDED)
+{ fprintf(stderr, "account_get_channel_events called!\n"); abort(); }
+/* Generated stub for account_get_credit_debit */
+bool account_get_credit_debit(const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED,
+ const char *acct_name UNNEEDED,
+ struct amount_msat *credit UNNEEDED,
+ struct amount_msat *debit UNNEEDED)
+{ fprintf(stderr, "account_get_credit_debit called!\n"); abort(); }
+/* Generated stub for account_onchain_closeheight */
+u64 account_onchain_closeheight(const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED,
+ const struct account *acct UNNEEDED)
+{ fprintf(stderr, "account_onchain_closeheight called!\n"); abort(); }
+/* Generated stub for account_update_closeheight */
+void account_update_closeheight(struct command *cmd UNNEEDED,
+ struct account *acct UNNEEDED,
+ u64 close_height UNNEEDED)
+{ fprintf(stderr, "account_update_closeheight called!\n"); abort(); }
+/* Generated stub for add_payment_hash_description */
+void add_payment_hash_description(struct command *cmd UNNEEDED,
+ struct bkpr *bkpr UNNEEDED,
+ const struct sha256 *payment_hash UNNEEDED,
+ const char *desc UNNEEDED)
+{ fprintf(stderr, "add_payment_hash_description called!\n"); abort(); }
+/* Generated stub for add_utxo_description */
+void add_utxo_description(struct command *cmd UNNEEDED,
+ struct bkpr *bkpr UNNEEDED,
+ const struct bitcoin_outpoint *outpoint UNNEEDED,
+ const char *desc UNNEEDED)
+{ fprintf(stderr, "add_utxo_description called!\n"); abort(); }
+/* Generated stub for aux_command */
+struct command *aux_command(const struct command *cmd)
+
+{ fprintf(stderr, "aux_command called!\n"); abort(); }
+/* Generated stub for channel_apy_sum */
+ bool channel_apy_sum(struct channel_apy *sum_apy UNNEEDED,
+ const struct channel_apy *entry UNNEEDED)
+{ fprintf(stderr, "channel_apy_sum called!\n"); abort(); }
+/* Generated stub for command_check_done */
+struct command_result *command_check_done(struct command *cmd)
+
+{ fprintf(stderr, "command_check_done called!\n"); abort(); }
+/* Generated stub for command_check_only */
+bool command_check_only(const struct command *cmd UNNEEDED)
+{ fprintf(stderr, "command_check_only called!\n"); abort(); }
+/* Generated stub for command_deprecated_in_ok */
+bool command_deprecated_in_ok(struct command *cmd UNNEEDED,
+ const char *param UNNEEDED,
+ const char *depr_start UNNEEDED,
+ const char *depr_end UNNEEDED)
+{ fprintf(stderr, "command_deprecated_in_ok called!\n"); abort(); }
+/* Generated stub for command_dev_apis */
+bool command_dev_apis(const struct command *cmd UNNEEDED)
+{ fprintf(stderr, "command_dev_apis called!\n"); abort(); }
+/* Generated stub for command_fail */
+struct command_result *command_fail(struct command *cmd UNNEEDED, enum jsonrpc_errcode code UNNEEDED,
+ const char *fmt UNNEEDED, ...)
+
+{ fprintf(stderr, "command_fail called!\n"); abort(); }
+/* Generated stub for command_filter_ptr */
+struct json_filter **command_filter_ptr(struct command *cmd UNNEEDED)
+{ fprintf(stderr, "command_filter_ptr called!\n"); abort(); }
+/* Generated stub for command_finished */
+struct command_result *command_finished(struct command *cmd UNNEEDED, struct json_stream *response)
+
+{ fprintf(stderr, "command_finished called!\n"); abort(); }
+/* Generated stub for command_log */
+void command_log(struct command *cmd UNNEEDED, enum log_level level UNNEEDED,
+ const char *fmt UNNEEDED, ...)
+
+{ fprintf(stderr, "command_log called!\n"); abort(); }
+/* Generated stub for command_param_failed */
+struct command_result *command_param_failed(void)
+
+{ fprintf(stderr, "command_param_failed called!\n"); abort(); }
+/* Generated stub for command_set_usage */
+void command_set_usage(struct command *cmd UNNEEDED, const char *usage UNNEEDED)
+{ fprintf(stderr, "command_set_usage called!\n"); abort(); }
+/* Generated stub for command_still_pending */
+struct command_result *command_still_pending(struct command *cmd)
+
+{ fprintf(stderr, "command_still_pending called!\n"); abort(); }
+/* Generated stub for command_usage_only */
+bool command_usage_only(const struct command *cmd UNNEEDED)
+{ fprintf(stderr, "command_usage_only called!\n"); abort(); }
+/* Generated stub for compute_channel_apys */
+struct channel_apy **compute_channel_apys(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED,
+ u64 start_time UNNEEDED,
+ u64 end_time UNNEEDED,
+ u32 current_blockheight UNNEEDED)
+{ fprintf(stderr, "compute_channel_apys called!\n"); abort(); }
+/* Generated stub for csv_filename */
+const char *csv_filename(const tal_t *ctx UNNEEDED, const struct csv_fmt *fmt UNNEEDED)
+{ fprintf(stderr, "csv_filename called!\n"); abort(); }
+/* Generated stub for csv_list_fmts */
+const char *csv_list_fmts(const tal_t *ctx UNNEEDED)
+{ fprintf(stderr, "csv_list_fmts called!\n"); abort(); }
+/* Generated stub for csv_match_token */
+const struct csv_fmt *csv_match_token(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED)
+{ fprintf(stderr, "csv_match_token called!\n"); abort(); }
+/* Generated stub for csv_print_income_events */
+char *csv_print_income_events(const tal_t *ctx UNNEEDED,
+ const struct csv_fmt *csvfmt UNNEEDED,
+ const char *filename UNNEEDED,
+ struct income_event **evs UNNEEDED)
+{ fprintf(stderr, "csv_print_income_events called!\n"); abort(); }
+/* Generated stub for do_bkpr_report */
+struct command_result *do_bkpr_report(struct command *cmd UNNEEDED,
+ struct report_info *info UNNEEDED)
+{ fprintf(stderr, "do_bkpr_report called!\n"); abort(); }
+/* Generated stub for find_account */
+struct account *find_account(const struct bkpr *bkpr UNNEEDED,
+ const char *name UNNEEDED)
+{ fprintf(stderr, "find_account called!\n"); abort(); }
+/* Generated stub for find_account_onchain_fees */
+struct fee_sum **find_account_onchain_fees(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ const struct account *acct UNNEEDED)
+{ fprintf(stderr, "find_account_onchain_fees called!\n"); abort(); }
+/* Generated stub for find_chain_events_bytxid */
+struct chain_event **find_chain_events_bytxid(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED,
+ const struct bitcoin_txid *txid UNNEEDED)
+{ fprintf(stderr, "find_chain_events_bytxid called!\n"); abort(); }
+/* Generated stub for find_close_account_name */
+const char *find_close_account_name(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED,
+ const struct bitcoin_txid *txid UNNEEDED)
+{ fprintf(stderr, "find_close_account_name called!\n"); abort(); }
+/* Generated stub for find_or_create_account */
+struct account *find_or_create_account(struct command *cmd UNNEEDED,
+ struct bkpr *bkpr UNNEEDED,
+ const char *name UNNEEDED)
+{ fprintf(stderr, "find_or_create_account called!\n"); abort(); }
+/* Generated stub for find_txo_chain */
+bool find_txo_chain(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED,
+ const struct account *acct UNNEEDED,
+ struct txo_set ***sets UNNEEDED)
+{ fprintf(stderr, "find_txo_chain called!\n"); abort(); }
+/* Generated stub for forward_error */
+struct command_result *forward_error(struct command *cmd UNNEEDED,
+ const char *method UNNEEDED,
+ const char *buf UNNEEDED,
+ const jsmntok_t *error UNNEEDED,
+ void *arg)
+
+{ fprintf(stderr, "forward_error called!\n"); abort(); }
+/* Generated stub for get_chain_events_by_id */
+struct chain_event **get_chain_events_by_id(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED,
+ const struct sha256 *id UNNEEDED)
+{ fprintf(stderr, "get_chain_events_by_id called!\n"); abort(); }
+/* Generated stub for get_chain_events_by_outpoint */
+struct chain_event **get_chain_events_by_outpoint(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED,
+ const struct bitcoin_outpoint *outpoint UNNEEDED)
+{ fprintf(stderr, "get_chain_events_by_outpoint called!\n"); abort(); }
+/* Generated stub for get_chain_fees_by_txid */
+struct onchain_fee **get_chain_fees_by_txid(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ const struct bitcoin_txid *txid UNNEEDED)
+{ fprintf(stderr, "get_chain_fees_by_txid called!\n"); abort(); }
+/* Generated stub for get_channel_events_by_id */
+struct channel_event **get_channel_events_by_id(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED,
+ const struct sha256 *id UNNEEDED)
+{ fprintf(stderr, "get_channel_events_by_id called!\n"); abort(); }
+/* Generated stub for init_accounts */
+struct accounts *init_accounts(const tal_t *ctx UNNEEDED, struct command *init_cmd UNNEEDED)
+{ fprintf(stderr, "init_accounts called!\n"); abort(); }
+/* Generated stub for init_blockheights */
+struct blockheights *init_blockheights(const tal_t *ctx UNNEEDED,
+ struct command *init_cmd UNNEEDED)
+{ fprintf(stderr, "init_blockheights called!\n"); abort(); }
+/* Generated stub for init_descriptions */
+struct descriptions *init_descriptions(const tal_t *ctx UNNEEDED,
+ struct command *init_cmd UNNEEDED)
+{ fprintf(stderr, "init_descriptions called!\n"); abort(); }
+/* Generated stub for init_onchain_fees */
+struct onchain_fees *init_onchain_fees(const tal_t *ctx UNNEEDED,
+ struct command *init_cmd UNNEEDED)
+{ fprintf(stderr, "init_onchain_fees called!\n"); abort(); }
+/* Generated stub for init_rebalances */
+struct rebalances *init_rebalances(const tal_t *ctx UNNEEDED,
+ struct command *init_cmd UNNEEDED)
+{ fprintf(stderr, "init_rebalances called!\n"); abort(); }
+/* Generated stub for json_add_chain_event */
+void json_add_chain_event(struct json_stream *out UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct chain_event *ev UNNEEDED)
+{ fprintf(stderr, "json_add_chain_event called!\n"); abort(); }
+/* Generated stub for json_add_channel_apy */
+void json_add_channel_apy(struct json_stream *res UNNEEDED,
+ const struct channel_apy *apy UNNEEDED)
+{ fprintf(stderr, "json_add_channel_apy called!\n"); abort(); }
+/* Generated stub for json_add_channel_event */
+void json_add_channel_event(struct json_stream *out UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct channel_event *ev UNNEEDED)
+{ fprintf(stderr, "json_add_channel_event called!\n"); abort(); }
+/* Generated stub for json_add_income_event */
+void json_add_income_event(struct json_stream *str UNNEEDED, struct income_event *ev UNNEEDED)
+{ fprintf(stderr, "json_add_income_event called!\n"); abort(); }
+/* Generated stub for json_add_onchain_fee */
+void json_add_onchain_fee(struct json_stream *out UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ const struct onchain_fee *fee UNNEEDED)
+{ fprintf(stderr, "json_add_onchain_fee called!\n"); abort(); }
+/* Generated stub for jsonrpc_request_start_ */
+struct out_req *jsonrpc_request_start_(struct command *cmd UNNEEDED,
+ const char *method UNNEEDED,
+ const char *id_prefix UNNEEDED,
+ const char *filter UNNEEDED,
+ struct command_result *(*cb)(struct command *command UNNEEDED,
+ const char *methodname UNNEEDED,
+ const char *buf UNNEEDED,
+ const jsmntok_t *result UNNEEDED,
+ void *arg) UNNEEDED,
+ struct command_result *(*errcb)(struct command *command UNNEEDED,
+ const char *methodname UNNEEDED,
+ const char *buf UNNEEDED,
+ const jsmntok_t *result UNNEEDED,
+ void *arg) UNNEEDED,
+ void *arg UNNEEDED)
+{ fprintf(stderr, "jsonrpc_request_start_ called!\n"); abort(); }
+/* Generated stub for jsonrpc_request_sync */
+const jsmntok_t *jsonrpc_request_sync(const tal_t *ctx UNNEEDED,
+ struct command *cmd UNNEEDED,
+ const char *method UNNEEDED,
+ const struct json_out *params TAKES UNNEEDED,
+ const char **resp UNNEEDED)
+{ fprintf(stderr, "jsonrpc_request_sync called!\n"); abort(); }
+/* Generated stub for jsonrpc_set_datastore_ */
+struct command_result *jsonrpc_set_datastore_(struct command *cmd UNNEEDED,
+ const char **keys UNNEEDED,
+ const void *value UNNEEDED,
+ int len_or_str UNNEEDED,
+ const char *mode UNNEEDED,
+ struct command_result *(*cb)(struct command *command UNNEEDED,
+ const char *method UNNEEDED,
+ const char *buf UNNEEDED,
+ const jsmntok_t *result UNNEEDED,
+ void *arg) UNNEEDED,
+ struct command_result *(*errcb)(struct command *command UNNEEDED,
+ const char *method UNNEEDED,
+ const char *buf UNNEEDED,
+ const jsmntok_t *result UNNEEDED,
+ void *arg) UNNEEDED,
+ void *arg)
+
+{ fprintf(stderr, "jsonrpc_set_datastore_ called!\n"); abort(); }
+/* Generated stub for jsonrpc_stream_success */
+struct json_stream *jsonrpc_stream_success(struct command *cmd)
+
+{ fprintf(stderr, "jsonrpc_stream_success called!\n"); abort(); }
+/* Generated stub for list_accounts */
+struct account **list_accounts(const tal_t *ctx UNNEEDED, const struct bkpr *bkpr UNNEEDED)
+{ fprintf(stderr, "list_accounts called!\n"); abort(); }
+/* Generated stub for list_chain_events */
+struct chain_event **list_chain_events(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED)
+{ fprintf(stderr, "list_chain_events called!\n"); abort(); }
+/* Generated stub for list_chain_fees */
+struct onchain_fee **list_chain_fees(const tal_t *ctx UNNEEDED, const struct bkpr *bkpr UNNEEDED)
+{ fprintf(stderr, "list_chain_fees called!\n"); abort(); }
+/* Generated stub for list_channel_events */
+struct channel_event **list_channel_events(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED)
+{ fprintf(stderr, "list_channel_events called!\n"); abort(); }
+/* Generated stub for list_income_events */
+struct income_event **list_income_events(const tal_t *ctx UNNEEDED,
+ const struct bkpr *bkpr UNNEEDED,
+ struct command *cmd UNNEEDED,
+ u64 start_time UNNEEDED,
+ u64 end_time UNNEEDED,
+ bool consolidate_fees UNNEEDED)
+{ fprintf(stderr, "list_income_events called!\n"); abort(); }
+/* Generated stub for maybe_closeout_external_deposits */
+void maybe_closeout_external_deposits(struct command *cmd UNNEEDED,
+ struct bkpr *bkpr UNNEEDED,
+ const struct bitcoin_txid *txid UNNEEDED,
+ u32 blockheight UNNEEDED)
+{ fprintf(stderr, "maybe_closeout_external_deposits called!\n"); abort(); }
+/* Generated stub for maybe_record_rebalance */
+void maybe_record_rebalance(struct command *cmd UNNEEDED,
+ struct bkpr *bkpr UNNEEDED,
+ const struct channel_event *out UNNEEDED)
+{ fprintf(stderr, "maybe_record_rebalance called!\n"); abort(); }
+/* Generated stub for maybe_update_account */
+void maybe_update_account(struct command *cmd UNNEEDED,
+ struct account *acct UNNEEDED,
+ struct chain_event *e UNNEEDED,
+ const enum mvt_tag *tags UNNEEDED,
+ u32 closed_count UNNEEDED,
+ struct node_id *peer_id UNNEEDED)
+{ fprintf(stderr, "maybe_update_account called!\n"); abort(); }
+/* Generated stub for maybe_update_onchain_fees */
+char *maybe_update_onchain_fees(const tal_t *ctx UNNEEDED,
+ struct command *cmd UNNEEDED,
+ struct bkpr *bkpr UNNEEDED,
+ struct bitcoin_txid *txid UNNEEDED)
+{ fprintf(stderr, "maybe_update_onchain_fees called!\n"); abort(); }
+/* Generated stub for new_channel_apy */
+struct channel_apy *new_channel_apy(const tal_t *ctx UNNEEDED)
+{ fprintf(stderr, "new_channel_apy called!\n"); abort(); }
+/* Generated stub for notification_handled */
+struct command_result *notification_handled(struct command *cmd)
+
+{ fprintf(stderr, "notification_handled called!\n"); abort(); }
+/* Generated stub for param_escape_format */
+struct command_result *param_escape_format(struct command *cmd UNNEEDED, const char *name UNNEEDED,
+ const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ enum escape_format **escape UNNEEDED)
+{ fprintf(stderr, "param_escape_format called!\n"); abort(); }
+/* Generated stub for param_report_format */
+struct command_result *param_report_format(struct command *cmd UNNEEDED, const char *name UNNEEDED,
+ const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED,
+ struct report_format **format UNNEEDED)
+{ fprintf(stderr, "param_report_format called!\n"); abort(); }
+/* Generated stub for plugin_broken_cb */
+struct command_result *plugin_broken_cb(struct command *cmd UNNEEDED,
+ const char *method UNNEEDED,
+ const char *buf UNNEEDED,
+ const jsmntok_t *result UNNEEDED,
+ void *arg UNNEEDED)
+{ fprintf(stderr, "plugin_broken_cb called!\n"); abort(); }
+/* Generated stub for plugin_err */
+void plugin_err(struct plugin *p UNNEEDED, const char *fmt UNNEEDED, ...)
+{ fprintf(stderr, "plugin_err called!\n"); abort(); }
+/* Generated stub for plugin_get_data_ */
+void *plugin_get_data_(struct plugin *plugin UNNEEDED)
+{ fprintf(stderr, "plugin_get_data_ called!\n"); abort(); }
+/* Generated stub for plugin_log */
+void plugin_log(struct plugin *p UNNEEDED, enum log_level l UNNEEDED, const char *fmt UNNEEDED, ...)
+{ fprintf(stderr, "plugin_log called!\n"); abort(); }
+/* Generated stub for plugin_main */
+void plugin_main(char *argv[] UNNEEDED,
+ const char *(*init)(struct command *init_cmd UNNEEDED,
+ const char *buf UNNEEDED,
+ const jsmntok_t *) UNNEEDED,
+ void *data TAKES UNNEEDED,
+ const enum plugin_restartability restartability UNNEEDED,
+ bool init_rpc UNNEEDED,
+ struct feature_set *features STEALS UNNEEDED,
+ const struct plugin_command *commands TAKES UNNEEDED,
+ size_t num_commands UNNEEDED,
+ const struct plugin_notification *notif_subs TAKES UNNEEDED,
+ size_t num_notif_subs UNNEEDED,
+ const struct plugin_hook *hook_subs TAKES UNNEEDED,
+ size_t num_hook_subs UNNEEDED,
+ const char **notif_topics TAKES UNNEEDED,
+ size_t num_notif_topics UNNEEDED,
+ ...)
+{ fprintf(stderr, "plugin_main called!\n"); abort(); }
+/* Generated stub for rpc_scan_datastore_hex */
+const char *rpc_scan_datastore_hex(const tal_t *ctx UNNEEDED,
+ struct command *cmd UNNEEDED,
+ const char **keys UNNEEDED,
+ ...)
+{ fprintf(stderr, "rpc_scan_datastore_hex called!\n"); abort(); }
+/* Generated stub for send_outreq */
+struct command_result *send_outreq(const struct out_req *req UNNEEDED)
+{ fprintf(stderr, "send_outreq called!\n"); abort(); }
+/* Generated stub for update_channel_onchain_fees */
+char *update_channel_onchain_fees(const tal_t *ctx UNNEEDED,
+ struct command *cmd UNNEEDED,
+ struct bkpr *bkpr UNNEEDED,
+ struct account *acct UNNEEDED)
+{ fprintf(stderr, "update_channel_onchain_fees called!\n"); abort(); }
+/* AUTOGENERATED MOCKS END */
+
+static struct bkpr *make_bkpr(const tal_t *ctx,
+ const char *name, unsigned int minor_unit)
+{
+ struct bkpr *bkpr = tal(ctx, struct bkpr);
+ struct iso4217_name_and_divisor *currency
+ = tal(bkpr, struct iso4217_name_and_divisor);
+ currency->name = name;
+ currency->minor_unit = minor_unit;
+ bkpr->currency = currency;
+ bkpr->currency_rates = tal(bkpr, currencymap_t);
+ uintmap_init(bkpr->currency_rates);
+ return bkpr;
+}
+
+static void add_rate(struct bkpr *bkpr, u64 ts, u64 raw_rate, u32 duration)
+{
+ struct currencyrate *crate = tal(bkpr->currency_rates, struct currencyrate);
+ crate->raw_rate = raw_rate;
+ crate->duration = duration;
+ uintmap_add(bkpr->currency_rates, ts, crate);
+}
+
+/* USD at $50,000/BTC: raw_rate = 5,000,000 cents/BTC, minor_unit=2, mul=100 */
+#define USD_RATE 5000000ULL
+#define TS 1000ULL
+
+/* No rate added yet -> returns NULL */
+static void test_no_rate(const tal_t *ctx)
+{
+ struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
+ assert(currencyrate_str(ctx, bkpr, TS, NULL) == NULL);
+ tal_free(bkpr);
+}
+
+/* Timestamp before any rate -> NULL */
+static void test_before_rate(const tal_t *ctx)
+{
+ struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
+ add_rate(bkpr, TS, USD_RATE, 3600);
+ assert(currencyrate_str(ctx, bkpr, TS - 1, NULL) == NULL);
+ tal_free(bkpr);
+}
+
+/* Timestamp after rate expiry -> NULL */
+static void test_expired_rate(const tal_t *ctx)
+{
+ struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
+ add_rate(bkpr, TS, USD_RATE, 3600);
+ assert(currencyrate_str(ctx, bkpr, TS + 3600, NULL) == NULL);
+ tal_free(bkpr);
+}
+
+/* NULL msat: returns the rate for 1 BTC directly, no msat math */
+static void test_null_msat(const tal_t *ctx)
+{
+ struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
+ add_rate(bkpr, TS, USD_RATE, 3600);
+ /* raw_rate=5,000,000; intpart=50000, fracpart=0 */
+ assert(streq(currencyrate_str(ctx, bkpr, TS + 1, NULL), "50000.00"));
+ tal_free(bkpr);
+}
+
+/* 1 BTC: should match the NULL-msat rate */
+static void test_one_btc(const tal_t *ctx)
+{
+ struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
+ add_rate(bkpr, TS, USD_RATE, 3600);
+ /* MSAT_PER_BTC * 5,000,000 / MSAT_PER_BTC = 5,000,000 -> "50000.00" */
+ struct amount_msat msat = amount_msat(MSAT_PER_BTC);
+ assert(streq(currencyrate_str(ctx, bkpr, TS + 1, &msat), "50000.00"));
+ tal_free(bkpr);
+}
+
+/* 0.01 BTC = 1e9 msat: 1e9 * 5e6 / 1e11 = 50,000 cents -> "500.00" */
+static void test_fractional_btc(const tal_t *ctx)
+{
+ struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
+ add_rate(bkpr, TS, USD_RATE, 3600);
+ struct amount_msat msat = amount_msat(MSAT_PER_BTC / 100);
+ assert(streq(currencyrate_str(ctx, bkpr, TS + 1, &msat), "500.00"));
+ tal_free(bkpr);
+}
+
+/* 100 sat = 100,000 msat: 100,000 * 5,000,000 / 1e11 = 5 cents -> "0.05" */
+static void test_small_amount(const tal_t *ctx)
+{
+ struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
+ add_rate(bkpr, TS, USD_RATE, 3600);
+ struct amount_msat msat = amount_msat(100 * MSAT_PER_SAT);
+ assert(streq(currencyrate_str(ctx, bkpr, TS + 1, &msat), "0.05"));
+ tal_free(bkpr);
+}
+
+/* Overflow: msat > rate. 2000 BTC = 2e14 msat.
+ * 2e14 * 5e6 = 1e21 > u64_max, so we divide msat first:
+ * (2e14 / 1e11) * 5e6 = 2000 * 5,000,000 = 1e10 cents -> "100000000.00" */
+static void test_overflow_msat_larger(const tal_t *ctx)
+{
+ struct bkpr *bkpr = make_bkpr(ctx, "USD", 2);
+ add_rate(bkpr, TS, USD_RATE, 3600);
+ struct amount_msat msat = amount_msat(2000 * MSAT_PER_BTC);
+ assert(streq(currencyrate_str(ctx, bkpr, TS + 1, &msat), "100000000.00"));
+ tal_free(bkpr);
+}
+
+/* Overflow: rate > msat. Hypothetical 0-decimal currency at 5e12 units/BTC.
+ * 20,000 sat = 2e7 msat; 2e7 * 5e12 = 1e20 > u64_max, so divide rate first:
+ * 2e7 * (5e12 / 1e11) = 2e7 * 50 = 1e9 -> "1000000000" */
+static void test_overflow_rate_larger(const tal_t *ctx)
+{
+ struct bkpr *bkpr = make_bkpr(ctx, "BIG", 0);
+ add_rate(bkpr, TS, 5000000000000ULL, 3600);
+ struct amount_msat msat = amount_msat(20000 * MSAT_PER_SAT);
+ assert(streq(currencyrate_str(ctx, bkpr, TS + 1, &msat), "1000000000"));
+ tal_free(bkpr);
+}
+
+int main(int argc, char *argv[])
+{
+ const tal_t *ctx = tal(NULL, char);
+
+ common_setup(argv[0]);
+
+ test_no_rate(ctx);
+ test_before_rate(ctx);
+ test_expired_rate(ctx);
+ test_null_msat(ctx);
+ test_one_btc(ctx);
+ test_fractional_btc(ctx);
+ test_small_amount(ctx);
+ test_overflow_msat_larger(ctx);
+ test_overflow_rate_larger(ctx);
+
+ tal_free(ctx);
+ common_shutdown();
+}
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.