Skip to content

Commit

Permalink
proxy methods
Browse files Browse the repository at this point in the history
  • Loading branch information
dungeon-master-666 committed Dec 3, 2023
1 parent b0a038c commit 1cdfed7
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 62 deletions.
3 changes: 1 addition & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ x-indexer-environment: &index-common
x-index-api: &index-api
TON_INDEXER_API_ROOT_PATH:
TON_INDEXER_API_PORT:
TON_INDEXER_TON_HTTP_API_ENDPOINT:
<<: *index-common

x-index-worker: &index-worker
Expand All @@ -29,8 +30,6 @@ services:
ports:
- target: 8081
published: ${TON_INDEXER_API_PORT:-8081}
protocol: tcp
mode: host
environment: *index-api
networks:
internal:
Expand Down
65 changes: 26 additions & 39 deletions indexer/indexer/api/api_v1/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import List, Optional
from datetime import datetime

from fastapi import FastAPI, APIRouter, Depends, Query, Path, status
from fastapi import FastAPI, APIRouter, Depends, Query, Path, Body, status
from fastapi.responses import JSONResponse
from fastapi.exceptions import HTTPException
from starlette.exceptions import HTTPException as StarletteHTTPException
Expand All @@ -25,6 +25,10 @@

# logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)

# logging.basicConfig(format='%(asctime)s %(module)-15s %(message)s',
# level=logging.INFO)
# logger = logging.getLogger(__name__)

settings = Settings()
router = APIRouter()

Expand All @@ -42,8 +46,6 @@ async def get_db():
UINT32_MAX = 2**32 - 1


# masterchain

@router.get("/masterchainInfo", response_model=schemas.MasterchainInfo, response_model_exclude_none=True)
async def get_masterchain_info(db: AsyncSession = Depends(get_db)):
last = await db.run_sync(crud.get_blocks,
Expand All @@ -62,41 +64,6 @@ async def get_masterchain_info(db: AsyncSession = Depends(get_db)):
seqno='latest')
return schemas.MasterchainInfo(first=schemas.Block.from_orm(first[0]), last=schemas.Block.from_orm(last[0]))


# @router.get("/masterchain/block/latest", response_model=schemas.Block, response_model_exclude_none=True)
# async def get_masterchain_last_block(db: AsyncSession = Depends(get_db)):
# """
# Returns last known masterchain block.
# """
# result = await db.run_sync(crud.get_blocks,
# workchain=MASTERCHAIN_INDEX,
# shard=MASTERCHAIN_SHARD,
# sort_seqno='desc',
# limit=1)
# if len(result) < 1:
# raise exceptions.BlockNotFound(workchain=MASTERCHAIN_INDEX,
# shard=MASTERCHAIN_SHARD,
# seqno='latest')
# return schemas.Block.from_orm(result[0])

# @router.get("/masterchain/block/first_indexed", response_model=schemas.Block, response_model_exclude_none=True)
# async def get_masterchain_first_indexed_block(db: AsyncSession = Depends(get_db)):
# """
# Returns first indexed masterchain block.
# """
# result = await db.run_sync(crud.get_blocks,
# workchain=MASTERCHAIN_INDEX,
# shard=MASTERCHAIN_SHARD,
# sort_seqno='asc',
# limit=1)
# if len(result) < 1:
# raise exceptions.BlockNotFound(workchain=MASTERCHAIN_INDEX,
# shard=MASTERCHAIN_SHARD,
# seqno='first_indexed')
# return schemas.Block.from_orm(result[0])


# JsonRPC
def validate_block_idx(workchain, shard, seqno):
if workchain is None:
if shard is not None or seqno is not None:
Expand All @@ -106,7 +73,6 @@ def validate_block_idx(workchain, shard, seqno):
raise ValueError('shard id required')
return True


