Skip to content
Snippets Groups Projects
Commit fee9e464 authored by Johannes Hitzinger's avatar Johannes Hitzinger
Browse files

trade now confirms one other trade, confirming second trade still tbd

parent fa611a3a
No related branches found
No related tags found
No related merge requests found
import ipaddress
from pint import UnitRegistry from pint import UnitRegistry
import time import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
class Exchange: class Exchange:
def __init__(self, pk: bytes, p, ppw: float, extime: datetime): def __init__(self, pk: bytes, p, ppw: float, extime: datetime, ip: ipaddress.IPv4Address):
self.ureg = None self.ureg = None
self.Q_ = None self.Q_ = None
self.publicKey: bytes = pk self.publicKey: bytes = pk
self.publicIP: ipaddress.IPv4Address = ip
self.timestamp: int = time.time_ns() self.timestamp: int = time.time_ns()
self.executiontime: datetime = extime self.executiontime: datetime = extime
self.__isActive: bool = False self.__isActive: bool = False
...@@ -68,5 +71,5 @@ class Exchange: ...@@ -68,5 +71,5 @@ class Exchange:
return self.executiontime.timestamp() < int(now.timestamp()) return self.executiontime.timestamp() < int(now.timestamp())
def __repr__(self): def __repr__(self):
return self.pubicKey.__repr__() + " " + str(self.power) + " " + str(self.pricePerWatt) + " " + str( return self.publicKey.__repr__() + " " + str(self.power) + " " + str(self.pricePerWatt) + " " + str(
self.executiontime) self.executiontime)
\ No newline at end of file
...@@ -161,6 +161,7 @@ result = d1.verify(pk, msg, sig) ...@@ -161,6 +161,7 @@ result = d1.verify(pk, msg, sig)
#print(result) #print(result)
''' '''
'''
from trade import Trade from trade import Trade
from offer import Offer from offer import Offer
from request import Request from request import Request
...@@ -173,11 +174,12 @@ rp1 = RemoteParticipant(bytes(123), ipaddress.IPv4Address('1.2.3.4'), [o1], set( ...@@ -173,11 +174,12 @@ rp1 = RemoteParticipant(bytes(123), ipaddress.IPv4Address('1.2.3.4'), [o1], set(
rp2 = RemoteParticipant(bytes(123), ipaddress.IPv4Address('1.2.3.4'), [r1], set()) rp2 = RemoteParticipant(bytes(123), ipaddress.IPv4Address('1.2.3.4'), [r1], set())
rou = Route(rp1, rp2, rx.PyGraph(), 0.1) rou = Route(rp1, rp2, rx.PyGraph(), 0.1)
t1 = Trade(o1, r1, 0.1, 0.1, rou, datetime.datetime.now()) t1 = Trade(o1, r1, 0.1, 0.1, rou, datetime.datetime.now(), time.time_ns())
j = jsonpickle.encode(t1) j = jsonpickle.encode(t1)
t2 = jsonpickle.decode(j) t2 = jsonpickle.decode(j)
print(t2) print(t2)
'''
jobstores = { jobstores = {
'default': MemoryJobStore() 'default': MemoryJobStore()
......
import exchange import exchange
from datetime import datetime from datetime import datetime
import ipaddress
class Offer(exchange.Exchange): class Offer(exchange.Exchange):
def __init__(self, pk: bytes, p: float, ppw: float, extime: datetime): def __init__(self, pk: bytes, p: float, ppw: float, extime: datetime, ip: ipaddress.IPv4Address):
exchange.Exchange.__init__(self, pk, p, ppw, extime) exchange.Exchange.__init__(self, pk, p, ppw, extime, ip)
...@@ -41,7 +41,7 @@ from remotetransformer import RemoteTransformer, SignedRemoteTransformer ...@@ -41,7 +41,7 @@ from remotetransformer import RemoteTransformer, SignedRemoteTransformer
from remoteparticipant import RemoteParticipant from remoteparticipant import RemoteParticipant
from fastapi import APIRouter, FastAPI, Body from fastapi import APIRouter, FastAPI, Body, HTTPException
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -93,6 +93,7 @@ class Participant: ...@@ -93,6 +93,7 @@ class Participant:
self.fastRouter.add_api_route("/getgridpng", self.getGridPNG, methods=["GET"]) self.fastRouter.add_api_route("/getgridpng", self.getGridPNG, methods=["GET"])
self.fastRouter.add_api_route("/announceTrade", self.announceTrade, methods=["POST"]) self.fastRouter.add_api_route("/announceTrade", self.announceTrade, methods=["POST"])
self.fastRouter.add_api_route("/getTradeToSign", self.getTradeToSign, methods=["GET"]) self.fastRouter.add_api_route("/getTradeToSign", self.getTradeToSign, methods=["GET"])
self.fastRouter.add_api_route("/signTrade", self.signTrade, methods=["POST"])
# announce self on dht # announce self on dht
asyncio.run(self.dht_startup()) asyncio.run(self.dht_startup())
# self.loop.run_in_executor(None, self.dht_startup) # self.loop.run_in_executor(None, self.dht_startup)
...@@ -107,8 +108,9 @@ class Participant: ...@@ -107,8 +108,9 @@ class Participant:
asyncio.run(self.discoverGrid()) asyncio.run(self.discoverGrid())
# generate and add GenesisTrade # generate and add GenesisTrade
gt = GenesisTrade(self.publicKey, self.as_remote_participant()) gt = GenesisTrade(self.publicKey, self.as_remote_participant())
gt.sig_off = self.dil.sign_with_input(self.__secretKey, gt.offer.__repr__().encode()) with redirect_stdout(None):
gt.sig_req = self.dil.sign_with_input(self.__secretKey, gt.request.__repr__().encode()) gt.sig_off = self.dil.sign_with_input(self.__secretKey, gt.offer.__repr__().encode())
gt.sig_req = self.dil.sign_with_input(self.__secretKey, gt.request.__repr__().encode())
self.__tradeHistory.append(gt) self.__tradeHistory.append(gt)
# set up scheduling for tasks # set up scheduling for tasks
...@@ -300,6 +302,48 @@ class Participant: ...@@ -300,6 +302,48 @@ class Participant:
return jsonpickle.encode(self.__tradeHistory[-1]) # return the last trade in the history, this should only return jsonpickle.encode(self.__tradeHistory[-1]) # return the last trade in the history, this should only
# happen when the participant is new and has no trades beside the GenesisTrade # happen when the participant is new and has no trades beside the GenesisTrade
async def signTrade(self, body=Body()):
trade: Trade = jsonpickle.decode(body["trade"])
public_key = jsonpickle.decode(body["public_key"])
signature = jsonpickle.decode(body["signature"])
# find the matching trade and add the signature to the sig_chain
logger.info("===> matching trade and adding signature")
for t in self.availableTrades:
if t == trade:
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
t.sig_chain[1].append((public_key, signature))
return jsonpickle.encode(t)
for t in self.TradeQueue:
if t == trade:
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:
t.sig_chain[1].append((public_key, signature))
return jsonpickle.encode(t)
for t in self.activeTrades:
if t == trade:
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:
t.sig_chain[1].append((public_key, signature))
return jsonpickle.encode(t)
for t in self.__tradeHistory:
if t == trade:
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:
t.sig_chain[1].append((public_key, signature))
return jsonpickle.encode(t)
logger.info("===> no suitable trade found")
raise HTTPException(status_code=404, detail="Trade not found")
async def announceTrade(self, body=Body()): async def announceTrade(self, body=Body()):
# here a Trade will be announced from another participant which # here a Trade will be announced from another participant which
# tries to match their offer with the request of this participant # tries to match their offer with the request of this participant
...@@ -314,9 +358,17 @@ class Participant: ...@@ -314,9 +358,17 @@ class Participant:
if not self.dil.verify(pk, i.line.__repr__().encode(), i.signature): if not self.dil.verify(pk, i.line.__repr__().encode(), i.signature):
logger.info("===> Signature not valid") logger.info("===> Signature not valid")
return "Error: 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"
with redirect_stdout(None): with redirect_stdout(None):
trade.sig_offer = self.dil.sign_with_input(self.__secretKey, trade.offer.__repr__().encode()) trade.sig_offer = self.dil.sign_with_input(self.__secretKey, trade.offer.__repr__().encode())
logger.info("===> confirmed trade") logger.info("===> confirmed trade")
self.availableTrades.append(trade)
return jsonpickle.encode(trade) return jsonpickle.encode(trade)
async def produceNextTrade(self): async def produceNextTrade(self):
...@@ -352,7 +404,7 @@ class Participant: ...@@ -352,7 +404,7 @@ class Participant:
# additionally, all offers will be for the next minute, when the system is able to take variable # 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 # consumers (e.g. electric cars and heat pumps) into account, this can be adapted to request
# bigger amounts of energy in a foreseeable fashion # bigger amounts of energy in a foreseeable fashion
o = Offer(self.publicKey, power, 0.3, next_minute()) # price should be dynamic, no time :D o = Offer(self.publicKey, power, 0.3, next_minute(), self.publicIP) # price should be dynamic, no time :D
self.ExchangeQueue.append(o) self.ExchangeQueue.append(o)
logger.info("===> created offer on " + self.publicIP.__str__()) logger.info("===> created offer on " + self.publicIP.__str__())
else: else:
...@@ -362,7 +414,7 @@ class Participant: ...@@ -362,7 +414,7 @@ class Participant:
# additionally, all requests will be for the next minute, when the system is able to take variable # 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 # consumers (e.g. electric cars and heat pumps) into account, this can be adapted to request
# bigger amounts of energy in a foreseeable fashion # bigger amounts of energy in a foreseeable fashion
r = Request(self.publicKey, power, 0.3, next_minute()) r = Request(self.publicKey, power, 0.3, next_minute(), self.publicIP)
self.ExchangeQueue.append(r) self.ExchangeQueue.append(r)
logger.info("===> created request on " + self.publicIP.__str__()) logger.info("===> created request on " + self.publicIP.__str__())
...@@ -445,7 +497,7 @@ class Participant: ...@@ -445,7 +497,7 @@ class Participant:
# the offer is big enough, use price from offer as this is the price that will be paid # 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 # 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, t = Trade(o, self.ExchangeQueue[0], self.ExchangeQueue[0].power, o.pricePerWatt, r,
next_minute()) next_minute(), time.time_ns())
# now announce the route to all connections involved # now announce the route to all connections involved
signedt = await self.announceRoute(t) signedt = await self.announceRoute(t)
# sign the trade to mark it as valid # sign the trade to mark it as valid
...@@ -468,29 +520,66 @@ class Participant: ...@@ -468,29 +520,66 @@ class Participant:
if confirmedt.request == self.ExchangeQueue[0]: # own request is fulfilled by trade if confirmedt.request == self.ExchangeQueue[0]: # own request is fulfilled by trade
self.availableTrades.append(confirmedt) self.availableTrades.append(confirmedt)
self.ExchangeQueue.pop(0) self.ExchangeQueue.pop(0)
signable_trade1: Trade = await self.findConfirmableTrade()
# verify the route of the trade
for i in signable_trade1.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_trade1.__repr__().encode())
response = await client.post(
"http://" + signable_trade1.offer.publicIP.__str__() + ":8000/signTrade",
json={"trade": jsonpickle.encode(signable_trade1),
"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__())
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("-------------------------------------")
# confirmedt.confirmed_trades.append()
else: else:
logger.info("===> no one to trade with on " + self.publicIP.__str__()) logger.info("===> no one to trade with on " + self.publicIP.__str__())
return return
# when no partner can be found, this will be tried again after the next updateCurrentPower # when no partner can be found, this will be tried again after the next updateCurrentPower
# when a request is about to "time out" because no partner has been found, the required power of # when a request is about to "time out" because no partner has been found, the required power of
# the participant should be reduced to avoid a blackout. in a fully digitalized grid the participant # the participant should be reduced to avoid a blackout. in a fully digitalized grid the participant
# would be able to request power from the grid # would be able to request power from the grid operator at a high cost
# in an overlapping period the participant simply takes the power from the grid via the old counter # in an overlapping period the participant simply takes the power from the grid via the old counter
async def findConfirmableTrade(self) -> Trade: async def findConfirmableTrade(self):
possibleTrade = None possible_trade = None
for rp in self.remoteParticipants: for rp in self.remoteParticipants:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
response = await client.get("http://" + rp.publicIP.__str__() + ":8000/getTradeToSign") response = await client.get("http://" + rp.publicIP.__str__() + ":8000/getTradeToSign")
possibleTrade = jsonpickle.decode(response.json()) trade = jsonpickle.decode(response.json())
# TODO: continue here if isinstance(trade, Trade): # taking the first actual trade
return trade
if isinstance(trade, GenesisTrade): # if all thats available are genesistrades, we take one
possible_trade = trade
return possible_trade
async def updateCurrentPower(self): async def updateCurrentPower(self):
self.__availablePower = self.currentInHouseSupply - self.__currentInHouseDemand self.__availablePower = self.currentInHouseSupply - self.__currentInHouseDemand
...@@ -507,10 +596,6 @@ class Participant: ...@@ -507,10 +596,6 @@ class Participant:
logger.info("===> trying to find an offer on " + self.publicIP.__str__()) logger.info("===> trying to find an offer on " + self.publicIP.__str__())
await self.findOffer() await self.findOffer()
if len(self.availableTrades) > 0:
conftrade: Trade = await self.findConfirmableTrade()
class Weather: class Weather:
# Weather might be better done using this but time is running out # Weather might be better done using this but time is running out
......
import ipaddress
import exchange import exchange
from datetime import datetime from datetime import datetime
class Request(exchange.Exchange): class Request(exchange.Exchange):
def __init__(self, pk: bytes, p: float, ppw: float, extime: datetime): def __init__(self, pk: bytes, p: float, ppw: float, extime: datetime, ip: ipaddress.IPv4Address):
exchange.Exchange.__init__(self, pk, p, ppw, extime) exchange.Exchange.__init__(self, pk, p, ppw, extime, ip)
def __eq__(self, other): def __eq__(self, other):
return self.publicKey == other.publicKey and self.executiontime == other.executiontime return self.publicKey == other.publicKey and self.executiontime == other.executiontime
...@@ -12,10 +12,10 @@ from remoteparticipant import RemoteParticipant ...@@ -12,10 +12,10 @@ from remoteparticipant import RemoteParticipant
class Trade: class Trade:
def __init__(self, off: offer.Offer, req: request.Request, pwr, ppw: float, rou: route.Route, def __init__(self, off: offer.Offer, req: request.Request, pwr, ppw: float, rou: route.Route,
extime: datetime.datetime): extime: datetime.datetime, ts: int):
self.ureg = None self.ureg = None
self.Q_ = None self.Q_ = None
self.__timestamp = time.time_ns() self.__timestamp = ts
self.offer: offer.Offer = off self.offer: offer.Offer = off
self.request: request.Request = req self.request: request.Request = req
self.route = rou self.route = rou
...@@ -39,7 +39,10 @@ class Trade: ...@@ -39,7 +39,10 @@ class Trade:
def __repr__(self): def __repr__(self):
return (self.offer.__repr__() + " " + self.request.__repr__() + " " + str(self.power) + " " + return (self.offer.__repr__() + " " + self.request.__repr__() + " " + str(self.power) + " " +
str(self.__pricePerWatt) + " " + str(self.__executionTime)) str(self.__pricePerWatt) + " " + str(self.__executionTime) + " " + str(self.__timestamp))
def __eq__(self, other):
return self.offer.publicKey == other.offer.publicKey and self.request.publicKey == other.request.publicKey and self.__timestamp == other.__timestamp
def remove_ureg(self): def remove_ureg(self):
self.power = float(self.power.magnitude) self.power = float(self.power.magnitude)
...@@ -54,7 +57,7 @@ class GenesisTrade(Trade): ...@@ -54,7 +57,7 @@ class GenesisTrade(Trade):
def __init__(self, public_key: bytes, remote_me: RemoteParticipant): def __init__(self, public_key: bytes, remote_me: RemoteParticipant):
g = rustworkx.PyGraph() g = rustworkx.PyGraph()
g.add_node(remote_me) g.add_node(remote_me)
super().__init__(offer.Offer(public_key, 0, 0, datetime.datetime.now()), super().__init__(offer.Offer(public_key, 0, 0, datetime.datetime.now(), remote_me.publicIP),
request.Request(public_key, 0, 0, datetime.datetime.now()), 0, 0, request.Request(public_key, 0, 0, datetime.datetime.now(), remote_me.publicIP), 0, 0,
route.Route(remote_me, remote_me, g, 0), route.Route(remote_me, remote_me, g, 0),
datetime.datetime.now()) datetime.datetime.now(), time.time_ns())
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment