Update squeaklib to support private messages (#1898)

* Update squeaklib and update import from keys module

* Rename signing key variable to private key in test util

* Fix calling make squeak function

* Update db data file to v3

* Add recipient public key column to squeak table

* Got squeak entry db query working with alias for profiles table

* Add test for make squeak with recipient

* Add test case for get profile from squeak entry query

* Got outer join with recipient profile working

* Use outer join to get recipient profile for all squeak entry db queries

* Got itest passing with refactored squeak_store

* Remove unused config from squeak store class
This commit is contained in:
Jonathan Zernik 2021-12-27 01:15:05 -08:00 committed by GitHub
parent 51f412d75f
commit cf64664a44
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 315 additions and 318 deletions

View file

@ -65,6 +65,8 @@ class SqueakCore:
def make_squeak(self, signing_profile: SqueakProfile, content_str: str, replyto_hash: Optional[bytes] = None) -> Tuple[CSqueak, bytes]:
"""Create a new squeak.
TODO: Include the block header in the result tuple.
Args:
signing_profile: The profile of the author of the squeak.
content_str: The content of the squeak as a string.

View file

@ -233,7 +233,7 @@ class Connection(object):
host=msg.offer.host.decode('utf-8'),
port=msg.offer.port,
)
self.network_handler.save_received_offer(
self.network_handler.handle_received_offer(
offer,
self.peer.remote_address,
)

View file

@ -20,11 +20,10 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import logging
from typing import List
from typing import Optional
from bitcoin.core import CBlockHeader
from squeak.core import CSqueak
from squeak.core.keys import SqueakPublicKey
from squeak.messages import msg_getdata
from squeak.messages import MSG_SECRET_KEY
from squeak.messages import MSG_SQUEAK
@ -33,17 +32,23 @@ from squeak.net import CInterested
from squeak.net import CInv
from squeak.net import CSqueakLocator
from squeaknode.core.block_range import BlockRange
from squeaknode.core.interests import squeak_matches_interest
from squeaknode.core.lightning_address import LightningAddressHostPort
from squeaknode.core.offer import Offer
from squeaknode.core.peer_address import PeerAddress
from squeaknode.core.sent_offer import SentOffer
from squeaknode.core.squeak_core import SqueakCore
from squeaknode.node.active_download_manager import ActiveDownload
from squeaknode.node.downloaded_object import DownloadedOffer
from squeaknode.node.downloaded_object import DownloadedSqueak
from squeaknode.node.price_policy import PricePolicy
from squeaknode.node.secret_key_reply import FreeSecretKeyReply
from squeaknode.node.secret_key_reply import OfferReply
from squeaknode.node.secret_key_reply import SecretKeyReply
from squeaknode.node.squeak_store import SqueakStore
logger = logging.getLogger(__name__)
@ -55,20 +60,19 @@ class NetworkHandler:
def __init__(
self,
squeak_store: SqueakStore,
squeak_core: SqueakCore,
network_manager,
download_manager,
node_settings,
config,
):
self.squeak_store = squeak_store
self.squeak_core = squeak_core
self.network_manager = network_manager
self.active_download_manager = download_manager
self.node_settings = node_settings
self.config = config
def get_interested_locator(self) -> CSqueakLocator:
return self.squeak_store.get_interested_locator()
def get_squeak(self, squeak_hash: bytes) -> Optional[CSqueak]:
return self.squeak_store.get_squeak(squeak_hash)
@ -96,11 +100,11 @@ class NetworkHandler:
]
def save_squeak(self, squeak: CSqueak) -> Optional[bytes]:
# return self.squeak_store.save_squeak(squeak)
return self.save_active_download_squeak(squeak) or \
self.save_followed_squeak(squeak)
block_header = self.squeak_core.get_block_header(squeak)
return self.save_active_download_squeak(squeak, block_header) or \
self.save_followed_squeak(squeak, block_header)
def save_active_download_squeak(self, squeak: CSqueak) -> Optional[bytes]:
def save_active_download_squeak(self, squeak: CSqueak, block_header: CBlockHeader) -> Optional[bytes]:
"""Save the given squeak as an active download.
Returns:
@ -109,13 +113,13 @@ class NetworkHandler:
counter = self.get_download_squeak_counter(squeak)
if counter is None:
return None
saved_squeak_hash = self.squeak_store.save_squeak(squeak)
saved_squeak_hash = self.squeak_store.save_squeak(squeak, block_header)
if saved_squeak_hash is None:
return None
counter.increment()
return saved_squeak_hash
def save_followed_squeak(self, squeak: CSqueak) -> Optional[bytes]:
def save_followed_squeak(self, squeak: CSqueak, block_header: CBlockHeader) -> Optional[bytes]:
"""Save the given squeak because it matches the followed
interest criteria.
@ -125,7 +129,7 @@ class NetworkHandler:
if not self.squeak_matches_interest(squeak):
return None
# TODO: catch exception if save_squeak fails (because of rate limit, for example).
return self.squeak_store.save_squeak(squeak)
return self.squeak_store.save_squeak(squeak, block_header)
def squeak_matches_interest(self, squeak: CSqueak) -> bool:
locator = self.get_interested_locator()
@ -135,67 +139,16 @@ class NetworkHandler:
return False
def unlock_squeak(self, squeak_hash: bytes, secret_key: bytes):
return self.squeak_store.unlock_squeak(squeak_hash, secret_key)
def get_reply_invs(self, interest):
squeak_hashes = self._get_local_squeaks(interest)
secret_key_hashes = self._get_local_secret_keys(interest)
squeak_invs = [
CInv(type=MSG_SQUEAK, hash=squeak_hash)
for squeak_hash in squeak_hashes]
secret_key_invs = [
CInv(type=MSG_SECRET_KEY, hash=squeak_hash)
for squeak_hash in secret_key_hashes]
return squeak_invs + secret_key_invs
def _get_local_squeaks(self, interest: CInterested):
min_block = interest.nMinBlockHeight if interest.nMinBlockHeight != -1 else None
max_block = interest.nMaxBlockHeight if interest.nMaxBlockHeight != -1 else None
reply_to_hash = interest.hashReplySqk if interest.hashReplySqk != EMPTY_HASH else None
return self.lookup_squeaks(
public_keys=interest.pubkeys,
min_block=min_block,
max_block=max_block,
reply_to_hash=reply_to_hash,
# return self.squeak_store.unlock_squeak(squeak_hash, secret_key)
squeak = self.squeak_store.get_squeak(squeak_hash)
decrypted_content = self.squeak_core.get_decrypted_content(
squeak,
secret_key,
)
def _get_local_secret_keys(self, interest: CInterested):
min_block = interest.nMinBlockHeight if interest.nMinBlockHeight != -1 else None
max_block = interest.nMaxBlockHeight if interest.nMaxBlockHeight != -1 else None
reply_to_hash = interest.hashReplySqk if interest.hashReplySqk != EMPTY_HASH else None
return self.lookup_secret_keys(
public_keys=interest.pubkeys,
min_block=min_block,
max_block=max_block,
reply_to_hash=reply_to_hash,
)
def lookup_squeaks(
self,
public_keys: List[SqueakPublicKey],
min_block: Optional[int],
max_block: Optional[int],
reply_to_hash: Optional[bytes],
) -> List[bytes]:
return self.squeak_store.lookup_squeaks(
public_keys,
min_block,
max_block,
reply_to_hash,
)
def lookup_secret_keys(
self,
public_keys: List[SqueakPublicKey],
min_block: Optional[int],
max_block: Optional[int],
reply_to_hash: Optional[bytes],
) -> List[bytes]:
return self.squeak_store.lookup_secret_keys(
public_keys,
min_block,
max_block,
reply_to_hash,
self.squeak_store.unlock_squeak(
squeak_hash,
secret_key,
decrypted_content,
)
def get_secret_key_reply(self, squeak_hash: bytes, peer_address: PeerAddress) -> Optional[SecretKeyReply]:
@ -207,12 +160,132 @@ class NetworkHandler:
host=self.config.lnd.external_host,
port=self.config.lnd.port,
)
return self.squeak_store.get_secret_key_reply(
if price_msat == 0:
return self.get_free_squeak_secret_key_reply(
squeak_hash,
)
else:
return self.get_offer_reply(
squeak_hash,
lnd_external_address,
peer_address,
price_msat,
)
def get_offer_reply(
self,
squeak_hash: bytes,
lnd_external_address: Optional[LightningAddressHostPort],
peer_address: PeerAddress,
price_msat: int,
) -> Optional[OfferReply]:
sent_offer = self.get_sent_offer_for_peer(
squeak_hash,
lnd_external_address,
peer_address,
price_msat,
)
if sent_offer is None:
return None
try:
offer = self.squeak_core.package_offer(
sent_offer,
lnd_external_address,
)
return OfferReply(
squeak_hash=squeak_hash,
offer=offer,
)
except Exception:
return None
def get_free_squeak_secret_key_reply(self, squeak_hash: bytes) -> Optional[FreeSecretKeyReply]:
secret_key = self.squeak_store.get_squeak_secret_key(squeak_hash)
if secret_key is None:
return None
return FreeSecretKeyReply(
squeak_hash=squeak_hash,
secret_key=secret_key,
)
def get_sent_offer_for_peer(
self,
squeak_hash: bytes,
peer_address: PeerAddress,
price_msat: int,
) -> Optional[SentOffer]:
# Check if there is an existing offer for the hash/peer_address combination
sent_offer = self.squeak_store.get_sent_offer_by_squeak_hash_and_peer(
squeak_hash,
peer_address,
)
if sent_offer:
return sent_offer
squeak = self.squeak_store.get_squeak(squeak_hash)
secret_key = self.squeak_store.get_squeak_secret_key(squeak_hash)
if squeak is None or secret_key is None:
return None
try:
sent_offer = self.squeak_core.create_offer(
squeak,
secret_key,
peer_address,
price_msat,
)
except Exception:
logger.exception("Failed to create offer.")
return None
self.squeak_store.save_sent_offer(sent_offer)
return sent_offer
def save_received_offer(self, offer: Offer, peer_address: PeerAddress) -> Optional[int]:
squeak = self.squeak_store.get_squeak(offer.squeak_hash)
secret_key = self.squeak_store.get_squeak_secret_key(offer.squeak_hash)
if squeak is None or secret_key is not None:
return None
try:
# TODO: Call unpack_offer with check_payment_point=True.
received_offer = self.squeak_core.unpack_offer(
squeak,
offer,
peer_address,
)
except Exception:
logger.exception("Failed to save received offer.")
return None
return self.squeak_store.save_received_offer(received_offer)
def get_reply_invs(self, interest):
squeak_hashes = self.get_local_squeaks(interest)
secret_key_hashes = self.get_local_secret_keys(interest)
squeak_invs = [
CInv(type=MSG_SQUEAK, hash=squeak_hash)
for squeak_hash in squeak_hashes]
secret_key_invs = [
CInv(type=MSG_SECRET_KEY, hash=squeak_hash)
for squeak_hash in secret_key_hashes]
return squeak_invs + secret_key_invs
def get_local_squeaks(self, interest: CInterested):
min_block = interest.nMinBlockHeight if interest.nMinBlockHeight != -1 else None
max_block = interest.nMaxBlockHeight if interest.nMaxBlockHeight != -1 else None
reply_to_hash = interest.hashReplySqk if interest.hashReplySqk != EMPTY_HASH else None
return self.squeak_store.lookup_squeaks(
interest.pubkeys,
min_block,
max_block,
reply_to_hash,
)
def get_local_secret_keys(self, interest: CInterested):
min_block = interest.nMinBlockHeight if interest.nMinBlockHeight != -1 else None
max_block = interest.nMaxBlockHeight if interest.nMaxBlockHeight != -1 else None
reply_to_hash = interest.hashReplySqk if interest.hashReplySqk != EMPTY_HASH else None
return self.squeak_store.lookup_secret_keys(
interest.pubkeys,
min_block,
max_block,
reply_to_hash,
)
def request_offers(self, squeak_hash: bytes):
logger.info("Requesting offers for squeak: {}".format(
@ -224,9 +297,9 @@ class NetworkHandler:
getdata_msg = msg_getdata(inv=invs)
self.broadcast_msg(getdata_msg)
def save_received_offer(self, offer: Offer, peer_address: PeerAddress) -> Optional[int]:
def handle_received_offer(self, offer: Offer, peer_address: PeerAddress) -> Optional[int]:
# return self.squeak_store.save_received_offer(offer, peer_address)
received_offer_id = self.squeak_store.save_received_offer(
received_offer_id = self.save_received_offer(
offer,
peer_address,
)
@ -255,3 +328,30 @@ class NetworkHandler:
def broadcast_msg(self, msg: MsgSerializable) -> int:
return self.network_manager.broadcast_msg(msg)
def get_interested_locator(self) -> CSqueakLocator:
block_range = self.get_interested_block_range()
followed_public_keys = self.squeak_store.get_followed_public_keys()
if len(followed_public_keys) == 0:
return CSqueakLocator(
vInterested=[],
)
interests = [
CInterested(
pubkeys=followed_public_keys,
nMinBlockHeight=block_range.min_block,
nMaxBlockHeight=block_range.max_block,
)
]
return CSqueakLocator(
vInterested=interests,
)
def get_interested_block_range(self) -> BlockRange:
max_block = self.squeak_core.get_best_block_height()
min_block = max(
0,
# TODO: rename this.
max_block - self.config.node.interest_block_interval,
)
return BlockRange(min_block, max_block)

View file

@ -24,7 +24,7 @@ import threading
from squeaknode.bitcoin.bitcoin_block_subscription_client import BitcoinBlockSubscriptionClient
from squeaknode.network.network_manager import NetworkManager
from squeaknode.node.squeak_store import SqueakStore
from squeaknode.node.network_handler import NetworkHandler
logger = logging.getLogger(__name__)
@ -33,12 +33,12 @@ logger = logging.getLogger(__name__)
class PeerSubscriptionUpdateWorker:
def __init__(
self,
squeak_store: SqueakStore,
network_manager: NetworkManager,
network_handler: NetworkHandler,
block_subscription_client: BitcoinBlockSubscriptionClient,
):
self.squeak_store = squeak_store
self.network_manager = network_manager
self.network_handler = network_handler
self.block_subscription_client = block_subscription_client
def start_running(self):
@ -50,5 +50,5 @@ class PeerSubscriptionUpdateWorker:
def subscribe_blocks(self):
for block_hash in self.block_subscription_client.get_blocks():
logger.info("Got block from zeromq: {}".format(block_hash.hex()))
locator = self.squeak_store.get_interested_locator()
locator = self.network_handler.get_interested_locator()
self.network_manager.update_local_subscriptions(locator)

View file

@ -36,13 +36,13 @@ from squeak.net import CInv
from squeaknode.core.connected_peer import ConnectedPeer
from squeaknode.core.download_result import DownloadResult
from squeaknode.core.offer import Offer
from squeaknode.core.peer_address import PeerAddress
from squeaknode.core.received_offer import ReceivedOffer
from squeaknode.core.received_payment import ReceivedPayment
from squeaknode.core.received_payment_summary import ReceivedPaymentSummary
from squeaknode.core.sent_payment import SentPayment
from squeaknode.core.sent_payment_summary import SentPaymentSummary
from squeaknode.core.squeak_core import SqueakCore
from squeaknode.core.squeak_entry import SqueakEntry
from squeaknode.core.squeak_peer import SqueakPeer
from squeaknode.core.squeak_profile import SqueakProfile
@ -60,7 +60,7 @@ class SqueakController:
def __init__(
self,
squeak_store: SqueakStore,
squeak_core,
squeak_core: SqueakCore,
payment_processor,
network_manager,
download_manager,
@ -77,14 +77,72 @@ class SqueakController:
self.node_settings = node_settings
self.config = config
def save_squeak(self, squeak: CSqueak) -> Optional[bytes]:
return self.squeak_store.save_squeak(squeak)
def unlock_squeak(self, squeak_hash: bytes, secret_key: bytes):
return self.squeak_store.unlock_squeak(squeak_hash, secret_key)
def make_squeak(self, profile_id: int, content_str: str, replyto_hash: Optional[bytes]) -> Optional[bytes]:
return self.squeak_store.make_squeak(profile_id, content_str, replyto_hash)
# return self.squeak_store.make_squeak(profile_id, content_str, replyto_hash)
squeak_profile = self.squeak_store.get_squeak_profile(profile_id)
if squeak_profile is None:
raise Exception("Profile with id {} not found.".format(
profile_id,
))
squeak, secret_key = self.squeak_core.make_squeak(
squeak_profile,
content_str,
replyto_hash,
)
block_header = self.squeak_core.get_block_header(squeak)
inserted_squeak_hash = self.squeak_store.save_squeak(
squeak,
block_header,
)
if inserted_squeak_hash is None:
return None
self.squeak_store.unlock_squeak(
inserted_squeak_hash,
secret_key,
content_str,
)
return inserted_squeak_hash
def pay_offer(self, received_offer_id: int) -> int:
# return self.squeak_store.pay_offer(received_offer_id)
# Get the offer from the database
received_offer = self.squeak_store.get_received_offer(
received_offer_id,
)
if received_offer is None:
raise Exception("Received offer with id {} not found.".format(
received_offer_id,
))
logger.info("Paying received offer: {}".format(received_offer))
sent_payment = self.squeak_core.pay_offer(received_offer)
sent_payment_id = self.squeak_store.save_sent_payment(sent_payment)
# # Delete the received offer
# self.squeak_db.delete_offer(sent_payment.payment_hash)
# Mark the received offer as paid
self.squeak_store.mark_received_offer_paid(
sent_payment.payment_hash,
)
# self.unlock_squeak(
# received_offer.squeak_hash,
# sent_payment.secret_key,
# )
squeak = self.squeak_store.get_squeak(received_offer.squeak_hash)
decrypted_content = self.squeak_core.get_decrypted_content(
squeak,
sent_payment.secret_key,
)
self.squeak_store.unlock_squeak(
received_offer.squeak_hash,
sent_payment.secret_key,
decrypted_content,
)
return sent_payment_id
# def save_squeak(self, squeak: CSqueak) -> Optional[bytes]:
# return self.squeak_store.save_squeak(squeak)
# def unlock_squeak(self, squeak_hash: bytes, secret_key: bytes):
# return self.squeak_store.unlock_squeak(squeak_hash, secret_key)
def get_squeak(self, squeak_hash: bytes) -> Optional[CSqueak]:
return self.squeak_store.get_squeak(squeak_hash)
@ -173,9 +231,6 @@ class SqueakController:
def get_received_offer(self, received_offer_id: int) -> Optional[ReceivedOffer]:
return self.squeak_store.get_received_offer(received_offer_id)
def pay_offer(self, received_offer_id: int) -> int:
return self.squeak_store.pay_offer(received_offer_id)
def get_sent_payments(
self,
limit: int,
@ -272,12 +327,12 @@ class SqueakController:
last_entry,
)
def save_received_offer(self, offer: Offer, peer_address: PeerAddress) -> Optional[int]:
return self.squeak_store.save_received_offer(offer, peer_address)
# def save_received_offer(self, offer: Offer, peer_address: PeerAddress) -> Optional[int]:
# return self.squeak_store.save_received_offer(offer, peer_address)
# TODO: remove from controller.
def get_followed_public_keys(self) -> List[SqueakPublicKey]:
return self.squeak_store.get_followed_public_keys()
# # TODO: remove from controller.
# def get_followed_public_keys(self) -> List[SqueakPublicKey]:
# return self.squeak_store.get_followed_public_keys()
def get_received_payment_summary(self) -> ReceivedPaymentSummary:
return self.squeak_store.get_received_payment_summary()
@ -461,7 +516,7 @@ class SqueakController:
def subscribe_timeline_squeak_entries(self, stopped: threading.Event):
for item in self.squeak_store.subscribe_new_squeaks(stopped):
followed_public_keys = self.get_followed_public_keys()
followed_public_keys = self.squeak_store.get_followed_public_keys()
if item.GetPubKey() in set(followed_public_keys):
squeak_hash = get_hash(item)
yield self.get_squeak_entry(squeak_hash)

View file

@ -171,13 +171,11 @@ class SqueakNode:
def initialize_squeak_store(self):
self.squeak_store = SqueakStore(
self.squeak_db,
self.squeak_core,
self.config.node.max_squeaks,
self.config.node.max_squeaks_per_public_key_per_block,
self.config.node.squeak_retention_s,
self.config.node.received_offer_retention_s,
self.config.node.sent_offer_retention_s,
self.config.node.interest_block_interval,
)
def initialize_payment_processor(self):
@ -190,6 +188,7 @@ class SqueakNode:
def initialize_twitter_forwarder(self):
self.twitter_forwarder = TwitterForwarder(
self.squeak_store,
self.squeak_core,
self.config.twitter.forward_tweets_retry_s,
)
@ -211,6 +210,7 @@ class SqueakNode:
def initialize_network_handler(self):
self.network_handler = NetworkHandler(
self.squeak_store,
self.squeak_core,
self.network_manager,
self.download_manager,
self.node_settings,
@ -287,12 +287,13 @@ class SqueakNode:
self.new_follow_worker = UpdateFollowsWorker(
self.squeak_store,
self.network_manager,
self.network_handler,
)
def initialize_peer_subscription_update_worker(self):
self.new_bitcoin_block_worker = PeerSubscriptionUpdateWorker(
self.squeak_store,
self.network_manager,
self.network_handler,
self.bitcoin_block_subscription_client,
)

View file

@ -25,15 +25,12 @@ from typing import Iterator
from typing import List
from typing import Optional
from bitcoin.core import CBlockHeader
from squeak.core import CheckSqueak
from squeak.core import CSqueak
from squeak.core.keys import SqueakPrivateKey
from squeak.core.keys import SqueakPublicKey
from squeak.net import CInterested
from squeak.net import CSqueakLocator
from squeaknode.core.block_range import BlockRange
from squeaknode.core.lightning_address import LightningAddressHostPort
from squeaknode.core.offer import Offer
from squeaknode.core.peer_address import PeerAddress
from squeaknode.core.peers import create_saved_peer
from squeaknode.core.profiles import create_contact_profile
@ -45,7 +42,6 @@ from squeaknode.core.received_payment_summary import ReceivedPaymentSummary
from squeaknode.core.sent_offer import SentOffer
from squeaknode.core.sent_payment import SentPayment
from squeaknode.core.sent_payment_summary import SentPaymentSummary
from squeaknode.core.squeak_core import SqueakCore
from squeaknode.core.squeak_entry import SqueakEntry
from squeaknode.core.squeak_peer import SqueakPeer
from squeaknode.core.squeak_profile import SqueakProfile
@ -53,9 +49,6 @@ from squeaknode.core.twitter_account import TwitterAccount
from squeaknode.core.twitter_account_entry import TwitterAccountEntry
from squeaknode.core.update_subscriptions_event import UpdateSubscriptionsEvent
from squeaknode.node.listener_subscription_client import EventListener
from squeaknode.node.secret_key_reply import FreeSecretKeyReply
from squeaknode.node.secret_key_reply import OfferReply
from squeaknode.node.secret_key_reply import SecretKeyReply
logger = logging.getLogger(__name__)
@ -66,33 +59,27 @@ class SqueakStore:
def __init__(
self,
squeak_db,
squeak_core: SqueakCore,
max_squeaks,
max_squeaks_per_public_key_per_block,
squeak_retention_s,
received_offer_retention_s,
sent_offer_retention_s,
interested_block_range_size,
):
self.squeak_db = squeak_db
self.squeak_core = squeak_core
self.max_squeaks = max_squeaks
self.max_squeaks_per_public_key_per_block = max_squeaks_per_public_key_per_block
self.squeak_retention_s = squeak_retention_s
self.received_offer_retention_s = received_offer_retention_s
self.sent_offer_retention_s = sent_offer_retention_s
self.interested_block_range_size = interested_block_range_size
self.new_squeak_listener = EventListener()
self.new_received_offer_listener = EventListener()
self.new_secret_key_listener = EventListener()
self.new_follow_listener = EventListener()
self.twitter_stream_change_listener = EventListener()
def save_squeak(self, squeak: CSqueak) -> Optional[bytes]:
def save_squeak(self, squeak: CSqueak, block_header: CBlockHeader) -> Optional[bytes]:
# Check if the squeak is valid
self.squeak_core.check_squeak(squeak)
# Get the block header for the squeak.
block_header = self.squeak_core.get_block_header(squeak)
CheckSqueak(squeak)
# Check if limit exceeded.
if self.squeak_db.get_number_of_squeaks() >= self.max_squeaks:
raise Exception("Exceeded max number of squeaks.")
@ -113,16 +100,11 @@ class SqueakStore:
logger.info("Saved squeak: {}".format(
inserted_squeak_hash.hex(),
))
# Notify the listener
self.new_squeak_listener.handle_new_item(squeak)
return inserted_squeak_hash
def unlock_squeak(self, squeak_hash: bytes, secret_key: bytes):
def unlock_squeak(self, squeak_hash: bytes, secret_key: bytes, decrypted_content: str):
squeak = self.squeak_db.get_squeak(squeak_hash)
decrypted_content = self.squeak_core.get_decrypted_content(
squeak,
secret_key,
)
self.squeak_db.set_squeak_decryption_key(
squeak_hash,
secret_key,
@ -131,126 +113,8 @@ class SqueakStore:
logger.info("Unlocked squeak: {}".format(
squeak_hash.hex(),
))
# Notify the listener
self.new_secret_key_listener.handle_new_item(squeak)
def make_squeak(self, profile_id: int, content_str: str, replyto_hash: Optional[bytes]) -> Optional[bytes]:
squeak_profile = self.squeak_db.get_profile(profile_id)
squeak, decryption_key = self.squeak_core.make_squeak(
squeak_profile,
content_str,
replyto_hash,
)
inserted_squeak_hash = self.save_squeak(squeak)
if inserted_squeak_hash is None:
return None
self.unlock_squeak(
inserted_squeak_hash,
decryption_key,
)
return inserted_squeak_hash
def get_secret_key_reply(
self,
squeak_hash: bytes,
lnd_external_address: Optional[LightningAddressHostPort],
peer_address: PeerAddress,
price_msat: int,
) -> Optional[SecretKeyReply]:
if price_msat == 0:
return self.get_free_squeak_secret_key_reply(
squeak_hash,
)
else:
return self.get_offer_reply(
squeak_hash,
lnd_external_address,
peer_address,
price_msat,
)
def get_offer_reply(
self,
squeak_hash: bytes,
lnd_external_address: Optional[LightningAddressHostPort],
peer_address: PeerAddress,
price_msat: int,
) -> Optional[OfferReply]:
sent_offer = self.get_sent_offer_for_peer(
squeak_hash,
peer_address,
price_msat,
)
if sent_offer is None:
return None
try:
offer = self.squeak_core.package_offer(
sent_offer,
lnd_external_address,
)
return OfferReply(
squeak_hash=squeak_hash,
offer=offer,
)
except Exception:
return None
def get_free_squeak_secret_key_reply(self, squeak_hash: bytes) -> Optional[FreeSecretKeyReply]:
secret_key = self.get_squeak_secret_key(squeak_hash)
if secret_key is None:
return None
return FreeSecretKeyReply(
squeak_hash=squeak_hash,
secret_key=secret_key,
)
def pay_offer(self, received_offer_id: int) -> int:
# Get the offer from the database
received_offer = self.squeak_db.get_received_offer(
received_offer_id)
if received_offer is None:
raise Exception("Received offer with id {} not found.".format(
received_offer_id,
))
logger.info("Paying received offer: {}".format(received_offer))
sent_payment = self.squeak_core.pay_offer(received_offer)
sent_payment_id = self.squeak_db.insert_sent_payment(sent_payment)
# # Delete the received offer
# self.squeak_db.delete_offer(sent_payment.payment_hash)
# Mark the received offer as paid
self.squeak_db.set_received_offer_paid(
sent_payment.payment_hash,
True,
)
self.unlock_squeak(
received_offer.squeak_hash,
sent_payment.secret_key,
)
return sent_payment_id
def get_interested_locator(self) -> CSqueakLocator:
block_range = self.get_interested_block_range()
followed_public_keys = self.get_followed_public_keys()
if len(followed_public_keys) == 0:
return CSqueakLocator(
vInterested=[],
)
interests = [
CInterested(
pubkeys=followed_public_keys,
nMinBlockHeight=block_range.min_block,
nMaxBlockHeight=block_range.max_block,
)
]
return CSqueakLocator(
vInterested=interests,
)
def get_interested_block_range(self) -> BlockRange:
max_block = self.squeak_core.get_best_block_height()
min_block = max(0, max_block - self.interested_block_range_size)
return BlockRange(min_block, max_block)
def get_squeak(self, squeak_hash: bytes) -> Optional[CSqueak]:
return self.squeak_db.get_squeak(squeak_hash)
@ -260,35 +124,14 @@ class SqueakStore:
def delete_squeak(self, squeak_hash: bytes) -> None:
self.squeak_db.delete_squeak(squeak_hash)
def get_sent_offer_for_peer(
self,
squeak_hash: bytes,
peer_address: PeerAddress,
price_msat: int,
) -> Optional[SentOffer]:
# Check if there is an existing offer for the hash/peer_address combination
sent_offer = self.squeak_db.get_sent_offer_by_squeak_hash_and_peer(
def get_sent_offer_by_squeak_hash_and_peer(self, squeak_hash: bytes, peer_address: PeerAddress) -> Optional[SentOffer]:
return self.squeak_db.get_sent_offer_by_squeak_hash_and_peer(
squeak_hash,
peer_address,
)
if sent_offer:
return sent_offer
squeak = self.get_squeak(squeak_hash)
secret_key = self.get_squeak_secret_key(squeak_hash)
if squeak is None or secret_key is None:
return None
try:
sent_offer = self.squeak_core.create_offer(
squeak,
secret_key,
peer_address,
price_msat,
)
except Exception:
logger.exception("Failed to create offer.")
return None
self.squeak_db.insert_sent_offer(sent_offer)
return sent_offer
def save_sent_offer(self, sent_offer: SentOffer) -> int:
return self.squeak_db.insert_sent_offer(sent_offer)
def create_signing_profile(self, profile_name: str) -> int:
squeak_profile = create_signing_profile(
@ -517,21 +360,7 @@ class SqueakStore:
last_entry,
)
def save_received_offer(self, offer: Offer, peer_address: PeerAddress) -> Optional[int]:
squeak = self.get_squeak(offer.squeak_hash)
secret_key = self.get_squeak_secret_key(offer.squeak_hash)
if squeak is None or secret_key is not None:
return None
try:
# TODO: Call unpack_offer with check_payment_point=True.
received_offer = self.squeak_core.unpack_offer(
squeak,
offer,
peer_address,
)
except Exception:
logger.exception("Failed to save received offer.")
return None
def save_received_offer(self, received_offer: ReceivedOffer) -> Optional[int]:
received_offer_id = self.squeak_db.insert_received_offer(
received_offer)
if received_offer_id is None:

View file

@ -23,6 +23,7 @@ import logging
import threading
from squeaknode.network.network_manager import NetworkManager
from squeaknode.node.network_handler import NetworkHandler
from squeaknode.node.squeak_store import SqueakStore
@ -35,9 +36,11 @@ class UpdateFollowsWorker:
self,
squeak_store: SqueakStore,
network_manager: NetworkManager,
network_handler: NetworkHandler,
):
self.squeak_store = squeak_store
self.network_manager = network_manager
self.network_handler = network_handler
self.stopped = threading.Event()
def start_running(self):
@ -56,5 +59,5 @@ class UpdateFollowsWorker:
self.stopped,
):
logger.debug("Handling update subscriptions event")
locator = self.squeak_store.get_interested_locator()
locator = self.network_handler.get_interested_locator()
self.network_manager.update_local_subscriptions(locator)

View file

@ -22,7 +22,9 @@
import logging
import threading
from typing import Dict
from typing import Optional
from squeaknode.core.squeak_core import SqueakCore
from squeaknode.core.twitter_account_entry import TwitterAccountEntry
from squeaknode.node.squeak_store import SqueakStore
from squeaknode.twitter.twitter_stream import TwitterStream
@ -36,9 +38,11 @@ class TwitterForwarder:
def __init__(
self,
squeak_store: SqueakStore,
squeak_core: SqueakCore,
retry_s: int,
):
self.squeak_store = squeak_store
self.squeak_core = squeak_core
self.retry_s = retry_s
self.lock = threading.Lock()
self.current_tasks: Dict[str, TwitterForwarderTask] = {}
@ -54,6 +58,7 @@ class TwitterForwarder:
for account in self.squeak_store.get_twitter_accounts():
task = TwitterForwarderTask(
self.squeak_store,
self.squeak_core,
account,
self.retry_s,
)
@ -80,10 +85,12 @@ class TwitterForwarderTask:
def __init__(
self,
squeak_store: SqueakStore,
squeak_core: SqueakCore,
twitter_account: TwitterAccountEntry,
retry_s: int,
):
self.squeak_store = squeak_store
self.squeak_core = squeak_core
self.twitter_account = twitter_account
self.retry_s = retry_s
self.stopped = threading.Event()
@ -148,10 +155,9 @@ class TwitterForwarderTask:
return False
def forward_tweet(self, tweet: dict) -> None:
self.squeak_store.make_squeak(
self.make_squeak(
profile_id=self.twitter_account.profile_id,
content_str=tweet['data']['text'],
replyto_hash=None,
)
def handle_tweet(self, tweet: dict):
@ -159,3 +165,31 @@ class TwitterForwarderTask:
"Got tweet: {}".format(tweet))
if self.is_tweet_a_match(tweet):
self.forward_tweet(tweet)
def make_squeak(
self,
profile_id: int,
content_str: str,
) -> Optional[bytes]:
squeak_profile = self.squeak_store.get_squeak_profile(profile_id)
if squeak_profile is None:
raise Exception("Profile with id {} not found.".format(
profile_id,
))
squeak, secret_key = self.squeak_core.make_squeak(
squeak_profile,
content_str,
)
block_header = self.squeak_core.get_block_header(squeak)
inserted_squeak_hash = self.squeak_store.save_squeak(
squeak,
block_header,
)
if inserted_squeak_hash is None:
return None
self.squeak_store.unlock_squeak(
inserted_squeak_hash,
secret_key,
content_str,
)
return inserted_squeak_hash

View file

@ -24,7 +24,6 @@ import pytest
from sqlalchemy import create_engine
from squeaknode.core.lightning_address import LightningAddressHostPort
from squeaknode.core.squeak_core import SqueakCore
from squeaknode.db.squeak_db import SqueakDb
from squeaknode.node.squeak_store import SqueakStore
@ -42,11 +41,6 @@ def squeak_db(db_engine):
yield db
@pytest.fixture
def squeak_core():
return mock.Mock(spec=SqueakCore)
@pytest.fixture
def lightning_host_port():
return LightningAddressHostPort(host="my_lightning_host", port=8765)
@ -82,53 +76,33 @@ def sent_offer_retention_s():
return 7200
@pytest.fixture
def interested_block_range_size():
return 2016
@pytest.fixture
def inserted_signing_profile_id(squeak_db, signing_profile):
yield squeak_db.insert_profile(signing_profile)
# @pytest.fixture
# def inserted_received_offer_id(squeak_db, received_offer, creation_date):
# with mock.patch.object(SqueakDb, 'timestamp_now_ms', new_callable=mock.PropertyMock) as mock_timestamp_ms:
# mock_timestamp_ms.return_value = creation_date / 1000
# yield squeak_db.insert_received_offer(received_offer)
@pytest.fixture
def squeak_store(
squeak_db,
squeak_core,
max_squeaks,
max_squeaks_per_public_key_per_block,
squeak_retention_s,
received_offer_retention_s,
sent_offer_retention_s,
interested_block_range_size,
):
return SqueakStore(
squeak_db,
squeak_core,
max_squeaks,
max_squeaks_per_public_key_per_block,
squeak_retention_s,
received_offer_retention_s,
sent_offer_retention_s,
interested_block_range_size,
)
def test_save_squeak(squeak_store, squeak_db, squeak_core, block_header, squeak):
with mock.patch.object(squeak_core, 'check_squeak', autospec=True) as mock_check_squeak, \
mock.patch.object(squeak_core, 'get_block_header', autospec=True) as mock_get_block_header, \
mock.patch.object(squeak_db, 'insert_squeak', autospec=True) as mock_insert_squeak:
mock_check_squeak.return_value = None
mock_get_block_header.return_value = block_header
squeak_store.save_squeak(squeak)
def test_save_squeak(squeak_store, squeak_db, block_header, squeak):
with mock.patch.object(squeak_db, 'insert_squeak', autospec=True) as mock_insert_squeak:
squeak_store.save_squeak(squeak, block_header)
mock_insert_squeak.assert_called_once_with(squeak, block_header)
@ -179,10 +153,9 @@ def test_get_sent_offer_already_exists(squeak_store, squeak_db, sent_offer):
with mock.patch.object(squeak_db, 'get_sent_offer_by_squeak_hash_and_peer', autospec=True) as mock_get_sent_offer_by_squeak_hash_and_peer:
mock_get_sent_offer_by_squeak_hash_and_peer.return_value = sent_offer
retrieved_sent_offer = squeak_store.get_sent_offer_for_peer(
retrieved_sent_offer = squeak_store.get_sent_offer_by_squeak_hash_and_peer(
sent_offer.squeak_hash,
sent_offer.peer_address,
sent_offer.price_msat,
)
assert retrieved_sent_offer == sent_offer