@router.get("/blocks", response_model=List[schemas.Block])
async def get_blocks(
workchain: Optional[int] = Query(None, description='Block workchain.'),
Expand Down Expand Up @@ -140,6 +106,21 @@ async def get_blocks(
return [schemas.Block.from_orm(x) for x in res]


# NOTE: This method is not reliable in case account was destroyed, it will return it's state before destruction. So for now we comment it out.
# @router.get("/account", response_model=schemas.LatestAccountState)
# async def get_account(
# address: str = Query(..., description='Account address. Can be sent in raw or user-friendly format.'),
# db: AsyncSession = Depends(get_db)):
# """
# Returns latest account state by specified address.
# """
# address = address_to_raw(address)
# res = await db.run_sync(crud.get_latest_account_state_by_address,
# address=address)
# if res is None:
# raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f'Account {address} not found')
# return schemas.LatestAccountState.from_orm(res)

@router.get("/masterchainBlockShards", response_model=List[schemas.Block])
async def get_masterchain_block_shards(
seqno: int = Query(..., description='Masterchain block seqno'),
Expand Down Expand Up @@ -213,6 +194,7 @@ async def get_transactions_by_masterchain_block(
sort=sort)
return [schemas.Transaction.from_orm(tx) for tx in txs]


@router.get('/transactionsByMessage', response_model=List[schemas.Transaction])
async def get_transactions_by_message(
direction: Optional[str] = Query(..., description='Message direction.', enum=['in', 'out']),
Expand All @@ -234,6 +216,7 @@ async def get_transactions_by_message(
sort='desc')
return [schemas.Transaction.from_orm(tx) for tx in db_transactions]


@router.get('/adjacentTransactions', response_model=List[schemas.Transaction])
async def get_adjacent_transactions(
hash: str = Query(..., description='Transaction hash. Acceptable in hex, base64 and base64url forms.'),
Expand Down Expand Up @@ -523,3 +506,7 @@ async def get_top_accounts_by_balance(
limit=limit,
offset=offset)
return [schemas.AccountBalance.from_orm(x) for x in res]

if settings.ton_http_api_endpoint:
from indexer.api.api_v1.ton_http_api_proxy import router as proxy_router
router.include_router(proxy_router)
183 changes: 182 additions & 1 deletion indexer/indexer/api/api_v1/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,17 @@ class AccountStatus(str, Enum):
active = 'active'
nonexist = 'nonexist'

@classmethod
def from_ton_http_api(cls, value):
if value == 'uninitialized':
return cls.uninit
if value == 'active':
return cls.active
if value == 'frozen':
return cls.frozen
# ton-http-api returns 'uninitialized' for both uninit and nonexist accounts
raise ValueError(f'Unexpected account status: {value}')


class MessageContent(BaseModel):
hash: str
Expand Down Expand Up @@ -464,4 +475,174 @@ class AccountBalance(BaseModel):
@classmethod
def from_orm(cls, obj):
return AccountBalance(account=address_type_friendly(obj.account, obj),
balance=obj.balance)
balance=obj.balance)

class LatestAccountState(BaseModel):
account_state_hash: str
last_trans_lt: str
last_trans_timestamp: int
balance: str
account_status: AccountStatus
frozen_hash: Optional[str]
code_hash: Optional[str]
data_hash: Optional[str]

@classmethod
def from_orm(cls, obj):
return LatestAccountState(account_state_hash=hash_type(obj.hash),
last_trans_lt=obj.last_trans_lt,
last_trans_timestamp=obj.timestamp,
balance=obj.balance,
account_status=AccountStatus(obj.account_status),
frozen_hash=hash_type(obj.frozen_hash),
code_hash=hash_type(obj.code_hash),
data_hash=hash_type(obj.data_hash))

class ExternalMessage(BaseModel):
"""
Message in base64 boc serialized format.
"""
boc: str = Field(examples=["te6ccgECBQEAARUAAkWIAWTtae+KgtbrX26Bep8JSq8lFLfGOoyGR/xwdjfvpvEaHg"])

class SentMessage(BaseModel):
message_hash: str = Field(description="Hash of sent message in hex format", examples=["383E348617141E35BC25ED9CD0EDEC2A4EAF6413948BF1FB7F865CEFE8C2CD44"])

@classmethod
def from_ton_http_api(cls, obj):
return SentMessage(message_hash=hash_type(obj['hash']))

class GetMethodParameterType(Enum):
cell = "cell"
slice = "slice"
num = "num"
list = "list"
tuple = "tuple"
unsupported_type = "unsupported_type"

class GetMethodParameter(BaseModel):
type: GetMethodParameterType
value: Union[List['GetMethodParameter'], str, None]

@classmethod
def from_ton_http_api(cls, obj):
if obj[0] == 'cell':
return GetMethodParameter(type=GetMethodParameterType.cell, value=obj[1]['bytes'])
elif obj[0] == 'slice':
return GetMethodParameter(type=GetMethodParameterType.slice, value=obj[1]['bytes'])
elif obj[0] == 'num':
return GetMethodParameter(type=GetMethodParameterType.num, value=obj[1])
elif obj[0] == 'list':
return GetMethodParameter(type=GetMethodParameterType.list, value=[GetMethodParameter.from_ton_http_api(x) for x in obj[1]['elements']])
elif obj[0] == 'tuple':
return GetMethodParameter(type=GetMethodParameterType.tuple, value=[GetMethodParameter.from_ton_http_api(x) for x in obj[1]['elements']])

return GetMethodParameter(type=GetMethodParameterType.unsupported_type, value=None)

class RunGetMethodRequest(BaseModel):
address: str
method: str
stack: List[GetMethodParameter]

def to_ton_http_api(self) -> dict:
ton_http_api_stack = []
for p in self.stack:
if p.type == GetMethodParameterType.num:
ton_http_api_stack.append(['num', p.value])
elif p.type == GetMethodParameterType.cell:
ton_http_api_stack.append(['tvm.Cell', p.value])
elif p.type == GetMethodParameterType.slice:
ton_http_api_stack.append(['tvm.Slice', p.value])
else:
raise Exception(f"Unsupported stack parameter type: {p.type}")
return {
'address': self.address,
'method': self.method,
'stack': ton_http_api_stack
}

class RunGetMethodResponse(BaseModel):
gas_used: int
exit_code: int
stack: List[GetMethodParameter]

@classmethod
def from_ton_http_api(cls, obj):
return RunGetMethodResponse(gas_used=obj['gas_used'],
exit_code=obj['exit_code'],
stack=[GetMethodParameter.from_ton_http_api(x) for x in obj['stack']])

class EstimateFeeRequest(BaseModel):
address: str
body: str
init_code: Optional[str] = None
init_data: Optional[str] = None
ignore_chksig: bool = True

def to_ton_http_api(self) -> dict:
return {
'address': self.address,
'body': self.body,
'init_code': self.init_code if self.init_code else '',
'init_data': self.init_data if self.init_data else '',
'ignore_chksig': self.ignore_chksig
}

class Fee(BaseModel):
in_fwd_fee: int
storage_fee: int
gas_fee: int
fwd_fee: int

@classmethod
def from_ton_http_api(cls, obj):
return Fee(in_fwd_fee=obj['in_fwd_fee'],
storage_fee=obj['storage_fee'],
gas_fee=obj['gas_fee'],
fwd_fee=obj['fwd_fee'])

class EstimateFeeResponse(BaseModel):
source_fees: Fee
destination_fees: List[Fee]

@classmethod
def from_ton_http_api(cls, obj):
return EstimateFeeResponse(source_fees=Fee.from_ton_http_api(obj['source_fees']),
destination_fees=[Fee.from_ton_http_api(x) for x in obj['destination_fees']])

class Account(BaseModel):
balance: str
code: Optional[str]
data: Optional[str]
last_transaction_lt: Optional[str]
last_transaction_hash: Optional[str]
frozen_hash: Optional[str]
status: AccountStatus

@classmethod
def from_ton_http_api(cls, obj):
null_hash = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
return Account(balance=obj['balance'],
code=obj['code'] if len(obj['code']) > 0 else None,
data=obj['data'] if len(obj['data']) > 0 else None,
last_transaction_lt=obj['last_transaction_id']['lt'] if obj['last_transaction_id']['lt'] != '0' else None,
last_transaction_hash=obj['last_transaction_id']['hash'] if obj['last_transaction_id']['hash'] != null_hash else None,
frozen_hash=obj['frozen_hash'] if len(obj['frozen_hash']) > 0 else None,
status=AccountStatus.from_ton_http_api(obj['state']))

class WalletInfo(BaseModel):
balance: str
wallet_type: str
seqno: int
wallet_id: Optional[int]
last_transaction_lt: Optional[str]
last_transaction_hash: Optional[str]

@classmethod
def from_ton_http_api(cls, obj):
null_hash = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
return WalletInfo(balance=obj['balance'],
wallet_type=obj['wallet_type'],
seqno=obj['seqno'],
wallet_id=obj.get('wallet_id'),
last_transaction_lt=obj['last_transaction_id']['lt'] if obj['last_transaction_id']['lt'] != '0' else None,
last_transaction_hash=obj['last_transaction_id']['hash'] if obj['last_transaction_id']['hash'] != null_hash else None)
Loading

0 comments on commit 1cdfed7

Please sign in to comment.