Merge pull request #1062 from moonstream-to/verify-call-requests-post

Verify if requests exists before push new list
pull/1064/head
Sergei Sumarokov 2024-05-07 19:16:42 +03:00 zatwierdzone przez GitHub
commit e2fb5b10e5
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
3 zmienionych plików z 105 dodań i 10 usunięć

Wyświetl plik

@ -3,9 +3,9 @@ import json
import logging import logging
import uuid import uuid
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Set, Tuple
from sqlalchemy import func, or_, text from sqlalchemy import func, or_, text, tuple_
from sqlalchemy.dialects.postgresql import insert from sqlalchemy.dialects.postgresql import insert
from sqlalchemy.engine import Row from sqlalchemy.engine import Row
from sqlalchemy.exc import IntegrityError, NoResultFound from sqlalchemy.exc import IntegrityError, NoResultFound
@ -71,6 +71,12 @@ class CallRequestAlreadyRegistered(Exception):
""" """
class CallRequestIdDuplicates(Exception):
"""
Raised when same call request IDs passed in one request.
"""
def parse_registered_contract_response( def parse_registered_contract_response(
obj: Tuple[RegisteredContract, Blockchain] obj: Tuple[RegisteredContract, Blockchain]
) -> data.RegisteredContractResponse: ) -> data.RegisteredContractResponse:
@ -431,6 +437,38 @@ def create_request_calls(
return len(call_specs) return len(call_specs)
def get_call_request_from_tuple(
db_session: Session,
metatx_requester_id: uuid.UUID,
requests: Set[Tuple[str, str]],
contract_id: Optional[uuid.UUID] = None,
contract_address: Optional[str] = None,
) -> List[CallRequest]:
if contract_id is None and contract_address is None:
raise ValueError(
"At least one of contract_id or contract_address must be specified"
)
query = (
db_session.query(CallRequest)
.join(
RegisteredContract,
CallRequest.registered_contract_id == RegisteredContract.id,
)
.filter(RegisteredContract.metatx_requester_id == metatx_requester_id)
.filter(tuple_(CallRequest.caller, CallRequest.request_id).in_(requests))
)
if contract_id is not None:
query = query.filter(RegisteredContract.id == contract_id)
if contract_address is not None:
query = query.filter(
RegisteredContract.address == Web3.toChecksumAddress(contract_address)
)
existing_requests = query.all()
return existing_requests
def get_call_request( def get_call_request(
db_session: Session, db_session: Session,
request_id: uuid.UUID, request_id: uuid.UUID,

Wyświetl plik

@ -1,6 +1,6 @@
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from typing import Any, Dict, List, Optional, Set from typing import Any, Dict, List, Optional, Set, Tuple
from uuid import UUID from uuid import UUID
from bugout.data import BugoutResource from bugout.data import BugoutResource
@ -329,6 +329,10 @@ class CallRequestResponse(BaseModel):
return Web3.toChecksumAddress(v) return Web3.toChecksumAddress(v)
class CallRequestsCheck(BaseModel):
existing_requests: Set[Tuple[str, str]] = Field(default_factory=set)
class CompleteCallRequestsAPIRequest(BaseModel): class CompleteCallRequestsAPIRequest(BaseModel):
tx_hash: str tx_hash: str

Wyświetl plik

@ -5,14 +5,16 @@ Moonstream users can register contracts on Moonstream Engine. This allows them t
as part of their chain-adjacent activities (like performing signature-based token distributions on the as part of their chain-adjacent activities (like performing signature-based token distributions on the
Dropper contract). Dropper contract).
""" """
import logging import logging
from typing import Dict, List, Optional from typing import Dict, List, Optional, Set, Tuple
from uuid import UUID from uuid import UUID
from bugout.data import BugoutUser from bugout.data import BugoutUser
from fastapi import Body, Depends, FastAPI, Form, Path, Query, Request from fastapi import Body, Depends, FastAPI, Form, Path, Query, Request
from sqlalchemy.exc import NoResultFound from sqlalchemy.exc import NoResultFound
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from web3 import Web3
from .. import contracts_actions, data, db from .. import contracts_actions, data, db
from ..middleware import ( from ..middleware import (
@ -316,6 +318,57 @@ async def list_requests_route(
return [contracts_actions.parse_call_request_response(r) for r in requests] return [contracts_actions.parse_call_request_response(r) for r in requests]
@app.get(
"/requests/check",
response_model=data.CallRequestsCheck,
)
async def check_requests_route(
request_data: data.CreateCallRequestsAPIRequest = Body(...),
user: BugoutUser = Depends(request_user_auth),
db_session: Session = Depends(db.yield_db_session),
) -> data.CallRequestsCheck:
"""
Implemented for pre-check until list of requests to be pushed into database.
"""
try:
incoming_requests: Set[Tuple[str, str]] = set()
incoming_request_ids: List[str] = []
for r in request_data.specifications:
caller_addr = Web3.toChecksumAddress(r.caller)
incoming_requests.add((caller_addr, r.request_id))
incoming_request_ids.append(r.request_id)
if len(incoming_requests) != len(incoming_request_ids):
raise contracts_actions.CallRequestIdDuplicates(
"There are same call_request_id's in one request"
)
existing_requests = contracts_actions.get_call_request_from_tuple(
db_session=db_session,
metatx_requester_id=user.id,
requests=incoming_requests,
contract_id=request_data.contract_id,
contract_address=request_data.contract_address,
)
except contracts_actions.CallRequestIdDuplicates:
raise EngineHTTPException(
status_code=400, detail="There are same call_request_id's in one request"
)
except Exception as err:
logger.error(repr(err))
raise EngineHTTPException(status_code=500)
existing_requests_set: Set[Tuple[str, str]] = set()
if len(existing_requests) != 0:
existing_requests_set = {
(er.caller, str(er.request_id)) for er in existing_requests
}
return data.CallRequestsCheck(
existing_requests=existing_requests_set,
)
@app.get( @app.get(
"/requests/{request_id}", tags=["requests"], response_model=data.CallRequestResponse "/requests/{request_id}", tags=["requests"], response_model=data.CallRequestResponse
) )
@ -348,7 +401,7 @@ async def get_request(
@app.post("/requests", tags=["requests"], response_model=int) @app.post("/requests", tags=["requests"], response_model=int)
async def create_requests( async def create_requests(
data: data.CreateCallRequestsAPIRequest = Body(...), request_data: data.CreateCallRequestsAPIRequest = Body(...),
user: BugoutUser = Depends(request_user_auth), user: BugoutUser = Depends(request_user_auth),
db_session: Session = Depends(db.yield_db_session), db_session: Session = Depends(db.yield_db_session),
) -> int: ) -> int:
@ -361,11 +414,11 @@ async def create_requests(
num_requests = contracts_actions.create_request_calls( num_requests = contracts_actions.create_request_calls(
db_session=db_session, db_session=db_session,
metatx_requester_id=user.id, metatx_requester_id=user.id,
registered_contract_id=data.contract_id, registered_contract_id=request_data.contract_id,
contract_address=data.contract_address, contract_address=request_data.contract_address,
call_specs=data.specifications, call_specs=request_data.specifications,
ttl_days=data.ttl_days, ttl_days=request_data.ttl_days,
live_at=data.live_at, live_at=request_data.live_at,
) )
except contracts_actions.InvalidAddressFormat as err: except contracts_actions.InvalidAddressFormat as err:
raise EngineHTTPException( raise EngineHTTPException(