Merge pull request #932 from moonstream-to/complete-call-request

Complete call request
pull/1014/head
Sergei Sumarokov 2024-02-01 13:23:21 +03:00 zatwierdzone przez GitHub
commit 9ebe300e45
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
8 zmienionych plików z 408 dodań i 113 usunięć

Wyświetl plik

@ -0,0 +1,30 @@
"""Tx hash for call requests
Revision ID: 7191eb70e99e
Revises: 6d07739cb13e
Create Date: 2023-10-04 11:23:12.516797
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '7191eb70e99e'
down_revision = '6d07739cb13e'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('call_requests', sa.Column('tx_hash', sa.VARCHAR(length=256), nullable=True))
op.create_unique_constraint(op.f('uq_call_requests_tx_hash'), 'call_requests', ['tx_hash'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(op.f('uq_call_requests_tx_hash'), 'call_requests', type_='unique')
op.drop_column('call_requests', 'tx_hash')
# ### end Alembic commands ###

Wyświetl plik

@ -19,23 +19,17 @@ import argparse
import base64
import json
import time
from typing import Any, cast, Dict
from typing import Any, Dict, Optional, cast
import eth_keys
from eip712.messages import EIP712Message, _hash_eip191_message
from eth_account import Account
from eth_account._utils.signing import sign_message_hash
import eth_keys
from eth_typing import ChecksumAddress
from hexbytes import HexBytes
from web3 import Web3
AUTH_PAYLOAD_NAME = "MoonstreamAuthorization"
AUTH_VERSION = "1"
# By default, authorizations will remain active for 24 hours.
DEFAULT_INTERVAL = 60 * 60 * 24
class MoonstreamAuthorizationVerificationError(Exception):
"""
Raised when invalid signer is provided.
@ -48,12 +42,48 @@ class MoonstreamAuthorizationExpired(Exception):
"""
class MoonstreamAuthorization(EIP712Message):
_name_: "string"
_version_: "string"
class MoonstreamAuthorizationStructureError(Exception):
"""
Raised when signature has incorrect structure.
"""
address: "address"
deadline: "uint256"
class MoonstreamAuthorization(EIP712Message):
_name_: "string" # type: ignore
_version_: "string" # type: ignore
address: "address" # type: ignore
deadline: "uint256" # type: ignore
class MetaTXAuthorization(EIP712Message):
_name_: "string" # type: ignore
_version_: "string" # type: ignore
caller: "address" # type: ignore
expires_at: "uint256" # type: ignore
EIP712_AUTHORIZATION_TYPES = {
"MoonstreamAuthorization": {
"name": "MoonstreamAuthorization",
"version": "1",
"eip712_message_class": MoonstreamAuthorization,
"primary_types": [
{"name": "address", "type": "address"},
{"name": "deadline", "type": int},
],
},
"MetaTXAuthorization": {
"name": "MetaTXAuthorization",
"version": "1",
"eip712_message_class": MetaTXAuthorization,
"primary_types": [
{"name": "caller", "type": "address"},
{"name": "expires_at", "type": int},
],
},
}
def sign_message(message_hash_bytes: HexBytes, private_key: HexBytes) -> HexBytes:
@ -64,52 +94,77 @@ def sign_message(message_hash_bytes: HexBytes, private_key: HexBytes) -> HexByte
return signed_message_bytes
def authorize(deadline: int, address: str, private_key: HexBytes) -> Dict[str, Any]:
message = MoonstreamAuthorization(
_name_=AUTH_PAYLOAD_NAME,
_version_=AUTH_VERSION,
address=address,
deadline=deadline,
)
def authorize(
authorization_type: Dict[str, Any],
primary_types: Dict[str, Any],
private_key: HexBytes,
signature_name_output: str,
) -> Dict[str, Any]:
# Initializing instance of EIP712Message class
attrs: Dict[str, Any] = {
"_name_": authorization_type["name"],
"_version_": authorization_type["version"],
}
attrs.update(primary_types)
message = authorization_type["eip712_message_class"](**attrs)
# Generating message hash and signature
msg_hash_bytes = HexBytes(_hash_eip191_message(message.signable_message))
signed_message = sign_message(msg_hash_bytes, private_key)
api_payload: Dict[str, Any] = {
"address": address,
"deadline": deadline,
"signed_message": signed_message.hex(),
}
api_payload: Dict[str, Any] = {signature_name_output: signed_message.hex()}
api_payload.update(primary_types)
return api_payload
def verify(authorization_payload: Dict[str, Any]) -> bool:
def verify(
authorization_type: Dict[str, Any],
authorization_payload: Dict[str, Any],
signature_name_input: str,
) -> bool:
"""
Verifies provided signature signer by correct address.
**Important** Assume that not address field is timefield (live_at, expires_at, deadline, etc)
"""
# Initializing instance of EIP712Message class
attrs: Dict[str, Any] = {
"_name_": authorization_type["name"],
"_version_": authorization_type["version"],
}
time_now = int(time.time())
web3_client = Web3()
address = Web3.toChecksumAddress(cast(str, authorization_payload["address"]))
deadline = cast(int, authorization_payload["deadline"])
signature = cast(str, authorization_payload["signed_message"])
message = MoonstreamAuthorization(
_name_=AUTH_PAYLOAD_NAME,
_version_=AUTH_VERSION,
address=address,
deadline=deadline,
)
address: Optional[ChecksumAddress] = None
time_field: Optional[int] = None
for pt in authorization_type["primary_types"]:
pt_name = pt["name"]
pt_type = pt["type"]
if pt_type == "address":
address = Web3.toChecksumAddress(cast(str, authorization_payload[pt_name]))
attrs[pt_name] = address
else:
time_field = cast(pt_type, authorization_payload[pt_name])
attrs[pt_name] = time_field
if address is None or time_field is None:
raise MoonstreamAuthorizationStructureError(
"Field address or time_field could not be None"
)
message = authorization_type["eip712_message_class"](**attrs)
signature = cast(str, authorization_payload[signature_name_input])
signer_address = web3_client.eth.account.recover_message(
message.signable_message, signature=signature
)
if signer_address != address:
raise MoonstreamAuthorizationVerificationError("Invalid signer")
if deadline < time_now:
raise MoonstreamAuthorizationExpired("Deadline exceeded")
if time_field < time_now:
raise MoonstreamAuthorizationExpired("Time field exceeded")
return True
@ -121,15 +176,37 @@ def decrypt_keystore(keystore_path: str, password: str) -> HexBytes:
def handle_authorize(args: argparse.Namespace) -> None:
address, private_key = decrypt_keystore(args.signer, args.password)
authorization = authorize(args.deadline, address, private_key)
if args.authorization_type not in EIP712_AUTHORIZATION_TYPES:
raise Exception("Provided unsupported EIP712 Authorization type")
authorization_type = EIP712_AUTHORIZATION_TYPES[args.authorization_type]
primary_types = json.loads(args.primary_types)
for ptk in authorization_type["primary_types"]:
if ptk["name"] not in primary_types:
raise Exception(f"Lost primary type: {ptk}")
_, private_key = decrypt_keystore(args.signer, args.password)
authorization = authorize(
authorization_type=authorization_type,
primary_types=primary_types,
private_key=private_key,
signature_name_output=args.signature_name_output,
)
print(json.dumps(authorization))
def handle_verify(args: argparse.Namespace) -> None:
if args.authorization_type not in EIP712_AUTHORIZATION_TYPES:
raise Exception("Provided unsupported EIP712 Authorization type")
authorization_type = EIP712_AUTHORIZATION_TYPES[args.authorization_type]
payload_json = base64.decodebytes(args.payload).decode("utf-8")
payload = json.loads(payload_json)
verify(payload)
verify(
authorization_type=authorization_type,
authorization_payload=payload,
signature_name_input=args.signature_name_input,
)
print("Verified!")
@ -140,13 +217,6 @@ def generate_cli() -> argparse.ArgumentParser:
subcommands = parser.add_subparsers()
authorize_parser = subcommands.add_parser("authorize")
authorize_parser.add_argument(
"-t",
"--deadline",
type=int,
default=int(time.time()) + DEFAULT_INTERVAL,
help="Authorization deadline (seconds since epoch timestamp).",
)
authorize_parser.add_argument(
"-s",
"--signer",
@ -159,6 +229,30 @@ def generate_cli() -> argparse.ArgumentParser:
required=False,
help="(Optional) password for signing account. If you don't provide it here, you will be prompte for it.",
)
authorize_parser.add_argument(
"-t",
"--authorization-type",
required=True,
choices=[k for k in EIP712_AUTHORIZATION_TYPES.keys()],
help="One of supported EIP712 Message authorization types",
)
authorize_parser.add_argument(
"--primary-types",
required=True,
help="Primary types for specified EIP712 Message authorization in JSON format {0}. Available keys: {1}".format(
{"name_1": "value", "name_2": "value"},
[
f"{v['primary_types']} for {k}"
for k, v in EIP712_AUTHORIZATION_TYPES.items()
],
),
)
authorize_parser.add_argument(
"--signature-name-output",
type=str,
default="signed_message",
help="Key in output dictionary of signature",
)
authorize_parser.set_defaults(func=handle_authorize)
verify_parser = subcommands.add_parser("verify")
@ -168,6 +262,19 @@ def generate_cli() -> argparse.ArgumentParser:
required=True,
help="Base64-encoded payload to verify",
)
verify_parser.add_argument(
"-t",
"--authorization-type",
required=True,
choices=[k for k in EIP712_AUTHORIZATION_TYPES.keys()],
help="One of supported EIP712 Message authorization types",
)
verify_parser.add_argument(
"--signature-name-input",
type=str,
default="signed_message",
help="Key for signature in payload",
)
verify_parser.set_defaults(func=handle_verify)
return parser

Wyświetl plik

@ -100,6 +100,7 @@ def parse_call_request_response(
method=obj[0].method,
request_id=str(obj[0].request_id),
parameters=obj[0].parameters,
tx_hash=obj[0].tx_hash,
expires_at=obj[0].expires_at,
live_at=obj[0].live_at,
created_at=obj[0].created_at,
@ -480,7 +481,7 @@ def list_call_requests(
limit: int = 10,
offset: Optional[int] = None,
show_expired: bool = False,
show_before_live_at: bool = False,
live_after: Optional[int] = None,
metatx_requester_id: Optional[uuid.UUID] = None,
) -> List[Row[Tuple[CallRequest, RegisteredContract, CallRequestType]]]:
"""
@ -526,15 +527,17 @@ def list_call_requests(
query = query.filter(
CallRequest.metatx_requester_id == metatx_requester_id,
)
if not show_before_live_at:
query = query.filter(
or_(CallRequest.live_at < func.now(), CallRequest.live_at == None)
)
else:
query = query.filter(
or_(CallRequest.live_at < func.now(), CallRequest.live_at == None)
)
if live_after is not None:
assert live_after == int(live_after)
if live_after <= 0:
raise ValueError("live_after must be positive")
query = query.filter(CallRequest.live_at >= datetime.fromtimestamp(live_after))
if offset is not None:
query = query.offset(offset)
@ -579,6 +582,46 @@ def delete_requests(
return requests_to_delete_num
def complete_call_request(
db_session: Session,
tx_hash: str,
call_request_id: uuid.UUID,
caller: str,
) -> CallRequest:
results = (
db_session.query(CallRequest, RegisteredContract)
.join(
RegisteredContract,
CallRequest.registered_contract_id == RegisteredContract.id,
)
.filter(CallRequest.id == call_request_id)
.filter(CallRequest.caller == caller)
.all()
)
if len(results) == 0:
raise CallRequestNotFound("Call request with given ID not found")
elif len(results) != 1:
raise Exception(
f"Incorrect number of results found for request_id {call_request_id}"
)
call_request, registered_contract = results[0]
call_request.tx_hash = tx_hash
try:
db_session.add(call_request)
db_session.commit()
except Exception as err:
logger.error(
f"complete_call_request -- error updating in database: {repr(err)}"
)
db_session.rollback()
raise
return (call_request, registered_contract)
def handle_register(args: argparse.Namespace) -> None:
"""
Handles the register command.

Wyświetl plik

@ -306,6 +306,7 @@ class CallRequestResponse(BaseModel):
method: str
request_id: str
parameters: Dict[str, Any]
tx_hash: Optional[str] = None
expires_at: Optional[datetime] = None
live_at: Optional[datetime] = None
created_at: datetime
@ -328,6 +329,10 @@ class CallRequestResponse(BaseModel):
return Web3.toChecksumAddress(v)
class CompleteCallRequestsAPIRequest(BaseModel):
tx_hash: str
class QuartilesResponse(BaseModel):
percentile_25: Dict[str, Any]
percentile_50: Dict[str, Any]

Wyświetl plik

@ -6,18 +6,24 @@ from uuid import UUID
from bugout.data import BugoutResource, BugoutResources, BugoutUser
from bugout.exceptions import BugoutResponseException
from fastapi import Header, HTTPException, Request, Response
from eip712.messages import EIP712Message, _hash_eip191_message
from eth_account.messages import encode_defunct
from fastapi import Depends, Header, HTTPException, Request, Response
from fastapi.security import OAuth2PasswordBearer
from hexbytes import HexBytes
from pydantic import AnyHttpUrl, parse_obj_as
from starlette.datastructures import Headers
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.middleware.cors import CORSMiddleware
from starlette.responses import Response
from starlette.types import ASGIApp
from web3 import Web3
from web3.auto import w3 as w3_auto
from . import data
from .auth import (
EIP712_AUTHORIZATION_TYPES,
MoonstreamAuthorizationExpired,
MoonstreamAuthorizationStructureError,
MoonstreamAuthorizationVerificationError,
verify,
)
@ -34,6 +40,8 @@ from .settings import bugout_client as bc
logger = logging.getLogger(__name__)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class InvalidAuthHeaderFormat(Exception):
"""
@ -77,7 +85,44 @@ def bugout_auth(token: str) -> BugoutUser:
return user
async def user_for_auth_header(
def brood_auth(token: UUID) -> BugoutUser:
try:
user: BugoutUser = bugout_auth(token=token)
except BugoutUnverifiedAuth:
logger.info(f"Attempted access by unverified Brood account: {user.id}")
raise EngineHTTPException(
status_code=403,
detail="Only verified accounts can have access",
)
except BugoutAuthWrongApp:
raise EngineHTTPException(
status_code=403,
detail="User does not belong to this application",
)
except BugoutResponseException as e:
raise EngineHTTPException(
status_code=e.status_code,
detail=e.detail,
)
except Exception as e:
logger.error(f"Error processing Brood response: {str(e)}")
raise EngineHTTPException(
status_code=500,
detail="Internal server error",
)
return user
async def request_user_auth(
token: UUID = Depends(oauth2_scheme),
) -> BugoutUser:
user = brood_auth(token=token)
return user
async def request_none_or_user_auth(
authorization: str = Header(None),
) -> Optional[BugoutUser]:
"""
@ -85,39 +130,74 @@ async def user_for_auth_header(
"""
user: Optional[BugoutUser] = None
if authorization is not None:
user_token: str = ""
token: str = ""
try:
_, user_token = parse_auth_header(auth_header=authorization)
_, token = parse_auth_header(auth_header=authorization)
except InvalidAuthHeaderFormat:
raise EngineHTTPException(
status_code=403, detail="Wrong authorization header"
)
except Exception as e:
logger.error(f"Error processing Brood response: {str(e)}")
logger.error(f"Error parsing auth header: {str(e)}")
raise EngineHTTPException(status_code=500, detail="Internal server error")
if user_token != "":
try:
user: BugoutUser = bugout_auth(token=user_token)
except BugoutUnverifiedAuth:
logger.info(f"Attempted access by unverified Brood account: {user.id}")
raise EngineHTTPException(
status_code=403,
detail="Only verified accounts can have access",
)
except BugoutAuthWrongApp:
raise EngineHTTPException(
status_code=403, detail="User does not belong to this application"
)
except BugoutResponseException as e:
raise HTTPException(status_code=e.status_code, detail=e.detail)
except Exception as e:
logger.error(f"Error processing Brood response: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
if token != "":
user = brood_auth(token=token)
return user
async def metatx_verify_header(
authorization: str = Header(None),
) -> Optional[Dict[str, Any]]:
message: Optional[Dict[str, Any]] = None
if authorization is not None:
try:
auth_format, user_token = parse_auth_header(auth_header=authorization)
except InvalidAuthHeaderFormat:
raise EngineHTTPException(
status_code=403, detail="Wrong authorization header"
)
except Exception as e:
logger.error(f"Error parsing auth header: {str(e)}")
raise EngineHTTPException(status_code=500, detail="Internal server error")
if auth_format != "metatx":
raise EngineHTTPException(
status_code=403,
detail=f"Wrong authorization header format: {auth_format}",
)
try:
json_payload_str = base64.b64decode(user_token).decode("utf-8")
payload = json.loads(json_payload_str)
verify(
authorization_type=EIP712_AUTHORIZATION_TYPES["MetaTXAuthorization"],
authorization_payload=payload,
signature_name_input="signature",
)
message = {
"caller": Web3.toChecksumAddress(payload.get("caller")),
"expires_at": payload.get("expires_at"),
}
except MoonstreamAuthorizationVerificationError as e:
logger.info("MetaTX authorization verification error: %s", e)
raise EngineHTTPException(status_code=403, detail="Invalid signer")
except MoonstreamAuthorizationExpired as e:
logger.info("MetaTX authorization expired: %s", e)
raise EngineHTTPException(status_code=403, detail="Authorization expired")
except MoonstreamAuthorizationStructureError as e:
logger.info("MetaTX authorization incorrect structure error: %s", e)
raise EngineHTTPException(
status_code=403, detail="Incorrect signature structure"
)
except Exception as e:
logger.error("Unexpected exception: %s", e)
raise EngineHTTPException(status_code=500, detail="Internal server error")
return message
class BroodAuthMiddleware(BaseHTTPMiddleware):
"""
Checks the authorization header on the request. If it represents a verified Brood user,
@ -155,7 +235,7 @@ class BroodAuthMiddleware(BaseHTTPMiddleware):
except InvalidAuthHeaderFormat:
return Response(status_code=403, content="Wrong authorization header")
except Exception as e:
logger.error(f"Error processing Brood response: {str(e)}")
logger.error(f"Error parsing auth header: {str(e)}")
return Response(status_code=500, content="Internal server error")
try:
@ -226,9 +306,15 @@ class EngineAuthMiddleware(BaseHTTPMiddleware):
authorization_header_components[-1]
).decode("utf-8")
json_payload = json.loads(json_payload_str)
verified = verify(json_payload)
address = json_payload.get("address")
payload = json.loads(json_payload_str)
verified = verify(
authorization_type=EIP712_AUTHORIZATION_TYPES[
"MoonstreamAuthorization"
],
authorization_payload=payload,
signature_name_input="signed_message",
)
address = payload.get("address")
if address is not None:
address = Web3.toChecksumAddress(address)
else:

Wyświetl plik

@ -315,6 +315,7 @@ class CallRequest(Base):
method = Column(String, nullable=False, index=True)
request_id = Column(DECIMAL, nullable=False, index=True)
parameters = Column(JSONB, nullable=False)
tx_hash = Column(VARCHAR(256), unique=True, nullable=True)
expires_at = Column(DateTime(timezone=True), nullable=True, index=True)
live_at = Column(DateTime(timezone=True), nullable=True)

Wyświetl plik

@ -10,16 +10,17 @@ from typing import Dict, List, Optional
from uuid import UUID
from bugout.data import BugoutUser
from fastapi import Body, Depends, FastAPI, Path, Query, Request
from fastapi import Body, Depends, FastAPI, Form, Path, Query, Request
from sqlalchemy.exc import NoResultFound
from sqlalchemy.orm import Session
from .. import contracts_actions, data, db
from ..middleware import (
BroodAuthMiddleware,
BugoutCORSMiddleware,
EngineHTTPException,
user_for_auth_header,
metatx_verify_header,
request_none_or_user_auth,
request_user_auth,
)
from ..settings import DOCS_TARGET_PATH
from ..version import VERSION
@ -40,15 +41,6 @@ tags_metadata = [
]
whitelist_paths = {
"/metatx/openapi.json": "GET",
f"/metatx/{DOCS_TARGET_PATH}": "GET",
"/metatx/blockchains": "GET",
"/metatx/contracts/types": "GET",
"/metatx/requests/types": "GET",
"/metatx/requests": "GET", # Controls by custom authentication check
}
app = FastAPI(
title=TITLE,
description=DESCRIPTION,
@ -59,9 +51,6 @@ app = FastAPI(
redoc_url=f"/{DOCS_TARGET_PATH}",
)
app.add_middleware(BroodAuthMiddleware, whitelist=whitelist_paths)
app.add_middleware(
BugoutCORSMiddleware,
allow_credentials=True,
@ -95,11 +84,11 @@ async def blockchains_route(
response_model=List[data.RegisteredContractResponse],
)
async def list_registered_contracts_route(
request: Request,
blockchain: Optional[str] = Query(None),
address: Optional[str] = Query(None),
limit: int = Query(10),
offset: Optional[int] = Query(None),
user: BugoutUser = Depends(request_user_auth),
db_session: Session = Depends(db.yield_db_read_only_session),
) -> List[data.RegisteredContractResponse]:
"""
@ -109,7 +98,7 @@ async def list_registered_contracts_route(
registered_contracts_with_blockchain = (
contracts_actions.lookup_registered_contracts(
db_session=db_session,
metatx_requester_id=request.state.user.id,
metatx_requester_id=user.id,
blockchain=blockchain,
address=address,
limit=limit,
@ -132,8 +121,8 @@ async def list_registered_contracts_route(
response_model=data.RegisteredContractResponse,
)
async def get_registered_contract_route(
request: Request,
contract_id: UUID = Path(...),
user: BugoutUser = Depends(request_user_auth),
db_session: Session = Depends(db.yield_db_read_only_session),
) -> List[data.RegisteredContractResponse]:
"""
@ -142,7 +131,7 @@ async def get_registered_contract_route(
try:
contract_with_blockchain = contracts_actions.get_registered_contract(
db_session=db_session,
metatx_requester_id=request.state.user.id,
metatx_requester_id=user.id,
contract_id=contract_id,
)
except NoResultFound:
@ -163,8 +152,8 @@ async def get_registered_contract_route(
"/contracts", tags=["contracts"], response_model=data.RegisteredContractResponse
)
async def register_contract_route(
request: Request,
contract: data.RegisterContractRequest = Body(...),
user: BugoutUser = Depends(request_user_auth),
db_session: Session = Depends(db.yield_db_session),
) -> data.RegisteredContractResponse:
"""
@ -173,7 +162,7 @@ async def register_contract_route(
try:
contract_with_blockchain = contracts_actions.register_contract(
db_session=db_session,
metatx_requester_id=request.state.user.id,
metatx_requester_id=user.id,
blockchain_name=contract.blockchain,
address=contract.address,
title=contract.title,
@ -204,15 +193,15 @@ async def register_contract_route(
response_model=data.RegisteredContractResponse,
)
async def update_contract_route(
request: Request,
contract_id: UUID = Path(...),
update_info: data.UpdateContractRequest = Body(...),
user: BugoutUser = Depends(request_user_auth),
db_session: Session = Depends(db.yield_db_session),
) -> data.RegisteredContractResponse:
try:
contract_with_blockchain = contracts_actions.update_registered_contract(
db_session=db_session,
metatx_requester_id=request.state.user.id,
metatx_requester_id=user.id,
contract_id=contract_id,
title=update_info.title,
description=update_info.description,
@ -239,8 +228,8 @@ async def update_contract_route(
response_model=data.RegisteredContractResponse,
)
async def delete_contract_route(
request: Request,
contract_id: UUID = Path(...),
user: BugoutUser = Depends(request_user_auth),
db_session: Session = Depends(db.yield_db_session),
) -> data.RegisteredContractResponse:
"""
@ -249,7 +238,7 @@ async def delete_contract_route(
try:
deleted_contract_with_blockchain = contracts_actions.delete_registered_contract(
db_session=db_session,
metatx_requester_id=request.state.user.id,
metatx_requester_id=user.id,
registered_contract_id=contract_id,
)
except Exception as err:
@ -296,8 +285,8 @@ async def list_requests_route(
limit: int = Query(100),
offset: Optional[int] = Query(None),
show_expired: bool = Query(False),
show_before_live_at: bool = Query(False),
user: Optional[BugoutUser] = Depends(user_for_auth_header),
live_after: Optional[int] = Query(None),
user: Optional[BugoutUser] = Depends(request_none_or_user_auth),
db_session: Session = Depends(db.yield_db_read_only_session),
) -> List[data.CallRequestResponse]:
"""
@ -314,7 +303,7 @@ async def list_requests_route(
limit=limit,
offset=offset,
show_expired=show_expired,
show_before_live_at=show_before_live_at,
live_after=live_after,
metatx_requester_id=user.id if user is not None else None,
)
except ValueError as e:
@ -332,6 +321,7 @@ async def list_requests_route(
)
async def get_request(
request_id: UUID = Path(...),
_: BugoutUser = Depends(request_user_auth),
db_session: Session = Depends(db.yield_db_read_only_session),
) -> List[data.CallRequestResponse]:
"""
@ -358,8 +348,8 @@ async def get_request(
@app.post("/requests", tags=["requests"], response_model=int)
async def create_requests(
request: Request,
data: data.CreateCallRequestsAPIRequest = Body(...),
user: BugoutUser = Depends(request_user_auth),
db_session: Session = Depends(db.yield_db_session),
) -> int:
"""
@ -370,7 +360,7 @@ async def create_requests(
try:
num_requests = contracts_actions.create_request_calls(
db_session=db_session,
metatx_requester_id=request.state.user.id,
metatx_requester_id=user.id,
registered_contract_id=data.contract_id,
contract_address=data.contract_address,
call_specs=data.specifications,
@ -411,8 +401,8 @@ async def create_requests(
@app.delete("/requests", tags=["requests"], response_model=int)
async def delete_requests(
request: Request,
request_ids: List[UUID] = Body(...),
user: BugoutUser = Depends(request_user_auth),
db_session: Session = Depends(db.yield_db_session),
) -> int:
"""
@ -421,7 +411,7 @@ async def delete_requests(
try:
deleted_requests = contracts_actions.delete_requests(
db_session=db_session,
metatx_requester_id=request.state.user.id,
metatx_requester_id=user.id,
request_ids=request_ids,
)
except Exception as err:
@ -429,3 +419,32 @@ async def delete_requests(
raise EngineHTTPException(status_code=500)
return deleted_requests
@app.post("/requests/{request_id}/complete", tags=["requests"])
async def complete_call_request_route(
complete_request: data.CompleteCallRequestsAPIRequest = Body(...),
request_id: UUID = Path(...),
message=Depends(metatx_verify_header),
db_session: Session = Depends(db.yield_db_session),
):
"""
Set tx hash for specified call_request by verified account.
"""
try:
request = contracts_actions.complete_call_request(
db_session=db_session,
tx_hash=complete_request.tx_hash,
call_request_id=request_id,
caller=message["caller"],
)
except contracts_actions.CallRequestNotFound:
raise EngineHTTPException(
status_code=404,
detail="There is no call request with that ID.",
)
except Exception as e:
logger.error(repr(e))
raise EngineHTTPException(status_code=500)
return contracts_actions.parse_call_request_response(request)

Wyświetl plik

@ -0,0 +1,4 @@
[mypy]
[mypy-eth_keys.*]
ignore_missing_imports = True