diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000000000000000000000000000000000000..80422de1933b4ae6b5038382e3388d40502d7126 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,5 @@ +services: + test: + build: . + test2: + build: . diff --git a/dockerfile b/dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..17b99802167614880e5a460599c6912c97e18bf6 --- /dev/null +++ b/dockerfile @@ -0,0 +1,6 @@ +FROM python:3.12 +# Or any preferred Python version. +ADD main.py exchange.py offer.py participant.py request.py trade.py . +RUN pip install numpy pint fastapi uvicorn dilithium +CMD ["python", "./main.py"] +# Or enter the name of your unique directory and parameter set. \ No newline at end of file diff --git a/exchange.py b/exchange.py index 7847e92cd637ed194b26b6bb2b4f5303751d344d..f5f9e1833b41a49e4327dafe7f4e902c17318393 100644 --- a/exchange.py +++ b/exchange.py @@ -1,12 +1,13 @@ -from main import ureg,Q_ +#from main import ureg,Q_ import locale import time -from trade import Trade +class Trade: + pass class Exchange: def __init__(self, p: float, ppw: float): self.__timestamp: time.time_ns() - self.__power = Q_(p, ureg.watt) + #self.__power = Q_(p, ureg.watt) self.__pricePerWatt = locale.currency(ppw) self.__trades: set[Trade] = set() self.__sig: bytes #signed exchange hash when becoming active \ No newline at end of file diff --git a/main.py b/main.py index 6915656ccf3d8bc588e4e2c16bc78895df0cb540..1c1670c885b5193496325217b5780515765c5bc2 100644 --- a/main.py +++ b/main.py @@ -1,14 +1,102 @@ +import dataclasses +import ipaddress import sys import random -import fastapi +import numpy +import uvicorn +from fastapi import FastAPI, Path, APIRouter +from pydantic import BaseModel +import jsonpickle +from typing import Annotated + import dilithium.dilithium from dilithium import Dilithium from pint import UnitRegistry +import participant + ureg = UnitRegistry() Q_ = ureg.Quantity +app = FastAPI() +part = participant.Participant() +app.include_router(part.fastRouter) + + +from remoteparticipant import RemoteParticipant + +# --------- Chaos is following ------------- + +rps: list[RemoteParticipant] = [RemoteParticipant(pk=b'abc', ip=ipaddress.IPv4Address("0.0.0.1"), nex=set()), + RemoteParticipant(pk=b'def', ip=ipaddress.IPv4Address("0.0.0.2"), nex=set()), + RemoteParticipant(pk=b'ghi', ip=ipaddress.IPv4Address("0.0.0.3"), nex=set()), + RemoteParticipant(pk=b'jkl', ip=ipaddress.IPv4Address("0.0.0.4"), nex=set())] + +print(rps) + +serialized = [] + +for r in rps: + serialized.append(jsonpickle.dumps(r)) + +print(serialized) + +rps2 = [] + +for s in serialized: + rps2.append(jsonpickle.loads(s)) + +for s in serialized: + print(s.__repr__()) + +import socket + + +def get_ip(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.settimeout(0) + try: + # doesn't even have to be reachable + s.connect(('10.254.254.254', 1)) + IP = s.getsockname()[0] + except Exception: + IP = '127.0.0.1' + finally: + s.close() + return IP + + +class Item(BaseModel): + name: str + description: str | None = None + price: float + tax: float | None = None + + +info = [] + + +@app.put("/items/{item_id}") +async def update_item( + item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)], + q: str | None = None, + item: Item | None = None, +): + results = {"item_id": item_id} + if q: + results.update({"q": q}) + if item: + results.update({"item": item}) + info.append(item) + + return results + + +@app.get("/hello2/{name}") +async def say_hello(name: str): + return {"message": f"Hello {name}" + info.__repr__()} + v1 = Q_(5, ureg.volt) a1 = Q_(4, ureg.amp) @@ -16,12 +104,15 @@ p1 = v1 * a1 p1.ito(ureg.watt) print(p1) - - d1 = Dilithium(dilithium.dilithium.DEFAULT_PARAMETERS["dilithium3"]) -pk, sk = d1.keygen([random.randint(0, sys.maxsize), random.randint(0, sys.maxsize), random.randint(0, sys.maxsize), - random.randint(0, sys.maxsize)]) +pk, sk = d1.keygen([random.randint(0, numpy.iinfo('uint32').max), random.randint(0, numpy.iinfo('uint32').max), + random.randint(0, numpy.iinfo('uint32').max), + random.randint(0, numpy.iinfo('uint32').max)]) msg = b"this is a message" sig = d1.sign_with_input(sk, msg) result = d1.verify(pk, msg, sig) print(result) + +# start the server +if __name__ == "__main__": + uvicorn.run(app, host=get_ip(), port=8000) diff --git a/offer.py b/offer.py index 2f33c059c7cddcd5d07fc68cd309622f79fa7345..ec21ca3904928ac09b3dfd0a592c6f5431570bb3 100644 --- a/offer.py +++ b/offer.py @@ -1,4 +1,4 @@ -from exchange import Exchange -class Offer(Exchange): +import exchange +class Offer(exchange.Exchange): def __init__(self, p: float, ppw: float): - Exchange.__init__(self, p, ppw) + exchange.Exchange.__init__(self, p, ppw) diff --git a/participant.py b/participant.py index 79e67fe4ff611e68eb32a4b0b53ca06215c197bb..925a78a5487bbf25a4df7d292a8de6454a98b5e7 100644 --- a/participant.py +++ b/participant.py @@ -1,20 +1,28 @@ -from main import ureg,Q_ +#from main import ureg, Q_ import locale from random import randint import ipaddress import sys +import jsonpickle + from dilithium import Dilithium import dilithium.dilithium - from offer import Offer from request import Request from exchange import Exchange from trade import Trade +from remoteparticipant import RemoteParticipant + +from fastapi import FastAPI, APIRouter, Path +from pydantic import BaseModel +from typing import Annotated import socket + + def get_pub_ip(): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.settimeout(0) @@ -30,18 +38,33 @@ def get_pub_ip(): class Participant: - def __init__(self): + def __init__(self, ip:ipaddress = get_pub_ip()): d1 = Dilithium(dilithium.dilithium.DEFAULT_PARAMETERS["dilithium3"]) - self.__secretKey, self.__publicKey = d1.keygen([randint(0,sys.maxsize),randint(0,sys.maxsize),randint(0,sys.maxsize),randint(0,sys.maxsize)]) - self.public_ip: ipaddress.IPv4Address = get_pub_ip() #set to current ipv4, confirm before each new round - self.known_participants: set[Participant] = set() - self.available_exchanges: set[Exchange] = set() #known available exchanges from other participants for next turns - self.next_exchanges: set[Exchange] = set() #own exchanges for next turn - self.active_trades: set[Trade] = set() #own active trades for this turn - self.__trade_history: list[Trade] = [] #every own past trade - self.__current_power = Q_(0, ureg.watt) #real time power exchange with the grid - self.__current_inhouse_demand = Q_(0, ureg.watt) #real time demand from household appliances and storage - self.__projected_inhouse_demand = Q_(0, ureg.watt) #expected demand for next round - self.__current_inhouse_supply = Q_(0, ureg.watt) #real time inhouse production from solar, wind, storage, ... - self.__projected_inhouse_supply = Q_(0, ureg.watt) #expected supply for next round + self.__secretKey, self.__publicKey = d1.keygen( + [randint(0, sys.maxsize), randint(0, sys.maxsize), randint(0, sys.maxsize), randint(0, sys.maxsize)]) + self.fastRouter = APIRouter() + self.publicIP: ipaddress.IPv4Address = ip # set to current ipv4, confirm before each new round + self.knownParticipants: set[RemoteParticipant] = set() + self.availableExchanges: set[ + Exchange] = set() # known available exchanges from other participants for next turns + self.nextExchanges: set[Exchange] = set() # own exchanges for next turn + self.activeTrades: set[Trade] = set() # own active trades for this turn + self.__tradeHistory: list[Trade] = [] # every own past trade + #self.__currentPower = Q_(0, ureg.watt) # real time power exchange with the grid + #self.__currentInhouseDemand = Q_(0, ureg.watt) # real time demand from household appliances and storage + #self.__projectedInhouseDemand = Q_(0, ureg.watt) # expected demand for next round + #self.__currentInhouseSupply = Q_(0, ureg.watt) # real time inhouse production from solar, wind, storage, ... + #self.__projectedInhouseSupply = Q_(0, ureg.watt) # expected supply for next round + + #register rest endpoints with fastapi + self.fastRouter.add_api_route("/get_remoteparticipants", self.getremoteParticipants, methods=["GET"]) + + def as_remoteParticipant(self): + return RemoteParticipant(pk=self.__publicKey, ip=self.publicIP, nex=self.nextExchanges) + #fastapi call + def getremoteParticipants(self): + all_remoteparticipants: set[RemoteParticipant] = set() + all_remoteparticipants.add(self.as_remoteParticipant()) + all_remoteparticipants.update(self.knownParticipants) + return jsonpickle.dumps(all_remoteparticipants) diff --git a/remoteparticipant.py b/remoteparticipant.py new file mode 100644 index 0000000000000000000000000000000000000000..9d7eb8e9bd0446b605ca01b794ac6898fae026b8 --- /dev/null +++ b/remoteparticipant.py @@ -0,0 +1,13 @@ +import ipaddress + +from pydantic.dataclasses import dataclass + +import exchange + + +class RemoteParticipant: + def __init__(self, pk: bytes, ip: ipaddress.IPv4Address, nex: set[exchange.Exchange]): + self.__publicKey: bytes = pk + self.__publicIP: ipaddress.IPv4Address = ip + self.__nextExchanges: set[exchange.Exchange] = nex + diff --git a/request.py b/request.py index 05cb08f87ebe68d25fb141747a38c2ef4dbe5c6f..5a734dd6e323855f988c504fdfc9382e4f15dd4d 100644 --- a/request.py +++ b/request.py @@ -1,7 +1,7 @@ -from exchange import Exchange +import exchange -class Request(Exchange): +class Request(exchange.Exchange): def __init__(self, p: float, ppw: float): - Exchange.__init__(self, p,ppw) + exchange.Exchange.__init__(self, p,ppw) diff --git a/shell.nix b/shell.nix index 4f35d8c1e6bf8142f37544f9d9e673196a7875a3..e6d7bbc800c6633697dae2feb2adfb3e6a0141dc 100644 --- a/shell.nix +++ b/shell.nix @@ -6,6 +6,8 @@ let python-pkgs.numpy python-pkgs.pint python-pkgs.fastapi + python-pkgs.uvicorn + python-pkgs.jsonpickle (python-pkgs.callPackage ./dilithium.nix { }) ]); in diff --git a/trade.py b/trade.py index b822381a9dbe44a6bc427cbc2414736cc667868b..b28dcd8b13e27bd5257d672efe92d4a224ff6fe2 100644 --- a/trade.py +++ b/trade.py @@ -1,15 +1,15 @@ -from main import ureg,Q_ +#from main import ureg,Q_ import locale import time -from offer import Offer -from request import Request +import offer +import request class Trade: - def __init__(self, o: Offer, r: Request, p: float, ppw: float): + def __init__(self, o: offer.Offer, r: request.Request, p: float, ppw: float): self.__timestamp = time.time_ns() - self.__offer: Offer = o - self.__request: Request = r - self.__power = Q_(p, ureg.watt) + self.__offer: offer.Offer = o + self.__request: request.Request = r + #self.__power = Q_(p, ureg.watt) self.__pricePerWatt = locale.currency(ppw) self.__sig: bytes \ No newline at end of file