diff --git a/pool/pool.py b/pool/pool.py index 0ae1b967..74dd6e20 100644 --- a/pool/pool.py +++ b/pool/pool.py @@ -50,7 +50,7 @@ from .store.abstract import AbstractPoolStore from .store.sqlite_store import SqlitePoolStore from .record import FarmerRecord -from .util import error_dict +from .util import error_dict, RequestMetadata class Pool: @@ -547,7 +547,7 @@ async def check_and_confirm_partial(self, partial: PostPartialRequest, points_re error_stack = traceback.format_exc() self.log.error(f"Exception in confirming partial: {e} {error_stack}") - async def add_farmer(self, request: PostFarmerRequest) -> Dict: + async def add_farmer(self, request: PostFarmerRequest, metadata: RequestMetadata) -> Dict: async with self.store.lock: farmer_record: Optional[FarmerRecord] = await self.store.get_farmer_record(request.payload.launcher_id) if farmer_record is not None: @@ -613,7 +613,7 @@ async def add_farmer(self, request: PostFarmerRequest) -> Dict: True, ) self.scan_p2_singleton_puzzle_hashes.add(p2_singleton_puzzle_hash) - await self.store.add_farmer_record(farmer_record) + await self.store.add_farmer_record(farmer_record, metadata) return PostFarmerResponse(self.welcome_message).to_json_dict() diff --git a/pool/pool_server.py b/pool/pool_server.py index 57a102fe..2dbd961f 100644 --- a/pool/pool_server.py +++ b/pool/pool_server.py @@ -31,7 +31,7 @@ from .record import FarmerRecord from .pool import Pool from .store.abstract import AbstractPoolStore -from .util import error_response +from .util import error_response, RequestMetadata def allow_cors(response: web.Response) -> web.Response: @@ -134,6 +134,16 @@ async def get_farmer(self, request_obj) -> web.Response: self.pool.log.info(f"get_farmer response {response.to_json_dict()}, " f"launcher_id: {launcher_id.hex()}") return obj_to_response(response) + def post_metadata_from_request(self, request_obj): + return RequestMetadata( + url=request_obj.url, + scheme=request_obj.scheme, + headers=request_obj.headers, + cookies=dict(request_obj.cookies), + query=dict(request_obj.query), + remote=request_obj.remote, + ) + async def post_farmer(self, request_obj) -> web.Response: # TODO(pool): add rate limiting post_farmer_request: PostFarmerRequest = PostFarmerRequest.from_json_dict(await request_obj.json()) @@ -146,7 +156,8 @@ async def post_farmer(self, request_obj) -> web.Response: if authentication_token_error is not None: return authentication_token_error - post_farmer_response = await self.pool.add_farmer(post_farmer_request) + post_farmer_response = await self.pool.add_farmer( + post_farmer_request, self.post_metadata_from_request(request_obj)) self.pool.log.info( f"post_farmer response {post_farmer_response}, " diff --git a/pool/record.py b/pool/record.py index 11f9bc47..540f9798 100644 --- a/pool/record.py +++ b/pool/record.py @@ -21,4 +21,4 @@ class FarmerRecord(Streamable): points: uint64 # Total points accumulated since last rest (or payout) difficulty: uint64 # Current difficulty for this farmer payout_instructions: str # This is where the pool will pay out rewards to the farmer - is_pool_member: bool # If the farmer leaves the pool, this gets set to False \ No newline at end of file + is_pool_member: bool # If the farmer leaves the pool, this gets set to False diff --git a/pool/store/abstract.py b/pool/store/abstract.py index 9add2b7e..bc5b9a71 100644 --- a/pool/store/abstract.py +++ b/pool/store/abstract.py @@ -8,6 +8,7 @@ from chia.util.ints import uint64 from ..record import FarmerRecord +from ..util import RequestMetadata class AbstractPoolStore(ABC): @@ -22,7 +23,7 @@ async def connect(self): """Perform IO-related initialization""" @abstractmethod - async def add_farmer_record(self, farmer_record: FarmerRecord): + async def add_farmer_record(self, farmer_record: FarmerRecord, metadata: RequestMetadata): """Persist a new Farmer in the store""" @abstractmethod diff --git a/pool/store/sqlite_store.py b/pool/store/sqlite_store.py index be5cd48e..e0da6022 100644 --- a/pool/store/sqlite_store.py +++ b/pool/store/sqlite_store.py @@ -10,6 +10,7 @@ from .abstract import AbstractPoolStore from ..record import FarmerRecord +from ..util import RequestMetadata class SqlitePoolStore(AbstractPoolStore): @@ -68,7 +69,7 @@ def _row_to_farmer_record(row) -> FarmerRecord: True if row[10] == 1 else False, ) - async def add_farmer_record(self, farmer_record: FarmerRecord): + async def add_farmer_record(self, farmer_record: FarmerRecord, metadata: RequestMetadata): cursor = await self.connection.execute( f"INSERT OR REPLACE INTO farmer VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", ( diff --git a/pool/util.py b/pool/util.py index 1c11876b..32bb2bea 100644 --- a/pool/util.py +++ b/pool/util.py @@ -1,3 +1,6 @@ +from dataclasses import dataclass +from typing import Dict, Mapping + from chia.protocols.pool_protocol import PoolErrorCode, ErrorResponse from chia.util.ints import uint16 from chia.util.json_util import obj_to_response @@ -11,3 +14,20 @@ def error_response(code: PoolErrorCode, message: str): def error_dict(code: PoolErrorCode, message: str): error: ErrorResponse = ErrorResponse(uint16(code.value), message) return error.to_json_dict() + + +@dataclass +class RequestMetadata: + """ + HTTP-related metadata passed with HTTP requests + """ + url: str # original request url, as used by the client + scheme: str # for example https + headers: Mapping[str, str] # header names are all lower case + cookies: Dict[str, str] + query: Dict[str, str] # query params passed in the url. These are not used by chia clients at the moment, but + # allow for a lot of adjustments and thanks to including them now they can be used without introducing breaking changes + remote: str # address of the client making the request + + def __post_init__(self): + self.headers = {k.lower(): v for k, v in self.headers.items()}