Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions common/interactivetx.c
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,35 @@ bool interactivetx_has_changes(struct interactivetx_context *ictx,
|| tal_count(set->added_outs) || tal_count(set->rm_outs);
}

char *interactive_tx_add_output_check_max_money(const tal_t *ctx,
struct wally_psbt *psbt,
struct amount_sat amt)
{
struct amount_sat output_amt;
struct amount_sat out_total = AMOUNT_SAT(0);
for (size_t i = 0; i < psbt->num_outputs; i++) {
output_amt = psbt_output_get_amount(psbt, i);
if (!amount_sat_add(&out_total, out_total, output_amt))
return tal_fmt(ctx,
"Output amount total overflow "
"(partial sum is %s, current "
"output is %s at output number %d)",
fmt_amount_sat(tmpctx, out_total),
fmt_amount_sat(tmpctx, output_amt),
(int)i);
}
if (!amount_sat_add(&out_total, out_total, amt) ||
amount_sat_greater(out_total, chainparams->max_supply))
return tal_fmt(ctx,
"Adding output amount %s would exceed max "
"supply (current total is %s over %d "
"outputs)",
fmt_amount_sat(tmpctx, amt),
fmt_amount_sat(tmpctx, out_total),
(int)psbt->num_outputs);
return NULL;
}

char *process_interactivetx_updates(const tal_t *ctx,
struct interactivetx_context *ictx,
bool *received_tx_complete,
Expand Down Expand Up @@ -739,6 +768,16 @@ char *process_interactivetx_updates(const tal_t *ctx,
ictx->current_psbt->num_outputs + 1,
MAX_FUNDING_OUTPUTS);

/* BOLT #2:
* The receiving node: ...
* - MUST fail the negotiation if: ...
* - the `sats` amount is greater than 2,100,000,000,000,000 (`MAX_MONEY`)
*/
error = interactive_tx_add_output_check_max_money(
ctx, ictx->current_psbt, amt);
if (error)
return error;

out = psbt_append_output(ictx->current_psbt,
scriptpubkey,
amt);
Expand Down
6 changes: 6 additions & 0 deletions common/interactivetx.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ struct interactivetx_context {
struct psbt_changeset *change_set;
};

/* Sanity checks on the transaction outputs amount and the new value to be added
* so that MAX_MONEY (21M BTC) is not exceeded. */
char *interactive_tx_add_output_check_max_money(const tal_t *ctx,
struct wally_psbt *psbt,
struct amount_sat amt);

/* Builds a new default interactivetx context with default values */
struct interactivetx_context *new_interactivetx_context(const tal_t *ctx,
enum tx_role our_role,
Expand Down
317 changes: 317 additions & 0 deletions common/test/run-interactivetx.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
#include "config.h"
#include "../amount.c"
#include "../interactivetx.c"
#include <assert.h>
#include <bitcoin/chainparams.h>
#include <ccan/array_size/array_size.h>
#include <common/setup.h>
#include <stdio.h>
#include <string.h>

/* AUTOGENERATED MOCKS START */
/* Generated stub for fromwire */
const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED)
{ fprintf(stderr, "fromwire called!\n"); abort(); }
/* Generated stub for fromwire_bool */
bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED)
{ fprintf(stderr, "fromwire_bool called!\n"); abort(); }
/* Generated stub for fromwire_fail */
void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED)
{ fprintf(stderr, "fromwire_fail called!\n"); abort(); }
/* Generated stub for fromwire_peektype */
int fromwire_peektype(const u8 *cursor UNNEEDED)
{ fprintf(stderr, "fromwire_peektype called!\n"); abort(); }
/* Generated stub for fromwire_secp256k1_ecdsa_signature */
void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED,
secp256k1_ecdsa_signature *signature UNNEEDED)
{ fprintf(stderr, "fromwire_secp256k1_ecdsa_signature called!\n"); abort(); }
/* Generated stub for fromwire_sha256 */
void fromwire_sha256(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct sha256 *sha256 UNNEEDED)
{ fprintf(stderr, "fromwire_sha256 called!\n"); abort(); }
/* Generated stub for fromwire_tal_arrn */
u8 *fromwire_tal_arrn(const tal_t *ctx UNNEEDED,
const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED)
{ fprintf(stderr, "fromwire_tal_arrn called!\n"); abort(); }
/* Generated stub for fromwire_tx_add_input */
bool fromwire_tx_add_input(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct channel_id *channel_id UNNEEDED, u64 *serial_id UNNEEDED, u8 **prevtx UNNEEDED, u32 *prevtx_vout UNNEEDED, u32 *sequence UNNEEDED, struct tlv_tx_add_input_tlvs **tlvs UNNEEDED)
{ fprintf(stderr, "fromwire_tx_add_input called!\n"); abort(); }
/* Generated stub for fromwire_tx_add_output */
bool fromwire_tx_add_output(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct channel_id *channel_id UNNEEDED, u64 *serial_id UNNEEDED, u64 *sats UNNEEDED, u8 **script UNNEEDED)
{ fprintf(stderr, "fromwire_tx_add_output called!\n"); abort(); }
/* Generated stub for fromwire_tx_complete */
bool fromwire_tx_complete(const void *p UNNEEDED, struct channel_id *channel_id UNNEEDED)
{ fprintf(stderr, "fromwire_tx_complete called!\n"); abort(); }
/* Generated stub for fromwire_tx_remove_input */
bool fromwire_tx_remove_input(const void *p UNNEEDED, struct channel_id *channel_id UNNEEDED, u64 *serial_id UNNEEDED)
{ fprintf(stderr, "fromwire_tx_remove_input called!\n"); abort(); }
/* Generated stub for fromwire_tx_remove_output */
bool fromwire_tx_remove_output(const void *p UNNEEDED, struct channel_id *channel_id UNNEEDED, u64 *serial_id UNNEEDED)
{ fprintf(stderr, "fromwire_tx_remove_output called!\n"); abort(); }
/* Generated stub for fromwire_u32 */
u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED)
{ fprintf(stderr, "fromwire_u32 called!\n"); abort(); }
/* Generated stub for fromwire_u64 */
u64 fromwire_u64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED)
{ fprintf(stderr, "fromwire_u64 called!\n"); abort(); }
/* Generated stub for fromwire_u8 */
u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED)
{ fprintf(stderr, "fromwire_u8 called!\n"); abort(); }
/* Generated stub for fromwire_u8_array */
void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED)
{ fprintf(stderr, "fromwire_u8_array called!\n"); abort(); }
/* Generated stub for is_peer_error */
const char *is_peer_error(const tal_t *ctx UNNEEDED, const u8 *msg UNNEEDED)
{ fprintf(stderr, "is_peer_error called!\n"); abort(); }
/* Generated stub for is_peer_warning */
const char *is_peer_warning(const tal_t *ctx UNNEEDED, const u8 *msg UNNEEDED)
{ fprintf(stderr, "is_peer_warning called!\n"); abort(); }
/* Generated stub for is_unknown_msg_discardable */
bool is_unknown_msg_discardable(const u8 *cursor UNNEEDED)
{ fprintf(stderr, "is_unknown_msg_discardable called!\n"); abort(); }
/* Generated stub for peer_read */
u8 *peer_read(const tal_t *ctx UNNEEDED, struct per_peer_state *pps UNNEEDED)
{ fprintf(stderr, "peer_read called!\n"); abort(); }
/* Generated stub for peer_write */
void peer_write(struct per_peer_state *pps UNNEEDED, const void *msg TAKES UNNEEDED)
{ fprintf(stderr, "peer_write called!\n"); abort(); }
/* Generated stub for psbt_find_serial_input */
int psbt_find_serial_input(struct wally_psbt *psbt UNNEEDED, u64 serial_id UNNEEDED)
{ fprintf(stderr, "psbt_find_serial_input called!\n"); abort(); }
/* Generated stub for psbt_find_serial_output */
int psbt_find_serial_output(struct wally_psbt *psbt UNNEEDED, u64 serial_id UNNEEDED)
{ fprintf(stderr, "psbt_find_serial_output called!\n"); abort(); }
/* Generated stub for psbt_get_changeset */
struct psbt_changeset *psbt_get_changeset(const tal_t *ctx UNNEEDED,
struct wally_psbt *orig UNNEEDED,
struct wally_psbt *new UNNEEDED)
{ fprintf(stderr, "psbt_get_changeset called!\n"); abort(); }
/* Generated stub for psbt_get_serial_id */
bool psbt_get_serial_id(const struct wally_map *map UNNEEDED,
u64 *serial_id UNNEEDED)
{ fprintf(stderr, "psbt_get_serial_id called!\n"); abort(); }
/* Generated stub for psbt_input_set_serial_id */
void psbt_input_set_serial_id(const tal_t *ctx UNNEEDED,
struct wally_psbt_input *input UNNEEDED,
u64 serial_id UNNEEDED)
{ fprintf(stderr, "psbt_input_set_serial_id called!\n"); abort(); }
/* Generated stub for psbt_output_set_serial_id */
void psbt_output_set_serial_id(const tal_t *ctx UNNEEDED,
struct wally_psbt_output *output UNNEEDED,
u64 serial_id UNNEEDED)
{ fprintf(stderr, "psbt_output_set_serial_id called!\n"); abort(); }
/* Generated stub for psbt_sort_by_serial_id */
void psbt_sort_by_serial_id(struct wally_psbt *psbt UNNEEDED)
{ fprintf(stderr, "psbt_sort_by_serial_id called!\n"); abort(); }
/* Generated stub for siphash_seed */
const struct siphash_seed *siphash_seed(void)
{ fprintf(stderr, "siphash_seed called!\n"); abort(); }
/* Generated stub for status_fmt */
void status_fmt(enum log_level level UNNEEDED,
const struct node_id *peer UNNEEDED,
const char *fmt UNNEEDED, ...)

