mirror of
https://github.com/yzernik/squeaknode.git
synced 2026-08-13 12:33:25 +02:00
Add buy request (#39)
* Add buy squeak request * Add payment of invoice in itest * Use latest beta release of lnd * Got connect peer from itest working * Open channel in itest before trying to pay * Add list channels and list peers commands * Got lightning payment working * Got full itest working with payment and decryption
This commit is contained in:
parent
30034d35ab
commit
6fe55699f4
14 changed files with 324 additions and 5 deletions
|
|
@ -7,7 +7,7 @@ RUN apk update && \
|
|||
apk add git
|
||||
|
||||
# Copy in the local repository to build from.
|
||||
RUN git clone https://github.com/lightningnetwork/lnd.git --branch v0.9.0-beta
|
||||
RUN git clone https://github.com/lightningnetwork/lnd.git --branch v0.10.1-beta
|
||||
|
||||
# Force Go to use the cgo based DNS resolver. This is required to ensure DNS
|
||||
# queries required to connect to linked containers succeed.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ network=simnet
|
|||
[lnd]
|
||||
rpc_host=lnd
|
||||
rpc_port=10009
|
||||
port=9735
|
||||
|
||||
[electrum]
|
||||
rpc_host=
|
||||
|
|
|
|||
10
itests/generate_blocks.sh
Executable file
10
itests/generate_blocks.sh
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/bash
|
||||
|
||||
|
||||
while true; do
|
||||
alice_address=$(docker exec -it lnd_alice lncli --network=simnet newaddress np2wkh | jq .address -r)
|
||||
MINING_ADDRESS=$alice_address docker-compose up -d btcd
|
||||
echo "Mining 1 blocks to address: $alice_address ..."
|
||||
docker-compose run btcctl generate 400
|
||||
sleep 10
|
||||
done
|
||||
|
|
@ -1,5 +1,17 @@
|
|||
#!/bin/bash
|
||||
|
||||
trap "exit" INT TERM
|
||||
trap "kill 0" EXIT
|
||||
|
||||
function mine_blocks {
|
||||
while true; do
|
||||
printf "Mining 1 block to address: $MINING_ADDRESS ..."
|
||||
docker-compose run btcctl generate 1
|
||||
sleep 10
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
cd itests
|
||||
docker-compose down --volumes
|
||||
docker-compose build
|
||||
|
|
@ -14,6 +26,10 @@ docker-compose run btcctl generate 400
|
|||
echo "Finished mining blocks."
|
||||
sleep 10
|
||||
|
||||
echo "Continue mining blocks, 1 every 10 seconds."
|
||||
mine_blocks &
|
||||
echo "Background mining task is in background..."
|
||||
|
||||
echo "Running test.sh...."
|
||||
docker-compose run test ./test.sh
|
||||
|
||||
|
|
|
|||
0
itests/shell.py
Normal file
0
itests/shell.py
Normal file
|
|
@ -18,10 +18,10 @@ import logging
|
|||
import random
|
||||
import time
|
||||
|
||||
|
||||
from bitcoin.core import lx, x
|
||||
from squeak.params import SelectParams
|
||||
from squeak.core import CSqueak
|
||||
from squeak.core import CheckSqueak
|
||||
from squeak.core import HASH_LENGTH
|
||||
from squeak.core import MakeSqueakFromStr
|
||||
from squeak.core.signing import CSigningKey
|
||||
|
|
@ -89,6 +89,14 @@ def load_lightning_client() -> LNDLightningClient:
|
|||
)
|
||||
|
||||
|
||||
|
||||
def bxor(b1, b2): # use xor for bytes
|
||||
result = bytearray()
|
||||
for b1, b2 in zip(b1, b2):
|
||||
result.append(b1 ^ b2)
|
||||
return bytes(result)
|
||||
|
||||
|
||||
def run():
|
||||
# Set the network to simnet for itest.
|
||||
SelectParams("mainnet")
|
||||
|
|
@ -102,7 +110,7 @@ def run():
|
|||
lnd_lightning_client = load_lightning_client()
|
||||
balance_from_client = lnd_lightning_client.get_wallet_balance()
|
||||
print("Balance from direct client: %s" % balance_from_client)
|
||||
assert balance_from_client.total_balance == 1505000000000
|
||||
assert balance_from_client.total_balance >= 1505000000000
|
||||
|
||||
# Make the stubs
|
||||
server_stub = squeak_server_pb2_grpc.SqueakServerStub(server_channel)
|
||||
|
|
@ -164,6 +172,53 @@ def run():
|
|||
))
|
||||
assert get_hash(squeak) not in set(lookup_response.hashes)
|
||||
|
||||
# Buy the squeak data key
|
||||
buy_response = server_stub.BuySqueak(squeak_server_pb2.BuySqueakRequest(hash=post_response.hash))
|
||||
print("Server buy response: " + str(buy_response))
|
||||
assert buy_response.offer.payment_request.startswith('ln')
|
||||
|
||||
# Connect to the server lightning node
|
||||
# lightning_host_port = buy_response.offer.host + ':' + buy_response.offer.port
|
||||
lightning_host_port = buy_response.offer.host
|
||||
# connect_peer_response = lnd_lightning_client.connect_peer(buy_response.offer.pubkey, 'lnd_sqkserver')
|
||||
connect_peer_response = lnd_lightning_client.connect_peer(buy_response.offer.pubkey, lightning_host_port)
|
||||
print("Server connect peer response: " + str(connect_peer_response))
|
||||
|
||||
# List peers
|
||||
list_peers_response = lnd_lightning_client.list_peers()
|
||||
print("Server list peers response: " + str(list_peers_response))
|
||||
|
||||
# Open channel to the server lightning node
|
||||
# pubkey_bytes = bytes.fromhex(buy_response.offer.pubkey)
|
||||
open_channel_response = lnd_lightning_client.open_channel_sync(buy_response.offer.pubkey, 1000000)
|
||||
print("Server open channel response: " + str(open_channel_response))
|
||||
|
||||
# List channels
|
||||
list_channels_response = lnd_lightning_client.list_channels()
|
||||
print("Server list channels response: " + str(list_channels_response))
|
||||
|
||||
# Sleep for 30 seconds to confirm the channel open transaction
|
||||
time.sleep(60)
|
||||
|
||||
# List channels
|
||||
list_channels_response = lnd_lightning_client.list_channels()
|
||||
print("Server list channels response: " + str(list_channels_response))
|
||||
|
||||
# Pay the invoice
|
||||
preimage = None
|
||||
payment = lnd_lightning_client.pay_invoice_sync(buy_response.offer.payment_request)
|
||||
print("Server pay invoice response: " + str(payment))
|
||||
preimage = payment.payment_preimage
|
||||
print("preimage: " + str(preimage))
|
||||
|
||||
# Clear the data key from the squeak and verify with the payment preimage
|
||||
new_data_key = bxor(buy_response.offer.nonce, preimage)
|
||||
print("new data key: " + str(new_data_key))
|
||||
squeak.ClearDataKey()
|
||||
squeak.SetDataKey(new_data_key)
|
||||
CheckSqueak(squeak)
|
||||
print("Finished checking squeak.")
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -52,3 +52,74 @@ class LNDLightningClient():
|
|||
def get_wallet_balance(self):
|
||||
# Retrieve and display the wallet balance
|
||||
return self.stub.WalletBalance(self.ln_module.WalletBalanceRequest(), metadata=[('macaroon', self.macaroon)])
|
||||
|
||||
def add_invoice(self, preimage, amount):
|
||||
""" Create a new invoice with the given hash pre-image.
|
||||
|
||||
args:
|
||||
preimage -- the preimage bytes used to create the invoice
|
||||
amount -- the value of the invoice
|
||||
"""
|
||||
invoice = self.ln_module.Invoice(
|
||||
r_preimage = preimage,
|
||||
value=amount,
|
||||
)
|
||||
return self.stub.AddInvoice(invoice, metadata=[('macaroon', self.macaroon)])
|
||||
|
||||
def pay_invoice_sync(self, payment_request):
|
||||
""" Pay an invoice with a given payment_request
|
||||
|
||||
args:
|
||||
payment_request -- the payment_request as a string
|
||||
"""
|
||||
send_payment_request = self.ln_module.SendRequest(
|
||||
payment_request=payment_request,
|
||||
)
|
||||
return self.stub.SendPaymentSync(send_payment_request, metadata=[('macaroon', self.macaroon)])
|
||||
|
||||
def connect_peer(self, pubkey, host):
|
||||
""" Connect to a lightning node peer.
|
||||
|
||||
args:
|
||||
pubkey -- The identity pubkey of the Lightning node
|
||||
host -- The network location of the lightning node
|
||||
"""
|
||||
lightning_address = self.ln_module.LightningAddress(
|
||||
pubkey=pubkey,
|
||||
host=host,
|
||||
)
|
||||
connect_peer_request = self.ln_module.ConnectPeerRequest(
|
||||
addr=lightning_address,
|
||||
)
|
||||
return self.stub.ConnectPeer(connect_peer_request, metadata=[('macaroon', self.macaroon)])
|
||||
|
||||
def get_info(self):
|
||||
""" Get info about the lightning network node.
|
||||
"""
|
||||
get_info_request = self.ln_module.GetInfoRequest()
|
||||
return self.stub.GetInfo(get_info_request, metadata=[('macaroon', self.macaroon)])
|
||||
|
||||
def open_channel_sync(self, pubkey_str, local_amount):
|
||||
""" Open a channel with a remote lightning node.
|
||||
|
||||
args:
|
||||
pubkey (str) -- The identity pubkey of the Lightning node
|
||||
local_amount -- The number of satoshis the wallet should commit to the channel
|
||||
"""
|
||||
open_channel_request = self.ln_module.OpenChannelRequest(
|
||||
node_pubkey_string=pubkey_str,
|
||||
local_funding_amount=local_amount,
|
||||
)
|
||||
return self.stub.OpenChannelSync(open_channel_request, metadata=[('macaroon', self.macaroon)])
|
||||
|
||||
def list_channels(self):
|
||||
""" List the channels
|
||||
"""
|
||||
list_channels_request = self.ln_module.ListChannelsRequest()
|
||||
return self.stub.ListChannels(list_channels_request, metadata=[('macaroon', self.macaroon)])
|
||||
|
||||
def list_peers(self):
|
||||
""" List the peers
|
||||
"""
|
||||
list_peers_request = self.ln_module.ListPeersRequest()
|
||||
return self.stub.ListPeers(list_peers_request, metadata=[('macaroon', self.macaroon)])
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ service SqueakServer {
|
|||
*/
|
||||
rpc LookupSqueaks (LookupSqueaksRequest) returns (LookupSqueaksReply) {}
|
||||
|
||||
/** sqk: `buysqueak`
|
||||
*/
|
||||
rpc BuySqueak (BuySqueakRequest) returns (BuySqueakReply) {}
|
||||
|
||||
}
|
||||
|
||||
message PostSqueakRequest {
|
||||
|
|
@ -57,6 +61,11 @@ message LookupSqueaksRequest {
|
|||
int32 max_block = 3;
|
||||
}
|
||||
|
||||
message BuySqueakRequest {
|
||||
/// Hash of the squeak to buy.
|
||||
bytes hash = 1;
|
||||
}
|
||||
|
||||
message PostSqueakReply {
|
||||
/// The squeak.
|
||||
bytes hash = 1;
|
||||
|
|
@ -72,6 +81,11 @@ message LookupSqueaksReply {
|
|||
repeated bytes hashes = 1;
|
||||
}
|
||||
|
||||
message BuySqueakReply {
|
||||
/// The buy offer
|
||||
SqueakBuyOffer offer = 1;
|
||||
}
|
||||
|
||||
message Squeak {
|
||||
/// Hash of the squeak.
|
||||
bytes hash = 1;
|
||||
|
|
@ -79,3 +93,29 @@ message Squeak {
|
|||
/// Serialized squeak.
|
||||
bytes serialized_squeak = 2;
|
||||
}
|
||||
|
||||
message SqueakBuyOffer {
|
||||
/// The squeak hash.
|
||||
bytes squeak_hash = 1;
|
||||
|
||||
/// The decryption nonce
|
||||
bytes nonce = 2;
|
||||
|
||||
/// The amount
|
||||
int64 amount = 3;
|
||||
|
||||
/// The invoice preimage hash.
|
||||
bytes preimage_hash = 4;
|
||||
|
||||
/// The invoice
|
||||
string payment_request = 5;
|
||||
|
||||
/// The seller node pubkey
|
||||
string pubkey = 6;
|
||||
|
||||
/// The host of the seller lightning node
|
||||
string host = 7;
|
||||
|
||||
/// The port of the seller lightning node
|
||||
string port = 8;
|
||||
}
|
||||
|
|
|
|||
13
squeakserver/server/buy_offer.py
Normal file
13
squeakserver/server/buy_offer.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
|
||||
class BuyOffer():
|
||||
|
||||
def __init__(self, squeak_hash, nonce, amount, preimage_hash, payment_request, pubkey, host, port):
|
||||
self.squeak_hash = squeak_hash
|
||||
self.nonce = nonce
|
||||
self.amount = amount
|
||||
self.preimage_hash = preimage_hash
|
||||
self.payment_request = payment_request
|
||||
self.pubkey = pubkey
|
||||
self.host = host
|
||||
self.port = port
|
||||
4
squeakserver/server/lightning_address.py
Normal file
4
squeakserver/server/lightning_address.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from collections import namedtuple
|
||||
|
||||
|
||||
LightningAddressHostPort = namedtuple('LightningAddress', ['host', 'port'])
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import socket
|
||||
import argparse
|
||||
import logging
|
||||
import signal
|
||||
|
|
@ -14,6 +15,7 @@ import squeakserver.common.rpc.lnd_pb2 as ln
|
|||
import squeakserver.common.rpc.lnd_pb2_grpc as lnrpc
|
||||
|
||||
from squeakserver.common.lnd_lightning_client import LNDLightningClient
|
||||
from squeakserver.server.lightning_address import LightningAddressHostPort
|
||||
from squeakserver.server.squeak_server_servicer import SqueakServerServicer
|
||||
from squeakserver.server.squeak_server_handler import SqueakServerHandler
|
||||
from squeakserver.server.db_params import parse_db_params
|
||||
|
|
@ -30,6 +32,17 @@ def load_lightning_client(config) -> LNDLightningClient:
|
|||
)
|
||||
|
||||
|
||||
def load_lightning_host_port(config) -> LNDLightningClient:
|
||||
lnd_ip_address = socket.gethostbyname(
|
||||
config['lnd']['rpc_host'],
|
||||
)
|
||||
lnd_port = config['lnd']['port']
|
||||
return LightningAddressHostPort(
|
||||
lnd_ip_address,
|
||||
lnd_port,
|
||||
)
|
||||
|
||||
|
||||
def load_rpc_server(config, handler) -> SqueakServerServicer:
|
||||
return SqueakServerServicer(
|
||||
config['server']['rpc_host'],
|
||||
|
|
@ -51,8 +64,9 @@ def start_rpc_server(handler):
|
|||
server.serve()
|
||||
|
||||
|
||||
def load_handler(lightning_client, postgres_db):
|
||||
def load_handler(lightning_host_port, lightning_client, postgres_db):
|
||||
return SqueakServerHandler(
|
||||
lightning_host_port,
|
||||
lightning_client,
|
||||
postgres_db
|
||||
)
|
||||
|
|
@ -142,8 +156,9 @@ def run_server(config):
|
|||
|
||||
print('starting lightning client here...', flush=True)
|
||||
lightning_client = load_lightning_client(config)
|
||||
lightning_host_port = load_lightning_host_port(config)
|
||||
# db_factory = load_db_factory(config)
|
||||
handler = load_handler(lightning_client, postgres_db)
|
||||
handler = load_handler(lightning_host_port, lightning_client, postgres_db)
|
||||
|
||||
# start rpc server
|
||||
# start_rpc_server(handler)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ from squeak.core.signing import CSigningKey
|
|||
from squeak.core.signing import CSqueakAddress
|
||||
|
||||
from squeakserver.common.lnd_lightning_client import LNDLightningClient
|
||||
from squeakserver.server.buy_offer import BuyOffer
|
||||
from squeakserver.server.postgres_db import PostgresDb
|
||||
from squeakserver.server.util import generate_offer_nonce
|
||||
from squeakserver.server.util import bxor
|
||||
from squeakserver.server.lightning_address import LightningAddressHostPort
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -17,9 +21,11 @@ class SqueakServerHandler(object):
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
lightning_host_port: LightningAddressHostPort,
|
||||
lightning_client: LNDLightningClient,
|
||||
postgres_db: PostgresDb,
|
||||
) -> None:
|
||||
self.lightning_host_port = lightning_host_port
|
||||
self.lightning_client = lightning_client
|
||||
self.postgres_db = postgres_db
|
||||
|
||||
|
|
@ -42,3 +48,41 @@ class SqueakServerHandler(object):
|
|||
hashes = self.postgres_db.lookup_squeaks(addresses, min_block, max_block)
|
||||
logger.info("Got hashes from db: " + str(hashes))
|
||||
return hashes
|
||||
|
||||
def handle_buy_squeak(self, squeak_hash):
|
||||
logger.info("Handler buy squeak by hash: " + str(squeak_hash))
|
||||
|
||||
# Get the squeak from the database
|
||||
squeak = self.postgres_db.get_squeak(squeak_hash)
|
||||
# Get the datakey from the squeak
|
||||
data_key = squeak.GetDataKey()
|
||||
# Generate a new random offer nonce
|
||||
nonce = generate_offer_nonce()
|
||||
# Get the invoice preimage from the nonce and the squeak data key
|
||||
logger.info("Handling buy with nonce: " + str(nonce))
|
||||
logger.info("Handling buy with data_key: " + str(data_key))
|
||||
preimage = bxor(nonce, data_key)
|
||||
# TODO: Get the offer price
|
||||
amount = 100
|
||||
|
||||
logger.info("Handling buy with preimage: " + str(preimage))
|
||||
# Create the lightning invoice
|
||||
add_invoice_response = self.lightning_client.add_invoice(preimage, amount)
|
||||
preimage_hash = add_invoice_response.r_hash
|
||||
invoice_payment_request = add_invoice_response.payment_request
|
||||
|
||||
# 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,
|
||||
nonce,
|
||||
amount,
|
||||
preimage_hash,
|
||||
invoice_payment_request,
|
||||
pubkey,
|
||||
self.lightning_host_port.host,
|
||||
self.lightning_host_port.port,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -85,6 +85,41 @@ class SqueakServerServicer(squeak_server_pb2_grpc.SqueakServerServicer):
|
|||
hashes=hashes,
|
||||
)
|
||||
|
||||
def BuySqueak(self, request, context):
|
||||
squeak_hash = request.hash
|
||||
# TODO: check if hash is valid
|
||||
|
||||
buy_response = self.handler.handle_buy_squeak(squeak_hash)
|
||||
|
||||
if buy_response == None:
|
||||
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
|
||||
return squeak_server_pb2.BuySqueakReply(
|
||||
offer=None,
|
||||
)
|
||||
|
||||
offer_squeak_hash = buy_response.squeak_hash
|
||||
amount = buy_response.amount
|
||||
nonce = buy_response.nonce
|
||||
|
||||
if offer_squeak_hash != squeak_hash:
|
||||
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
|
||||
return squeak_server_pb2.BuySqueakReply(
|
||||
offer=None,
|
||||
)
|
||||
|
||||
return squeak_server_pb2.BuySqueakReply(
|
||||
offer=squeak_server_pb2.SqueakBuyOffer(
|
||||
squeak_hash=offer_squeak_hash,
|
||||
nonce=nonce,
|
||||
amount=amount,
|
||||
preimage_hash=buy_response.preimage_hash,
|
||||
payment_request=buy_response.payment_request,
|
||||
pubkey=buy_response.pubkey,
|
||||
host=buy_response.host,
|
||||
port=buy_response.port,
|
||||
)
|
||||
)
|
||||
|
||||
def serve(self):
|
||||
print('Calling serve...', flush=True)
|
||||
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,19 @@
|
|||
import os
|
||||
|
||||
|
||||
DATA_KEY_LENGTH = 32
|
||||
|
||||
|
||||
def get_hash(squeak):
|
||||
return squeak.GetHash()[::-1]
|
||||
|
||||
|
||||
def generate_offer_nonce():
|
||||
return os.urandom(DATA_KEY_LENGTH)
|
||||
|
||||
|
||||
def bxor(b1, b2): # use xor for bytes
|
||||
result = bytearray()
|
||||
for b1, b2 in zip(b1, b2):
|
||||
result.append(b1 ^ b2)
|
||||
return bytes(result)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue