kopia lustrzana https://github.com/bugout-dev/moonstream
Merge branch 'main' into arb-sepolia-crawlers
commit
64a4036fd7
|
@ -0,0 +1,28 @@
|
|||
"""Live at for metatx
|
||||
|
||||
Revision ID: 6d07739cb13e
|
||||
Revises: 71e888082a6d
|
||||
Create Date: 2023-12-06 14:33:04.814144
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '6d07739cb13e'
|
||||
down_revision = '71e888082a6d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('call_requests', sa.Column('live_at', sa.DateTime(timezone=True), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('call_requests', 'live_at')
|
||||
# ### end Alembic commands ###
|
|
@ -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 ###
|
|
@ -0,0 +1,50 @@
|
|||
"""leaderboard metadata
|
||||
|
||||
Revision ID: 71e888082a6d
|
||||
Revises: cc80e886e153
|
||||
Create Date: 2023-11-15 13:21:16.108399
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "71e888082a6d"
|
||||
down_revision = "cc80e886e153"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column(
|
||||
"leaderboards",
|
||||
sa.Column(
|
||||
"blockchain_ids",
|
||||
sa.ARRAY(sa.Integer()),
|
||||
nullable=False,
|
||||
server_default="{}",
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"leaderboards",
|
||||
sa.Column(
|
||||
"wallet_connect", sa.Boolean(), nullable=False, server_default="false"
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"leaderboards",
|
||||
sa.Column(
|
||||
"columns_names", postgresql.JSONB(astext_type=sa.Text()), nullable=True
|
||||
),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column("leaderboards", "columns_names")
|
||||
op.drop_column("leaderboards", "wallet_connect")
|
||||
op.drop_column("leaderboards", "blockchain_ids")
|
||||
# ### end Alembic commands ###
|
|
@ -5,7 +5,13 @@ from typing import List, Any, Optional, Dict, Union, Tuple, cast
|
|||
import uuid
|
||||
import logging
|
||||
|
||||
from bugout.data import BugoutResource, BugoutSearchResult
|
||||
from bugout.data import (
|
||||
BugoutResource,
|
||||
BugoutSearchResult,
|
||||
ResourcePermissions,
|
||||
HolderType,
|
||||
BugoutResourceHolder,
|
||||
)
|
||||
from eth_typing import Address
|
||||
from hexbytes import HexBytes
|
||||
import requests # type: ignore
|
||||
|
@ -16,7 +22,14 @@ from sqlalchemy.engine import Row
|
|||
from web3 import Web3
|
||||
from web3.types import ChecksumAddress
|
||||
|
||||
from .data import Score, LeaderboardScore, LeaderboardConfigUpdate, LeaderboardConfig
|
||||
from .data import (
|
||||
Score,
|
||||
LeaderboardScore,
|
||||
LeaderboardConfigUpdate,
|
||||
LeaderboardConfig,
|
||||
LeaderboardPosition,
|
||||
ColumnsNames,
|
||||
)
|
||||
from .contracts import Dropper_interface, ERC20_interface, Terminus_interface
|
||||
from .models import (
|
||||
DropperClaimant,
|
||||
|
@ -96,6 +109,10 @@ class LeaderboardVersionNotFound(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class LeaderboardAssignResourceError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
BATCH_SIGNATURE_PAGE_SIZE = 500
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -1268,7 +1285,7 @@ def get_leaderboard_score(
|
|||
|
||||
def get_leaderboard_positions(
|
||||
db_session: Session,
|
||||
leaderboard_id,
|
||||
leaderboard_id: uuid.UUID,
|
||||
limit: int,
|
||||
offset: int,
|
||||
version_number: Optional[int] = None,
|
||||
|
@ -1481,31 +1498,47 @@ def create_leaderboard(
|
|||
title: str,
|
||||
description: Optional[str],
|
||||
token: Optional[Union[uuid.UUID, str]] = None,
|
||||
wallet_connect: bool = False,
|
||||
blockchain_ids: List[int] = [],
|
||||
columns_names: ColumnsNames = None,
|
||||
) -> Leaderboard:
|
||||
"""
|
||||
Create a leaderboard
|
||||
"""
|
||||
|
||||
if columns_names is not None:
|
||||
columns_names = columns_names.dict()
|
||||
|
||||
if not token:
|
||||
token = uuid.UUID(MOONSTREAM_ADMIN_ACCESS_TOKEN)
|
||||
try:
|
||||
leaderboard = Leaderboard(title=title, description=description)
|
||||
# deduplicate and sort
|
||||
blockchain_ids = sorted(list(set(blockchain_ids)))
|
||||
|
||||
leaderboard = Leaderboard(
|
||||
title=title,
|
||||
description=description,
|
||||
wallet_connect=wallet_connect,
|
||||
blockchain_ids=blockchain_ids,
|
||||
columns_names=columns_names,
|
||||
)
|
||||
db_session.add(leaderboard)
|
||||
db_session.commit()
|
||||
|
||||
user = None
|
||||
if token is not None:
|
||||
user = bc.get_user(token=token)
|
||||
|
||||
resource = create_leaderboard_resource(
|
||||
leaderboard_id=str(leaderboard.id),
|
||||
token=token,
|
||||
user_id=str(user.id) if user is not None else None,
|
||||
)
|
||||
|
||||
leaderboard.resource_id = resource.id
|
||||
|
||||
db_session.commit()
|
||||
except Exception as e:
|
||||
db_session.rollback()
|
||||
logger.error(f"Error creating leaderboard: {e}")
|
||||
raise LeaderboardCreateError(f"Error creating leaderboard: {e}")
|
||||
|
||||
return leaderboard
|
||||
|
||||
|
||||
|
@ -1548,6 +1581,9 @@ def update_leaderboard(
|
|||
leaderboard_id: uuid.UUID,
|
||||
title: Optional[str],
|
||||
description: Optional[str],
|
||||
wallet_connect: Optional[bool],
|
||||
blockchain_ids: Optional[List[int]],
|
||||
columns_names: Optional[ColumnsNames],
|
||||
) -> Leaderboard:
|
||||
"""
|
||||
Update a leaderboard
|
||||
|
@ -1561,6 +1597,23 @@ def update_leaderboard(
|
|||
leaderboard.title = title
|
||||
if description is not None:
|
||||
leaderboard.description = description
|
||||
if wallet_connect is not None:
|
||||
leaderboard.wallet_connect = wallet_connect
|
||||
if blockchain_ids is not None:
|
||||
# deduplicate and sort
|
||||
blockchain_ids = sorted(list(set(blockchain_ids)))
|
||||
leaderboard.blockchain_ids = blockchain_ids
|
||||
|
||||
if columns_names is not None:
|
||||
if leaderboard.columns_names is not None:
|
||||
current_columns_names = ColumnsNames(**leaderboard.columns_names)
|
||||
|
||||
for key, value in columns_names.dict(exclude_none=True).items():
|
||||
setattr(current_columns_names, key, value)
|
||||
else:
|
||||
current_columns_names = columns_names
|
||||
|
||||
leaderboard.columns_names = current_columns_names.dict()
|
||||
|
||||
db_session.commit()
|
||||
|
||||
|
@ -1659,38 +1712,62 @@ def add_scores(
|
|||
# leaderboard access actions
|
||||
|
||||
|
||||
def create_leaderboard_resource(
|
||||
leaderboard_id: str, token: Union[Optional[uuid.UUID], str] = None
|
||||
) -> BugoutResource:
|
||||
def create_leaderboard_resource(leaderboard_id: str, user_id: Optional[str] = None):
|
||||
resource_data: Dict[str, Any] = {
|
||||
"type": LEADERBOARD_RESOURCE_TYPE,
|
||||
"leaderboard_id": leaderboard_id,
|
||||
}
|
||||
|
||||
if token is None:
|
||||
token = MOONSTREAM_ADMIN_ACCESS_TOKEN
|
||||
try:
|
||||
resource = bc.create_resource(
|
||||
token=token,
|
||||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
application_id=MOONSTREAM_APPLICATION_ID,
|
||||
resource_data=resource_data,
|
||||
timeout=10,
|
||||
)
|
||||
except Exception as e:
|
||||
raise LeaderboardCreateError(f"Error creating leaderboard resource: {e}")
|
||||
|
||||
if user_id is not None:
|
||||
try:
|
||||
bc.add_resource_holder_permissions(
|
||||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
resource_id=resource.id,
|
||||
holder_permissions=BugoutResourceHolder(
|
||||
holder_type=HolderType.user,
|
||||
holder_id=user_id,
|
||||
permissions=[
|
||||
ResourcePermissions.ADMIN,
|
||||
ResourcePermissions.READ,
|
||||
ResourcePermissions.UPDATE,
|
||||
ResourcePermissions.DELETE,
|
||||
],
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
raise LeaderboardCreateError(
|
||||
f"Error adding resource holder permissions: {e}"
|
||||
)
|
||||
|
||||
return resource
|
||||
|
||||
|
||||
def assign_resource(
|
||||
db_session: Session,
|
||||
leaderboard_id: uuid.UUID,
|
||||
user_token: Union[uuid.UUID, str],
|
||||
user_token: Optional[Union[uuid.UUID, str]] = None,
|
||||
resource_id: Optional[uuid.UUID] = None,
|
||||
):
|
||||
"""
|
||||
Assign a resource handler to a leaderboard
|
||||
"""
|
||||
|
||||
### get user_name from token
|
||||
|
||||
user = None
|
||||
if user_token is not None:
|
||||
user = bc.get_user(token=user_token)
|
||||
|
||||
leaderboard = (
|
||||
db_session.query(Leaderboard).filter(Leaderboard.id == leaderboard_id).one() # type: ignore
|
||||
)
|
||||
|
@ -1698,11 +1775,9 @@ def assign_resource(
|
|||
if resource_id is not None:
|
||||
leaderboard.resource_id = resource_id
|
||||
else:
|
||||
# Create resource via admin token
|
||||
|
||||
resource = create_leaderboard_resource(
|
||||
leaderboard_id=str(leaderboard_id),
|
||||
token=user_token,
|
||||
user_id=user.id if user is not None else None,
|
||||
)
|
||||
|
||||
leaderboard.resource_id = resource.id
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,10 +2,10 @@ import argparse
|
|||
import json
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from sqlalchemy import func, text
|
||||
from sqlalchemy import func, or_, text
|
||||
from sqlalchemy.dialects.postgresql import insert
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.exc import IntegrityError, NoResultFound
|
||||
|
@ -100,7 +100,9 @@ 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,
|
||||
updated_at=obj[0].updated_at,
|
||||
)
|
||||
|
@ -326,13 +328,14 @@ def delete_registered_contract(
|
|||
return (registered_contract, blockchain)
|
||||
|
||||
|
||||
def request_calls(
|
||||
def create_request_calls(
|
||||
db_session: Session,
|
||||
metatx_requester_id: uuid.UUID,
|
||||
registered_contract_id: Optional[uuid.UUID],
|
||||
contract_address: Optional[str],
|
||||
call_specs: List[data.CallSpecification],
|
||||
ttl_days: Optional[int] = None,
|
||||
live_at: Optional[int] = None,
|
||||
) -> int:
|
||||
"""
|
||||
Batch creates call requests for the given registered contract.
|
||||
|
@ -350,6 +353,11 @@ def request_calls(
|
|||
if ttl_days <= 0:
|
||||
raise ValueError("ttl_days must be positive")
|
||||
|
||||
if live_at is not None:
|
||||
assert live_at == int(live_at)
|
||||
if live_at <= 0:
|
||||
raise ValueError("live_at must be positive")
|
||||
|
||||
# Check that the moonstream_user_id matches a RegisteredContract with the given id or address
|
||||
query = db_session.query(RegisteredContract).filter(
|
||||
RegisteredContract.metatx_requester_id == metatx_requester_id
|
||||
|
@ -406,6 +414,7 @@ def request_calls(
|
|||
request_id=specification.request_id,
|
||||
parameters=specification.parameters,
|
||||
expires_at=expires_at,
|
||||
live_at=datetime.fromtimestamp(live_at) if live_at is not None else None,
|
||||
)
|
||||
|
||||
db_session.add(request)
|
||||
|
@ -422,7 +431,7 @@ def request_calls(
|
|||
return len(call_specs)
|
||||
|
||||
|
||||
def get_call_requests(
|
||||
def get_call_request(
|
||||
db_session: Session,
|
||||
request_id: uuid.UUID,
|
||||
) -> Tuple[CallRequest, RegisteredContract]:
|
||||
|
@ -472,9 +481,14 @@ def list_call_requests(
|
|||
limit: int = 10,
|
||||
offset: Optional[int] = None,
|
||||
show_expired: bool = False,
|
||||
live_after: Optional[int] = None,
|
||||
metatx_requester_id: Optional[uuid.UUID] = None,
|
||||
) -> List[Row[Tuple[CallRequest, RegisteredContract, CallRequestType]]]:
|
||||
"""
|
||||
List call requests for the given moonstream_user_id
|
||||
List call requests.
|
||||
|
||||
Argument moonstream_user_id took from authorization workflow. And if it is specified
|
||||
then user has access to call_requests before live_at param.
|
||||
"""
|
||||
if caller is None:
|
||||
raise ValueError("caller must be specified")
|
||||
|
@ -507,6 +521,23 @@ def list_call_requests(
|
|||
CallRequest.expires_at > func.now(),
|
||||
)
|
||||
|
||||
# If user id not specified, do not show call_requests before live_at.
|
||||
# Otherwise check show_before_live_at argument from query parameter
|
||||
if metatx_requester_id is not None:
|
||||
query = query.filter(
|
||||
CallRequest.metatx_requester_id == metatx_requester_id,
|
||||
)
|
||||
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)
|
||||
|
||||
|
@ -551,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.
|
||||
|
@ -633,7 +704,7 @@ def handle_request_calls(args: argparse.Namespace) -> None:
|
|||
|
||||
try:
|
||||
with db.yield_db_session_ctx() as db_session:
|
||||
request_calls(
|
||||
create_request_calls(
|
||||
db_session=db_session,
|
||||
moonstream_user_id=args.moonstream_user_id,
|
||||
registered_contract_id=args.registered_contract_id,
|
||||
|
|
|
@ -284,6 +284,7 @@ class CreateCallRequestsAPIRequest(BaseModel):
|
|||
contract_address: Optional[str] = None
|
||||
specifications: List[CallSpecification] = Field(default_factory=list)
|
||||
ttl_days: Optional[int] = None
|
||||
live_at: Optional[int] = None
|
||||
|
||||
# Solution found thanks to https://github.com/pydantic/pydantic/issues/506
|
||||
@root_validator
|
||||
|
@ -305,7 +306,9 @@ 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
|
||||
updated_at: datetime
|
||||
|
||||
|
@ -326,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]
|
||||
|
@ -365,11 +372,22 @@ class LeaderboardScore(BaseModel):
|
|||
points_data: Dict[str, Any]
|
||||
|
||||
|
||||
class ColumnsNames(BaseModel):
|
||||
rank: Optional[str] = None
|
||||
address: Optional[str] = None
|
||||
score: Optional[str] = None
|
||||
points_data: Optional[str] = None
|
||||
points_data_fields: Optional[Dict[str, str]] = None
|
||||
|
||||
|
||||
class Leaderboard(BaseModel):
|
||||
id: UUID
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
resource_id: Optional[UUID] = None
|
||||
wallet_connect: bool = False
|
||||
blockchain_ids: List[int] = Field(default_factory=list)
|
||||
columns_names: Optional[ColumnsNames] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
@ -385,6 +403,9 @@ class LeaderboardInfoResponse(BaseModel):
|
|||
class LeaderboardCreateRequest(BaseModel):
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
wallet_connect: bool = False
|
||||
blockchain_ids: List[int] = Field(default_factory=list)
|
||||
columns_names: Optional[ColumnsNames] = None
|
||||
|
||||
|
||||
class LeaderboardCreatedResponse(BaseModel):
|
||||
|
@ -392,6 +413,9 @@ class LeaderboardCreatedResponse(BaseModel):
|
|||
title: str
|
||||
description: Optional[str] = None
|
||||
resource_id: Optional[UUID] = None
|
||||
wallet_connect: bool = False
|
||||
blockchain_ids: List[int] = Field(default_factory=list)
|
||||
columns_names: Optional[ColumnsNames] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
@ -404,6 +428,9 @@ class LeaderboardUpdatedResponse(BaseModel):
|
|||
title: str
|
||||
description: Optional[str] = None
|
||||
resource_id: Optional[UUID] = None
|
||||
wallet_connect: bool = False
|
||||
blockchain_ids: List[int] = Field(default_factory=list)
|
||||
columns_names: Optional[ColumnsNames] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
@ -414,6 +441,9 @@ class LeaderboardUpdatedResponse(BaseModel):
|
|||
class LeaderboardUpdateRequest(BaseModel):
|
||||
title: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
wallet_connect: bool = False
|
||||
blockchain_ids: List[int] = Field(default_factory=list)
|
||||
columns_names: Optional[ColumnsNames] = None
|
||||
|
||||
|
||||
class LeaderboardDeletedResponse(BaseModel):
|
||||
|
@ -421,6 +451,9 @@ class LeaderboardDeletedResponse(BaseModel):
|
|||
title: str
|
||||
description: Optional[str] = None
|
||||
resource_id: Optional[UUID] = None
|
||||
wallet_connect: bool = False
|
||||
blockchain_ids: List[int] = Field(default_factory=list)
|
||||
columns_names: Optional[ColumnsNames] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
|
|
@ -6,17 +6,24 @@ from uuid import UUID
|
|||
|
||||
from bugout.data import BugoutResource, BugoutResources, BugoutUser
|
||||
from bugout.exceptions import BugoutResponseException
|
||||
from fastapi import 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.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,
|
||||
)
|
||||
|
@ -26,13 +33,170 @@ from .settings import (
|
|||
BUGOUT_REQUEST_TIMEOUT_SECONDS,
|
||||
BUGOUT_RESOURCE_TYPE_APPLICATION_CONFIG,
|
||||
MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
MOONSTREAM_APPLICATION_ID,
|
||||
MOONSTREAM_ADMIN_ID,
|
||||
MOONSTREAM_APPLICATION_ID,
|
||||
)
|
||||
from .settings import bugout_client as bc
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
||||
|
||||
|
||||
class InvalidAuthHeaderFormat(Exception):
|
||||
"""
|
||||
Raised when authorization header not pass validation.
|
||||
"""
|
||||
|
||||
|
||||
class BugoutUnverifiedAuth(Exception):
|
||||
"""
|
||||
Raised when attempted access by unverified Brood account.
|
||||
"""
|
||||
|
||||
|
||||
class BugoutAuthWrongApp(Exception):
|
||||
"""
|
||||
Raised when user does not belong to this application.
|
||||
"""
|
||||
|
||||
|
||||
def parse_auth_header(auth_header: str) -> Tuple[str, str]:
|
||||
"""
|
||||
Returns: auth_format and user_token passed in authorization header.
|
||||
"""
|
||||
auth_list = auth_header.split()
|
||||
if len(auth_list) != 2:
|
||||
raise InvalidAuthHeaderFormat("Wrong authorization header")
|
||||
|
||||
return auth_list[0], auth_list[1]
|
||||
|
||||
|
||||
def bugout_auth(token: str) -> BugoutUser:
|
||||
"""
|
||||
Extended bugout.get_user with additional checks.
|
||||
"""
|
||||
user: BugoutUser = bc.get_user(token)
|
||||
if not user.verified:
|
||||
raise BugoutUnverifiedAuth("Only verified accounts can have access")
|
||||
if str(user.application_id) != str(MOONSTREAM_APPLICATION_ID):
|
||||
raise BugoutAuthWrongApp("User does not belong to this application")
|
||||
|
||||
return user
|
||||
|
||||
|
||||
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]:
|
||||
"""
|
||||
Fetch Bugout user if authorization token provided.
|
||||
"""
|
||||
user: Optional[BugoutUser] = None
|
||||
if authorization is not None:
|
||||
token: str = ""
|
||||
try:
|
||||
_, 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 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):
|
||||
"""
|
||||
|
@ -59,30 +223,33 @@ class BroodAuthMiddleware(BaseHTTPMiddleware):
|
|||
if path in self.whitelist.keys() and self.whitelist[path] == method:
|
||||
return await call_next(request)
|
||||
|
||||
authorization_header = request.headers.get("authorization")
|
||||
if authorization_header is None:
|
||||
authorization = request.headers.get("authorization")
|
||||
if authorization is None:
|
||||
return Response(
|
||||
status_code=403, content="No authorization header passed with request"
|
||||
status_code=403,
|
||||
content="No authorization header passed with request",
|
||||
)
|
||||
user_token_list = authorization_header.split()
|
||||
if len(user_token_list) != 2:
|
||||
return Response(status_code=403, content="Wrong authorization header")
|
||||
user_token: str = user_token_list[-1]
|
||||
|
||||
try:
|
||||
user: BugoutUser = bc.get_user(user_token)
|
||||
if not user.verified:
|
||||
logger.info(
|
||||
f"Attempted journal access by unverified Brood account: {user.id}"
|
||||
)
|
||||
return Response(
|
||||
status_code=403,
|
||||
content="Only verified accounts can access journals",
|
||||
)
|
||||
if str(user.application_id) != str(MOONSTREAM_APPLICATION_ID):
|
||||
return Response(
|
||||
status_code=403, content="User does not belong to this application"
|
||||
)
|
||||
_, user_token = parse_auth_header(auth_header=authorization)
|
||||
except InvalidAuthHeaderFormat:
|
||||
return Response(status_code=403, content="Wrong authorization header")
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing auth header: {str(e)}")
|
||||
return Response(status_code=500, content="Internal server error")
|
||||
|
||||
try:
|
||||
user: BugoutUser = bugout_auth(token=user_token)
|
||||
except BugoutUnverifiedAuth:
|
||||
logger.info(f"Attempted access by unverified Brood account: {user.id}")
|
||||
return Response(
|
||||
status_code=403,
|
||||
content="Only verified accounts can have access",
|
||||
)
|
||||
except BugoutAuthWrongApp:
|
||||
return Response(
|
||||
status_code=403, content="User does not belong to this application"
|
||||
)
|
||||
except BugoutResponseException as e:
|
||||
return Response(status_code=e.status_code, content=e.detail)
|
||||
except Exception as e:
|
||||
|
@ -139,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:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import uuid
|
||||
|
||||
from sqlalchemy import (
|
||||
ARRAY,
|
||||
DECIMAL,
|
||||
VARCHAR,
|
||||
BigInteger,
|
||||
|
@ -314,8 +315,10 @@ 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)
|
||||
|
||||
created_at = Column(
|
||||
DateTime(timezone=True), server_default=utcnow(), nullable=False
|
||||
|
@ -335,7 +338,6 @@ class CallRequest(Base):
|
|||
|
||||
class Leaderboard(Base): # type: ignore
|
||||
__tablename__ = "leaderboards"
|
||||
# __table_args__ = (UniqueConstraint("dropper_contract_id", "address"),)
|
||||
|
||||
id = Column(
|
||||
UUID(as_uuid=True),
|
||||
|
@ -347,6 +349,10 @@ class Leaderboard(Base): # type: ignore
|
|||
title = Column(VARCHAR(128), nullable=False)
|
||||
description = Column(String, nullable=True)
|
||||
resource_id = Column(UUID(as_uuid=True), nullable=True, index=True)
|
||||
blockchain_ids = Column(ARRAY(Integer), nullable=False, default=[])
|
||||
|
||||
wallet_connect = Column(Boolean, default=False, nullable=False)
|
||||
columns_names = Column(JSONB, nullable=True)
|
||||
created_at = Column(
|
||||
DateTime(timezone=True), server_default=utcnow(), nullable=False
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
Leaderboard API.
|
||||
"""
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, Any, Union
|
||||
from uuid import UUID
|
||||
|
||||
from bugout.exceptions import BugoutResponseException
|
||||
|
@ -92,9 +92,12 @@ app.add_middleware(
|
|||
"",
|
||||
response_model=List[data.LeaderboardPosition],
|
||||
tags=["Public Endpoints"],
|
||||
include_in_schema=False,
|
||||
)
|
||||
@app.get("/", response_model=List[data.LeaderboardPosition], tags=["Public Endpoints"])
|
||||
@app.get(
|
||||
"/",
|
||||
response_model=List[data.LeaderboardPosition],
|
||||
tags=["Public Endpoints"],
|
||||
)
|
||||
async def leaderboard(
|
||||
leaderboard_id: UUID = Query(..., description="Leaderboard ID"),
|
||||
limit: int = Query(10),
|
||||
|
@ -108,7 +111,7 @@ async def leaderboard(
|
|||
|
||||
### Check if leaderboard exists
|
||||
try:
|
||||
actions.get_leaderboard_by_id(db_session, leaderboard_id)
|
||||
leaderboard = actions.get_leaderboard_by_id(db_session, leaderboard_id)
|
||||
except NoResultFound as e:
|
||||
raise EngineHTTPException(
|
||||
status_code=404,
|
||||
|
@ -119,8 +122,9 @@ async def leaderboard(
|
|||
raise EngineHTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
leaderboard_positions = actions.get_leaderboard_positions(
|
||||
db_session, leaderboard_id, limit, offset, version
|
||||
db_session, leaderboard.id, limit, offset, version
|
||||
)
|
||||
|
||||
result = [
|
||||
data.LeaderboardPosition(
|
||||
address=position.address,
|
||||
|
@ -150,7 +154,6 @@ async def create_leaderboard(
|
|||
Authorization: str = AuthHeader,
|
||||
) -> data.LeaderboardCreatedResponse:
|
||||
"""
|
||||
|
||||
Create leaderboard.
|
||||
"""
|
||||
|
||||
|
@ -162,6 +165,9 @@ async def create_leaderboard(
|
|||
title=leaderboard.title,
|
||||
description=leaderboard.description,
|
||||
token=token,
|
||||
wallet_connect=leaderboard.wallet_connect,
|
||||
blockchain_ids=leaderboard.blockchain_ids,
|
||||
columns_names=leaderboard.columns_names,
|
||||
)
|
||||
except actions.LeaderboardCreateError as e:
|
||||
logger.error(f"Error while creating leaderboard: {e}")
|
||||
|
@ -177,12 +183,15 @@ async def create_leaderboard(
|
|||
# Add resource to the leaderboard
|
||||
|
||||
return data.LeaderboardCreatedResponse(
|
||||
id=created_leaderboard.id, # type: ignore
|
||||
title=created_leaderboard.title, # type: ignore
|
||||
description=created_leaderboard.description, # type: ignore
|
||||
resource_id=created_leaderboard.resource_id, # type: ignore
|
||||
created_at=created_leaderboard.created_at, # type: ignore
|
||||
updated_at=created_leaderboard.updated_at, # type: ignore
|
||||
id=created_leaderboard.id,
|
||||
title=created_leaderboard.title,
|
||||
description=created_leaderboard.description,
|
||||
resource_id=created_leaderboard.resource_id,
|
||||
wallet_connect=created_leaderboard.wallet_connect,
|
||||
blockchain_ids=created_leaderboard.blockchain_ids,
|
||||
columns_names=created_leaderboard.columns_names,
|
||||
created_at=created_leaderboard.created_at,
|
||||
updated_at=created_leaderboard.updated_at,
|
||||
)
|
||||
|
||||
|
||||
|
@ -226,6 +235,9 @@ async def update_leaderboard(
|
|||
leaderboard_id=leaderboard_id,
|
||||
title=leaderboard.title,
|
||||
description=leaderboard.description,
|
||||
wallet_connect=leaderboard.wallet_connect,
|
||||
blockchain_ids=leaderboard.blockchain_ids,
|
||||
columns_names=leaderboard.columns_names,
|
||||
)
|
||||
except actions.LeaderboardUpdateError as e:
|
||||
logger.error(f"Error while updating leaderboard: {e}")
|
||||
|
@ -239,12 +251,15 @@ async def update_leaderboard(
|
|||
raise EngineHTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
return data.LeaderboardUpdatedResponse(
|
||||
id=updated_leaderboard.id, # type: ignore
|
||||
title=updated_leaderboard.title, # type: ignore
|
||||
description=updated_leaderboard.description, # type: ignore
|
||||
resource_id=updated_leaderboard.resource_id, # type: ignore
|
||||
created_at=updated_leaderboard.created_at, # type: ignore
|
||||
updated_at=updated_leaderboard.updated_at, # type: ignore
|
||||
id=updated_leaderboard.id,
|
||||
title=updated_leaderboard.title,
|
||||
description=updated_leaderboard.description,
|
||||
resource_id=updated_leaderboard.resource_id,
|
||||
wallet_connect=updated_leaderboard.wallet_connect,
|
||||
blockchain_ids=updated_leaderboard.blockchain_ids,
|
||||
columns_names=updated_leaderboard.columns_names,
|
||||
created_at=updated_leaderboard.created_at,
|
||||
updated_at=updated_leaderboard.updated_at,
|
||||
)
|
||||
|
||||
|
||||
|
@ -299,11 +314,15 @@ async def delete_leaderboard(
|
|||
raise EngineHTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
return data.LeaderboardDeletedResponse(
|
||||
id=deleted_leaderboard.id, # type: ignore
|
||||
title=deleted_leaderboard.title, # type: ignore
|
||||
description=deleted_leaderboard.description, # type: ignore
|
||||
created_at=deleted_leaderboard.created_at, # type: ignore
|
||||
updated_at=deleted_leaderboard.updated_at, # type: ignore
|
||||
id=deleted_leaderboard.id,
|
||||
title=deleted_leaderboard.title,
|
||||
description=deleted_leaderboard.description,
|
||||
resource_id=deleted_leaderboard.resource_id,
|
||||
wallet_connect=deleted_leaderboard.wallet_connect,
|
||||
blockchain_ids=deleted_leaderboard.blockchain_ids,
|
||||
columns_names=deleted_leaderboard.columns_names,
|
||||
created_at=deleted_leaderboard.created_at,
|
||||
updated_at=deleted_leaderboard.updated_at,
|
||||
)
|
||||
|
||||
|
||||
|
@ -336,12 +355,15 @@ async def get_leaderboards(
|
|||
|
||||
results = [
|
||||
data.Leaderboard(
|
||||
id=leaderboard.id, # type: ignore
|
||||
title=leaderboard.title, # type: ignore
|
||||
description=leaderboard.description, # type: ignore
|
||||
resource_id=leaderboard.resource_id, # type: ignore
|
||||
created_at=leaderboard.created_at, # type: ignore
|
||||
updated_at=leaderboard.updated_at, # type: ignore
|
||||
id=leaderboard.id,
|
||||
title=leaderboard.title,
|
||||
description=leaderboard.description,
|
||||
resource_id=leaderboard.resource_id,
|
||||
wallet_connect=leaderboard.wallet_connect,
|
||||
blockchain_ids=leaderboard.blockchain_ids,
|
||||
columns_names=leaderboard.columns_names,
|
||||
created_at=leaderboard.created_at,
|
||||
updated_at=leaderboard.updated_at,
|
||||
)
|
||||
for leaderboard in leaderboards
|
||||
]
|
||||
|
@ -453,7 +475,7 @@ async def quartiles(
|
|||
"""
|
||||
### Check if leaderboard exists
|
||||
try:
|
||||
actions.get_leaderboard_by_id(db_session, leaderboard_id)
|
||||
leaderboard = actions.get_leaderboard_by_id(db_session, leaderboard_id)
|
||||
except NoResultFound as e:
|
||||
raise EngineHTTPException(
|
||||
status_code=404,
|
||||
|
@ -472,12 +494,14 @@ async def quartiles(
|
|||
logger.error(f"Error while getting quartiles: {e}")
|
||||
raise EngineHTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
return data.QuartilesResponse(
|
||||
percentile_25={"address": q1[0], "score": q1[1], "rank": q1[2]},
|
||||
percentile_50={"address": q2[0], "score": q2[1], "rank": q2[2]},
|
||||
percentile_75={"address": q3[0], "score": q3[1], "rank": q3[2]},
|
||||
result = data.QuartilesResponse(
|
||||
percentile_25={"address": q1.address, "rank": q1.rank, "score": q1.score},
|
||||
percentile_50={"address": q2.address, "rank": q2.rank, "score": q2.score},
|
||||
percentile_75={"address": q3.address, "rank": q3.rank, "score": q3.score},
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@app.get(
|
||||
"/position",
|
||||
|
@ -503,7 +527,7 @@ async def position(
|
|||
|
||||
### Check if leaderboard exists
|
||||
try:
|
||||
actions.get_leaderboard_by_id(db_session, leaderboard_id)
|
||||
leaderboard = actions.get_leaderboard_by_id(db_session, leaderboard_id)
|
||||
except NoResultFound as e:
|
||||
raise EngineHTTPException(
|
||||
status_code=404,
|
||||
|
@ -540,7 +564,9 @@ async def position(
|
|||
|
||||
|
||||
@app.get(
|
||||
"/rank", response_model=List[data.LeaderboardPosition], tags=["Public Endpoints"]
|
||||
"/rank",
|
||||
response_model=List[data.LeaderboardPosition],
|
||||
tags=["Public Endpoints"],
|
||||
)
|
||||
async def rank(
|
||||
leaderboard_id: UUID = Query(..., description="Leaderboard ID"),
|
||||
|
@ -556,7 +582,7 @@ async def rank(
|
|||
|
||||
### Check if leaderboard exists
|
||||
try:
|
||||
actions.get_leaderboard_by_id(db_session, leaderboard_id)
|
||||
leaderboard = actions.get_leaderboard_by_id(db_session, leaderboard_id)
|
||||
except NoResultFound as e:
|
||||
raise EngineHTTPException(
|
||||
status_code=404,
|
||||
|
@ -574,14 +600,15 @@ async def rank(
|
|||
offset=offset,
|
||||
version_number=version,
|
||||
)
|
||||
|
||||
results = [
|
||||
data.LeaderboardPosition(
|
||||
address=rank_position.address,
|
||||
score=rank_position.score,
|
||||
rank=rank_position.rank,
|
||||
points_data=rank_position.points_data,
|
||||
address=position.address,
|
||||
score=position.score,
|
||||
rank=position.rank,
|
||||
points_data=position.points_data,
|
||||
)
|
||||
for rank_position in leaderboard_rank
|
||||
for position in leaderboard_rank
|
||||
]
|
||||
return results
|
||||
|
||||
|
|
|
@ -9,12 +9,19 @@ import logging
|
|||
from typing import Dict, List, Optional
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import Body, Depends, FastAPI, Path, Query, Request
|
||||
from bugout.data import BugoutUser
|
||||
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
|
||||
from ..middleware import (
|
||||
BugoutCORSMiddleware,
|
||||
EngineHTTPException,
|
||||
metatx_verify_header,
|
||||
request_none_or_user_auth,
|
||||
request_user_auth,
|
||||
)
|
||||
from ..settings import DOCS_TARGET_PATH
|
||||
from ..version import VERSION
|
||||
|
||||
|
@ -34,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",
|
||||
}
|
||||
|
||||
app = FastAPI(
|
||||
title=TITLE,
|
||||
description=DESCRIPTION,
|
||||
|
@ -53,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,
|
||||
|
@ -89,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]:
|
||||
"""
|
||||
|
@ -103,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,
|
||||
|
@ -126,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]:
|
||||
"""
|
||||
|
@ -136,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:
|
||||
|
@ -157,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:
|
||||
"""
|
||||
|
@ -167,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,
|
||||
|
@ -198,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,
|
||||
|
@ -233,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:
|
||||
"""
|
||||
|
@ -243,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:
|
||||
|
@ -278,14 +273,20 @@ async def call_request_types_route(
|
|||
return call_request_types
|
||||
|
||||
|
||||
@app.get("/requests", tags=["requests"], response_model=List[data.CallRequestResponse])
|
||||
@app.get(
|
||||
"/requests",
|
||||
tags=["requests"],
|
||||
response_model=List[data.CallRequestResponse],
|
||||
)
|
||||
async def list_requests_route(
|
||||
contract_id: Optional[UUID] = Query(None),
|
||||
contract_address: Optional[str] = Query(None),
|
||||
caller: str = Query(...),
|
||||
limit: int = Query(100),
|
||||
offset: Optional[int] = Query(None),
|
||||
show_expired: Optional[bool] = Query(False),
|
||||
show_expired: bool = Query(False),
|
||||
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]:
|
||||
"""
|
||||
|
@ -302,6 +303,8 @@ async def list_requests_route(
|
|||
limit=limit,
|
||||
offset=offset,
|
||||
show_expired=show_expired,
|
||||
live_after=live_after,
|
||||
metatx_requester_id=user.id if user is not None else None,
|
||||
)
|
||||
except ValueError as e:
|
||||
logger.error(repr(e))
|
||||
|
@ -318,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]:
|
||||
"""
|
||||
|
@ -326,7 +330,7 @@ async def get_request(
|
|||
At least one of `contract_id` or `contract_address` must be provided as query parameters.
|
||||
"""
|
||||
try:
|
||||
request = contracts_actions.get_call_requests(
|
||||
request = contracts_actions.get_call_request(
|
||||
db_session=db_session,
|
||||
request_id=request_id,
|
||||
)
|
||||
|
@ -344,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:
|
||||
"""
|
||||
|
@ -354,13 +358,14 @@ async def create_requests(
|
|||
At least one of `contract_id` or `contract_address` must be provided in the request body.
|
||||
"""
|
||||
try:
|
||||
num_requests = contracts_actions.request_calls(
|
||||
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,
|
||||
ttl_days=data.ttl_days,
|
||||
live_at=data.live_at,
|
||||
)
|
||||
except contracts_actions.InvalidAddressFormat as err:
|
||||
raise EngineHTTPException(
|
||||
|
@ -396,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:
|
||||
"""
|
||||
|
@ -406,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:
|
||||
|
@ -414,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)
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.0.7
|
||||
0.0.8
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
[mypy]
|
||||
|
||||
[mypy-eth_keys.*]
|
||||
ignore_missing_imports = True
|
|
@ -7,7 +7,7 @@ base58==2.1.1
|
|||
bitarray==2.7.6
|
||||
boto3==1.27.0
|
||||
botocore==1.30.0
|
||||
bugout==0.2.14
|
||||
bugout==0.2.15
|
||||
certifi==2023.5.7
|
||||
charset-normalizer==3.1.0
|
||||
click==8.1.3
|
||||
|
|
|
@ -13,7 +13,7 @@ setup(
|
|||
packages=find_packages(),
|
||||
install_requires=[
|
||||
"boto3",
|
||||
"bugout>=0.2.14",
|
||||
"bugout>=0.2.15",
|
||||
"eip712==0.1.0",
|
||||
"eth-typing>=2.3.0",
|
||||
"fastapi",
|
||||
|
|
|
@ -15,6 +15,9 @@ from bugout.data import (
|
|||
BugoutResources,
|
||||
BugoutSearchResult,
|
||||
BugoutSearchResults,
|
||||
BugoutResourceHolder,
|
||||
HolderType,
|
||||
ResourcePermissions,
|
||||
)
|
||||
from bugout.exceptions import BugoutResponseException
|
||||
from bugout.journal import SearchOrder
|
||||
|
@ -470,7 +473,7 @@ def upload_abi_to_s3(
|
|||
|
||||
|
||||
def get_all_entries_from_search(
|
||||
journal_id: str, search_query: str, limit: int, token: str
|
||||
journal_id: str, search_query: str, limit: int, token: str, content: bool = False
|
||||
) -> List[BugoutSearchResult]:
|
||||
"""
|
||||
Get all required entries from journal using search interface
|
||||
|
@ -483,7 +486,7 @@ def get_all_entries_from_search(
|
|||
token=token,
|
||||
journal_id=journal_id,
|
||||
query=search_query,
|
||||
content=False,
|
||||
content=content,
|
||||
timeout=10.0,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
|
@ -496,7 +499,7 @@ def get_all_entries_from_search(
|
|||
token=token,
|
||||
journal_id=journal_id,
|
||||
query=search_query,
|
||||
content=False,
|
||||
content=content,
|
||||
timeout=10.0,
|
||||
limit=limit,
|
||||
offset=offset,
|
||||
|
@ -526,47 +529,45 @@ def apply_moonworm_tasks(
|
|||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
)
|
||||
|
||||
# create historical crawl task in journal
|
||||
|
||||
# will use create_entries_pack for creating entries in journal
|
||||
|
||||
existing_tags = [entry.tags for entry in entries]
|
||||
|
||||
existing_hashes = [
|
||||
tag.split(":")[-1]
|
||||
for tag in chain(*existing_tags)
|
||||
if "abi_method_hash" in tag
|
||||
existing_selectors = [
|
||||
tag.split(":")[-1] for tag in chain(*existing_tags) if "abi_selector" in tag
|
||||
]
|
||||
|
||||
abi_hashes_dict = {
|
||||
hashlib.md5(json.dumps(method).encode("utf-8")).hexdigest(): method
|
||||
abi_selectors_dict = {
|
||||
Web3.keccak(
|
||||
text=method["name"]
|
||||
+ "("
|
||||
+ ",".join(map(lambda x: x["type"], method["inputs"]))
|
||||
+ ")"
|
||||
)[:4].hex(): method
|
||||
for method in abi
|
||||
if (method["type"] in ("event", "function"))
|
||||
and (method.get("stateMutability", "") != "view")
|
||||
}
|
||||
|
||||
for hash in abi_hashes_dict:
|
||||
if hash not in existing_hashes:
|
||||
abi_selector = Web3.keccak(
|
||||
text=abi_hashes_dict[hash]["name"]
|
||||
+ "("
|
||||
+ ",".join(
|
||||
map(lambda x: x["type"], abi_hashes_dict[hash]["inputs"])
|
||||
)
|
||||
+ ")"
|
||||
)[:4].hex()
|
||||
for abi_selector in abi_selectors_dict:
|
||||
if abi_selector not in existing_selectors:
|
||||
hash = hashlib.md5(
|
||||
json.dumps(abi_selectors_dict[abi_selector]).encode("utf-8")
|
||||
).hexdigest()
|
||||
|
||||
moonworm_abi_tasks_entries_pack.append(
|
||||
{
|
||||
"title": address,
|
||||
"content": json.dumps(abi_hashes_dict[hash], indent=4),
|
||||
"content": json.dumps(
|
||||
abi_selectors_dict[abi_selector], indent=4
|
||||
),
|
||||
"tags": [
|
||||
f"address:{address}",
|
||||
f"type:{abi_hashes_dict[hash]['type']}",
|
||||
f"type:{abi_selectors_dict[abi_selector]['type']}",
|
||||
f"abi_method_hash:{hash}",
|
||||
f"abi_selector:{abi_selector}",
|
||||
f"subscription_type:{subscription_type}",
|
||||
f"abi_name:{abi_hashes_dict[hash]['name']}",
|
||||
f"abi_name:{abi_selectors_dict[abi_selector]['name']}",
|
||||
f"status:active",
|
||||
f"task_type:moonworm",
|
||||
f"moonworm_task_pickedup:False", # True if task picked up by moonworm-crawler(default each 120 sec)
|
||||
|
@ -711,11 +712,7 @@ def generate_journal_for_user(
|
|||
}
|
||||
|
||||
try:
|
||||
bc.create_resource(
|
||||
token=token,
|
||||
application_id=MOONSTREAM_APPLICATION_ID,
|
||||
resource_data=resource_data,
|
||||
)
|
||||
create_resource_for_user(user_id=user_id, resource_data=resource_data)
|
||||
except BugoutResponseException as e:
|
||||
raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail)
|
||||
except Exception as e:
|
||||
|
@ -851,6 +848,8 @@ def get_list_of_support_interfaces(
|
|||
Returns list of interfaces supported by given address
|
||||
"""
|
||||
|
||||
result = {}
|
||||
|
||||
try:
|
||||
_, _, is_contract = check_if_smart_contract(
|
||||
blockchain_type=blockchain_type, address=address, user_token=user_token
|
||||
|
@ -866,8 +865,6 @@ def get_list_of_support_interfaces(
|
|||
abi=supportsInterface_abi,
|
||||
)
|
||||
|
||||
result = {}
|
||||
|
||||
if blockchain_type in multicall_contracts:
|
||||
calls = []
|
||||
|
||||
|
@ -952,3 +949,57 @@ def check_if_smart_contract(
|
|||
is_contract = True
|
||||
|
||||
return blockchain_type, address, is_contract
|
||||
|
||||
|
||||
def create_resource_for_user(
|
||||
user_id: uuid.UUID,
|
||||
resource_data: Dict[str, Any],
|
||||
) -> BugoutResource:
|
||||
"""
|
||||
Create resource for user
|
||||
"""
|
||||
try:
|
||||
resource = bc.create_resource(
|
||||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
application_id=MOONSTREAM_APPLICATION_ID,
|
||||
resource_data=resource_data,
|
||||
timeout=BUGOUT_REQUEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
except BugoutResponseException as e:
|
||||
raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail)
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating resource: {str(e)}")
|
||||
raise MoonstreamHTTPException(status_code=500, internal_error=e)
|
||||
|
||||
try:
|
||||
bc.add_resource_holder_permissions(
|
||||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
resource_id=resource.id,
|
||||
holder_permissions=BugoutResourceHolder(
|
||||
holder_type=HolderType.user,
|
||||
holder_id=user_id,
|
||||
permissions=[
|
||||
ResourcePermissions.ADMIN,
|
||||
ResourcePermissions.READ,
|
||||
ResourcePermissions.UPDATE,
|
||||
ResourcePermissions.DELETE,
|
||||
],
|
||||
),
|
||||
timeout=BUGOUT_REQUEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
except BugoutResponseException as e:
|
||||
logger.error(
|
||||
f"Error adding resource holder permissions to resource resource {str(resource.id)} {str(e)}"
|
||||
)
|
||||
raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail)
|
||||
except Exception as e:
|
||||
bc.delete_resource(
|
||||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
resource_id=resource.id,
|
||||
)
|
||||
logger.error(
|
||||
f"Error adding resource holder permissions to resource {str(resource.id)} {str(e)}"
|
||||
)
|
||||
raise MoonstreamHTTPException(status_code=500, internal_error=e)
|
||||
|
||||
return resource
|
||||
|
|
|
@ -12,7 +12,12 @@ from sqlalchemy.orm import with_expression
|
|||
|
||||
from moonstreamdb.db import SessionLocal
|
||||
|
||||
from ..settings import BUGOUT_BROOD_URL, BUGOUT_SPIRE_URL, MOONSTREAM_APPLICATION_ID
|
||||
from ..settings import (
|
||||
BUGOUT_BROOD_URL,
|
||||
BUGOUT_SPIRE_URL,
|
||||
MOONSTREAM_APPLICATION_ID,
|
||||
MOONSTREAM_MOONWORM_TASKS_JOURNAL,
|
||||
)
|
||||
from ..web3_provider import yield_web3_provider
|
||||
|
||||
from . import subscription_types, subscriptions, moonworm_tasks, queries
|
||||
|
@ -20,6 +25,7 @@ from .migrations import (
|
|||
checksum_address,
|
||||
update_dashboard_subscription_key,
|
||||
generate_entity_subscriptions,
|
||||
add_selectors,
|
||||
)
|
||||
|
||||
|
||||
|
@ -87,6 +93,9 @@ steps:
|
|||
- id: 20230501
|
||||
name: fix_duplicates_keys_in_entity_subscription
|
||||
description: Fix entity duplicates keys for all subscriptions introduced in 20230213
|
||||
- id: 20230904
|
||||
name fill_missing_selectors_in_moonworm_tasks
|
||||
description: Get all moonworm jobs from moonworm journal and add selector tag if it not represent
|
||||
"""
|
||||
logger.info(entity_migration_overview)
|
||||
|
||||
|
@ -117,6 +126,30 @@ def migrations_run(args: argparse.Namespace) -> None:
|
|||
web3_session = yield_web3_provider()
|
||||
db_session = SessionLocal()
|
||||
try:
|
||||
if args.id == 20230904:
|
||||
step_order = [
|
||||
"fill_missing_selectors_in_moonworm_tasks",
|
||||
"deduplicate_moonworm_tasks",
|
||||
]
|
||||
step_map: Dict[str, Dict[str, Any]] = {
|
||||
"upgrade": {
|
||||
"fill_missing_selectors_in_moonworm_tasks": {
|
||||
"action": add_selectors.fill_missing_selectors_in_moonworm_tasks,
|
||||
"description": "Get all moonworm jobs from moonworm journal and add selector tag if it not represent",
|
||||
},
|
||||
"deduplicate_moonworm_tasks": {
|
||||
"action": add_selectors.deduplicate_moonworm_task_by_selector,
|
||||
"description": "Deduplicate moonworm tasks by selector",
|
||||
},
|
||||
},
|
||||
"downgrade": {},
|
||||
}
|
||||
if args.command not in ["upgrade", "downgrade"]:
|
||||
logger.info("Wrong command. Please use upgrade or downgrade")
|
||||
step = args.step
|
||||
|
||||
migration_run(step_map, args.command, step, step_order)
|
||||
|
||||
if args.id == 20230501:
|
||||
# fix entity duplicates keys for all subscriptions introduced in 20230213
|
||||
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
"""
|
||||
Add selectors to all moonworm tasks.
|
||||
"""
|
||||
import logging
|
||||
import json
|
||||
|
||||
|
||||
from bugout.exceptions import BugoutResponseException
|
||||
from web3 import Web3
|
||||
|
||||
from ...settings import (
|
||||
BUGOUT_REQUEST_TIMEOUT_SECONDS,
|
||||
MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
MOONSTREAM_MOONWORM_TASKS_JOURNAL,
|
||||
)
|
||||
from ...settings import bugout_client as bc
|
||||
from ...actions import get_all_entries_from_search
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def fill_missing_selectors_in_moonworm_tasks() -> None:
|
||||
"""
|
||||
Add selectors to all moonworm tasks.
|
||||
"""
|
||||
|
||||
batch_size = 100
|
||||
|
||||
moonworm_tasks = get_all_entries_from_search(
|
||||
journal_id=MOONSTREAM_MOONWORM_TASKS_JOURNAL,
|
||||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
search_query="#task_type:moonworm !#version:2.0",
|
||||
limit=batch_size,
|
||||
content=True,
|
||||
)
|
||||
|
||||
logger.info(f"Found {len(moonworm_tasks)} moonworm tasks versions 1.0")
|
||||
|
||||
entries_tags = []
|
||||
|
||||
## batch tasks
|
||||
|
||||
for task_batch in [
|
||||
moonworm_tasks[i : i + batch_size]
|
||||
for i in range(0, len(moonworm_tasks), batch_size)
|
||||
]:
|
||||
count = 0
|
||||
for task in task_batch:
|
||||
tags = ["version:2.0"]
|
||||
|
||||
## get abi
|
||||
try:
|
||||
abi = json.loads(task.content)
|
||||
except Exception as e:
|
||||
logger.warn(
|
||||
f"Unable to parse abi from task: {task.entry_url.split()[-1]}: {e}"
|
||||
)
|
||||
continue
|
||||
|
||||
if "name" not in abi:
|
||||
logger.warn(
|
||||
f"Unable to find abi name in task: {task.entry_url.split()[-1]}"
|
||||
)
|
||||
continue
|
||||
|
||||
if not any([tag.startswith("abi_selector:") for tag in task.tags]):
|
||||
## generate selector
|
||||
|
||||
abi_selector = Web3.keccak(
|
||||
text=abi["name"]
|
||||
+ "("
|
||||
+ ",".join(map(lambda x: x["type"], abi["inputs"]))
|
||||
+ ")"
|
||||
)[:4].hex()
|
||||
|
||||
tags.append(f"abi_selector:{abi_selector}")
|
||||
|
||||
count += 1
|
||||
|
||||
entries_tags.append(
|
||||
{
|
||||
"entry_id": task.entry_url.split("/")[-1], ## 😭
|
||||
"tags": tags,
|
||||
}
|
||||
)
|
||||
|
||||
logger.info(f"Found {count} missing selectors in batch {len(task_batch)} tasks")
|
||||
|
||||
## update entries
|
||||
|
||||
try:
|
||||
bc.create_entries_tags(
|
||||
journal_id=MOONSTREAM_MOONWORM_TASKS_JOURNAL,
|
||||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
entries_tags=entries_tags,
|
||||
timeout=15,
|
||||
)
|
||||
except BugoutResponseException as e:
|
||||
logger.error(f"Unable to update entries tags: {e}")
|
||||
continue
|
||||
|
||||
|
||||
def deduplicate_moonworm_task_by_selector():
|
||||
"""
|
||||
Find moonworm tasks with same selector and remove old versions
|
||||
"""
|
||||
|
||||
moonworm_tasks = get_all_entries_from_search(
|
||||
journal_id=MOONSTREAM_MOONWORM_TASKS_JOURNAL,
|
||||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
search_query="#task_type:moonworm #version:2.0",
|
||||
limit=100,
|
||||
content=False,
|
||||
)
|
||||
|
||||
logger.info(f"Found {len(moonworm_tasks)} moonworm tasks versions 2.0")
|
||||
|
||||
## loop over tasks
|
||||
|
||||
selectors = {}
|
||||
|
||||
for task in moonworm_tasks:
|
||||
tags = task.tags
|
||||
|
||||
## get selector
|
||||
selector = [tag for tag in tags if tag.startswith("abi_selector:")]
|
||||
|
||||
address = [tag for tag in tags if tag.startswith("address:")]
|
||||
|
||||
if len(selector) == 0:
|
||||
logger.warn(
|
||||
f"Unable to find selector in task: {task.entry_url.split()[-1]}"
|
||||
)
|
||||
continue
|
||||
|
||||
selector = selector[0].split(":")[1]
|
||||
|
||||
if len(address) == 0:
|
||||
logger.warn(f"Unable to find address in task: {task.entry_url.split()[-1]}")
|
||||
continue
|
||||
|
||||
address = address[0].split(":")[1]
|
||||
|
||||
if address not in selectors:
|
||||
selectors[address] = {}
|
||||
|
||||
if selector not in selectors[address]:
|
||||
selectors[address][selector] = {"entries": {}}
|
||||
|
||||
selectors[address][selector]["entries"][
|
||||
task.entry_url.split("/")[-1]
|
||||
] = task.created_at
|
||||
|
||||
logger.info(f"Found {len(selectors)} addresses")
|
||||
|
||||
for address, selectors_dict in selectors.items():
|
||||
for selector, tasks_dict in selectors_dict.items():
|
||||
if len(tasks_dict["entries"]) == 1:
|
||||
continue
|
||||
|
||||
## find earliest task
|
||||
|
||||
earliest_task_id = min(
|
||||
tasks_dict["entries"], key=lambda key: tasks_dict["entries"][key]
|
||||
)
|
||||
|
||||
## remove all tasks except latest
|
||||
|
||||
logger.info(
|
||||
f"Found {len(tasks_dict['entries'])} tasks with selector {selector} erliest task {earliest_task_id} with created_at: {tasks_dict['entries'][earliest_task_id]}"
|
||||
)
|
||||
|
||||
for task_id in tasks_dict["entries"]:
|
||||
if task_id == earliest_task_id:
|
||||
continue
|
||||
|
||||
try:
|
||||
bc.delete_entry(
|
||||
journal_id=MOONSTREAM_MOONWORM_TASKS_JOURNAL,
|
||||
entry_id=task_id,
|
||||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
)
|
||||
except BugoutResponseException as e:
|
||||
logger.error(f"Unable to delete entry with id {task_id} : {e}")
|
||||
continue
|
||||
|
||||
logger.info(f"Deleted entry: {task_id}")
|
|
@ -25,11 +25,11 @@ from ..actions import (
|
|||
get_query_by_name,
|
||||
name_normalization,
|
||||
query_parameter_hash,
|
||||
create_resource_for_user,
|
||||
)
|
||||
from ..middleware import MoonstreamHTTPException
|
||||
from ..settings import (
|
||||
MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
MOONSTREAM_APPLICATION_ID,
|
||||
MOONSTREAM_CRAWLERS_SERVER_PORT,
|
||||
MOONSTREAM_CRAWLERS_SERVER_URL,
|
||||
MOONSTREAM_INTERNAL_REQUEST_TIMEOUT_SECONDS,
|
||||
|
@ -130,24 +130,16 @@ async def create_query_handler(
|
|||
except Exception as e:
|
||||
raise MoonstreamHTTPException(status_code=500, internal_error=e)
|
||||
|
||||
try:
|
||||
# create resource query_name_resolver
|
||||
bc.create_resource(
|
||||
token=token,
|
||||
application_id=MOONSTREAM_APPLICATION_ID,
|
||||
resource_data={
|
||||
"type": data.BUGOUT_RESOURCE_QUERY_RESOLVER,
|
||||
"user_id": str(user.id),
|
||||
"user": str(user.username),
|
||||
"name": query_name,
|
||||
"entry_id": str(entry.id),
|
||||
},
|
||||
)
|
||||
except BugoutResponseException as e:
|
||||
logger.error(f"Error creating name resolving resource: {str(e)}")
|
||||
raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail)
|
||||
except Exception as e:
|
||||
raise MoonstreamHTTPException(status_code=500, internal_error=e)
|
||||
create_resource_for_user(
|
||||
user_id=user.id,
|
||||
resource_data={
|
||||
"type": data.BUGOUT_RESOURCE_QUERY_RESOLVER,
|
||||
"user_id": str(user.id),
|
||||
"user": str(user.username),
|
||||
"name": query_name,
|
||||
"entry_id": str(entry.id),
|
||||
},
|
||||
)
|
||||
|
||||
try:
|
||||
bc.update_tags(
|
||||
|
@ -355,7 +347,7 @@ async def update_query_handler(
|
|||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN,
|
||||
journal_id=MOONSTREAM_QUERIES_JOURNAL_ID,
|
||||
entry_id=query_id,
|
||||
title=query_name,
|
||||
title=f"Query:{query_name}",
|
||||
content=request_update.query,
|
||||
tags=["preapprove"],
|
||||
)
|
||||
|
@ -620,7 +612,9 @@ async def remove_query_handler(
|
|||
raise MoonstreamHTTPException(status_code=404, detail="Query does not exists")
|
||||
|
||||
try:
|
||||
bc.delete_resource(token=token, resource_id=query_ids[query_name][0])
|
||||
bc.delete_resource(
|
||||
token=MOONSTREAM_ADMIN_ACCESS_TOKEN, resource_id=query_ids[query_name][0]
|
||||
)
|
||||
except BugoutResponseException as e:
|
||||
raise MoonstreamHTTPException(status_code=e.status_code, detail=e.detail)
|
||||
except Exception as e:
|
||||
|
|
|
@ -9,7 +9,7 @@ base58==2.1.1
|
|||
bitarray==2.6.0
|
||||
boto3==1.26.5
|
||||
botocore==1.29.5
|
||||
bugout>=0.2.13
|
||||
bugout>=0.2.15
|
||||
certifi==2022.9.24
|
||||
charset-normalizer==2.1.1
|
||||
click==8.1.3
|
||||
|
|
|
@ -13,7 +13,7 @@ setup(
|
|||
install_requires=[
|
||||
"appdirs",
|
||||
"boto3",
|
||||
"bugout>=0.2.13",
|
||||
"bugout>=0.2.15",
|
||||
"fastapi",
|
||||
"moonstreamdb>=0.3.5",
|
||||
"humbug",
|
||||
|
|
|
@ -198,7 +198,7 @@ func (bpool *BlockchainPool) HealthCheck() {
|
|||
for _, b := range bpool.Blockchains {
|
||||
var timeout time.Duration
|
||||
getLatestBlockReq := `{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest", false],"id":1}`
|
||||
if b.Blockchain == "starknet" || b.Blockchain == "starknet-goerli" {
|
||||
if b.Blockchain == "starknet" || b.Blockchain == "starknet-goerli" || b.Blockchain == "starknet-sepolia" {
|
||||
getLatestBlockReq = `{"jsonrpc":"2.0","method":"starknet_getBlockWithTxHashes","params":["latest"],"id":"0"}`
|
||||
timeout = NB_HEALTH_CHECK_CALL_TIMEOUT * 2
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ func (bpool *BlockchainPool) HealthCheck() {
|
|||
}
|
||||
|
||||
var blockNumber uint64
|
||||
if b.Blockchain == "starknet" || b.Blockchain == "starknet-goerli" {
|
||||
if b.Blockchain == "starknet" || b.Blockchain == "starknet-goerli" || b.Blockchain == "starknet-sepolia" {
|
||||
blockNumber = statusResponse.Result.BlockNumber
|
||||
} else {
|
||||
blockNumberHex := strings.Replace(statusResponse.Result.Number, "0x", "", -1)
|
||||
|
|
|
@ -35,6 +35,7 @@ var (
|
|||
NB_CONTROLLER_TOKEN = os.Getenv("NB_CONTROLLER_TOKEN")
|
||||
NB_CONTROLLER_ACCESS_ID = os.Getenv("NB_CONTROLLER_ACCESS_ID")
|
||||
MOONSTREAM_CORS_ALLOWED_ORIGINS = os.Getenv("MOONSTREAM_CORS_ALLOWED_ORIGINS")
|
||||
CORS_WHITELIST_MAP = make(map[string]bool)
|
||||
|
||||
NB_CONNECTION_RETRIES = 2
|
||||
NB_CONNECTION_RETRIES_INTERVAL = time.Millisecond * 10
|
||||
|
@ -86,6 +87,9 @@ func CheckEnvVarSet() {
|
|||
NB_CONTROLLER_ACCESS_ID = uuid.New().String()
|
||||
log.Printf("Access ID for internal usage in NB_CONTROLLER_ACCESS_ID environment variable is not valid uuid, generated random one: %v", NB_CONTROLLER_ACCESS_ID)
|
||||
}
|
||||
for _, o := range strings.Split(MOONSTREAM_CORS_ALLOWED_ORIGINS, ",") {
|
||||
CORS_WHITELIST_MAP[o] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Nodes configuration
|
||||
|
|
|
@ -368,19 +368,29 @@ func panicMiddleware(next http.Handler) http.Handler {
|
|||
// CORS middleware
|
||||
func corsMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodOptions {
|
||||
for _, allowedOrigin := range strings.Split(MOONSTREAM_CORS_ALLOWED_ORIGINS, ",") {
|
||||
if r.Header.Get("Origin") == allowedOrigin {
|
||||
w.Header().Set("Access-Control-Allow-Origin", allowedOrigin)
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET,POST")
|
||||
// Credentials are cookies, authorization headers, or TLS client certificates
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
|
||||
}
|
||||
var allowedOrigin string
|
||||
if CORS_WHITELIST_MAP["*"] {
|
||||
allowedOrigin = "*"
|
||||
} else {
|
||||
origin := r.Header.Get("Origin")
|
||||
if _, ok := CORS_WHITELIST_MAP[origin]; ok {
|
||||
allowedOrigin = origin
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
if allowedOrigin != "" {
|
||||
w.Header().Set("Access-Control-Allow-Origin", allowedOrigin)
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
|
||||
// Credentials are cookies, authorization headers, or TLS client certificates
|
||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
|
||||
}
|
||||
|
||||
if r.Method == "OPTIONS" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ var (
|
|||
func initHealthCheck(debug bool) {
|
||||
healthCheckInterval, convErr := strconv.Atoi(NB_HEALTH_CHECK_INTERVAL)
|
||||
if convErr != nil {
|
||||
healthCheckInterval = 5
|
||||
healthCheckInterval = 30
|
||||
}
|
||||
t := time.NewTicker(time.Second * time.Duration(healthCheckInterval))
|
||||
for {
|
||||
|
|
Ładowanie…
Reference in New Issue