diff --git a/docker-compose.yaml b/docker-compose.yaml index e073f704d6d122f3fbede383a0aea723104e307b..bd8d29be1705691bcfc39ccea8e71855560097d8 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -128,7 +128,7 @@ services: build: context: . dockerfile: /home/jonny0815/git/dismagr/participant/dockerfile - command: [ "python", "./participant.py", "--con", "172.20.0.105", "--cc", "0.2", "--sh", "0.8", "--dmd", "300", "--solar", "4000"] + command: [ "python", "./participant.py", "--con", "172.20.0.101", "--cc", "0.2", "--sh", "0.8", "--dmd", "1000", "--solar", "4000" ] part13: networks: network1: @@ -138,4 +138,94 @@ services: build: context: . dockerfile: /home/jonny0815/git/dismagr/participant/dockerfile - command: [ "python", "./participant.py", "--con", "172.20.0.106", "--cc", "0.7", "--sh", "0.7", "--dmd", "800", "--solar", "4000"] \ No newline at end of file + command: [ "python", "./participant.py", "--con", "172.20.0.101", "--cc", "0.2", "--sh", "0.8", "--dmd", "1000", "--solar", "4000" ] + part14: + networks: + network1: + ipv4_address: 172.20.0.14 + ports: + - 8014:8000 + build: + context: . + dockerfile: /home/jonny0815/git/dismagr/participant/dockerfile + command: [ "python", "./participant.py", "--con", "172.20.0.101", "--cc", "0.2", "--sh", "0.8", "--dmd", "1000", "--solar", "4000" ] + part15: + networks: + network1: + ipv4_address: 172.20.0.15 + ports: + - 8015:8000 + build: + context: . + dockerfile: /home/jonny0815/git/dismagr/participant/dockerfile + command: [ "python", "./participant.py", "--con", "172.20.0.101", "--cc", "0.2", "--sh", "0.8", "--dmd", "1000", "--solar", "4000" ] + part21: + networks: + network1: + ipv4_address: 172.20.0.21 + ports: + - 8021:8000 + build: + context: . + dockerfile: /home/jonny0815/git/dismagr/participant/dockerfile + command: [ "python", "./participant.py", "--con", "172.20.0.105", "--cc", "0.2", "--sh", "0.8", "--dmd", "300", "--solar", "4000"] + part22: + networks: + network1: + ipv4_address: 172.20.0.22 + ports: + - 8022:8000 + build: + context: . + dockerfile: /home/jonny0815/git/dismagr/participant/dockerfile + command: [ "python", "./participant.py", "--con", "172.20.0.105", "--cc", "0.2", "--sh", "0.8", "--dmd", "300", "--solar", "4000" ] + part23: + networks: + network1: + ipv4_address: 172.20.0.23 + ports: + - 8023:8000 + build: + context: . + dockerfile: /home/jonny0815/git/dismagr/participant/dockerfile + command: [ "python", "./participant.py", "--con", "172.20.0.105", "--cc", "0.2", "--sh", "0.8", "--dmd", "300", "--solar", "4000" ] + part31: + networks: + network1: + ipv4_address: 172.20.0.31 + ports: + - 8031:8000 + build: + context: . + dockerfile: /home/jonny0815/git/dismagr/participant/dockerfile + command: [ "python", "./participant.py", "--con", "172.20.0.106", "--cc", "0.7", "--sh", "0.7", "--dmd", "800", "--solar", "4000"] + part32: + networks: + network1: + ipv4_address: 172.20.0.32 + ports: + - 8032:8000 + build: + context: . + dockerfile: /home/jonny0815/git/dismagr/participant/dockerfile + command: [ "python", "./participant.py", "--con", "172.20.0.106", "--cc", "0.7", "--sh", "0.7", "--dmd", "800", "--solar", "4000" ] + part33: + networks: + network1: + ipv4_address: 172.20.0.33 + ports: + - 8033:8000 + build: + context: . + dockerfile: /home/jonny0815/git/dismagr/participant/dockerfile + command: [ "python", "./participant.py", "--con", "172.20.0.106", "--cc", "0.7", "--sh", "0.7", "--dmd", "800", "--solar", "4000" ] + part34: + networks: + network1: + ipv4_address: 172.20.0.34 + ports: + - 8034:8000 + build: + context: . + dockerfile: /home/jonny0815/git/dismagr/participant/dockerfile + command: [ "python", "./participant.py", "--con", "172.20.0.106", "--cc", "0.7", "--sh", "0.7", "--dmd", "800", "--solar", "4000" ] \ No newline at end of file diff --git a/participant.py b/participant.py index 9420d6edb96ce5073ec7d36f9b30494fd078d223..68fb1e6097badbd246b73c997dcd349c3700a4b1 100644 --- a/participant.py +++ b/participant.py @@ -117,6 +117,10 @@ class Participant: self.scheduler.add_job(self.request_remoteparticipants, 'interval', seconds=15, id='1', name='requestRemoteParticipants') self.scheduler.add_job(self.updateCurrentPower, 'interval', seconds=5, id='2', name='updateCurrentPower') + now = datetime.now() + next_min = (now + timedelta(minutes=1)).replace(second=0, microsecond=0) + run_time = next_min - timedelta(seconds=5) + self.scheduler.add_job(job_function, 'interval', minutes=1, next_run_time=run_time) logger.info("~~~>Finished setup on " + self.publicIP.__str__()) @@ -132,6 +136,7 @@ class Participant: for ex in self.ExchangeQueue: if isinstance(ex, Offer): offers.append(ex) + logger.info("===> returining " + len(offers).__str__() + " offers") return jsonpickle.encode(offers) async def dht_startup(self) -> None: @@ -314,7 +319,8 @@ class Participant: if len(t.sig_chain[0]) == 0: t.sig_chain[0].append((public_key, signature)) return jsonpickle.encode(t) - if len(t.sig_chain[1]) == 0 and t.sig_chain[0][0][0] != public_key: # not adding the same signature twice + if len(t.sig_chain[1]) == 0 and t.sig_chain[0][0][ + 0] != public_key: # not adding the same signature twice t.sig_chain[1].append((public_key, signature)) return jsonpickle.encode(t) for t in self.TradeQueue: @@ -371,7 +377,7 @@ class Participant: self.availableTrades.append(trade) return jsonpickle.encode(trade) - async def produceNextTrade(self): + async def produce_next_Exchange(self): # here we can take into account if there is a variable consumer in the house (e.g. electric car or heat pump) # and if a battery is available to take care of the fluctuations or to even supply energy to the grid @@ -392,11 +398,13 @@ class Participant: for e in self.ExchangeQueue: # only produce a new exchange if there is no exchange for the next minute if e.is_min_next(): ex_next_min = True + logger.info("===> exchange for next minute already exists on " + self.publicIP.__str__()) for t in self.TradeQueue: # if an exchange already has been matched for the next minute, no new exchange is needed if t.request.is_min_next() and t.offer.is_min_next(): ex_next_min = True + logger.info("===> trade for next minute already exists on " + self.publicIP.__str__()) if power > Q_(0, ureg.watt) and not ex_next_min: # more energy will be available than needed, so we can create an offer @@ -418,9 +426,6 @@ class Participant: self.ExchangeQueue.append(r) logger.info("===> created request on " + self.publicIP.__str__()) - if ex_next_min: - logger.info("===> exchange or trade for next minute already exists on " + self.publicIP.__str__()) - async def findRoute(self, start: RemoteParticipant, end: RemoteParticipant) -> Route: s = self.gridNodeIndexes[start.publicKey] e = self.gridNodeIndexes[end.publicKey] @@ -474,11 +479,19 @@ class Participant: return t async def findOffer(self, discarded_rps=frozenset()): + discarded_rps = set(discarded_rps) 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 + async with httpx.AsyncClient() as client: # this is an ugly fix + response = await client.get("http://" + rp.publicIP.__str__() + ":8000/getOffersJSON") + offers: list[Offer] = jsonpickle.decode(response.json()) + if len(offers) > 0: + closest = rp + logger.info("===> found closer participant with offer: " + rp.publicIP.__str__()) + else: + discarded_rps.add(rp) + logger.info("===> discarded participant without offer: " + rp.publicIP.__str__()) if closest != self.as_remote_participant(): # check if there is actually someone async with httpx.AsyncClient() as client: @@ -550,16 +563,53 @@ class Participant: "public_key": jsonpickle.encode(self.publicKey), "signature": jsonpickle.encode(sig)}) if response.status_code == 200: - logger.info("===> received confirmation from " + signable_trade1.offer.publicIP.__str__()) + logger.info( + "===> received confirmation from " + signable_trade1.offer.publicIP.__str__()) signed_trade1 = jsonpickle.decode(response.json()) confirmedt.confirmed_trades.append(signed_trade1) if response.status_code == 404: - logger.info("===> no trade returned from " + signable_trade1.offer.publicIP.__str__()) + logger.info( + "===> no trade returned from " + signable_trade1.offer.publicIP.__str__()) - logger.info("-------------------------------------") - - # confirmedt.confirmed_trades.append() + signable_trade2: Trade = await self.findConfirmableTrade() + # verify the route of the trade + for i in signable_trade2.singedRoute: + if isinstance(i, SignedRemoteLine): + response = await client.get( + "http://" + i.line.publicIP.__str__() + ":8000/getPublicKey") + pk = jsonpickle.decode(response.json()) + # logger.info("--------------->" + i.line.__repr__().encode().__str__()) + if not self.dil.verify(pk, i.line.__repr__().encode(), i.signature): + logger.info("===> Signature not valid") + return "Error: Signature not valid" + if isinstance(i, SignedRemoteTransformer): + response = await client.get( + "http://" + i.transformer.publicIP.__str__() + ":8000/getPublicKey") + pk = jsonpickle.decode(response.json()) + # logger.info("--------------->" + i.trafo.__repr__().encode().__str__()) + if not self.dil.verify(pk, i.transformer.__repr__().encode(), i.signature): + logger.info("===> Signature not valid") + return "Error: Signature not valid" + # route seems to be fine, now we can sign + with redirect_stdout(None): + sig = self.dil.sign_with_input(self.__secretKey, + signable_trade2.__repr__().encode()) + response = await client.post( + "http://" + signable_trade2.offer.publicIP.__str__() + ":8000/signTrade", + json={"trade": jsonpickle.encode(signable_trade2), + "public_key": jsonpickle.encode(self.publicKey), + "signature": jsonpickle.encode(sig)}) + if response.status_code == 200: + logger.info( + "===> received confirmation from " + signable_trade2.offer.publicIP.__str__()) + signed_trade1 = jsonpickle.decode(response.json()) + confirmedt.confirmed_trades.append(signed_trade1) + if response.status_code == 404: + logger.info( + "===> no trade returned from " + signable_trade2.offer.publicIP.__str__()) + # trying to get confirmation for a trade must happen more often than just after creating it + logger.info("-------------------------------------") else: logger.info("===> no one to trade with on " + self.publicIP.__str__()) return @@ -590,12 +640,25 @@ class Participant: 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() + await self.produce_next_Exchange() 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() + # check available Trades for confirmation and move them to the TradeQueue + for t in self.availableTrades: + if len(t.sig_chain[0]) > 0 and len(t.sig_chain[1]) > 0: + self.TradeQueue.append(t) + self.availableTrades.remove(t) + logger.info("===> trade with two signatures moved from available to Queue") + + async def enact_Trades(self): + for t in self.TradeQueue: + pass + # TODO: continue here + + class Weather: # Weather might be better done using this but time is running out diff --git a/remotetransformer.py b/remotetransformer.py index 02e2f31aa7ee58b6ea1f2bd9838d64559e7a45eb..31608592fcee949b60ab258dae1f8984cc83e23b 100644 --- a/remotetransformer.py +++ b/remotetransformer.py @@ -28,3 +28,4 @@ class SignedRemoteTransformer: 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) + # need to add the previous but not to infinite depth