Add squeak core module (#587)

* Add squeak_core module

* Remove unused import

* Delete squeakblockverifier

* Use squeak_core to make squeak

* Remove blockchain client from controller

* Move create offer method to squeak core

* Add some type hints

* Move get buy offer method to squeak_core

* Delete commented old get buy offer block

* Add pay_offer to squeak_core

* Add get received payments generator function to squak core

* Add get_offer method to squeak_core

* Remove lightning_client property from peer_task

* Remove old comments from payment verifier class

* Use bytes internally for payment_hash

* Represent secret_key as bytes internally
This commit is contained in:
Jonathan Zernik 2021-01-10 09:31:41 -08:00 committed by GitHub
parent ae61726053
commit 138ef7d984
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 443 additions and 279 deletions

View file

@ -81,6 +81,17 @@ services:
- ../createdb.sql:/docker-entrypoint-initdb.d/init.sql
- squeaknode_pgdata:/var/lib/postgresql
squeaknode_other_db:
image: postgres
container_name: test_squeaknode_other_db
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
volumes:
- ../createdb.sql:/docker-entrypoint-initdb.d/init.sql
- squeaknode_other_pgdata:/var/lib/postgresql
squeaknode:
image: squeaknode
container_name: test_squeaknode
@ -110,6 +121,8 @@ services:
build:
context: ../
dockerfile: Dockerfile
environment:
- SQUEAKNODE_DB_CONNECTION_STRING=postgresql://postgres:postgres@squeaknode_other_db/squeaknode
volumes:
- test_shared:/rpc
- test_lnd_client_dir:/root/.lnd
@ -117,6 +130,7 @@ services:
links:
- "btcd:btcd"
- "lnd_client:lnd"
- "squeaknode_other_db:squeaknode_other_db"
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
entrypoint: ["./start-squeaknode.sh"]
@ -155,3 +169,5 @@ volumes:
# postgres db data
squeaknode_pgdata:
driver: local
squeaknode_other_pgdata:
driver: local

View file

@ -93,7 +93,7 @@ def sent_payment_with_peer_to_message(sent_payment_with_peer):
peer_name=peer.peer_name,
squeak_hash=sent_payment.squeak_hash.hex(),
payment_hash=sent_payment.payment_hash.hex(),
secret_key=sent_payment.secret_key,
secret_key=sent_payment.secret_key.hex(),
price_msat=sent_payment.price_msat,
node_pubkey=sent_payment.node_pubkey,
time_ms=int(sent_payment.created.timestamp()) * 1000,
@ -136,8 +136,8 @@ def sent_offer_to_message(sent_offer):
return squeak_admin_pb2.SentOffer(
sent_offer_id=sent_offer.sent_offer_id,
squeak_hash=sent_offer.squeak_hash.hex(),
payment_hash=sent_offer.payment_hash,
secret_key=sent_offer.secret_key,
payment_hash=sent_offer.payment_hash.hex(),
secret_key=sent_offer.secret_key.hex(),
nonce=sent_offer.nonce.hex(),
price_msat=sent_offer.price_msat,
)
@ -149,7 +149,7 @@ def received_payments_to_message(received_payment):
return squeak_admin_pb2.ReceivedPayment(
received_payment_id=received_payment.received_payment_id,
squeak_hash=received_payment.squeak_hash.hex(),
payment_hash=received_payment.payment_hash,
payment_hash=received_payment.payment_hash.hex(),
price_msat=received_payment.price_msat,
payment_time_ms=int(received_payment.created.timestamp()) * 1000,
client_addr=received_payment.client_addr,

View file

@ -6,9 +6,9 @@ class Offer(NamedTuple):
"""Class for saving an offer from a remote peer."""
offer_id: Optional[int]
squeak_hash: bytes
price_msat: bytes
price_msat: int
payment_hash: bytes
nonce: str
nonce: bytes
payment_point: bytes
invoice_timestamp: int
invoice_expiry: int

View file

@ -12,4 +12,4 @@ class SentPayment(NamedTuple):
payment_hash: bytes
secret_key: bytes
price_msat: int
node_pubkey: bytes
node_pubkey: str

View file

@ -6,21 +6,15 @@ from squeak.core import CSqueak
from squeak.core.signing import CSigningKey
from squeak.core.signing import CSqueakAddress
from squeaknode.core.buy_offer import BuyOffer
from proto import squeak_server_pb2
from squeaknode.core.offer import Offer
from squeaknode.core.sent_offer import SentOffer
from squeaknode.core.sent_payment import SentPayment
from squeaknode.core.squeak_address_validator import SqueakAddressValidator
from squeaknode.core.squeak_peer import SqueakPeer
from squeaknode.core.squeak_profile import SqueakProfile
from squeaknode.core.util import add_tweak
from squeaknode.core.util import generate_tweak
from squeaknode.core.util import subtract_tweak
from squeaknode.node.received_payments_subscription_client import (
OpenReceivedPaymentsSubscriptionClient,
)
from squeaknode.node.sent_offers_verifier import SentOffersVerifier
from squeaknode.node.squeak_maker import SqueakMaker
logger = logging.getLogger(__name__)
@ -29,20 +23,18 @@ class SqueakController:
def __init__(
self,
squeak_db,
blockchain_client,
lightning_client,
squeak_core,
squeak_store,
squeak_whitelist,
config,
):
self.squeak_db = squeak_db
self.blockchain_client = blockchain_client
self.lightning_client = lightning_client
self.squeak_core = squeak_core
self.squeak_store = squeak_store
self.squeak_whitelist = squeak_whitelist
self.sent_offers_verifier = SentOffersVerifier(
self.squeak_db,
self.lightning_client,
self.squeak_core,
)
self.config = config
@ -64,59 +56,10 @@ class SqueakController:
def get_buy_offer(self, squeak_hash: bytes, client_addr: str):
# Check if there is an existing offer for the hash/client_addr combination
sent_offer = self.get_saved_sent_offer(squeak_hash, client_addr)
# Get the lightning network node pubkey
get_info_response = self.lightning_client.get_info()
pubkey = get_info_response.identity_pubkey
# Return the buy offer
return BuyOffer(
squeak_hash=squeak_hash,
price_msat=self.config.core.price_msat,
nonce=sent_offer.nonce,
payment_request=sent_offer.payment_request,
pubkey=pubkey,
host=self.config.lnd.external_host,
port=self.config.lnd.port,
)
def create_offer(self, squeak_hash: bytes, client_addr: str):
# Generate a new random nonce
nonce = generate_tweak()
# Get the squeak secret key
squeak = self.squeak_store.get_squeak(squeak_hash)
secret_key = squeak.GetDecryptionKey()
# Calculate the preimage
# preimage = bxor(nonce, secret_key)
preimage = add_tweak(secret_key, nonce)
logger.info(
"Create offer with secret key: {} nonce: {} preimage: {}".format(
secret_key, nonce, preimage
)
)
# Create the lightning invoice
add_invoice_response = self.lightning_client.add_invoice(
preimage, self.config.core.price_msat
)
logger.info("add_invoice_response: {}".format(add_invoice_response))
payment_hash = add_invoice_response.r_hash
invoice_payment_request = add_invoice_response.payment_request
# invoice_expiry = add_invoice_response.expiry
lookup_invoice_response = self.lightning_client.lookup_invoice(
payment_hash.hex()
)
invoice_time = lookup_invoice_response.creation_date
invoice_expiry = lookup_invoice_response.expiry
# Save the incoming potential payment in the databse.
return SentOffer(
sent_offer_id=None,
squeak_hash=squeak_hash,
payment_hash=payment_hash.hex(),
secret_key=preimage.hex(),
nonce=nonce,
price_msat=self.config.core.price_msat,
payment_request=invoice_payment_request,
invoice_time=invoice_time,
invoice_expiry=invoice_expiry,
client_addr=client_addr,
return self.squeak_core.create_buy_offer(
sent_offer,
self.config.lnd.external_host,
self.config.lnd.port,
)
def get_saved_sent_offer(self, squeak_hash: bytes, client_addr: str):
@ -127,7 +70,14 @@ class SqueakController:
)
if sent_offer:
return sent_offer
sent_offer = self.create_offer(squeak_hash, client_addr)
squeak = self.squeak_store.get_squeak(squeak_hash)
# sent_offer = self.create_offer(
# squeak, client_addr, self.config.core.price_msat)
sent_offer = self.squeak_core.create_offer(
squeak,
client_addr,
self.config.core.price_msat,
)
self.squeak_db.insert_sent_offer(sent_offer)
return sent_offer
@ -189,10 +139,9 @@ class SqueakController:
def make_squeak(self, profile_id: int, content_str: str, replyto_hash: bytes):
squeak_profile = self.squeak_db.get_profile(profile_id)
squeak_maker = SqueakMaker(self.blockchain_client)
squeak = squeak_maker.make_squeak(
squeak_entry = self.squeak_core.make_squeak(
squeak_profile, content_str, replyto_hash)
return self.save_created_squeak(squeak)
return self.save_created_squeak(squeak_entry.squeak)
def get_squeak_entry_with_profile(self, squeak_hash: bytes):
return self.squeak_store.get_squeak_entry_with_profile(squeak_hash)
@ -258,42 +207,10 @@ class SqueakController:
offer_with_peer = self.squeak_db.get_offer_with_peer(offer_id)
offer = offer_with_peer.offer
logger.info("Paying offer: {}".format(offer))
# Pay the invoice
payment = self.lightning_client.pay_invoice_sync(offer.payment_request)
preimage = payment.payment_preimage
if not preimage:
raise Exception(
"Payment failed with error: {}".format(payment.payment_error)
)
# Calculate the secret key
nonce = offer.nonce
# secret_key = bxor(nonce, preimage)
secret_key = subtract_tweak(preimage, nonce)
logger.info(
"Pay offer with secret key: {} nonce: {} preimage: {}".format(
secret_key, nonce, preimage
)
)
# Save the preimage of the sent payment
sent_payment = SentPayment(
sent_payment_id=None,
created=None,
offer_id=offer_id,
peer_id=offer.peer_id,
squeak_hash=offer.squeak_hash,
payment_hash=offer.payment_hash,
secret_key=secret_key.hex(),
price_msat=offer.price_msat,
node_pubkey=offer.destination,
)
sent_payment = self.squeak_core.pay_offer(offer)
sent_payment_id = self.squeak_db.insert_sent_payment(sent_payment)
secret_key = sent_payment.secret_key
self.unlock_squeak(offer, secret_key)
return sent_payment_id
def unlock_squeak(self, offer: Offer, secret_key: bytes):
@ -350,10 +267,10 @@ class SqueakController:
yield payment
def get_best_block_height(self):
block_info = self.blockchain_client.get_best_block_info()
return block_info.block_height
return self.squeak_core.get_best_block_height()
def get_network(self):
print(self.config)
print(self.config.core)
return self.config.core.network
def get_offer(self, squeak: CSqueak, offer_msg: squeak_server_pb2.SqueakBuyOffer, peer: SqueakPeer) -> Offer:
return self.squeak_core.get_offer(squeak, offer_msg, peer)

View file

@ -0,0 +1,251 @@
import logging
import time
from typing import Iterator
from typing import Optional
from squeak.core import CSqueak
from squeak.core import MakeSqueakFromStr
from squeak.core.signing import CSigningKey
from proto import squeak_server_pb2
from squeaknode.core.buy_offer import BuyOffer
from squeaknode.core.offer import Offer
from squeaknode.core.received_payment import ReceivedPayment
from squeaknode.core.sent_offer import SentOffer
from squeaknode.core.sent_payment import SentPayment
from squeaknode.core.squeak_entry import SqueakEntry
from squeaknode.core.squeak_peer import SqueakPeer
from squeaknode.core.squeak_profile import SqueakProfile
from squeaknode.core.util import add_tweak
from squeaknode.core.util import generate_tweak
from squeaknode.core.util import get_hash
from squeaknode.core.util import subtract_tweak
from squeaknode.node.received_payments_subscription_client import (
OpenReceivedPaymentsSubscriptionClient,
)
logger = logging.getLogger(__name__)
class SqueakCore:
def __init__(
self,
blockchain_client,
lightning_client,
):
self.blockchain_client = blockchain_client
self.lightning_client = lightning_client
def make_squeak(self, signing_profile: SqueakProfile, content_str: str, replyto_hash: Optional[bytes] = None) -> SqueakEntry:
if signing_profile.private_key is None:
raise Exception("Can't make squeak with a contact profile.")
signing_key_str = signing_profile.private_key.decode()
signing_key = CSigningKey(signing_key_str)
block_info = self.blockchain_client.get_best_block_info()
block_height = block_info.block_height
block_hash = block_info.block_hash
timestamp = int(time.time())
if replyto_hash is None or len(replyto_hash) == 0:
squeak = MakeSqueakFromStr(
signing_key,
content_str,
block_height,
block_hash,
timestamp,
)
else:
squeak = MakeSqueakFromStr(
signing_key,
content_str,
block_height,
block_hash,
timestamp,
replyto_hash,
)
return SqueakEntry(
squeak=squeak,
block_header=block_info.block_header,
)
def validate_squeak(self, squeak: CSqueak) -> SqueakEntry:
block_info = self.blockchain_client.get_block_info_by_height(
squeak.nBlockHeight)
if squeak.hashBlock != block_info.block_hash:
raise Exception("Block hash incorrect.")
return SqueakEntry(
squeak=squeak,
block_header=block_info.block_header,
)
def get_best_block_height(self) -> int:
block_info = self.blockchain_client.get_best_block_info()
return block_info.block_height
def create_offer(self, squeak: CSqueak, client_addr: str, price_msat: int) -> SentOffer:
# Get the squeak hash
squeak_hash = get_hash(squeak)
# Generate a new random nonce
nonce = generate_tweak()
# Get the squeak secret key
secret_key = squeak.GetDecryptionKey()
# Calculate the preimage
# preimage = bxor(nonce, secret_key)
preimage = add_tweak(secret_key, nonce)
# Create the lightning invoice
add_invoice_response = self.lightning_client.add_invoice(
preimage, price_msat
)
payment_hash = add_invoice_response.r_hash
invoice_payment_request = add_invoice_response.payment_request
# invoice_expiry = add_invoice_response.expiry
lookup_invoice_response = self.lightning_client.lookup_invoice(
payment_hash.hex()
)
invoice_time = lookup_invoice_response.creation_date
invoice_expiry = lookup_invoice_response.expiry
# Save the incoming potential payment in the databse.
return SentOffer(
sent_offer_id=None,
squeak_hash=squeak_hash,
payment_hash=payment_hash,
secret_key=preimage,
nonce=nonce,
price_msat=price_msat,
payment_request=invoice_payment_request,
invoice_time=invoice_time,
invoice_expiry=invoice_expiry,
client_addr=client_addr,
)
def create_buy_offer(self, sent_offer: SentOffer, lnd_external_host: str, lnd_port: int) -> BuyOffer:
# Get the lightning network node pubkey
get_info_response = self.lightning_client.get_info()
pubkey = get_info_response.identity_pubkey
# Return the buy offer
return BuyOffer(
squeak_hash=sent_offer.squeak_hash,
price_msat=sent_offer.price_msat,
nonce=sent_offer.nonce,
payment_request=sent_offer.payment_request,
pubkey=pubkey,
host=lnd_external_host,
port=lnd_port,
)
def pay_offer(self, offer: Offer) -> SentPayment:
if offer.offer_id is None:
raise Exception("Offer must have a non-null offer_id.")
# Pay the invoice
payment = self.lightning_client.pay_invoice_sync(offer.payment_request)
preimage = payment.payment_preimage
if not preimage:
raise Exception(
"Payment failed with error: {}".format(payment.payment_error)
)
# Calculate the secret key
nonce = offer.nonce
# secret_key = bxor(nonce, preimage)
secret_key = subtract_tweak(preimage, nonce)
# Save the preimage of the sent payment
return SentPayment(
sent_payment_id=None,
created=None,
offer_id=offer.offer_id,
peer_id=offer.peer_id,
squeak_hash=offer.squeak_hash,
payment_hash=offer.payment_hash,
secret_key=secret_key,
price_msat=offer.price_msat,
node_pubkey=offer.destination,
)
def get_received_payments(self, get_sent_offer_fn, latest_settle_index, retry_s) -> Iterator[ReceivedPayment]:
try:
for invoice in self.lightning_client.subscribe_invoices(
settle_index=latest_settle_index,
):
if invoice.settled:
payment_hash = invoice.r_hash
settle_index = invoice.settle_index
# sent_offer = self.squeak_db.get_sent_offer_by_payment_hash(
# payment_hash)
sent_offer = get_sent_offer_fn(payment_hash)
received_payment = ReceivedPayment(
received_payment_id=None,
created=None,
squeak_hash=sent_offer.squeak_hash,
payment_hash=sent_offer.payment_hash,
price_msat=sent_offer.price_msat,
settle_index=settle_index,
client_addr=sent_offer.client_addr,
)
# self.squeak_db.insert_received_payment(received_payment)
yield received_payment
except Exception:
logger.info(
"Unable to subscribe invoices from lnd. Retrying in "
"{} seconds.".format(retry_s),
)
time.sleep(retry_s)
def get_offer(self, squeak: CSqueak, offer_msg: squeak_server_pb2.SqueakBuyOffer, peer: SqueakPeer) -> Offer:
if peer.peer_id is None:
raise Exception("Peer must have a non-null peer_id.")
# Get the squeak hash
squeak_hash = get_hash(squeak)
# Decode the payment request
pay_req = self.lightning_client.decode_pay_req(
offer_msg.payment_request)
logger.info("Decoded payment request: {}".format(pay_req))
squeak_payment_point = squeak.paymentPoint
payment_hash = bytes.fromhex(pay_req.payment_hash)
price_msat = pay_req.num_msat
destination = pay_req.destination
invoice_timestamp = pay_req.timestamp
invoice_expiry = pay_req.expiry
node_host = offer_msg.host or peer.host
node_port = offer_msg.port
logger.info("price_msat: {}".format(price_msat))
logger.info("destination: {}".format(destination))
logger.info("invoice_timestamp: {}".format(invoice_timestamp))
logger.info("invoice_expiry: {}".format(invoice_expiry))
logger.info("node_host: {}".format(node_host))
logger.info("node_port: {}".format(node_port))
decoded_offer = Offer(
offer_id=None,
squeak_hash=squeak_hash,
price_msat=price_msat,
payment_hash=payment_hash,
nonce=offer_msg.nonce,
payment_point=squeak_payment_point,
invoice_timestamp=invoice_timestamp,
invoice_expiry=invoice_expiry,
payment_request=offer_msg.payment_request,
destination=destination,
node_host=node_host,
node_port=node_port,
peer_id=peer.peer_id,
)
# TODO: Check the payment point
# payment_point = offer.payment_point
# logger.info("Payment point: {}".format(payment_point.hex()))
# expected_payment_point = squeak.paymentPoint
# logger.info("Expected payment point: {}".format(expected_payment_point.hex()))
# if payment_point != expected_payment_point:
# raise Exception(
# "Invalid offer payment point: {}, expected: {}".format(
# payment_point.hex(),
# expected_payment_point.hex(),
# )
# )
return decoded_offer

View file

@ -732,9 +732,9 @@ class SqueakDb:
""" Insert a new offer. """
ins = self.offers.insert().values(
squeak_hash=offer.squeak_hash.hex(),
payment_hash=offer.payment_hash,
payment_hash=offer.payment_hash.hex(),
nonce=offer.nonce.hex(),
payment_point=offer.payment_point,
payment_point=offer.payment_point.hex(),
invoice_timestamp=offer.invoice_timestamp,
invoice_expiry=offer.invoice_expiry,
price_msat=offer.price_msat,
@ -881,8 +881,8 @@ class SqueakDb:
offer_id=sent_payment.offer_id,
peer_id=sent_payment.peer_id,
squeak_hash=sent_payment.squeak_hash.hex(),
payment_hash=sent_payment.payment_hash,
secret_key=sent_payment.secret_key,
payment_hash=sent_payment.payment_hash.hex(),
secret_key=sent_payment.secret_key.hex(),
price_msat=sent_payment.price_msat,
node_pubkey=sent_payment.node_pubkey,
)
@ -933,8 +933,8 @@ class SqueakDb:
""" Insert a new sent offer. """
ins = self.sent_offers.insert().values(
squeak_hash=sent_offer.squeak_hash.hex(),
payment_hash=sent_offer.payment_hash,
secret_key=sent_offer.secret_key,
payment_hash=sent_offer.payment_hash.hex(),
secret_key=sent_offer.secret_key.hex(),
nonce=sent_offer.nonce.hex(),
price_msat=sent_offer.price_msat,
payment_request=sent_offer.payment_request,
@ -958,10 +958,10 @@ class SqueakDb:
sent_offers = [self._parse_sent_offer(row) for row in rows]
return sent_offers
def get_sent_offer_by_payment_hash(self, payment_hash):
def get_sent_offer_by_payment_hash(self, payment_hash: bytes):
""" Get a sent offer by preimage hash. """
s = select([self.sent_offers]).where(
self.sent_offers.c.payment_hash == payment_hash
self.sent_offers.c.payment_hash == payment_hash.hex()
)
with self.get_connection() as connection:
result = connection.execute(s)
@ -1009,7 +1009,7 @@ class SqueakDb:
""" Insert a new received payment. """
ins = self.received_payments.insert().values(
squeak_hash=received_payment.squeak_hash.hex(),
payment_hash=received_payment.payment_hash,
payment_hash=received_payment.payment_hash.hex(),
price_msat=received_payment.price_msat,
settle_index=received_payment.settle_index,
client_addr=received_payment.client_addr,
@ -1108,9 +1108,9 @@ class SqueakDb:
return Offer(
offer_id=row["offer_id"],
squeak_hash=bytes.fromhex(row["squeak_hash"]),
payment_hash=row["payment_hash"],
payment_hash=bytes.fromhex(row["payment_hash"]),
nonce=bytes.fromhex(row["nonce"]),
payment_point=row["payment_point"],
payment_point=bytes.fromhex(row["payment_point"]),
invoice_timestamp=row["invoice_timestamp"],
invoice_expiry=row["invoice_expiry"],
price_msat=row["price_msat"],
@ -1140,8 +1140,8 @@ class SqueakDb:
offer_id=row["offer_id"],
peer_id=row["peer_id"],
squeak_hash=bytes.fromhex(row["squeak_hash"]),
payment_hash=row["payment_hash"],
secret_key=row["secret_key"],
payment_hash=bytes.fromhex(row["payment_hash"]),
secret_key=bytes.fromhex(row["secret_key"]),
price_msat=row["price_msat"],
node_pubkey=row["node_pubkey"],
)
@ -1162,8 +1162,8 @@ class SqueakDb:
return SentOffer(
sent_offer_id=row["sent_offer_id"],
squeak_hash=bytes.fromhex(row["squeak_hash"]),
payment_hash=row["payment_hash"],
secret_key=row["secret_key"],
payment_hash=bytes.fromhex(row["payment_hash"]),
secret_key=bytes.fromhex(row["secret_key"]),
nonce=bytes.fromhex(row["nonce"]),
price_msat=row["price_msat"],
payment_request=row["payment_request"],
@ -1179,7 +1179,7 @@ class SqueakDb:
received_payment_id=row["received_payment_id"],
created=row["created"],
squeak_hash=bytes.fromhex(row["squeak_hash"]),
payment_hash=row["payment_hash"],
payment_hash=bytes.fromhex(row["payment_hash"]),
price_msat=row["price_msat"],
settle_index=row["settle_index"],
client_addr=row["client_addr"],

View file

@ -12,6 +12,7 @@ from squeaknode.admin.webapp.app import SqueakAdminWebServer
from squeaknode.bitcoin.bitcoin_blockchain_client import BitcoinBlockchainClient
from squeaknode.config.config import SqueaknodeConfig
from squeaknode.core.squeak_controller import SqueakController
from squeaknode.core.squeak_core import SqueakCore
from squeaknode.db.db_engine import get_engine
from squeaknode.db.db_engine import get_sqlite_connection_string
from squeaknode.db.squeak_db import SqueakDb
@ -19,7 +20,6 @@ from squeaknode.lightning.lnd_lightning_client import LNDLightningClient
from squeaknode.node.received_payments_subscription_client import (
OpenReceivedPaymentsSubscriptionClient,
)
from squeaknode.node.squeak_block_verifier import SqueakBlockVerifier
from squeaknode.node.squeak_memory_whitelist import SqueakMemoryWhitelist
from squeaknode.node.squeak_node import SqueakNode
from squeaknode.node.squeak_rate_limiter import SqueakRateLimiter
@ -233,7 +233,10 @@ def run_node(config):
# load the blockchain client
blockchain_client = load_blockchain_client(config)
squeak_block_verifier = SqueakBlockVerifier(blockchain_client)
squeak_core = SqueakCore(
blockchain_client,
lightning_client,
)
squeak_rate_limiter = SqueakRateLimiter(
squeak_db,
blockchain_client,
@ -245,15 +248,14 @@ def run_node(config):
)
squeak_store = SqueakStore(
squeak_db,
squeak_block_verifier,
squeak_core,
squeak_rate_limiter,
squeak_whitelist,
)
squeak_controller = SqueakController(
squeak_db,
blockchain_client,
lightning_client,
squeak_core,
squeak_store,
squeak_whitelist,
config,

View file

@ -1,7 +1,7 @@
import logging
import time
from squeaknode.core.received_payment import ReceivedPayment
from squeaknode.core.sent_offer import SentOffer
logger = logging.getLogger(__name__)
@ -9,16 +9,9 @@ LND_CONNECT_RETRY_S = 10
class SentOffersVerifier:
def __init__(self, squeak_db, lightning_client):
def __init__(self, squeak_db, squeak_core):
self.squeak_db = squeak_db
self.lightning_client = lightning_client
def verify_sent_offer(self, invoice):
logger.info("Verifying invoice: {}".format(invoice))
if invoice.settled:
payment_hash = invoice.r_hash.hex()
settle_index = invoice.settle_index
self._record_payment(payment_hash, settle_index)
self.squeak_core = squeak_core
def process_subscribed_invoices(self):
while True:
@ -26,39 +19,19 @@ class SentOffersVerifier:
def try_processing(self):
latest_settle_index = self._get_latest_settle_index() or 0
logger.info("latest settle index: {}".format(latest_settle_index))
try:
for invoice in self.lightning_client.subscribe_invoices(
settle_index=latest_settle_index,
):
self.verify_sent_offer(invoice)
except Exception:
logger.info(
"Unable to subscribe invoices from lnd. Retrying in "
"{} seconds.".format(LND_CONNECT_RETRY_S),
def get_sent_offer_for_payment_hash(payment_hash: bytes) -> SentOffer:
return self.squeak_db.get_sent_offer_by_payment_hash(
payment_hash
)
time.sleep(LND_CONNECT_RETRY_S)
for received_payment in self.squeak_core.get_received_payments(
get_sent_offer_for_payment_hash,
latest_settle_index,
LND_CONNECT_RETRY_S,
):
self.squeak_db.insert_received_payment(received_payment)
def _get_latest_settle_index(self):
logger.info("Getting latest settle index from db...")
return self.squeak_db.get_latest_settle_index()
def _record_payment(self, payment_hash, settle_index):
logger.info(
"Saving received payment for payment_hash: {} with settle_index: {}".format(
payment_hash,
settle_index,
)
)
sent_offer = self.squeak_db.get_sent_offer_by_payment_hash(
payment_hash)
received_payment = ReceivedPayment(
received_payment_id=None,
created=None,
squeak_hash=sent_offer.squeak_hash,
payment_hash=sent_offer.payment_hash,
price_msat=sent_offer.price_msat,
settle_index=settle_index,
client_addr=sent_offer.client_addr,
)
self.squeak_db.insert_received_payment(received_payment)

View file

@ -1,23 +0,0 @@
import logging
logger = logging.getLogger(__name__)
class SqueakBlockVerifier:
def __init__(self, blockchain_client):
self.blockchain_client = blockchain_client
def _get_block_info_for_height(self, block_height):
return self.blockchain_client.get_block_info_by_height(block_height)
def get_block_header(self, squeak):
try:
block_info = self._get_block_info_for_height(squeak.nBlockHeight)
except Exception:
logger.error("Failed to get block info for squeak.",
exc_info=False)
return None
if squeak.hashBlock != block_info.block_hash:
logger.info("block hash incorrect: {}".format(block_info))
return None
return block_info.block_header

View file

@ -8,10 +8,10 @@ logger = logging.getLogger(__name__)
class SqueakStore:
def __init__(
self, squeak_db, squeak_block_verifier, squeak_rate_limiter, squeak_whitelist
self, squeak_db, squeak_core, squeak_rate_limiter, squeak_whitelist
):
self.squeak_db = squeak_db
self.squeak_block_verifier = squeak_block_verifier
self.squeak_core = squeak_core
self.squeak_rate_limiter = squeak_rate_limiter
self.squeak_whitelist = squeak_whitelist
@ -23,10 +23,9 @@ class SqueakStore:
if not self.squeak_rate_limiter.should_rate_limit_allow(squeak):
raise Exception(
"Excedeed allowed number of squeaks per block.")
block_header_bytes = self.squeak_block_verifier.get_block_header(
squeak)
block_info = self.squeak_core.validate_squeak(squeak)
inserted_squeak_hash = self.squeak_db.insert_squeak(
squeak, block_header_bytes)
squeak, block_info.block_header)
return inserted_squeak_hash
def get_squeak(self, squeak_hash: bytes, clear_decryption_key: bool = False):

View file

@ -1,6 +1,5 @@
import logging
from squeaknode.core.offer import Offer
from squeaknode.core.util import get_hash
logger = logging.getLogger(__name__)
@ -27,10 +26,6 @@ class PeerSyncTask:
def squeak_db(self):
return self.squeak_controller.squeak_db
@property
def lightning_client(self):
return self.squeak_controller.lightning_client
@property
def peer_client(self):
return self.peer_connection.peer_client
@ -167,56 +162,8 @@ class PeerSyncTask:
# Download the buy offer
offer_msg = self._download_offer_msg(squeak_hash)
# Decode the payment request
pay_req = self._decode_payment_request(offer_msg.payment_request)
logger.info("Decoded payment request: {}".format(pay_req))
# TODO: Use the real payment point, not a fake value.
squeak_payment_point = squeak.paymentPoint
# payment_point = b""
payment_hash = bytes.fromhex(pay_req.payment_hash)
price_msat = pay_req.num_msat
destination = pay_req.destination
invoice_timestamp = pay_req.timestamp
invoice_expiry = pay_req.expiry
node_host = offer_msg.host or self.peer.host
node_port = offer_msg.port
logger.info("price_msat: {}".format(price_msat))
logger.info("destination: {}".format(destination))
logger.info("invoice_timestamp: {}".format(invoice_timestamp))
logger.info("invoice_expiry: {}".format(invoice_expiry))
logger.info("node_host: {}".format(node_host))
logger.info("node_port: {}".format(node_port))
decoded_offer = Offer(
offer_id=None,
squeak_hash=squeak_hash,
price_msat=price_msat,
payment_hash=payment_hash,
nonce=offer_msg.nonce,
payment_point=squeak_payment_point,
invoice_timestamp=invoice_timestamp,
invoice_expiry=invoice_expiry,
payment_request=offer_msg.payment_request,
destination=destination,
node_host=node_host,
node_port=node_port,
peer_id=self.peer.peer_id,
)
# TODO: Check the payment point
# payment_point = offer.payment_point
# logger.info("Payment point: {}".format(payment_point.hex()))
# expected_payment_point = squeak.paymentPoint
# logger.info("Expected payment point: {}".format(expected_payment_point.hex()))
# if payment_point != expected_payment_point:
# raise Exception(
# "Invalid offer payment point: {}, expected: {}".format(
# payment_point.hex(),
# expected_payment_point.hex(),
# )
# )
decoded_offer = self.squeak_controller.get_offer(
squeak, offer_msg, self.peer)
# Save the offer
self._save_offer(decoded_offer)
@ -297,6 +244,3 @@ class PeerSyncTask:
def _save_offer(self, offer):
logger.info("Saving offer: {}".format(offer))
self.squeak_db.insert_offer(offer)
def _decode_payment_request(self, payment_request):
return self.lightning_client.decode_pay_req(payment_request)

View file

@ -1,10 +1,10 @@
import mock
import pytest
from squeaknode.bitcoin.blockchain_client import BlockchainClient
from squeaknode.config.config import SqueaknodeConfig
from squeaknode.core.lightning_address import LightningAddressHostPort
from squeaknode.core.squeak_controller import SqueakController
from squeaknode.core.squeak_core import SqueakCore
from squeaknode.core.squeak_peer import SqueakPeer
from squeaknode.db.squeak_db import SqueakDb
from squeaknode.node.squeak_store import SqueakStore
@ -34,13 +34,8 @@ def squeak_db():
@pytest.fixture
def blockchain_client():
return mock.Mock(spec=BlockchainClient)
@pytest.fixture
def lightning_client():
return mock.Mock()
def squeak_core():
return mock.Mock(spec=SqueakCore)
@pytest.fixture
@ -71,16 +66,14 @@ def squeak_store():
@pytest.fixture
def squeak_controller(
squeak_db,
blockchain_client,
lightning_client,
squeak_core,
squeak_store,
squeak_whitelist,
config,
):
return SqueakController(
squeak_db,
blockchain_client,
lightning_client,
squeak_core,
squeak_store,
squeak_whitelist,
config,
@ -90,16 +83,14 @@ def squeak_controller(
@pytest.fixture
def regtest_squeak_controller(
squeak_db,
blockchain_client,
lightning_client,
squeak_core,
squeak_store,
squeak_whitelist,
regtest_config,
):
return SqueakController(
squeak_db,
blockchain_client,
lightning_client,
squeak_core,
squeak_store,
squeak_whitelist,
regtest_config,

View file

@ -0,0 +1,94 @@
import mock
import pytest
from bitcoin.core import CoreMainParams
from squeak.core.signing import CSigningKey
from squeak.core.signing import CSqueakAddress
from squeaknode.bitcoin.block_info import BlockInfo
from squeaknode.bitcoin.blockchain_client import BlockchainClient
from squeaknode.core.lightning_address import LightningAddressHostPort
from squeaknode.core.squeak_core import SqueakCore
from squeaknode.core.squeak_profile import SqueakProfile
@pytest.fixture
def lightning_client():
return mock.Mock()
@pytest.fixture
def lightning_host_port():
return LightningAddressHostPort(host="my_lightning_host", port=8765)
@pytest.fixture
def price_msat():
return 777
@pytest.fixture
def max_squeaks_per_address_per_hour():
return 5000
class MockBitcoinClient(BlockchainClient):
genesis_block_info = BlockInfo(
block_height=0,
block_hash=CoreMainParams.GENESIS_BLOCK.GetHash(),
block_header=CoreMainParams.GENESIS_BLOCK.serialize(),
)
def get_best_block_info(self) -> BlockInfo:
return self.genesis_block_info
def get_block_info_by_height(self, block_height: int) -> BlockInfo:
if block_height == 0:
return self.genesis_block_info
else:
raise Exception("Invalid block height")
def get_block_hash(self, block_height: int) -> bytes:
if block_height == 0:
return self.genesis_block_info.block_hash
else:
raise Exception("Invalid block height")
def get_block_header(self, block_hash: bytes, verbose: bool) -> bytes:
if block_hash == self.genesis_block_info.block_hash:
return self.genesis_block_info.block_header
else:
raise Exception("Invalid block hash")
@pytest.fixture
def signing_profile():
profile_name = "fake_name"
signing_key = CSigningKey.generate()
verifying_key = signing_key.get_verifying_key()
address = CSqueakAddress.from_verifying_key(verifying_key)
signing_key_str = str(signing_key)
signing_key_bytes = signing_key_str.encode()
return SqueakProfile(
profile_id=None,
profile_name=profile_name,
private_key=signing_key_bytes,
address=str(address),
sharing=False,
following=False,
)
@pytest.fixture
def bitcoin_client():
return MockBitcoinClient()
def test_make_squeak(bitcoin_client, lightning_client, signing_profile):
squeak_core = SqueakCore(bitcoin_client, lightning_client)
squeak_entry = squeak_core.make_squeak(signing_profile, "hello")
assert squeak_entry.squeak.GetDecryptedContentStr() == "hello"
validated_squeak_entry = squeak_core.validate_squeak(squeak_entry.squeak)
assert validated_squeak_entry == squeak_entry