#include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SCRIPT_DUMP_TOKENS 0 #define SCRIPT_DUMP_SEGMENTS 0 #define ARROW_SYMBOL "->" #define PIPE_SYMBOL '|' #define AT_SYMBOL '@' #define COLON_SYMBOL ':' #define Q_SYMBOL '?' #define WILD_SYMBOL '*' #define PLUS_SYMBOL '+' #define MINUS_SYMBOL '-' #define FEE_SYMBOL "fee" #define PERCENT_REGEX "^([0-9]*)[.]?([0-9]*)%$" #define Q_REGEX "^\\?$" #define WILD_REGEX "^\\*$" #define CHANID_REGEX "^[0-9A-Fa-f]{64}$" #define NODEID_REGEX "^0[23][0-9A-Fa-f]{62}$" #define WALLET_REGEX "^wallet$" #define FEE_REGEX "^" FEE_SYMBOL "$" #define SATM_REGEX "^([0-9]*)[.]?([0-9]*)[Mm]$" #define SATK_REGEX "^([0-9]*)[.]?([0-9]*)[Kk]$" #define CODE_SNIPPET_PADDING 80 /* Minimum # of matching charaters to autocomplete nodeid or chanid */ #define NODEID_MIN_CHARS 4 #define CHANID_MIN_CHARS 4 /* Token types from simplest to most complex. */ enum token_type { TOK_CHAR, TOK_DELIMITER, /* Newline or semicolon. Guaranteed one at end. */ TOK_ARROW, TOK_STR, TOK_PIPE, /* lease separator "|", ex 5M|3M (add 5M, lease 3M) */ TOK_COLON, /* node query separator ":", ex nodeid:0 */ TOK_ATSYM, /* lease rate separator "@", ex 3M@2% */ TOK_PLUS, TOK_MINUS, TOK_PERCENT, /* ie "80%" */ TOK_SATS, /* ie 8M or 0 */ TOK_QUESTION, /* ie "?" */ TOK_WILDCARD, /* ie "*" */ TOK_FEE, /* ie the word "fee" */ TOK_CHANID, TOK_WALLET, TOK_FEERATE, /* The fee rate */ TOK_NODEID, TOK_BTCADDR, TOK_CHANQUERY, /* ie nodeid:? */ TOK_MULTI_CHANID, /* Matches stored in ->right */ TOK_LEASEREQ, TOK_LEASERATE, /* ie @2% */ TOK_SEGMENT, /* An entire line or semicolon separated segment */ }; #define TOKEN_FLAG_FEERATE_NEGATIVE 0x01 struct token { enum token_type type; size_t script_index; /* For error messages */ char c; char *str; u32 ppm; struct amount_sat amount_sat; struct node_id *node_id; struct channel_id *chan_id; struct token *left, *middle, *right; u32 flags; }; static struct token *new_token(const tal_t *ctx, enum token_type token_type, size_t script_index) { struct token *token = tal(ctx, struct token); token->type = token_type; token->script_index = script_index; token->c = 0; token->str = NULL; token->ppm = 0; token->amount_sat = AMOUNT_SAT(0); token->node_id = NULL; token->chan_id = NULL; token->left = NULL; token->middle = NULL; token->right = NULL; token->flags = 0; return token; } #if SCRIPT_DUMP_TOKENS || SCRIPT_DUMP_SEGMENTS static const char *token_type_str(enum token_type type) { switch (type) { case TOK_CHAR: return "TOK_CHAR"; case TOK_DELIMITER: return "TOK_DELIMITER"; case TOK_ARROW: return "TOK_ARROW"; case TOK_STR: return "TOK_STR"; case TOK_PIPE: return "TOK_PIPE"; case TOK_ATSYM: return "TOK_ATSYM"; case TOK_PLUS: return "TOK_PLUS"; case TOK_MINUS: return "TOK_MINUS"; case TOK_COLON: return "TOK_COLON"; case TOK_SATS: return "TOK_SATS"; case TOK_PERCENT: return "TOK_PERCENT"; case TOK_QUESTION: return "TOK_QUESTION"; case TOK_WILDCARD: return "TOK_WILDCARD"; case TOK_FEE: return "TOK_FEE"; case TOK_CHANID: return "TOK_CHANID"; case TOK_WALLET: return "TOK_WALLET"; case TOK_FEERATE: return "TOK_FEERATE"; case TOK_NODEID: return "TOK_NODEID"; case TOK_BTCADDR: return "TOK_BTCADDR"; case TOK_CHANQUERY: return "TOK_CHANQUERY"; case TOK_MULTI_CHANID: return "TOK_MULTI_CHANID"; case TOK_LEASEREQ: return "TOK_LEASEREQ"; case TOK_LEASERATE: return "TOK_LEASERATE"; case TOK_SEGMENT: return "TOK_SEGMENT"; } return NULL; } static void dump_token_shallow(char **str, struct token *token, char *prefix) { const char *tmp; tal_append_fmt(str, "%s%zu:%s", prefix, token->script_index, token_type_str(token->type)); if (token->c) { tal_append_fmt(str, " char:"); if (token->c == '\n') tal_append_fmt(str, "'\\n'"); else if (token->c == '\r') tal_append_fmt(str, "'\\r'"); else if (token->c == '\t') tal_append_fmt(str, "'\\t'"); else if (token->c < ' ') tal_append_fmt(str, "0x%02x", token->c); else tal_append_fmt(str, "'%c'", token->c); } if (token->str) tal_append_fmt(str, " str:\"%s\"", token->str); if (token->node_id) tal_append_fmt(str, " node_id:%s", fmt_node_id(tmpctx, token->node_id)); if (token->chan_id) tal_append_fmt(str, " chan_id:%s", tal_hexstr(tmpctx, token->chan_id, sizeof(struct channel_id))); if (token->ppm) tal_append_fmt(str, " ppm:%u", token->ppm); if (!amount_sat_is_zero(token->amount_sat) || token->type == TOK_SATS) { tmp = fmt_amount_sat(tmpctx, token->amount_sat); tal_append_fmt(str, " amnt:%s", tmp); tal_free(tmp); } if (token->flags) tal_append_fmt(str, " flags:%u", token->flags); } #endif /* SCRIPT_DUMP_TOKENS || SCRIPT_DUMP_SEGMENTS */ #if SCRIPT_DUMP_TOKENS /* Returns the indent used */ static int dump_token(char **str, struct token *token, int indent, char *prefix) { if (token->left) indent = dump_token(str, token->left, indent, "l ") + 1; for (int i = 0; i < indent; i++) tal_append_fmt(str, " "); dump_token_shallow(str, token, prefix); tal_append_fmt(str, "\n"); if (token->middle) dump_token(str, token->middle, indent, "m "); if (token->right) dump_token(str, token->right, indent + 1, "r "); return indent; } static struct splice_script_error *debug_dump(const tal_t *ctx, struct token **tokens) { struct splice_script_error *error = tal(ctx, struct splice_script_error); error->type = DEBUG_DUMP; error->script_index = 0; error->message = tal_strdup(error, ""); for (size_t i = 0; i < tal_count(tokens); i++) dump_token(&error->message, tokens[i], 0, "- "); return error; } #endif /* SCRIPT_DUMP_TOKENS */ #if SCRIPT_DUMP_SEGMENTS static struct splice_script_error *dump_segments(const tal_t *ctx, struct token **tokens) { struct splice_script_error *error = tal(ctx, struct splice_script_error); error->type = DEBUG_DUMP; error->script_index = 0; error->message = tal_strdup(error, ""); for (size_t i = 0; i < tal_count(tokens); i++) { if (tokens[i]->type == TOK_SEGMENT) { dump_token_shallow(&error->message, tokens[i]->left, ""); dump_token_shallow(&error->message, tokens[i]->middle, " -> "); if (tokens[i]->right) dump_token_shallow(&error->message, tokens[i]->right, " -> "); } else { tal_append_fmt(&error->message, "Invalid token!! "); dump_token_shallow(&error->message, tokens[i], ""); } tal_append_fmt(&error->message, "\n"); } return error; } #endif /* SCRIPT_DUMP_SEGMENTS */ static struct splice_script_error *new_error_offset(const tal_t *ctx, enum splice_script_error_type type, struct token *token, const char *phase, int index_offset) { struct splice_script_error *error = tal(ctx, struct splice_script_error); error->type = type; error->script_index = token->script_index + index_offset; error->message = tal_strdup(error, ""); error->phase = phase; return error; } static struct splice_script_error *new_error(const tal_t *ctx, enum splice_script_error_type type, struct token *token, const char *phase) { return new_error_offset(ctx, type, token, phase, 0); } static char *context_snippet(const tal_t *ctx, const char *script, struct splice_script_error *error) { const char *start = script + error->script_index; const char *last = start; const char *end = script + strlen(script); char *str; for (size_t i = 0; i < CODE_SNIPPET_PADDING && start-1 >= script && start[-1] >= ' '; i++) start--; for (size_t i = 0; i < CODE_SNIPPET_PADDING && last < end && last[0] >= ' '; i++) last++; str = tal_strndup(ctx, start, last - start); tal_append_fmt(&str, "\n"); for (const char *ptr = start; ptr < script + error->script_index; ptr++) tal_append_fmt(&str, " "); tal_append_fmt(&str, "^\n"); if (error->message && strlen(error->message)) tal_append_fmt(&str, "%s\n", error->message); if (error->phase) tal_append_fmt(&str, "Compiler phase: %s\n", error->phase); return str; } char *fmt_splice_script_compiler_error(const tal_t *ctx, const char *script, struct splice_script_error *error) { switch (error->type) { case INTERNAL_ERROR: return tal_fmt(ctx, "Internal error\n%s", context_snippet(ctx, script, error)); case INVALID_TOKEN: return tal_fmt(ctx, "Invalid token error\n%s", context_snippet(ctx, script, error)); case DEBUG_DUMP: return tal_fmt(ctx, "Token Dump:\n%s", error->message); case TOO_MANY_PIPES: return tal_fmt(ctx, "Too many '%c' symbols near here\n%s", PIPE_SYMBOL, context_snippet(ctx, script, error)); case TOO_MANY_ATS: return tal_fmt(ctx, "Too many '%c' symbols near here\n%s", AT_SYMBOL, context_snippet(ctx, script, error)); case TOO_MANY_COLONS: return tal_fmt(ctx, "Too many '%c' symbols near here\n%s", COLON_SYMBOL, context_snippet(ctx, script, error)); case TOO_MANY_PLUS: return tal_fmt(ctx, "Too many '%c' symbols near here\n%s", PLUS_SYMBOL, context_snippet(ctx, script, error)); case TOO_MANY_MINUS: return tal_fmt(ctx, "Too many '%c' symbols near here\n%s", MINUS_SYMBOL, context_snippet(ctx, script, error)); case INVALID_NODEID: return tal_fmt(ctx, "Invalid node id\n%s", context_snippet(ctx, script, error)); case INVALID_CHANID: return tal_fmt(ctx, "Invalid channel id\n%s", context_snippet(ctx, script, error)); case WRONG_NUM_SEGMENT_CHUNKS: return tal_fmt(ctx, "Segments must have one or two \"" ARROW_SYMBOL "\" symbols\n%s", context_snippet(ctx, script, error)); case MISSING_ARROW: return tal_fmt(ctx, "Segments elements must be separated by \"" ARROW_SYMBOL "\" symbols\n%s", context_snippet(ctx, script, error)); case NO_MATCHING_NODES: return tal_fmt(ctx, "No matching nodes for node query\n%s", context_snippet(ctx, script, error)); case INVALID_INDEX: return tal_fmt(ctx, "Valid index must be only number digits" " and no other characters\n%s", context_snippet(ctx, script, error)); case CHAN_INDEX_ON_WILDCARD_NODE: return tal_fmt(ctx, "Node wildcard matches must use an index," " '%c', or '%c' after the '%c'\n%s", Q_SYMBOL, WILD_SYMBOL, COLON_SYMBOL, context_snippet(ctx, script, error)); case CHANQUERY_TYPEERROR: return tal_fmt(ctx, "Channel query has invalid type(s)\n%s", context_snippet(ctx, script, error)); case CHAN_INDEX_NOT_FOUND: return tal_fmt(ctx, "Channel index not found for node\n%s", context_snippet(ctx, script, error)); case NODE_ID_MULTIMATCH: return tal_fmt(ctx, "Node id matched multiple nodes, specify" " more characters to be more specific\n%s", context_snippet(ctx, script, error)); case NODE_ID_CHAN_OVERMATCH: return tal_fmt(ctx, "Node id matched a channel id, specify" " more characters to be more specific\n%s", context_snippet(ctx, script, error)); case CHAN_ID_MULTIMATCH: return tal_fmt(ctx, "Channel id matched multiple channels," " specify more characters to be more specific" "\n%s", context_snippet(ctx, script, error)); case CHAN_ID_NODE_OVERMATCH: return tal_fmt(ctx, "Channel id matched a node id, specify" " more characters to be more specific\n%s", context_snippet(ctx, script, error)); case NODE_ID_NO_UNUSED: return tal_fmt(ctx, "No unused channels for node id. Other" " channel queries already claimed all channels" "\n%s", context_snippet(ctx, script, error)); case DOUBLE_MIDDLE_OP: return tal_fmt(ctx, "Duplicate channel or address equivalent." " Each line must contain only one\n%s", context_snippet(ctx, script, error)); case MISSING_MIDDLE_OP: return tal_fmt(ctx, "Missing channel or address equivalent." " Each line must contain one\n%s", context_snippet(ctx, script, error)); case MISSING_AMOUNT_OP: return tal_fmt(ctx, "Missing amount. An amount is required here" "\n%s", context_snippet(ctx, script, error)); case MISSING_AMOUNT_OR_WILD_OP: return tal_fmt(ctx, "Missing sat amount. A sat amount or '%c'" " is required here\n%s", WILD_SYMBOL, context_snippet(ctx, script, error)); case CANNOT_PARSE_SAT_AMNT: return tal_fmt(ctx, "Failed to parse sat amount\n%s", context_snippet(ctx, script, error)); case ZERO_AMOUNTS: return tal_fmt(ctx, "Each line must specify a non-zero amount," "lease request, or pay the onchain fee. This" " line specifies none of these\n%s", context_snippet(ctx, script, error)); case IN_AND_OUT_AMOUNTS: return tal_fmt(ctx, "Can't specify funds going into and out of" " in the same segment\n%s", context_snippet(ctx, script, error)); case MISSING_PERCENT: return tal_fmt(ctx, "A percentage value is required here (ie." " 1.5%%)\n%s", context_snippet(ctx, script, error)); case LEASE_AMOUNT_ZERO: return tal_fmt(ctx, "Lease specified without a non-zero amount." " Must specify a non-zero amount.\n%s", context_snippet(ctx, script, error)); case CHANNEL_ID_UNRECOGNIZED: return tal_fmt(ctx, "Channel id not one of our channels.\n%s", context_snippet(ctx, script, error)); case DUPLICATE_CHANID: return tal_fmt(ctx, "Channel referenced on multiple lines. Each" " channel id must appear only once.\n%s", context_snippet(ctx, script, error)); case INVALID_MIDDLE_OP: return tal_fmt(ctx, "Unrecognized channel query. Must be" " channel id query, bitcoin address, or" " \"wallet\"\n%s", context_snippet(ctx, script, error)); case INSUFFICENT_FUNDS: return tal_fmt(ctx, "Script as written has insufficent funds to" " be completed\n%s", context_snippet(ctx, script, error)); case PERCENT_IS_ZERO: return tal_fmt(ctx, "Percentage channel input will result in" " zero\n%s", context_snippet(ctx, script, error)); case WILDCARD_IS_ZERO: return tal_fmt(ctx, "Wildcard channel input will result in zero" "\n%s", context_snippet(ctx, script, error)); case INVALID_PERCENT: return tal_fmt(ctx, "Percentage value invalid. Percentages must" " be in range 0%% to 100%%.\n%s", context_snippet(ctx, script, error)); case LEFT_PERCENT_OVER_100: return tal_fmt(ctx, "Left operand percentage total out of" " range. Left percentages must add up to 100%%" " or less\n%s", context_snippet(ctx, script, error)); case LEFT_FEE_NOT_NEGATIVE: return tal_fmt(ctx, "Fees on the left operand must be negative" " as they subtract from the amount\n%s", context_snippet(ctx, script, error)); case RIGHT_FEE_NOT_POSITIVE: return tal_fmt(ctx, "Fees on the right operand must be positive" " as they pull out extra to cover the fee\n%s", context_snippet(ctx, script, error)); case MISSING_FEESTR: return tal_fmt(ctx, "Must have \"%s\" token here\n%s", FEE_SYMBOL, context_snippet(ctx, script, error)); case DUPLICATE_FEESTR: return tal_fmt(ctx, "Duplicate \"%s\" token here. Only one" " channel or location may pay the fee\n%s", FEE_SYMBOL, context_snippet(ctx, script, error)); case TOO_MUCH_DECIMAL: return tal_fmt(ctx, "Too many digits after the decimal. This" " type does not support this many\n%s", context_snippet(ctx, script, error)); case INVALID_FEERATE: return tal_fmt(ctx, "Valid feerate must be only number digits" " and no other characters\n%s", context_snippet(ctx, script, error)); } return NULL; } static bool is_whitespace(char c) { return isspace(c); } static struct splice_script_error *clean_whitespace(const tal_t *ctx, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens = tal_arr(ctx, struct token *, tal_count(input)); size_t n = 0; for (size_t i = 0; i < tal_count(input); i++) if (input[i]->type != TOK_CHAR || !is_whitespace(input[i]->c)) tokens[n++] = tal_steal(tokens, input[i]); tal_free(input); tal_resize(&tokens, n); *tokens_inout = tokens; return NULL; } /* Returns point in str that starts match of suffix. */ static char *find_suffix(char *str, char *suffix) { char *ptr; if (strlen(str) < strlen(suffix)) return false; ptr = str + strlen(str) - strlen(suffix); if (streq(ptr, suffix)) return ptr; return NULL; } static struct splice_script_error *find_arrows_and_strs(const tal_t *ctx, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens = tal_arr(ctx, struct token *, tal_count(input)); struct token *token = NULL; size_t n = 0; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_DELIMITER: if (token) tokens[n++] = token; token = NULL; tokens[n++] = tal_steal(tokens, input[i]); break; case TOK_CHAR: if (!token) { token = new_token(tokens, TOK_STR, input[i]->script_index); token->str = tal_strdup(token, ""); } tal_append_fmt(&token->str, "%c", input[i]->c); if (find_suffix(token->str, ARROW_SYMBOL)) { /* Terminmate the string at the arrow */ *find_suffix(token->str, ARROW_SYMBOL) = 0; if (*token->str) tokens[n++] = token; else tal_free(token); token = NULL; tokens[n++] = new_token(tokens, TOK_ARROW, input[i]->script_index); } break; case TOK_ARROW: case TOK_STR: case TOK_PIPE: case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: case TOK_COLON: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASERATE: case TOK_LEASEREQ: case TOK_SEGMENT: return new_error(ctx, INVALID_TOKEN, input[i], "arrows"); } } /* Script should always end in a delimiter which NULLS token */ assert(!token); tal_free(input); tal_resize(&tokens, n); *tokens_inout = tokens; return NULL; } static struct splice_script_error *process_top_separators(const tal_t *ctx, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens = tal_arr(ctx, struct token *, tal_count(input) * 3); char *split_point; size_t script_index; size_t n = 0; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_CHAR: return new_error(ctx, INVALID_TOKEN, input[i], "top_separators"); case TOK_DELIMITER: case TOK_ARROW: tokens[n++] = tal_steal(tokens, input[i]); break; case TOK_STR: if ((split_point = strchr(input[i]->str, PIPE_SYMBOL))) { if (split_point != strrchr(input[i]->str, PIPE_SYMBOL)) return new_error_offset(ctx, TOO_MANY_PIPES, input[i], "top_separators", split_point - input[i]->str); *split_point = 0; tokens[n++] = tal_steal(tokens, input[i]); script_index = input[i]->script_index; script_index += (split_point - input[i]->str); tokens[n++] = new_token(tokens, TOK_PIPE, script_index); script_index++; tokens[n] = new_token(tokens, TOK_STR, script_index); tokens[n++]->str = split_point + 1; } else if ((split_point = strchr(input[i]->str, COLON_SYMBOL))) { if (split_point != strrchr(input[i]->str, COLON_SYMBOL)) return new_error_offset(ctx, TOO_MANY_COLONS, input[i], "top_separators", split_point - input[i]->str); *split_point = 0; tokens[n++] = tal_steal(tokens, input[i]); script_index = input[i]->script_index; script_index += (split_point - input[i]->str); tokens[n++] = new_token(tokens, TOK_COLON, script_index); script_index++; tokens[n] = new_token(tokens, TOK_STR, script_index); tokens[n++]->str = split_point + 1; } else { tokens[n++] = tal_steal(tokens, input[i]); } break; case TOK_PIPE: case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: case TOK_COLON: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASERATE: case TOK_LEASEREQ: case TOK_SEGMENT: return new_error(ctx, INVALID_TOKEN, input[i], "top_separators"); } } tal_free(input); tal_resize(&tokens, n); *tokens_inout = tokens; return NULL; } static struct splice_script_error *process_2nd_separators(const tal_t *ctx, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens = tal_arr(ctx, struct token *, tal_count(input) * 3); char *split_point; size_t script_index; size_t n = 0; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_CHAR: return new_error(ctx, INVALID_TOKEN, input[i], "2nd_separators"); case TOK_DELIMITER: case TOK_ARROW: case TOK_PIPE: case TOK_COLON: tokens[n++] = tal_steal(tokens, input[i]); break; case TOK_STR: if ((split_point = strchr(input[i]->str, PLUS_SYMBOL))) { if (split_point != strrchr(input[i]->str, PLUS_SYMBOL)) return new_error_offset(ctx, TOO_MANY_PLUS, input[i], "2nd_separators", split_point - input[i]->str); *split_point = 0; tokens[n++] = tal_steal(tokens, input[i]); script_index = input[i]->script_index; script_index += (split_point - input[i]->str); tokens[n++] = new_token(tokens, TOK_PLUS, script_index); script_index++; tokens[n] = new_token(tokens, TOK_STR, script_index); tokens[n++]->str = split_point + 1; } else if ((split_point = strchr(input[i]->str, MINUS_SYMBOL))) { if (split_point != strrchr(input[i]->str, MINUS_SYMBOL)) return new_error_offset(ctx, TOO_MANY_MINUS, input[i], "2nd_separators", split_point - input[i]->str); *split_point = 0; tokens[n++] = tal_steal(tokens, input[i]); script_index = input[i]->script_index; script_index += (split_point - input[i]->str); tokens[n++] = new_token(tokens, TOK_MINUS, script_index); script_index++; tokens[n] = new_token(tokens, TOK_STR, script_index); tokens[n++]->str = split_point + 1; } else { tokens[n++] = tal_steal(tokens, input[i]); } break; case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASERATE: case TOK_LEASEREQ: case TOK_SEGMENT: return new_error(ctx, INVALID_TOKEN, input[i], "2nd_separators"); } } tal_free(input); tal_resize(&tokens, n); *tokens_inout = tokens; return NULL; } static struct splice_script_error *process_3rd_separators(const tal_t *ctx, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens = tal_arr(ctx, struct token *, tal_count(input) * 3); char *split_point; size_t script_index; size_t n = 0; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_CHAR: return new_error(ctx, INVALID_TOKEN, input[i], "3rd_separators"); case TOK_DELIMITER: case TOK_ARROW: case TOK_PIPE: case TOK_COLON: case TOK_PLUS: case TOK_MINUS: tokens[n++] = tal_steal(tokens, input[i]); break; case TOK_STR: if ((split_point = strchr(input[i]->str, AT_SYMBOL))) { if (split_point != strrchr(input[i]->str, AT_SYMBOL)) return new_error_offset(ctx, TOO_MANY_ATS, input[i], "3rd_separators", split_point - input[i]->str); *split_point = 0; tokens[n++] = tal_steal(tokens, input[i]); script_index = input[i]->script_index; script_index += (split_point - input[i]->str); tokens[n++] = new_token(tokens, TOK_ATSYM, script_index); script_index++; tokens[n] = new_token(tokens, TOK_STR, script_index); tokens[n++]->str = split_point + 1; } else { tokens[n++] = tal_steal(tokens, input[i]); } break; case TOK_ATSYM: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASERATE: case TOK_LEASEREQ: case TOK_SEGMENT: return new_error(ctx, INVALID_TOKEN, input[i], "3rd_separators"); } } tal_free(input); tal_resize(&tokens, n); *tokens_inout = tokens; return NULL; } static const char *segwit_addr_net_decode(int *witness_version, uint8_t *witness_program, size_t *witness_program_len, const char *addrz, const struct chainparams *chainparams) { if (segwit_addr_decode(witness_version, witness_program, witness_program_len, chainparams->onchain_hrp, addrz)) return chainparams->onchain_hrp; else return NULL; } static bool is_bitcoin_address(const char *address) { struct ripemd160 addr; int witness_version; /* segwit_addr_net_decode requires a buffer of size 40, and will * not write to the buffer if the address is too long, so a buffer * of fixed size 40 will not overflow. */ uint8_t witness_program[40]; size_t witness_program_len; const char *bech32; u8 addr_version; if (ripemd160_from_base58(&addr_version, &addr, address, strlen(address))) { if (addr_version == chainparams->p2pkh_version) { return true; } else if (addr_version == chainparams->p2sh_version) { return true; } return false; } bech32 = segwit_addr_net_decode(&witness_version, witness_program, &witness_program_len, address, chainparams); if (bech32) { bool witness_ok; /* Only V0 has restricted lengths of witness programs */ if (witness_version == 0) { witness_ok = (witness_program_len == 20 || witness_program_len == 32); } else if (witness_version == 1) { witness_ok = (witness_program_len == 32); } else { witness_ok = true; } if (!witness_ok) return false; return true; } return false; } /* Checks token->str for a short node id and auto completes it. */ static bool autocomplete_node_id(struct token *token, struct splice_script_chan **channels, bool *multiple_nodes, bool *chan_id_overmatch) { struct node_id *match; struct node_id candidate; size_t len = strlen(token->str) / 2; *multiple_nodes = false; *chan_id_overmatch = false; if (strlen(token->str) < NODEID_MIN_CHARS) return false; if (len > PUBKEY_CMPR_LEN) return false; if (!hex_decode(token->str, len * 2, candidate.k, len)) return false; match = NULL; for (size_t i = 0; i < tal_count(channels); i++) { if (len <= sizeof(channels[i]->node_id.k) && memeq(candidate.k, len, channels[i]->node_id.k, len)) { /* must not match multiple node ids */ if (match && !node_id_eq(match, &channels[i]->node_id)) { *multiple_nodes = true; return true; } match = &channels[i]->node_id; } /* nodeid query must *not* match any channel ids */ if (len <= sizeof(channels[i]->chan_id.id) && memeq(candidate.k, len, channels[i]->chan_id.id, len)) *chan_id_overmatch = true; } if (!match) return false; assert(!token->node_id); token->node_id = tal_dup(token, struct node_id, match); return true; } static bool autocomplete_chan_id(struct token *token, struct splice_script_chan **channels, bool *multiple_chans, bool *node_id_overmatch) { struct channel_id *match; struct channel_id candidate; size_t len = strlen(token->str) / 2; *multiple_chans = false; *node_id_overmatch = false; if (strlen(token->str) < NODEID_MIN_CHARS) return false; if (len > PUBKEY_CMPR_LEN) return false; if (!hex_decode(token->str, len * 2, candidate.id, len)) return false; match = NULL; for (size_t i = 0; i < tal_count(channels); i++) { if (len <= sizeof(channels[i]->chan_id.id) && memeq(candidate.id, len, channels[i]->chan_id.id, len)) { /* must not match multiple channel ids */ if (match && !channel_id_eq(match, &channels[i]->chan_id)) { *multiple_chans = true; return true; } match = &channels[i]->chan_id; } /* nodeid query must *not* match any node ids */ if (len <= sizeof(channels[i]->node_id.k) && memeq(candidate.id, len, channels[i]->node_id.k, len)) *node_id_overmatch = true; } if (!match) return false; assert(!token->chan_id); token->chan_id = tal_dup(token, struct channel_id, match); return true; } static struct splice_script_error *type_data(const tal_t *ctx, struct splice_script_chan **channels, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens = tal_arr(ctx, struct token *, tal_count(input)); char *whole, *decimal; char *sat_candidate; struct amount_sat amount_sat; bool multiple = false; bool overmatch = false; size_t n = 0; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_CHAR: return new_error(ctx, INVALID_TOKEN, input[i], "type_data"); case TOK_DELIMITER: case TOK_ARROW: case TOK_PIPE: case TOK_COLON: case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: tokens[n++] = tal_steal(tokens, input[i]); break; case TOK_STR: if (tal_strreg(ctx, input[i]->str, PERCENT_REGEX, &whole, &decimal)) { if (atoll(whole) < 0 || atoll(decimal) < 0) return new_error(ctx, INVALID_PERCENT, input[i], "type_data"); while (strlen(decimal) < 4) tal_append_fmt(&decimal, "0"); if (decimal[4]) return new_error(ctx, TOO_MUCH_DECIMAL, input[i], "type_data"); input[i]->ppm = (u32)(10000 * atoll(whole) + atoll(decimal)); input[i]->type = TOK_PERCENT; if (input[i]->ppm > 1000000) return new_error(ctx, INVALID_PERCENT, input[i], "type_data"); } else if (tal_strreg(ctx, input[i]->str, Q_REGEX)) { input[i]->type = TOK_QUESTION; } else if (tal_strreg(ctx, input[i]->str, WILD_REGEX)) { input[i]->type = TOK_WILDCARD; } else if (tal_strreg(ctx, input[i]->str, NODEID_REGEX)) { input[i]->type = TOK_NODEID; input[i]->node_id = tal(input[i], struct node_id); if (!node_id_from_hexstr(input[i]->str, strlen(input[i]->str), input[i]->node_id)) return new_error(ctx, INVALID_NODEID, input[i], "type_data"); /* Rare corner case where channel begins with * prefix of 02 or 03 */ if (autocomplete_chan_id(input[i], channels, &multiple, &overmatch)) { if (multiple) return new_error(ctx, CHAN_ID_MULTIMATCH, input[i], "type_data"); if (overmatch) return new_error(ctx, CHAN_ID_NODE_OVERMATCH, input[i], "type_data"); input[i]->type = TOK_CHANID; input[i]->node_id = tal_free(input[i]->node_id); } } else if (is_bitcoin_address(input[i]->str)) { input[i]->type = TOK_BTCADDR; } else if (tal_strreg(ctx, input[i]->str, CHANID_REGEX)) { input[i]->type = TOK_CHANID; input[i]->chan_id = tal(input[i], struct channel_id); if (!hex_decode(input[i]->str, strlen(input[i]->str), input[i]->chan_id, 32)) return new_error(ctx, INVALID_CHANID, input[i], "type_data"); } else if (tal_strreg(ctx, input[i]->str, WALLET_REGEX)) { input[i]->type = TOK_WALLET; } else if (tal_strreg(ctx, input[i]->str, FEE_REGEX)) { input[i]->type = TOK_FEE; } else if (autocomplete_node_id(input[i], channels, &multiple, &overmatch)) { if (multiple) return new_error(ctx, NODE_ID_MULTIMATCH, input[i], "type_data"); if (overmatch) return new_error(ctx, NODE_ID_CHAN_OVERMATCH, input[i], "type_data"); input[i]->type = TOK_NODEID; } else if (autocomplete_chan_id(input[i], channels, &multiple, &overmatch)) { if (multiple) return new_error(ctx, CHAN_ID_MULTIMATCH, input[i], "type_data"); if (overmatch) return new_error(ctx, CHAN_ID_NODE_OVERMATCH, input[i], "type_data"); input[i]->type = TOK_CHANID; } else { /* Parse shorthand sat formats */ sat_candidate = input[i]->str; if (tal_strreg(ctx, sat_candidate, SATM_REGEX, &whole, &decimal)) { while (strlen(decimal) < 6) tal_append_fmt(&decimal, "0"); if (decimal[6]) return new_error(ctx, TOO_MUCH_DECIMAL, input[i], "type_data"); sat_candidate = tal_fmt(input[i], "%"PRIu64, (u64)(1000000 * atoll(whole) + atoll(decimal))); } else if (tal_strreg(ctx, sat_candidate, SATK_REGEX, &whole, &decimal)) { while (strlen(decimal) < 3) tal_append_fmt(&decimal, "0"); if (decimal[3]) return new_error(ctx, TOO_MUCH_DECIMAL, input[i], "type_data"); sat_candidate = tal_fmt(input[i], "%"PRIu64, (u64)(1000 * atoll(whole) + atoll(decimal))); } if (parse_amount_sat(&amount_sat, sat_candidate, strlen(sat_candidate))) { input[i]->type = TOK_SATS; input[i]->amount_sat = amount_sat; } else if(!sat_candidate || !strlen(sat_candidate)) { input[i]->type = TOK_SATS; input[i]->amount_sat = AMOUNT_SAT(0); } else { return new_error(ctx, CANNOT_PARSE_SAT_AMNT, input[i], "type_data"); } if (sat_candidate != input[i]->str) tal_free(sat_candidate); } tokens[n++] = tal_steal(tokens, input[i]); break; case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASERATE: case TOK_LEASEREQ: case TOK_SEGMENT: return new_error(ctx, INVALID_TOKEN, input[i], "type_data"); } } tal_free(input); tal_resize(&tokens, n); *tokens_inout = tokens; return NULL; } static struct splice_script_error *compress_top_operands(const tal_t *ctx, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens = tal_arr(ctx, struct token *, tal_count(input)); size_t n = 0; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_CHAR: return new_error(ctx, INVALID_TOKEN, input[i], "operands"); case TOK_DELIMITER: case TOK_ARROW: case TOK_PIPE: case TOK_STR: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_PLUS: case TOK_MINUS: case TOK_FEE: case TOK_CHANID: case TOK_WALLET: case TOK_NODEID: case TOK_BTCADDR: tokens[n++] = tal_steal(tokens, input[i]); break; case TOK_ATSYM: if (!n || i + 1 >= tal_count(input)) return new_error(ctx, INVALID_TOKEN, input[i], "operands"); input[i]->type = tokens[n-1]->type == TOK_FEE ? TOK_FEERATE : TOK_LEASERATE; input[i]->right = tal_steal(input[i], input[i+1]); tokens[n-1]->right = tal_steal(tokens[n-1], input[i]); i++; break; case TOK_COLON: if (!n || i + 1 >= tal_count(input)) return new_error(ctx, INVALID_TOKEN, input[i], "operands"); input[i]->type = TOK_CHANQUERY; input[i]->left = tal_steal(input[i], tokens[n-1]); input[i]->right = tal_steal(input[i], input[i+1]); tokens[n-1] = tal_steal(tokens, input[i]); i++; break; case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_FEERATE: case TOK_LEASERATE: case TOK_LEASEREQ: case TOK_SEGMENT: return new_error(ctx, INVALID_TOKEN, input[i], "operands"); } } tal_free(input); tal_resize(&tokens, n); *tokens_inout = tokens; return NULL; } static struct splice_script_error *compress_2nd_operands(const tal_t *ctx, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens = tal_arr(ctx, struct token *, tal_count(input)); size_t n = 0; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_CHAR: case TOK_ATSYM: case TOK_COLON: return new_error(ctx, INVALID_TOKEN, input[i], "2nd_operands"); case TOK_DELIMITER: case TOK_ARROW: case TOK_STR: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASERATE: tokens[n++] = tal_steal(tokens, input[i]); break; case TOK_MINUS: case TOK_PLUS: if (i + 1 >= tal_count(input) || input[i+1]->type != TOK_FEE) return new_error(ctx, MISSING_FEESTR, input[i], "2nd_operands"); /* This token is consumed. If negative, add flag to next * token */ if (input[i]->type == TOK_MINUS) input[i+1]->flags |= TOKEN_FLAG_FEERATE_NEGATIVE; break; case TOK_FEE: if (!n) return new_error(ctx, INVALID_TOKEN, input[i], "2nd_operands"); /* We put FEE on the middle spot of the amount */ tokens[n-1]->middle = tal_steal(tokens[n-1], input[i]); break; case TOK_PIPE: if (!n || i + 1 >= tal_count(input)) return new_error(ctx, INVALID_TOKEN, input[i], "2nd_operands"); input[i]->type = TOK_LEASEREQ; input[i]->right = tal_steal(input[i], input[i+1]); /* We put LEASEREQ on the right spot of the amount */ tokens[n-1]->right = tal_steal(tokens[n-1], input[i]); i++; break; case TOK_LEASEREQ: case TOK_SEGMENT: return new_error(ctx, INVALID_TOKEN, input[i], "2nd_operands"); } } tal_free(input); tal_resize(&tokens, n); *tokens_inout = tokens; return NULL; } static bool matches_chan_id(struct token *token, struct channel_id chan_id) { if (token->chan_id && channel_id_eq(token->chan_id, &chan_id)) return true; if (token->left && matches_chan_id(token->left, chan_id)) return true; if (token->middle && matches_chan_id(token->middle, chan_id)) return true; if (token->right && matches_chan_id(token->right, chan_id)) return true; return false; } /* Searches through both tokensA and tokensB. */ static struct node_id *first_node_with_unused_chan(const tal_t *ctx, struct splice_script_chan **channels, struct token **tokensA, size_t a_size, struct token **tokensB, size_t b_size) { for (size_t i = 0; i < tal_count(channels); i++) { bool used = false; for (size_t j = 0; j < a_size; j++) if (matches_chan_id(tokensA[j], channels[i]->chan_id)) used = true; for (size_t k = 0; k < b_size; k++) if (matches_chan_id(tokensB[k], channels[i]->chan_id)) used = true; if (!used) return tal_dup(ctx, struct node_id, &channels[i]->node_id); } return NULL; } static struct channel_id *chan_for_node_index(const tal_t *ctx, struct splice_script_chan **channels, struct node_id node_id, size_t channel_index) { for (size_t i = 0; i < tal_count(channels); i++) if (node_id_eq(&node_id, &channels[i]->node_id)) if (channel_index-- == 0) return tal_dup(ctx, struct channel_id, &channels[i]->chan_id); return NULL; } static struct channel_id **unused_chans(const tal_t *ctx, struct splice_script_chan **channels, struct token **tokensA, size_t a_size, struct token **tokensB, size_t b_size) { struct channel_id **result = tal_arr(ctx, struct channel_id*, 0); for (size_t i = 0; i < tal_count(channels); i++) { bool used = false; for (size_t j = 0; j < a_size; j++) if (matches_chan_id(tokensA[j], channels[i]->chan_id)) used = true; for (size_t k = 0; k < b_size; k++) if (matches_chan_id(tokensB[k], channels[i]->chan_id)) used = true; if (!used) tal_arr_expand(&result, tal_dup(result, struct channel_id, &channels[i]->chan_id)); } if (!tal_count(result)) result = tal_free(result); return result; } static struct channel_id **unused_chans_for_node(const tal_t *ctx, struct splice_script_chan **channels, struct token **tokensA, size_t a_size, struct token **tokensB, size_t b_size, struct node_id node_id) { struct channel_id **result = tal_arr(ctx, struct channel_id*, 0); for (size_t i = 0; i < tal_count(channels); i++) { bool used = false; if (!node_id_eq(&node_id, &channels[i]->node_id)) continue; for (size_t j = 0; j < a_size; j++) if (matches_chan_id(tokensA[j], channels[i]->chan_id)) used = true; for (size_t k = 0; k < b_size; k++) if (matches_chan_id(tokensB[k], channels[i]->chan_id)) used = true; if (!used) tal_arr_expand(&result, tal_dup(result, struct channel_id, &channels[i]->chan_id)); } if (!tal_count(result)) result = tal_free(result); return result; } static bool parse_channel_index(struct token *token, size_t *channel_index) { long long result; char *endptr; if (!token->str || !strlen(token->str)) return false; errno = 0; result = strtoll(token->str, &endptr, 10); if (errno || *endptr) return false; *channel_index = result; return true; } static bool parse_feerate(struct token *token, u32 *feerate) { long long result; char *endptr; if (!token->str) return false; /* zero length feerate is valid and implies 0 */ if (0 == strlen(token->str)) { *feerate = 0; return true; } /* If no feerate was found, str wont contain feerate */ if (0 == strcmp(token->str, FEE_SYMBOL)) { *feerate = 0; return true; } errno = 0; result = strtoll(token->str, &endptr, 10); if (errno || *endptr) return false; *feerate = result; return true; } static struct splice_script_error *resolve_channel_ids(const tal_t *ctx, struct splice_script_chan **channels, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens = tal_arr(ctx, struct token *, tal_count(input)); struct node_id *node_id; struct channel_id *chan_id; struct channel_id **chan_ids; struct token *token_itr; size_t channel_index; size_t n = 0; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_CHAR: case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: case TOK_COLON: case TOK_PIPE: case TOK_STR: case TOK_FEE: return new_error(ctx, INVALID_TOKEN, input[i], "resolve_channel_ids"); case TOK_LEASERATE: case TOK_ARROW: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_LEASEREQ: case TOK_DELIMITER: tokens[n++] = tal_steal(tokens, input[i]); break; case TOK_CHANQUERY: if (!input[i]->left || !input[i]->right) return new_error(ctx, INVALID_TOKEN, input[i], "resolve_channel_ids"); /* If user specifies *:? it is same as ?:? */ if (input[i]->left->type == TOK_WILDCARD && input[i]->right->type == TOK_QUESTION) input[i]->left->type = TOK_QUESTION; /* If user specifies *:# it is same as ?:# */ if (input[i]->left->type == TOK_WILDCARD && input[i]->right->type == TOK_SATS) input[i]->left->type = TOK_QUESTION; if (input[i]->left->type == TOK_QUESTION) { node_id = first_node_with_unused_chan(ctx, channels, input, tal_count(input), tokens, n); if (!node_id) return new_error(ctx, NO_MATCHING_NODES, input[i], "resolve_channel_ids"); input[i]->left->type = TOK_NODEID; input[i]->left->node_id = tal_steal(input[i]->left, node_id); } if (input[i]->left->type == TOK_NODEID && input[i]->right->type == TOK_SATS) { if (!parse_channel_index(input[i]->right, &channel_index)) return new_error(ctx, INVALID_INDEX, input[i], "resolve_channel_ids"); chan_id = chan_for_node_index(ctx, channels, *input[i]->left->node_id, channel_index); if (!chan_id) return new_error(ctx, CHAN_INDEX_NOT_FOUND, input[i], "resolve_channel_ids"); input[i]->type = TOK_CHANID; input[i]->chan_id = tal_steal(input[i], chan_id); input[i]->left = tal_free(input[i]->left); input[i]->right = tal_free(input[i]->right); tokens[n++] = tal_steal(tokens, input[i]); } else if (input[i]->left->type == TOK_NODEID) { chan_ids = unused_chans_for_node(ctx, channels, input, tal_count(input), tokens, n, *input[i]->left->node_id); if (!tal_count(chan_ids)) return new_error(ctx, NODE_ID_NO_UNUSED, input[i], "resolve_channel_ids"); if (input[i]->right->type == TOK_QUESTION) { input[i]->type = TOK_CHANID; input[i]->chan_id = tal_steal(input[i], chan_ids[0]); tokens[n++] = tal_steal(tokens, input[i]); } else if (input[i]->right->type == TOK_WILDCARD) { input[i]->type = TOK_MULTI_CHANID; token_itr = input[i]; input[i]->right = tal_free(input[i]->right); for (size_t j = 0; j < tal_count(chan_ids); j++) { token_itr->right = new_token(token_itr, TOK_CHANID, token_itr->script_index); token_itr->right->chan_id = tal_dup(token_itr->right, struct channel_id, chan_ids[j]); token_itr = token_itr->right; } tokens[n++] = tal_steal(tokens, input[i]); } else { return new_error(ctx, CHAN_INDEX_ON_WILDCARD_NODE, input[i], "resolve_channel_ids"); } tal_free(chan_ids); } else if (input[i]->left->type == TOK_WILDCARD) { if (input[i]->right->type != TOK_WILDCARD) return new_error(ctx, CHAN_INDEX_ON_WILDCARD_NODE, input[i], "resolve_channel_ids"); chan_ids = unused_chans(ctx, channels, input, tal_count(input), tokens, n); input[i]->type = TOK_MULTI_CHANID; input[i]->right = tal_free(input[i]->right); token_itr = input[i]; for (size_t j = 0; j < tal_count(chan_ids); j++) { token_itr->right = new_token(token_itr, TOK_CHANID, token_itr->script_index); token_itr->right->chan_id = tal_dup(token_itr->right, struct channel_id, chan_ids[j]); token_itr = token_itr->right; } tokens[n++] = tal_steal(tokens, input[i]); tal_free(chan_ids); } else { return new_error(ctx, CHANQUERY_TYPEERROR, input[i], "resolve_channel_ids"); } break; case TOK_SEGMENT: case TOK_MULTI_CHANID: return new_error(ctx, INVALID_TOKEN, input[i], "resolve_channel_ids"); } } tal_free(input); tal_resize(&tokens, n); *tokens_inout = tokens; return NULL; } static bool is_valid_middle(struct token *token) { switch(token->type) { case TOK_CHANID: if (!token->chan_id) return false; return true; case TOK_MULTI_CHANID: case TOK_BTCADDR: case TOK_WALLET: return true; case TOK_FEERATE: case TOK_CHAR: case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: case TOK_COLON: case TOK_LEASERATE: case TOK_ARROW: case TOK_PIPE: case TOK_STR: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_FEE: case TOK_NODEID: case TOK_CHANQUERY: case TOK_LEASEREQ: case TOK_DELIMITER: case TOK_SEGMENT: return false; } return false; } static struct splice_script_error *make_segments(const tal_t *ctx, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens = tal_arr(ctx, struct token *, tal_count(input)); size_t n = 0; size_t next_consumable = 0; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_CHAR: case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: case TOK_FEE: case TOK_COLON: case TOK_LEASERATE: case TOK_STR: case TOK_CHANQUERY: return new_error(ctx, INVALID_TOKEN, input[i], "segments"); case TOK_ARROW: case TOK_PIPE: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_MULTI_CHANID: case TOK_LEASEREQ: break; case TOK_DELIMITER: if (i == 0 || input[i-1]->type == TOK_SEGMENT || input[i-1]->type == TOK_DELIMITER) { next_consumable = i+1; break; } if (i - next_consumable == 3) { if (input[next_consumable+1]->type != TOK_ARROW) return new_error(ctx, MISSING_ARROW, input[next_consumable+1], "segments"); input[i]->type = TOK_SEGMENT; input[i]->left = tal_steal(input[i], input[next_consumable]); input[i]->middle = tal_steal(input[i], input[next_consumable+2]); tokens[n++] = tal_steal(tokens, input[i]); next_consumable = i+1; } else if (i - next_consumable == 5) { if (input[next_consumable+1]->type != TOK_ARROW) return new_error(ctx, MISSING_ARROW, input[next_consumable+1], "segments"); if (input[next_consumable+3]->type != TOK_ARROW) return new_error(ctx, MISSING_ARROW, input[next_consumable+3], "segments"); input[i]->type = TOK_SEGMENT; input[i]->left = tal_steal(input[i], input[next_consumable]); input[i]->middle = tal_steal(input[i], input[next_consumable+2]); input[i]->right = tal_steal(input[i], input[next_consumable+4]); tokens[n++] = tal_steal(tokens, input[i]); next_consumable = i+1; } else { return new_error(ctx, WRONG_NUM_SEGMENT_CHUNKS, input[i], "segments"); } /* Move middle OP to middle and validate */ if (!tokens[n-1]->right) { if (is_valid_middle(tokens[n-1]->left)) { if (is_valid_middle(tokens[n-1]->middle)) return new_error(ctx, DOUBLE_MIDDLE_OP, tokens[n-1]->middle, "make_segments"); tokens[n-1]->right = tokens[n-1]->middle; tokens[n-1]->middle = tokens[n-1]->left; tokens[n-1]->left = new_token(tokens[n-1], TOK_SATS, tokens[n-1]->script_index); tokens[n-1]->left->amount_sat = AMOUNT_SAT(0); } else if (is_valid_middle(tokens[n-1]->middle)) { tokens[n-1]->right = new_token(tokens[n-1], TOK_SATS, tokens[n-1]->script_index); tokens[n-1]->right->amount_sat = AMOUNT_SAT(0); } else { return new_error(ctx, MISSING_MIDDLE_OP, tokens[n-1]->middle, "make_segments"); } } if (tokens[n-1]->left->type == TOK_STR && !strlen(tokens[n-1]->left->str)) { tokens[n-1]->left->type = TOK_SATS; tokens[n-1]->left->amount_sat = AMOUNT_SAT(0); } if (tokens[n-1]->right->type == TOK_STR && !strlen(tokens[n-1]->right->str)) { tokens[n-1]->right->type = TOK_SATS; tokens[n-1]->right->amount_sat = AMOUNT_SAT(0); } break; case TOK_SEGMENT: return new_error(ctx, INVALID_TOKEN, input[i], "segments"); } } tal_free(input); tal_resize(&tokens, n); *tokens_inout = tokens; return NULL; } static void steal_sub_tokens(const tal_t *ctx, struct token *token) { tal_steal(token, token->left); tal_steal(token, token->middle); tal_steal(token, token->right); } static struct splice_script_error *expand_multichans(const tal_t *ctx, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens = tal_arr(ctx, struct token *, 0); struct token *token_itr; struct token *token; size_t chan_count; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_CHAR: case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: case TOK_COLON: case TOK_LEASERATE: case TOK_ARROW: case TOK_PIPE: case TOK_STR: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASEREQ: case TOK_DELIMITER: return new_error(ctx, INVALID_TOKEN, input[i], "expand_multichans"); case TOK_SEGMENT: if (input[i]->middle->type == TOK_MULTI_CHANID) { token_itr = input[i]->middle->right; /* First we count how many chan_ids */ chan_count = 0; while (token_itr) { chan_count++; token_itr = token_itr->right; } /* Now we loop through each chan_id */ token_itr = input[i]->middle->right; while (token_itr) { /* Duplicate the SEGMENT token */ token = tal_dup(tokens, struct token, input[i]); token->left = tal_dup(token, struct token, token->left); token->right = tal_dup(token, struct token, token->right); /* token_itr is already a CHANID */ token->middle = tal_steal(token, token_itr); steal_sub_tokens(token->left, token->left); steal_sub_tokens(token->middle, token->middle); steal_sub_tokens(token->right, token->right); /* Divide percentage between chans */ if (input[i]->left->type == TOK_PERCENT) { token->left->ppm = input[i]->left->ppm / chan_count; } /* Add modified copy to token array */ tal_arr_expand(&tokens, token); token_itr = token_itr->right; /* Any remainder points to go the last * destination in script */ if (!token_itr) token->left->ppm += input[i]->left->ppm % chan_count; } } else { tal_arr_expand(&tokens, tal_steal(tokens, input[i])); } break; } } tal_free(input); *tokens_inout = tokens; return NULL; } static bool is_valid_amount(struct token *token) { switch(token->type) { case TOK_SATS: case TOK_WILDCARD: case TOK_PERCENT: return true; case TOK_FEE: case TOK_CHAR: case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: case TOK_COLON: case TOK_LEASERATE: case TOK_ARROW: case TOK_PIPE: case TOK_STR: case TOK_QUESTION: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASEREQ: case TOK_DELIMITER: case TOK_SEGMENT: return false; } return false; } static bool is_valid_nonzero_amount(struct token *token) { if (token->type == TOK_SATS && amount_sat_is_zero(token->amount_sat)) return false; return is_valid_amount(token); } static bool valid_channel_id(struct channel_id *chan_id, struct splice_script_chan **channels) { for (size_t i = 0; i < tal_count(channels); i++) if (channel_id_eq(chan_id, &channels[i]->chan_id)) return true; return false; } static struct splice_script_error *validate_and_clean(const tal_t *ctx, struct splice_script_chan **channels, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens = tal_arr(ctx, struct token *, tal_count(input)); size_t n = 0; struct channel_id *chan_ids = tal_arr(ctx, struct channel_id, 0); struct token *lease, *leaserate, *fee, *feerate; bool pay_fee_found = false; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_CHAR: case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: case TOK_COLON: case TOK_LEASERATE: case TOK_ARROW: case TOK_PIPE: case TOK_STR: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASEREQ: case TOK_DELIMITER: return new_error(ctx, INVALID_TOKEN, input[i], "validate_and_clean"); case TOK_SEGMENT: if (!is_valid_amount(input[i]->left)) return new_error(ctx, MISSING_AMOUNT_OR_WILD_OP, input[i]->left, "validate_and_clean"); if (!is_valid_amount(input[i]->right)) return new_error(ctx, MISSING_AMOUNT_OR_WILD_OP, input[i]->right, "validate_and_clean"); /* Process lease & lease rate. * ex: "0|10M@1% -> chan_id" (lease 10M at 1% fee) */ lease = input[i]->left->right; leaserate = NULL; if (lease) { if (lease->type != TOK_LEASEREQ) return new_error(ctx, INTERNAL_ERROR, lease, "validate_and_clean"); if (lease->right->type != TOK_SATS) return new_error(ctx, LEASE_AMOUNT_ZERO, lease, "validate_and_clean"); lease->amount_sat = lease->right->amount_sat; if (amount_sat_is_zero(lease->amount_sat)) return new_error(ctx, LEASE_AMOUNT_ZERO, lease->right, "validate_and_clean"); leaserate = lease->right->right; } if (leaserate) { if (leaserate->type != TOK_LEASERATE) return new_error(ctx, INTERNAL_ERROR, leaserate, "validate_and_clean"); if (leaserate->right->type != TOK_PERCENT) return new_error(ctx, MISSING_PERCENT, leaserate, "validate_and_clean"); lease->ppm = leaserate->right->ppm; } /* Process left fee & fee rate. * ex: "10M-fee@4k -> chan_id" (splice in 10M less fee) */ fee = input[i]->left->middle; feerate = NULL; if (fee) { if (fee->type != TOK_FEE) return new_error(ctx, INTERNAL_ERROR, fee, "validate_and_clean"); if (!(fee->flags & TOKEN_FLAG_FEERATE_NEGATIVE)) return new_error(ctx, LEFT_FEE_NOT_NEGATIVE, fee, "validate_and_clean"); if (pay_fee_found) return new_error(ctx, DUPLICATE_FEESTR, fee, "validate_and_clean"); pay_fee_found = true; feerate = fee->right; } if (feerate) { if (feerate->type != TOK_FEERATE) return new_error(ctx, INTERNAL_ERROR, feerate, "validate_and_clean"); if (feerate->right->type != TOK_SATS) return new_error(ctx, MISSING_AMOUNT_OP, feerate, "validate_and_clean"); fee->amount_sat = feerate->right->amount_sat; fee->str = feerate->right->str; } /* Process right fee & fee rate. * ex: "chan_id -> 10M+fee@4k" (splice out 10M + fee) */ fee = input[i]->right->middle; feerate = NULL; if (fee) { if (fee->type != TOK_FEE) return new_error(ctx, INTERNAL_ERROR, fee, "validate_and_clean"); if ((fee->flags & TOKEN_FLAG_FEERATE_NEGATIVE)) return new_error(ctx, RIGHT_FEE_NOT_POSITIVE, fee, "validate_and_clean"); if (pay_fee_found) return new_error(ctx, DUPLICATE_FEESTR, fee, "validate_and_clean"); pay_fee_found = true; feerate = fee->right; } if (feerate) { if (feerate->type != TOK_FEERATE) return new_error(ctx, INTERNAL_ERROR, feerate, "validate_and_clean"); if (feerate->right->type != TOK_SATS) return new_error(ctx, MISSING_AMOUNT_OP, feerate, "validate_and_clean"); fee->amount_sat = feerate->right->amount_sat; fee->str = feerate->right->str; } if (!is_valid_nonzero_amount(input[i]->left) && !is_valid_nonzero_amount(input[i]->right) && !lease && !fee) return new_error(ctx, ZERO_AMOUNTS, input[i]->left, "validate_and_clean"); /* Can't specify funds going into and out of into the * same segment. User should simply subtract one amount * from the other. */ if (is_valid_nonzero_amount(input[i]->left) && is_valid_nonzero_amount(input[i]->right)) return new_error(ctx, IN_AND_OUT_AMOUNTS, input[i]->left, "validate_and_clean"); /* Check the channel id is one of our channels */ if (input[i]->middle->type == TOK_CHANID && !valid_channel_id(input[i]->middle->chan_id, channels)) return new_error(ctx, CHANNEL_ID_UNRECOGNIZED, input[i]->middle, "validate_and_clean"); /* Check for duplicate channel ids */ for (size_t j = 0; j < tal_count(chan_ids); j++) if (input[i]->middle->type == TOK_CHANID && channel_id_eq(&chan_ids[j], input[i]->middle->chan_id)) return new_error(ctx, DUPLICATE_CHANID, input[i]->middle, "validate_and_clean"); /* Now add our channel id to the array */ if (input[i]->middle->type == TOK_CHANID) tal_arr_expand(&chan_ids, *input[i]->middle->chan_id); if (!is_valid_middle(input[i]->middle)) return new_error(ctx, INVALID_MIDDLE_OP, input[i]->middle, "validate_and_clean"); tokens[n++] = tal_steal(tokens, input[i]); break; } } tal_free(chan_ids); tal_free(input); tal_resize(&tokens, n); *tokens_inout = tokens; return NULL; } static struct splice_script_error *calculate_amounts(const tal_t *ctx, struct token ***tokens_inout) { struct token **input = *tokens_inout; struct token **tokens; size_t n, left_wild_index, left_wilds, right_wilds; u32 left_used_ppm; bool right_used_ppm; struct amount_sat left_total, right_total; struct token *left_wildcard_token, *left_percent_token, *wallet_token; left_wilds = 0; right_wilds = 0; left_used_ppm = 0; right_used_ppm = false; left_total = AMOUNT_SAT(0); right_total = AMOUNT_SAT(0); left_wildcard_token = NULL; left_percent_token = NULL; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_CHAR: case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: case TOK_COLON: case TOK_LEASERATE: case TOK_ARROW: case TOK_PIPE: case TOK_STR: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASEREQ: case TOK_DELIMITER: return new_error(ctx, INVALID_TOKEN, input[i], "calculate_amounts"); case TOK_SEGMENT: if (input[i]->left->type == TOK_WILDCARD) { left_wildcard_token = input[i]->left; left_wilds++; } if (input[i]->right->type == TOK_WILDCARD) right_wilds++; if (input[i]->left->type == TOK_SATS) if (!amount_sat_add(&left_total, left_total, input[i]->left->amount_sat)) return new_error(ctx, INTERNAL_ERROR, input[i]->left, "calculate_amounts"); if (input[i]->right->type == TOK_SATS) if (!amount_sat_add(&right_total, right_total, input[i]->right->amount_sat)) return new_error(ctx, INTERNAL_ERROR, input[i]->right, "calculate_amounts"); if (input[i]->left->type == TOK_PERCENT) { left_percent_token = input[i]->left; left_used_ppm += input[i]->left->ppm; if (left_used_ppm > 1000000) return new_error(ctx, LEFT_PERCENT_OVER_100, input[i]->left, "calculate_amounts"); } if (input[i]->right->type == TOK_PERCENT) right_used_ppm = true; break; } } /* Do we know precisely how much we're getting? */ if (!right_wilds && !right_used_ppm) { /* Then we can give sat amount errors */ if (amount_sat_greater(left_total, right_total)) return new_error(ctx, INSUFFICENT_FUNDS, left_wildcard_token ?: input[0], "calculate_amounts"); if (left_used_ppm && amount_sat_eq(left_total, right_total)) return new_error(ctx, PERCENT_IS_ZERO, left_percent_token ?: input[0], "calculate_amounts"); if (left_wilds && amount_sat_eq(left_total, right_total)) return new_error(ctx, WILDCARD_IS_ZERO, left_wildcard_token ?: input[0], "calculate_amounts"); } tokens = tal_arr(ctx, struct token *, tal_count(input)); n = 0; left_wild_index = 0; for (size_t i = 0; i < tal_count(input); i++) { switch(input[i]->type) { case TOK_CHAR: case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: case TOK_COLON: case TOK_LEASERATE: case TOK_ARROW: case TOK_PIPE: case TOK_STR: case TOK_SATS: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASEREQ: case TOK_DELIMITER: return new_error(ctx, INVALID_TOKEN, input[i], "calculate_amounts"); case TOK_SEGMENT: if (input[i]->left->type == TOK_WILDCARD) { input[i]->left->ppm = (1000000 - left_used_ppm) / left_wilds; /* Place remainder points into the last wildcard * spot */ if (++left_wild_index == left_wilds) input[i]->left->ppm += (1000000 - left_used_ppm) % left_wilds; } if (input[i]->right->type == TOK_WILDCARD) input[i]->right->ppm = 1000000; tokens[n++] = tal_steal(tokens, input[i]); break; } } tal_free(input); tal_resize(&tokens, n); /* Are there potential unclaimed funds? Typically we will have right * side funds but in some channel lease situations and / or user * provided funds (via user provided PSBT) we may have 0 funds coming * in aka 'on the right side'. */ if (!amount_sat_is_zero(right_total) || right_wilds || right_used_ppm) { /* Are they not already claimed by a % or wildcard? */ if (!left_wilds && left_used_ppm < 1000000) { wallet_token = new_token(tokens, TOK_SEGMENT, 0); wallet_token->left = new_token(wallet_token, TOK_WILDCARD, 0); wallet_token->middle = new_token(wallet_token, TOK_WALLET, 0); wallet_token->right = new_token(wallet_token, TOK_SATS, 0); wallet_token->left->ppm = 1000000 - left_used_ppm; tal_arr_expand(&tokens, wallet_token); } } *tokens_inout = tokens; return NULL; } static bool is_delimiter(char c) { return c == '\n' || c == ';'; } struct splice_script_error *parse_splice_script(const tal_t *ctx, const char *script, struct splice_script_chan **channels, struct splice_script_result ***result) { struct splice_script_error *error; struct token **tokens = tal_arr(ctx, struct token *, strlen(script) + 1); for (size_t i = 0; i < strlen(script); i++) { tokens[i] = new_token(tokens, is_delimiter(script[i]) ? TOK_DELIMITER : TOK_CHAR, i); tokens[i]->c = script[i]; } /* We add a delimiter on the end to make life simple. */ tokens[strlen(script)] = new_token(tokens, TOK_DELIMITER, strlen(script)); if ((error = clean_whitespace(ctx, &tokens))) return error; if ((error = find_arrows_and_strs(ctx, &tokens))) return error; if ((error = process_top_separators(ctx, &tokens))) return error; if ((error = process_2nd_separators(ctx, &tokens))) return error; if ((error = process_3rd_separators(ctx, &tokens))) return error; if ((error = type_data(ctx, channels, &tokens))) return error; if ((error = compress_top_operands(ctx, &tokens))) return error; if ((error = compress_2nd_operands(ctx, &tokens))) return error; if ((error = resolve_channel_ids(ctx, channels, &tokens))) return error; if ((error = make_segments(ctx, &tokens))) return error; if ((error = expand_multichans(ctx, &tokens))) return error; if ((error = validate_and_clean(ctx, channels, &tokens))) return error; if ((error = calculate_amounts(ctx, &tokens))) return error; #if SCRIPT_DUMP_TOKENS return debug_dump(ctx, tokens); #endif #if SCRIPT_DUMP_SEGMENTS return dump_segments(ctx, tokens); #endif *result = tal_arr(ctx, struct splice_script_result*, tal_count(tokens)); for (size_t i = 0; i < tal_count(tokens); i++) { (*result)[i] = talz(*result, struct splice_script_result); struct splice_script_result *itr = (*result)[i]; struct token *lease = tokens[i]->left->right; struct token *fee = tokens[i]->left->middle ? tokens[i]->left->middle : tokens[i]->right->middle; if (lease) { itr->lease_sat = lease->amount_sat; itr->lease_max_ppm = lease->ppm; } itr->in_sat = tokens[i]->left->amount_sat; itr->in_ppm = tokens[i]->left->ppm; if (tokens[i]->middle->type == TOK_CHANID) itr->channel_id = tal_dup(itr, struct channel_id, tokens[i]->middle->chan_id); else if (tokens[i]->middle->type == TOK_BTCADDR) itr->bitcoin_address = tal_strdup(itr, tokens[i]->middle->str); else if (tokens[i]->middle->type == TOK_WALLET) itr->onchain_wallet = true; else return new_error(ctx, INTERNAL_ERROR, tokens[i], "splice_script_result"); itr->out_sat = tokens[i]->right->amount_sat; itr->out_ppm = tokens[i]->right->ppm; if (tokens[i]->right->type == TOK_WILDCARD) itr->out_ppm = UINT32_MAX; if (fee) { itr->pays_fee = true; if (!parse_feerate(fee, &itr->feerate_per_kw)) return new_error(ctx, INVALID_FEERATE, fee->right ? fee->right->right ?: fee->right : fee, "splice_script_result"); } } return NULL; } void splice_to_json(const tal_t *ctx, struct splice_script_result **splice, struct json_stream *js) { json_object_start(js, NULL); json_array_start(js, "splice"); for (size_t i = 0; i < tal_count(splice); i++) { json_object_start(js, NULL); if (!amount_sat_is_zero(splice[i]->lease_sat)) { json_object_start(js, "lease_request"); json_add_amount_sat_msat(js, "amount_msat", splice[i]->lease_sat); json_add_u32(js, "max_ppm", splice[i]->lease_max_ppm); json_object_end(js); } if (!amount_sat_is_zero(splice[i]->in_sat) || splice[i]->in_ppm) { json_object_start(js, "into_destination"); if (!amount_sat_is_zero(splice[i]->in_sat)) json_add_amount_sat_msat(js, "amount_msat", splice[i]->in_sat); if (splice[i]->in_ppm) json_add_u32(js, "ppm", splice[i]->in_ppm); json_object_end(js); } json_object_start(js, "destination"); if (splice[i]->channel_id) json_add_channel_id(js, "channel_id", splice[i]->channel_id); if (splice[i]->bitcoin_address) json_add_string(js, "bitcoin_address", splice[i]->bitcoin_address); if (splice[i]->onchain_wallet) json_add_bool(js, "onchain_wallet", true); json_object_end(js); if (!amount_sat_is_zero(splice[i]->out_sat) || splice[i]->out_ppm) { json_object_start(js, "outof_destination"); if (!amount_sat_is_zero(splice[i]->out_sat)) json_add_amount_sat_msat(js, "amount_msat", splice[i]->out_sat); if (splice[i]->out_ppm) json_add_u32(js, "ppm", splice[i]->out_ppm); json_object_end(js); } if (splice[i]->pays_fee || splice[i]->feerate_per_kw) { json_object_start(js, "fee"); if (splice[i]->pays_fee) json_add_bool(js, "pays_fee", splice[i]->pays_fee); if (splice[i]->feerate_per_kw) json_add_u32(js, "feerate_per_kw", splice[i]->feerate_per_kw); json_object_end(js); } json_object_end(js); } json_array_end(js); json_object_end(js); } static bool json_to_msat_to_sat(const char *buffer, const jsmntok_t *tok, struct amount_sat *sat) { struct amount_msat msat; if (!json_to_msat(buffer, tok, &msat)) return false; return amount_msat_to_sat(sat, msat); } bool json_to_splice(const tal_t *ctx, const char *buffer, const jsmntok_t *tok, struct splice_script_result ***result) { const jsmntok_t *splice; const jsmntok_t *itr; size_t i; splice = json_get_member(buffer, tok, "splice"); if (!splice || splice->type != JSMN_ARRAY) return false; *result = tal_arr(ctx, struct splice_script_result*, 0); json_for_each_arr(i, itr, splice) { const jsmntok_t *lease, *in, *dest, *out, *fee, *obj; struct splice_script_result *ele = talz(*result, struct splice_script_result); if ((lease = json_get_member(buffer, itr, "lease_request"))) { if ((obj = json_get_member(buffer, lease, "amount_msat"))) { if (!json_to_msat_to_sat(buffer, obj, &ele->lease_sat)) return false; } if ((obj = json_get_member(buffer, lease, "max_ppm"))) { if (!json_to_u32(buffer, obj, &ele->lease_max_ppm)) return false; } } if ((in = json_get_member(buffer, itr, "into_destination"))) { if ((obj = json_get_member(buffer, in, "amount_msat"))) { if (!json_to_msat_to_sat(buffer, obj, &ele->in_sat)) return false; } if ((obj = json_get_member(buffer, in, "ppm"))) { if (!json_to_u32(buffer, obj, &ele->in_ppm)) return false; } } if ((dest = json_get_member(buffer, itr, "destination"))) { if ((obj = json_get_member(buffer, dest, "channel_id"))) { ele->channel_id = tal(ele, struct channel_id); if (!json_to_channel_id(buffer, obj, ele->channel_id)) return false; } if ((obj = json_get_member(buffer, dest, "bitcoin_address"))) { ele->bitcoin_address = json_strdup(ele, buffer, obj); if (!ele->bitcoin_address) return false; } if ((obj = json_get_member(buffer, dest, "onchain_wallet"))) { if (!json_to_bool(buffer, obj, &ele->onchain_wallet)) return false; } } if ((out = json_get_member(buffer, itr, "outof_destination"))) { if ((obj = json_get_member(buffer, out, "amount_msat"))) { if (!json_to_msat_to_sat(buffer, obj, &ele->out_sat)) return false; } if ((obj = json_get_member(buffer, out, "ppm"))) { if (!json_to_u32(buffer, obj, &ele->out_ppm)) return false; } } if ((fee = json_get_member(buffer, itr, "fee"))) { if ((obj = json_get_member(buffer, fee, "pays_fee"))) { if (!json_to_bool(buffer, obj, &ele->pays_fee)) return false; } if ((obj = json_get_member(buffer, fee, "feerate_per_kw"))) { if (!json_to_u32(buffer, obj, &ele->feerate_per_kw)) return false; } } tal_arr_expand(result, ele); } return true; } static char *ppm_to_str(const tal_t *ctx, u32 ppm) { if (ppm == UINT_MAX) return tal_fmt(ctx, "max"); if (ppm % 10000) return tal_fmt(ctx, "%u.%04u%%", ppm / 10000, ppm % 10000); return tal_fmt(ctx, "%u%%", ppm / 10000); } char *splice_to_string(const tal_t *ctx, struct splice_script_result *result) { const char *into_prefix, *fee_str; char *str = tal_strdup(ctx, ""); into_prefix = ""; fee_str = ""; if (!amount_sat_is_zero(result->lease_sat)) { into_prefix = "and "; tal_append_fmt(&str, "lease %s ", fmt_amount_sat(ctx, result->lease_sat)); if (result->lease_max_ppm) tal_append_fmt(&str, "(max fee %s) ", ppm_to_str(ctx, result->lease_max_ppm)); } if (result->pays_fee) fee_str = " less fee"; if (result->feerate_per_kw) fee_str = tal_fmt(tmpctx, " less fee (%u/kw)", result->feerate_per_kw); if (!amount_sat_is_zero(result->in_sat)) tal_append_fmt(&str, "%sput %s%s into ", into_prefix, fmt_amount_sat(ctx, result->in_sat), fee_str); if (result->in_ppm) tal_append_fmt(&str, "%sput %s%s of rest into ", into_prefix, ppm_to_str(ctx, result->in_ppm), fee_str); if (result->channel_id) tal_append_fmt(&str, "%s", tal_hexstr(tmpctx, result->channel_id, sizeof(struct channel_id))); if (result->bitcoin_address) tal_append_fmt(&str, "%s", result->bitcoin_address); if (result->onchain_wallet) tal_append_fmt(&str, "wallet"); fee_str = ""; if (result->pays_fee) fee_str = " plus fee"; if (result->feerate_per_kw) fee_str = tal_fmt(tmpctx, " plus fee (%u/kw, %.02f" " sat/vB) ", result->feerate_per_kw, 4 * result->feerate_per_kw / 1000.0f); if (!amount_sat_is_zero(result->out_sat)) tal_append_fmt(&str, " withdraw %s%s", fmt_amount_sat(ctx, result->out_sat), fee_str); if (result->out_ppm) tal_append_fmt(&str, " withdraw %s%s", ppm_to_str(ctx, result->out_ppm), fee_str); if (amount_sat_is_zero(result->in_sat) && !result->in_ppm && amount_sat_is_zero(result->out_sat) && !result->out_ppm && result->pays_fee) { tal_append_fmt(&str, " withdraw fee"); if (result->feerate_per_kw) tal_append_fmt(&str, " (%u/kw, %.02f sat/vB)", result->feerate_per_kw, 4 * result->feerate_per_kw / 1000.0f); } return str; } char *splicearr_to_string(const tal_t *ctx, struct splice_script_result **result) { char *str = tal_strdup(ctx, ""); for (size_t i = 0; i < tal_count(result); i++) tal_append_fmt(&str, "%s\n", splice_to_string(str, result[i])); return str; }