diff --git a/bin/check_electrum_wallet_completeness b/bin/check_electrum_wallet_completeness new file mode 100644 index 0000000000000000000000000000000000000000..ddcdec01d9bcd90b9964b5b5d73a4b7ce823ed21 --- /dev/null +++ b/bin/check_electrum_wallet_completeness @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'redis' +require 'dhall' +require_relative '../lib/redis_addresses' +require_relative '../lib/electrum' + +config = + Dhall::Coder + .new(safe: Dhall::Coder::JSON_LIKE + [Symbol, Proc]) + .load(ARGV[0], transform_keys: :to_sym) + +redis = Redis.new +electrum = Electrum.new(**config) + +electrum_addrs = electrum.listaddresses + +get_addresses_with_users(redis).each do |addr, keys| + unless electrum_addrs.include?(addr) + puts "The address #{addr} (included in #{keys.join(", ")}) isn't included in electrum's list" + end +end diff --git a/bin/correct_duplicate_addrs b/bin/correct_duplicate_addrs new file mode 100644 index 0000000000000000000000000000000000000000..9d5550a91363fd50edfb448f7fcf36fa0e57dbc5 --- /dev/null +++ b/bin/correct_duplicate_addrs @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# This is meant to be run with the output of detect_duplicate_addrs on stdin +# The assumption is that some logging will dump that, and then someone will +# run this after looking into why +# Theoretically they could be piped together directly for automated fixing + +require 'redis' + +redis = Redis.new + +customer_id = ENV['DEFAULT_CUSTOMER_ID'] +unless customer_id + puts "The env-var DEFAULT_CUSTOMER_ID must be set to the ID of the customer who will receive the duplicated addrs, preferably a support customer or something linked to notifications when stray money is sent to these addresses" + exit 1 +end + + +STDIN.each_line do |line| + match = line.match(/^(\w+) is used by the following \d+ keys: (.*)/) + unless match + puts "The following line can't be understood and is being ignored" + puts " #{line}" + next + end + + addr = match[1] + keys = match[2].split(" ") + + # This is the customer ID of the support chat + # All duplicates are moved to the support addr so we still hear when people + # send money there + redis.sadd("jmp_customer_btc_addresses-#{customer_id}", addr) + + keys.each do |key| + redis.srem(key, addr) + end +end diff --git a/bin/detect_duplicate_addrs b/bin/detect_duplicate_addrs new file mode 100755 index 0000000000000000000000000000000000000000..309ae1e7193e95c74ab5acdfa97b698b30cc8b7e --- /dev/null +++ b/bin/detect_duplicate_addrs @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'redis' +require_relative '../lib/redis_addresses' + +redis = Redis.new + +get_addresses_with_users(redis).each do |addr, keys| + if keys.length > 1 + puts "#{addr} is used by the following #{keys.length} keys: #{keys.join(" ")}" + end +end diff --git a/bin/reassert_electrum_notification b/bin/reassert_electrum_notification new file mode 100644 index 0000000000000000000000000000000000000000..83bc1d2705ae660c24be87f5c91b84d8cf620962 --- /dev/null +++ b/bin/reassert_electrum_notification @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'redis' +require 'dhall' +require_relative '../lib/redis_addresses' +require_relative '../lib/electrum' + +config = + Dhall::Coder + .new(safe: Dhall::Coder::JSON_LIKE + [Symbol, Proc]) + .load(ARGV[0], transform_keys: :to_sym) + +redis = Redis.new +electrum = Electrum.new(**config) + +get_addresses_with_users(redis).each do |addr, keys| + match = keys.first.match(/.*-(\d+)$/) + unless match + puts "Can't understand key #{keys.first}, skipping" + next + end + + customer_id = match[1] + url = "https://pay.jmp.chat/electrum_notify?address=#{addr}&customer_id=#{customer_id}" + + unless electrum.notify(addr, url) + puts "Failed to setup #{addr} to notify #{url}. Skipping" + end +end diff --git a/lib/electrum.rb b/lib/electrum.rb index 1cdaf908d6635df2dafd1b35587165af0e5f389e..f4489ebd78b3a544b35b7787f56c72bc635975ee 100644 --- a/lib/electrum.rb +++ b/lib/electrum.rb @@ -27,6 +27,14 @@ class Electrum rpc_call(:get_tx_status, txid: tx_hash)["result"] end + def listaddresses + rpc_call(:listaddresses, {})["result"] + end + + def notify(address, url) + rpc_call(:notify, address: address, URL: url)["result"] + end + class Transaction def initialize(electrum, tx_hash, tx) @electrum = electrum diff --git a/lib/redis_addresses.rb b/lib/redis_addresses.rb new file mode 100644 index 0000000000000000000000000000000000000000..17a5a1e10b273bef430b9597c102217350cc198f --- /dev/null +++ b/lib/redis_addresses.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "redis" + +# This returns a hash +# The keys are the bitcoin addresses, the values are all of the keys which +# contain that address +# If there are no duplicates, then each value will be a singleton list +def get_addresses_with_users(redis) + addrs = Hash.new { |h, k| h[k] = [] } + + # I picked 1000 because it made a relatively trivial case take 15 seconds + # instead of forever. + # Basically it's "how long does each command take" + # The lower it is (default is 10), it will go back and forth to the client a + # ton + redis.scan_each(match: "jmp_customer_btc_addresses-*", count: 1000) do |key| + redis.smembers(key).each do |addr| + addrs[addr] << key + end + end + + addrs +end