2020-12-16 04:18:00 +01:00
# include <bitcoin/chainparams.h>
# include <ccan/array_size/array_size.h>
# include <ccan/json_out/json_out.h>
2020-12-16 04:18:20 +01:00
# include <ccan/mem/mem.h>
2020-12-16 04:18:00 +01:00
# include <ccan/tal/str/str.h>
# include <ccan/time/time.h>
# include <common/blindedpath.h>
# include <common/bolt11.h>
# include <common/bolt12.h>
# include <common/bolt12_merkle.h>
# include <common/dijkstra.h>
# include <common/gossmap.h>
# include <common/json_stream.h>
# include <common/memleak.h>
2020-12-16 04:18:20 +01:00
# include <common/overflows.h>
2020-12-16 04:18:00 +01:00
# include <common/route.h>
# include <common/type_to_string.h>
# include <common/utils.h>
# include <errno.h>
# include <inttypes.h>
# include <plugins/libplugin.h>
2020-12-16 04:18:20 +01:00
# include <secp256k1_schnorrsig.h>
2020-12-16 04:18:00 +01:00
static struct gossmap * global_gossmap ;
static struct node_id local_id ;
2020-12-16 04:18:20 +01:00
static LIST_HEAD ( sent_list ) ;
2020-12-16 04:18:00 +01:00
struct sent {
2020-12-16 04:18:20 +01:00
/* We're in sent_invreqs, awaiting reply. */
struct list_node list ;
/* The blinding factor used by reply. */
struct pubkey reply_blinding ;
/* The command which sent us. */
struct command * cmd ;
2020-12-16 04:18:00 +01:00
/* The offer we are trying to get an invoice for. */
struct tlv_offer * offer ;
/* The invreq we sent. */
struct tlv_invoice_request * invreq ;
} ;
2020-12-16 04:18:20 +01:00
static struct sent * find_sent ( const struct pubkey * blinding )
{
struct sent * i ;
list_for_each ( & sent_list , i , list ) {
if ( pubkey_eq ( & i - > reply_blinding , blinding ) )
return i ;
}
return NULL ;
}
static const char * field_diff_ ( const tal_t * a , const tal_t * b ,
const char * fieldname )
{
/* One is set and the other isn't? */
if ( ( a = = NULL ) ! = ( b = = NULL ) )
return fieldname ;
if ( ! memeq ( a , tal_bytelen ( a ) , b , tal_bytelen ( b ) ) )
return fieldname ;
return NULL ;
}
# define field_diff(a, b, fieldname) \
field_diff_ ( a - > fieldname , b - > fieldname , # fieldname )
/* Returns true if b is a with something appended. */
static bool description_is_appended ( const char * a , const char * b )
{
if ( ! a | | ! b )
return false ;
if ( tal_bytelen ( b ) < tal_bytelen ( a ) )
return false ;
return memeq ( a , tal_bytelen ( a ) , b , tal_bytelen ( a ) ) ;
}
/* Hack to suppress warnings when we finish a different command */
static void discard_result ( struct command_result * ret )
{
}
static struct command_result * recv_onion_message ( struct command * cmd ,
const char * buf ,
const jsmntok_t * params )
{
const jsmntok_t * om , * invtok , * blindingtok ;
const u8 * invbin ;
size_t len ;
struct tlv_invoice * inv ;
struct sent * sent ;
struct sha256 merkle , sighash ;
struct json_stream * out ;
const char * badfield ;
struct pubkey blinding ;
u64 * expected_amount ;
plugin_log ( cmd - > plugin , LOG_INFORM , " Received onion message: %.*s " ,
json_tok_full_len ( params ) ,
json_tok_full ( buf , params ) ) ;
om = json_get_member ( buf , params , " onion_message " ) ;
invtok = json_get_member ( buf , om , " invoice " ) ;
if ( ! invtok )
return command_hook_success ( cmd ) ;
blindingtok = json_get_member ( buf , om , " blinding_in " ) ;
if ( ! blindingtok | | ! json_to_pubkey ( buf , blindingtok , & blinding ) )
return command_hook_success ( cmd ) ;
sent = find_sent ( & blinding ) ;
if ( ! sent ) {
plugin_log ( cmd - > plugin , LOG_DBG ,
" No match for received invoice %.*s " ,
json_tok_full_len ( invtok ) ,
json_tok_full ( buf , invtok ) ) ;
return command_hook_success ( cmd ) ;
}
/* From here on, we know it's genuine, so we will fail the
* fetchinvoice command if the invoice is invalid */
invbin = json_tok_bin_from_hex ( cmd , buf , invtok ) ;
len = tal_bytelen ( invbin ) ;
inv = tlv_invoice_new ( cmd ) ;
if ( ! fromwire_invoice ( & invbin , & len , inv ) ) {
badfield = " invoice " ;
goto badinv ;
}
/* BOLT-offers #12:
* - MUST reject the invoice unless ` node_id ` is equal to the offer .
*/
if ( ! pubkey32_eq ( sent - > offer - > node_id , inv - > node_id ) ) {
badfield = " node_id " ;
goto badinv ;
}
/* BOLT-offers #12:
* - MUST reject the invoice if ` signature ` is not a valid signature
* using ` node_id ` as described in [ Signature Calculation ]
*/
merkle_tlv ( inv - > fields , & merkle ) ;
sighash_from_merkle ( " invoice " , " signature " , & merkle , & sighash ) ;
if ( ! inv - > signature
| | secp256k1_schnorrsig_verify ( secp256k1_ctx , inv - > signature - > u8 ,
sighash . u . u8 , & inv - > node_id - > pubkey ) ! = 1 ) {
badfield = " signature " ;
goto badinv ;
}
/* BOLT-offers #12:
* - MUST reject the invoice if ` msat ` is not present .
*/
if ( ! inv - > amount ) {
badfield = " amount " ;
goto badinv ;
}
/* BOLT-offers #12:
* - MUST reject the invoice unless ` offer_id ` is equal to the id of the
* offer .
*/
if ( ( badfield = field_diff ( sent - > invreq , inv , offer_id ) ) )
goto badinv ;
/* BOLT-offers #12:
* - if the invoice is a reply to an ` invoice_request ` :
* . . .
* - MUST reject the invoice unless the following fields are equal or
* unset exactly as they are in the ` invoice_request : `
* - ` quantity `
* - ` recurrence_counter `
* - ` recurrence_start `
* - ` payer_key `
* - ` payer_info `
*/
if ( ( badfield = field_diff ( sent - > invreq , inv , quantity ) ) )
goto badinv ;
if ( ( badfield = field_diff ( sent - > invreq , inv , recurrence_counter ) ) )
goto badinv ;
if ( ( badfield = field_diff ( sent - > invreq , inv , recurrence_start ) ) )
goto badinv ;
if ( ( badfield = field_diff ( sent - > invreq , inv , payer_key ) ) )
goto badinv ;
if ( ( badfield = field_diff ( sent - > invreq , inv , payer_info ) ) )
goto badinv ;
/* Get the amount we expected. */
if ( sent - > offer - > amount & & ! sent - > offer - > currency ) {
expected_amount = tal ( tmpctx , u64 ) ;
* expected_amount = * sent - > offer - > amount ;
if ( sent - > invreq - > quantity ) {
/* We should never have sent this! */
if ( mul_overflows_u64 ( * expected_amount ,
* sent - > invreq - > quantity ) ) {
badfield = " quantity overflow " ;
goto badinv ;
}
* expected_amount * = * sent - > invreq - > quantity ;
}
} else
expected_amount = NULL ;
/* BOLT-offers #12:
* - SHOULD confirm authorization if the ` description ` does not exactly
* match the ` offer `
* - MAY highlight if ` description ` has simply had a change appended .
*/
/* We highlight these changes to the caller, for them to handle */
out = jsonrpc_stream_success ( sent - > cmd ) ;
json_add_string ( out , " invoice " , invoice_encode ( tmpctx , inv ) ) ;
json_object_start ( out , " changes " ) ;
if ( field_diff ( sent - > offer , inv , description ) ) {
/* Did they simply append? */
if ( description_is_appended ( sent - > offer - > description ,
inv - > description ) ) {
size_t off = tal_bytelen ( sent - > offer - > description ) ;
json_add_stringn ( out , " description_appended " ,
inv - > description + off ,
tal_bytelen ( inv - > description ) - off ) ;
} else if ( ! inv - > description )
json_add_stringn ( out , " description_removed " ,
sent - > offer - > description ,
tal_bytelen ( sent - > offer - > description ) ) ;
else
json_add_stringn ( out , " description " ,
inv - > description ,
tal_bytelen ( inv - > description ) ) ;
}
/* BOLT-offers #12:
* - SHOULD confirm authorization if ` vendor ` does not exactly
* match the ` offer `
*/
if ( field_diff ( sent - > offer , inv , vendor ) ) {
if ( ! inv - > vendor )
json_add_stringn ( out , " vendor_removed " ,
sent - > offer - > vendor ,
tal_bytelen ( sent - > offer - > vendor ) ) ;
else
json_add_stringn ( out , " vendor " ,
inv - > vendor ,
tal_bytelen ( inv - > vendor ) ) ;
}
/* BOLT-offers #12:
* - SHOULD confirm authorization if ` msat ` is not within the amount
* range authorized .
*/
/* We always tell them this unless it's trivial to calc and
* exactly as expected . */
if ( ! expected_amount | | * inv - > amount ! = * expected_amount )
json_add_amount_msat_only ( out , " msat " ,
amount_msat ( * inv - > amount ) ) ;
json_object_end ( out ) ;
discard_result ( command_finished ( sent - > cmd , out ) ) ;
return command_hook_success ( cmd ) ;
badinv :
plugin_log ( cmd - > plugin , LOG_DBG , " Failed invoice due to %s " , badfield ) ;
discard_result ( command_fail ( sent - > cmd ,
OFFER_BAD_INVREQ_REPLY ,
" Incorrect %s field in %.*s " ,
badfield ,
json_tok_full_len ( invtok ) ,
json_tok_full ( buf , invtok ) ) ) ;
return command_hook_success ( cmd ) ;
}
static void destroy_sent ( struct sent * sent )
{
list_del ( & sent - > list ) ;
}
2020-12-16 04:18:00 +01:00
static struct command_result * sendonionmsg_done ( struct command * cmd ,
const char * buf UNUSED ,
const jsmntok_t * result UNUSED ,
struct sent * sent )
{
2020-12-16 04:18:20 +01:00
/* FIXME: timeout! */
sent - > cmd = cmd ;
list_add_tail ( & sent_list , & sent - > list ) ;
tal_add_destructor ( sent , destroy_sent ) ;
2020-12-16 04:18:00 +01:00
return command_still_pending ( cmd ) ;
}
static void init_gossmap ( struct plugin * plugin )
{
global_gossmap
= notleak_with_children ( gossmap_load ( NULL ,
GOSSIP_STORE_FILENAME ) ) ;
if ( ! global_gossmap )
plugin_err ( plugin , " Could not load gossmap %s: %s " ,
GOSSIP_STORE_FILENAME , strerror ( errno ) ) ;
}
static struct gossmap * get_gossmap ( struct plugin * plugin )
{
if ( ! global_gossmap )
init_gossmap ( plugin ) ;
else
gossmap_refresh ( global_gossmap ) ;
return global_gossmap ;
}
static struct command_result * param_offer ( struct command * cmd ,
const char * name ,
const char * buffer ,
const jsmntok_t * tok ,
struct tlv_offer * * offer )
{
char * fail ;
/* BOLT-offers #12:
* - if ` features ` contains unknown _odd_ bits that are non - zero :
* - MUST ignore the bit .
* - if ` features ` contains unknown _even_ bits that are non - zero :
* - MUST NOT respond to the offer .
* - SHOULD indicate the unknown bit to the user .
*/
/* BOLT-offers #12:
* - MUST NOT set or imply any ` chain_hash ` not set or implied by
* the offer .
*/
* offer = offer_decode ( cmd , buffer + tok - > start , tok - > end - tok - > start ,
plugin_feature_set ( cmd - > plugin ) , chainparams ,
& fail ) ;
if ( ! * offer )
return command_fail_badparam ( cmd , name , buffer , tok ,
tal_fmt ( cmd ,
" Unparsable offer: %s " ,
fail ) ) ;
/* BOLT-offers #12:
*
* - if ` node_id ` , ` description ` or ` signature ` is not set :
* - MUST NOT respond to the offer .
*/
/* Note: offer_decode checks `signature` */
if ( ! ( * offer ) - > node_id )
return command_fail_badparam ( cmd , name , buffer , tok ,
" Offer does not contain a node_id " ) ;
if ( ! ( * offer ) - > description )
return command_fail_badparam ( cmd , name , buffer , tok ,
" Offer does not contain a description " ) ;
return NULL ;
}
static bool can_carry_onionmsg ( const struct gossmap * map ,
const struct gossmap_chan * c ,
int dir ,
struct amount_msat amount UNUSED ,
void * arg UNUSED )
{
const struct gossmap_node * n ;
/* Don't use it if either side says it's disabled */
if ( ! c - > half [ dir ] . enabled | | ! c - > half [ ! dir ] . enabled )
return false ;
/* Check features of recipient */
n = gossmap_nth_node ( map , c , ! dir ) ;
return n & & gossmap_node_get_feature ( map , n , OPT_ONION_MESSAGES ) ! = - 1 ;
}
/* make_blindedpath only needs pubkeys */
static const struct pubkey * route_backwards ( const tal_t * ctx ,
const struct gossmap * gossmap ,
struct route * * r )
{
struct pubkey * rarr ;
rarr = tal_arr ( ctx , struct pubkey , tal_count ( r ) ) ;
for ( size_t i = 0 ; i < tal_count ( r ) ; i + + ) {
const struct gossmap_node * dst ;
struct node_id id ;
dst = gossmap_nth_node ( gossmap , r [ i ] - > c , r [ i ] - > dir ) ;
gossmap_node_get_id ( gossmap , dst , & id ) ;
/* We're going backwards */
if ( ! pubkey_from_node_id ( & rarr [ tal_count ( rarr ) - 1 - i ] , & id ) )
abort ( ) ;
}
return rarr ;
}
static struct command_result * send_message ( struct command * cmd ,
struct sent * sent ,
const char * msgfield ,
const u8 * msgval )
{
const struct dijkstra * dij ;
const struct gossmap_node * dst , * src ;
struct route * * r ;
struct gossmap * gossmap = get_gossmap ( cmd - > plugin ) ;
const struct pubkey * backwards ;
struct onionmsg_path * * path ;
2020-12-16 04:18:20 +01:00
struct pubkey blinding ;
2020-12-16 04:18:00 +01:00
struct out_req * req ;
struct node_id dstid ;
/* FIXME: Use blinded path if avail. */
gossmap_guess_node_id ( gossmap , sent - > offer - > node_id , & dstid ) ;
dst = gossmap_find_node ( gossmap , & dstid ) ;
if ( ! dst )
return command_fail ( cmd , LIGHTNINGD ,
" Unknown destination %s " ,
type_to_string ( tmpctx , struct node_id ,
& dstid ) ) ;
/* If we don't exist in gossip, routing can't happen. */
src = gossmap_find_node ( gossmap , & local_id ) ;
if ( ! src )
return command_fail ( cmd , PAY_ROUTE_NOT_FOUND ,
" We don't have any channels " ) ;
dij = dijkstra ( tmpctx , gossmap , dst , AMOUNT_MSAT ( 0 ) , 0 ,
can_carry_onionmsg , route_score_shorter , NULL ) ;
r = route_from_dijkstra ( tmpctx , gossmap , dij , src ) ;
if ( ! r )
/* FIXME: We need to retry kind of like keysend here... */
return command_fail ( cmd , OFFER_ROUTE_NOT_FOUND ,
" Can't find route " ) ;
/* Ok, now make reply for onion_message */
backwards = route_backwards ( tmpctx , gossmap , r ) ;
2020-12-16 04:18:20 +01:00
path = make_blindedpath ( tmpctx , backwards , & blinding ,
& sent - > reply_blinding ) ;
2020-12-16 04:18:00 +01:00
req = jsonrpc_request_start ( cmd - > plugin , cmd , " sendonionmessage " ,
& sendonionmsg_done ,
& forward_error ,
sent ) ;
json_array_start ( req - > js , " hops " ) ;
for ( size_t i = 0 ; i < tal_count ( r ) ; i + + ) {
struct node_id id ;
json_object_start ( req - > js , NULL ) ;
gossmap_node_get_id ( gossmap ,
gossmap_nth_node ( gossmap , r [ i ] - > c , ! r [ i ] - > dir ) ,
& id ) ;
json_add_node_id ( req - > js , " id " , & id ) ;
if ( i = = tal_count ( r ) - 1 )
json_add_hex_talarr ( req - > js , msgfield , msgval ) ;
json_object_end ( req - > js ) ;
}
json_array_end ( req - > js ) ;
json_object_start ( req - > js , " reply_path " ) ;
json_add_pubkey ( req - > js , " blinding " , & blinding ) ;
json_array_start ( req - > js , " path " ) ;
for ( size_t i = 0 ; i < tal_count ( path ) ; i + + ) {
json_object_start ( req - > js , NULL ) ;
json_add_pubkey ( req - > js , " id " , & path [ i ] - > node_id ) ;
if ( path [ i ] - > enctlv )
json_add_hex_talarr ( req - > js , " enctlv " , path [ i ] - > enctlv ) ;
json_object_end ( req - > js ) ;
}
json_array_end ( req - > js ) ;
json_object_end ( req - > js ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
static struct command_result * invreq_done ( struct command * cmd ,
const char * buf ,
const jsmntok_t * result ,
struct tlv_offer * offer )
{
const jsmntok_t * t ;
struct sent * sent ;
char * fail ;
u8 * rawinvreq ;
/* We need to remember both offer and invreq to check reply. */
sent = tal ( cmd , struct sent ) ;
sent - > offer = tal_steal ( sent , offer ) ;
/* Get invoice request */
t = json_get_member ( buf , result , " bolt12 " ) ;
if ( ! t )
return command_fail ( cmd , LIGHTNINGD ,
" Missing bolt12 %.*s " ,
json_tok_full_len ( result ) ,
json_tok_full ( buf , result ) ) ;
plugin_log ( cmd - > plugin , LOG_DBG ,
" invoice_request: %.*s " ,
json_tok_full_len ( t ) ,
json_tok_full ( buf , t ) ) ;
sent - > invreq = invrequest_decode ( sent ,
buf + t - > start ,
t - > end - t - > start ,
plugin_feature_set ( cmd - > plugin ) ,
chainparams ,
& fail ) ;
if ( ! sent - > invreq )
return command_fail ( cmd , LIGHTNINGD ,
" Invalid invoice_request %.*s: %s " ,
json_tok_full_len ( t ) ,
json_tok_full ( buf , t ) ,
fail ) ;
rawinvreq = tal_arr ( tmpctx , u8 , 0 ) ;
towire_invoice_request ( & rawinvreq , sent - > invreq ) ;
return send_message ( cmd , sent , " invoice_request " , rawinvreq ) ;
}
/* Fetches an invoice for this offer, and makes sure it corresponds. */
static struct command_result * json_fetchinvoice ( struct command * cmd ,
const char * buffer ,
const jsmntok_t * params )
{
struct tlv_offer * offer ;
struct amount_msat * msat ;
const char * rec_label ;
struct out_req * req ;
struct tlv_invoice_request * invreq ;
invreq = tlv_invoice_request_new ( cmd ) ;
if ( ! param ( cmd , buffer , params ,
p_req ( " offer " , param_offer , & offer ) ,
p_opt ( " msatoshi " , param_msat , & msat ) ,
p_opt ( " quantity " , param_u64 , & invreq - > quantity ) ,
p_opt ( " recurrence_counter " , param_number ,
& invreq - > recurrence_counter ) ,
p_opt ( " recurrence_start " , param_number ,
& invreq - > recurrence_start ) ,
p_opt ( " recurrence_label " , param_string , & rec_label ) ,
NULL ) )
return command_param_failed ( ) ;
/* BOLT-offers #12:
* - MUST set ` offer_id ` to the merkle root of the offer as described
* in [ Signature Calculation ] ( # signature - calculation ) .
*/
invreq - > offer_id = tal ( invreq , struct sha256 ) ;
merkle_tlv ( offer - > fields , invreq - > offer_id ) ;
/* Check if they are trying to send us money. */
if ( offer - > send_invoice )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" Offer wants an invoice, not invoice_request " ) ;
/* BOLT-offers #12:
* - SHOULD not respond to an offer if the current time is after
* ` absolute_expiry ` .
*/
if ( offer - > absolute_expiry
& & time_now ( ) . ts . tv_sec > * offer - > absolute_expiry )
return command_fail ( cmd , OFFER_EXPIRED , " Offer expired " ) ;
/* BOLT-offers #12:
* - if the offer did not specify ` amount ` :
* - MUST specify ` amount ` . ` msat ` in multiples of the minimum
* lightning - payable unit ( e . g . milli - satoshis for bitcoin ) for the
* first ` chains ` entry .
* - otherwise :
* - MUST NOT set ` amount `
*/
if ( offer - > amount ) {
if ( msat )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" msatoshi parameter unnecessary " ) ;
} else {
if ( ! msat )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" msatoshi parameter required " ) ;
invreq - > amount = tal_dup ( invreq , u64 ,
& msat - > millisatoshis ) ; /* Raw: tu64 */
}
/* BOLT-offers #12:
* - if the offer had a ` quantity_min ` or ` quantity_max ` field :
* - MUST set ` quantity `
* - MUST set it within that ( inclusive ) range .
* - otherwise :
* - MUST NOT set ` quantity `
*/
if ( offer - > quantity_min | | offer - > quantity_max ) {
if ( ! invreq - > quantity )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" quantity parameter required " ) ;
if ( offer - > quantity_min
& & * invreq - > quantity < * offer - > quantity_min )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" quantity must be >= % " PRIu64 ,
* offer - > quantity_min ) ;
if ( offer - > quantity_max
& & * invreq - > quantity > * offer - > quantity_max )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" quantity must be <= % " PRIu64 ,
* offer - > quantity_max ) ;
} else {
if ( invreq - > quantity )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" quantity parameter unnecessary " ) ;
}
/* BOLT-offers #12:
* - if the offer contained ` recurrence ` :
*/
if ( offer - > recurrence ) {
/* BOLT-offers #12:
* - for the initial request :
* . . .
* - MUST set ` recurrence_counter ` ` counter ` to 0.
*/
/* BOLT-offers #12:
* - for any successive requests :
* . . .
* - MUST set ` recurrence_counter ` ` counter ` to one greater
* than the highest - paid invoice .
*/
if ( ! invreq - > recurrence_counter )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" needs recurrence_counter " ) ;
/* BOLT-offers #12:
* - if the offer contained ` recurrence_base ` with
* ` start_any_period ` non - zero :
* - MUST include ` recurrence_start `
* . . .
* - otherwise :
* - MUST NOT include ` recurrence_start `
*/
if ( offer - > recurrence_base
& & offer - > recurrence_base - > start_any_period ) {
if ( ! invreq - > recurrence_start )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" needs recurrence_start " ) ;
} else {
if ( invreq - > recurrence_start )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" unnecessary recurrence_start " ) ;
}
/* recurrence_label uniquely identifies this series of
* payments . */
if ( ! rec_label )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" needs recurrence_label " ) ;
/* FIXME! */
/* BOLT-offers #12:
* - SHOULD NOT send an ` invoice_request ` for a period which has
* already passed .
*/
/* If there's no recurrence_base, we need the initial payment
* for this . . . */
} else {
/* BOLT-offers #12:
* - otherwise :
* - MUST NOT set ` recurrence_counter ` .
* . . .
* - MUST NOT set ` recurrence_start `
*/
if ( invreq - > recurrence_counter )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" unnecessary recurrence_counter " ) ;
if ( invreq - > recurrence_start )
return command_fail ( cmd , JSONRPC2_INVALID_PARAMS ,
" unnecessary recurrence_start " ) ;
}
/* BOLT-offers #12:
*
* - if the chain for the invoice is not solely bitcoin :
* - MUST specify ` chains ` the offer is valid for .
* - otherwise :
* - the bitcoin chain is implied as the first and only entry .
*/
if ( ! streq ( chainparams - > network_name , " bitcoin " ) ) {
invreq - > chains = tal_arr ( invreq , struct bitcoin_blkid , 1 ) ;
invreq - > chains [ 0 ] = chainparams - > genesis_blockhash ;
}
invreq - > features
= plugin_feature_set ( cmd - > plugin ) - > bits [ BOLT11_FEATURE ] ;
/* Make the invoice request (fills in payer_key and payer_info) */
req = jsonrpc_request_start ( cmd - > plugin , cmd , " createinvoicerequest " ,
& invreq_done ,
& forward_error ,
offer ) ;
json_add_string ( req - > js , " bolt12 " , invrequest_encode ( tmpctx , invreq ) ) ;
if ( rec_label )
json_add_string ( req - > js , " recurrence_label " , rec_label ) ;
return send_outreq ( cmd - > plugin , req ) ;
}
static const struct plugin_command commands [ ] = { {
" fetchinvoice " ,
" payment " ,
" Request remote node for an invoice for this {offer}, with {amount}, {quanitity}, {recurrence_counter}, {recurrence_start} and {recurrence_label} iff required. " ,
NULL ,
json_fetchinvoice ,
}
} ;
static void init ( struct plugin * p , const char * buf UNUSED ,
const jsmntok_t * config UNUSED )
{
const char * field ;
field = rpc_delve ( tmpctx , p , " getinfo " ,
take ( json_out_obj ( NULL , NULL , NULL ) ) , " .id " ) ;
if ( ! node_id_from_hexstr ( field , strlen ( field ) , & local_id ) )
plugin_err ( p , " getinfo didn't contain valid id: '%s' " , field ) ;
}
2020-12-16 04:18:20 +01:00
static const struct plugin_hook hooks [ ] = {
{
" onion_message_blinded " ,
recv_onion_message
} ,
} ;
2020-12-16 04:18:00 +01:00
int main ( int argc , char * argv [ ] )
{
setup_locale ( ) ;
plugin_main ( argv , init , PLUGIN_RESTARTABLE , true , NULL ,
commands , ARRAY_SIZE ( commands ) ,
/* No notifications */
NULL , 0 ,
2020-12-16 04:18:20 +01:00
hooks , ARRAY_SIZE ( hooks ) ,
2020-12-16 04:18:00 +01:00
/* No options */
NULL ) ;
}