Add inital version.

pull/659/head
Andrey 2022-08-30 17:37:38 +03:00
rodzic 77c3e7e2f8
commit 54870630c4
6 zmienionych plików z 766 dodań i 473 usunięć

Wyświetl plik

@ -29,6 +29,22 @@ DOCS_TARGET_PATH = "docs"
# Crawler label
CRAWLER_LABEL = "moonworm-alpha"
VIEW_STATE_CRAWLER_LABEL = "view-state-alpha"
MOONSTREAM_STATE_CRAWLER_DB_STATEMENT_TIMEOUT_MILLIS = 30000
MOONSTREAM_STATE_CRAWLER_DB_STATEMENT_TIMEOUT_MILLIS_RAW = os.environ.get(
"MOONSTREAM_QUERY_API_DB_STATEMENT_TIMEOUT_MILLIS"
)
try:
if MOONSTREAM_STATE_CRAWLER_DB_STATEMENT_TIMEOUT_MILLIS_RAW is not None:
MOONSTREAM_STATE_CRAWLER_DB_STATEMENT_TIMEOUT_MILLIS = int(
MOONSTREAM_STATE_CRAWLER_DB_STATEMENT_TIMEOUT_MILLIS_RAW
)
except:
raise Exception(
f"Could not parse MOONSTREAM_QUERY_API_DB_STATEMENT_TIMEOUT_MILLIS as int: {MOONSTREAM_STATE_CRAWLER_DB_STATEMENT_TIMEOUT_MILLIS_RAW}"
)
# Geth connection address
MOONSTREAM_ETHEREUM_WEB3_PROVIDER_URI = os.environ.get(

Wyświetl plik

@ -0,0 +1,487 @@
# Code generated by moonworm : https://github.com/bugout-dev/moonworm
# Moonworm version : 0.2.4
import argparse
import json
import os
from pathlib import Path
from typing import Any, Dict, List, Optional, Union
from brownie import Contract, network, project
from brownie.network.contract import ContractContainer
from eth_typing.evm import ChecksumAddress
PROJECT_DIRECTORY = os.path.abspath(os.path.dirname(__file__))
print("PROJECT_DIRECTORY", PROJECT_DIRECTORY)
BUILD_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "build", "contracts")
def boolean_argument_type(raw_value: str) -> bool:
TRUE_VALUES = ["1", "t", "y", "true", "yes"]
FALSE_VALUES = ["0", "f", "n", "false", "no"]
if raw_value.lower() in TRUE_VALUES:
return True
elif raw_value.lower() in FALSE_VALUES:
return False
raise ValueError(
f"Invalid boolean argument: {raw_value}. Value must be one of: {','.join(TRUE_VALUES + FALSE_VALUES)}"
)
def bytes_argument_type(raw_value: str) -> str:
return raw_value
def get_abi_json(abi_name: str) -> List[Dict[str, Any]]:
abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json")
if not os.path.isfile(abi_full_path):
raise IOError(
f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?"
)
with open(abi_full_path, "r") as ifp:
build = json.load(ifp)
abi_json = build.get("abi")
if abi_json is None:
raise ValueError(f"Could not find ABI definition in: {abi_full_path}")
return abi_json
def contract_from_build(abi_name: str) -> ContractContainer:
# This is workaround because brownie currently doesn't support loading the same project multiple
# times. This causes problems when using multiple contracts from the same project in the same
# python project.
PROJECT = project.main.Project("moonworm", Path(PROJECT_DIRECTORY))
abi_full_path = os.path.join(BUILD_DIRECTORY, f"{abi_name}.json")
if not os.path.isfile(abi_full_path):
raise IOError(
f"File does not exist: {abi_full_path}. Maybe you have to compile the smart contracts?"
)
with open(abi_full_path, "r") as ifp:
build = json.load(ifp)
return ContractContainer(PROJECT, build)
class Multicall2:
def __init__(self, contract_address: Optional[ChecksumAddress]):
print("contract_address", contract_address)
self.contract_name = "Multicall2"
self.address = contract_address
self.contract = None
self.abi = get_abi_json("Multicall2")
if self.address is not None:
print(f"Using contract at {self.address}")
print(f"Contract ABI: {self.abi}")
self.contract: Optional[Contract] = Contract.from_abi(
self.contract_name, self.address, self.abi
)
def deploy(self, transaction_config):
contract_class = contract_from_build(self.contract_name)
deployed_contract = contract_class.deploy(transaction_config)
self.address = deployed_contract.address
self.contract = deployed_contract
return deployed_contract.tx
def assert_contract_is_instantiated(self) -> None:
if self.contract is None:
raise Exception("contract has not been instantiated")
def verify_contract(self):
self.assert_contract_is_instantiated()
contract_class = contract_from_build(self.contract_name)
contract_class.publish_source(self.contract)
def aggregate(self, calls: List, transaction_config) -> Any:
self.assert_contract_is_instantiated()
return self.contract.aggregate(calls, transaction_config)
def block_and_aggregate(self, calls: List, transaction_config) -> Any:
self.assert_contract_is_instantiated()
return self.contract.blockAndAggregate(calls, transaction_config)
def get_block_hash(
self, block_number_arg: int, block_number: Optional[Union[str, int]] = "latest"
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.getBlockHash.call(
block_number_arg, block_identifier=block_number
)
def get_block_number(
self, block_number: Optional[Union[str, int]] = "latest"
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.getBlockNumber.call(block_identifier=block_number)
def get_current_block_coinbase(
self, block_number: Optional[Union[str, int]] = "latest"
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.getCurrentBlockCoinbase.call(block_identifier=block_number)
def get_current_block_difficulty(
self, block_number: Optional[Union[str, int]] = "latest"
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.getCurrentBlockDifficulty.call(
block_identifier=block_number
)
def get_current_block_gas_limit(
self, block_number: Optional[Union[str, int]] = "latest"
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.getCurrentBlockGasLimit.call(block_identifier=block_number)
def get_current_block_timestamp(
self, block_number: Optional[Union[str, int]] = "latest"
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.getCurrentBlockTimestamp.call(
block_identifier=block_number
)
def get_eth_balance(
self, addr: ChecksumAddress, block_number: Optional[Union[str, int]] = "latest"
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.getEthBalance.call(addr, block_identifier=block_number)
def get_last_block_hash(
self, block_number: Optional[Union[str, int]] = "latest"
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.getLastBlockHash.call(block_identifier=block_number)
def try_aggregate(
self, require_success: bool, calls: List, transaction_config
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.tryAggregate(require_success, calls, transaction_config)
def try_block_and_aggregate(
self, require_success: bool, calls: List, transaction_config
) -> Any:
self.assert_contract_is_instantiated()
return self.contract.tryBlockAndAggregate(
require_success, calls, transaction_config
)
def get_transaction_config(args: argparse.Namespace) -> Dict[str, Any]:
signer = network.accounts.load(args.sender, args.password)
transaction_config: Dict[str, Any] = {"from": signer}
if args.gas_price is not None:
transaction_config["gas_price"] = args.gas_price
if args.max_fee_per_gas is not None:
transaction_config["max_fee"] = args.max_fee_per_gas
if args.max_priority_fee_per_gas is not None:
transaction_config["priority_fee"] = args.max_priority_fee_per_gas
if args.confirmations is not None:
transaction_config["required_confs"] = args.confirmations
if args.nonce is not None:
transaction_config["nonce"] = args.nonce
return transaction_config
def add_default_arguments(parser: argparse.ArgumentParser, transact: bool) -> None:
parser.add_argument(
"--network", required=True, help="Name of brownie network to connect to"
)
parser.add_argument(
"--address", required=False, help="Address of deployed contract to connect to"
)
if not transact:
parser.add_argument(
"--block-number",
required=False,
type=int,
help="Call at the given block number, defaults to latest",
)
return
parser.add_argument(
"--sender", required=True, help="Path to keystore file for transaction sender"
)
parser.add_argument(
"--password",
required=False,
help="Password to keystore file (if you do not provide it, you will be prompted for it)",
)
parser.add_argument(
"--gas-price", default=None, help="Gas price at which to submit transaction"
)
parser.add_argument(
"--max-fee-per-gas",
default=None,
help="Max fee per gas for EIP1559 transactions",
)
parser.add_argument(
"--max-priority-fee-per-gas",
default=None,
help="Max priority fee per gas for EIP1559 transactions",
)
parser.add_argument(
"--confirmations",
type=int,
default=None,
help="Number of confirmations to await before considering a transaction completed",
)
parser.add_argument(
"--nonce", type=int, default=None, help="Nonce for the transaction (optional)"
)
parser.add_argument(
"--value", default=None, help="Value of the transaction in wei(optional)"
)
parser.add_argument("--verbose", action="store_true", help="Print verbose output")
def handle_deploy(args: argparse.Namespace) -> None:
network.connect(args.network)
transaction_config = get_transaction_config(args)
contract = Multicall2(None)
result = contract.deploy(transaction_config=transaction_config)
print(result)
if args.verbose:
print(result.info())
def handle_verify_contract(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
result = contract.verify_contract()
print(result)
def handle_aggregate(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
transaction_config = get_transaction_config(args)
result = contract.aggregate(calls=args.calls, transaction_config=transaction_config)
print(result)
if args.verbose:
print(result.info())
def handle_block_and_aggregate(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
transaction_config = get_transaction_config(args)
result = contract.block_and_aggregate(
calls=args.calls, transaction_config=transaction_config
)
print(result)
if args.verbose:
print(result.info())
def handle_get_block_hash(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
result = contract.get_block_hash(
block_number_arg=args.block_number_arg, block_number=args.block_number
)
print(result)
def handle_get_block_number(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
result = contract.get_block_number(block_number=args.block_number)
print(result)
def handle_get_current_block_coinbase(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
result = contract.get_current_block_coinbase(block_number=args.block_number)
print(result)
def handle_get_current_block_difficulty(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
result = contract.get_current_block_difficulty(block_number=args.block_number)
print(result)
def handle_get_current_block_gas_limit(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
result = contract.get_current_block_gas_limit(block_number=args.block_number)
print(result)
def handle_get_current_block_timestamp(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
result = contract.get_current_block_timestamp(block_number=args.block_number)
print(result)
def handle_get_eth_balance(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
result = contract.get_eth_balance(addr=args.addr, block_number=args.block_number)
print(result)
def handle_get_last_block_hash(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
result = contract.get_last_block_hash(block_number=args.block_number)
print(result)
def handle_try_aggregate(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
transaction_config = get_transaction_config(args)
result = contract.try_aggregate(
require_success=args.require_success,
calls=args.calls,
transaction_config=transaction_config,
)
print(result)
if args.verbose:
print(result.info())
def handle_try_block_and_aggregate(args: argparse.Namespace) -> None:
network.connect(args.network)
contract = Multicall2(args.address)
transaction_config = get_transaction_config(args)
result = contract.try_block_and_aggregate(
require_success=args.require_success,
calls=args.calls,
transaction_config=transaction_config,
)
print(result)
if args.verbose:
print(result.info())
def generate_cli() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="CLI for Multicall2")
parser.set_defaults(func=lambda _: parser.print_help())
subcommands = parser.add_subparsers()
deploy_parser = subcommands.add_parser("deploy")
add_default_arguments(deploy_parser, True)
deploy_parser.set_defaults(func=handle_deploy)
verify_contract_parser = subcommands.add_parser("verify-contract")
add_default_arguments(verify_contract_parser, False)
verify_contract_parser.set_defaults(func=handle_verify_contract)
aggregate_parser = subcommands.add_parser("aggregate")
add_default_arguments(aggregate_parser, True)
aggregate_parser.add_argument(
"--calls", required=True, help="Type: tuple[]", nargs="+"
)
aggregate_parser.set_defaults(func=handle_aggregate)
block_and_aggregate_parser = subcommands.add_parser("block-and-aggregate")
add_default_arguments(block_and_aggregate_parser, True)
block_and_aggregate_parser.add_argument(
"--calls", required=True, help="Type: tuple[]", nargs="+"
)
block_and_aggregate_parser.set_defaults(func=handle_block_and_aggregate)
get_block_hash_parser = subcommands.add_parser("get-block-hash")
add_default_arguments(get_block_hash_parser, False)
get_block_hash_parser.add_argument(
"--block-number", required=True, help="Type: uint256", type=int
)
get_block_hash_parser.set_defaults(func=handle_get_block_hash)
get_block_number_parser = subcommands.add_parser("get-block-number")
add_default_arguments(get_block_number_parser, False)
get_block_number_parser.set_defaults(func=handle_get_block_number)
get_current_block_coinbase_parser = subcommands.add_parser(
"get-current-block-coinbase"
)
add_default_arguments(get_current_block_coinbase_parser, False)
get_current_block_coinbase_parser.set_defaults(
func=handle_get_current_block_coinbase
)
get_current_block_difficulty_parser = subcommands.add_parser(
"get-current-block-difficulty"
)
add_default_arguments(get_current_block_difficulty_parser, False)
get_current_block_difficulty_parser.set_defaults(
func=handle_get_current_block_difficulty
)
get_current_block_gas_limit_parser = subcommands.add_parser(
"get-current-block-gas-limit"
)
add_default_arguments(get_current_block_gas_limit_parser, False)
get_current_block_gas_limit_parser.set_defaults(
func=handle_get_current_block_gas_limit
)
get_current_block_timestamp_parser = subcommands.add_parser(
"get-current-block-timestamp"
)
add_default_arguments(get_current_block_timestamp_parser, False)
get_current_block_timestamp_parser.set_defaults(
func=handle_get_current_block_timestamp
)
get_eth_balance_parser = subcommands.add_parser("get-eth-balance")
add_default_arguments(get_eth_balance_parser, False)
get_eth_balance_parser.add_argument("--addr", required=True, help="Type: address")
get_eth_balance_parser.set_defaults(func=handle_get_eth_balance)
get_last_block_hash_parser = subcommands.add_parser("get-last-block-hash")
add_default_arguments(get_last_block_hash_parser, False)
get_last_block_hash_parser.set_defaults(func=handle_get_last_block_hash)
try_aggregate_parser = subcommands.add_parser("try-aggregate")
add_default_arguments(try_aggregate_parser, True)
try_aggregate_parser.add_argument(
"--require-success",
required=True,
help="Type: bool",
type=boolean_argument_type,
)
try_aggregate_parser.add_argument(
"--calls", required=True, help="Type: tuple[]", nargs="+"
)
try_aggregate_parser.set_defaults(func=handle_try_aggregate)
try_block_and_aggregate_parser = subcommands.add_parser("try-block-and-aggregate")
add_default_arguments(try_block_and_aggregate_parser, True)
try_block_and_aggregate_parser.add_argument(
"--require-success",
required=True,
help="Type: bool",
type=boolean_argument_type,
)
try_block_and_aggregate_parser.add_argument(
"--calls", required=True, help="Type: tuple[]", nargs="+"
)
try_block_and_aggregate_parser.set_defaults(func=handle_try_block_and_aggregate)
return parser
def main() -> None:
parser = generate_cli()
args = parser.parse_args()
args.func(args)
if __name__ == "__main__":
main()

Wyświetl plik

@ -2,22 +2,28 @@ import argparse
import json
import hashlib
import itertools
from pickle import TRUE
from pprint import pprint
import logging
from random import random
from typing import Optional, List, Any, Union, Dict, Tuple, Callable, Iterable, cast
from typing import List, Any
from uuid import UUID
from moonstreamdb.blockchain import AvailableBlockchainType
from moonstreamdb.db import yield_db_session_ctx
import web3
from web3 import Web3
from web3.middleware import geth_poa_middleware
from moonstreamdb.db import (
MOONSTREAM_DB_URI_READ_ONLY,
MOONSTREAM_POOL_SIZE,
create_moonstream_engine,
)
from sqlalchemy.orm import sessionmaker
from .db import view_call_to_label, commit_session
from ..settings import (
NB_CONTROLLER_ACCESS_ID,
MOONSTREAM_STATE_CRAWLER_DB_STATEMENT_TIMEOUT_MILLIS,
)
from ..settings import MOONSTREAM_MOONWORM_TASKS_JOURNAL, NB_CONTROLLER_ACCESS_ID
from .db import get_first_labeled_block_number, get_last_labeled_block_number
from .historical_crawler import historical_crawler
from brownie import Contract, network, chain
# from .db import get_first_labeled_block_number, get_last_labeled_block_number
from brownie import Contract, network, chain, web3
import Multicall2
logging.basicConfig(level=logging.INFO)
@ -30,19 +36,21 @@ Multicall2_address = "0xc8E51042792d7405184DfCa245F2d27B94D013b6"
def make_multicall(
multicall_method: Any,
calls: List[Any],
block_timestamp: int,
block_number: str = "latest",
) -> Any:
# print("calls")
# pprint(calls)
multicall_calls = [
(
call["address"],
call["method"].encode_input(*call["inputs"]),
)
for call in calls
]
multicall_result = multicall_method.call(
False, # success not required
[
(
call["address"],
call["method"].encode_input(*call["inputs"]),
)
for call in calls
],
multicall_calls,
block_identifier=block_number,
)
@ -51,65 +59,143 @@ def make_multicall(
# Handle the case with not successful calls
for index, encoded_data in enumerate(multicall_result):
if encoded_data[0]:
# print(dir(calls[index]["method"]))
results.append(
{
"result": calls[index]["method"].decode_output(encoded_data[1]),
"hash": calls[index]["hash"],
"method": calls[index]["method"],
"address": calls[index]["address"],
"name": calls[index]["method"]._name,
"name": calls[index]["method"].abi["name"],
"inputs": calls[index]["inputs"],
"call_data": multicall_calls[index][1],
"block_number": block_number,
"block_timestamp": block_timestamp,
"status": encoded_data[0],
}
)
else:
# print(encoded_data)
results.append(
{
"result": None, # calls[index]["method"].decode_output(encoded_data[1]),
"hash": calls[index]["hash"],
"method": calls[index]["method"],
"address": calls[index]["address"],
"name": calls[index]["method"]._name,
"name": calls[index]["method"].abi["name"],
"inputs": calls[index]["inputs"],
"call_data": multicall_calls[index][1],
"block_number": block_number,
"block_timestamp": block_timestamp,
"status": encoded_data[0],
}
)
return results
# def crawl_job(job):
# print(job)
# pass
def crawl_calls_level(
db_session,
calls,
responces,
contracts_ABIs,
interfaces,
batch_size,
multicall_method,
block_number,
blockchain_type,
block_timestamp,
):
calls_of_level = []
for call in calls:
parameters = []
for input in call["inputs"]:
if type(input["value"]) in (str, int):
if input["value"] not in responces:
parameters.append([input["value"]])
else:
if (
contracts_ABIs[call["address"]][input["value"]]["name"]
== "totalSupply"
):
parameters.append(
list(range(1, responces[input["value"]][0] + 1))
)
else:
parameters.append(responces[input["value"]])
elif type(input["value"]) == list:
parameters.append(input["value"])
else:
raise
for call_parameters in itertools.product(*parameters):
calls_of_level.append(
{
"address": call["address"],
"method": interfaces[call["address"]].get_method_object(
interfaces[call["address"]].signatures[call["name"]]
),
"hash": call["generated_hash"],
"inputs": call_parameters,
}
)
for call_chunk in [
calls_of_level[i : i + batch_size]
for i in range(0, len(calls_of_level), batch_size)
]:
while True:
try:
make_multicall_result = make_multicall(
multicall_method=multicall_method,
calls=call_chunk,
block_number=block_number,
block_timestamp=block_timestamp,
)
break
except ValueError:
continue
# results parsing and writing to database
for result in make_multicall_result:
db_view = view_call_to_label(blockchain_type, result)
db_session.add(db_view)
if result["hash"] not in responces:
responces[result["hash"]] = []
responces[result["hash"]].append(result["result"])
commit_session(db_session)
# return generated_hash
def generate_call_tree(jobs):
def parse_jobs(jobs, blockchain_type, block_number):
contracts_ABIs = {}
contracts_methods = {}
calls = {"WithoutSubcalls": [], "WithSubcalls": {}}
calls = {0: []}
# client = web3.Web3()
blockchain_type_to_brownie_network = {
AvailableBlockchainType.POLYGON: "polygon-main",
AvailableBlockchainType.MUMBAI: "polygon-test",
}
network.connect("polygon-main")
network.connect(blockchain_type_to_brownie_network[blockchain_type])
if block_number is None:
block_number = len(chain) - 1
block_timestamp = web3.eth.get_block(block_number).timestamp
print(len(chain))
multicaller = Multicall2.Multicall2(Multicall2_address)
multicall_method = multicaller.contract.tryAggregate
# test call
def recursive_unpack(method_abi: Any, level: int = 0) -> Any:
have_subcalls = False
print(level)
print(method_abi)
abi = {
"level": level,
"inputs": [],
"outputs": method_abi["outputs"],
"name": method_abi["name"],
@ -129,41 +215,22 @@ def generate_call_tree(jobs):
input["value"] = hash_link
have_subcalls = True
abi["inputs"].append(input)
abi["address"] = method_abi["address"]
generated_hash = hashlib.md5(json.dumps(abi).encode("utf-8")).hexdigest()
abi["generated_hash"] = generated_hash
if have_subcalls:
abi["generated_hash"] = generated_hash
abi["address"] = method_abi["address"]
if not calls["WithSubcalls"].get(level):
calls["WithSubcalls"][level] = []
calls["WithSubcalls"][level].append(abi)
level += 1
if not calls.get(level):
calls[level] = []
calls[level].append(abi)
else:
# generate calls
# generate parameters list of parameters lists
level = 0
parameters = []
for input in abi["inputs"]:
if type(input["value"]) in (str, int):
parameters.append([input["value"]])
elif type(input["value"]) == list:
parameters.append(input["value"])
else:
raise Exception(f"Unknown type {type(input['value'])}")
for call_parameters in itertools.product(*parameters):
# if not calls["WithoutSubcalls"].get(generated_hash):
# calls["WithoutSubcalls"][generated_hash] = []
calls["WithoutSubcalls"].append(
{
"address": job["address"],
"inputs": call_parameters,
"generated_hash": generated_hash,
"name": abi["name"],
}
)
if not calls.get(level):
calls[level] = []
calls[level].append(abi)
if not contracts_methods.get(job["address"]):
contracts_methods[job["address"]] = []
@ -175,9 +242,6 @@ def generate_call_tree(jobs):
return generated_hash
# selector
# hashlib.md5(json.dumps(method).encode("utf-8")).hexdigest()
for job in jobs:
if job["address"] not in contracts_ABIs:
contracts_ABIs[job["address"]] = []
@ -188,32 +252,6 @@ def generate_call_tree(jobs):
interfaces = {}
# web3 general interface workflow
# for contract_address in contracts_ABIs:
# # collect abis for each contract
# abis = []
# for method_hash in contracts_methods[contract_address]:
# abis.append(contracts_ABIs[contract_address][method_hash])
# # generate interface
# interfaces[contract_address] = client.eth.contract(abi=abis,address=contract_address)
# batch_calls = []
# for call in calls["WithoutSubcalls"]:
# print(dir(interfaces[call["address"]].functions[call["name"]](*call["inputs"])))
# batch_calls.append(
# {
# "to": call["address"],
# "call": interfaces[call["address"]].encodeABI(fn_name=call["name"], args=call["inputs"]),
# "hash": call["generated_hash"]
# }
# )
# Brownie interface generating
for contract_address in contracts_ABIs:
# collect abis for each contract
@ -227,134 +265,60 @@ def generate_call_tree(jobs):
random(), contract_address, abis
)
batch_calls = []
responces = {}
for call in calls["WithoutSubcalls"]:
# print(dir(interfaces[call["address"]]))
# print(
# interfaces[call["address"]].get_method_object(
# interfaces[call["address"]].signatures[call["name"]]
# )
# )
batch_calls.append(
{
"address": call["address"],
"method": interfaces[call["address"]].get_method_object(
interfaces[call["address"]].signatures[call["name"]]
),
"hash": call["generated_hash"],
"inputs": call["inputs"],
}
)
make_multicall_result = make_multicall(
multicall_method=multicall_method, calls=batch_calls
)
# responces dict
responces = {} # {generated_hash: []}
# create chunks of calls
# # create chunks of calls
batch_size = 500
for call_chunk in [
batch_calls[i : i + batch_size] for i in range(0, len(batch_calls), batch_size)
]:
print("batch_step", len(call_chunk))
while True:
try:
make_multicall_result = make_multicall(
multicall_method=multicall_method, calls=call_chunk
)
break
except ValueError:
continue
# results parsing and writing to database
for result in make_multicall_result:
if result["hash"] not in responces:
responces[result["hash"]] = []
responces[result["hash"]].append(result["result"])
# proccessing call_tree
# reverse call_tree
call_tree_levels = sorted(calls["WithSubcalls"].keys(), reverse=True)
call_tree_levels = sorted(calls.keys(), reverse=True)[:-1]
for level in call_tree_levels:
calls_of_level = []
for call in calls["WithSubcalls"][level]:
parameters = []
for input in call["inputs"]:
if type(input["value"]) in (str, int):
if input["value"] not in responces:
parameters.append([input["value"]])
else:
# print(responces[input["value"]])
if (
contracts_ABIs[call["address"]][input["value"]]["name"]
== "totalSupply"
):
parameters.append(
list(range(0, responces[input["value"]][0]))
)
print(len(list(range(0, responces[input["value"]][0]))))
else:
parameters.append(responces[input["value"]])
elif type(input["value"]) == list:
parameters.append(input["value"])
else:
raise
for call_parameters in itertools.product(*parameters):
calls_of_level.append(
{
"address": call["address"],
"method": interfaces[call["address"]].get_method_object(
interfaces[call["address"]].signatures[call["name"]]
),
"hash": call["generated_hash"],
"inputs": call_parameters,
}
)
print(len(call_parameters))
for call_chunk in [
calls_of_level[i : i + batch_size]
for i in range(0, len(calls_of_level), batch_size)
]:
print("batch_step", len(call_chunk))
while True:
try:
make_multicall_result = make_multicall(
multicall_method=multicall_method, calls=call_chunk
)
break
except ValueError:
continue
# results parsing and writing to database
for result in make_multicall_result:
if result["hash"] not in responces:
responces[result["hash"]] = []
responces[result["hash"]].append(result["result"])
print("contracts_methods")
pprint(contracts_methods)
print("contracts_ABIs")
pprint(contracts_ABIs)
print("calls")
pprint(calls)
print("responses")
pprint(responces)
engine = create_moonstream_engine(
MOONSTREAM_DB_URI_READ_ONLY,
pool_pre_ping=True,
pool_size=MOONSTREAM_POOL_SIZE,
statement_timeout=MOONSTREAM_STATE_CRAWLER_DB_STATEMENT_TIMEOUT_MILLIS,
)
process_session = sessionmaker(bind=engine)
db_session = process_session()
# run crawling of levels
try:
# initial call
crawl_calls_level(
db_session,
calls[0],
responces,
contracts_ABIs,
interfaces,
batch_size,
multicall_method,
block_number,
blockchain_type,
block_timestamp,
)
for level in call_tree_levels:
crawl_calls_level(
db_session,
calls[level],
responces,
contracts_ABIs,
interfaces,
batch_size,
multicall_method,
block_number,
blockchain_type,
block_timestamp,
)
finally:
db_session.close()
print(responces)
def handle_crawl(args: argparse.Namespace) -> None:
@ -363,195 +327,39 @@ def handle_crawl(args: argparse.Namespace) -> None:
Ability to track states of the contracts.
Read all view methods of the contracts and crawl
The querstion is input patameters of method
tags:
address:0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f
type:function
abi_method_hash:5fbaec05ae19ac90eea75b676a20e495
subscription_type:polygon_smartcontract
abi_name:transfer
status:active
contract_address:0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f
content:
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_tokenId",
"type": "uint256"
}
],
"name": "getDNA",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
{
"inputs": [
{
"name": "_tokenId",
"type": "uint256"
"position":0,
"value": {
"address": "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f",
"inputs": [],
"name": "totalSupply"
}
}
],
"name": "getDNA",
"address": "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f",
}
{
"inputs": [
{
"name": "_tokenId",
"type": "uint256"
"value": {
"address": "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f",
"inputs": [],
"name": "totalSupply"
}
}
],
"name": "getDNA",
"address": "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f",
}
{
"name": "getUnicornBodyParts",
"address": "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f",
"inputs": [ {
"name": "_dna",
"type": "uint256"
"value":{
"inputs": [
{
"name": "_tokenId",
"type": "uint256"
"value": {
"address": "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f",
"inputs": [],
"name": "totalSupply"
}
}
],
"name": "getDNA",
"address": "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f",
}
}
]
}
"""
my_job = {
"name": "getUnicornBodyParts",
"address": "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f",
"type": "function",
"stateMutability": "view",
"inputs": [
{
"name": "_dna",
"type": "uint256",
"internalType": "uint256",
"name": "tokenId",
"type": "uint256",
"value": {
"type": "function",
"inputs": [
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"modify": "range",
"name": "_tokenId",
"name": "",
"type": "uint256",
"value": {
"type": "function",
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256",
}
],
"address": "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f",
"inputs": [],
},
}
],
"name": "getDNA",
"outputs": [
{"internalType": "uint256", "name": "", "type": "uint256"}
],
"address": "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f",
"inputs": [],
},
}
],
"outputs": [
{"internalType": "uint256", "name": "bodyPartId", "type": "uint256"},
{"internalType": "uint256", "name": "facePartId", "type": "uint256"},
{"internalType": "uint256", "name": "hornPartId", "type": "uint256"},
{"internalType": "uint256", "name": "hoovesPartId", "type": "uint256"},
{"internalType": "uint256", "name": "manePartId", "type": "uint256"},
{"internalType": "uint256", "name": "tailPartId", "type": "uint256"},
{"internalType": "uint8", "name": "mythicCount", "type": "uint8"},
],
"name": "tokenURI",
"outputs": [{"internalType": "string", "name": "", "type": "string"}],
"address": "0xdC0479CC5BbA033B3e7De9F178607150B3AbCe1f",
}
# blockchain_type = AvailableBlockchainType(args.blockchain_type)
blockchain_type = AvailableBlockchainType(args.blockchain_type)
# if args.web3 is None:
# raise ValueError("Web3 provider URL is required")
# web3 = Web3(Web3.HTTPProvider(args.web3))
# if args.poa:
# web3.middleware_stack.inject(geth_poa_middleware, layer=0)
# Generate call tree
call_tree = generate_call_tree([my_job])
print(call_tree)
parse_jobs([my_job], blockchain_type, args.block_number)
def parse_abi(args: argparse.Namespace) -> None:
@ -586,99 +394,21 @@ def main() -> None:
subparsers = parser.add_subparsers()
parser_parse_abi = subparsers.add_parser(
"parse_job",
view_state_crawler_parser = subparsers.add_parser(
"crawl_jobs",
help="continuous crawling the event/function call jobs from bugout journal",
)
parser_parse_abi.set_defaults(func=handle_crawl)
crawl_parser = subparsers.add_parser(
"crawl",
help="continuous crawling the event/function call jobs from bugout journal",
)
crawl_parser.add_argument(
"--start",
"-s",
type=int,
default=None,
help="start block number",
)
crawl_parser.add_argument(
view_state_crawler_parser.add_argument(
"--blockchain-type",
"-b",
type=str,
help=f"Available blockchain types: {[member.value for member in AvailableBlockchainType]}",
help="Type of blovkchain wich writng in database",
required=True,
)
crawl_parser.add_argument(
"--web3",
type=str,
default=None,
help="Web3 provider URL",
view_state_crawler_parser.add_argument(
"--block-number", "-N", type=str, help="Block number."
)
crawl_parser.add_argument(
"--poa",
action="store_true",
default=False,
help="Use PoA middleware",
)
crawl_parser.add_argument(
"--max-blocks-batch",
"-m",
type=int,
default=80,
help="Maximum number of blocks to crawl in a single batch",
)
crawl_parser.add_argument(
"--min-blocks-batch",
"-n",
type=int,
default=20,
help="Minimum number of blocks to crawl in a single batch",
)
crawl_parser.add_argument(
"--confirmations",
"-c",
type=int,
default=175,
help="Number of confirmations to wait for",
)
crawl_parser.add_argument(
"--min-sleep-time",
"-t",
type=float,
default=0.1,
help="Minimum time to sleep between crawl step",
)
crawl_parser.add_argument(
"--heartbeat-interval",
"-i",
type=float,
default=60,
help="Heartbeat interval in seconds",
)
crawl_parser.add_argument(
"--new-jobs-refetch-interval",
"-r",
type=float,
default=180,
help="Time to wait before refetching new jobs",
)
crawl_parser.add_argument(
"--force",
action="store_true",
default=False,
help="Force start from the start block",
)
crawl_parser.set_defaults(func=handle_crawl)
view_state_crawler_parser.set_defaults(func=handle_crawl)
generate_view_parser = subparsers.add_parser(
"parse-abi",

Wyświetl plik

@ -0,0 +1,59 @@
import logging
from typing import Dict, Any
from moonstreamdb.blockchain import AvailableBlockchainType, get_label_model
from sqlalchemy.orm import Session
from ..settings import VIEW_STATE_CRAWLER_LABEL
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def view_call_to_label(
blockchain_type: AvailableBlockchainType,
call: Dict[str, Any],
label_name=VIEW_STATE_CRAWLER_LABEL,
):
"""
Creates a label model.
"result": calls[index]["method"].decode_output(encoded_data[1]),
"hash": calls[index]["hash"],
"address": calls[index]["address"],
"name": calls[index]["method"].name,
"inputs": calls[index]["inputs"],
"block_number": block_number,
"""
label_model = get_label_model(blockchain_type)
label = label_model(
label=label_name,
label_data={
"type": "view",
"name": call["name"],
"result": call["result"],
"inputs": call["inputs"],
"call_data": call["call_data"],
"status": call["status"],
},
address=call["address"],
block_number=call["block_number"],
transaction_hash=None,
block_timestamp=call["block_timestamp"],
)
return label
def commit_session(db_session: Session) -> None:
"""
Save labels in the database.
"""
try:
logger.info("Committing session to database")
db_session.commit()
except Exception as e:
logger.error(f"Failed to save labels: {e}")
db_session.rollback()
raise e

Wyświetl plik

@ -62,6 +62,7 @@ setup(
"moonworm-crawler=mooncrawl.moonworm_crawler.cli:main",
"nft=mooncrawl.nft.cli:main",
"statistics=mooncrawl.stats_worker.dashboard:main",
"state-crawler=mooncrawl.state_crawler.cli:main"
]
},
)