From 1d6970f28f53c0c6ed34d2e7b6f49386a6b4d2dc Mon Sep 17 00:00:00 2001 From: Tobias Baur <1097224+believethehype@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:50:41 +0200 Subject: [PATCH] send heartbeats, expire all events (heartbeat 5 min, other events 10 min) --- nostr_dvm/dvm.py | 27 ++++++++++++++++++--------- nostr_dvm/utils/definitions.py | 1 + nostr_dvm/utils/dvmconfig.py | 1 + nostr_dvm/utils/heartbeat.py | 22 ++++++++++++++++++++++ nostr_dvm/utils/print_utils.py | 8 ++++++++ setup.py | 6 +++--- tests/generic_dvm.py | 2 +- 7 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 nostr_dvm/utils/heartbeat.py diff --git a/nostr_dvm/dvm.py b/nostr_dvm/dvm.py index c2bef5c..a5b93b0 100644 --- a/nostr_dvm/dvm.py +++ b/nostr_dvm/dvm.py @@ -15,6 +15,7 @@ from nostr_dvm.utils.database_utils import create_sql_table, get_or_add_user, up update_user_subscription from nostr_dvm.utils.definitions import EventDefinitions, RequiredJobToWatch, JobToWatch from nostr_dvm.utils.dvmconfig import DVMConfig +from nostr_dvm.utils.heartbeat import beat from nostr_dvm.utils.mediasource_utils import input_data_file_duration from nostr_dvm.utils.nip88_utils import nip88_has_active_subscription from nostr_dvm.utils.nostr_utils import get_event_by_id, get_referenced_event_by_id, check_and_decrypt_tags, \ @@ -52,6 +53,7 @@ class DVM: self.dvm_config = dvm_config self.admin_config = admin_config self.keys = Keys.parse(dvm_config.PRIVATE_KEY) + self.heartbeat_frequency = 300 relaylimits = RelayLimits.disable() opts = Options().relay_limits(relaylimits) #.difficulty(28) @@ -79,6 +81,7 @@ class DVM: ping_filter= Filter().pubkey(pk).kind(EventDefinitions.KIND_NIP90_PING).since(Timestamp.now()) create_sql_table(self.dvm_config.DB) await admin_make_database_updates(adminconfig=self.admin_config, dvmconfig=self.dvm_config, client=self.client) + await beat(self.dvm_config, self.client, self.heartbeat_frequency ) await self.client.subscribe(dvm_filter, None) await self.client.subscribe(zap_filter, None) await self.client.subscribe(ping_filter, None) @@ -638,8 +641,10 @@ class DVM: if tag.as_vec()[0] == "i": if not encrypted: reply_tags.append(tag) - elif tag.as_vec()[0] == "expiration": - reply_tags.append(tag) + + + expiration_tag = Tag.parse(["expiration", str(Timestamp.now().as_secs() + dvm_config.EXPIRATION_DURATION)]) + reply_tags.append(expiration_tag) if encrypted: encryption_tags.append(p_tag) @@ -708,15 +713,12 @@ class DVM: encrypted = False is_legacy_encryption = False - expiration_tag = None for tag in original_event.tags().to_vec(): if tag.as_vec()[0] == "encrypted": encrypted = True encrypted_tag = Tag.parse(["encrypted"]) encryption_tags.append(encrypted_tag) #_, is_legacy_encryption = check_and_decrypt_tags(original_event, dvm_config) - elif tag.as_vec()[0] == "expiration": - expiration_tag = tag if encrypted: encryption_tags.append(p_tag) @@ -797,8 +799,10 @@ class DVM: else: content = reaction - if expiration_tag is not None: - reply_tags.append(expiration_tag) + + + expiration_tag = Tag.parse(["expiration", str(Timestamp.now().as_secs() + dvm_config.EXPIRATION_DURATION)]) + reply_tags.append(expiration_tag) keys = Keys.parse(dvm_config.PRIVATE_KEY) reaction_event = EventBuilder(EventDefinitions.KIND_FEEDBACK, str(content)).tags(reply_tags).sign_with_keys(keys) @@ -941,10 +945,14 @@ class DVM: asyncio.create_task(self.client.handle_notifications(NotificationHandler())) try: - + heartbeatsignal = 0 + sleep = 1 while self.stop_thread is False: for dvm in self.dvm_config.SUPPORTED_DVMS: await dvm.schedule(self.dvm_config) + if heartbeatsignal >= self.heartbeat_frequency : + heartbeatsignal = 0 + await beat(self.dvm_config, self.client, self.heartbeat_frequency ) for job in self.job_list: if job.bolt11 != "" and job.payment_hash != "" and not job.payment_hash is None and not job.is_paid: @@ -979,7 +987,8 @@ class DVM: if Timestamp.now().as_secs() > job.timestamp + 60 * 20: # remove jobs to look for after 20 minutes.. self.jobs_on_hold_list.remove(job) - await asyncio.sleep(1) + await asyncio.sleep(sleep) + heartbeatsignal += sleep except BaseException: print("end") diff --git a/nostr_dvm/utils/definitions.py b/nostr_dvm/utils/definitions.py index 80c899b..ca381ab 100644 --- a/nostr_dvm/utils/definitions.py +++ b/nostr_dvm/utils/definitions.py @@ -67,6 +67,7 @@ class EventDefinitions: KIND_ZAP = Kind(9735) KIND_RELAY_ANNOUNCEMENT = Kind(10002) KIND_ANNOUNCEMENT = Kind(31990) + KIND_HEARTBEAT = Kind(11998) KIND_WIKI = Kind(30818) KIND_LONGFORM = Kind(30023) KIND_NIP88_TIER_EVENT = Kind(37001) diff --git a/nostr_dvm/utils/dvmconfig.py b/nostr_dvm/utils/dvmconfig.py index 53b04da..c45917b 100644 --- a/nostr_dvm/utils/dvmconfig.py +++ b/nostr_dvm/utils/dvmconfig.py @@ -70,6 +70,7 @@ class DVMConfig: CUSTOM_PROCESSING_MESSAGE = None LOGLEVEL = LogLevel.INFO KIND = None + EXPIRATION_DURATION = 600 DVM_KEY = None CHATBOT = None diff --git a/nostr_dvm/utils/heartbeat.py b/nostr_dvm/utils/heartbeat.py new file mode 100644 index 0000000..22c598c --- /dev/null +++ b/nostr_dvm/utils/heartbeat.py @@ -0,0 +1,22 @@ +from nostr_dvm.utils.definitions import EventDefinitions +from nostr_dvm.utils.nostr_utils import send_event +from nostr_dvm.utils.print_utils import bcolors +from nostr_sdk import Tag, Keys, EventBuilder, Timestamp + + +async def beat(dvm_config, client, frequency=300): + status_tag = Tag.parse(["status", "My heart keeps beating like a hammer"]) + d_tag = Tag.parse(["d", dvm_config.NIP89.DTAG]) + expiration_tag = Tag.parse(["expiration", str(Timestamp.now().as_secs() + frequency)]) + + tags = [status_tag, d_tag, expiration_tag] + keys = Keys.parse(dvm_config.NIP89.PK) + content = "Alive and kicking" + + event = EventBuilder(EventDefinitions.KIND_HEARTBEAT, content).tags(tags).sign_with_keys(keys) + + response_status = await send_event(event, client=client, dvm_config=dvm_config, broadcast=True) + + + print(bcolors.BRIGHT_RED + "[" + dvm_config.NIP89.NAME + "] Sent heartbeat for " + dvm_config.NIP89.NAME + ". Success: " + str(response_status.success) + " Failed: " + str(response_status.failed) + " EventID: " + + response_status.id.to_hex() + " / " + response_status.id.to_bech32()) diff --git a/nostr_dvm/utils/print_utils.py b/nostr_dvm/utils/print_utils.py index dd2a736..2c97a6e 100644 --- a/nostr_dvm/utils/print_utils.py +++ b/nostr_dvm/utils/print_utils.py @@ -12,4 +12,12 @@ class bcolors: MAGENTA = '\033[95m' GREY = '\033[90m' BLACK = '\033[90m' + BRIGHT_BLACK = '\033[90m' + BRIGHT_RED = '\033[91m' + BRIGHT_GREEN = '\033[92m' + BRIGHT_YELLOW = '\033[93m' + BRIGHT_BLUE = '\033[94m' + BRIGHT_MAGENTA = '\033[95m' + BRIGHT_CYAN = '\033[96m' + BRIGHT_WHITE = '\033[97m' DEFAULT = '\033[99m' diff --git a/setup.py b/setup.py index c14fc15..b233aaa 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup, find_packages -VERSION = '1.1.2' +VERSION = '1.1.3' DESCRIPTION = 'A framework to build and run Nostr NIP90 Data Vending Machines' LONG_DESCRIPTION = ('A framework to build and run Nostr NIP90 Data Vending Machines. See the github repository for more information') @@ -17,7 +17,7 @@ setup( install_requires=["nostr-sdk==0.39.0", "bech32==1.2.0", "pycryptodome==3.20.0", - "yt-dlp==2024.11.04", + "yt-dlp==2025.7.21", "python-dotenv==1.0.0", "emoji==2.12.1", "ffmpegio==0.9.1", @@ -27,7 +27,7 @@ setup( "requests==2.32.3", "moviepy==2.0.0", "zipp==3.19.1", - "urllib3==2.2.2", + "urllib3==2.5.0", "networkx==3.3", "scipy==1.13.1", "typer==0.15.1", diff --git a/tests/generic_dvm.py b/tests/generic_dvm.py index 1b8d481..f06b841 100644 --- a/tests/generic_dvm.py +++ b/tests/generic_dvm.py @@ -51,7 +51,7 @@ def playground(announce=False): return result dvm.process = process # overwrite the process function with the above one - dvm.run(True) + dvm.run() if __name__ == '__main__':