diff --git a/README.md b/README.md index 7c2d196..77b344e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Ringtools Ringtools is a tool to keep an eye on the by you provided channels, like to monitor a Ring of Fire. + + -------------------- ## Installation @@ -9,6 +11,8 @@ Ringtools is a tool to keep an eye on the by you provided channels, like to moni 3. Clone this repository `git clone https://github.com/StijnBTC/Ringtools` 4. Navigate to the right folder `cd Ringtools` 5. Install the requirements `pip3 install -r requirements.txt` -6. Edit the channel file `sudo nano channels.txt` -7. Replace all channels with the channels from your ring and save (Ctrl+X following Y (Yes)) -8. Run Ringtools `python3 ringtools.py -f -l status` (When you're ready, hit Ctrl+C) + +## Usage +1. Put all public keys and telegram usernames, separated with a comma in `pubkeys.txt` +2. Run `checkring` functionality with write to create channels.txt `python3 ringtools.py check --write-channels` +3. Run Ringtools `python3 ringtools.py -f -l status` (When you're ready, hit Ctrl+C) diff --git a/checkring.py b/checkring.py new file mode 100644 index 0000000..0667528 --- /dev/null +++ b/checkring.py @@ -0,0 +1,89 @@ +import os +import grpc +from time import sleep + +from output import format_channel_error, format_error, clear_screen, format_channel +from yachalk import chalk + + +class CheckRing: + def __init__(self, lnd, output, pubkeys_file, write_channels, show_fees, channels_file): + self.lnd = lnd + self.output = output + self.pubkeys_file = pubkeys_file + self.show_fees = show_fees + self.write_channels = write_channels + self.channels_file = channels_file + + def read_file(self, file): + if not os.path.isfile(file): + self.handle_error("File does not exist") + else: + with open(self.pubkeys_file) as file: + return file.read().splitlines() + + def run(self): + self.once() + + + def once(self): + pubkeys = self.read_file(self.pubkeys_file) + channelList = [] + + for idx, pubkeyInfo in enumerate(pubkeys): + # pubkeys format is , to be able to mimic the manual pubkey overview with usernames + pubkey = pubkeyInfo.split(',') + try: + response = self.lnd.get_node_channels(pubkey[0]) + + print("%s" % + (chalk.yellow(response.node.alias))) + + channelTo = pubkeys[(idx+1) % (len(pubkeys))].split(',')[0] + hasChannel = False + channelId = 0 + channelInfo = {} + + for channel in response.channels: + if (channel.node1_pub == channelTo) or (channel.node2_pub == channelTo): + hasChannel = True + channelId = channel.channel_id + channelInfo = channel + channelList.append(channelId) + + if hasChannel: + outputHas = chalk.green('✅') + else: + outputHas = chalk.red('🙌🏻') + print("%s %s\r\n%s" % + (outputHas, pubkey[1], pubkey[0])) + + if hasChannel: + print(chalk.green("Channel is open with ID: %s") % channelId) + + if self.show_fees: + response = self.lnd.get_edge(int(channelId)) + node1 = self.lnd.get_node(response.node1_pub) + node2 = self.lnd.get_node(response.node2_pub) + disabled = response.node1_policy.disabled or response.node2_policy.disabled + self.print_channel( + channelInfo, node1.alias, node2.alias, disabled) + else: + print(chalk.red("Should open to node with pubkey %s") % channelTo) + except grpc.RpcError as e: + self.output.print_line(format_channel_error(pubkey, repr(e))) + except Exception as error: + self.output.print_line( + format_channel_error(pubkey, repr(error))) + if self.write_channels: + f = open(self.channels_file, "w") + for c in channelList: + f.write(str(c) + "\r\n") + f.close() + + def print_channel(self, channel, node1_alias, node2_alias, chan_disabled): + self.output.print_line(format_channel( + channel, node1_alias, node2_alias, chan_disabled, self.show_fees)) + + def handle_error(self, error): + self.output.print_line(format_error(error)) diff --git a/lnd.py b/lnd.py index 3f4727d..7912d07 100644 --- a/lnd.py +++ b/lnd.py @@ -48,6 +48,13 @@ class Lnd: def get_edge(self, channel_id): return self.stub.GetChanInfo(ln.ChanInfoRequest(chan_id=channel_id)) + + @lru_cache(maxsize=None) + def get_node_channels(self, pubkey): + return self.stub.GetNodeInfo(ln.NodeInfoRequest( + + pub_key=pubkey, include_channels=True)) + @lru_cache(maxsize=None) def get_node_alias(self, pub_key): return self.stub.GetNodeInfo( diff --git a/pubkeys.txt b/pubkeys.txt new file mode 100644 index 0000000..9232011 --- /dev/null +++ b/pubkeys.txt @@ -0,0 +1,21 @@ +0294e9ad2727d623fb22870e32f167d4d014e2f7adccb0926802f0bd4d17959093,@akkorokamui +028e748c8f24acb985cd428eb072d3d71d1a692acd38a4d22b14fb7e6bf6513f33,@MBTC_1 +03a3c59c4081ec3f0f5b889ca16c8193d22d1c4aec94d1e6e3873e95c9c3592763,@Kippers37 +02258a9aa9463749bf729511af78b1ea339ecdb36b37a6e18a8c8caa895228bfcb,@PeteDoo +02fa5c81d1d41e8d8001e6169fe454441360cfb876dc6c5b82fb5d20c0da5b9a51,Chefke +02719ddac30c16dd856bb61ec4a52a5984d345da078c1855baeef856976e9f312a,Jobe[Jotne-Nodenor] +03aba668b98dbe9550cd998648878c445118c7eff35471f6583c526ad0f39f68aa,@Frodo7 +037305dfd540c28353120b42cabd6debf3a832283a18679a7ef205684223998e85,@WolligSchaap +026776c241841182e8f44648b7972249b3a1ba1291a8e1af5c383b3219864cb027,@SatStekker +021d70a400e9b8b0ebc1a8816da9c3294eba17d01beaafa210e5870233aee13d98,@RDdeBruyn +023d153b512346a3a6026859ecf906d7f6912bc9fa4214ed4439ffafe47ba53cef,@TReader01 +02b74b259fbfb180e5fac9369204b27aa889cffcc4c348ce7a2b99b52eb0d11af9,@BentedeBitcoinBoer +031ff1fa0ce00bece0879270798ee06d29a54f54e9a4ac632beeddcc74f000e217,@1665 +02c23df35912b9393b92cd16761e22e122651b7960720e94a13c01697bfdd27aa1,@commaCamel (Comma) +027ce2952b775e3cc4700d159d82b53f2559dc1f53faf2ca05c7ef28a26d90ac43,Roy +03706a7c275576f161d916e28704f0fe0a487758168147d3722e90855d18cc4336,@J_A_C_C_O (JACCO) +02b6b8e6b683811f358e9602f8271694951b92dd66ff0841501f2ce9ddcf7bc2fc,@I2Sappig +0334aadd6b72f00168c87385c081a171e1a1a1936435c2952d25dd10d2f17d6c43,@GrunnBliksems +037280da8985d5b1b143adec8277d182b709414fcfd9771aaeb31f57bbe81d5b83,@Knetsooj +035c709aae4bcb860ebf07da75e6454172ce27b0dc2131c7263479696e2f1fe6bd,@kapital8one +0205a19356bbb7482057356aef070285a2ce6141d2448545210e9d575b57eddd37,@dsbaars diff --git a/ringtools.py b/ringtools.py index 6b99093..7f62bb2 100644 --- a/ringtools.py +++ b/ringtools.py @@ -5,7 +5,7 @@ from lnd import Lnd from output import Output from status import Status from utils import is_umbrel - +from checkring import CheckRing class RingTools: def __init__(self, arguments): @@ -20,6 +20,14 @@ class RingTools: self.arguments.channels_file, self.arguments.loop, self.arguments.show_fees).run() + elif self.arguments.function == "check": + CheckRing(self.lnd, + self.output, + self.arguments.pubkeys_file, + self.arguments.write_channels, + self.arguments.show_fees, + self.arguments.channels_file + ).run() pass @@ -34,7 +42,7 @@ def get_argument_parser(): # This is needed for the cert and macaroon of LND parser.add_argument( dest="function", - choices=['status'], + choices=['status', 'check'], help="Choose which function of the RingTools you would " "like to use", default="help", @@ -56,10 +64,31 @@ def get_argument_parser(): dest="grpc", help="(default localhost:10009) lnd gRPC endpoint", ) + status_group = parser.add_argument_group( "status", "Get the current status of all channels", ) + check_group = parser.add_argument_group( + "check", + "Check if channels are open with pubkey list", + ) + + check_group.add_argument( + "-pubkeys-file", + "-pk", + default="./pubkeys.txt", + dest="pubkeys_file", + help="(default ./pubkeys.txt) pubkeys file" + ) + check_group.add_argument( + '-w', + '--write-channels', + action="store_true", + dest="write_channels", + help="(default False) Write channels.txt" + ) + status_group.add_argument( "-channels-file", "-c", @@ -67,6 +96,7 @@ def get_argument_parser(): dest="channels_file", help="(default ./channels.txt) channels file" ) + status_group.add_argument( "-l", "--loop", @@ -81,6 +111,7 @@ def get_argument_parser(): dest="show_fees", help="(default False) Shows fees in status screen" ) + return parser