def upsert_iou(self, iou: IOU) -> None: iou_dict = IOU.Schema(exclude=["receiver", "chain_id"]).dump(iou) iou_dict["one_to_n_address"] = to_checksum_address( iou_dict["one_to_n_address"]) for key in ("amount", "expiration_block"): iou_dict[key] = hex256(iou_dict[key]) self.upsert("iou", iou_dict)
def get_ious( self, sender: Address = None, expiration_block: BlockNumber = None, claimed: bool = None, expires_before: BlockNumber = None, amount_at_least: TokenAmount = None, ) -> Iterator[IOU]: query = """ SELECT *, (SELECT chain_id FROM blockchain) AS chain_id FROM iou WHERE 1=1 """ args: list = [] if sender is not None: query += " AND sender = ?" args.append(to_checksum_address(sender)) if expiration_block is not None: query += " AND expiration_block = ?" args.append(hex256(expiration_block)) if claimed is not None: query += " AND claimed = ?" args.append(claimed) if expires_before is not None: query += " AND expiration_block < ?" args.append(hex256(expires_before)) if amount_at_least is not None: query += " AND amount >= ?" args.append(hex256(amount_at_least)) for row in self.conn.execute(query, args): iou_dict = dict(zip(row.keys(), row)) iou_dict["receiver"] = to_checksum_address(self.pfs_address) yield IOU.Schema().load(iou_dict)
def upsert_iou(self, iou: IOU): iou_dict = IOU.Schema(strict=True).dump(iou)[0] self.conn.execute(""" INSERT OR REPLACE INTO iou ( sender, amount, expiration_block, signature, claimed ) VALUES (:sender, :amount, :expiration_block, :signature, :claimed) """, iou_dict)
def test_get_iou(api_sut: PFSApi, api_url: str, token_network_model: TokenNetwork, make_iou): privkey = PrivateKey(get_random_privkey()) sender = private_key_to_address(privkey) url = api_url + f"/v1/{to_checksum_address(token_network_model.address)}/payment/iou" def make_params(timestamp: str): params = { "sender": to_checksum_address(sender), "receiver": to_checksum_address(api_sut.pathfinding_service.address), "timestamp": timestamp, } local_signer = LocalSigner(private_key=privkey) params["signature"] = encode_hex( local_signer.sign( to_canonical_address(params["sender"]) + to_canonical_address(params["receiver"]) + params["timestamp"].encode("utf8") ) ) return params # Request without IOU in database params = make_params(datetime.utcnow().isoformat()) response = requests.get(url, params=params) assert response.status_code == 404, response.json() assert response.json() == {"last_iou": None} # Add IOU to database iou = make_iou( privkey, api_sut.pathfinding_service.address, one_to_n_address=api_sut.one_to_n_address ) iou.claimed = False api_sut.pathfinding_service.database.upsert_iou(iou) # Is returned IOU the one save into the db? response = requests.get(url, params=params) assert response.status_code == 200, response.json() iou_dict = IOU.Schema(exclude=["claimed"]).dump(iou) assert response.json()["last_iou"] == iou_dict # Invalid signatures must fail params["signature"] = encode_hex((int(params["signature"], 16) + 1).to_bytes(65, "big")) response = requests.get(url, params=params) assert response.status_code == 400, response.json() assert response.json()["error_code"] == exceptions.InvalidSignature.error_code # Timestamp must no be too old to prevent replay attacks old_timestamp = datetime.utcnow() - timedelta(days=1) params = make_params(old_timestamp.isoformat()) response = requests.get(url, params=params) assert response.status_code == 400, response.json() assert response.json()["error_code"] == exceptions.RequestOutdated.error_code # Timestamp with timezone info is invalid for timestamp in (datetime.now(tz=timezone.utc).isoformat(), "2019-11-07T12:52:25.079Z"): params = make_params(timestamp) response = requests.get(url, params=params) assert response.status_code == 400, response.json() assert response.json()["error_code"] == exceptions.InvalidRequest.error_code
def test_load_and_save_iou(pathfinding_service_mocked_listeners): pfs = pathfinding_service_mocked_listeners iou_dict = make_iou(get_random_privkey(), pfs.address) iou = IOU.Schema().load(iou_dict)[0] iou.claimed = False pfs.database.upsert_iou(iou) stored_iou = pfs.database.get_iou(iou.sender, iou.expiration_block) assert stored_iou == iou
def test_get_iou(api_sut: ServiceApi, api_url: str, token_network_model: TokenNetwork, make_iou): privkey = get_random_privkey() sender = private_key_to_address(privkey) url = api_url + f"/{to_checksum_address(token_network_model.address)}/payment/iou" def make_params(timestamp: datetime): params = { "sender": to_checksum_address(sender), "receiver": to_checksum_address(api_sut.pathfinding_service.address), "timestamp": timestamp.isoformat(), } local_signer = LocalSigner(private_key=decode_hex(privkey)) params["signature"] = encode_hex( local_signer.sign( pack_data( (params["sender"], "address"), (params["receiver"], "address"), (params["timestamp"], "string"), ))) return params # Request without IOU in database params = make_params(datetime.utcnow()) response = requests.get(url, params=params) assert response.status_code == 404, response.json() assert response.json() == {"last_iou": None} # Add IOU to database iou = make_iou(privkey, api_sut.pathfinding_service.address, one_to_n_address=api_sut.one_to_n_address) iou.claimed = False api_sut.pathfinding_service.database.upsert_iou(iou) # Is returned IOU the one save into the db? response = requests.get(url, params=params) assert response.status_code == 200, response.json() iou_dict = IOU.Schema(exclude=["claimed"]).dump(iou) assert response.json()["last_iou"] == iou_dict # Invalid signatures must fail params["signature"] = encode_hex( (int(params["signature"], 16) + 1).to_bytes(65, "big")) response = requests.get(url, params=params) assert response.status_code == 400, response.json() assert response.json( )["error_code"] == exceptions.InvalidSignature.error_code # Timestamp must no be too old to prevent replay attacks params = make_params(datetime.utcnow() - timedelta(days=1)) response = requests.get(url, params=params) assert response.status_code == 400, response.json() assert response.json( )["error_code"] == exceptions.RequestOutdated.error_code
def test_get_iou( api_sut: ServiceApi, api_url: str, pathfinding_service_mock, token_network_model: TokenNetwork ): privkey = get_random_privkey() sender = private_key_to_address(privkey) url = api_url + f'/{token_network_model.address}/payment/iou' def make_params(timestamp: datetime): params = { 'sender': sender, 'receiver': api_sut.pathfinding_service.address, 'timestamp': timestamp.isoformat(), } local_signer = LocalSigner(private_key=decode_hex(privkey)) params['signature'] = encode_hex( local_signer.sign( pack_data( ['address', 'address', 'string'], [params['sender'], params['receiver'], params['timestamp']], ) ) ) return params # Request without IOU in database params = make_params(datetime.utcnow()) response = requests.get(url, params=params) assert response.status_code == 404, response.json() assert response.json() == {'last_iou': None} # Add IOU to database iou = make_iou(privkey, api_sut.pathfinding_service.address) iou.claimed = False api_sut.pathfinding_service.database.upsert_iou(iou) # Is returned IOU the one save into the db? response = requests.get(url, params=params) assert response.status_code == 200, response.json() iou_dict = IOU.Schema(exclude=['claimed']).dump(iou)[0] assert response.json()['last_iou'] == iou_dict # Invalid signatures must fail params['signature'] = encode_hex((int(params['signature'], 16) + 1).to_bytes(65, 'big')) response = requests.get(url, params=params) assert response.status_code == 400, response.json() assert response.json()['error_code'] == exceptions.InvalidSignature.error_code # Timestamp must no be too old to prevent replay attacks params = make_params(datetime.utcnow() - timedelta(days=1)) response = requests.get(url, params=params) assert response.status_code == 400, response.json() assert response.json()['error_code'] == exceptions.RequestOutdated.error_code # kill all running greenlets gevent.killall([obj for obj in gc.get_objects() if isinstance(obj, gevent.Greenlet)])
def process_payment(iou_dict: dict, pathfinding_service: PathfindingService): if pathfinding_service.service_fee == 0: return if iou_dict is None: raise exceptions.MissingIOU # Basic IOU validity checks iou, errors = IOU.Schema().load(iou_dict) if errors: raise exceptions.InvalidRequest(**errors) if iou.receiver != pathfinding_service.address: raise exceptions.WrongIOURecipient( expected=pathfinding_service.address) if not iou.is_signature_valid(): raise exceptions.InvalidSignature # Compare with known IOU active_iou = pathfinding_service.database.get_iou( sender=iou.sender, claimed=False, ) if active_iou: if active_iou.expiration_block != iou.expiration_block: raise exceptions.UseThisIOU(iou=active_iou) expected_amount = active_iou.amount + pathfinding_service.service_fee else: claimed_iou = pathfinding_service.database.get_iou( sender=iou.sender, expiration_block=iou.expiration_block, claimed=True, ) if claimed_iou: raise exceptions.IOUAlreadyClaimed min_expiry = pathfinding_service.web3.eth.blockNumber + MIN_IOU_EXPIRY if iou.expiration_block < min_expiry: raise exceptions.IOUExpiredTooEarly(min_expiry=min_expiry) expected_amount = pathfinding_service.service_fee if iou.amount < expected_amount: raise exceptions.InsufficientServicePayment( expected_amount=expected_amount) # Check client's deposit in UserDeposit contract udc = pathfinding_service.user_deposit_contract udc_balance = udc.functions.effectiveBalance(iou.sender).call() required_deposit = round(expected_amount * UDC_SECURITY_MARGIN_FACTOR) if udc_balance < required_deposit: raise exceptions.DepositTooLow(required_deposit=required_deposit) # Save latest IOU iou.claimed = False pathfinding_service.database.upsert_iou(iou)
def upsert_iou(self, iou: IOU) -> None: iou_dict = IOU.Schema(strict=True).dump(iou)[0] for key in ("amount", "expiration_block"): iou_dict[key] = hex256(iou_dict[key]) self.conn.execute( """ INSERT OR REPLACE INTO iou ( sender, amount, expiration_block, signature, claimed ) VALUES ( :sender, :amount, :expiration_block, :signature, :claimed ) """, iou_dict, )
def get(self, token_network_address: TokenNetworkAddress) -> Tuple[dict, int]: iou_request, errors = IOURequest.Schema().load(request.args) if errors: raise exceptions.InvalidRequest(**errors) if not iou_request.is_signature_valid(): raise exceptions.InvalidSignature if iou_request.timestamp < datetime.utcnow() - MAX_AGE_OF_IOU_REQUESTS: raise exceptions.RequestOutdated last_iou = self.pathfinding_service.database.get_iou( sender=iou_request.sender, claimed=False) if last_iou: last_iou = IOU.Schema(strict=True, exclude=["claimed"]).dump(last_iou)[0] return {"last_iou": last_iou}, 200 return {"last_iou": None}, 404
def make_iou(sender_priv_key, receiver, amount=1, expiration_block=MIN_IOU_EXPIRY + 100) -> IOU: iou_dict = { "sender": private_key_to_address(sender_priv_key), "receiver": receiver, "amount": amount, "expiration_block": expiration_block, } iou_dict["signature"] = encode_hex( sign_one_to_n_iou( privatekey=sender_priv_key, sender=iou_dict["sender"], receiver=receiver, amount=amount, expiration=expiration_block, ) ) iou = IOU.Schema().load(iou_dict)[0] iou.claimed = False return iou
def get( self, token_network_address: str # pylint: disable=unused-argument ) -> Tuple[dict, int]: try: iou_request = IOURequest.Schema().load(request.args) except marshmallow.ValidationError as ex: raise exceptions.InvalidRequest(**ex.messages) if not iou_request.is_signature_valid(): raise exceptions.InvalidSignature if iou_request.timestamp < datetime.utcnow() - MAX_AGE_OF_IOU_REQUESTS: raise exceptions.RequestOutdated last_iou = self.pathfinding_service.database.get_iou( sender=iou_request.sender, claimed=False) if last_iou: last_iou = IOU.Schema(exclude=["claimed"]).dump(last_iou) return {"last_iou": last_iou}, 200 return {"last_iou": None}, 404
def make_iou(sender_priv_key, receiver, amount=1, expiration_block=MIN_IOU_EXPIRY + 100) -> IOU: iou_dict = { 'sender': private_key_to_address(sender_priv_key), 'receiver': receiver, 'amount': amount, 'expiration_block': expiration_block, } iou_dict['signature'] = encode_hex( sign_one_to_n_iou( privatekey=sender_priv_key, sender=iou_dict['sender'], receiver=receiver, amount=amount, expiration=expiration_block, )) iou = IOU.Schema().load(iou_dict)[0] iou.claimed = False return iou
def get_ious( self, sender: Optional[Address] = None, claimable_until: Optional[Timestamp] = None, claimed: Optional[bool] = None, claimable_until_after: Optional[Timestamp] = None, claimable_until_before: Optional[Timestamp] = None, amount_at_least: Optional[TokenAmount] = None, ) -> Iterator[IOU]: query = """ SELECT *, (SELECT chain_id FROM blockchain) AS chain_id FROM iou WHERE 1=1 """ args: list = [] if sender is not None: query += " AND sender = ?" args.append(to_checksum_address(sender)) if claimable_until is not None: query += " AND claimable_until = ?" args.append(hex256(claimable_until)) if claimed is not None: query += " AND claimed = ?" args.append(claimed) if claimable_until_before is not None: query += " AND claimable_until < ?" args.append(hex256(claimable_until_before)) if claimable_until_after is not None: query += " AND claimable_until > ?" args.append(hex256(claimable_until_after)) if amount_at_least is not None: query += " AND amount >= ?" args.append(hex256(amount_at_least)) with self._cursor() as cursor: for row in cursor.execute(query, args): iou_dict = dict(zip(row.keys(), row)) iou_dict["receiver"] = to_checksum_address(self.pfs_address) yield IOU.Schema().load(iou_dict)
def get_iou( self, sender: Address, expiration_block: int = None, claimed: bool = None, ) -> Optional[IOU]: query = "SELECT * FROM iou WHERE sender = ?" args: list = [sender] if expiration_block is not None: query += " AND expiration_block = ?" args.append(expiration_block) if claimed is not None: query += " AND claimed = ?" args.append(claimed) row = self.conn.execute(query, args).fetchone() if row is None: return None iou_dict = dict(zip(row.keys(), row)) iou_dict['receiver'] = self.pfs_address return IOU.Schema().load(iou_dict)[0]
def f( sender_priv_key: PrivateKey, receiver: Address, amount=1, claimable_until=1000000000 * 15 + MIN_IOU_EXPIRY, one_to_n_address: Address = one_to_n_contract_address, chain_id: ChainID = ChainID(61), ) -> IOU: receiver_hex: str = to_checksum_address(receiver) iou_dict = { "sender": to_checksum_address(private_key_to_address(sender_priv_key)), "receiver": receiver_hex, "amount": amount, "claimable_until": claimable_until, "one_to_n_address": to_checksum_address(one_to_n_address), "chain_id": chain_id, } iou_dict["signature"] = encode_hex( sign_one_to_n_iou(privatekey=sender_priv_key, **iou_dict)) iou = IOU.Schema().load(iou_dict) iou.claimed = False return iou
def f( sender_priv_key, receiver: Address, amount=1, expiration_block=MIN_IOU_EXPIRY + 100, one_to_n_address=one_to_n_contract.address, chain_id=1, ) -> IOU: receiver_hex: str = to_checksum_address(receiver) iou_dict = { "sender": to_checksum_address(private_key_to_address(sender_priv_key)), "receiver": receiver_hex, "amount": amount, "expiration_block": expiration_block, "one_to_n_address": to_checksum_address(one_to_n_address), "chain_id": chain_id, } iou_dict["signature"] = encode_hex( sign_one_to_n_iou(privatekey=sender_priv_key, **iou_dict)) iou = IOU.Schema().load(iou_dict) iou.claimed = False return iou