diff --git a/ccan/ccan/io/poll.c b/ccan/ccan/io/poll.c index 656cc0e3a33c..c4cbaee85678 100644 --- a/ccan/ccan/io/poll.c +++ b/ccan/ccan/io/poll.c @@ -433,6 +433,7 @@ void *io_loop(struct timers *timers, struct timer **expired) fairness_counter++; for (size_t rotation = 0; rotation < num_fds && !io_loop_return; rotation++) { + socklen_t errno_len = sizeof(errno); struct io_conn *c; int events; @@ -469,8 +470,18 @@ void *io_loop(struct timers *timers, struct timer **expired) r--; io_ready(c, events); } else if (events & (POLLHUP|POLLNVAL|POLLERR)) { + /* On `connect` failure, Linux typically + * returns POLLIN|POLLERR. MacOS returns either + * POLLHUP|POLLERR or POLLOUT|POLLHUP depending + * on version, setting the socket error to + * ECONNREFUSED. */ r--; - errno = EBADF; + /* Get fd's specific error to find Mac's + * ECONNREFUSED, among others */ + if(getsockopt(fds[i]->fd, SOL_SOCKET, SO_ERROR, + &errno, &errno_len) == -1) { + errno = EBADF; + } io_close(c); } } diff --git a/channeld/channeld.c b/channeld/channeld.c index ed4cc6e8a733..dc936cdb726c 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -3901,7 +3901,7 @@ static void resume_splice_negotiation(struct peer *peer, txsig_tlvs = tlv_txsigs_tlvs_new(tmpctx); txsig_tlvs->shared_input_signature = &splice_sig.s; - /* DTODO: is this finalize call required? */ + /* This will delete the signatures from psbt and fill the witness if they're valid */ psbt_finalize(current_psbt); outws = psbt_to_witnesses(tmpctx, current_psbt, diff --git a/common/splice_script.c b/common/splice_script.c index 209f5b0b6dce..a100afee408b 100644 --- a/common/splice_script.c +++ b/common/splice_script.c @@ -9,10 +9,12 @@ #include #include #include +#include #define SCRIPT_DUMP_TOKENS 0 #define SCRIPT_DUMP_SEGMENTS 0 +/* Basic primitives */ #define ARROW_SYMBOL "->" #define PIPE_SYMBOL '|' #define AT_SYMBOL '@' @@ -22,10 +24,24 @@ #define PLUS_SYMBOL '+' #define MINUS_SYMBOL '-' #define FEE_SYMBOL "fee" - +#define OPAREN_SYMBOL '(' +#define CPAREN_SYMBOL ')' +#define DOT_SYMBOL '.' + +/* Script keywords */ +#define BALANCE_SYMBOL "balance" +#define PEER_SYMBOL "peer" +#define CHAN_SYMBOL "chan" +#define NEW_SYMBOL "new" +#define PRIVATE_SYMBOL "private" +#define CLOSE_TO_SYMBOL "close_to" +#define COMMIT_FEERATE_SYMBOL "commit_feerate" +#define LEASE_SYMBOL "lease" + +/* Complex primitve regexes */ #define PERCENT_REGEX "^([0-9]*)[.]?([0-9]*)%$" -#define Q_REGEX "^\\?$" -#define WILD_REGEX "^\\*$" +#define Q_REGEX "^\\?|any|first|one$" +#define WILD_REGEX "^\\*|all$" #define CHANID_REGEX "^[0-9A-Fa-f]{64}$" #define NODEID_REGEX "^0[23][0-9A-Fa-f]{62}$" #define WALLET_REGEX "^wallet$" @@ -33,16 +49,34 @@ #define SATM_REGEX "^([0-9]*)[.]?([0-9]*)[Mm]$" #define SATK_REGEX "^([0-9]*)[.]?([0-9]*)[Kk]$" +/* Terminal width of debug output */ #define CODE_SNIPPET_PADDING 80 /* Minimum # of matching charaters to autocomplete nodeid or chanid */ #define NODEID_MIN_CHARS 4 #define CHANID_MIN_CHARS 4 +/* Shorthand for failing if `parameter` is set. */ +#define CHECK_ASSIGN_TOKEN(token, parameter, error, section) \ + do { \ + if (token->parameter) \ + return new_error(ctx, error, token, section); \ + } while(0) + +#define CHECK_ASSIGN_LEFT(token, section) \ + CHECK_ASSIGN_TOKEN(token, left, LEFT_ALREADY_SET, section) +#define CHECK_ASSIGN_MIDDLE(token, section) \ + CHECK_ASSIGN_TOKEN(token, middle, MIDDLE_ALREADY_SET, section) +#define CHECK_ASSIGN_RIGHT(token, section) \ + CHECK_ASSIGN_TOKEN(token, right, RIGHT_ALREADY_SET, section) + /* Token types from simplest to most complex. */ enum token_type { - TOK_CHAR, + + TOK_CHAR, /* Everything starts as a char */ TOK_DELIMITER, /* Newline or semicolon. Guaranteed one at end. */ + + /* Basic primitives */ TOK_ARROW, TOK_STR, TOK_PIPE, /* lease separator "|", ex 5M|3M (add 5M, lease 3M) */ @@ -50,8 +84,14 @@ enum token_type { TOK_ATSYM, /* lease rate separator "@", ex 3M@2% */ TOK_PLUS, TOK_MINUS, + + /* Complex primitives */ TOK_PERCENT, /* ie "80%" */ TOK_SATS, /* ie 8M or 0 */ + TOK_NUMBER, /* Floating point number (stored as double) */ + TOK_OPAREN, /* open parentheses */ + TOK_CPAREN, /* close parentheses */ + TOK_DOT, TOK_QUESTION, /* ie "?" */ TOK_WILDCARD, /* ie "*" */ TOK_FEE, /* ie the word "fee" */ @@ -60,6 +100,18 @@ enum token_type { TOK_FEERATE, /* The fee rate */ TOK_NODEID, TOK_BTCADDR, + + /* Script keywords */ + TOK_BALANCE, + TOK_PEER, + TOK_CHAN, + TOK_NEW, + TOK_PRIVATE, + TOK_CLOSE_TO, + TOK_COMMIT_FEERATE, + TOK_LEASE, + + /* Compiler introduced types */ TOK_CHANQUERY, /* ie nodeid:? */ TOK_MULTI_CHANID, /* Matches stored in ->right */ TOK_LEASEREQ, @@ -69,16 +121,68 @@ enum token_type { #define TOKEN_FLAG_FEERATE_NEGATIVE 0x01 +/* We turn the script into an array of tokens. The intial parse creates an array + * of tokens of type TOK_CHAR with `c` set. As we progress in proccessing the + * script we shrink the token array into less tokens where each token is + * logically more descriptive tokens. + * + * While the array trends smaller, some operations do actually increase the + * array size. For example TOK_MULTI_CHANID can match multiple channels, which + * results in more tokens being added. + * + * Eventually tokens will start getting attached to other tokens. For example + * a 'feerate' will get attached to an `amount`, which will itself get attached + * to a 'channel'. In these cases the most significant token stays in the token + * array and the detail tokens get attached as children via `left`, `middle` and + * `right`. + * + * Finally the tokens get compressed all the way down into TOK_SEGMENT which + * represents a single action (ie. splice, open channel, send funds to X). + * + * Each TOK_SEGMENT is turned into a splice_script_result which describes to the + * caller the specific actions to be performed. + */ struct token { enum token_type type; size_t script_index; /* For error messages */ + + /* Generally one of the values below will be set. Sometimes extra values + * are left set to ease in debugging. `type` will tell you which value + * to expect to be filled. */ char c; char *str; u32 ppm; struct amount_sat amount_sat; struct node_id *node_id; struct channel_id *chan_id; + double number; + + /* Tokens can refer to other tokens. For example "a.b" will result in: + * a.right = b + * + * "a(b)" will result in + * a.middle = b + * + * "a -> b -> c" will result in: + * b.left = a + * b.right = c + * + * These relationships can have arbitrary depth, for instance: + * a.b.c.d(e) results in: + * a.right = b + * b.right = c + * c.right = d + * d.middle = e + * + * Whenever a token takes another as any of these fields it *must* take + * over it's memory (ie. `tal_steal`). + * + * Circular token reference are not allowed (ie. KISS). + */ struct token *left, *middle, *right; + + /* Currently only used for tracking negative feerate values. Available + * for future use. */ u32 flags; }; @@ -93,6 +197,7 @@ static struct token *new_token(const tal_t *ctx, enum token_type token_type, token->str = NULL; token->ppm = 0; token->amount_sat = AMOUNT_SAT(0); + token->number = 0; token->node_id = NULL; token->chan_id = NULL; token->left = NULL; @@ -103,7 +208,6 @@ static struct token *new_token(const tal_t *ctx, enum token_type token_type, return token; } -#if SCRIPT_DUMP_TOKENS || SCRIPT_DUMP_SEGMENTS static const char *token_type_str(enum token_type type) { switch (type) { @@ -117,7 +221,11 @@ static const char *token_type_str(enum token_type type) case TOK_MINUS: return "TOK_MINUS"; case TOK_COLON: return "TOK_COLON"; case TOK_SATS: return "TOK_SATS"; + case TOK_NUMBER: return "TOK_NUMBER"; case TOK_PERCENT: return "TOK_PERCENT"; + case TOK_OPAREN: return "TOK_OPAREN"; + case TOK_CPAREN: return "TOK_CPAREN"; + case TOK_DOT: return "TOK_DOT"; case TOK_QUESTION: return "TOK_QUESTION"; case TOK_WILDCARD: return "TOK_WILDCARD"; case TOK_FEE: return "TOK_FEE"; @@ -126,6 +234,14 @@ static const char *token_type_str(enum token_type type) case TOK_FEERATE: return "TOK_FEERATE"; case TOK_NODEID: return "TOK_NODEID"; case TOK_BTCADDR: return "TOK_BTCADDR"; + case TOK_BALANCE: return "TOK_BALANCE"; + case TOK_PEER: return "TOK_PEER"; + case TOK_CHAN: return "TOK_CHAN"; + case TOK_NEW: return "TOK_NEW"; + case TOK_PRIVATE: return "TOK_PRIVATE"; + case TOK_CLOSE_TO: return "TOK_CLOSE_TO"; + case TOK_COMMIT_FEERATE: return "TOK_COMMIT_FEERATE"; + case TOK_LEASE: return "TOK_LEASE"; case TOK_CHANQUERY: return "TOK_CHANQUERY"; case TOK_MULTI_CHANID: return "TOK_MULTI_CHANID"; case TOK_LEASEREQ: return "TOK_LEASEREQ"; @@ -136,6 +252,7 @@ static const char *token_type_str(enum token_type type) return NULL; } +#if SCRIPT_DUMP_TOKENS || SCRIPT_DUMP_SEGMENTS static void dump_token_shallow(char **str, struct token *token, char *prefix) { const char *tmp; @@ -166,7 +283,8 @@ static void dump_token_shallow(char **str, struct token *token, char *prefix) if (token->chan_id) tal_append_fmt(str, " chan_id:%s", - tal_hexstr(tmpctx, token->chan_id, sizeof(struct channel_id))); + tal_hexstr(tmpctx, token->chan_id, + sizeof(struct channel_id))); if (token->ppm) tal_append_fmt(str, " ppm:%u", token->ppm); @@ -177,6 +295,9 @@ static void dump_token_shallow(char **str, struct token *token, char *prefix) tal_free(tmp); } + if (token->type == TOK_NUMBER) + tal_append_fmt(str, " number:%g", token->number); + if (token->flags) tal_append_fmt(str, " flags:%u", token->flags); } @@ -197,7 +318,7 @@ static int dump_token(char **str, struct token *token, int indent, char *prefix) tal_append_fmt(str, "\n"); if (token->middle) - dump_token(str, token->middle, indent, "m "); + dump_token(str, token->middle, indent + 1, "m "); if (token->right) dump_token(str, token->right, indent + 1, "r "); @@ -223,6 +344,20 @@ static struct splice_script_error *debug_dump(const tal_t *ctx, #endif /* SCRIPT_DUMP_TOKENS */ #if SCRIPT_DUMP_SEGMENTS +static void append_token_modifiers(char **str, struct token *token) +{ + if (!token) + return; + + if (token->middle) + tal_append_fmt(str, "(%s)", token->middle->str ?: ""); + + if (token->right) { + tal_append_fmt(str, ".%s", token->right->str ?: ""); + append_token_modifiers(str, token->right); + } +} + static struct splice_script_error *dump_segments(const tal_t *ctx, struct token **tokens) { @@ -237,11 +372,18 @@ static struct splice_script_error *dump_segments(const tal_t *ctx, if (tokens[i]->type == TOK_SEGMENT) { dump_token_shallow(&error->message, tokens[i]->left, ""); + append_token_modifiers(&error->message, + tokens[i]->left); dump_token_shallow(&error->message, tokens[i]->middle, " -> "); - if (tokens[i]->right) + append_token_modifiers(&error->message, + tokens[i]->middle); + if (tokens[i]->right) { dump_token_shallow(&error->message, tokens[i]->right, " -> "); + append_token_modifiers(&error->message, + tokens[i]->right); + } } else { tal_append_fmt(&error->message, "Invalid token!! "); @@ -264,7 +406,10 @@ static struct splice_script_error *new_error_offset(const tal_t *ctx, error->type = type; error->script_index = token->script_index + index_offset; - error->message = tal_strdup(error, ""); + error->message = tal_fmt(error, "%s", + type == INVALID_TOKEN + ? token_type_str(token->type) + : ""); error->phase = phase; return error; @@ -335,6 +480,9 @@ char *fmt_splice_script_compiler_error(const tal_t *ctx, case INVALID_TOKEN: return tal_fmt(ctx, "Invalid token error\n%s", context_snippet(ctx, script, error)); + case UNRECOGNIZED_TOKEN: + return tal_fmt(ctx, "Unrecognized keyword or value\n%s", + context_snippet(ctx, script, error)); case DEBUG_DUMP: return tal_fmt(ctx, "Token Dump:\n%s", error->message); case TOO_MANY_PIPES: @@ -386,6 +534,10 @@ char *fmt_splice_script_compiler_error(const tal_t *ctx, case CHANQUERY_TYPEERROR: return tal_fmt(ctx, "Channel query has invalid type(s)\n%s", context_snippet(ctx, script, error)); + case CHANQUERY_PEERID_IS_CHANID: + return tal_fmt(ctx, "Invalid peer id (this query matches a" + " channel but we expect a peer)\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)); @@ -505,6 +657,57 @@ char *fmt_splice_script_compiler_error(const tal_t *ctx, return tal_fmt(ctx, "Valid feerate must be only number digits" " and no other characters\n%s", context_snippet(ctx, script, error)); + case DOUBLE_CPAREN: + return tal_fmt(ctx, "Not allowed to nest parentheses\n%s", + context_snippet(ctx, script, error)); + case MULTI_ITEMS_IN_PAREN: + return tal_fmt(ctx, "Only one item allowed between" + " parentheses\n%s", + context_snippet(ctx, script, error)); + case MISSING_CLOSE_PAREN: + return tal_fmt(ctx, "Missing an closing parentheses\n%s", + context_snippet(ctx, script, error)); + case PAREN_ON_NOTHING: + return tal_fmt(ctx, "Parentheses must come after something\n%s", + context_snippet(ctx, script, error)); + case PAREN_ON_DOT: + return tal_fmt(ctx, "Parentheses can't come after a dot\n%s", + context_snippet(ctx, script, error)); + case DOT_ON_DOT: + return tal_fmt(ctx, "Dot can't come after another dot\n%s", + context_snippet(ctx, script, error)); + case UNOPENED_PAREN: + return tal_fmt(ctx, "Parentheses was never opened\n%s", + context_snippet(ctx, script, error)); + case DOT_ON_NOTHING: + return tal_fmt(ctx, "Dot operator must come after something\n%s", + context_snippet(ctx, script, error)); + case DOT_OF_NOTHING: + return tal_fmt(ctx, "Dot operator must have something after" + " it\n%s", context_snippet(ctx, script, error)); + case LEFT_ALREADY_SET: + return tal_fmt(ctx, "Trying to left attach an element that" + " already has something attached there.\n%s", + context_snippet(ctx, script, error)); + case MIDDLE_ALREADY_SET: + return tal_fmt(ctx, "Trying to middle attach an element that" + " already has something attached there.\n%s", + context_snippet(ctx, script, error)); + case RIGHT_ALREADY_SET: + return tal_fmt(ctx, "Trying to right attach an element that" + " already has something attached there.\n%s", + context_snippet(ctx, script, error)); + case NODEID_NOT_FOUND: + return tal_fmt(ctx, "Unable to resolve node id for node query" + "\n%s", context_snippet(ctx, script, error)); + case NODE_NEEDS_RIGHT: + return tal_fmt(ctx, "Node query needs to be modified by adding" + " something to the right, for example .chan() or" + " .new()\n%s", + context_snippet(ctx, script, error)); + case NODE_UNRECOGNIZED_RIGHT: + return tal_fmt(ctx, "Node is followed by unrecognized modifier." + "\n%s", context_snippet(ctx, script, error)); } return NULL; @@ -515,6 +718,12 @@ static bool is_whitespace(char c) return cisspace(c); } +static bool is_digit(char c) +{ + return cisdigit(c); +} + +/* Removes all whitespace */ static struct splice_script_error *clean_whitespace(const tal_t *ctx, struct token ***tokens_inout) { @@ -548,6 +757,13 @@ static char *find_suffix(char *str, char *suffix) return NULL; } +/* Eats all "->"s, and CHARs, turning them into TOK_STRs. + * Input must end in a TOK_DELIMITER + * + * Result: + * TOK_STR TOK_ARROW TOK_STR TOK_DELIMITER + * TOK_STR TOK_ARROW TOK_STR TOK_DELIMITER... + */ static struct splice_script_error *find_arrows_and_strs(const tal_t *ctx, struct token ***tokens_inout) { @@ -596,8 +812,12 @@ static struct splice_script_error *find_arrows_and_strs(const tal_t *ctx, case TOK_MINUS: case TOK_COLON: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: @@ -605,6 +825,14 @@ static struct splice_script_error *find_arrows_and_strs(const tal_t *ctx, case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASERATE: @@ -624,6 +852,11 @@ static struct splice_script_error *find_arrows_and_strs(const tal_t *ctx, return NULL; } +/* Eats all "|:" turning them into tokens. + * + * Result: + * TOK_STR TOK_PIPE TOK_STR TOK_COLON... + */ static struct splice_script_error *process_top_separators(const tal_t *ctx, struct token ***tokens_inout) { @@ -703,8 +936,12 @@ static struct splice_script_error *process_top_separators(const tal_t *ctx, case TOK_MINUS: case TOK_COLON: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: @@ -712,6 +949,14 @@ static struct splice_script_error *process_top_separators(const tal_t *ctx, case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASERATE: @@ -806,8 +1051,12 @@ static struct splice_script_error *process_2nd_separators(const tal_t *ctx, case TOK_PLUS: case TOK_MINUS: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: @@ -815,6 +1064,14 @@ static struct splice_script_error *process_2nd_separators(const tal_t *ctx, case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASERATE: @@ -885,8 +1142,12 @@ static struct splice_script_error *process_3rd_separators(const tal_t *ctx, break; case TOK_ATSYM: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: @@ -894,6 +1155,14 @@ static struct splice_script_error *process_3rd_separators(const tal_t *ctx, case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASERATE: @@ -1006,8 +1275,9 @@ static bool autocomplete_node_id(struct token *token, 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)) + if (channels[i]->chan_id + && len <= sizeof(channels[i]->chan_id->id) + && memeq(candidate.k, len, channels[i]->chan_id->id, len)) *chan_id_overmatch = true; } @@ -1042,14 +1312,15 @@ static bool autocomplete_chan_id(struct token *token, 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)) { + if (channels[i]->chan_id + && 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)) { + if (match && !channel_id_eq(match, channels[i]->chan_id)) { *multiple_chans = true; return true; } - match = &channels[i]->chan_id; + match = channels[i]->chan_id; } /* nodeid query must *not* match any node ids */ if (len <= sizeof(channels[i]->node_id.k) @@ -1066,6 +1337,227 @@ static bool autocomplete_chan_id(struct token *token, return true; } +/* Eats all "()." turning them into tokens. + * + * Example: new.feerate(1.1) + * TOK_STR TOK_DOT TOK_STR TOK_OPAREN TOK_SATS TOK_CPAREN + */ +static struct splice_script_error *process_paren_dot_separators(const tal_t *ctx, + struct token ***tokens_inout) +{ + const int tokenmap[] = + { + OPAREN_SYMBOL, TOK_OPAREN, + CPAREN_SYMBOL, TOK_CPAREN, + DOT_SYMBOL, TOK_DOT, + }; + const int tokencount = sizeof(tokenmap) / sizeof(tokenmap[0]); + enum token_type token_type, last_token; + struct token **input = *tokens_inout; + struct token **tokens = tal_arr(ctx, struct token *, 0); + struct token *token; + size_t script_index; + char *start, *itr; + + 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], + "paren_dot_separators"); + case TOK_DELIMITER: + case TOK_ARROW: + case TOK_PIPE: + case TOK_SATS: + case TOK_NUMBER: + case TOK_PERCENT: + case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: + case TOK_WILDCARD: + case TOK_PLUS: + case TOK_MINUS: + case TOK_FEE: + case TOK_CHANID: + case TOK_WALLET: + case TOK_NODEID: + case TOK_BTCADDR: + case TOK_COLON: + case TOK_ATSYM: + tal_arr_expand(&tokens, + tal_steal(tokens, input[i])); + break; + case TOK_STR: + start = itr = input[i]->str; + last_token = TOK_CHAR; + while (*itr) { + token_type = TOK_CHAR; + for (size_t j = 0; j < tokencount; j += 2) { + if (*itr == tokenmap[j]) + token_type = tokenmap[j + 1]; + } + if (token_type == TOK_CHAR) { + itr++; + continue; + } + /* Turn off dot parsing inside parens */ + if (token_type == TOK_DOT + && last_token == TOK_OPAREN) { + itr++; + continue; + } + /* Turn off dot parsing on numbers */ + if (token_type == TOK_DOT + && itr > start + && is_digit(itr[-1])) { + itr++; + continue; + } + last_token = token_type; + + /* Calc script_index for `str` */ + script_index = input[i]->script_index; + script_index += (start - input[i]->str); + + /* Add any string before the token to result */ + if (itr > start) { + *itr = 0; + token = new_token(tokens, + TOK_STR, + script_index); + token->str = tal_strdup(token, start); + tal_arr_expand(&tokens, token); + } + + /* One character forward for matching token */ + script_index++; + + /* Add the matching token */ + tal_arr_expand(&tokens, + new_token(tokens, + token_type, + script_index)); + + /* Adjust start to be just after the token */ + start = ++itr; + } + + /* Add any remaining string to result */ + if (itr > start) { + token = new_token(tokens, + TOK_STR, + script_index); + token->str = tal_strdup(token, start); + tal_arr_expand(&tokens, token); + } + break; + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: + 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], + "paren_dot_separators"); + } + } + + tal_free(input); + *tokens_inout = tokens; + return NULL; +} + +/* Eats all script keyword methods turning them into tokens. + * + * Example: new.commit_feerate(1.1) + * TOK_NEW TOK_DOT TOK_COMMIT_FEERATE TOK_OPAREN TOK_SATS TOK_CPAREN + */ +static struct splice_script_error *process_keywords(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], + "type_data"); + case TOK_DELIMITER: + case TOK_ARROW: + case TOK_PIPE: + case TOK_COLON: + case TOK_ATSYM: + case TOK_PLUS: + case TOK_MINUS: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: + tokens[n++] = tal_steal(tokens, input[i]); + break; + case TOK_STR: + if (0 == strcmp(input[i]->str, BALANCE_SYMBOL)) + input[i]->type = TOK_BALANCE; + if (0 == strcmp(input[i]->str, PEER_SYMBOL)) + input[i]->type = TOK_PEER; + if (0 == strcmp(input[i]->str, CHAN_SYMBOL)) + input[i]->type = TOK_CHAN; + if (0 == strcmp(input[i]->str, NEW_SYMBOL)) + input[i]->type = TOK_NEW; + if (0 == strcmp(input[i]->str, PRIVATE_SYMBOL)) + input[i]->type = TOK_PRIVATE; + if (0 == strcmp(input[i]->str, CLOSE_TO_SYMBOL)) + input[i]->type = TOK_CLOSE_TO; + if (0 == strcmp(input[i]->str, COMMIT_FEERATE_SYMBOL)) + input[i]->type = TOK_COMMIT_FEERATE; + if (0 == strcmp(input[i]->str, LEASE_SYMBOL)) + input[i]->type = TOK_LEASE; + tokens[n++] = tal_steal(tokens, input[i]); + break; + case TOK_SATS: + case TOK_NUMBER: + 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_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: + 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 *type_data(const tal_t *ctx, struct splice_script_chan **channels, struct token ***tokens_inout) @@ -1074,6 +1566,8 @@ static struct splice_script_error *type_data(const tal_t *ctx, struct token **tokens = tal_arr(ctx, struct token *, tal_count(input)); char *whole, *decimal; char *sat_candidate; + char *endptr; + double number; struct amount_sat amount_sat; bool multiple = false; bool overmatch = false; @@ -1091,6 +1585,17 @@ static struct splice_script_error *type_data(const tal_t *ctx, case TOK_ATSYM: case TOK_PLUS: case TOK_MINUS: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: tokens[n++] = tal_steal(tokens, input[i]); break; case TOK_STR: @@ -1197,7 +1702,6 @@ static struct splice_script_error *type_data(const tal_t *ctx, "type_data"); input[i]->type = TOK_CHANID; } else { - /* Parse shorthand sat formats */ sat_candidate = input[i]->str; @@ -1231,6 +1735,10 @@ static struct splice_script_error *type_data(const tal_t *ctx, + atoll(decimal))); } + errno = 0; + number = strtod(input[i]->str, &endptr); + + /* Try to parse it as a sat first */ if (parse_amount_sat(&amount_sat, sat_candidate, strlen(sat_candidate))) { input[i]->type = TOK_SATS; @@ -1238,11 +1746,13 @@ static struct splice_script_error *type_data(const tal_t *ctx, } 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"); + } else if (endptr != input[i]->str + && *endptr == '\0' && !errno) { + + /* Fall back to a double if it couldnt + * be parsed as a sat */ + input[i]->type = TOK_NUMBER; + input[i]->number = number; } if (sat_candidate != input[i]->str) @@ -1252,6 +1762,7 @@ static struct splice_script_error *type_data(const tal_t *ctx, tokens[n++] = tal_steal(tokens, input[i]); break; case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: case TOK_WILDCARD: @@ -1294,8 +1805,20 @@ static struct splice_script_error *compress_top_operands(const tal_t *ctx, case TOK_PIPE: case TOK_STR: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_WILDCARD: case TOK_PLUS: case TOK_MINUS: @@ -1313,6 +1836,9 @@ static struct splice_script_error *compress_top_operands(const tal_t *ctx, input[i]->type = tokens[n-1]->type == TOK_FEE ? TOK_FEERATE : TOK_LEASERATE; + CHECK_ASSIGN_RIGHT(input[i], "operands"); + CHECK_ASSIGN_RIGHT(tokens[n-1], "operands"); + input[i]->right = tal_steal(input[i], input[i+1]); tokens[n-1]->right = tal_steal(tokens[n-1], input[i]); i++; @@ -1321,6 +1847,9 @@ static struct splice_script_error *compress_top_operands(const tal_t *ctx, if (!n || i + 1 >= tal_count(input)) return new_error(ctx, INVALID_TOKEN, input[i], "operands"); + CHECK_ASSIGN_LEFT(input[i], "operands"); + CHECK_ASSIGN_RIGHT(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]); @@ -1362,8 +1891,20 @@ static struct splice_script_error *compress_2nd_operands(const tal_t *ctx, case TOK_ARROW: case TOK_STR: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_WILDCARD: case TOK_CHANID: case TOK_WALLET: @@ -1391,6 +1932,7 @@ static struct splice_script_error *compress_2nd_operands(const tal_t *ctx, if (!n) return new_error(ctx, INVALID_TOKEN, input[i], "2nd_operands"); + CHECK_ASSIGN_MIDDLE(tokens[n-1], "2nd_operands"); /* We put FEE on the middle spot of the amount */ tokens[n-1]->middle = tal_steal(tokens[n-1], input[i]); @@ -1399,6 +1941,9 @@ static struct splice_script_error *compress_2nd_operands(const tal_t *ctx, if (!n || i + 1 >= tal_count(input)) return new_error(ctx, INVALID_TOKEN, input[i], "2nd_operands"); + CHECK_ASSIGN_RIGHT(input[i], "2nd_operands"); + CHECK_ASSIGN_RIGHT(tokens[n-1], "2nd_operands"); + input[i]->type = TOK_LEASEREQ; input[i]->right = tal_steal(input[i], input[i+1]); @@ -1419,9 +1964,12 @@ static struct splice_script_error *compress_2nd_operands(const tal_t *ctx, return NULL; } -static bool matches_chan_id(struct token *token, struct channel_id chan_id) +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)) + if (!chan_id) + return false; + + if (token->chan_id && channel_id_eq(token->chan_id, chan_id)) return true; if (token->left && matches_chan_id(token->left, chan_id)) @@ -1460,6 +2008,31 @@ static struct node_id *first_node_with_unused_chan(const tal_t *ctx, return NULL; } +/* Searches through channels looking at just node_ids, return the `at_index`th + * unique node id. */ +static struct node_id *node_at_index(const tal_t *ctx, + struct splice_script_chan **channels, + int at_index) +{ + struct node_id *last_node_id = NULL; + if (at_index < 0) + return NULL; + + for (size_t i = 0; i < tal_count(channels); i++) { + if (at_index == 0) + return tal_dup(ctx, struct node_id, + &channels[i]->node_id); + + if (!last_node_id || !node_id_eq(last_node_id, + &channels[i]->node_id)) { + at_index--; + last_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, @@ -1467,9 +2040,9 @@ static struct channel_id *chan_for_node_index(const tal_t *ctx, { for (size_t i = 0; i < tal_count(channels); i++) if (node_id_eq(&node_id, &channels[i]->node_id)) - if (channel_index-- == 0) + if (channels[i]->chan_id && channel_index-- == 0) return tal_dup(ctx, struct channel_id, - &channels[i]->chan_id); + channels[i]->chan_id); return NULL; } @@ -1493,7 +2066,7 @@ static struct channel_id **unused_chans(const tal_t *ctx, if (!used) tal_arr_expand(&result, tal_dup(result, struct channel_id, - &channels[i]->chan_id)); + channels[i]->chan_id)); } if (!tal_count(result)) @@ -1525,7 +2098,7 @@ static struct channel_id **unused_chans_for_node(const tal_t *ctx, if (!used) tal_arr_expand(&result, tal_dup(result, struct channel_id, - &channels[i]->chan_id)); + channels[i]->chan_id)); } if (!tal_count(result)) @@ -1606,8 +2179,20 @@ static struct splice_script_error *resolve_channel_ids(const tal_t *ctx, case TOK_LEASERATE: case TOK_ARROW: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_WILDCARD: case TOK_CHANID: case TOK_WALLET: @@ -1664,6 +2249,7 @@ static struct splice_script_error *resolve_channel_ids(const tal_t *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); @@ -1690,8 +2276,11 @@ static struct splice_script_error *resolve_channel_ids(const tal_t *ctx, } 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++) { + CHECK_ASSIGN_RIGHT(token_itr, "resolve_channel_ids"); + token_itr->right = new_token(token_itr, TOK_CHANID, token_itr->script_index); @@ -1715,6 +2304,7 @@ static struct splice_script_error *resolve_channel_ids(const tal_t *ctx, CHAN_INDEX_ON_WILDCARD_NODE, input[i], "resolve_channel_ids"); + chan_ids = unused_chans(ctx, channels, input, tal_count(input), @@ -1723,6 +2313,8 @@ static struct splice_script_error *resolve_channel_ids(const tal_t *ctx, input[i]->right = tal_free(input[i]->right); token_itr = input[i]; for (size_t j = 0; j < tal_count(chan_ids); j++) { + CHECK_ASSIGN_RIGHT(token_itr, "resolve_channel_ids"); + token_itr->right = new_token(token_itr, TOK_CHANID, token_itr->script_index); @@ -1733,6 +2325,11 @@ static struct splice_script_error *resolve_channel_ids(const tal_t *ctx, } tokens[n++] = tal_steal(tokens, input[i]); tal_free(chan_ids); + } else if (input[i]->left->type == TOK_CHANID) { + return new_error(ctx, + CHANQUERY_PEERID_IS_CHANID, + input[i], + "resolve_channel_ids"); } else { return new_error(ctx, CHANQUERY_TYPEERROR, @@ -1753,6 +2350,179 @@ static struct splice_script_error *resolve_channel_ids(const tal_t *ctx, return NULL; } +static struct splice_script_error *process_peer_token(const tal_t *ctx, + struct splice_script_chan **channels, + struct token **token_inout) +{ + struct token *chan; + struct token *token = *token_inout; + struct token *old_token_right; + + if (!token->right) + return new_error(ctx, NODE_NEEDS_RIGHT, token, + "process_peer_token"); + + if (token->right->type == TOK_CHAN) { + chan = token->right; + + CHECK_ASSIGN_LEFT(token, "process_peer_token"); + + token->type = TOK_CHANQUERY; + + /* TOK_CHANQUERY takes nodeid on left and query on the right */ + + token->left = token->middle; + token->middle = NULL; + + if (chan->middle && token->right) { + old_token_right = token->right; + token->right = tal_steal(token, chan->middle); + token->right->right = tal_steal(token->right, + old_token_right); + } + chan->middle = NULL; + + /* Modifying the token in place so we dont have to use + * token_inout */ + return NULL; + } + + if (token->middle && token->middle->type == TOK_QUESTION) + token->node_id = node_at_index(token, channels, 0); + + /* node().new will be handled later after node id processing phase */ + if (token->right->type == TOK_NEW) + return NULL; + + return new_error(ctx, NODE_UNRECOGNIZED_RIGHT, token, + "process_peer_token"); +} + +static struct splice_script_error *process_chan_token(const tal_t *ctx, + struct splice_script_chan **channels, + struct token **token_inout) +{ + struct token *token = *token_inout; + + if (!token->middle) + return new_error(ctx, NODE_NEEDS_RIGHT, token, + "process_chan_token"); + + *token_inout = token->middle; + (*token_inout)->right = tal_steal(*token_inout, token->right); + return NULL; +} + +static struct splice_script_error *resolve_peer_and_chan(const tal_t *ctx, + struct splice_script_chan **channels, + struct token ***tokens_inout) +{ + struct splice_script_error *error; + 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++) { + struct token *token = input[i]; + switch(token->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: + case TOK_NODEID: + return new_error(ctx, INVALID_TOKEN, token, + "resolve_peer_and_chan."); + case TOK_CHANID: + case TOK_LEASERATE: + case TOK_ARROW: + case TOK_SATS: + case TOK_NUMBER: + case TOK_PERCENT: + case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: + case TOK_BALANCE: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: + case TOK_WILDCARD: + case TOK_WALLET: + case TOK_FEERATE: + case TOK_BTCADDR: + case TOK_LEASEREQ: + case TOK_DELIMITER: + tokens[n++] = tal_steal(tokens, token); + break; + case TOK_PEER: + error = process_peer_token(ctx, channels, &token); + if (error) + return error; + tokens[n++] = tal_steal(tokens, token); + break; + case TOK_CHAN: + error = process_chan_token(ctx, channels, &token); + if (error) + return error; + tokens[n++] = tal_steal(tokens, token); + break; + case TOK_MULTI_CHANID: + case TOK_SEGMENT: + case TOK_CHANQUERY: + return new_error(ctx, INVALID_TOKEN, token, + "resolve_peer_and_chan.."); + } + } + + tal_free(input); + tal_resize(&tokens, n); + *tokens_inout = tokens; + return NULL; +} + +static struct splice_script_error *check_for_unparsed_tokens(const tal_t *ctx, + struct token *input) +{ + struct splice_script_error *error; + + error = NULL; + if (input->type == TOK_STR) + error = new_error_msg(ctx, UNRECOGNIZED_TOKEN, input, + "check_for_unparsed_tokens", + tal_fmt(tmpctx, "'%s' unrecognized", + input->str ?: "NULL")); + + if (!error && input->left) + error = check_for_unparsed_tokens(ctx, input->left); + + if (!error && input->middle) + error = check_for_unparsed_tokens(ctx, input->middle); + + if (!error && input->right) + error = check_for_unparsed_tokens(ctx, input->right); + + return error; +} + +static struct splice_script_error *error_on_strings(const tal_t *ctx, + struct token **input) +{ + struct splice_script_error *error; + for (size_t i = 0; i < tal_count(input); i++) { + error = check_for_unparsed_tokens(ctx, input[i]); + if (error) + return error; + } + + return NULL; +} + static bool is_valid_middle(struct token *token) { switch(token->type) { @@ -1764,6 +2534,10 @@ static bool is_valid_middle(struct token *token) case TOK_BTCADDR: case TOK_WALLET: return true; + case TOK_PEER: + return token->node_id + && token->right + && token->right->type == TOK_NEW; case TOK_FEERATE: case TOK_CHAR: case TOK_ATSYM: @@ -1775,8 +2549,19 @@ static bool is_valid_middle(struct token *token) case TOK_PIPE: case TOK_STR: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: + case TOK_BALANCE: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_WILDCARD: case TOK_FEE: case TOK_NODEID: @@ -1814,8 +2599,20 @@ static struct splice_script_error *make_segments(const tal_t *ctx, case TOK_ARROW: case TOK_PIPE: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_WILDCARD: case TOK_CHANID: case TOK_WALLET: @@ -1836,6 +2633,10 @@ static struct splice_script_error *make_segments(const tal_t *ctx, return new_error(ctx, MISSING_ARROW, input[next_consumable+1], "segments"); + CHECK_ASSIGN_LEFT(input[i], "segments."); + CHECK_ASSIGN_MIDDLE(input[i], "segments"); + CHECK_ASSIGN_RIGHT(input[i], "segments"); + input[i]->type = TOK_SEGMENT; input[i]->left = tal_steal(input[i], input[next_consumable]); @@ -1853,6 +2654,10 @@ static struct splice_script_error *make_segments(const tal_t *ctx, return new_error(ctx, MISSING_ARROW, input[next_consumable+3], "segments"); + CHECK_ASSIGN_LEFT(input[i], "segments.."); + CHECK_ASSIGN_MIDDLE(input[i], "segments"); + CHECK_ASSIGN_RIGHT(input[i], "segments"); + input[i]->type = TOK_SEGMENT; input[i]->left = tal_steal(input[i], input[next_consumable]); @@ -1876,6 +2681,8 @@ static struct splice_script_error *make_segments(const tal_t *ctx, DOUBLE_MIDDLE_OP, tokens[n-1]->middle, "make_segments"); + CHECK_ASSIGN_RIGHT(tokens[n-1], "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], @@ -1884,6 +2691,8 @@ static struct splice_script_error *make_segments(const tal_t *ctx, tokens[n-1]->left->amount_sat = AMOUNT_SAT(0); } else if (is_valid_middle(tokens[n-1]->middle)) { + CHECK_ASSIGN_RIGHT(tokens[n-1], "segments"); + tokens[n-1]->right = new_token(tokens[n-1], TOK_SATS, tokens[n-1]->script_index); @@ -1917,6 +2726,190 @@ static struct splice_script_error *make_segments(const tal_t *ctx, return NULL; } +/* Goes through the tokens and eats all OPAREN, CPAREN, and DOT tokens. + * Parens will be attached to the token just left of them on the `middle` ptr. + * Dots will attach the trailing token onto the token before it on the `right`. + * + * Example: "peer(0234).private" + * Input tokens: + * STR OPAREN STR CPAREN DOT STR + * Output tokens: + * STR "peer" + * ->middle: + * STR "0234" + * ->right: + * STR "private" + * + * Example: "peer.private(0234)" + * Input tokens: + * STR DOT STR OPAREN STR CPAREN + * Output tokens: + * STR "peer" + * ->right: + * STR "private" + * ->middle: + * STR "0234" + */ +static struct splice_script_error *compress_parens_and_dots(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; + struct token *last_token = NULL; + struct token *close_paren = NULL; + struct token *paren_content = NULL; + + /* Parens and dots are special in that they can be chained. In order to + * keep our `right` assignment checks working correctly, we have to + * process these tokens in reverse order. */ + for (int i = tal_count(input) - 1; i >= 0; i--) { + switch(input[i]->type) { + case TOK_CHAR: + return new_error(ctx, INVALID_TOKEN, input[i], + "parens_and_dots"); + case TOK_DELIMITER: + case TOK_ARROW: + case TOK_PIPE: + case TOK_STR: + case TOK_SATS: + case TOK_NUMBER: + case TOK_PERCENT: + case TOK_QUESTION: + case TOK_WILDCARD: + case TOK_PLUS: + case TOK_MINUS: + case TOK_CHANID: + case TOK_FEE: + case TOK_WALLET: + case TOK_NODEID: + case TOK_BTCADDR: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: + if (close_paren) { + /* If we're inside parens, save this token for + * processing when we get to the final paren */ + if (paren_content) + return new_error(ctx, + MULTI_ITEMS_IN_PAREN, + input[i], + "parens_and_dots"); + paren_content = input[i]; + } else { + /* Otherwise, just push it onto the result as + * normal */ + last_token = input[i]; + tokens[n++] = tal_steal(tokens, input[i]); + } + break; + case TOK_CPAREN: + /* NOTE: Don't forget we're looping backwards. */ + + /* When we see close paren we just store a reference to + * it for later. */ + if (close_paren) + return new_error(ctx, DOUBLE_CPAREN, input[i], + "parens_and_dots"); + close_paren = input[i]; + paren_content = NULL; + break; + case TOK_OPAREN: + /* NOTE: Don't forget we're looping backwards. */ + + /* On opening paren, take the contents of paren pair + * and attach them to the next token as `middle` */ + if (!close_paren) + return new_error(ctx, MISSING_CLOSE_PAREN, + input[i], "parens_and_dots"); + if (i < 1) + return new_error(ctx, PAREN_ON_NOTHING, + input[i], "parens_and_dots"); + if (input[i-1]->type == TOK_DOT) + return new_error(ctx, PAREN_ON_DOT, + input[i], "parens_and_dots"); + CHECK_ASSIGN_MIDDLE(input[i-1], "parens_and_dots"); + + input[i-1]->middle = tal_steal(input[i-1], + paren_content); + + /* Take this example: "peer(abc).new" + * 'new' will be attached to the close paren. + * We must foward this attachment onto 'peer' */ + if (close_paren->right) { + CHECK_ASSIGN_RIGHT(input[i-1], "parens_and_dots."); + input[i-1]->right = tal_steal(input[i-1], + close_paren->right); + } + /* Fees must be at the end of the paren chain. We put + * them on the right even though theyre coming from + * middle. */ + if (close_paren->middle + && close_paren->middle->type == TOK_FEE) { + CHECK_ASSIGN_RIGHT(input[i-1], "parens_and_dots."); + input[i-1]->right = tal_steal(input[i-1], + close_paren->middle); + } + close_paren = NULL; + paren_content = NULL; + break; + case TOK_DOT: + /* NOTE: Don't forget we're looping backwards. */ + + /* On dot, attach last token onto the next token as + * `right` */ + if (i < 1) + return new_error(ctx, DOT_ON_NOTHING, + input[i], "parens_and_dots"); + if (input[i-1]->type == TOK_DOT) + return new_error(ctx, DOT_ON_DOT, + input[i], "parens_and_dots"); + if (!last_token) + return new_error(ctx, DOT_OF_NOTHING, + input[i], "parens_and_dots"); + CHECK_ASSIGN_RIGHT(input[i-1], "parens_and_dots"); + + /* Steal next token and attach it to the last token */ + input[i-1]->right = tal_steal(input[i-1], last_token); + + /* Remove `last_token` from results */ + tokens[n] = NULL; + n--; + break; + case TOK_ATSYM: + case TOK_COLON: + 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], + "parens_and_dots"); + } + } + + if (close_paren) + return new_error(ctx, UNOPENED_PAREN, + close_paren, "parens_and_dots"); + + tal_free(input); + tal_resize(&tokens, n); + + /* Now reverse the array back and put it into *tokens_inout */ + *tokens_inout = tal_arr(ctx, struct token *, tal_count(tokens)); + n = 0; + for (int i = tal_count(tokens) - 1; i >= 0; i--) + (*tokens_inout)[n++] = tal_steal(*tokens_inout, tokens[i]); + + return NULL; +} + static void steal_sub_tokens(const tal_t *ctx, struct token *token) { tal_steal(token, token->left); @@ -1945,8 +2938,12 @@ static struct splice_script_error *expand_multichans(const tal_t *ctx, case TOK_PIPE: case TOK_STR: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: @@ -1954,6 +2951,14 @@ static struct splice_script_error *expand_multichans(const tal_t *ctx, case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASEREQ: @@ -1975,13 +2980,16 @@ static struct splice_script_error *expand_multichans(const tal_t *ctx, /* 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); @@ -2020,14 +3028,16 @@ static struct splice_script_error *expand_multichans(const tal_t *ctx, return NULL; } -static bool is_valid_amount(struct token *token) +static bool is_valid_sat_amount(struct token *token) { switch(token->type) { case TOK_SATS: case TOK_WILDCARD: case TOK_PERCENT: - return true; case TOK_FEE: + case TOK_BALANCE: + return true; + case TOK_NUMBER: case TOK_CHAR: case TOK_ATSYM: case TOK_PLUS: @@ -2038,11 +3048,21 @@ static bool is_valid_amount(struct token *token) case TOK_PIPE: case TOK_STR: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: case TOK_CHANID: case TOK_WALLET: case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASEREQ: @@ -2054,24 +3074,33 @@ static bool is_valid_amount(struct token *token) return false; } -static bool is_valid_nonzero_amount(struct token *token) +static bool is_valid_nonzero_sat_amount(struct token *token) { if (token->type == TOK_SATS && amount_sat_is_zero(token->amount_sat)) return false; - return is_valid_amount(token); + return is_valid_sat_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)) + if (channel_id_eq(chan_id, channels[i]->chan_id)) return true; return false; } +static struct token *find_on_right(struct token *token, enum token_type type) +{ + if (!token) + return NULL; + if (token && token->type == type) + return token; + return find_on_right(token->right, type); +} + static struct splice_script_error *validate_and_clean(const tal_t *ctx, struct splice_script_chan **channels, struct token ***tokens_inout) @@ -2080,7 +3109,7 @@ static struct splice_script_error *validate_and_clean(const tal_t *ctx, 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; + struct token *lease, *leaserate, *fee, *leftfee, *rightfee, *feerate; bool pay_fee_found = false; for (size_t i = 0; i < tal_count(input); i++) { @@ -2095,8 +3124,12 @@ static struct splice_script_error *validate_and_clean(const tal_t *ctx, case TOK_PIPE: case TOK_STR: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: @@ -2104,6 +3137,15 @@ static struct splice_script_error *validate_and_clean(const tal_t *ctx, case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: + case TOK_BALANCE: + /* Balance is invalid on a newchan query */ + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASEREQ: @@ -2111,25 +3153,29 @@ static struct splice_script_error *validate_and_clean(const tal_t *ctx, return new_error(ctx, INVALID_TOKEN, input[i], "validate_and_clean"); case TOK_SEGMENT: - if (!is_valid_amount(input[i]->left)) + if (!is_valid_sat_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)) + if (!is_valid_sat_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; + * ex: "0|10M@1% -> chan_id" + * ex: "0 -> chan(chan_id).lease(10M @ 1%)" + */ + + /* Splice in amounts on the left side of a segment can have leases + * attached or they can be found in channel identifeir + * in the segment's middle segment. */ + lease = find_on_right(input[i]->left, TOK_LEASE) + ?: find_on_right(input[i]->middle, TOK_LEASE); 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) + if (lease && lease->type == TOK_LEASEREQ) { + if (!lease->right + || lease->right->type != TOK_SATS) return new_error(ctx, LEASE_AMOUNT_ZERO, lease, "validate_and_clean"); @@ -2139,7 +3185,24 @@ static struct splice_script_error *validate_and_clean(const tal_t *ctx, lease->right, "validate_and_clean"); leaserate = lease->right->right; + } else if(lease && lease->type == TOK_LEASE) { + if (!lease->middle + || lease->middle->type != TOK_SATS) + return new_error(ctx, LEASE_AMOUNT_ZERO, + lease, + "validate_and_clean"); + lease->amount_sat = lease->middle->amount_sat; + if (amount_sat_is_zero(lease->amount_sat)) + return new_error(ctx, LEASE_AMOUNT_ZERO, + lease->right, + "validate_and_clean"); + leaserate = lease->middle->right; + } else if(lease) { + return new_error(ctx, INTERNAL_ERROR, + input[i], + "validate_and_clean"); } + if (leaserate) { if (leaserate->type != TOK_LEASERATE) return new_error(ctx, INTERNAL_ERROR, @@ -2152,53 +3215,30 @@ static struct splice_script_error *validate_and_clean(const tal_t *ctx, lease->ppm = leaserate->right->ppm; } - /* Process left fee & fee rate. + /* Process fee & fee rate. * ex: "10M-fee@4k -> chan_id" (splice in 10M less fee) */ - fee = input[i]->left->middle; + leftfee = find_on_right(input[i]->left->middle, TOK_FEE) + ?: find_on_right(input[i]->left, TOK_FEE); + rightfee = find_on_right(input[i]->right->middle, TOK_FEE) + ?: find_on_right(input[i]->right, TOK_FEE); + if (leftfee && rightfee) + return new_error(ctx, DUPLICATE_FEESTR, + leftfee, + "validate_and_clean"); + fee = leftfee ?: rightfee; 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)) + if (leftfee && !(leftfee->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, + leftfee, "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)) + if (rightfee && (rightfee->flags + & TOKEN_FLAG_FEERATE_NEGATIVE)) return new_error(ctx, RIGHT_FEE_NOT_POSITIVE, - fee, + rightfee, "validate_and_clean"); if (pay_fee_found) return new_error(ctx, DUPLICATE_FEESTR, @@ -2215,13 +3255,13 @@ static struct splice_script_error *validate_and_clean(const tal_t *ctx, if (feerate->right->type != TOK_SATS) return new_error(ctx, MISSING_AMOUNT_OP, feerate, - "validate_and_clean"); + "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) + if (!is_valid_nonzero_sat_amount(input[i]->left) + && !is_valid_nonzero_sat_amount(input[i]->right) && !lease && !fee) return new_error(ctx, ZERO_AMOUNTS, @@ -2230,8 +3270,8 @@ static struct splice_script_error *validate_and_clean(const tal_t *ctx, /* 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)) + if (is_valid_nonzero_sat_amount(input[i]->left) + && is_valid_nonzero_sat_amount(input[i]->right)) return new_error(ctx, IN_AND_OUT_AMOUNTS, input[i]->left, "validate_and_clean"); @@ -2302,8 +3342,12 @@ static struct splice_script_error *calculate_amounts(const tal_t *ctx, case TOK_PIPE: case TOK_STR: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: @@ -2311,6 +3355,14 @@ static struct splice_script_error *calculate_amounts(const tal_t *ctx, case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASEREQ: @@ -2389,8 +3441,12 @@ static struct splice_script_error *calculate_amounts(const tal_t *ctx, case TOK_PIPE: case TOK_STR: case TOK_SATS: + case TOK_NUMBER: case TOK_PERCENT: case TOK_QUESTION: + case TOK_OPAREN: + case TOK_CPAREN: + case TOK_DOT: case TOK_WILDCARD: case TOK_FEE: case TOK_CHANID: @@ -2398,6 +3454,14 @@ static struct splice_script_error *calculate_amounts(const tal_t *ctx, case TOK_FEERATE: case TOK_NODEID: case TOK_BTCADDR: + case TOK_BALANCE: + case TOK_PEER: + case TOK_CHAN: + case TOK_NEW: + case TOK_PRIVATE: + case TOK_CLOSE_TO: + case TOK_COMMIT_FEERATE: + case TOK_LEASE: case TOK_CHANQUERY: case TOK_MULTI_CHANID: case TOK_LEASEREQ: @@ -2487,15 +3551,30 @@ struct splice_script_error *parse_splice_script(const tal_t *ctx, if ((error = process_3rd_separators(ctx, &tokens))) return error; + if ((error = process_paren_dot_separators(ctx, &tokens))) + return error; + + if ((error = process_keywords(ctx, &tokens))) + return error; + if ((error = type_data(ctx, channels, &tokens))) return error; + if ((error = error_on_strings(ctx, tokens))) + return error; + if ((error = compress_top_operands(ctx, &tokens))) return error; if ((error = compress_2nd_operands(ctx, &tokens))) return error; + if ((error = compress_parens_and_dots(ctx, &tokens))) + return error; + + if ((error = resolve_peer_and_chan(ctx, channels, &tokens))) + return error; + if ((error = resolve_channel_ids(ctx, channels, &tokens))) return error; @@ -2512,7 +3591,7 @@ struct splice_script_error *parse_splice_script(const tal_t *ctx, return error; #if SCRIPT_DUMP_TOKENS - return debug_dump(ctx, tokens); + // return debug_dump(ctx, tokens); #endif #if SCRIPT_DUMP_SEGMENTS return dump_segments(ctx, tokens); @@ -2527,6 +3606,7 @@ struct splice_script_error *parse_splice_script(const tal_t *ctx, struct token *fee = tokens[i]->left->middle ? tokens[i]->left->middle : tokens[i]->right->middle; + struct token *itr_r; if (lease) { itr->lease_sat = lease->amount_sat; @@ -2536,17 +3616,51 @@ struct splice_script_error *parse_splice_script(const tal_t *ctx, itr->in_sat = tokens[i]->left->amount_sat; itr->in_ppm = tokens[i]->left->ppm; - if (tokens[i]->middle->type == TOK_CHANID) + 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_r = find_on_right(tokens[i]->middle, TOK_LEASE); + if (itr_r) { + itr->lease_sat = itr_r->amount_sat; + itr->lease_max_ppm = itr_r->ppm; + } + } + 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) + } + else if (tokens[i]->middle->type == TOK_WALLET){ itr->onchain_wallet = true; - else + } + else if (tokens[i]->middle->type == TOK_PEER + && tokens[i]->middle->right + && tokens[i]->middle->right->type == TOK_NEW) { + itr->peer_id = tal_dup(itr, struct node_id, + tokens[i]->middle->node_id); + itr_r = tokens[i]->middle->right; + for (; itr_r; itr_r = itr_r->right) { + if (itr_r->type == TOK_PRIVATE) + itr->private_channel = true; + if (itr_r->type == TOK_CLOSE_TO + && itr_r->middle) + itr->close_to_address = tal_strdup(itr, itr_r->middle->str); + if (itr_r->type == TOK_COMMIT_FEERATE + && itr_r->middle) { + if (!parse_feerate(itr_r->middle, &itr->commit_feerate_per_kw)) + return new_error(ctx, INVALID_FEERATE, + itr_r, + "splice_script_result"); + } + if (itr_r->type == TOK_LEASE) { + itr->lease_sat = itr_r->amount_sat; + itr->lease_max_ppm = itr_r->ppm; + } + } + } + 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; @@ -2598,6 +3712,8 @@ void splice_to_json(const tal_t *ctx, if (splice[i]->channel_id) json_add_channel_id(js, "channel_id", splice[i]->channel_id); + if (splice[i]->peer_id) + json_add_node_id(js, "peer_id", splice[i]->peer_id); if (splice[i]->bitcoin_address) json_add_string(js, "bitcoin_address", splice[i]->bitcoin_address); @@ -2616,6 +3732,24 @@ void splice_to_json(const tal_t *ctx, json_object_end(js); } + if (splice[i]->balance_ppm) + json_add_u32(js, "balance_ppm", splice[i]->balance_ppm); + + if (splice[i]->peer_id) { + json_object_start(js, "new_chan_params"); + if (splice[i]->commit_feerate_per_kw) + json_add_u32(js, "commit_feerate_per_kw", + splice[i]->commit_feerate_per_kw); + + if (splice[i]->private_channel) + json_add_bool(js, "private_channel", true); + + if (splice[i]->close_to_address) + json_add_string(js, "close_to_address", + splice[i]->close_to_address); + json_object_end(js); + } + if (splice[i]->pays_fee || splice[i]->feerate_per_kw) { json_object_start(js, "fee"); if (splice[i]->pays_fee) @@ -2656,7 +3790,7 @@ bool json_to_splice(const tal_t *ctx, const char *buffer, const jsmntok_t *tok, *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; + const jsmntok_t *lease, *in, *dest, *out, *bal, *chan, *fee, *obj; struct splice_script_result *ele = talz(*result, struct splice_script_result); if ((lease = json_get_member(buffer, itr, "lease_request"))) { @@ -2694,6 +3828,11 @@ bool json_to_splice(const tal_t *ctx, const char *buffer, const jsmntok_t *tok, ele->channel_id)) return false; } + if ((obj = json_get_member(buffer, dest, "peer_id"))) { + ele->peer_id = tal(ele, struct node_id); + if (!json_to_node_id(buffer, obj, ele->peer_id)) + return false; + } if ((obj = json_get_member(buffer, dest, "bitcoin_address"))) { ele->bitcoin_address = json_strdup(ele, buffer, @@ -2722,6 +3861,32 @@ bool json_to_splice(const tal_t *ctx, const char *buffer, const jsmntok_t *tok, } } + if ((bal = json_get_member(buffer, itr, "balance_ppm"))) + if (!json_to_u32(buffer, bal, &ele->balance_ppm)) + return false; + + if ((chan = json_get_member(buffer, itr, "new_chan_params"))) { + if ((obj = json_get_member(buffer, chan, + "commit_feerate_per_kw"))) { + if (!json_to_u32(buffer, obj, + &ele->commit_feerate_per_kw)) + return false; + } + if ((obj = json_get_member(buffer, chan, + "private_channel"))) { + if (!json_to_bool(buffer, obj, + &ele->private_channel)) + return false; + } + if ((obj = json_get_member(buffer, chan, + "close_to_address"))) { + ele->close_to_address = json_strdup(ele, buffer, + obj); + if (!ele->close_to_address) + 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)) @@ -2786,10 +3951,20 @@ char *splice_to_string(const tal_t *ctx, amount_sat_is_zero(result->in_sat) ? "put " : "", ppm_to_str(ctx, result->in_ppm), fee_str); + if (result->balance_ppm) + tal_append_fmt(&str, "%senough to %s balance channel%s into ", + into_prefix, + ppm_to_str(ctx, result->balance_ppm), + fee_str); if (result->channel_id) - tal_append_fmt(&str, "%s", - tal_hexstr(tmpctx, result->channel_id, sizeof(struct channel_id))); + tal_append_fmt(&str, "%s%s%s", + result->peer_id ? "new(" : "", + tal_hexstr(tmpctx, result->channel_id, sizeof(struct channel_id)), + result->peer_id ? ")" : ""); + else if (result->peer_id) + tal_append_fmt(&str, "new channel with %s", + fmt_node_id(ctx, result->peer_id)); if (result->bitcoin_address) tal_append_fmt(&str, "%s", result->bitcoin_address); if (result->onchain_wallet) diff --git a/common/splice_script.h b/common/splice_script.h index fd2c9b07bb99..67db8081992c 100644 --- a/common/splice_script.h +++ b/common/splice_script.h @@ -5,54 +5,7 @@ #include #include #include - -enum splice_script_error_type { - INTERNAL_ERROR, - INVALID_TOKEN, - DEBUG_DUMP, - TOO_MANY_PIPES, - TOO_MANY_ATS, - TOO_MANY_COLONS, - TOO_MANY_PLUS, - TOO_MANY_MINUS, - INVALID_NODEID, - INVALID_CHANID, - WRONG_NUM_SEGMENT_CHUNKS, - MISSING_ARROW, - NO_MATCHING_NODES, - INVALID_INDEX, - CHAN_INDEX_ON_WILDCARD_NODE, - CHAN_INDEX_NOT_FOUND, - CHANQUERY_TYPEERROR, - NODE_ID_MULTIMATCH, - NODE_ID_CHAN_OVERMATCH, - CHAN_ID_MULTIMATCH, - CHAN_ID_NODE_OVERMATCH, - NODE_ID_NO_UNUSED, - DOUBLE_MIDDLE_OP, - MISSING_MIDDLE_OP, - MISSING_AMOUNT_OP, - MISSING_AMOUNT_OR_WILD_OP, - CANNOT_PARSE_SAT_AMNT, - ZERO_AMOUNTS, - IN_AND_OUT_AMOUNTS, - MISSING_PERCENT, - LEASE_AMOUNT_ZERO, - CHANNEL_ID_UNRECOGNIZED, - DUPLICATE_CHANID, - INVALID_MIDDLE_OP, - INSUFFICENT_FUNDS, - PERCENT_IS_ZERO, - WILDCARD_IS_ZERO, - INVALID_PERCENT, - LEFT_PERCENT_OVER_100, - LEFT_FEE_NOT_NEGATIVE, - RIGHT_FEE_NOT_POSITIVE, - MISSING_FEESTR, - DUPLICATE_FEESTR, - TOO_MUCH_DECIMAL, - INVALID_FEERATE, -}; +#include struct splice_script_error { enum splice_script_error_type type; @@ -66,11 +19,15 @@ char *fmt_splice_script_compiler_error(const tal_t *ctx, const char *script, struct splice_script_error *error); +/* You are responsible for filling this in for every channel the + * node has. If you would like a peer that has no channels to be queryable by + * the script, include it here with a NULL channel. */ struct splice_script_chan { struct node_id node_id; - struct channel_id chan_id; + struct channel_id *chan_id; }; +/* If the script parses successfully, you will receive an array of these */ struct splice_script_result { /* Lease request info */ struct amount_sat lease_sat; @@ -82,6 +39,7 @@ struct splice_script_result { /* Destination (just one) */ struct channel_id *channel_id; + struct node_id *peer_id; /* Open new channel if set */ char *bitcoin_address; bool onchain_wallet; @@ -89,12 +47,27 @@ struct splice_script_result { struct amount_sat out_sat; u32 out_ppm; /* UINT32_MAX means "max available from channel" */ + /* If set, this `in_sat` and `out_sat` wont be set. Instead at the point + * our channel's funds are known (after `stfu`). At this point `in_sat` + * and `out_sat` must be set so that the resulting channel balance is + * `balance_ppm`. + * in_sat = max(0, balance_ppm * chan_size - sats_owed) + * out_sat = max(0, (1000000 - balance_ppm) * chan_size - sats_owed) */ + u32 balance_ppm; + + /* Open new channel parameters */ + u32 commit_feerate_per_kw; + bool private_channel; + char *close_to_address; + /* If true, this 'destination' pays the fee. Only one destination may * do so. If feerate_per_kw is non-zero, it will be used for feerate. */ bool pays_fee; u32 feerate_per_kw; }; +/* Parses `script` taking `channels` into account. The result is returned into + * `result` or an error is returned. */ struct splice_script_error *parse_splice_script(const tal_t *ctx, const char *script, struct splice_script_chan **channels, @@ -107,6 +80,7 @@ void splice_to_json(const tal_t *ctx, bool json_to_splice(const tal_t *ctx, const char *buffer, const jsmntok_t *tok, struct splice_script_result ***result); +/* Convenience methods for printing out compiled scripts */ char *splice_to_string(const tal_t *ctx, struct splice_script_result *splice); char *splicearr_to_string(const tal_t *ctx, struct splice_script_result **splice); diff --git a/common/splice_script_errors.h b/common/splice_script_errors.h new file mode 100644 index 000000000000..beea5768ec74 --- /dev/null +++ b/common/splice_script_errors.h @@ -0,0 +1,69 @@ +#ifndef LIGHTNING_COMMON_SPLICE_SCRIPT_ERRORS_H +#define LIGHTNING_COMMON_SPLICE_SCRIPT_ERRORS_H + +enum splice_script_error_type { + INTERNAL_ERROR, + INVALID_TOKEN, + UNRECOGNIZED_TOKEN, + DEBUG_DUMP, + TOO_MANY_PIPES, + TOO_MANY_ATS, + TOO_MANY_COLONS, + TOO_MANY_PLUS, + TOO_MANY_MINUS, + INVALID_NODEID, + INVALID_CHANID, + WRONG_NUM_SEGMENT_CHUNKS, + MISSING_ARROW, + NO_MATCHING_NODES, + INVALID_INDEX, + CHAN_INDEX_ON_WILDCARD_NODE, + CHAN_INDEX_NOT_FOUND, + CHANQUERY_TYPEERROR, + CHANQUERY_PEERID_IS_CHANID, + NODE_ID_MULTIMATCH, + NODE_ID_CHAN_OVERMATCH, + CHAN_ID_MULTIMATCH, + CHAN_ID_NODE_OVERMATCH, + NODE_ID_NO_UNUSED, + DOUBLE_MIDDLE_OP, + MISSING_MIDDLE_OP, + MISSING_AMOUNT_OP, + MISSING_AMOUNT_OR_WILD_OP, + CANNOT_PARSE_SAT_AMNT, + ZERO_AMOUNTS, + IN_AND_OUT_AMOUNTS, + MISSING_PERCENT, + LEASE_AMOUNT_ZERO, + CHANNEL_ID_UNRECOGNIZED, + DUPLICATE_CHANID, + INVALID_MIDDLE_OP, + INSUFFICENT_FUNDS, + PERCENT_IS_ZERO, + WILDCARD_IS_ZERO, + INVALID_PERCENT, + LEFT_PERCENT_OVER_100, + LEFT_FEE_NOT_NEGATIVE, + RIGHT_FEE_NOT_POSITIVE, + MISSING_FEESTR, + DUPLICATE_FEESTR, + TOO_MUCH_DECIMAL, + INVALID_FEERATE, + DOUBLE_CPAREN, + MULTI_ITEMS_IN_PAREN, + MISSING_CLOSE_PAREN, + PAREN_ON_NOTHING, + PAREN_ON_DOT, + DOT_ON_DOT, + UNOPENED_PAREN, + DOT_ON_NOTHING, + DOT_OF_NOTHING, + LEFT_ALREADY_SET, + MIDDLE_ALREADY_SET, + RIGHT_ALREADY_SET, + NODEID_NOT_FOUND, + NODE_NEEDS_RIGHT, + NODE_UNRECOGNIZED_RIGHT, +}; + +#endif /* LIGHTNING_COMMON_SPLICE_SCRIPT_ERRORS_H */ diff --git a/common/test/run-splice_script.c b/common/test/run-splice_script.c index 58157e026fb9..6253c49dc59f 100644 --- a/common/test/run-splice_script.c +++ b/common/test/run-splice_script.c @@ -57,20 +57,114 @@ static void set_chan_id(struct splice_script_chan *chan, const char *hexstr) assert(result); } +static struct splice_script_result **static_to_dynamic(const tal_t *ctx, struct splice_script_result *input) +{ + struct splice_script_result **result = tal_arr(ctx, struct splice_script_result*, tal_count(input)); -int main(int argc, char *argv[]) + for (size_t i = 0; i < tal_count(input); i++) + result[i] = &input[i]; + + return result; +} + +static const char *result_to_str(const tal_t *ctx, struct splice_script_result **result, size_t *len) { - size_t i, len; const char *str; + struct json_stream *js = new_json_stream(ctx, NULL, NULL); + + splice_to_json(tmpctx, result, js); + + str = json_out_contents(js->jout, len); + assert(str); + + str = tal_steal(ctx, str); + tal_free(js); + + return str; +} + +static struct splice_script_result **run_script(const char *script, + struct splice_script_chan **channels) +{ struct splice_script_error *error; - struct splice_script_result **result, **final; + struct splice_script_result **result; + struct splice_script_result **final; jsmntok_t *toks; + const char *str; + size_t len; + + static int script_count = 0; + + printf("(%d) Running Script:\n%s\n", script_count, script); + error = parse_splice_script(tmpctx, script, channels, &result); + + if (error) { + printf("\n\n%s\n", fmt_splice_script_compiler_error(tmpctx, + script, + error)); + common_shutdown(); + abort(); + } + + str = result_to_str(tmpctx, result, &len); + printf("(%d) Script Result:\n%.*s\n\n", script_count, (int)len, str); + + toks = json_parse_simple(tmpctx, str, len); + + assert(toks); + + assert(json_to_splice(tmpctx, str, toks, &final)); + + return final; +} + +static void verify_result(struct splice_script_result **final, + struct splice_script_result *expect, + size_t size) +{ + assert(tal_count(final) == size); + + for (size_t i = 0; i < size; i++) { + assert(amount_sat_eq(final[i]->lease_sat, expect[i].lease_sat)); + assert(final[i]->lease_max_ppm == expect[i].lease_max_ppm); + assert(amount_sat_eq(final[i]->in_sat, expect[i].in_sat)); + assert(final[i]->in_ppm == expect[i].in_ppm); + if (final[i]->channel_id != expect[i].channel_id) + assert(channel_id_eq(final[i]->channel_id, + expect[i].channel_id)); + if (final[i]->peer_id != expect[i].peer_id) + assert(node_id_eq(final[i]->peer_id, + expect[i].peer_id)); + if (final[i]->bitcoin_address != expect[i].bitcoin_address) + assert(!strcmp(final[i]->bitcoin_address, + expect[i].bitcoin_address)); + assert(final[i]->onchain_wallet == expect[i].onchain_wallet); + assert(amount_sat_eq(final[i]->out_sat, expect[i].out_sat)); + assert(final[i]->out_ppm == expect[i].out_ppm); + assert(final[i]->balance_ppm == expect[i].balance_ppm); + assert(final[i]->commit_feerate_per_kw == expect[i].commit_feerate_per_kw); + assert(final[i]->private_channel == expect[i].private_channel); + if (final[i]->close_to_address != expect[i].close_to_address) + assert(!strcmp(final[i]->close_to_address, + expect[i].close_to_address)); + if (expect[i].pays_fee) + assert(final[i]->pays_fee); + else + assert(!final[i]->pays_fee); + assert(final[i]->feerate_per_kw == expect[i].feerate_per_kw); + } +} + +int main(int argc, char *argv[]) +{ struct splice_script_chan **channels; - const char *script; struct splice_script_result *expect; + const char *script; + size_t i; common_setup(argv[0]); + /* Test massive many channel splice script */ i = 0; channels = tal_arr(tmpctx, struct splice_script_chan*, 0); /* A */ @@ -135,25 +229,26 @@ int main(int argc, char *argv[]) i++; script = "" - "0->0399:0->3M;\n" /* A */ + "0->peer(0399).chan(first)->3M;\n" /* A */ "3.000001M->bcrt1pp5ygqjg0q3mmv8ng8ceu59kl5a3etlf2vvryvnnyumvdyr8a77tqx507vk;\n" "wallet->1M;\n" - "0->f4699c->3M;\n" /* H */ - "0->0393069f1693fd89a453f0caf03ee36b6f6c8abaa7ef778d3e2bcc7c2b44120101:0->*;\n" /* B */ - "0->0393069f1693fd89a453f0caf03ee36b6f6c8abaa7ef778d3e2bcc7c2b44120101:?->12M;\n" /* C */ - "0->03930:*->*\n" /* D, E, F */ - "|4.91M@2%->*:?;\n" /* G */ - "25.010%|100K->*:?;\n" /* I */ - "*:?->+fee@40000;\n" /* J */ - "10.0003%->*:*;\n"; /* K, L */ - - expect = tal_arr(tmpctx, struct splice_script_result, 15); + "0->chan(f4699c)->3M;\n" /* H */ + "0->peer(0393069f1693fd89a453f0caf03ee36b6f6c8abaa7ef778d3e2bcc7c2b44120101).chan(0)->all;\n" /* B */ + "0->peer(0393069f1693fd89a453f0caf03ee36b6f6c8abaa7ef778d3e2bcc7c2b44120101).chan(?)->12M;\n" /* C */ + "peer(03930).chan(all) -> all\n" /* D, E, F */ + "0->peer(all).chan(any).lease(4.91M @ 2%);\n" /* G */ + "25.010%.lease(100K@2%)->peer(all).chan(first);\n" /* I */ + "peer(all).chan(one)->0+fee@40000;\n" /* J */ + "10.0003%->peer(all).chan(all);\n" /* K, L */ + "50k -> peer(0399).new.lease(4.91M @ 4%).private.commit_feerate(40000).close_to(bcrt1pp5ygqjg0q3mmv8ng8ceu59kl5a3etlf2vvryvnnyumvdyr8a77tqx507vk);"; + + expect = tal_arr(tmpctx, struct splice_script_result, 16); i = 0; expect[i].lease_sat = AMOUNT_SAT(0); expect[i].lease_max_ppm = 0; expect[i].in_sat = AMOUNT_SAT(0); expect[i].in_ppm = 0; - expect[i].channel_id = &channels[0]->chan_id; + expect[i].channel_id = channels[0]->chan_id; expect[i].bitcoin_address = 0; expect[i].onchain_wallet = 0; expect[i].out_sat = AMOUNT_SAT(3000000); @@ -189,7 +284,7 @@ int main(int argc, char *argv[]) expect[i].lease_max_ppm = 0; expect[i].in_sat = AMOUNT_SAT(0); expect[i].in_ppm = 0; - expect[i].channel_id = &channels[7]->chan_id; + expect[i].channel_id = channels[7]->chan_id; expect[i].bitcoin_address = 0; expect[i].onchain_wallet = 0; expect[i].out_sat = AMOUNT_SAT(3000000); @@ -201,7 +296,7 @@ int main(int argc, char *argv[]) expect[i].lease_max_ppm = 0; expect[i].in_sat = AMOUNT_SAT(0); expect[i].in_ppm = 0; - expect[i].channel_id = &channels[1]->chan_id; + expect[i].channel_id = channels[1]->chan_id; expect[i].bitcoin_address = 0; expect[i].onchain_wallet = 0; expect[i].out_sat = AMOUNT_SAT(0); @@ -213,7 +308,7 @@ int main(int argc, char *argv[]) expect[i].lease_max_ppm = 0; expect[i].in_sat = AMOUNT_SAT(0); expect[i].in_ppm = 0; - expect[i].channel_id = &channels[2]->chan_id; + expect[i].channel_id = channels[2]->chan_id; expect[i].bitcoin_address = 0; expect[i].onchain_wallet = 0; expect[i].out_sat = AMOUNT_SAT(12000000); @@ -225,7 +320,7 @@ int main(int argc, char *argv[]) expect[i].lease_max_ppm = 0; expect[i].in_sat = AMOUNT_SAT(0); expect[i].in_ppm = 0; - expect[i].channel_id = &channels[3]->chan_id; + expect[i].channel_id = channels[3]->chan_id; expect[i].bitcoin_address = 0; expect[i].onchain_wallet = 0; expect[i].out_sat = AMOUNT_SAT(0); @@ -237,7 +332,7 @@ int main(int argc, char *argv[]) expect[i].lease_max_ppm = 0; expect[i].in_sat = AMOUNT_SAT(0); expect[i].in_ppm = 0; - expect[i].channel_id = &channels[4]->chan_id; + expect[i].channel_id = channels[4]->chan_id; expect[i].bitcoin_address = 0; expect[i].onchain_wallet = 0; expect[i].out_sat = AMOUNT_SAT(0); @@ -249,7 +344,7 @@ int main(int argc, char *argv[]) expect[i].lease_max_ppm = 0; expect[i].in_sat = AMOUNT_SAT(0); expect[i].in_ppm = 0; - expect[i].channel_id = &channels[5]->chan_id; + expect[i].channel_id = channels[5]->chan_id; expect[i].bitcoin_address = 0; expect[i].onchain_wallet = 0; expect[i].out_sat = AMOUNT_SAT(0); @@ -261,7 +356,7 @@ int main(int argc, char *argv[]) expect[i].lease_max_ppm = 20000; expect[i].in_sat = AMOUNT_SAT(0); expect[i].in_ppm = 0; - expect[i].channel_id = &channels[6]->chan_id; + expect[i].channel_id = channels[6]->chan_id; expect[i].bitcoin_address = 0; expect[i].onchain_wallet = 0; expect[i].out_sat = AMOUNT_SAT(0); @@ -270,10 +365,10 @@ int main(int argc, char *argv[]) expect[i].feerate_per_kw = 0; i++; expect[i].lease_sat = AMOUNT_SAT(100000); - expect[i].lease_max_ppm = 0; + expect[i].lease_max_ppm = 20000; expect[i].in_sat = AMOUNT_SAT(0); expect[i].in_ppm = 250100; - expect[i].channel_id = &channels[8]->chan_id; + expect[i].channel_id = channels[8]->chan_id; expect[i].bitcoin_address = 0; expect[i].onchain_wallet = 0; expect[i].out_sat = AMOUNT_SAT(0); @@ -285,7 +380,7 @@ int main(int argc, char *argv[]) expect[i].lease_max_ppm = 0; expect[i].in_sat = AMOUNT_SAT(0); expect[i].in_ppm = 0; - expect[i].channel_id = &channels[9]->chan_id; + expect[i].channel_id = channels[9]->chan_id; expect[i].bitcoin_address = 0; expect[i].onchain_wallet = 0; expect[i].out_sat = AMOUNT_SAT(0); @@ -297,7 +392,7 @@ int main(int argc, char *argv[]) expect[i].lease_max_ppm = 0; expect[i].in_sat = AMOUNT_SAT(0); expect[i].in_ppm = 50001; - expect[i].channel_id = &channels[10]->chan_id; + expect[i].channel_id = channels[10]->chan_id; expect[i].bitcoin_address = 0; expect[i].onchain_wallet = 0; expect[i].out_sat = AMOUNT_SAT(0); @@ -309,14 +404,32 @@ int main(int argc, char *argv[]) expect[i].lease_max_ppm = 0; expect[i].in_sat = AMOUNT_SAT(0); expect[i].in_ppm = 50002; - expect[i].channel_id = &channels[11]->chan_id; + expect[i].channel_id = channels[11]->chan_id; + expect[i].bitcoin_address = 0; + expect[i].onchain_wallet = 0; + expect[i].out_sat = AMOUNT_SAT(0); + expect[i].out_ppm = 0; + expect[i].pays_fee = 0; + expect[i].feerate_per_kw = 0; + i++; + expect[i].lease_sat = AMOUNT_SAT(4910000); + expect[i].lease_max_ppm = 40000; + expect[i].in_sat = AMOUNT_SAT(50000); + expect[i].in_ppm = 0; + expect[i].channel_id = 0; + expect[i].peer_id = &channels[0]->node_id; expect[i].bitcoin_address = 0; expect[i].onchain_wallet = 0; expect[i].out_sat = AMOUNT_SAT(0); expect[i].out_ppm = 0; + expect[i].private_channel = true; + expect[i].commit_feerate_per_kw = 40000; + expect[i].close_to_address = "bcrt1pp5ygqjg0q3mmv8ng8ceu59kl5a3etlf2vvryvnnyumvdyr8a77tqx507vk"; expect[i].pays_fee = 0; expect[i].feerate_per_kw = 0; i++; + + /* Final onchain wallet deposit is assumed. */ expect[i].lease_sat = AMOUNT_SAT(0); expect[i].lease_max_ppm = 0; expect[i].in_sat = AMOUNT_SAT(0); @@ -334,51 +447,11 @@ int main(int argc, char *argv[]) chainparams = chainparams_for_network("regtest"); - error = parse_splice_script(tmpctx, script, channels, &result); - - if (error) { - printf("%s\n", fmt_splice_script_compiler_error(tmpctx, script, - error)); - common_shutdown(); - abort(); - } - - struct json_stream *js = new_json_stream(tmpctx, NULL, NULL); - - splice_to_json(tmpctx, result, js); - - str = json_out_contents(js->jout, &len); - assert(str); - printf("%.*s\n", (int)len, str); - - toks = json_parse_simple(tmpctx, str, len); - - assert(toks); - - assert(json_to_splice(tmpctx, str, toks, &final)); - - assert(tal_count(final) == tal_count(expect)); - - for (i = 0; i < tal_count(expect); i++) { - assert(amount_sat_eq(final[i]->lease_sat, expect[i].lease_sat)); - assert(final[i]->lease_max_ppm == expect[i].lease_max_ppm); - assert(amount_sat_eq(final[i]->in_sat, expect[i].in_sat)); - assert(final[i]->in_ppm == expect[i].in_ppm); - if (final[i]->channel_id != expect[i].channel_id) - assert(channel_id_eq(final[i]->channel_id, expect[i].channel_id)); - if (final[i]->bitcoin_address != expect[i].bitcoin_address) - assert(!strcmp(final[i]->bitcoin_address, expect[i].bitcoin_address)); - assert(final[i]->onchain_wallet == expect[i].onchain_wallet); - assert(amount_sat_eq(final[i]->out_sat, expect[i].out_sat)); - assert(final[i]->out_ppm == expect[i].out_ppm); - if (expect[i].pays_fee) - assert(final[i]->pays_fee); - else - assert(!final[i]->pays_fee); - assert(final[i]->feerate_per_kw == expect[i].feerate_per_kw); - } + size_t len; + const char *str = result_to_str(tmpctx, static_to_dynamic(tmpctx, expect), &len); + printf("Expected Result:\n%.*s\n\n", (int)len, str); - printf("DRY RUN:\n%s", splicearr_to_string(tmpctx, final)); + verify_result(run_script(script, channels), expect, i); common_shutdown(); diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 7c440e59098b..5be175b4ae14 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -2966,7 +2966,14 @@ static struct command_result *json_openchannel_update(struct command *cmd, if (!channel->open_attempt) { /* Check if the last inflight for this matches? */ inflight = find_inprogress_inflight(channel, psbt); + if (inflight) { + /* Update the inflight's psbt to what the user provided. This + * could happen if signatures were added for instance. */ + tal_free(inflight->funding_psbt); + inflight->funding_psbt = tal_steal(inflight, psbt); + wallet_inflight_save(cmd->ld->wallet, inflight); + return command_success(cmd, build_commit_response(cmd, channel, inflight)); } diff --git a/plugins/spender/splice.c b/plugins/spender/splice.c index ad25f3867875..9b151611f068 100644 --- a/plugins/spender/splice.c +++ b/plugins/spender/splice.c @@ -965,10 +965,10 @@ static size_t calc_weight(struct splice_cmd *splice_cmd, } static struct command_result *splice_init_get_result(struct command *cmd, - const char *methodname, - const char *buf, - const jsmntok_t *result, - struct splice_cmd *splice_cmd) + const char *methodname, + const char *buf, + const jsmntok_t *result, + struct splice_cmd *splice_cmd) { const jsmntok_t *tok = json_get_member(buf, result, "psbt"); @@ -1011,11 +1011,88 @@ static struct command_result *splice_init(struct command *cmd, return send_outreq(req); } +static struct command_result *open_init_get_result(struct command *cmd, + const char *methodname, + const char *buf, + const jsmntok_t *result, + struct splice_index_pkg *pkg) +{ + struct splice_cmd *splice_cmd = pkg->splice_cmd; + struct splice_script_result *action = splice_cmd->actions[pkg->index]; + + tal_free(pkg); + + const jsmntok_t *psbt_tok = json_get_member(buf, result, "psbt"); + const jsmntok_t *chanid_tok = json_get_member(buf, result, "channel_id"); + + if (!psbt_tok) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "open_init didn't have a psbt"); + if (!chanid_tok) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "open_init didn't have a channel_id"); + + tal_free(splice_cmd->psbt); + splice_cmd->psbt = json_to_psbt(splice_cmd, buf, psbt_tok); + + assert(!action->channel_id); + action->channel_id = tal(action, struct channel_id); + if (!json_to_channel_id(buf, chanid_tok, action->channel_id)) + return command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "open_init didn't have a valid channel_id"); + + plugin_log(cmd->plugin, LOG_DBG, + "open_init_get_result(peer_id:%s,channel_id:%s)", + fmt_node_id(tmpctx, action->peer_id), + fmt_channel_id(tmpctx, action->channel_id)); + + return continue_splice(splice_cmd->cmd, splice_cmd); +} + +static struct command_result *open_init(struct command *cmd, + struct splice_cmd *splice_cmd, + size_t index) +{ + struct splice_script_result *action = splice_cmd->actions[index]; + struct splice_cmd_action_state *state = splice_cmd->states[index]; + struct out_req *req; + struct splice_index_pkg *pkg = tal(cmd, struct splice_index_pkg); + + pkg->splice_cmd = splice_cmd; + pkg->index = index; + + plugin_log(cmd->plugin, LOG_DBG, + "open_init(peer_id:%s)", + fmt_node_id(tmpctx, action->peer_id)); + + req = jsonrpc_request_start(cmd, "openchannel_init", + open_init_get_result, splice_error_pkg, + pkg); + + json_add_node_id(req->js, "id", action->peer_id); + json_add_sats(req->js, "amount", action->in_sat); + json_add_psbt(req->js, "initialpsbt", splice_cmd->psbt); + json_add_u32(req->js, "commitment_feerate", + action->commit_feerate_per_kw); + json_add_u32(req->js, "funding_feerate", splice_cmd->feerate_per_kw); + json_add_bool(req->js, "announce", !action->private_channel); + + if (action->close_to_address) + json_add_string(req->js, "close_to", action->close_to_address); + + /* DTODO: Add lease after lease spec rework, fields request_amt + * & compact_lease. */ + + state->state = SPLICE_CMD_INIT; + + return send_outreq(req); +} + static struct command_result *splice_update_get_result(struct command *cmd, - const char *methodname, - const char *buf, - const jsmntok_t *result, - struct splice_index_pkg *pkg) + const char *methodname, + const char *buf, + const jsmntok_t *result, + struct splice_index_pkg *pkg) { size_t index = pkg->index; struct splice_cmd *splice_cmd = pkg->splice_cmd; @@ -1078,6 +1155,84 @@ static struct command_result *splice_update(struct command *cmd, return send_outreq(req); } +static struct command_result *open_update_get_result(struct command *cmd, + const char *methodname, + const char *buf, + const jsmntok_t *result, + struct splice_index_pkg *pkg) +{ + size_t index = pkg->index; + struct splice_cmd *splice_cmd = pkg->splice_cmd; + struct splice_cmd_action_state *state = splice_cmd->states[index]; + const jsmntok_t *tok; + struct wally_psbt *psbt; + enum splice_cmd_state old_state = state->state; + bool got_sigs; + + tal_free(pkg); + + /* DTODO: juggle serial ids correctly for cross-channel splice */ + tok = json_get_member(buf, result, "psbt"); + psbt = json_to_psbt(splice_cmd, buf, tok); + + plugin_log(cmd->plugin, LOG_DBG, "OpenUpdateGetResult Witness Size: %d", (int)tal_count(psbt_input_get_witscript(tmpctx, psbt, 0))); + + plugin_log(cmd->plugin, LOG_DBG, "psbt num sigs: %d", + (int)psbt->inputs[0].signatures.num_items); + + plugin_log(cmd->plugin, LOG_DBG, "splice_cmd->psbt num sigs: %d", + (int)splice_cmd->psbt->inputs[0].signatures.num_items); + + if (psbt_contribs_changed(splice_cmd->psbt, psbt)) + for (size_t i = 0; i < tal_count(splice_cmd->states); i++) + if (splice_cmd->actions[i]->channel_id) + splice_cmd->states[i]->state = SPLICE_CMD_UPDATE_NEEDS_CHANGES; + + assert(psbt); + tal_free(splice_cmd->psbt); + splice_cmd->psbt = tal_steal(splice_cmd, psbt); + + tok = json_get_member(buf, result, "commitments_secured"); + if (!json_to_bool(buf, tok, &got_sigs)) + return command_fail_badparam(cmd, "commitments_secured", buf, + tok, "invalid bool"); + + if (old_state != SPLICE_CMD_UPDATE) + state->state = SPLICE_CMD_UPDATE; + else + state->state = got_sigs ? SPLICE_CMD_RECVED_SIGS : SPLICE_CMD_UPDATE_DONE; + + return continue_splice(splice_cmd->cmd, splice_cmd); +} + +static struct command_result *open_update(struct command *cmd, + struct splice_cmd *splice_cmd, + size_t index) +{ + struct splice_script_result *action = splice_cmd->actions[index]; + struct out_req *req; + struct splice_index_pkg *pkg = tal(cmd->plugin, struct splice_index_pkg); + + pkg->splice_cmd = splice_cmd; + pkg->index = index; + + plugin_log(cmd->plugin, LOG_DBG, + "open_update(channel_id:%s)", + fmt_channel_id(tmpctx, action->channel_id)); + + plugin_log(cmd->plugin, LOG_DBG, "psbt num sigs: %d", + (int)splice_cmd->psbt->inputs[0].signatures.num_items); + + req = jsonrpc_request_start(cmd, "openchannel_update", + open_update_get_result, splice_error_pkg, + pkg); + + json_add_channel_id(req->js, "channel_id", action->channel_id); + json_add_psbt(req->js, "psbt", splice_cmd->psbt); + + return send_outreq(req); +} + static struct command_result *signpsbt_get_result(struct command *cmd, const char *methodname, const char *buf, @@ -1215,6 +1370,13 @@ static struct command_result *splice_signed(struct command *cmd, struct out_req *req; struct splice_index_pkg *pkg; + plugin_log(cmd->plugin, LOG_DBG, + "splice_signed(channel_id:%s)", + fmt_channel_id(tmpctx, action->channel_id)); + + plugin_log(cmd->plugin, LOG_DBG, "psbt num sigs: %d", + (int)splice_cmd->psbt->inputs[0].signatures.num_items); + pkg = tal(cmd->plugin, struct splice_index_pkg); pkg->splice_cmd = splice_cmd; pkg->index = index; @@ -1230,6 +1392,51 @@ static struct command_result *splice_signed(struct command *cmd, return send_outreq(req); } +static struct command_result *open_signed_get_result(struct command *cmd, + const char *methodname, + const char *buf, + const jsmntok_t *result, + struct splice_index_pkg *pkg) +{ + size_t index = pkg->index; + struct splice_cmd *splice_cmd = pkg->splice_cmd; + const jsmntok_t *tok; + + tal_free(pkg); + + tok = json_get_member(buf, result, "txid"); + if (!json_to_txid(buf, tok, &splice_cmd->final_txid)) + return command_fail_badparam(cmd, "txid", buf, + tok, "invalid txid"); + + splice_cmd->states[index]->state = SPLICE_CMD_DONE; + + return continue_splice(splice_cmd->cmd, splice_cmd); +} + +static struct command_result *open_signed(struct command *cmd, + struct splice_cmd *splice_cmd, + size_t index) +{ + struct splice_script_result *action = splice_cmd->actions[index]; + struct out_req *req; + struct splice_index_pkg *pkg; + + pkg = tal(cmd->plugin, struct splice_index_pkg); + pkg->splice_cmd = splice_cmd; + pkg->index = index; + + req = jsonrpc_request_start(cmd, "openchannel_signed", + open_signed_get_result, + splice_signed_error_pkg, + pkg); + + json_add_channel_id(req->js, "channel_id", action->channel_id); + json_add_psbt(req->js, "signed_psbt", splice_cmd->psbt); + + return send_outreq(req); +} + static struct command_result *check_emergency_sat(struct command *cmd, struct splice_cmd *splice_cmd) { @@ -1705,20 +1912,28 @@ static struct command_result *continue_splice(struct command *cmd, state = splice_cmd->states[i]; if (state->state != SPLICE_CMD_NONE) continue; - if (!action->channel_id) + if (action->peer_id) + return open_init(cmd, splice_cmd, i); + else if (action->channel_id) + return splice_init(cmd, splice_cmd, i); + else return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS, "Internal error; should not get" " here with non-channels with state" " NONE"); - return splice_init(cmd, splice_cmd, i); } for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) { action = splice_cmd->actions[i]; state = splice_cmd->states[i]; if (state->state == SPLICE_CMD_INIT - || state->state == SPLICE_CMD_UPDATE_NEEDS_CHANGES) - return splice_update(cmd, splice_cmd, i); + || state->state == SPLICE_CMD_UPDATE_NEEDS_CHANGES) { + + if (action->peer_id) + return open_update(cmd, splice_cmd, i); + else if (action->channel_id) + return splice_update(cmd, splice_cmd, i); + } } /* It is possible to receive a signature when we do splice_update with @@ -1730,16 +1945,24 @@ static struct command_result *continue_splice(struct command *cmd, for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) { action = splice_cmd->actions[i]; state = splice_cmd->states[i]; - if (state->state == SPLICE_CMD_UPDATE) - return splice_update(cmd, splice_cmd, i); + if (state->state == SPLICE_CMD_UPDATE) { + if (action->peer_id) + return open_update(cmd, splice_cmd, i); + else if (action->channel_id) + return splice_update(cmd, splice_cmd, i); + } } /* The signpsbt operation also adds channel_ids to psbt */ if (splice_cmd->wallet_inputs_to_signed) return signpsbt(cmd, splice_cmd); - if (requires_our_sigs(splice_cmd, &index, &multiple_require_sigs)) - return splice_signed(cmd, splice_cmd, index); + if (requires_our_sigs(splice_cmd, &index, &multiple_require_sigs)) { + if (splice_cmd->actions[index]->peer_id) + return open_signed(cmd, splice_cmd, index); + else if (splice_cmd->actions[index]->channel_id) + return splice_signed(cmd, splice_cmd, index); + } if (multiple_require_sigs) return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS, @@ -1750,8 +1973,12 @@ static struct command_result *continue_splice(struct command *cmd, for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) { action = splice_cmd->actions[i]; state = splice_cmd->states[i]; - if (i != index && state->state == SPLICE_CMD_RECVED_SIGS) - return splice_signed(cmd, splice_cmd, i); + if (i != index && state->state == SPLICE_CMD_RECVED_SIGS) { + if (action->peer_id) + return open_signed(cmd, splice_cmd, i); + else if (action->channel_id) + return splice_signed(cmd, splice_cmd, i); + } } for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) @@ -1804,6 +2031,8 @@ static struct command_result *execute_splice(struct command *cmd, dest_count++; if (splice_cmd->actions[i]->onchain_wallet) dest_count++; + if (splice_cmd->actions[i]->peer_id) + dest_count++; if (dest_count < 1) return do_fail(cmd, splice_cmd, JSONRPC2_INVALID_PARAMS, "Must specify 1 destination per"); @@ -2020,10 +2249,18 @@ static struct command_result *handle_splice_cmd(struct command *cmd, struct splice_cmd *splice_cmd) { struct out_req *req; + int channels = 0; if (splice_cmd->dryrun) return splice_dryrun(cmd, splice_cmd); + for (size_t i = 0; i < tal_count(splice_cmd->actions); i++) + if (splice_cmd->actions[i]->channel_id) + channels++; + + if (!channels) + return execute_splice(splice_cmd->cmd, splice_cmd); + req = jsonrpc_request_start(cmd, "stfu_channels", stfu_channels_get_result, splice_error, splice_cmd); @@ -2091,12 +2328,14 @@ static struct command_result *listpeerchannels_get_result(struct command *cmd, tal_arr_expand(&channels, tal(channels, struct splice_script_chan)); + channels[i]->chan_id = tal(channels[i], struct channel_id); + err = json_scan(tmpctx, buf, jchannel, "{peer_id?:%,channel_id?:%}", JSON_SCAN(json_to_node_id, &channels[i]->node_id), JSON_SCAN(json_to_channel_id, - &channels[i]->chan_id)); + channels[i]->chan_id)); if (err) errx(1, "Bad listpeerchannels.channels %zu: %s", i, err); diff --git a/plugins/spender/splice.h b/plugins/spender/splice.h index f52777fe915e..525f27a5e400 100644 --- a/plugins/spender/splice.h +++ b/plugins/spender/splice.h @@ -38,7 +38,7 @@ struct splice_cmd { bool dryrun; /* Execute the splice and abort at the last moment */ bool wetrun; - /* Feerate queried from lightningd */ + /* Feerate provided, otherwise queried from lightningd */ u32 feerate_per_kw; /* Override max feerate */ bool force_feerate; diff --git a/tests/test_splice.py b/tests/test_splice.py index 2c20c2031a9c..5280efcba1c9 100644 --- a/tests/test_splice.py +++ b/tests/test_splice.py @@ -919,3 +919,23 @@ def test_easy_splice_out_into_channel(node_factory, bitcoind, chainparams): end_chan1_balance = Millisatoshi(bkpr_account_balance(l2, chan1)) assert initial_chan1_balance + Millisatoshi(spliceamt * 1000) == end_chan1_balance + + +@pytest.mark.openchannel('v1') +@pytest.mark.openchannel('v2') +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +def test_splicescript_open(node_factory, bitcoind, chainparams): + l1, l2 = node_factory.line_graph(2, fundamount=10000000, wait_for_announce=True, opts={'experimental-dual-fund': None}) + + l1.daemon.wait_for_log(r'DUALOPEND_AWAITING_LOCKIN to CHANNELD_NORMAL') + + print ("Performing the channel open splice script") + + print (l1.rpc.splice("peer(first).chan(first) -> 1M+fee; 1M -> peer(first).new()")) + + bitcoind.generate_block(6, wait_for_mempool=1) + + l1.daemon.wait_for_log(r'DUALOPEND_AWAITING_LOCKIN to CHANNELD_NORMAL') + + wait_for(lambda: len(l1.rpc.listfunds()['channels']) == 2) +