{ fprintf(stderr, "status_fmt called!\n"); abort(); }
/* Generated stub for tlv_tx_add_input_tlvs_new */
struct tlv_tx_add_input_tlvs *tlv_tx_add_input_tlvs_new(const tal_t *ctx UNNEEDED)
{ fprintf(stderr, "tlv_tx_add_input_tlvs_new called!\n"); abort(); }
/* Generated stub for towire */
void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED)
{ fprintf(stderr, "towire called!\n"); abort(); }
/* Generated stub for towire_bool */
void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED)
{ fprintf(stderr, "towire_bool called!\n"); abort(); }
/* Generated stub for towire_secp256k1_ecdsa_signature */
void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED,
const secp256k1_ecdsa_signature *signature UNNEEDED)
{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); }
/* Generated stub for towire_sha256 */
void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED)
{ fprintf(stderr, "towire_sha256 called!\n"); abort(); }
/* Generated stub for towire_tx_add_input */
u8 *towire_tx_add_input(const tal_t *ctx UNNEEDED, const struct channel_id *channel_id UNNEEDED, u64 serial_id UNNEEDED, const u8 *prevtx UNNEEDED, u32 prevtx_vout UNNEEDED, u32 sequence UNNEEDED, const struct tlv_tx_add_input_tlvs *tlvs UNNEEDED)
{ fprintf(stderr, "towire_tx_add_input called!\n"); abort(); }
/* Generated stub for towire_tx_add_output */
u8 *towire_tx_add_output(const tal_t *ctx UNNEEDED, const struct channel_id *channel_id UNNEEDED, u64 serial_id UNNEEDED, u64 sats UNNEEDED, const u8 *script UNNEEDED)
{ fprintf(stderr, "towire_tx_add_output called!\n"); abort(); }
/* Generated stub for towire_tx_complete */
u8 *towire_tx_complete(const tal_t *ctx UNNEEDED, const struct channel_id *channel_id UNNEEDED)
{ fprintf(stderr, "towire_tx_complete called!\n"); abort(); }
/* Generated stub for towire_tx_remove_input */
u8 *towire_tx_remove_input(const tal_t *ctx UNNEEDED, const struct channel_id *channel_id UNNEEDED, u64 serial_id UNNEEDED)
{ fprintf(stderr, "towire_tx_remove_input called!\n"); abort(); }
/* Generated stub for towire_tx_remove_output */
u8 *towire_tx_remove_output(const tal_t *ctx UNNEEDED, const struct channel_id *channel_id UNNEEDED, u64 serial_id UNNEEDED)
{ fprintf(stderr, "towire_tx_remove_output called!\n"); abort(); }
/* Generated stub for towire_u32 */
void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED)
{ fprintf(stderr, "towire_u32 called!\n"); abort(); }
/* Generated stub for towire_u64 */
void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED)
{ fprintf(stderr, "towire_u64 called!\n"); abort(); }
/* Generated stub for towire_u8 */
void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED)
{ fprintf(stderr, "towire_u8 called!\n"); abort(); }
/* Generated stub for towire_u8_array */
void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED)
{ fprintf(stderr, "towire_u8_array called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */

/* Placeholder P2WPKH scriptpubkey for psbt_append_output. The helper under
* test only inspects output amounts, so the script contents do not matter.
*
* Byte layout:
* 0x00 - OP_0 (witness version 0)
* 0x14 - OP_DATA_20: push the following 20 bytes
* 0x00..0x13 - 20-byte witness program (dummy pubkey hash, not a real key)
*/
static const u8 dummy_script[] = {
0x00, 0x14, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13};

static struct wally_psbt *empty_psbt(const tal_t *ctx)
{
return create_psbt(ctx, 0, 0, 0);
}

static void add_output(struct wally_psbt *psbt, struct amount_sat sat)
{
struct wally_psbt_output *out;
u8 *script =
tal_dup_arr(tmpctx, u8, dummy_script, ARRAY_SIZE(dummy_script), 0);

out = psbt_append_output(psbt, script, sat);
assert(out);
}

/* Force an output amount without going through libwally's MAX_MONEY checks. */
static void set_output_amount(struct wally_psbt *psbt, size_t idx, u64 sats)
{
assert(idx < psbt->num_outputs);
psbt->outputs[idx].amount = sats;
psbt->outputs[idx].has_amount = 1;
if (psbt->tx && idx < psbt->tx->num_outputs)
psbt->tx->outputs[idx].satoshi = sats;
}

static void test_empty_psbt_accepts_valid_amounts(void)
{
struct wally_psbt *psbt = empty_psbt(tmpctx);
char *err;

err = interactive_tx_add_output_check_max_money(tmpctx, psbt,
AMOUNT_SAT(0));
assert(!err);

err = interactive_tx_add_output_check_max_money(tmpctx, psbt,
AMOUNT_SAT(1));
assert(!err);

err = interactive_tx_add_output_check_max_money(tmpctx, psbt,
AMOUNT_SAT(100000000));
assert(!err);

/* Exactly max supply is allowed (amount_sat_greater, not greater_eq).
*/
err = interactive_tx_add_output_check_max_money(
tmpctx, psbt, chainparams->max_supply);
assert(!err);
}

static void test_empty_psbt_rejects_over_max_supply(void)
{
struct wally_psbt *psbt = empty_psbt(tmpctx);
struct amount_sat over;
char *err;

assert(amount_sat_add(&over, chainparams->max_supply, AMOUNT_SAT(1)));
err = interactive_tx_add_output_check_max_money(tmpctx, psbt, over);
assert(err);
assert(strstr(err, "would exceed max supply"));
}

static void test_existing_outputs_sum_with_new_amount(void)
{
struct wally_psbt *psbt = empty_psbt(tmpctx);
/* 10M BTC and 11M BTC - sum is 21M, so +1 sat exceeds max supply. */
struct amount_sat ten_m = AMOUNT_SAT(1000000000000000);
struct amount_sat eleven_m = AMOUNT_SAT(1100000000000000);
struct amount_sat almost_eleven_m;
char *err;

add_output(psbt, ten_m);
add_output(psbt, eleven_m);

/* 10M + 11M + 1 sat > 21M */
err = interactive_tx_add_output_check_max_money(tmpctx, psbt,
AMOUNT_SAT(1));
assert(err);
assert(strstr(err, "would exceed max supply"));

/* Rebuild: 10M + (11M - 1) + 1 == 21M, should pass */
psbt = empty_psbt(tmpctx);
add_output(psbt, ten_m);
assert(amount_sat_sub(&almost_eleven_m, eleven_m, AMOUNT_SAT(1)));
add_output(psbt, almost_eleven_m);

err = interactive_tx_add_output_check_max_money(tmpctx, psbt,
AMOUNT_SAT(1));
assert(!err);

/* One more sat would exceed */
err = interactive_tx_add_output_check_max_money(tmpctx, psbt,
AMOUNT_SAT(2));
assert(err);
assert(strstr(err, "would exceed max supply"));
}

static void test_output_total_overflow(void)
{
struct wally_psbt *psbt = empty_psbt(tmpctx);
char *err;

/* Two legal individual outputs whose sum overflows u64. */
add_output(psbt, AMOUNT_SAT(1));
add_output(psbt, AMOUNT_SAT(1));
set_output_amount(psbt, 0, UINT64_MAX);
set_output_amount(psbt, 1, 1);

err = interactive_tx_add_output_check_max_money(tmpctx, psbt,
AMOUNT_SAT(1));
assert(err);
assert(strstr(err, "Output amount total overflow"));
}

static void test_add_amount_overflow(void)
{
struct wally_psbt *psbt = empty_psbt(tmpctx);
char *err;

add_output(psbt, AMOUNT_SAT(1));
set_output_amount(psbt, 0, UINT64_MAX);

/* Existing total is UINT64_MAX; adding anything overflows. */
err = interactive_tx_add_output_check_max_money(tmpctx, psbt,
AMOUNT_SAT(1));
assert(err);
/* This path uses the max-supply error (add fails or exceeds). */
assert(strstr(err, "would exceed max supply") ||
strstr(err, "Output amount total overflow"));
}

int main(int argc, char *argv[])
{
common_setup(argv[0]);
chainparams = chainparams_for_network("bitcoin");

assert(amount_sat_eq(chainparams->max_supply,
AMOUNT_SAT(2100000000000000)));

test_empty_psbt_accepts_valid_amounts();
test_empty_psbt_rejects_over_max_supply();
test_existing_outputs_sum_with_new_amount();
test_output_total_overflow();
test_add_amount_overflow();

common_shutdown();
return 0;
}
Loading
Loading