SatSale

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 9592d4218fca15abda9dc6df80332538e60fa39e
parent 5b62afbc6a3982f15e916bacd7be15e608553bab
Author: Kristaps Kaupe <kristaps@blogiem.lv>
Date:   Tue, 11 Jan 2022 08:18:46 +0200

Use Python logging facility (#34)


Diffstat:
Mconfig.py | 1+
Mconfig.toml | 3+++
Mgateways/lightning_address.py | 11++++++-----
Mgateways/paynym.py | 7++++---
Mgateways/ssh_tunnel.py | 11++++++-----
Mgateways/tor.py | 6+++---
Mgateways/woo_webhook.py | 3++-
Mnode/bitcoind.py | 19++++++++++---------
Mnode/clightning.py | 17+++++++++--------
Mnode/lnd.py | 31++++++++++++++++---------------
Mpayments/database.py | 2+-
Mpayments/price_feed.py | 9+++++----
Mpayments/weakhands.py | 23++++++++++++-----------
Msatsale.py | 29+++++++++++++++++------------
14 files changed, 95 insertions(+), 77 deletions(-)

diff --git a/config.py b/config.py @@ -45,3 +45,4 @@ lightning_address_comment = get_opt("lightning_address_comment", None) liquid_address = get_opt("liquid_address", None) paynym = get_opt("paynym", None) free_mode = get_opt("free_mode", False) +loglevel = get_opt("loglevel", "DEBUG") diff --git a/config.toml b/config.toml @@ -65,6 +65,9 @@ required_confirmations = 2 # Global connection attempts connection_attempts = 3 +# Console log level (DEBUG, INFO, WARNING, ERROR) +loglevel = "DEBUG" + # Generic redirect url after payment redirect = "https://github.com/nickfarrow/satsale" diff --git a/gateways/lightning_address.py b/gateways/lightning_address.py @@ -1,6 +1,7 @@ from flask import request from flask_restplus import Resource, Api, Namespace, fields import hashlib +import logging import config @@ -19,7 +20,7 @@ def add_ln_address_decorators(app, api, node): class get_ln_address(Resource): def get(self): try: - print("Someone requested our ln address: {}!".format(config.lightning_address)) + logging.info("Someone requested our ln address: {}!".format(config.lightning_address)) resp = { "callback": "https://{}/lnaddr".format(config.lightning_address.split("@")[1]), "maxSendable": max_sats*10**3, @@ -30,7 +31,7 @@ def add_ln_address_decorators(app, api, node): return resp except Exception as e: - print(e) + logging.error(e) return {"status": "ERROR", "reason": e} @@ -43,19 +44,19 @@ def add_ln_address_decorators(app, api, node): amount_msats = int(request.args.get("amount")) amount_btc = amount_msats / 10**(3+8) - print("Received payment request from ln address for {} msats...".format(amount_msats)) + logging.info("Received payment request from ln address for {} msats...".format(amount_msats)) description_hash = hashlib.sha256(metadata.encode()).digest() try: invoice, _ = node.create_lnd_invoice(amount_btc, memo="lightning address payment", description_hash=description_hash) - print("Responding with invoice {}".format(invoice)) + logging.info("Responding with invoice {}".format(invoice)) return { "pr": invoice, "routes": [] } except Exception as e: - print(e) + logging.error(e) return {"status": "ERROR", "reason": e} diff --git a/gateways/paynym.py b/gateways/paynym.py @@ -1,4 +1,5 @@ import requests +import logging paynym_site = "https://paynym.is/" @@ -21,14 +22,14 @@ def insert_paynym_html(nym): donate_html = f.read() if 'class="paynym"' in donate_html: - print("Found existing paynym HTML in donate.html.") + logging.info("Found existing paynym HTML in donate.html.") return payment_code = get_paynym(nym) avatar_url = paynym_site + "{}/avatar".format(payment_code) codeimage_url = paynym_site + "{}/codeimage".format(payment_code) - print("Fetching paynym images...") + logging.info("Fetching paynym images...") avatar_data = requests.get(avatar_url).content with open("static/avatar.png", "wb") as f: @@ -93,6 +94,6 @@ def insert_paynym_html(nym): with open(donate_file, "w") as f: f.write(modified_html) - print("Wrote donate.html with paynym tags") + logging.info("Wrote donate.html with paynym tags") return diff --git a/gateways/ssh_tunnel.py b/gateways/ssh_tunnel.py @@ -1,6 +1,7 @@ import subprocess import time import os +import logging import config from node import bitcoind @@ -18,14 +19,14 @@ def open_tunnel(host, port): "-L", "{}:localhost:{}".format(port, port), ] - print("Opening tunnel to {}.".format(" ".join(command))) + logging.info("Opening tunnel to {}.".format(" ".join(command))) tunnel_proc = subprocess.Popen(command) return tunnel_proc else: tunnel_proc = None except Exception as e: - print("FAILED TO OPEN TUNNEL. Exception: {}".format(e)) + logging.error("FAILED TO OPEN TUNNEL. Exception: {}".format(e)) tunnel_proc = None pass return @@ -44,12 +45,12 @@ def clightning_unix_domain_socket_ssh(rpc_store_dir=None): "lightning-rpc:{}".format(config.clightning_rpc_file), "{}".format(config.tunnel_host), ] - print("Opening tunnel to {}.".format(" ".join(command))) + logging.info("Opening tunnel to {}.".format(" ".join(command))) tunnel_proc = subprocess.Popen(command) return tunnel_proc except Exception as e: - print("FAILED TO OPEN UNIX DOMAIN SOCKET OVER SSH. Exception: {}".format(e)) + logging.error("FAILED TO OPEN UNIX DOMAIN SOCKET OVER SSH. Exception: {}".format(e)) tunnel_proc = None pass @@ -62,7 +63,7 @@ def clightning_unix_domain_socket_ssh(rpc_store_dir=None): def close_tunnel(): if tunnel_proc is not None: tunnel_proc.kill() - print("Tunnel closed.") + logging.info("Tunnel closed.") return diff --git a/gateways/tor.py b/gateways/tor.py @@ -10,15 +10,15 @@ time.sleep(3) if config.tor_proxy is None: config.tor_proxy = "127.0.0.1:9050" -print("Using tor proxies {}".format(config.tor_proxy)) +logging.info("Using tor proxies {}".format(config.tor_proxy)) session = requests.session() session.proxies = { "http": "socks5h://{}".format(config.tor_proxy), "https": "socks5h://{}".format(config.tor_proxy), } -print( +logging.info( "Checking tor circuit IP address... You should check this is different to your IP." ) r = session.get("http://httpbin.org/ip") -print(r.text) +logging.info(r.text) diff --git a/gateways/woo_webhook.py b/gateways/woo_webhook.py @@ -4,6 +4,7 @@ import json import codecs import time import requests +import logging def hook(satsale_secret, invoice, order_id): @@ -12,7 +13,7 @@ def hook(satsale_secret, invoice, order_id): # Calculate a secret that is required to send back to the # woocommerce gateway, proving we did not modify id nor amount. secret_seed = str(int(100 * float(invoice["fiat_value"]))).encode("utf-8") - print("Secret seed: {}".format(secret_seed)) + logging.info("Secret seed: {}".format(secret_seed)) secret = hmac.new(key, secret_seed, hashlib.sha256).hexdigest() diff --git a/node/bitcoind.py b/node/bitcoind.py @@ -2,6 +2,7 @@ import time import uuid import qrcode import json +import logging import config from payments.price_feed import get_btc_value @@ -38,10 +39,10 @@ class btcd: config.rpcport, config.wallet, ) - print("Attempting to connect to Bitcoin node RPC with user {}.".format(config.username)) + logging.info("Attempting to connect to Bitcoin node RPC with user {}.".format(config.username)) else: self.tor = True - print( + logging.info( "Attempting to contact bitcoind rpc tor hidden service: {}:{}".format( config.tor_bitcoinrpc_host, config.rpcport ) @@ -56,14 +57,14 @@ class btcd: else: info = call_tor_bitcoin_rpc("getblockchaininfo", None) - print(info) - print("Successfully contacted bitcoind.") + logging.info(info) + logging.info("Successfully contacted bitcoind.") break except Exception as e: - print(e) + logging.error(e) time.sleep(config.pollrate) - print( + logging.info( "Attempting again... {}/{}...".format( i + 1, config.connection_attempts ) @@ -109,13 +110,13 @@ class btcd: return address, None except Exception as e: - print(e) - print( + logging.error(e) + logging.info( "Attempting again... {}/{}...".format( i + 1, config.connection_attempts ) ) if config.connection_attempts - i == 1: - print("Reconnecting...") + logging.info("Reconnecting...") self.__init__() return None diff --git a/node/clightning.py b/node/clightning.py @@ -5,6 +5,7 @@ import os import json import uuid import qrcode +import logging from payments.price_feed import get_btc_value @@ -23,20 +24,20 @@ class clightning: for i in range(config.connection_attempts): try: - print("Attempting to connect to clightning...") + logging.info("Attempting to connect to clightning...") self.clightning = LightningRpc(config.clightning_rpc_file) - print("Getting clightning info...") + logging.info("Getting clightning info...") info = self.clightning.getinfo() - print(info) + logging.info(info) - print("Successfully clightning lnd.") + logging.info("Successfully clightning lnd.") break except Exception as e: - print(e) + logging.error(e) time.sleep(config.pollrate) - print( + logging.info( "Attempting again... {}/{}...".format( i + 1, config.connection_attempts ) @@ -46,7 +47,7 @@ class clightning: "Could not connect to clightning. Check your port tunneling settings and try again." ) - print("Ready for payments requests.") + logging.info("Ready for payments requests.") return def create_qr(self, uuid, address, value): @@ -71,7 +72,7 @@ class clightning: invoices = self.clightning.listinvoices(uuid)['invoices'] if len(invoices) == 0: - print("Could not find invoice on node. Something's wrong.") + logging.error("Could not find invoice on node. Something's wrong.") return 0, 0 invoice = invoices[0] diff --git a/node/lnd.py b/node/lnd.py @@ -7,6 +7,7 @@ from base64 import b64decode from google.protobuf.json_format import MessageToJson import uuid import qrcode +import logging from payments.price_feed import get_btc_value @@ -22,7 +23,7 @@ class lnd: # Conect to lightning node connection_str = "{}:{}".format(config.host, config.lnd_rpcport) - print( + logging.info( "Attempting to connect to lightning node {}. This may take a few seconds...".format( connection_str ) @@ -30,7 +31,7 @@ class lnd: for i in range(config.connection_attempts): try: - print("Attempting to initialise lnd rpc client...") + logging.info("Attempting to initialise lnd rpc client...") time.sleep(3) self.lnd = LNDClient( "{}:{}".format(config.host, config.lnd_rpcport), @@ -39,21 +40,21 @@ class lnd: ) if "invoice" in self.certs["macaroon"]: - print("Testing we can fetch invoices...") + logging.info("Testing we can fetch invoices...") inv, _ = self.create_lnd_invoice(1) - print(inv) + logging.info(inv) else: - print("Getting lnd info...") + logging.info("Getting lnd info...") info = self.lnd.get_info() - print(info) + logging.info(info) - print("Successfully contacted lnd.") + logging.info("Successfully contacted lnd.") break except Exception as e: - print(e) + logging.error(e) time.sleep(config.pollrate) - print( + logging.info( "Attempting again... {}/{}...".format( i + 1, config.connection_attempts ) @@ -63,7 +64,7 @@ class lnd: "Could not connect to lnd. Check your gRPC / port tunneling settings and try again." ) - print("Ready for payments requests.") + logging.info("Ready for payments requests.") return def create_qr(self, uuid, address, value): @@ -85,7 +86,7 @@ class lnd: # SSH copy if config.tunnel_host is not None: - print( + logging.warning( "Could not find tls.cert or {} in SatSale folder. \ Attempting to download from remote lnd directory.".format(config.lnd_macaroon) ) @@ -109,10 +110,10 @@ class lnd: } except Exception as e: - print(e) - print("Failed to copy tls and macaroon files to local machine.") + logging.error(e) + logging.error("Failed to copy tls and macaroon files to local machine.") else: - print("Found tls.cert and admin.macaroon.") + logging.info("Found tls.cert and admin.macaroon.") return # Create lightning invoice @@ -132,7 +133,7 @@ class lnd: ret = json.loads( MessageToJson(self.lnd.send_payment(invoice, fee_limit_msat=20*1000)) ) - print(ret) + logging.info(ret) return diff --git a/payments/database.py b/payments/database.py @@ -3,7 +3,7 @@ import sqlite3 def create_database(name="database.db"): with sqlite3.connect("database.db") as conn: - print("Creating new database.db...") + logging.info("Creating new database.db...") conn.execute( "CREATE TABLE payments (uuid TEXT, fiat_value DECIMAL, btc_value DECIMAL, method TEXT, address TEXT, time DECIMAL, webhook TEXT, rhash TEXT)" ) diff --git a/payments/price_feed.py b/payments/price_feed.py @@ -1,4 +1,5 @@ import requests +import logging import config @@ -31,8 +32,8 @@ def get_price(currency, currency_provider=config.currency_provider): break except Exception as e: - print(e) - print( + logging.error(e) + logging.info( "Attempting again... {}/{}...".format(i + 1, config.connection_attempts) ) @@ -44,7 +45,7 @@ def get_price(currency, currency_provider=config.currency_provider): return price except: - print("Failed to find currency {} from {}.".format(currency, price_feed)) + logging.error("Failed to find currency {} from {}.".format(currency, price_feed)) return None @@ -57,7 +58,7 @@ def get_btc_value(base_amount, currency): if not isinstance(float_value, float): raise Exception("Fiat value should be a float.") except Exception as e: - print(e) + logging.error(e) return float_value diff --git a/payments/weakhands.py b/payments/weakhands.py @@ -1,4 +1,5 @@ import requests +import logging import config @@ -15,12 +16,12 @@ def get_quote(amount_lnbtc): "affiliateId": affiliate, "depositAmount": str(amount_lnbtc) } - print("Getting quote to swap {:.8f} LN-BTC to USDT".format(amount_lnbtc)) + logging.info("Getting quote to swap {:.8f} LN-BTC to USDT".format(amount_lnbtc)) resp = requests.post(quote_url, json=quote_data) if resp.status_code != 201: - print("Failed quote request:") - print(resp.json()) + logging.error("Failed quote request:") + logging.error(resp.json()) return False return resp.json() @@ -34,20 +35,20 @@ def get_swap(quote, amount_lnbtc, liquid_address): "settleAddress": liquid_address, "affiliateId": affiliate, } - print("Creating order to swap {:.8f} LN-BTC to USDT (liquid: {})".format(amount_lnbtc, liquid_address)) + logging.info("Creating order to swap {:.8f} LN-BTC to USDT (liquid: {})".format(amount_lnbtc, liquid_address)) resp = requests.post(swap_url, json=data) if resp.status_code != 201: - print("Failed to create order:") - print(resp.json()) + logging.error("Failed to create order:") + logging.error(resp.json()) return False return resp.json() def pay_swap(node, swap): payment_req = swap['depositAddress']['paymentRequest'] - print("Paying invoice: {}".format(payment_req)) + logging.info("Paying invoice: {}".format(payment_req)) node.pay_invoice(payment_req) return True @@ -55,18 +56,18 @@ def swap_lnbtc_for_lusdt(node, amount_lnbtc, liquid_address): try: quote = get_quote(amount_lnbtc) if not quote: - print("Quote failed, not swapping this order.") + logging.error("Quote failed, not swapping this order.") return False swap = get_swap(quote, amount_lnbtc, liquid_address) if not swap: - print("Creating order failed, not swapping this payment.") + logging.error("Creating order failed, not swapping this payment.") return False pay_swap(node, swap) - print("Paid invoice! Swapped ") + logging.info("Paid invoice! Swapped ") except Exception as e: - print("Error encountered during swap: {}".format(e)) + logging.error("Error encountered during swap: {}".format(e)) return diff --git a/satsale.py b/satsale.py @@ -14,6 +14,7 @@ import uuid import sqlite3 from pprint import pprint import json +import logging from gateways import ssh_tunnel from gateways import paynym @@ -26,6 +27,10 @@ from node import clightning from gateways import woo_webhook +logging.basicConfig(format='[%(asctime)s] [%(levelname)s] %(message)s', + datefmt='%Y-%m-%d %H:%M:%S %z', + level=getattr(logging, config.loglevel)) + app = Flask(__name__) # Load a SatSale API key or create a new one @@ -37,7 +42,7 @@ else: app.config["SECRET_KEY"] = os.urandom(64).hex() f.write(app.config["SECRET_KEY"]) -print("Initialised Flask with secret key: {}".format(app.config["SECRET_KEY"])) +logging.info("Initialised Flask with secret key: {}".format(app.config["SECRET_KEY"])) # Create payment database if it does not exist if not os.path.exists("database.db"): @@ -127,13 +132,13 @@ class create_payment(Resource): if webhook is None: webhook = None else: - print("Webhook payment: {}".format(webhook)) + logging.info("Webhook payment: {}".format(webhook)) # Create the payment using one of the connected nodes as a base # ready to recieve the invoice. node = get_node(payment_method) if node is None: - print("Invalid payment method {}".format(payment_method)) + logging.warning("Invalid payment method {}".format(payment_method)) return {"message": "Invalid payment method."}, 400 invoice = { @@ -155,7 +160,7 @@ class create_payment(Resource): database.write_to_database(invoice) invoice["time_left"] = config.payment_timeout - (time.time() - invoice["time"]) - print("Created invoice:") + logging.info("Created invoice:") pprint(invoice) print() @@ -222,17 +227,17 @@ class complete_payment(Resource): # Call webhook to confirm payment with merchant if (invoice["webhook"] != None) and (invoice["webhook"] != ""): - print("Calling webhook {}".format(invoice["webhook"])) + logging.info("Calling webhook {}".format(invoice["webhook"])) response = woo_webhook.hook(app.config["SECRET_KEY"], invoice, order_id) if response.status_code != 200: err = "Failed to confirm order payment via webhook {}, please contact the store to ensure the order has been confirmed, error response is: {}".format( response.status_code, response.text ) - print(err) + logging.error(err) return {"message": err}, 500 - print("Successfully confirmed payment via webhook.") + logging.info("Successfully confirmed payment via webhook.") return {"message": "Payment confirmed with store."}, 200 return {"message": "Payment confirmed."}, 200 @@ -283,7 +288,7 @@ def check_payment_status(uuid): } ) - print("Invoice {} status: {}".format(uuid, status)) + logging.debug("Invoice {} status: {}".format(uuid, status)) return status @@ -306,15 +311,15 @@ api.add_resource(complete_payment, "/api/completepayment") # Test connections on startup: -print("Connecting to node...") +logging.info("Connecting to node...") bitcoin_node = bitcoind.btcd() -print("Connection to bitcoin node successful.") +logging.info("Connection to bitcoin node successful.") if config.pay_method == "lnd": lightning_node = lnd.lnd() - print("Connection to lightning node (lnd) successful.") + logging.info("Connection to lightning node (lnd) successful.") elif config.pay_method == "clightning": lightning_node = clightning.clightning() - print("Connection to lightning node (clightning) successful.") + logging.info("Connection to lightning node (clightning) successful.") if config.lightning_address is not None: