From 0dde62bf464c0522ee5037a1498c7d677e83e490 Mon Sep 17 00:00:00 2001 From: Jonathan Zernik Date: Wed, 11 Nov 2020 03:04:09 -0500 Subject: [PATCH] Add received payments table and get it in rpc method (#443) --- ...387dd872e766_add_received_payment_table.py | 38 ++++++++++++++++ itests/tests/test_squeak_node.py | 7 +++ proto/squeak_admin.proto | 32 ++++++++++++++ .../admin/squeak_admin_server_handler.py | 10 +++++ .../admin/squeak_admin_server_servicer.py | 3 ++ squeaknode/admin/util.py | 14 ++++++ squeaknode/db/models.py | 12 +++++ squeaknode/db/squeak_db.py | 44 +++++++++++++++++++ squeaknode/node/squeak_node.py | 15 +++++++ squeaknode/server/received_payment.py | 6 +++ 10 files changed, 181 insertions(+) create mode 100644 alembic/versions/387dd872e766_add_received_payment_table.py create mode 100644 squeaknode/server/received_payment.py diff --git a/alembic/versions/387dd872e766_add_received_payment_table.py b/alembic/versions/387dd872e766_add_received_payment_table.py new file mode 100644 index 00000000..ee9620d7 --- /dev/null +++ b/alembic/versions/387dd872e766_add_received_payment_table.py @@ -0,0 +1,38 @@ +"""Add received_payment table + +Revision ID: 387dd872e766 +Revises: ccc641c2ce52 +Create Date: 2020-11-11 01:24:10.148710 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '387dd872e766' +down_revision = 'ccc641c2ce52' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('received_payment', + sa.Column('received_payment_id', sa.Integer(), nullable=False), + sa.Column('created', sa.DateTime(), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=False), + sa.Column('squeak_hash', sa.String(length=64), nullable=False), + sa.Column('preimage_hash', sa.String(length=64), nullable=False), + sa.Column('price_msat', sa.Integer(), nullable=False), + sa.Column('is_paid', sa.Boolean(), nullable=False), + sa.Column('payment_time', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('received_payment_id'), + sa.UniqueConstraint('preimage_hash') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('received_payment') + # ### end Alembic commands ### diff --git a/itests/tests/test_squeak_node.py b/itests/tests/test_squeak_node.py index 70fea502..5f8d46d5 100644 --- a/itests/tests/test_squeak_node.py +++ b/itests/tests/test_squeak_node.py @@ -913,6 +913,13 @@ def test_connect_other_node(server_stub, admin_stub, other_server_stub, other_ad assert sent_payment_time > datetime.datetime.now() - five_minutes assert sent_payment_time < datetime.datetime.now() + # Get the received payment from the seller node + get_received_payments_response = admin_stub.GetReceivedPayments( + squeak_admin_pb2.GetReceivedPaymentsRequest(), + ) + squeak_hashes = [received_payment.squeak_hash for received_payment in get_received_payments_response.received_payments] + assert saved_squeak_hash in squeak_hashes + def test_download_single_squeak(server_stub, admin_stub, other_server_stub, other_admin_stub, lightning_client, signing_profile_id, saved_squeak_hash): diff --git a/proto/squeak_admin.proto b/proto/squeak_admin.proto index 27a96483..f4679960 100644 --- a/proto/squeak_admin.proto +++ b/proto/squeak_admin.proto @@ -180,6 +180,10 @@ service SqueakAdmin { */ rpc GetSqueakDetails (GetSqueakDetailsRequest) returns (GetSqueakDetailsReply) {} + /** sqkadmin: `getreceivedpayments` + */ + rpc GetReceivedPayments (GetReceivedPaymentsRequest) returns (GetReceivedPaymentsReply) {} + } message CreateSigningProfileRequest { @@ -645,3 +649,31 @@ message SqueakDetailEntry { /// The seriallized squeak in hex encoding string serialized_squeak_hex = 1; } + +message GetReceivedPaymentsRequest { +} + +message GetReceivedPaymentsReply { + /// The received payments + repeated ReceivedPayment received_payments = 1; +} + +message ReceivedPayment { + /// The received payment id + int32 received_payment_id = 1; + + /// The squeak hash + string squeak_hash = 2; + + /// The preimage hash + string preimage_hash = 3; + + /// The price_msat + int64 price_msat = 4; + + /// Is the payment paid + bool is_paid = 5; + + /// Time of payment + int64 payment_time_ms = 6; +} diff --git a/squeaknode/admin/squeak_admin_server_handler.py b/squeaknode/admin/squeak_admin_server_handler.py index 74c5fbc3..db465ada 100644 --- a/squeaknode/admin/squeak_admin_server_handler.py +++ b/squeaknode/admin/squeak_admin_server_handler.py @@ -11,6 +11,7 @@ from squeaknode.admin.util import offer_entry_to_message from squeaknode.admin.util import sent_payment_with_peer_to_message from squeaknode.admin.util import sync_result_to_message from squeaknode.admin.util import squeak_entry_to_detail_message +from squeaknode.admin.util import received_payment_to_message from proto import squeak_admin_pb2, squeak_admin_pb2_grpc @@ -415,3 +416,12 @@ class SqueakAdminServerHandler(object): return squeak_admin_pb2.GetSqueakDetailsReply( squeak_detail_entry=detail_message ) + + def handle_get_received_payments(self, request): + logger.info("Handle get received payments") + received_payments = self.squeak_node.get_received_payments() + logger.info("Received payments: {}".format(received_payments)) + received_payment_msgs = [received_payment_to_message(received_payment) for received_payment in received_payments] + return squeak_admin_pb2.GetReceivedPaymentsReply( + received_payments=received_payment_msgs, + ) diff --git a/squeaknode/admin/squeak_admin_server_servicer.py b/squeaknode/admin/squeak_admin_server_servicer.py index b557cdbd..0719003d 100644 --- a/squeaknode/admin/squeak_admin_server_servicer.py +++ b/squeaknode/admin/squeak_admin_server_servicer.py @@ -154,6 +154,9 @@ class SqueakAdminServerServicer(squeak_admin_pb2_grpc.SqueakAdminServicer): def GetSqueakDetails(self, request, context): return self.handler.handle_get_squeak_details(request) + def GetReceivedPayments(self, request, context): + return self.handler.handle_get_received_payments(request) + def serve(self): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) squeak_admin_pb2_grpc.add_SqueakAdminServicer_to_server(self, server) diff --git a/squeaknode/admin/util.py b/squeaknode/admin/util.py index 4f09ba5a..d52c7d13 100644 --- a/squeaknode/admin/util.py +++ b/squeaknode/admin/util.py @@ -120,3 +120,17 @@ def squeak_entry_to_detail_message(squeak_entry_with_profile): return squeak_admin_pb2.SqueakDetailEntry( serialized_squeak_hex=serialized_squeak.hex(), ) + +def received_payment_to_message(received_payment): + if received_payment is None: + return None + return squeak_admin_pb2.ReceivedPayment( + received_payment_id=received_payment.received_payment_id, + squeak_hash=received_payment.squeak_hash, + preimage_hash=received_payment.preimage_hash, + price_msat=received_payment.price_msat, + is_paid=received_payment.is_paid, + payment_time_ms=int(received_payment.payment_time.timestamp()) * 1000 + if received_payment.payment_time + else None, + ) diff --git a/squeaknode/db/models.py b/squeaknode/db/models.py index 73200f75..e4910327 100644 --- a/squeaknode/db/models.py +++ b/squeaknode/db/models.py @@ -108,3 +108,15 @@ class Models: Column("node_pubkey", String(66), nullable=False), Column("preimage_is_valid", Boolean, nullable=False), ) + + self.received_payments = Table( + "received_payment", + self.metadata, + Column("received_payment_id", Integer, primary_key=True), + Column("created", DateTime, server_default=func.now(), nullable=False), + Column("squeak_hash", String(64), nullable=False), + Column("preimage_hash", String(64), unique=True, nullable=False), + Column("price_msat", Integer, nullable=False, default=0), + Column("is_paid", Boolean, nullable=False), + Column("payment_time", DateTime, nullable=True), + ) diff --git a/squeaknode/db/squeak_db.py b/squeaknode/db/squeak_db.py index 57428ff9..3fc0ba5a 100644 --- a/squeaknode/db/squeak_db.py +++ b/squeaknode/db/squeak_db.py @@ -28,6 +28,7 @@ from squeaknode.core.squeak_entry_with_profile import SqueakEntryWithProfile from squeaknode.server.squeak_peer import SqueakPeer from squeaknode.server.squeak_profile import SqueakProfile from squeaknode.server.sent_payment import SentPayment +from squeaknode.server.received_payment import ReceivedPayment from squeaknode.server.util import get_hash from squeaknode.db.models import Models from squeaknode.db.migrations import run_migrations @@ -80,6 +81,10 @@ class SqueakDb: def sent_payments(self): return self.models.sent_payments + @property + def received_payments(self): + return self.models.received_payments + def insert_squeak(self, squeak): """ Insert a new squeak. """ vch_decryption_key = squeak.GetDecryptionKey().get_bytes() if squeak.HasDecryptionKey() else None @@ -927,6 +932,33 @@ class SqueakDb: row = result.fetchone() return self._parse_sent_payment_with_peer(row) + def insert_received_payment(self, received_payment): + """ Insert a new received payment. """ + ins = self.received_payments.insert().values( + squeak_hash=received_payment.squeak_hash, + preimage_hash=received_payment.preimage_hash, + price_msat=received_payment.price_msat, + is_paid=received_payment.is_paid, + ) + with self.get_connection() as connection: + res = connection.execute(ins) + received_payment_id = res.inserted_primary_key[0] + return received_payment_id + + def get_received_payments(self): + """ Get all received payments. """ + s = ( + select([self.received_payments]) + .order_by( + self.received_payments.c.created.desc(), + ) + ) + with self.get_connection() as connection: + result = connection.execute(s) + rows = result.fetchall() + received_payments = [self._parse_received_payment(row) for row in rows] + return received_payments + def _parse_squeak_entry(self, row): if row is None: return None @@ -1037,3 +1069,15 @@ class SqueakDb: sent_payment=sent_payment, peer=peer, ) + + def _parse_received_payment(self, row): + if row is None: + return None + return ReceivedPayment( + received_payment_id=row["received_payment_id"], + squeak_hash=row["squeak_hash"], + preimage_hash=row["preimage_hash"], + price_msat=row["price_msat"], + is_paid=row["is_paid"], + payment_time=row["payment_time"], + ) diff --git a/squeaknode/node/squeak_node.py b/squeaknode/node/squeak_node.py index fe3d6426..2e5f1aac 100644 --- a/squeaknode/node/squeak_node.py +++ b/squeaknode/node/squeak_node.py @@ -21,6 +21,7 @@ from squeaknode.node.squeak_store import SqueakStore from squeaknode.node.squeak_sync_status import SqueakSyncController from squeaknode.node.squeak_whitelist import SqueakWhitelist from squeaknode.server.buy_offer import BuyOffer +from squeaknode.server.received_payment import ReceivedPayment from squeaknode.server.squeak_peer import SqueakPeer from squeaknode.server.squeak_profile import SqueakProfile from squeaknode.server.sent_payment import SentPayment @@ -131,6 +132,17 @@ class SqueakNode: # Get the lightning network node pubkey get_info_response = self.lightning_client.get_info() pubkey = get_info_response.identity_pubkey + # Save the incoming potential payment in the databse. + self.squeak_db.insert_received_payment( + ReceivedPayment( + received_payment_id=None, + squeak_hash=squeak_hash, + preimage_hash=preimage_hash.hex(), + price_msat=self.price_msat, + is_paid=False, + payment_time=None, + ) + ) # Return the buy offer return BuyOffer( squeak_hash, @@ -336,3 +348,6 @@ class SqueakNode: def get_sent_payment(self, sent_payment_id): return self.squeak_db.get_sent_payment(sent_payment_id) + + def get_received_payments(self): + return self.squeak_db.get_received_payments() diff --git a/squeaknode/server/received_payment.py b/squeaknode/server/received_payment.py new file mode 100644 index 00000000..1a36a6be --- /dev/null +++ b/squeaknode/server/received_payment.py @@ -0,0 +1,6 @@ +from collections import namedtuple + +ReceivedPayment = namedtuple( + "ReceivedPayment", + "received_payment_id, squeak_hash, preimage_hash, price_msat, is_paid, payment_time", +)