diff --git a/exchange.py b/exchange.py index 3a92a4cdc4b90024243f33465dd4ee074d481941..58b747e9ca0849cccd7f3323480598ed5053cc44 100644 --- a/exchange.py +++ b/exchange.py @@ -1,22 +1,25 @@ -#from participant import ureg, Q_ +from pint import UnitRegistry import time from datetime import datetime, timedelta -class Trade: - pass - - class Exchange: - def __init__(self, pk: bytes, p: float, ppw: float): + def __init__(self, pk: bytes, p: float, ppw: float, extime: datetime): + self.ureg = None + self.Q_ = None self.pubicKey: bytes = pk self.timestamp: int = time.time_ns() + self.executiontime: datetime = extime self.__isActive: bool = False - self.power = Q_(p, ureg.watt) - self.__pricePerWatt = ppw # this is in EUR, no new cryptocurrency should be created for this - self.__trades: set[Trade] = set() + self.power = p + self.pricePerWatt = ppw # this is in EUR, no new cryptocurrency should be created for this self.__sig: bytes + def set_ureg(self, ureg: UnitRegistry) -> None: + self.ureg = ureg + self.Q_ = ureg.Quantity + self.power = self.Q_(self.power, ureg.watt) + def activate(self) -> None: self.__isActive = True @@ -34,7 +37,7 @@ class Exchange: end_of_minute = start_of_minute + timedelta(seconds=59) end_of_minute_unix = int(end_of_minute.timestamp()) - if start_of_minute_unix <= self.timestamp <= end_of_minute_unix: + if start_of_minute_unix <= self.executiontime.timestamp() <= end_of_minute_unix: return True else: return False @@ -51,11 +54,11 @@ class Exchange: end_of_minute = start_of_minute + timedelta(seconds=59) end_of_minute_unix = int(end_of_minute.timestamp()) - if start_of_minute_unix <= self.timestamp <= end_of_minute_unix: + if start_of_minute_unix <= self.executiontime.timestamp() <= end_of_minute_unix: return True else: return False def is_old(self) -> bool: now = datetime.now() - return self.timestamp < int(now.timestamp()) + return self.executiontime.timestamp() < int(now.timestamp()) diff --git a/line.py b/line.py index aaf9bddfa9ec3463aefad7554466692d2b18795d..c0fa8e4f0449004710f2d463bb18e54d7fbd2179 100644 --- a/line.py +++ b/line.py @@ -16,7 +16,7 @@ import logging from connection import Connection from remotetransformer import RemoteTransformer from remoteparticipant import RemoteParticipant -from remoteline import RemoteLine +from remoteline import RemoteLine, SignedRemoteLine logger = logging.getLogger(__name__) @@ -25,8 +25,10 @@ class Line(Connection): def __init__(self, cap: float, conips: set[ipaddress.IPv4Address], sched: AsyncIOScheduler): super().__init__(cap, conips, sched) self.adjacentConnections: set[RemoteTransformer | RemoteParticipant] = set() + self.lastSignedLine: dict[datetime.datetime, SignedRemoteLine] = dict() # adding fastapi endpoints self.fastRouter.add_api_route("/asRemoteJSON", self.asRemoteLineJSON, methods=["GET"]) + self.fastRouter.add_api_route("/sign/{time}", self.sign, methods=["POST"]) # setting up scheduling run_date = datetime.datetime.now() + datetime.timedelta(hours=1, seconds=5) # +1 hour because timezones suck hard @@ -47,6 +49,29 @@ class Line(Connection): self.usedCapacity, self.adjacentConnections, self.loss) return jsonpickle.encode(rl) + async def sign(self, time: datetime.datetime, rljson): + rl = jsonpickle.decode(rljson) + if rl.publicKey == self.publicKey: # check if the rl actually is me + time = time.replace(second=0, microsecond=0) # we are scheduling in minute intervals + if time not in self.lastSignedLine.keys(): + origin = SignedRemoteLine( + RemoteLine(self.publicIP, self.publicKey, self.availableCapacity, + self.usedCapacity, self.adjacentConnections, self.loss), None) + origin.isOrigin = True + origin.signature = self.dil.sign_with_input(self.__secretKey, origin.__str__()) + result = SignedRemoteLine(rl, origin) + result.signature = self.dil.sign_with_input(self.__secretKey, result.__str__()) + self.lastSignedLine[time] = result + return jsonpickle.encode(result) + else: + # if there has been a route announced before, add the previous to the new one + result = SignedRemoteLine(rl, self.lastSignedLine[time]) + result.signature = self.dil.sign_with_input(self.__secretKey, result.__str__()) + self.lastSignedLine[time] = result + return jsonpickle.encode(result) + else: + return "Unauthorized" # better handling here would be nice + if __name__ == "__main__": parser = argparse.ArgumentParser(description='Connection service') diff --git a/main.py b/main.py index 4f65d7774206b80ec14dffcb7c14e33e43978d9a..c8d3eef9466271ef1a2a79302fb79a2fcd71c3de 100644 --- a/main.py +++ b/main.py @@ -27,10 +27,10 @@ import participant import DHTdummy import rustworkx as rx +from rustworkx.visit import DijkstraVisitor import matplotlib.pyplot as plt from rustworkx.visualization import mpl_draw - ureg = UnitRegistry() Q_ = ureg.Quantity @@ -52,6 +52,36 @@ def get_ip(): return IP +class CustomDijkstraVisitor(DijkstraVisitor): + def __init__(self): + super().__init__() + self.examined_edges = set() + + def discover_vertex(self, vertex, distance): + print(f"Discovered vertex: {vertex} with distance: {distance}") + + def examine_edge(self, edge): + for e in self.examined_edges: + if edge[0] == e[1] and edge[1] == e[0]: + print(f"Edge already examined: {edge}") + return + + print(f"Examining edge: {edge}") + self.examined_edges.add(edge) + + +# Create a new graph +graph = rx.PyGraph() +graph.add_nodes_from([0, 1, 2, 3]) +graph.add_edges_from([(0, 1, 1.0), (1, 2, 2.0), (2, 3, 1.0)]) + +# Create an instance of the custom visitor +visitor = CustomDijkstraVisitor() + +# Perform Dijkstra's search +rx.dijkstra_search(graph, [0], lambda _: 1.0, visitor) + +''' class GraphNode: def __init__(self, value): self.index = None @@ -87,11 +117,20 @@ for edge in graph.edges(): print(edge) plt.clf() -mpl_draw(graph, with_labels=True) +mpl_draw(graph, with_labels=True, edge_labels=str, labels=str) +path = rx.dijkstra_shortest_paths(graph, 3, 5, lambda e: e.value) length = rx.dijkstra_shortest_path_lengths(graph, 2, lambda e: e.value, 6) print(length) plt.show() + +route = graph.subgraph(path[5]) + +plt.clf() +mpl_draw(route, with_labels=True, edge_labels=str, labels=str) +plt.show() +''' + # --------------- Examples ----------------------- diff --git a/offer.py b/offer.py index 8cd5d55c38fc6ad3daad6c92c77558b8e42df832..5fc8f302935204a1dd7d8318dfda8e91ba3a3609 100644 --- a/offer.py +++ b/offer.py @@ -1,6 +1,7 @@ import exchange +from datetime import datetime class Offer(exchange.Exchange): - def __init__(self, pk: bytes, p: float, ppw: float): - exchange.Exchange.__init__(self, pk, p, ppw) + def __init__(self, pk: bytes, p: float, ppw: float, extime: datetime): + exchange.Exchange.__init__(self, pk, p, ppw, extime) diff --git a/participant.py b/participant.py index 3b9a8ad2e8101bc9faaf9ae071702757fb4db0ef..e032d1c9572dc5ade7f396938633abd2e0981038 100644 --- a/participant.py +++ b/participant.py @@ -14,6 +14,7 @@ import argparse import rustworkx as rx from rustworkx.visualization import mpl_draw +from rustworkx.visit import DijkstraVisitor import base64 from io import BytesIO from fastapi.responses import HTMLResponse @@ -27,8 +28,9 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from dilithium import Dilithium import dilithium.dilithium import numpy -from util import get_pub_ip +from util import get_pub_ip, next_minute +from route import Route from offer import Offer from request import Request from exchange import Exchange @@ -101,7 +103,7 @@ class Participant: # set up scheduling for tasks self.scheduler.add_job(self.request_remoteparticipants, 'interval', seconds=15, id='1', name='requestRemoteParticipants') - self.scheduler.add_job(self.updateCurrentPower, 'interval', seconds=2, id='2', name='updateCurrentPower') + self.scheduler.add_job(self.updateCurrentPower, 'interval', seconds=5, id='2', name='updateCurrentPower') logger.info("~~~>Finished setup on " + self.publicIP.__str__()) @@ -140,14 +142,17 @@ class Participant: all_remoteparticipants.update(self.remoteParticipants) return jsonpickle.encode(all_remoteparticipants) - def update_remoteparticipants(self, new_rps: set[RemoteParticipant], target_rps: set[RemoteParticipant]): + def update_remoteparticipants(self, new_rps: set[RemoteParticipant]): for new_rp in new_rps: - for rp in target_rps: - if new_rp == rp: # found same participant - rp.update_nextExchanges(new_rp.availableExchanges) - rp.timestamp = new_rp.timestamp - else: # participant is new - target_rps.add(new_rp) + if new_rp == self.as_remote_participant(): # found self + continue + if new_rp in self.remoteParticipants: # found same participant + for rp in self.remoteParticipants: + if rp == new_rp: + rp.update_nextExchanges(new_rp.availableExchanges) + rp.timestamp = new_rp.timestamp + else: # participant is new + self.remoteParticipants.add(new_rp) async def calc_distance(self, gridnodeindex: int) -> float: distdict = rx.dijkstra_shortest_path_lengths(self.grid, gridnodeindex, lambda l: l.loss, @@ -176,7 +181,7 @@ class Participant: self.gridEdgeIndexes[rline.publicKey].add(i_edge) # this is actually stupid, when the distance changes based on the load of the line # we have to calculate this a when matching exchanges - # but as a first step to get an order in which to contact participants this is fine + # but as a first step to get an order in which to contact to participants, this is fine await self.add_distance_to_participants(rp.publicKey, await self.calc_distance(i)) break @@ -209,7 +214,7 @@ class Participant: logger.error("httpx Error: " + err.__str__()) continue - self.update_remoteparticipants(all_remoteparticipants, self.remoteParticipants) + self.update_remoteparticipants(all_remoteparticipants) await self.insert_participants_into_grid(all_remoteparticipants) async def get_knownIPs(self): @@ -293,39 +298,89 @@ class Participant: for t in self.ExchangeQueue: # only produce a new exchange if there is no exchange for the next minute if t.is_min_next(): ex_next_min = True - break if power > Q_(0, ureg.watt) and not ex_next_min: # more energy will be available than needed, so we can create an offer - o = Offer(self.publicKey, power, 0.3) # price should be dynamic, no time :D + # for now all offers will be at the same price, in a real world scenario this would be dynamic + # additionally, all offers will be for the next minute, when the system is able to take variable + # consumers (e.g. electric cars and heat pumps) into account, this can be adapted to request + # bigger amounts of energy in a foreseeable fashion + o = Offer(self.publicKey, power, 0.3, next_minute()) # price should be dynamic, no time :D self.ExchangeQueue.append(o) logger.info("===> created offer on " + self.publicIP.__str__()) else: if power < Q_(0, ureg.watt) and not ex_next_min: # more energy is needed than available, so we create a request - r = Request(self.publicKey, power, 0.3) + # for now all requests will be at the same price, in a real world scenario this would be dynamic + # additionally, all requests will be for the next minute, when the system is able to take variable + # consumers (e.g. electric cars and heat pumps) into account, this can be adapted to request + # bigger amounts of energy in a foreseeable fashion + r = Request(self.publicKey, power, 0.3, next_minute()) self.ExchangeQueue.append(r) logger.info("===> created request on " + self.publicIP.__str__()) + async def findRoute(self, start: RemoteParticipant, end: RemoteParticipant) -> Route: + s = self.gridNodeIndexes[start.publicKey] + e = self.gridNodeIndexes[end.publicKey] + path = rx.dijkstra_shortest_paths(self.grid, s, e, lambda l: l.loss) + routeloss = rx.dijkstra_shortest_path_lengths(self.grid, s, lambda l: l.loss, e) + graph = self.grid.subgraph(path[e]) + return Route(start, end, graph, routeloss) + + async def announceRoute(self, t: Trade): + class GridVisitor(DijkstraVisitor): + def __init__(self, trade: Trade): + super().__init__() + self.examined_edges = set() + self.trade: Trade = trade + + def discover_vertex(self, vertex, distance): + logger.info(f"Discovered Trafo: {vertex}") + + + def examine_edge(self, edge): + for e in self.examined_edges: + if edge[0] == e[1] and edge[1] == e[0]: + logger.info(f"Line already examined: {edge}") + return + + logger.info(f"Examining Line: {edge}") + self.examined_edges.add(edge) + + visitor = GridVisitor(t) + # walk the route and announce the route to all grid elements involved + rx.dijkstra_search(t.route.graph, [self.gridNodeIndexes[t.route.start.publicKey]], lambda _: 1.0, visitor) + async def findOffer(self, discarded_rps=frozenset()): closest = self.as_remote_participant() # this is just to have a rp to compare to for rp in self.remoteParticipants: if rp.distance < closest.distance and rp not in discarded_rps: + logger.info("===> found closer participant: " + rp.publicIP.__str__()) closest = rp if closest != self.as_remote_participant(): # check if there is actually someone async with httpx.AsyncClient() as client: response = await client.get("http://" + closest.publicIP.__str__() + ":8000/getOffersJSON") - offers = jsonpickle.decode(response.json()) + offers: list[Offer] = jsonpickle.decode(response.json()) for o in offers: + o.set_ureg(ureg) # update the ureg of the offer for pint to work if o.is_min_next(): # once again, only trying to find an offer for the next minute - # TODO: now check if power is sufficient, for this solve pint ureg issue - logger.info("===> found offer on " + closest.publicIP.__str__()) - - - + if o.power >= self.ExchangeQueue[0].power: + # the offer is big enough to fulfill the request + # now find the route to the offering participant + r = await self.findRoute(self.as_remote_participant(), closest) + logger.info("===> found route to " + closest.publicIP.__str__()) + # create a trade, include offer and request, use power from request since we ensured that + # the offer is big enough, use price from offer as this is the price that will be paid + # and again, only trades for the next minute are possible at this point + t = Trade(o, self.ExchangeQueue[0], self.ExchangeQueue[0].power, o.pricePerWatt, r, + next_minute()) + t.set_ureg(ureg) + # now announce the route to all connections involved + await self.announceRoute(t) + logger.info("===> found offer on " + closest.publicIP.__str__()) else: logger.info("===> no one to trade with on " + self.publicIP.__str__()) return @@ -339,14 +394,16 @@ class Participant: self.__availablePower = self.currentInHouseSupply - self.__currentInHouseDemand # simulate some random fluctuations self.__currentInHouseDemand = self.__currentInHouseDemand + Q_(uniform(-0.1, 0.1), ureg.watt) - t = Q_(2, ureg.second) # this is run every 2 seconds + t = Q_(5, ureg.second) # this is run every 5 seconds t.ito(ureg.hour) self.__energyOffset += self.__availablePower * t logger.info(">>>> available power: " + self.__availablePower.__str__() + " and energy offset " + self.__energyOffset.__str__() + " on " + self.publicIP.__str__()) await self.produceNextTrade() - if isinstance(self.ExchangeQueue[0], Request): # see if there is a request for power in front of the queue - await self.findOffer() + if len(self.ExchangeQueue) > 0: + if isinstance(self.ExchangeQueue[0], Request): # see if there is a request for power in front of the queue + logger.info("===> trying to find an offer on " + self.publicIP.__str__()) + await self.findOffer() class Weather: @@ -358,7 +415,7 @@ class Weather: self.participant = p self.scheduler = schd - run_date = datetime.now() + timedelta(hours=1, seconds=2) # +1 hour because timezones suck hard + run_date = datetime.now() + timedelta(hours=1, seconds=1) # +1 hour because timezones suck hard self.scheduler.add_job(self.setCurrentWeather, 'date', run_date=run_date, id='100', ) self.scheduler.add_job(self.changeWeather, 'interval', seconds=300, id='101', name='changeWeather') diff --git a/remoteconnection.py b/remoteconnection.py index db3400bc71ce73b71c019362685a1e5748d15173..5feb406a3b0f2fa1233271ba5d964a95ff840389 100644 --- a/remoteconnection.py +++ b/remoteconnection.py @@ -3,11 +3,11 @@ import time class RemoteConnection: - def __init__(self, ip: ipaddress.IPv4Address, pk: bytes, cap: float, ucap: float, l): + def __init__(self, ip: ipaddress.IPv4Address, pk: bytes, cap: float, ucap: float, l: float): self.publicIP: ipaddress.IPv4Address = ip self.publicKey: bytes = pk - self.__availableCapacity: float = cap - self.__usedCapacity: float = ucap + self.availableCapacity: float = cap + self.usedCapacity: float = ucap self.loss = l # loss rate during transmission or transformation self.timestamp = time.time_ns() diff --git a/remoteline.py b/remoteline.py index 1b31a3314974ffffefe0fbc869749da926b2bd0a..1a1b6228a25688454bd5034fea847b167e451a8b 100644 --- a/remoteline.py +++ b/remoteline.py @@ -9,3 +9,12 @@ class RemoteLine(remoteconnection.RemoteConnection): cons, ll): super().__init__(ip, pk, cap, ucap, ll) self.adjacentConnections = cons + + +class SignedRemoteLine: + def __init__(self, line: RemoteLine, prev: 'SignedRemoteLine'): + self.line = line + self.previous: SignedRemoteLine = prev + self.timestamp = time.time_ns() + self.isOrigin = False + self.signature: bytes = bytes(0) diff --git a/remoteparticipant.py b/remoteparticipant.py index 52e6b10ebce36c33c02572748b0d120d3b409e7c..55eebbc086cf32f27fd285979806a016693777c4 100644 --- a/remoteparticipant.py +++ b/remoteparticipant.py @@ -1,10 +1,8 @@ import ipaddress +import sys import time from datetime import datetime, timedelta -import numpy -from pydantic.dataclasses import dataclass - import exchange import remoteline @@ -18,14 +16,14 @@ class RemoteParticipant: self.adjacentLines: set[remoteline.RemoteLine] = adjlines self.timestamp = time.time_ns() # a participant will calc the distance in electric loss to this participant when inserting - self.distance: float = numpy.iinfo(float).max + self.distance: float = sys.float_info.max def update_nextExchanges(self, exchanges: list[exchange.Exchange]): for ex in self.availableExchanges: # clear all old exchanges if ex.is_old(): self.availableExchanges.remove(ex) - self.availableExchanges = self.availableExchanges.extend(exchanges) + self.availableExchanges.extend(exchanges) def is_min_now(self) -> bool: now = datetime.now() diff --git a/remotetransformer.py b/remotetransformer.py index cb5db4202ad832eb42d0937b8992f31572f3e3b5..7794e0e21667823c8a13b49638e20c76389ee035 100644 --- a/remotetransformer.py +++ b/remotetransformer.py @@ -10,3 +10,17 @@ class RemoteTransformer(remoteconnection.RemoteConnection): cons: set['remoteline.RemoteLine'], ll): super().__init__(ip, pk, cap, ucap, ll) self.adjacentLines: set[remoteline.RemoteLine] = cons + + +class SignedRemoteTransformer: + def __init__(self, transformer: RemoteTransformer, prev: 'SignedRemoteTransformer'): + self.transformer = transformer + self.previous: SignedRemoteTransformer = prev + self.timestamp = time.time_ns() + self.isOrigin = False + self.signature: bytes = bytes(0) + + def __str__(self): + return self.transformer.publicIP.__str__() + " " + self.transformer.publicKey.hex() + " " + str( + self.transformer.availableCapacity) + " " + str(self.transformer.usedCapacity) + " " + str( + self.transformer.loss) + " " + str(self.timestamp) + " " + str(self.isOrigin) diff --git a/request.py b/request.py index 805c613b07ff20a9bb9e5ddc23c59f836e075e01..38a58e0ccc78d8126277442abfb87c125f311a10 100644 --- a/request.py +++ b/request.py @@ -1,6 +1,7 @@ import exchange +from datetime import datetime class Request(exchange.Exchange): - def __init__(self, pk: bytes, p: float, ppw: float): - exchange.Exchange.__init__(self, pk, p, ppw) + def __init__(self, pk: bytes, p: float, ppw: float, extime: datetime): + exchange.Exchange.__init__(self, pk, p, ppw, extime) diff --git a/route.py b/route.py index 953ca344544ccab4882cd1c6cd8ad1ab63c0ff35..f08628f9965ad4aa8427239fc856eb961ef1871a 100644 --- a/route.py +++ b/route.py @@ -5,8 +5,8 @@ import rustworkx as rx class Route: - def __init__(self, start: remoteparticipant, end: remoteparticipant): - self.__start = start - self.__end = end - self.__route = rx.PyGraph() - self.routeloss = 0.0 + def __init__(self, start: remoteparticipant, end: remoteparticipant, g: rx.PyGraph, rl: float): + self.start: remoteparticipant = start + self.end: remoteparticipant = end + self.graph = g + self.routeloss = rl diff --git a/trade.py b/trade.py index 1d5d2e15105f9023f055a915b3f3eb9a265fee68..a47a9090d7c692667c1d6ddab02ed7c0f9912b26 100644 --- a/trade.py +++ b/trade.py @@ -1,8 +1,8 @@ -# from main import ureg,Q_ -import locale import time import datetime +from pint import UnitRegistry + import offer import request import route @@ -11,12 +11,19 @@ import route class Trade: def __init__(self, off: offer.Offer, req: request.Request, pwr: float, ppw: float, rou: route.Route, extime: datetime.datetime): + self.ureg = None + self.Q_ = None self.__timestamp = time.time_ns() self.__offer: offer.Offer = off self.__request: request.Request = req - self.__route = rou - # self.__power = Q_(pwr, ureg.watt) - self.__pricePerWatt = locale.currency(ppw) + self.route = rou + self.power = pwr + self.__pricePerWatt = ppw self.__executionTime: datetime.datetime = extime self.sig_off: bytes self.sig_req: bytes + + def set_ureg(self, ureg: UnitRegistry) -> None: + self.ureg = ureg + self.Q_ = ureg.Quantity + self.power = self.Q_(self.power, ureg.watt) diff --git a/transformer.py b/transformer.py index 3dd9ed68efd82be04f0641ae209f1bbdd5cc76df..fff67ddc22f5d5f1c7255e5b4d4668fbbe6cc695 100644 --- a/transformer.py +++ b/transformer.py @@ -14,7 +14,7 @@ from util import get_pub_ip import logging from connection import Connection -from remotetransformer import RemoteTransformer +from remotetransformer import RemoteTransformer, SignedRemoteTransformer from remoteline import RemoteLine logger = logging.getLogger(__name__) @@ -24,8 +24,10 @@ class Transformer(Connection): def __init__(self, cap: float, conips: set[ipaddress.IPv4Address], sched: AsyncIOScheduler): super().__init__(cap, conips, sched) self.adjacentLines: set[RemoteLine] = set() + self.lastSignedTransformer: dict[datetime.datetime, SignedRemoteTransformer] = dict() # adding fastapi endpoints self.fastRouter.add_api_route("/asRemoteJSON", self.asRemoteTransformerJSON, methods=["GET"]) + self.fastRouter.add_api_route("/sign/{time}", self.sign, methods=["POST"]) # setting up scheduling run_date = datetime.datetime.now() + datetime.timedelta(hours=1, seconds=5) # +1 hour because timezones suck hard @@ -46,6 +48,29 @@ class Transformer(Connection): self.adjacentLines.update(result) logger.info("===> Transformer: " + self.publicIP.__str__() + " retrieved connections: " + len(result).__str__()) + async def sign(self, time: datetime.datetime, rtjson): + rt = jsonpickle.decode(rtjson) + if rt.publicKey == self.publicKey: # check if the rt actually is me + if time not in self.lastSignedTransformer.keys(): + # has there been no route announced before this one? then create an origin node for the trust chain + origin = SignedRemoteTransformer( + RemoteTransformer(self.publicIP, self.publicKey, self.availableCapacity, + self.usedCapacity, self.adjacentLines, self.loss), None) + origin.isOrigin = True + origin.signature = self.dil.sign_with_input(self.__secretKey, origin.__str__()) + result = SignedRemoteTransformer(rt, origin) + result.signature = self.dil.sign_with_input(self.__secretKey, result.__str__()) + self.lastSignedTransformer[time] = result + return jsonpickle.encode(result) + else: + # if there has been a route announced before, add the previous to the new one + result = SignedRemoteTransformer(rt, self.lastSignedTransformer[time]) + result.signature = self.dil.sign_with_input(self.__secretKey, result.__str__()) + self.lastSignedTransformer[time] = result + return jsonpickle.encode(result) + else: + return "Unauthorized" # better handling here would be nice + if __name__ == "__main__": parser = argparse.ArgumentParser(description='Connection service') diff --git a/util.py b/util.py index 5df2296ca207e54c4a7ebed39e28c60b1a1a714f..cbc70944d7902ffc227652f0fd036669d2eb31d4 100644 --- a/util.py +++ b/util.py @@ -1,4 +1,5 @@ import socket +from datetime import datetime, timedelta def get_pub_ip(): @@ -12,4 +13,14 @@ def get_pub_ip(): ip = '127.0.0.1' finally: s.close() - return ip \ No newline at end of file + return ip + + +def next_minute() -> datetime: + now = datetime.now() + + # Calculate the first second of the next minute + start_of_minute = datetime(now.year, now.month, now.day, now.hour, now.minute, 0) + start_of_minute += timedelta(minutes=1) + + return start_of_minute