2024-07-12 21:16:36 -07:00
# This script is used to re-generate all RPC examples for methods listed in doc/schemas/lightning-*.json schema files.
# It uses the pre existing test setup to start nodes, fund channels and execute other RPC calls to generate these examples.
# This test will only run with GENERATE_EXAMPLES=True setup to avoid accidental overwriting of examples with other test executions.
# Set the test TIMEOUT to more than 3 seconds to avoid failures while waiting for the bitcoind response. The `dev-bitcoind-poll` is set to 3 seconds, so a shorter timeout may lead to test failures.
# Note: Different nodes are used to record examples depending upon the availability, quality and volume of the data. For example: Node l1 has been used to listsendpays and l2 for listforwards.
from fixtures import * # noqa: F401,F403
from fixtures import TEST_NETWORK
from io import BytesIO
from pyln . client import RpcError , Millisatoshi
from pyln . proto . onion import TlvPayload
from utils import only_one , mine_funding_to_announce , sync_blockheight , wait_for , first_scid
import os
import re
import time
import pytest
import unittest
import json
import logging
import ast
import struct
import subprocess
CWD = os . getcwd ( )
REGENERATING_RPCS = [ ]
ALL_METHOD_NAMES = [ ]
RPCS_STATUS = [ ]
ALL_RPC_EXAMPLES = { }
GENERATE_EXAMPLES = True
FUND_WALLET_AMOUNT_SAT = 200000000
FUND_CHANNEL_AMOUNT_SAT = 10 * * 6
LOG_FILE = ' autogenerate-examples-status.log '
if os . path . exists ( LOG_FILE ) :
open ( LOG_FILE , ' w ' ) . close ( )
logging . basicConfig ( level = logging . INFO ,
format = ' %(levelname)s - %(message)s ' ,
handlers = [
# logging.FileHandler(LOG_FILE),
logging . StreamHandler ( )
] )
logger = logging . getLogger ( __name__ )
class TaskFinished ( Exception ) :
def __init__ ( self , message ) :
self . message = message
super ( ) . __init__ ( self . message )
def update_example ( node , method , params , res = None , description = None , execute = True , filename = None ) :
""" Update examples in JSON files with rpc calls and responses """
try :
def replace_local_paths ( data , replacements ) :
""" Replace local paths in JSON objects """
try :
# For dictionary or list, recursively replace paths
if isinstance ( data , dict ) :
return { k : replace_local_paths ( v , replacements ) for k , v in data . items ( ) }
elif isinstance ( data , list ) :
return [ replace_local_paths ( v , replacements ) for v in data ]
# Replace when it is string
elif isinstance ( data , str ) :
for old_path , new_path in replacements :
data = re . sub ( old_path , new_path , data )
return data
# For other data types, return as is
else :
return data
except Exception as e :
logger . error ( f ' Error in replacing local paths: { e } ' )
def replace_with_example_values ( schema , res , idx ) :
""" Replace the response values with the ' example_values ' from the schema """
def update_value ( schema , res , idx ) :
if isinstance ( res , dict ) :
for key , value in res . items ( ) :
if key in schema . get ( ' properties ' , { } ) :
prop_schema = schema [ ' properties ' ] [ key ]
if ' example_values ' in prop_schema :
if prop_schema [ ' example_values ' ] [ idx ] :
res [ key ] = prop_schema [ ' example_values ' ] [ idx ]
else :
update_value ( prop_schema , value , idx )
elif isinstance ( res , list ) :
for index , item in enumerate ( res ) :
if ' items ' in schema :
update_value ( schema [ ' items ' ] , item , idx )
update_value ( schema [ ' response ' ] , res , idx )
return res
def format_json_with_jq ( json_data ) :
""" Formats the JSON data with jq to avoid check-fmt-schemas errors.
It is because check - fmt - schemas uses jq to format the JSON data and compare the difference .
For example , jq will convert 18446744073709551685 to 18446744073709552000 before comparing .
JQ behaves this way because it uses C doubles to represent numbers , and on pretty much all
modern systems that ' s an IEEE 754 double, which can only represent integers without loss
between - 2 ^ 53. .2 ^ 53. 125276004817190914 is about 14 times larger than the largest integer
that jq can represent losslessly , therefore jq can only approximate it .
Reference : https : / / github . com / jqlang / jq / issues / 369
"""
jq_command = ' jq . '
if not isinstance ( json_data , str ) :
json_data = json . dumps ( json_data )
# Run the jq command and capture the output
result = subprocess . run (
jq_command ,
input = json_data ,
text = True ,
capture_output = True ,
shell = True
)
if result . returncode != 0 :
logger . error ( f " Error running jq: { result . stderr } " )
return json . loads ( result . stdout )
global CWD , ALL_RPC_EXAMPLES , REGENERATING_RPCS , RPCS_STATUS
# Usually file name is same as method name, but `sql` is an exception;
# For sql, the `sql-template` file should be updated with examples then this template with finally generate the sql file with tables
# See doc/Makefile `doc/schemas/lightning-sql.json` for more details
file_path = os . path . join ( CWD , ' doc ' , ' schemas ' , f ' lightning- { method } .json ' ) if filename is None else os . path . join ( CWD , ' doc ' , ' schemas ' , f ' lightning- { filename } .json ' )
with open ( file_path , ' r+ ' , encoding = ' utf-8 ' ) as file :
schema = json . load ( file )
method_id = len ( schema [ ' examples ' ] ) + 1 if ' examples ' in schema else 1
req = {
' id ' : f ' example: { method } # { method_id } ' ,
' method ' : method ,
' params ' : params
}
logger . info ( f ' Method \' { method } \' , Params { params } ' )
# Execute the RPC call and get the response
if execute :
res = node . rpc . call ( method , params )
logger . info ( f ' { method } response: { res } ' )
# Return response without updating the file because user doesn't want to update the example
2024-08-25 09:52:34 -04:00
# Executing the method and returning the response is useful for further example updates
2024-07-12 21:16:36 -07:00
if method not in REGENERATING_RPCS :
return res
else :
# Replace local path in the request with default path
if method == ' plugin ' and ' plugin ' in req [ ' params ' ] :
req [ ' params ' ] [ ' plugin ' ] = req [ ' params ' ] [ ' plugin ' ] . replace ( CWD , ' /root/lightning ' )
methods_to_replace_path = [ ' commando ' , ' listconfigs ' , ' plugin ' ]
# Replace local paths in responses to ensure the example's consistency for different users
if method in methods_to_replace_path :
replacements = [
( CWD , ' /root/lightning ' ) ,
( r ' /tmp/ltests-[^/]+/test_generate_examples_[^/]+/lightning-[^/]+ ' , ' /tmp/.lightning ' )
]
res = replace_local_paths ( res , replacements )
# Format the JSON data with jq to avoid check-fmt-schemas errors
res = format_json_with_jq ( res )
res = replace_with_example_values ( schema , res , method_id - 1 )
# Create the example key with description, request & response
schema . setdefault ( ' examples ' , [ ] ) . append ( { ' request ' : req , ' response ' : res } if description is None else { ' description ' : description , ' request ' : req , ' response ' : res } )
# Update the file with the new example
file . seek ( 0 )
json . dump ( schema , file , indent = 2 , ensure_ascii = False )
file . write ( ' \n ' )
file . truncate ( )
logger . info ( f ' Updated { method } # { method_id } ' )
for rpc in ALL_RPC_EXAMPLES :
if rpc [ ' method ' ] == method :
rpc [ ' executed ' ] + = 1
if rpc [ ' executed ' ] == rpc [ ' num_examples ' ] :
RPCS_STATUS [ REGENERATING_RPCS . index ( method ) ] = True
break
# Exit if listed commands have been executed
if all ( RPCS_STATUS ) :
raise TaskFinished ( ' All Done!!! ' )
return res
except FileNotFoundError as fnf_error :
logger . error ( f ' File not found error { fnf_error } at: { file_path } ' )
def setup_test_nodes ( node_factory , bitcoind ) :
""" Sets up six test nodes for various transaction scenarios:
l1 , l2 , l3 for transactions and forwards
l4 for complex transactions ( sendpayment , keysend , renepay )
l5 for keysend with routehints and channel backup & recovery
l5 , l6 for backup and recovery
l7 , l8 for splicing ( added later )
l9 , l10 for low level fundchannel examples ( added later )
l11 , l12 for low level openchannel examples ( added later )
l13 for recover ( added later )
l1 - > l2 , l2 - > l3 , l3 - > l4 , l2 - > l5 ( unannounced ) , l9 - > l10 , l11 - > l12
l1 . info [ ' id ' ] : 0266e4598 d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518
l2 . info [ ' id ' ] : 022 d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d59
l3 . info [ ' id ' ] : 035 d2b1192dfba134e10e540875d366ebc8bc353d5aa766b80c090b39c3a5d885d
l4 . info [ ' id ' ] : 0382 ce59ebf18be7d84677c2e35f23294b9992ceca95491fcf8a56c6cb2d9de199
l5 . info [ ' id ' ] : 032 cf15d1ad9c4a08d26eab1918f732d8ef8fdc6abb9640bf3db174372c491304e
l6 . info [ ' id ' ] : 0265 b6ab5ec860cd257865d61ef0bbf5b3339c36cbda8b26b74e7f1dca490b6518
"""
try :
global FUND_WALLET_AMOUNT_SAT , FUND_CHANNEL_AMOUNT_SAT
options = [
{
' experimental-dual-fund ' : None ,
' experimental-offers ' : None ,
' may_reconnect ' : True ,
' dev-hsmd-no-preapprove-check ' : None ,
' allow-deprecated-apis ' : True ,
' allow_bad_gossip ' : True ,
' broken_log ' : ' .* ' , # plugin-topology: DEPRECATED API USED: *, lightningd-3: had memleak messages, lightningd: MEMLEAK:, lightningd: init_cupdate for unknown scid etc.
' dev-bitcoind-poll ' : 3 , # Default 1; increased to avoid rpc failures
} . copy ( )
for i in range ( 6 )
]
l1 , l2 , l3 , l4 , l5 , l6 = node_factory . get_nodes ( 6 , opts = options )
# Upgrade wallet
# Write the data/p2sh_wallet_hsm_secret to the hsm_path, so node can spend funds at p2sh_wrapped_addr
p2sh_wrapped_addr = ' 2N2V4ee2vMkiXe5FSkRqFjQhiS9hKqNytv3 '
update_example ( node = l1 , method = ' upgradewallet ' , params = { } )
txid = bitcoind . rpc . sendtoaddress ( p2sh_wrapped_addr , 20000000 / 10 * * 8 )
bitcoind . generate_block ( 1 )
l1 . daemon . wait_for_log ( ' Owning output .* txid {} CONFIRMED ' . format ( txid ) )
# Doing it with 'reserved ok' should have 1. We use a big feerate so we can get over the RBF hump
update_example ( node = l1 , method = ' upgradewallet ' , params = { ' feerate ' : ' urgent ' , ' reservedok ' : True } )
# Fund node wallets for further transactions
fund_nodes = [ l1 , l2 , l3 , l4 , l5 ]
for node in fund_nodes :
node . fundwallet ( FUND_WALLET_AMOUNT_SAT )
# Connect nodes and fund channels
update_example ( node = l2 , method = ' getinfo ' , params = { } )
update_example ( node = l1 , method = ' connect ' , params = { ' id ' : l2 . info [ ' id ' ] , ' host ' : ' localhost ' , ' port ' : l2 . daemon . port } )
update_example ( node = l2 , method = ' connect ' , params = { ' id ' : l3 . info [ ' id ' ] , ' host ' : ' localhost ' , ' port ' : l3 . daemon . port } )
l3 . rpc . connect ( l4 . info [ ' id ' ] , ' localhost ' , l4 . port )
l2 . rpc . connect ( l5 . info [ ' id ' ] , ' localhost ' , l5 . port )
c12 , _ = l1 . fundchannel ( l2 , FUND_CHANNEL_AMOUNT_SAT )
c23 , c23res = l2 . fundchannel ( l3 , FUND_CHANNEL_AMOUNT_SAT )
c34 , _ = l3 . fundchannel ( l4 , FUND_CHANNEL_AMOUNT_SAT )
c25 , _ = l2 . fundchannel ( l5 , announce_channel = False )
mine_funding_to_announce ( bitcoind , [ l1 , l2 , l3 , l4 ] )
l1 . wait_channel_active ( c12 )
l1 . wait_channel_active ( c23 )
l1 . wait_channel_active ( c34 )
# Balance these newly opened channels
l1 . rpc . pay ( l2 . rpc . invoice ( ' 500000sat ' , ' lbl balance l1 to l2 ' , ' description send some sats l1 to l2 ' ) [ ' bolt11 ' ] )
l2 . rpc . pay ( l3 . rpc . invoice ( ' 500000sat ' , ' lbl balance l2 to l3 ' , ' description send some sats l2 to l3 ' ) [ ' bolt11 ' ] )
l2 . rpc . pay ( l5 . rpc . invoice ( ' 500000sat ' , ' lbl balance l2 to l5 ' , ' description send some sats l2 to l5 ' ) [ ' bolt11 ' ] )
l3 . rpc . pay ( l4 . rpc . invoice ( ' 500000sat ' , ' lbl balance l3 to l4 ' , ' description send some sats l3 to l4 ' ) [ ' bolt11 ' ] )
return l1 , l2 , l3 , l4 , l5 , l6 , c12 , c23 , c25 , c34 , c23res
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in setting up nodes: { e } ' )
def generate_transactions_examples ( l1 , l2 , l3 , l4 , l5 , c25 , bitcoind ) :
""" Generate examples for various transactions and forwards """
try :
logger . info ( ' Simple Transactions Start... ' )
global FUND_CHANNEL_AMOUNT_SAT
# Simple Transactions by creating invoices, paying invoices, keysends
inv_l31 = update_example ( node = l3 , method = ' invoice ' , params = { ' amount_msat ' : 10 * * 4 , ' label ' : ' lbl_l31 ' , ' description ' : ' Invoice description l31 ' } )
route_l1_l3 = update_example ( node = l1 , method = ' getroute ' , params = { ' id ' : l3 . info [ ' id ' ] , ' amount_msat ' : 10 * * 4 , ' riskfactor ' : 1 } ) [ ' route ' ]
inv_l32 = update_example ( node = l3 , method = ' invoice ' , params = { ' amount_msat ' : ' 50000msat ' , ' label ' : ' lbl_l32 ' , ' description ' : ' l32 description ' } )
update_example ( node = l2 , method = ' getroute ' , params = { ' id ' : l4 . info [ ' id ' ] , ' amount_msat ' : 500000 , ' riskfactor ' : 10 , ' cltv ' : 9 } )
update_example ( node = l1 , method = ' sendpay ' , params = { ' route ' : route_l1_l3 , ' payment_hash ' : inv_l31 [ ' payment_hash ' ] , ' payment_secret ' : inv_l31 [ ' payment_secret ' ] } )
update_example ( node = l1 , method = ' waitsendpay ' , params = { ' payment_hash ' : inv_l31 [ ' payment_hash ' ] } )
update_example ( node = l1 , method = ' keysend ' , params = { ' destination ' : l3 . info [ ' id ' ] , ' amount_msat ' : 10000 } )
update_example ( node = l1 , method = ' keysend ' , params = { ' destination ' : l4 . info [ ' id ' ] , ' amount_msat ' : 10000000 , ' extratlvs ' : { ' 133773310 ' : ' 68656c6c6f776f726c64 ' , ' 133773312 ' : ' 66696c7465726d65 ' } } )
routehints = [ [ {
' scid ' : only_one ( [ channel for channel in l2 . rpc . listpeerchannels ( ) [ ' channels ' ] if channel [ ' peer_id ' ] == l3 . info [ ' id ' ] ] ) [ ' alias ' ] [ ' remote ' ] ,
' id ' : l2 . info [ ' id ' ] ,
' feebase ' : ' 1msat ' ,
' feeprop ' : 10 ,
' expirydelta ' : 9 ,
} ] ]
update_example ( node = l1 , method = ' keysend ' , params = { ' destination ' : l3 . info [ ' id ' ] , ' amount_msat ' : 10000 , ' routehints ' : routehints } )
inv_l11 = l1 . rpc . invoice ( ' 10000msat ' , ' lbl_l11 ' , ' l11 description ' )
inv_l21 = l2 . rpc . invoice ( ' any ' , ' lbl_l21 ' , ' l21 description ' )
inv_l22 = l2 . rpc . invoice ( ' 200000msat ' , ' lbl_l22 ' , ' l22 description ' )
inv_l33 = l3 . rpc . invoice ( ' 100000msat ' , ' lbl_l33 ' , ' l33 description ' )
inv_l34 = l3 . rpc . invoice ( 4000 , ' failed ' , ' failed description ' )
update_example ( node = l1 , method = ' pay ' , params = [ inv_l32 [ ' bolt11 ' ] ] )
update_example ( node = l2 , method = ' pay ' , params = { ' bolt11 ' : inv_l33 [ ' bolt11 ' ] } )
# Hops, create and send onion for onion routing
def truncate_encode ( i : int ) :
""" Encode a tu64 (or tu32 etc) value """
try :
ret = struct . pack ( " !Q " , i )
while ret . startswith ( b ' \0 ' ) :
ret = ret [ 1 : ]
return ret
except Exception as e :
logger . error ( f ' Error in encoding: { e } ' )
def serialize_payload_tlv ( n , blockheight : int = 0 ) :
""" Serialize payload according to BOLT #4: Onion Routing Protocol """
try :
block , tx , out = n [ ' channel ' ] . split ( ' x ' )
payload = TlvPayload ( )
b = BytesIO ( )
b . write ( truncate_encode ( int ( n [ ' amount_msat ' ] ) ) )
payload . add_field ( 2 , b . getvalue ( ) )
b = BytesIO ( )
b . write ( truncate_encode ( blockheight + n [ ' delay ' ] ) )
payload . add_field ( 4 , b . getvalue ( ) )
b = BytesIO ( )
b . write ( struct . pack ( " !Q " , int ( block ) << 40 | int ( tx ) << 16 | int ( out ) ) )
payload . add_field ( 6 , b . getvalue ( ) )
return payload . to_bytes ( ) . hex ( )
except Exception as e :
logger . error ( f ' Error in serializing payload: { e } ' )
def serialize_payload_final_tlv ( n , payment_secret : str , blockheight : int = 0 ) :
""" Serialize the last payload according to BOLT #4: Onion Routing Protocol """
try :
payload = TlvPayload ( )
b = BytesIO ( )
b . write ( truncate_encode ( int ( n [ ' amount_msat ' ] ) ) )
payload . add_field ( 2 , b . getvalue ( ) )
b = BytesIO ( )
b . write ( truncate_encode ( blockheight + n [ ' delay ' ] ) )
payload . add_field ( 4 , b . getvalue ( ) )
b = BytesIO ( )
b . write ( bytes . fromhex ( payment_secret ) )
b . write ( truncate_encode ( int ( n [ ' amount_msat ' ] ) ) )
payload . add_field ( 8 , b . getvalue ( ) )
return payload . to_bytes ( ) . hex ( )
except Exception as e :
logger . error ( f ' Error in serializing final payload: { e } ' )
blockheight = l1 . rpc . getinfo ( ) [ ' blockheight ' ]
amt = 10 * * 3
route = l1 . rpc . getroute ( l4 . info [ ' id ' ] , amt , 10 ) [ ' route ' ]
inv = l4 . rpc . invoice ( amt , " lbl l4 " , " desc l4 " )
first_hop = route [ 0 ]
hops = [ ]
for h , n in zip ( route [ : - 1 ] , route [ 1 : ] ) :
hops . append ( { ' pubkey ' : h [ ' id ' ] , ' payload ' : serialize_payload_tlv ( n , blockheight ) } )
hops . append ( { ' pubkey ' : route [ - 1 ] [ ' id ' ] , ' payload ' : serialize_payload_final_tlv ( route [ - 1 ] , inv [ ' payment_secret ' ] , blockheight ) } )
onion = update_example ( node = l1 , method = ' createonion ' , params = { ' hops ' : hops , ' assocdata ' : inv [ ' payment_hash ' ] } )
update_example ( node = l1 , method = ' createonion ' , params = [ hops , inv [ ' payment_hash ' ] , ' 41 ' * 32 ] )
update_example ( node = l1 , method = ' sendonion ' , params = { ' onion ' : onion [ ' onion ' ] , ' first_hop ' : first_hop , ' payment_hash ' : inv [ ' payment_hash ' ] } )
l1 . rpc . waitsendpay ( payment_hash = inv [ ' payment_hash ' ] )
# Close channels examples
update_example ( node = l2 , method = ' close ' , params = { ' id ' : l3 . info [ ' id ' ] , ' unilateraltimeout ' : 1 } )
update_example ( node = l3 , method = ' close ' , params = { ' id ' : l4 . info [ ' id ' ] , ' destination ' : l4 . rpc . newaddr ( ) [ ' bech32 ' ] } )
bitcoind . generate_block ( 1 )
sync_blockheight ( bitcoind , [ l1 , l2 , l3 , l4 ] )
# Channel 2 to 3 is closed, l1->l3 payment will fail where `failed` forward will be saved on l2
l1 . rpc . sendpay ( route_l1_l3 , inv_l34 [ ' payment_hash ' ] , payment_secret = inv_l34 [ ' payment_secret ' ] )
with pytest . raises ( RpcError ) :
l1 . rpc . waitsendpay ( inv_l34 [ ' payment_hash ' ] )
# Reopen channels for further examples
c23 , _ = l2 . fundchannel ( l3 , FUND_CHANNEL_AMOUNT_SAT )
l3 . fundchannel ( l4 , FUND_CHANNEL_AMOUNT_SAT )
mine_funding_to_announce ( bitcoind , [ l3 , l4 ] )
l2 . wait_channel_active ( c23 )
update_example ( node = l2 , method = ' setchannel ' , params = { ' id ' : c23 , ' ignorefeelimits ' : True } )
update_example ( node = l2 , method = ' setchannel ' , params = { ' id ' : c25 , ' feebase ' : 4000 , ' feeppm ' : 300 , ' enforcedelay ' : 0 } )
# Some more invoices for signing and preapproving
inv_l12 = l1 . rpc . invoice ( 1000 , ' label inv_l12 ' , ' description inv_l12 ' ) [ ' bolt11 ' ]
inv_l24 = l2 . rpc . invoice ( 123000 , ' label inv_l24 ' , ' description inv_l24 ' , 3600 ) [ ' bolt11 ' ]
inv_l25 = l2 . rpc . invoice ( 124000 , ' label inv_l25 ' , ' description inv_l25 ' , 3600 ) [ ' bolt11 ' ]
inv_l26 = l2 . rpc . invoice ( 125000 , ' label inv_l26 ' , ' description inv_l26 ' , 3600 ) [ ' bolt11 ' ]
update_example ( node = l2 , method = ' signinvoice ' , params = { ' invstring ' : inv_l12 } )
update_example ( node = l3 , method = ' signinvoice ' , params = [ inv_l26 ] )
update_example ( node = l1 , method = ' preapprovekeysend ' , params = { ' destination ' : l2 . info [ ' id ' ] , ' payment_hash ' : ' 00 ' * 32 , ' amount_msat ' : 1000 } )
update_example ( node = l5 , method = ' preapprovekeysend ' , params = [ l5 . info [ ' id ' ] , ' 01 ' * 32 , 2000 ] )
update_example ( node = l1 , method = ' preapproveinvoice ' , params = { ' bolt11 ' : inv_l24 } )
update_example ( node = l1 , method = ' preapproveinvoice ' , params = [ inv_l25 ] )
inv_req = update_example ( node = l2 , method = ' invoicerequest ' , params = { ' amount ' : 1000000 , ' description ' : ' Simple test ' } )
update_example ( node = l1 , method = ' sendinvoice ' , params = { ' invreq ' : inv_req [ ' bolt12 ' ] , ' label ' : ' test sendinvoice ' } )
inv_l13 = l1 . rpc . invoice ( amount_msat = 100000 , label = ' lbl_l13 ' , description = ' l13 description ' , preimage = ' 01 ' * 32 )
update_example ( node = l2 , method = ' createinvoice ' , params = { ' invstring ' : inv_l13 [ ' bolt11 ' ] , ' label ' : ' lbl_l13 ' , ' preimage ' : ' 01 ' * 32 } )
logger . info ( ' Simple Transactions Done! ' )
return inv_l11 , inv_l21 , inv_l22 , inv_l31 , inv_l32 , inv_l34
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in generating transactions examples: { e } ' )
def generate_runes_examples ( l1 , l2 , l3 ) :
""" Covers all runes related examples """
try :
logger . info ( ' Runes Start... ' )
# Runes
trimmed_id = l1 . info [ ' id ' ] [ : 20 ]
rune_l21 = update_example ( node = l2 , method = ' createrune ' , params = { } , description = [ ' This creates a fresh rune which can do anything: ' ] )
rune_l22 = update_example ( node = l2 , method = ' createrune ' , params = { ' rune ' : rune_l21 [ ' rune ' ] , ' restrictions ' : ' readonly ' } ,
description = [ ' We can add restrictions to that rune, like so: ' ,
' ' ,
' The `readonly` restriction is a short-cut for two restrictions: ' ,
' ' ,
' 1: `[ \' method^list \' , \' method^get \' , \' method=summary \' ]`: You may call list, get or summary. ' ,
' ' ,
' 2: `[ \' method/listdatastore \' ]`: But not listdatastore: that contains sensitive stuff! '
] )
update_example ( node = l2 , method = ' createrune ' , params = { ' rune ' : rune_l21 [ ' rune ' ] , ' restrictions ' : [ [ ' method^list ' , ' method^get ' , ' method=summary ' ] , [ ' method/listdatastore ' ] ] } , description = [ ' We can do the same manually (readonly), like so: ' ] )
rune_l23 = update_example ( node = l2 , method = ' createrune ' , params = { ' restrictions ' : [ [ f ' id^ { trimmed_id } ' ] , [ ' method=listpeers ' ] ] } , description = [ f ' This will allow the rune to be used for id starting with { trimmed_id } , and for the method listpeers: ' ] )
rune_l24 = update_example ( node = l2 , method = ' createrune ' , params = { ' restrictions ' : [ [ ' method=pay ' ] , [ ' pnameamountmsat<10000 ' ] ] } , description = [ ' This will allow the rune to be used for the method pay, and for the parameter amount \\ _msat to be less than 10000: ' ] )
update_example ( node = l2 , method = ' createrune ' , params = { ' restrictions ' : [ [ f ' id= { l1 . info [ " id " ] } ' ] , [ ' method=listpeers ' ] , [ ' pnum=1 ' ] , [ f ' pnameid= { l1 . info [ " id " ] } ' , f ' parr0= { l1 . info [ " id " ] } ' ] ] } , description = [ " Let ' s create a rune which lets a specific peer run listpeers on themselves: " ] )
rune_l25 = update_example ( node = l2 , method = ' createrune ' , params = { ' restrictions ' : [ [ f ' id= { l1 . info [ " id " ] } ' ] , [ ' method=listpeers ' ] , [ ' pnum=1 ' ] , [ f ' pnameid^ { trimmed_id } ' , f ' parr0^ { trimmed_id } ' ] ] } , description = [ " This allows `listpeers` with 1 argument (`pnum=1`), which is either by name (`pnameid`), or position (`parr0`). We could shorten this in several ways: either allowing only positional or named parameters, or by testing the start of the parameters only. Here ' s an example which only checks the first 10 bytes of the `listpeers` parameter: " ] )
update_example ( node = l2 , method = ' createrune ' , params = [ rune_l25 [ ' rune ' ] , [ [ ' time< " $(($(date + %s ) + 24*60*60)) " ' , ' rate=2 ' ] ] ] , description = [ " Before we give this to our peer, let ' s add two more restrictions: that it only be usable for 24 hours from now (`time<`), and that it can only be used twice a minute (`rate=2`). `date + %s ` can give us the current time in seconds: " ] )
update_example ( node = l2 , method = ' commando-listrunes ' , params = { ' rune ' : rune_l23 [ ' rune ' ] } )
update_example ( node = l2 , method = ' commando-listrunes ' , params = { } )
update_example ( node = l1 , method = ' commando ' , params = { ' peer_id ' : l2 . info [ ' id ' ] , ' rune ' : rune_l22 [ ' rune ' ] , ' method ' : ' getinfo ' , ' params ' : { } } )
update_example ( node = l1 , method = ' commando ' , params = { ' peer_id ' : l2 . info [ ' id ' ] , ' rune ' : rune_l23 [ ' rune ' ] , ' method ' : ' listpeers ' , ' params ' : [ l3 . info [ ' id ' ] ] } )
inv_l23 = l2 . rpc . invoice ( ' any ' , ' lbl_l23 ' , ' l23 description ' )
update_example ( node = l1 , method = ' commando ' , params = { ' peer_id ' : l2 . info [ ' id ' ] , ' rune ' : rune_l24 [ ' rune ' ] , ' method ' : ' pay ' , ' params ' : { ' bolt11 ' : inv_l23 [ ' bolt11 ' ] , ' amount_msat ' : 9900 } } )
update_example ( node = l2 , method = ' checkrune ' , params = { ' nodeid ' : l2 . info [ ' id ' ] , ' rune ' : rune_l22 [ ' rune ' ] , ' method ' : ' listpeers ' , ' params ' : { } } )
update_example ( node = l2 , method = ' checkrune ' , params = { ' nodeid ' : l2 . info [ ' id ' ] , ' rune ' : rune_l24 [ ' rune ' ] , ' method ' : ' pay ' , ' params ' : { ' amount_msat ' : 9999 } } )
update_example ( node = l2 , method = ' showrunes ' , params = { ' rune ' : rune_l21 [ ' rune ' ] } )
update_example ( node = l2 , method = ' showrunes ' , params = { } )
update_example ( node = l2 , method = ' commando-blacklist ' , params = { ' start ' : 1 } )
update_example ( node = l2 , method = ' commando-blacklist ' , params = { ' start ' : 2 , ' end ' : 3 } )
update_example ( node = l2 , method = ' blacklistrune ' , params = { ' start ' : 1 } )
update_example ( node = l2 , method = ' blacklistrune ' , params = { ' start ' : 0 , ' end ' : 2 } )
update_example ( node = l2 , method = ' blacklistrune ' , params = { ' start ' : 3 , ' end ' : 4 } )
# Commando runes
rune_l11 = update_example ( node = l1 , method = ' commando-rune ' , params = { } , description = [ ' This creates a fresh rune which can do anything: ' ] )
update_example ( node = l1 , method = ' commando-rune ' , params = { ' rune ' : rune_l11 [ ' rune ' ] , ' restrictions ' : ' readonly ' } ,
description = [ ' We can add restrictions to that rune, like so: ' ,
' ' ,
' The `readonly` restriction is a short-cut for two restrictions: ' ,
' ' ,
' 1: `[ \' method^list \' , \' method^get \' , \' method=summary \' ]`: You may call list, get or summary. ' ,
' ' ,
' 2: `[ \' method/listdatastore \' ]`: But not listdatastore: that contains sensitive stuff! '
] )
update_example ( node = l1 , method = ' commando-rune ' , params = { ' rune ' : rune_l11 [ ' rune ' ] , ' restrictions ' : [ [ ' method^list ' , ' method^get ' , ' method=summary ' ] , [ ' method/listdatastore ' ] ] } , description = [ ' We can do the same manually (readonly), like so: ' ] )
update_example ( node = l1 , method = ' commando-rune ' , params = { ' restrictions ' : [ [ f ' id^ { trimmed_id } ' ] , [ ' method=listpeers ' ] ] } , description = [ f ' This will allow the rune to be used for id starting with { trimmed_id } , and for the method listpeers: ' ] )
update_example ( node = l1 , method = ' commando-rune ' , params = { ' restrictions ' : [ [ ' method=pay ' ] , [ ' pnameamountmsat<10000 ' ] ] } , description = [ ' This will allow the rune to be used for the method pay, and for the parameter amount \\ _msat to be less than 10000: ' ] )
update_example ( node = l1 , method = ' commando-rune ' , params = { ' restrictions ' : [ [ f ' id= { l1 . info [ " id " ] } ' ] , [ ' method=listpeers ' ] , [ ' pnum=1 ' ] , [ f ' pnameid= { l1 . info [ " id " ] } ' , f ' parr0= { l1 . info [ " id " ] } ' ] ] } , description = [ " Let ' s create a rune which lets a specific peer run listpeers on themselves: " ] )
rune_l15 = update_example ( node = l1 , method = ' commando-rune ' , params = { ' restrictions ' : [ [ f ' id= { l1 . info [ " id " ] } ' ] , [ ' method=listpeers ' ] , [ ' pnum=1 ' ] , [ f ' pnameid^ { trimmed_id } ' , f ' parr0^ { trimmed_id } ' ] ] } , description = [ " This allows `listpeers` with 1 argument (`pnum=1`), which is either by name (`pnameid`), or position (`parr0`). We could shorten this in several ways: either allowing only positional or named parameters, or by testing the start of the parameters only. Here ' s an example which only checks the first 10 bytes of the `listpeers` parameter: " ] )
update_example ( node = l1 , method = ' commando-rune ' , params = [ rune_l15 [ ' rune ' ] , [ [ ' time< " $(($(date + %s ) + 24*60*60)) " ' , ' rate=2 ' ] ] ] , description = [ " Before we give this to our peer, let ' s add two more restrictions: that it only be usable for 24 hours from now (`time<`), and that it can only be used twice a minute (`rate=2`). `date + %s ` can give us the current time in seconds: " ] )
logger . info ( ' Runes Done! ' )
return rune_l21
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in generating runes examples: { e } ' )
def generate_datastore_examples ( l2 ) :
""" Covers all datastore related examples """
try :
logger . info ( ' Datastore Start... ' )
update_example ( node = l2 , method = ' datastore ' , params = { ' key ' : ' somekey ' , ' hex ' : ' 61 ' , ' mode ' : ' create-or-append ' } )
update_example ( node = l2 , method = ' datastore ' , params = { ' key ' : [ ' test ' , ' name ' ] , ' string ' : ' saving data to the store ' , ' mode ' : ' must-create ' } )
update_example ( node = l2 , method = ' datastore ' , params = { ' key ' : ' otherkey ' , ' string ' : ' foo ' , ' mode ' : ' must-create ' } )
update_example ( node = l2 , method = ' datastore ' , params = { ' key ' : ' otherkey ' , ' string ' : ' bar ' , ' mode ' : ' must-append ' , ' generation ' : 0 } )
update_example ( node = l2 , method = ' datastoreusage ' , params = { } )
update_example ( node = l2 , method = ' datastoreusage ' , params = { ' key ' : [ ' test ' , ' name ' ] } )
update_example ( node = l2 , method = ' datastoreusage ' , params = { ' key ' : ' otherkey ' } )
update_example ( node = l2 , method = ' listdatastore ' , params = { ' key ' : [ ' test ' ] } )
update_example ( node = l2 , method = ' listdatastore ' , params = { ' key ' : ' otherkey ' } )
update_example ( node = l2 , method = ' deldatastore ' , params = { ' key ' : [ ' test ' , ' name ' ] } )
update_example ( node = l2 , method = ' deldatastore ' , params = { ' key ' : ' otherkey ' , ' generation ' : 1 } )
logger . info ( ' Datastore Done! ' )
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in generating datastore examples: { e } ' )
def generate_bookkeeper_examples ( l2 , l3 , c23_chan_id ) :
""" Generates all bookkeeper rpc examples """
try :
logger . info ( ' Bookkeeper Start... ' )
update_example ( node = l2 , method = ' funderupdate ' , params = { } )
update_example ( node = l2 , method = ' funderupdate ' , params = { ' policy ' : ' fixed ' , ' policy_mod ' : ' 50000sat ' , ' min_their_funding_msat ' : 1000 , ' per_channel_min_msat ' : ' 1000sat ' , ' per_channel_max_msat ' : ' 500000sat ' , ' fund_probability ' : 100 , ' fuzz_percent ' : 0 , ' leases_only ' : False } )
update_example ( node = l2 , method = ' bkpr-inspect ' , params = { ' account ' : c23_chan_id } )
update_example ( node = l2 , method = ' bkpr-dumpincomecsv ' , params = [ ' koinly ' , ' koinly.csv ' ] )
update_example ( node = l2 , method = ' bkpr-channelsapy ' , params = { } )
update_example ( node = l3 , method = ' bkpr-listbalances ' , params = { } )
update_example ( node = l3 , method = ' bkpr-listaccountevents ' , params = { } )
update_example ( node = l3 , method = ' bkpr-listaccountevents ' , params = [ c23_chan_id ] )
update_example ( node = l3 , method = ' bkpr-listincome ' , params = { } )
update_example ( node = l3 , method = ' bkpr-listincome ' , params = { ' consolidate_fees ' : False } )
logger . info ( ' Bookkeeper Done! ' )
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in generating bookkeeper examples: { e } ' )
def generate_offers_renepay_examples ( l1 , l2 , inv_l21 , inv_l34 ) :
""" Covers all offers and renepay related examples """
try :
logger . info ( ' Offers and Renepay Start... ' )
# Offers & Offers Lists
offer_l21 = update_example ( node = l2 , method = ' offer ' , params = { ' amount ' : ' 10000msat ' , ' description ' : ' Fish sale! ' } )
offer_l22 = update_example ( node = l2 , method = ' offer ' , params = { ' amount ' : ' 1000sat ' , ' description ' : ' Coffee ' , ' quantity_max ' : 10 } )
offer_l23 = l2 . rpc . offer ( ' 2000sat ' , ' Offer to Disable ' )
update_example ( node = l1 , method = ' fetchinvoice ' , params = { ' offer ' : offer_l21 [ ' bolt12 ' ] , ' payer_note ' : ' Thanks for the fish! ' } )
update_example ( node = l1 , method = ' fetchinvoice ' , params = { ' offer ' : offer_l22 [ ' bolt12 ' ] , ' amount_msat ' : 2000000 , ' quantity ' : 2 } )
update_example ( node = l2 , method = ' disableoffer ' , params = { ' offer_id ' : offer_l23 [ ' offer_id ' ] } )
update_example ( node = l2 , method = ' listoffers ' , params = { ' active_only ' : True } )
update_example ( node = l2 , method = ' listoffers ' , params = [ offer_l23 [ ' offer_id ' ] ] )
# Invoice Requests
inv_req_l1_l22 = update_example ( node = l2 , method = ' invoicerequest ' , params = { ' amount ' : ' 10000sat ' , ' description ' : ' Requesting for invoice ' , ' issuer ' : ' clightning store ' } )
update_example ( node = l2 , method = ' disableinvoicerequest ' , params = { ' invreq_id ' : inv_req_l1_l22 [ ' invreq_id ' ] } )
update_example ( node = l2 , method = ' listinvoicerequests ' , params = [ inv_req_l1_l22 [ ' invreq_id ' ] ] )
update_example ( node = l2 , method = ' listinvoicerequests ' , params = { } )
# Renepay
update_example ( node = l1 , method = ' renepay ' , params = { ' invstring ' : inv_l21 [ ' bolt11 ' ] , ' amount_msat ' : 400000 } )
update_example ( node = l2 , method = ' renepay ' , params = { ' invstring ' : inv_l34 [ ' bolt11 ' ] } )
update_example ( node = l1 , method = ' renepaystatus ' , params = { ' invstring ' : inv_l21 [ ' bolt11 ' ] } )
logger . info ( ' Offers and Renepay Done! ' )
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in generating offers or renepay examples: { e } ' )
def generate_list_examples ( l1 , l2 , l3 , c12 , c23 , inv_l31 , inv_l32 ) :
""" Generates lists rpc examples """
try :
logger . info ( ' Lists Start... ' )
# Transactions Lists
update_example ( node = l1 , method = ' listfunds ' , params = { } )
update_example ( node = l2 , method = ' listforwards ' , params = { ' in_channel ' : c12 , ' out_channel ' : c23 , ' status ' : ' settled ' } )
update_example ( node = l2 , method = ' listforwards ' , params = { } )
update_example ( node = l2 , method = ' listinvoices ' , params = { ' label ' : ' lbl_l21 ' } )
update_example ( node = l2 , method = ' listinvoices ' , params = { } )
update_example ( node = l1 , method = ' listhtlcs ' , params = [ c12 ] )
update_example ( node = l1 , method = ' listhtlcs ' , params = { } )
update_example ( node = l1 , method = ' listsendpays ' , params = { ' bolt11 ' : inv_l31 [ ' bolt11 ' ] } )
update_example ( node = l1 , method = ' listsendpays ' , params = { } )
update_example ( node = l1 , method = ' listtransactions ' , params = { } )
update_example ( node = l2 , method = ' listpays ' , params = { ' bolt11 ' : inv_l32 [ ' bolt11 ' ] } )
update_example ( node = l2 , method = ' listpays ' , params = { } )
update_example ( node = l3 , method = ' listclosedchannels ' , params = { } )
# Network & Nodes Lists
update_example ( node = l2 , method = ' listconfigs ' , params = { ' config ' : ' network ' } )
update_example ( node = l2 , method = ' listconfigs ' , params = { ' config ' : ' experimental-dual-fund ' } )
# Schema checker error: listconfigs.json: Additional properties are not allowed ('plugin' was unexpected)
l2 . rpc . jsonschemas = { }
update_example ( node = l2 , method = ' listconfigs ' , params = { } )
update_example ( node = l2 , method = ' listsqlschemas ' , params = { ' table ' : ' offers ' } )
update_example ( node = l2 , method = ' listsqlschemas ' , params = [ ' closedchannels ' ] )
update_example ( node = l1 , method = ' listpeerchannels ' , params = { ' id ' : l2 . info [ ' id ' ] } )
update_example ( node = l1 , method = ' listpeerchannels ' , params = { } )
update_example ( node = l1 , method = ' listchannels ' , params = { ' short_channel_id ' : c12 } )
update_example ( node = l1 , method = ' listchannels ' , params = { } )
update_example ( node = l2 , method = ' listnodes ' , params = { ' id ' : l3 . info [ ' id ' ] } )
update_example ( node = l2 , method = ' listnodes ' , params = { } )
update_example ( node = l2 , method = ' listpeers ' , params = { ' id ' : l3 . info [ ' id ' ] } )
update_example ( node = l2 , method = ' listpeers ' , params = { } )
logger . info ( ' Lists Done! ' )
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in generating lists examples: { e } ' )
def generate_wait_examples ( l1 , l2 , bitcoind , executor ) :
""" Generates wait examples """
try :
logger . info ( ' Wait Start... ' )
inv1 = l2 . rpc . invoice ( 1000 , ' inv1 ' , ' inv1 ' )
inv2 = l2 . rpc . invoice ( 2000 , ' inv2 ' , ' inv2 ' )
inv3 = l2 . rpc . invoice ( 3000 , ' inv3 ' , ' inv3 ' )
inv4 = l2 . rpc . invoice ( 4000 , ' inv4 ' , ' inv4 ' )
inv5 = l2 . rpc . invoice ( 5000 , ' inv5 ' , ' inv5 ' )
# Wait invoice
wi3 = executor . submit ( l2 . rpc . waitinvoice , ' inv3 ' )
time . sleep ( 1 )
l1 . rpc . pay ( inv2 [ ' bolt11 ' ] )
time . sleep ( 1 )
wi2res = executor . submit ( l2 . rpc . waitinvoice , ' inv2 ' ) . result ( timeout = 5 )
update_example ( node = l2 , method = ' waitinvoice ' , params = { ' label ' : ' inv2 ' } , res = wi2res , execute = False )
l1 . rpc . pay ( inv3 [ ' bolt11 ' ] )
wi3res = wi3 . result ( timeout = 5 )
update_example ( node = l2 , method = ' waitinvoice ' , params = [ ' inv3 ' ] , res = wi3res , execute = False )
# Wait any invoice
wai = executor . submit ( l2 . rpc . waitanyinvoice )
time . sleep ( 1 )
l1 . rpc . pay ( inv5 [ ' bolt11 ' ] )
l1 . rpc . pay ( inv4 [ ' bolt11 ' ] )
waires = wai . result ( timeout = 5 )
update_example ( node = l2 , method = ' waitanyinvoice ' , params = { } , res = waires , execute = False )
pay_index = waires [ ' pay_index ' ]
wai_pay_index_res = executor . submit ( l2 . rpc . waitanyinvoice , pay_index , 0 ) . result ( timeout = 5 )
update_example ( node = l2 , method = ' waitanyinvoice ' , params = { ' lastpay_index ' : pay_index , ' timeout ' : 0 } , res = wai_pay_index_res , execute = False )
# Wait with subsystem examples
update_example ( node = l2 , method = ' wait ' , params = { ' subsystem ' : ' invoices ' , ' indexname ' : ' created ' , ' nextvalue ' : 0 } )
wspres_l1 = l1 . rpc . wait ( subsystem = ' sendpays ' , indexname = ' created ' , nextvalue = 0 )
nextvalue = int ( wspres_l1 [ ' created ' ] ) + 1
wsp_created_l1 = executor . submit ( l1 . rpc . call , ' wait ' , { ' subsystem ' : ' sendpays ' , ' indexname ' : ' created ' , ' nextvalue ' : nextvalue } )
wsp_updated_l1 = executor . submit ( l1 . rpc . call , ' wait ' , { ' subsystem ' : ' sendpays ' , ' indexname ' : ' updated ' , ' nextvalue ' : nextvalue } )
time . sleep ( 1 )
routestep = {
' amount_msat ' : 1000 ,
' id ' : l2 . info [ ' id ' ] ,
' delay ' : 5 ,
' channel ' : first_scid ( l1 , l2 )
}
l1 . rpc . sendpay ( [ routestep ] , inv1 [ ' payment_hash ' ] , payment_secret = inv1 [ ' payment_secret ' ] )
wspc_res = wsp_created_l1 . result ( 5 )
wspu_res = wsp_updated_l1 . result ( 5 )
update_example ( node = l1 , method = ' wait ' , params = { ' subsystem ' : ' sendpays ' , ' indexname ' : ' created ' , ' nextvalue ' : nextvalue } , res = wspc_res , execute = False )
update_example ( node = l1 , method = ' wait ' , params = [ ' sendpays ' , ' updated ' , nextvalue ] , res = wspu_res , execute = False )
# Wait blockheight
curr_blockheight = l2 . rpc . getinfo ( ) [ ' blockheight ' ]
update_example ( node = l2 , method = ' waitblockheight ' , params = { ' blockheight ' : curr_blockheight - 1 , ' timeout ' : 600 } )
wait_time = 60
wbh = executor . submit ( l2 . rpc . waitblockheight , curr_blockheight + 1 , wait_time )
bitcoind . generate_block ( 1 )
sync_blockheight ( bitcoind , [ l2 ] )
wbhres = wbh . result ( 5 )
update_example ( node = l2 , method = ' waitblockheight ' , params = { ' blockheight ' : curr_blockheight + 1 } , res = wbhres , execute = False )
logger . info ( ' Wait Done! ' )
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in generating wait examples: { e } ' )
def generate_utils_examples ( l1 , l2 , l3 , l4 , l5 , l6 , c23 , c34 , inv_l11 , inv_l22 , rune_l21 , bitcoind ) :
""" Generates other utilities examples """
try :
logger . info ( ' General Utils Start... ' )
global CWD , FUND_CHANNEL_AMOUNT_SAT
update_example ( node = l2 , method = ' batching ' , params = { ' enable ' : True } )
update_example ( node = l2 , method = ' ping ' , params = { ' id ' : l1 . info [ ' id ' ] , ' len ' : 128 , ' pongbytes ' : 128 } )
update_example ( node = l2 , method = ' ping ' , params = { ' id ' : l3 . info [ ' id ' ] , ' len ' : 1000 , ' pongbytes ' : 65535 } )
update_example ( node = l2 , method = ' help ' , params = { ' command ' : ' pay ' } )
update_example ( node = l2 , method = ' help ' , params = { ' command ' : ' dev ' } )
update_example ( node = l2 , method = ' setconfig ' , params = [ ' autoclean-expiredinvoices-age ' , 300 ] )
update_example ( node = l2 , method = ' setconfig ' , params = { ' config ' : ' min-capacity-sat ' , ' val ' : 500000 } )
update_example ( node = l2 , method = ' addgossip ' , params = { ' message ' : ' 010078c3314666731e339c0b8434f7824797a084ed7ca3655991a672da068e2c44cb53b57b53a296c133bc879109a8931dc31e6913a4bda3d58559b99b95663e6d52775579447ef5526300e1bb89bc6af8557aa1c3810a91814eafad6d103f43182e17b16644cb38c1d58a8edd094303959a9f1f9d42ff6c32a21f9c118531f512c8679cabaccc6e39dbd95a4dac90e75a258893c3aa3f733d1b8890174d5ddea8003cadffe557773c54d2c07ca1d535c4bf85885f879ae466c16a516e8ffcfec1740e3f5c98ca9ce13f452e867befef5517f306ed6aa5119b79059bcc6f68f329986b665d16de7bc7df64e3537504c91eeabe0e59d3a2b68e4216ead2b0f6e3ef7c000006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f0000670000010000022d223620a359a47ff7f7ac447c85c46c923da53389221a0054c11c1e3ca31d590266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c0351802e3bd38009866c9da8ec4aa99cc4ea9c6c0dd46df15c61ef0ce1f271291714e5702324266de8403b3ab157a09f1f784d587af61831c998c151bcc21bb74c2b2314b ' } )
update_example ( node = l2 , method = ' addgossip ' , params = { ' message ' : ' 0102420526c8eb62ec6999bbee5f1de4841cab734374ec642b7deeb0259e76220bf82e97a241c907d5ff52019655f7f9a614c285bb35690f3a1a2b928d7b2349a79e06226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f000067000001000065b32a0e010100060000000000000000000000010000000a000000003b023380 ' } )
update_example ( node = l2 , method = ' deprecations ' , params = { ' enable ' : True } )
update_example ( node = l2 , method = ' deprecations ' , params = { ' enable ' : False } )
update_example ( node = l2 , method = ' getlog ' , params = { ' level ' : ' unusual ' } )
update_example ( node = l2 , method = ' notifications ' , params = { ' enable ' : True } )
update_example ( node = l2 , method = ' notifications ' , params = { ' enable ' : False } )
update_example ( node = l2 , method = ' check ' , params = { ' command_to_check ' : ' sendpay ' , ' route ' : [ { ' amount_msat ' : 1011 , ' id ' : l3 . info [ ' id ' ] , ' delay ' : 20 , ' channel ' : c23 } , { ' amount_msat ' : 1000 , ' id ' : l4 . info [ ' id ' ] , ' delay ' : 10 , ' channel ' : c34 } ] , ' payment_hash ' : ' 0000000000000000000000000000000000000000000000000000000000000000 ' } )
update_example ( node = l2 , method = ' check ' , params = { ' command_to_check ' : ' dev ' , ' subcommand ' : ' slowcmd ' , ' msec ' : 1000 } )
update_example ( node = l6 , method = ' check ' , params = { ' command_to_check ' : ' recover ' , ' hsmsecret ' : ' 6c696768746e696e672d31000000000000000000000000000000000000000000 ' } )
update_example ( node = l2 , method = ' plugin ' , params = { ' subcommand ' : ' start ' , ' plugin ' : os . path . join ( CWD , ' tests/plugins/allow_even_msgs.py ' ) } )
update_example ( node = l2 , method = ' plugin ' , params = { ' subcommand ' : ' stop ' , ' plugin ' : os . path . join ( CWD , ' tests/plugins/allow_even_msgs.py ' ) } )
update_example ( node = l2 , method = ' plugin ' , params = [ ' list ' ] )
update_example ( node = l2 , method = ' sendcustommsg ' , params = { ' node_id ' : l3 . info [ ' id ' ] , ' msg ' : ' 77770012 ' } )
# Wallet Utils
address_l21 = update_example ( node = l2 , method = ' newaddr ' , params = { } )
address_l22 = update_example ( node = l2 , method = ' newaddr ' , params = { ' addresstype ' : ' p2tr ' } )
withdraw_l21 = update_example ( node = l2 , method = ' withdraw ' , params = { ' destination ' : address_l21 [ ' bech32 ' ] , ' satoshi ' : 555555 } )
bitcoind . generate_block ( 4 , wait_for_mempool = [ withdraw_l21 [ ' txid ' ] ] )
sync_blockheight ( bitcoind , [ l2 ] )
funds_l2 = l2 . rpc . listfunds ( )
withdraw_l22 = update_example ( node = l2 , method = ' withdraw ' , params = { ' destination ' : address_l22 [ ' p2tr ' ] , ' satoshi ' : ' all ' , ' feerate ' : ' 20000perkb ' , ' minconf ' : 0 , ' utxos ' : [ f " { funds_l2 [ ' outputs ' ] [ 2 ] [ ' txid ' ] } : { funds_l2 [ ' outputs ' ] [ 2 ] [ ' output ' ] } " ] } )
bitcoind . generate_block ( 4 , wait_for_mempool = [ withdraw_l22 [ ' txid ' ] ] )
update_example ( node = l2 , method = ' multiwithdraw ' , params = { ' outputs ' : [ { l1 . rpc . newaddr ( ) [ ' bech32 ' ] : ' 2222000msat ' } , { l1 . rpc . newaddr ( ) [ ' bech32 ' ] : ' 3333000msat ' } ] } )
update_example ( node = l2 , method = ' multiwithdraw ' , params = { ' outputs ' : [ { l1 . rpc . newaddr ( ' p2tr ' ) [ ' p2tr ' ] : 1000 } , { l1 . rpc . newaddr ( ) [ ' bech32 ' ] : 1000 } , { l2 . rpc . newaddr ( ) [ ' bech32 ' ] : 1000 } , { l3 . rpc . newaddr ( ) [ ' bech32 ' ] : 1000 } , { l3 . rpc . newaddr ( ) [ ' bech32 ' ] : 1000 } , { l4 . rpc . newaddr ( ' p2tr ' ) [ ' p2tr ' ] : 1000 } , { l1 . rpc . newaddr ( ) [ ' bech32 ' ] : 1000 } ] } )
l2 . rpc . connect ( l4 . info [ ' id ' ] , ' localhost ' , l4 . port )
l2 . rpc . connect ( l5 . info [ ' id ' ] , ' localhost ' , l5 . port )
update_example ( node = l2 , method = ' disconnect ' , params = { ' id ' : l4 . info [ ' id ' ] , ' force ' : False } )
update_example ( node = l2 , method = ' disconnect ' , params = { ' id ' : l5 . info [ ' id ' ] , ' force ' : True } )
update_example ( node = l2 , method = ' parsefeerate ' , params = [ ' unilateral_close ' ] )
update_example ( node = l2 , method = ' parsefeerate ' , params = [ ' 9999perkw ' ] )
update_example ( node = l2 , method = ' parsefeerate ' , params = [ 10000 ] )
update_example ( node = l2 , method = ' parsefeerate ' , params = [ ' urgent ' ] )
update_example ( node = l2 , method = ' feerates ' , params = { ' style ' : ' perkw ' } )
update_example ( node = l2 , method = ' feerates ' , params = { ' style ' : ' perkb ' } )
update_example ( node = l2 , method = ' signmessage ' , params = { ' message ' : ' this is a test! ' } )
update_example ( node = l2 , method = ' signmessage ' , params = { ' message ' : ' message for you ' } )
update_example ( node = l2 , method = ' checkmessage ' , params = { ' message ' : ' testcase to check new rpc error ' , ' zbase ' : ' d66bqz3qsku5fxtqsi37j11pci47ydxa95iusphutggz9ezaxt56neh77kxe5hyr41kwgkncgiu94p9ecxiexgpgsz8daoq4tw8kj8yx ' , ' pubkey ' : ' 03be3b0e9992153b1d5a6e1623670b6c3663f72ce6cf2e0dd39c0a373a7de5a3b7 ' } )
update_example ( node = l2 , method = ' checkmessage ' , params = { ' message ' : ' this is a test! ' , ' zbase ' : ' d6tqaeuonjhi98mmont9m4wag7gg4krg1f4txonug3h31e9h6p6k6nbwjondnj46dkyausobstnk7fhyy998bhgc1yr98dfmhb4k54d7 ' } )
update_example ( node = l2 , method = ' decodepay ' , params = { ' bolt11 ' : inv_l11 [ ' bolt11 ' ] } )
update_example ( node = l2 , method = ' decode ' , params = [ rune_l21 [ ' rune ' ] ] )
update_example ( node = l2 , method = ' decode ' , params = [ inv_l22 [ ' bolt11 ' ] ] )
# PSBT
amount1 = 1000000
amount2 = 3333333
result = update_example ( node = l1 , method = ' addpsbtoutput ' , params = { ' satoshi ' : amount1 , ' locktime ' : 111 } , description = [ f ' Here is a command to make a PSBT with a { amount1 : , } sat output that leads to the on-chain wallet: ' ] )
update_example ( node = l1 , method = ' setpsbtversion ' , params = { ' psbt ' : result [ ' psbt ' ] , ' version ' : 0 } )
result = l1 . rpc . addpsbtoutput ( amount2 , result [ ' psbt ' ] )
update_example ( node = l1 , method = ' addpsbtoutput ' , params = [ amount2 , result [ ' psbt ' ] ] , res = result , execute = False )
dest = l1 . rpc . newaddr ( ' p2tr ' ) [ ' p2tr ' ]
result = update_example ( node = l1 , method = ' addpsbtoutput ' , params = { ' satoshi ' : amount2 , ' initialpsbt ' : result [ ' psbt ' ] , ' destination ' : dest } )
l1 . rpc . addpsbtoutput ( amount2 , result [ ' psbt ' ] , None , dest )
update_example ( node = l1 , method = ' setpsbtversion ' , params = [ result [ ' psbt ' ] , 2 ] )
out_total = Millisatoshi ( 3000000 * 1000 )
funding = l1 . rpc . fundpsbt ( satoshi = out_total , feerate = 7500 , startweight = 42 )
psbt = bitcoind . rpc . decodepsbt ( funding [ ' psbt ' ] )
saved_input = psbt [ ' tx ' ] [ ' vin ' ] [ 0 ]
l1 . rpc . unreserveinputs ( funding [ ' psbt ' ] )
psbt = bitcoind . rpc . createpsbt ( [ { ' txid ' : saved_input [ ' txid ' ] ,
' vout ' : saved_input [ ' vout ' ] } ] , [ ] )
out_1_ms = Millisatoshi ( funding [ ' excess_msat ' ] )
output_psbt = bitcoind . rpc . createpsbt ( [ ] , [ { ' bcrt1qeyyk6sl5pr49ycpqyckvmttus5ttj25pd0zpvg ' : float ( ( out_total + out_1_ms ) . to_btc ( ) ) } ] )
fullpsbt = bitcoind . rpc . joinpsbts ( [ funding [ ' psbt ' ] , output_psbt ] )
l1 . rpc . reserveinputs ( fullpsbt )
signed_psbt = l1 . rpc . signpsbt ( fullpsbt ) [ ' signed_psbt ' ]
update_example ( node = l1 , method = ' sendpsbt ' , params = { ' psbt ' : signed_psbt } )
# SQL
update_example ( node = l1 , filename = ' sql-template ' , method = ' sql ' , params = { ' query ' : ' SELECT id FROM peers ' } , description = [ ' A simple peers selection query: ' ] )
update_example ( node = l1 , filename = ' sql-template ' , method = ' sql ' , params = [ f ' SELECT nodeid,last_timestamp FROM nodes WHERE last_timestamp>=1669578892 ' ] , description = [ " A statement containing `=` needs `-o` in shell: " ] )
update_example ( node = l1 , filename = ' sql-template ' , method = ' sql ' , params = [ f " SELECT nodeid FROM nodes WHERE nodeid != x ' { l3 . info [ ' id ' ] } ' " ] , description = [ ' If you want to get specific nodeid values from the nodes table: ' ] )
update_example ( node = l1 , filename = ' sql-template ' , method = ' sql ' , params = [ f " SELECT nodeid FROM nodes WHERE nodeid IN (x ' { l1 . info [ ' id ' ] } ' , x ' { l3 . info [ ' id ' ] } ' ) " ] , description = [ " If you want to compare a BLOB column, `x ' hex ' ` or `X ' hex ' ` are needed: " ] )
update_example ( node = l1 , filename = ' sql-template ' , method = ' sql ' , params = [ ' SELECT peer_id, short_channel_id, to_us_msat, total_msat, peerchannels_status.status FROM peerchannels INNER JOIN peerchannels_status ON peerchannels_status.row = peerchannels.rowid ' ] , description = [ ' Related tables are usually referenced by JOIN: ' ] )
update_example ( node = l2 , filename = ' sql-template ' , method = ' sql ' , params = [ ' SELECT COUNT(*) FROM forwards ' ] , description = [ " Simple function usage, in this case COUNT. Strings inside arrays need \" , and ' to protect them from the shell: " ] )
update_example ( node = l1 , filename = ' sql-template ' , method = ' sql ' , params = [ ' SELECT * from peerchannels_features ' ] )
logger . info ( ' General Utils Done! ' )
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in generating utils examples: { e } ' )
def generate_splice_examples ( node_factory , bitcoind ) :
""" Generates splice related examples """
try :
logger . info ( ' Splice Start... ' )
global FUND_WALLET_AMOUNT_SAT , FUND_CHANNEL_AMOUNT_SAT
# Basic setup for l7->l8
options = [
{
' experimental-splicing ' : None ,
' allow-deprecated-apis ' : True ,
' allow_bad_gossip ' : True ,
' broken_log ' : ' .* ' ,
' dev-bitcoind-poll ' : 3 ,
} . copy ( )
for i in range ( 2 )
]
l7 , l8 = node_factory . get_nodes ( 2 , opts = options )
l7 . fundwallet ( FUND_WALLET_AMOUNT_SAT )
l7 . rpc . connect ( l8 . info [ ' id ' ] , ' localhost ' , l8 . port )
c1112 , _ = l7 . fundchannel ( l8 , FUND_CHANNEL_AMOUNT_SAT )
mine_funding_to_announce ( bitcoind , [ l7 , l8 ] )
l7 . wait_channel_active ( c1112 )
chan_id = l7 . get_channel_id ( l8 )
# Splice
funds_result = l7 . rpc . fundpsbt ( ' 109000sat ' , ' slow ' , 166 , excess_as_change = True )
result = update_example ( node = l7 , method = ' splice_init ' , params = { ' channel_id ' : chan_id , ' relative_amount ' : 100000 , ' initialpsbt ' : funds_result [ ' psbt ' ] } )
result = update_example ( node = l7 , method = ' splice_update ' , params = { ' channel_id ' : chan_id , ' psbt ' : result [ ' psbt ' ] } )
result = l7 . rpc . signpsbt ( result [ ' psbt ' ] )
result = update_example ( node = l7 , method = ' splice_signed ' , params = { ' channel_id ' : chan_id , ' psbt ' : result [ ' signed_psbt ' ] } )
bitcoind . generate_block ( 1 )
sync_blockheight ( bitcoind , [ l7 ] )
l7 . daemon . wait_for_log ( ' to CHANNELD_NORMAL ' )
time . sleep ( 1 )
# Splice out
funds_result = l7 . rpc . addpsbtoutput ( 100000 )
# Pay with fee by subtracting 5000 from channel balance
result = update_example ( node = l7 , method = ' splice_init ' , params = [ chan_id , - 105000 , funds_result [ ' psbt ' ] ] )
result = update_example ( node = l7 , method = ' splice_update ' , params = [ chan_id , result [ ' psbt ' ] ] )
result = update_example ( node = l7 , method = ' splice_signed ' , params = [ chan_id , result [ ' psbt ' ] ] )
update_example ( node = l7 , method = ' stop ' , params = { } )
logger . info ( ' Splice Done! ' )
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in generating splicing examples: { e } ' )
def generate_channels_examples ( node_factory , bitcoind , l1 , l3 , l4 , l5 ) :
""" Generates fundchannel and openchannel related examples """
try :
logger . info ( ' Channels Start... ' )
global FUND_WALLET_AMOUNT_SAT , FUND_CHANNEL_AMOUNT_SAT
# Basic setup for l9->l10 for fundchannel examples
options = [
{
' may_reconnect ' : True ,
' dev-no-reconnect ' : None ,
' allow-deprecated-apis ' : True ,
' allow_bad_gossip ' : True ,
' broken_log ' : ' .* ' ,
' dev-bitcoind-poll ' : 3 ,
} . copy ( )
for i in range ( 2 )
]
l9 , l10 = node_factory . get_nodes ( 2 , opts = options )
amount = 2 * * 24
l9 . fundwallet ( amount + 10000000 )
bitcoind . generate_block ( 1 )
wait_for ( lambda : len ( l9 . rpc . listfunds ( ) [ " outputs " ] ) != 0 )
l9 . rpc . connect ( l10 . info [ ' id ' ] , ' localhost ' , l10 . port )
fund_start = update_example ( node = l9 , method = ' fundchannel_start ' , params = [ l10 . info [ ' id ' ] , amount ] )
tx_prep = update_example ( node = l9 , method = ' txprepare ' , params = [ [ { fund_start [ ' funding_address ' ] : amount } ] ] )
update_example ( node = l9 , method = ' fundchannel_cancel ' , params = [ l10 . info [ ' id ' ] ] )
update_example ( node = l9 , method = ' txdiscard ' , params = [ tx_prep [ ' txid ' ] ] )
fund_start = update_example ( node = l9 , method = ' fundchannel_start ' , params = { ' id ' : l10 . info [ ' id ' ] , ' amount ' : amount } )
tx_prep = update_example ( node = l9 , method = ' txprepare ' , params = { ' outputs ' : [ { fund_start [ ' funding_address ' ] : amount } ] } )
update_example ( node = l9 , method = ' fundchannel_complete ' , params = [ l10 . info [ ' id ' ] , tx_prep [ ' psbt ' ] ] )
update_example ( node = l9 , method = ' txsend ' , params = [ tx_prep [ ' txid ' ] ] )
l9 . rpc . close ( l10 . info [ ' id ' ] )
bitcoind . generate_block ( 1 )
sync_blockheight ( bitcoind , [ l9 ] )
amount = 1000000
fund_start = l9 . rpc . fundchannel_start ( l10 . info [ ' id ' ] , amount )
tx_prep = l9 . rpc . txprepare ( [ { fund_start [ ' funding_address ' ] : amount } ] )
update_example ( node = l9 , method = ' fundchannel_cancel ' , params = { ' id ' : l10 . info [ ' id ' ] } )
update_example ( node = l9 , method = ' txdiscard ' , params = { ' txid ' : tx_prep [ ' txid ' ] } )
funding_addr = l9 . rpc . fundchannel_start ( l10 . info [ ' id ' ] , amount ) [ ' funding_address ' ]
tx_prep = l9 . rpc . txprepare ( [ { funding_addr : amount } ] )
update_example ( node = l9 , method = ' fundchannel_complete ' , params = { ' id ' : l10 . info [ ' id ' ] , ' psbt ' : tx_prep [ ' psbt ' ] } )
update_example ( node = l9 , method = ' txsend ' , params = { ' txid ' : tx_prep [ ' txid ' ] } )
l9 . rpc . close ( l10 . info [ ' id ' ] )
# Basic setup for l11->l12 for openchannel examples
options = [
{
' experimental-dual-fund ' : None ,
' may_reconnect ' : True ,
' dev-no-reconnect ' : None ,
' allow_warning ' : True ,
' allow-deprecated-apis ' : True ,
' allow_bad_gossip ' : True ,
' broken_log ' : ' .* ' ,
' dev-bitcoind-poll ' : 3 ,
} . copy ( )
for i in range ( 2 )
]
l11 , l12 = node_factory . get_nodes ( 2 , opts = options )
l11 . fundwallet ( FUND_WALLET_AMOUNT_SAT )
l11 . rpc . connect ( l12 . info [ ' id ' ] , ' localhost ' , l12 . port )
c78res = l11 . rpc . fundchannel ( l12 . info [ ' id ' ] , FUND_CHANNEL_AMOUNT_SAT )
chan_id = c78res [ ' channel_id ' ]
vins = bitcoind . rpc . decoderawtransaction ( c78res [ ' tx ' ] ) [ ' vin ' ]
assert ( only_one ( vins ) )
prev_utxos = [ " {} : {} " . format ( vins [ 0 ] [ ' txid ' ] , vins [ 0 ] [ ' vout ' ] ) ]
l11 . daemon . wait_for_log ( ' to DUALOPEND_AWAITING_LOCKIN ' )
chan = only_one ( l11 . rpc . listpeerchannels ( l12 . info [ ' id ' ] ) [ ' channels ' ] )
rate = int ( chan [ ' feerate ' ] [ ' perkw ' ] )
next_feerate = ' {} perkw ' . format ( rate * 4 )
# Initiate an RBF
startweight = 42 + 172
initpsbt = update_example ( node = l11 , method = ' utxopsbt ' , params = [ FUND_CHANNEL_AMOUNT_SAT , next_feerate , startweight , prev_utxos , None , True , None , None , True ] )
bump = update_example ( node = l11 , method = ' openchannel_bump ' , params = [ chan_id , FUND_CHANNEL_AMOUNT_SAT , initpsbt [ ' psbt ' ] , next_feerate ] )
update_example ( node = l11 , method = ' openchannel_abort ' , params = { ' channel_id ' : chan_id } )
bump = update_example ( node = l11 , method = ' openchannel_bump ' , params = { ' channel_id ' : chan_id , ' amount ' : FUND_CHANNEL_AMOUNT_SAT , ' initialpsbt ' : initpsbt [ ' psbt ' ] , ' funding_feerate ' : next_feerate } )
update = update_example ( node = l11 , method = ' openchannel_update ' , params = { ' channel_id ' : chan_id , ' psbt ' : bump [ ' psbt ' ] } )
signed = update_example ( node = l11 , method = ' signpsbt ' , params = { ' psbt ' : update [ ' psbt ' ] } )
update_example ( node = l11 , method = ' openchannel_signed ' , params = { ' channel_id ' : chan_id , ' signed_psbt ' : signed [ ' signed_psbt ' ] } )
# 5x the feerate to beat the min-relay fee
chan = only_one ( l11 . rpc . listpeerchannels ( l12 . info [ ' id ' ] ) [ ' channels ' ] )
rate = int ( chan [ ' feerate ' ] [ ' perkw ' ] )
next_feerate = ' {} perkw ' . format ( rate * 5 )
# Another RBF with double the channel amount
startweight = 42 + 172
initpsbt = update_example ( node = l11 , method = ' utxopsbt ' , params = { ' satoshi ' : FUND_CHANNEL_AMOUNT_SAT * 2 , ' feerate ' : next_feerate , ' startweight ' : startweight , ' utxos ' : prev_utxos , ' reservedok ' : True , ' excess_as_change ' : True } )
bump = update_example ( node = l11 , method = ' openchannel_bump ' , params = [ chan_id , FUND_CHANNEL_AMOUNT_SAT * 2 , initpsbt [ ' psbt ' ] , next_feerate ] )
update = update_example ( node = l11 , method = ' openchannel_update ' , params = [ chan_id , bump [ ' psbt ' ] ] )
signed_psbt = update_example ( node = l11 , method = ' signpsbt ' , params = [ update [ ' psbt ' ] ] ) [ ' signed_psbt ' ]
update_example ( node = l11 , method = ' openchannel_signed ' , params = [ chan_id , signed_psbt ] )
bitcoind . generate_block ( 1 )
sync_blockheight ( bitcoind , [ l11 ] )
l11 . daemon . wait_for_log ( ' to CHANNELD_NORMAL ' )
# Fundpsbt, channelopen init, abort, unreserve
psbt_init = update_example ( node = l11 , method = ' fundpsbt ' , params = { ' satoshi ' : FUND_CHANNEL_AMOUNT_SAT , ' feerate ' : ' 253perkw ' , ' startweight ' : 250 , ' reserve ' : 0 } )
start = update_example ( node = l11 , method = ' openchannel_init ' , params = { ' id ' : l12 . info [ ' id ' ] , ' amount ' : FUND_CHANNEL_AMOUNT_SAT , ' initialpsbt ' : psbt_init [ ' psbt ' ] } )
l11 . rpc . openchannel_abort ( start [ ' channel_id ' ] )
update_example ( node = l11 , method = ' unreserveinputs ' , params = { ' psbt ' : psbt_init [ ' psbt ' ] , ' reserve ' : 200 } )
psbt_init = update_example ( node = l11 , method = ' fundpsbt ' , params = { ' satoshi ' : FUND_CHANNEL_AMOUNT_SAT / / 2 , ' feerate ' : ' urgent ' , ' startweight ' : 166 , ' reserve ' : 0 , ' excess_as_change ' : True , ' min_witness_weight ' : 110 } )
start = update_example ( node = l11 , method = ' openchannel_init ' , params = [ l12 . info [ ' id ' ] , FUND_CHANNEL_AMOUNT_SAT / / 2 , psbt_init [ ' psbt ' ] ] )
l11 . rpc . openchannel_abort ( start [ ' channel_id ' ] )
update_example ( node = l11 , method = ' unreserveinputs ' , params = [ psbt_init [ ' psbt ' ] ] )
# Reserveinputs
bitcoind . generate_block ( 1 )
sync_blockheight ( bitcoind , [ l11 ] )
outputs = l11 . rpc . listfunds ( ) [ ' outputs ' ]
psbt_1 = bitcoind . rpc . createpsbt ( [ { ' txid ' : outputs [ 0 ] [ ' txid ' ] , ' vout ' : outputs [ 0 ] [ ' output ' ] } ] , [ ] )
update_example ( node = l11 , method = ' reserveinputs ' , params = { ' psbt ' : psbt_1 } )
l11 . rpc . unreserveinputs ( psbt_1 )
psbt_2 = bitcoind . rpc . createpsbt ( [ { ' txid ' : outputs [ 1 ] [ ' txid ' ] , ' vout ' : outputs [ 1 ] [ ' output ' ] } ] , [ ] )
update_example ( node = l11 , method = ' reserveinputs ' , params = { ' psbt ' : psbt_2 } )
l11 . rpc . unreserveinputs ( psbt_2 )
# Multifundchannel 1
l3 . rpc . connect ( l5 . info [ ' id ' ] , ' localhost ' , l5 . port )
l4 . rpc . connect ( l1 . info [ ' id ' ] , ' localhost ' , l1 . port )
c35res = update_example ( node = l3 , method = ' fundchannel ' , params = { ' id ' : l5 . info [ ' id ' ] , ' amount ' : FUND_CHANNEL_AMOUNT_SAT , ' announce ' : True } )
outputs = l4 . rpc . listfunds ( ) [ ' outputs ' ]
utxo = f " { outputs [ 0 ] [ ' txid ' ] } : { outputs [ 0 ] [ ' output ' ] } "
c41res = update_example ( node = l4 , method = ' fundchannel ' ,
params = { ' id ' : l1 . info [ ' id ' ] , ' amount ' : ' all ' , ' feerate ' : ' normal ' , ' push_msat ' : 100000 , ' utxos ' : [ utxo ] } ,
description = [ f ' This example shows how to to open new channel with peer { l1 . info [ " id " ] } from one whole utxo { utxo } (you can use **listfunds** command to get txid and vout): ' ] )
# Close newly funded channels to bring the setup back to initial state
l3 . rpc . close ( c35res [ ' channel_id ' ] )
print ( f ' c41res: { c41res } ' )
l4 . rpc . close ( c41res [ ' channel_id ' ] )
l3 . rpc . disconnect ( l5 . info [ ' id ' ] , True )
l4 . rpc . disconnect ( l1 . info [ ' id ' ] , True )
# Multifundchannel 2
l1 . fundwallet ( 10 * * 8 )
l1 . rpc . connect ( l3 . info [ ' id ' ] , ' localhost ' , l3 . port )
l1 . rpc . connect ( l4 . info [ ' id ' ] , ' localhost ' , l4 . port )
l1 . rpc . connect ( l5 . info [ ' id ' ] , ' localhost ' , l5 . port )
multifund_res1 = update_example ( node = l1 , method = ' multifundchannel ' , params = {
' destinations ' :
[
{
' id ' : f ' { l3 . info [ " id " ] } @127.0.0.1: { l3 . port } ' ,
' amount ' : ' 20000sat '
} ,
{
' id ' : f ' { l4 . info [ " id " ] } @127.0.0.1: { l4 . port } ' ,
' amount ' : ' 0.0003btc '
} ,
{
' id ' : f ' { l5 . info [ " id " ] } @127.0.0.1: { l5 . port } ' ,
' amount ' : ' all '
}
] ,
' feerate ' : ' 10000perkw ' ,
' commitment_feerate ' : ' 2000perkw '
} , description = [
' This example opens three channels at once, with amounts 20,000 sats, 30,000 sats ' ,
' and the final channel using all remaining funds (actually, capped at 16,777,215 sats ' ,
' because large-channels is not enabled): '
] )
for channel in multifund_res1 [ ' channel_ids ' ] :
l1 . rpc . close ( channel [ ' channel_id ' ] )
l1 . fundwallet ( 10 * * 8 )
multifund_res2 = update_example ( node = l1 , method = ' multifundchannel ' , params = {
' destinations ' :
[
{
' id ' : f ' 03a389b3a2f7aa6f9f4ccc19f2bd7a2eba83596699e86b715caaaa147fc37f3144@127.0.0.1: { l3 . port } ' ,
' amount ' : 50000
} ,
{
' id ' : f ' { l4 . info [ " id " ] } @127.0.0.1: { l4 . port } ' ,
' amount ' : 50000
} ,
{
' id ' : f ' { l1 . info [ " id " ] } @127.0.0.1: { l1 . port } ' ,
' amount ' : 50000
}
] , ' minchannels ' : 1
} )
# Close newly funded channels to bring the setup back to initial state
for channel in multifund_res2 [ ' channel_ids ' ] :
l1 . rpc . close ( channel [ ' channel_id ' ] )
l1 . rpc . disconnect ( l3 . info [ ' id ' ] , True )
l1 . rpc . disconnect ( l4 . info [ ' id ' ] , True )
l1 . rpc . disconnect ( l5 . info [ ' id ' ] , True )
bitcoind . generate_block ( 1 )
sync_blockheight ( bitcoind , [ l1 , l3 , l4 , l5 ] )
logger . info ( ' Channels Done! ' )
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in generating fundchannel and openchannel examples: { e } ' )
def generate_autoclean_delete_examples ( l1 , l2 , l3 , l4 , l5 , c12 , c23 ) :
""" Records autoclean and delete examples """
try :
logger . info ( ' Auto-clean and Delete Start... ' )
global FUND_CHANNEL_AMOUNT_SAT
l2 . rpc . close ( l5 . info [ ' id ' ] )
update_example ( node = l2 , method = ' dev-forget-channel ' , params = { ' id ' : l5 . info [ ' id ' ] } , description = [ f ' Forget a channel by peer pubkey when only one channel exists with the peer: ' ] )
# Create invoices for delpay and delinvoice examples
inv_l35 = l3 . rpc . invoice ( ' 50000sat ' , ' lbl_l35 ' , ' l35 description ' )
inv_l36 = l3 . rpc . invoice ( ' 50000sat ' , ' lbl_l36 ' , ' l36 description ' )
inv_l37 = l3 . rpc . invoice ( ' 50000sat ' , ' lbl_l37 ' , ' l37 description ' )
# For MPP payment from l1 to l4; will use for delpay groupdid and partid example
inv_l41 = l4 . rpc . invoice ( ' 5000sat ' , ' lbl_l41 ' , ' l41 description ' )
l2 . rpc . connect ( l4 . info [ ' id ' ] , ' localhost ' , l4 . port )
c24 , _ = l2 . fundchannel ( l4 , FUND_CHANNEL_AMOUNT_SAT )
l2 . rpc . pay ( l4 . rpc . invoice ( 500000000 , ' lbl balance l2 to l4 ' , ' description send some sats l2 to l4 ' ) [ ' bolt11 ' ] )
# Create two routes; l1->l2->l3->l4 and l1->l2->l4
route_l1_l4 = l1 . rpc . getroute ( l4 . info [ ' id ' ] , ' 4000sat ' , 1 ) [ ' route ' ]
route_l1_l2_l4 = [ { ' amount_msat ' : ' 1000sat ' , ' id ' : l2 . info [ ' id ' ] , ' delay ' : 5 , ' channel ' : c12 } ,
{ ' amount_msat ' : ' 1000sat ' , ' id ' : l4 . info [ ' id ' ] , ' delay ' : 5 , ' channel ' : c24 } ]
l1 . rpc . sendpay ( route_l1_l4 , inv_l41 [ ' payment_hash ' ] , amount_msat = ' 5000sat ' , groupid = 1 , partid = 1 , payment_secret = inv_l41 [ ' payment_secret ' ] )
l1 . rpc . sendpay ( route_l1_l2_l4 , inv_l41 [ ' payment_hash ' ] , amount_msat = ' 5000sat ' , groupid = 1 , partid = 2 , payment_secret = inv_l41 [ ' payment_secret ' ] )
# Close l2->l4 for initial state
l2 . rpc . close ( l4 . info [ ' id ' ] )
l2 . rpc . disconnect ( l4 . info [ ' id ' ] , True )
# Delinvoice
l1 . rpc . pay ( inv_l35 [ ' bolt11 ' ] )
l1 . rpc . pay ( inv_l37 [ ' bolt11 ' ] )
update_example ( node = l3 , method = ' delinvoice ' , params = { ' label ' : ' lbl_l36 ' , ' status ' : ' unpaid ' } )
# invoice already deleted, pay will fail; used for delpay failed example
with pytest . raises ( RpcError ) :
l1 . rpc . pay ( inv_l36 [ ' bolt11 ' ] )
listsendpays_l1 = l1 . rpc . listsendpays ( ) [ ' payments ' ]
sendpay_g1_p1 = next ( ( x for x in listsendpays_l1 if ' groupid ' in x and x [ ' groupid ' ] == 1 and ' partid ' in x and x [ ' partid ' ] == 2 ) , None )
update_example ( node = l1 , method = ' delpay ' , params = { ' payment_hash ' : listsendpays_l1 [ 0 ] [ ' payment_hash ' ] , ' status ' : ' complete ' } )
update_example ( node = l1 , method = ' delpay ' , params = [ listsendpays_l1 [ - 1 ] [ ' payment_hash ' ] , listsendpays_l1 [ - 1 ] [ ' status ' ] ] )
update_example ( node = l1 , method = ' delpay ' , params = { ' payment_hash ' : sendpay_g1_p1 [ ' payment_hash ' ] , ' status ' : sendpay_g1_p1 [ ' status ' ] , ' groupid ' : 1 , ' partid ' : 2 } )
update_example ( node = l3 , method = ' delinvoice ' , params = { ' label ' : ' lbl_l37 ' , ' status ' : ' paid ' , ' desconly ' : True } )
# Delforward
failed_forwards = l2 . rpc . listforwards ( ' failed ' ) [ ' forwards ' ]
local_failed_forwards = l2 . rpc . listforwards ( ' local_failed ' ) [ ' forwards ' ]
if len ( local_failed_forwards ) > 0 and ' in_htlc_id ' in local_failed_forwards [ 0 ] :
update_example ( node = l2 , method = ' delforward ' , params = { ' in_channel ' : c12 , ' in_htlc_id ' : local_failed_forwards [ 0 ] [ ' in_htlc_id ' ] , ' status ' : ' local_failed ' } )
if len ( failed_forwards ) > 0 and ' in_htlc_id ' in failed_forwards [ 0 ] :
update_example ( node = l2 , method = ' delforward ' , params = [ c12 , failed_forwards [ 0 ] [ ' in_htlc_id ' ] , ' failed ' ] )
update_example ( node = l2 , method = ' dev-forget-channel ' , params = { ' id ' : l3 . info [ ' id ' ] , ' short_channel_id ' : c23 , ' force ' : True } , description = [ f ' Forget a channel by short channel id when peer has multiple channels: ' ] )
# Autoclean
update_example ( node = l2 , method = ' autoclean-once ' , params = [ ' failedpays ' , 1 ] )
update_example ( node = l2 , method = ' autoclean-once ' , params = [ ' succeededpays ' , 1 ] )
update_example ( node = l2 , method = ' autoclean-status ' , params = { ' subsystem ' : ' expiredinvoices ' } )
update_example ( node = l2 , method = ' autoclean-status ' , params = { } )
logger . info ( ' Auto-clean and Delete Done! ' )
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in generating autoclean and delete examples: { e } ' )
def generate_backup_recovery_examples ( node_factory , l4 , l5 , l6 ) :
""" Node backup and recovery examples """
try :
logger . info ( ' Backup and Recovery Start... ' )
# New node l13 used for recover example
l13 = node_factory . get_node ( )
update_example ( node = l5 , method = ' makesecret ' , params = [ ' 73636220736563726574 ' ] )
update_example ( node = l5 , method = ' makesecret ' , params = { ' string ' : ' scb secret ' } )
update_example ( node = l4 , method = ' emergencyrecover ' , params = { } )
backup_l4 = update_example ( node = l4 , method = ' staticbackup ' , params = { } )
# Recover channels
l4 . stop ( )
os . unlink ( os . path . join ( l4 . daemon . lightning_dir , TEST_NETWORK , ' lightningd.sqlite3 ' ) )
l4 . start ( )
time . sleep ( 1 )
update_example ( node = l4 , method = ' recoverchannel ' , params = [ backup_l4 [ ' scb ' ] ] )
# Emergency recover
l5 . stop ( )
os . unlink ( os . path . join ( l5 . daemon . lightning_dir , TEST_NETWORK , ' lightningd.sqlite3 ' ) )
l5 . start ( )
time . sleep ( 1 )
update_example ( node = l5 , method = ' emergencyrecover ' , params = { } )
# Recover
def get_hsm_secret ( n ) :
""" Returns codex32 and hex """
try :
hsmfile = os . path . join ( n . daemon . lightning_dir , TEST_NETWORK , " hsm_secret " )
codex32 = subprocess . check_output ( [ " tools/hsmtool " , " getcodexsecret " , hsmfile , " leet " ] ) . decode ( ' utf-8 ' ) . strip ( )
with open ( hsmfile , " rb " ) as f :
hexhsm = f . read ( ) . hex ( )
return codex32 , hexhsm
except Exception as e :
logger . error ( f ' Error in getting hsm secret: { e } ' )
_ , l6hex = get_hsm_secret ( l6 )
l13codex32 , _ = get_hsm_secret ( l13 )
update_example ( node = l6 , method = ' recover ' , params = { ' hsmsecret ' : l6hex } )
update_example ( node = l13 , method = ' recover ' , params = { ' hsmsecret ' : l13codex32 } )
logger . info ( ' Backup and Recovery Done! ' )
except TaskFinished :
raise
except Exception as e :
logger . error ( f ' Error in generating backup and recovery examples: { e } ' )
@unittest.skipIf ( GENERATE_EXAMPLES is not True , ' Generates examples for doc/schema/lightning-*.json files. ' )
def test_generate_examples ( node_factory , bitcoind , executor ) :
""" Re-generates examples for doc/schema/lightning-*.json files """
try :
global ALL_METHOD_NAMES , ALL_RPC_EXAMPLES , REGENERATING_RPCS , RPCS_STATUS
def list_all_examples ( ) :
""" list all methods used in ' update_example ' calls to ensure that all methods are covered """
try :
global REGENERATING_RPCS
methods = { }
file_path = os . path . abspath ( __file__ )
# Parse and traverse this file's content to list all methods & file names
with open ( file_path , " r " ) as file :
file_content = file . read ( )
tree = ast . parse ( file_content )
for node in ast . walk ( tree ) :
if isinstance ( node , ast . Call ) and isinstance ( node . func , ast . Name ) and node . func . id == ' update_example ' :
for keyword in node . keywords :
if ( keyword . arg == ' method ' and isinstance ( keyword . value , ast . Str ) ) or ( keyword . arg == ' filename ' and isinstance ( keyword . value , ast . Str ) ) :
method_name = keyword . value . s
if method_name not in methods :
methods [ method_name ] = { ' method ' : method_name , ' num_examples ' : 1 , ' executed ' : 0 }
else :
methods [ method_name ] [ ' num_examples ' ] + = 1
return list ( methods . values ( ) )
except Exception as e :
logger . error ( f ' Error in listing all examples: { e } ' )
def list_missing_examples ( ) :
""" Checks for missing example file or example & log an error if missing. """
try :
global ALL_METHOD_NAMES
for file_name in os . listdir ( ' doc/schemas ' ) :
if not file_name . endswith ( ' .json ' ) :
continue
file_name_str = str ( file_name ) . replace ( ' lightning- ' , ' ' ) . replace ( ' .json ' , ' ' )
# Log an error if the method is not in the list
if file_name_str not in ALL_METHOD_NAMES :
logger . error ( f ' Missing File or Example { file_name_str } . ' )
except Exception as e :
logger . error ( f ' Error in listing missing examples: { e } ' )
def clear_existing_examples ( ) :
2024-08-25 09:52:34 -04:00
""" Clear existing examples in JSON files to regenerate them later """
2024-07-12 21:16:36 -07:00
global REGENERATING_RPCS
for rpc in REGENERATING_RPCS :
try :
global CWD
file_path = os . path . join ( CWD , ' doc ' , ' schemas ' , f ' lightning- { rpc } .json ' )
with open ( file_path , ' r+ ' , encoding = ' utf-8 ' ) as file :
data = json . load ( file )
# Deletes the 'examples' key corresponding to the method's file
if ' examples ' in data :
del data [ ' examples ' ]
file . seek ( 0 )
json . dump ( data , file , indent = 2 , ensure_ascii = False )
file . write ( ' \n ' )
file . truncate ( )
except FileNotFoundError as fnf_error :
logger . error ( f ' File not found error { fnf_error } for { file_path } ' )
except Exception as e :
logger . error ( f ' Error saving example in file { file_path } : { e } ' )
logger . info ( f ' Cleared Examples: { REGENERATING_RPCS } ' )
return None
ALL_RPC_EXAMPLES = list_all_examples ( )
ALL_METHOD_NAMES = [ example [ ' method ' ] for example in ALL_RPC_EXAMPLES ]
logger . info ( f ' This test can reproduce examples for { len ( ALL_RPC_EXAMPLES ) } methods: { ALL_METHOD_NAMES } ' )
REGENERATING_RPCS = [ rpc . strip ( ) for rpc in os . getenv ( " REGENERATE " ) . split ( ' , ' ) ] if os . getenv ( " REGENERATE " ) else ALL_METHOD_NAMES
logger . info ( f ' Regenerating examples for: { REGENERATING_RPCS } ' )
RPCS_STATUS = [ False ] * len ( REGENERATING_RPCS )
list_missing_examples ( )
clear_existing_examples ( )
l1 , l2 , l3 , l4 , l5 , l6 , c12 , c23 , c25 , c34 , c23res = setup_test_nodes ( node_factory , bitcoind )
inv_l11 , inv_l21 , inv_l22 , inv_l31 , inv_l32 , inv_l34 = generate_transactions_examples ( l1 , l2 , l3 , l4 , l5 , c25 , bitcoind )
rune_l21 = generate_runes_examples ( l1 , l2 , l3 )
generate_datastore_examples ( l2 )
generate_bookkeeper_examples ( l2 , l3 , c23res [ ' channel_id ' ] )
generate_offers_renepay_examples ( l1 , l2 , inv_l21 , inv_l34 )
generate_list_examples ( l1 , l2 , l3 , c12 , c23 , inv_l31 , inv_l32 )
generate_wait_examples ( l1 , l2 , bitcoind , executor )
generate_utils_examples ( l1 , l2 , l3 , l4 , l5 , l6 , c23 , c34 , inv_l11 , inv_l22 , rune_l21 , bitcoind )
generate_splice_examples ( node_factory , bitcoind )
generate_channels_examples ( node_factory , bitcoind , l1 , l3 , l4 , l5 )
generate_autoclean_delete_examples ( l1 , l2 , l3 , l4 , l5 , c12 , c23 )
generate_backup_recovery_examples ( node_factory , l4 , l5 , l6 )
logger . info ( ' All examples generated successfully! ' )
except TaskFinished as m :
logger . info ( m )
except Exception as e :
# FIXME: The test passes but with flaky errors:
# 1: plugin-bcliBROKEN: bitcoin-cli -regtest -datadir=/tmp/ltests-65999628/test_generate_examples_1/lightning-6/ -rpcclienttimeout=60 -rpcport=57425 -rpcuser=... -stdinrpcpass getblockhash 159 exited 1 (after 60 other errors)
# 'Error: Specified data directory \"/tmp/ltests-65999628/test_generate_examples_1/lightning-6/\" does not exist.\n'; we have been retrying command for --bitcoin-retry-timeout=60 seconds; bitcoind setup or our --bitcoin-* configs broken?
# 2: Node /tmp/ltests-joqzs3fy/test_generate_examples_1/lightning-3/ has memory leaks: [{"subdaemon": "lightningd"}]
logger . error ( e )