From ed0cede02ab39b82a772f1fa0844577969ce7a65 Mon Sep 17 00:00:00 2001 From: dpuzyrkov <dpuzyrkov@gmail.com> Date: Thu, 25 May 2023 14:15:44 +0700 Subject: [PATCH] [+] initial --- __init__.py | 0 cellframenet.py | 439 ++++++++++++++++++++++++++++++++++++++++++++++++ contract.py | 154 +++++++++++++++++ helpers.py | 54 ++++++ 4 files changed, 647 insertions(+) create mode 100644 __init__.py create mode 100644 cellframenet.py create mode 100644 contract.py create mode 100644 helpers.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/cellframenet.py b/cellframenet.py new file mode 100644 index 0000000..68ad4f7 --- /dev/null +++ b/cellframenet.py @@ -0,0 +1,439 @@ +import CellFrame +from CellFrame.Network import Net +from CellFrame.Common import DatumEmission +from DAP import Crypto +from DAP.Crypto import Cert, Sign +from CellFrame.Chain import Mempool +from CellFrame.Consensus import DAG,Block +from CellFrame.Common import Datum, DatumTx, TxOut, TxIn, TxToken, TxSig, TxOutCondSubtypeSrvStakeLock, TxInCond +from DAP.Crypto import HashFast +from DAP.Core import logIt +from datetime import datetime +import hashlib + +import json +from cfhelpers.helpers import json_dump, find_tx_out + +class TSD: + TYPE_UNKNOWN = 0x0000 + TYPE_TIMESTAMP = 0x0001 + TYPE_ADDRESS = 0x0002 + TYPE_VALUE = 0x0003 + TYPE_CONTRACT = 0x0004 + TYPE_NET_ID = 0x0005 + TYPE_BLOCK_NUM = 0x0006 + TYPE_TOKEN_SYM = 0x0007 + TYPE_OUTER_TX_HASH = 0x0008 + TYPE_SOURCE = 0x0009 + TYPE_SOURCE_SUBTYPE = 0x000A + TYPE_DATA = 0x000B + TYPE_SENDER = 0x000C + TYPE_TOKEN_ADDRESS = 0x000D + TYPE_SIGNATURS = 0x000E + TYPE_UNIQUE_ID = 0x000F + TYPE_BASE_TX_HASH = 0x0010 + TYPE_EMISSION_CENTER_UID = 0x0011 + TYPE_EMISSION_CENTER_VER = 0x0012 + + +class CellframeEmission: + def __init__(self, datum): + self.datum = datum + self.hash = str(self.datum.hash) + + + + m = hashlib.sha256() + addr = datum.getTSD(TSD.TYPE_ADDRESS) + btx = datum.getTSD(TSD.TYPE_BASE_TX_HASH) + otx = datum.getTSD(TSD.TYPE_OUTER_TX_HASH) + src = datum.getTSD(TSD.TYPE_SOURCE) + stp = datum.getTSD(TSD.TYPE_SOURCE_SUBTYPE) + ts = datum.getTSD(TSD.TYPE_TIMESTAMP) + data = datum.getTSD(TSD.TYPE_DATA) + uid = datum.getTSD(TSD.TYPE_UNIQUE_ID) + + + m.update(str(addr).encode("utf-8")) + m.update(str(btx).encode("utf-8")) + m.update(str(otx).encode("utf-8")) + m.update(str(src).encode("utf-8")) + m.update(str(stp).encode("utf-8")) + m.update(str(data).encode("utf-8")) + m.update(str(ts).encode("utf-8")) + m.update(str(uid).encode("utf-8")) + m.update(str(data).encode("utf-8")) + + self.uid = m.hexdigest() + + def getTSD(self, type): + tsd = self.datum.getTSD(type) + if tsd: + try: + return tsd.decode("utf-8") + except: + pass + return None + + def setTSD(self, type, data): + self.datum.addTSD(type, data) + + +#if datum not base-tx - exception +class CellframeBaseTransactionDatum: + def __init__(self, datum, net, block=None): + self.block = block + self.datum = datum + self.hash = str(datum.hash) + self.created = datum.dateCreated + self.net = net + #base tx : has txToken item + if not self.tx_token(): + raise RuntimeError("Datum {} not base tx".format(self.datum)) + + self.to_address = str(self.tx_out().addr) + self.amount = self.tx_out().value + + self.emission_hash = str(self.tx_token().tokenEmissionHash) + + self.emission_ = None + + def tx_out(self): + try: + return next(filter(lambda x: isinstance(x, TxOut), self.datum.getItems())) + except: + return None + + def tx_in(self): + try: + return next(filter(lambda x: isinstance(x, TxIn), self.datum.getItems())) + except: + return None + + def tx_token(self): + try: + return next(filter(lambda x: isinstance(x, TxToken), self.datum.getItems())) + except: + return None + + def tx_sig(self): + try: + return next(filter(lambda x: isinstance(x, TxSig), self.datum.getItems())) + except: + return None + + def emission(self): + if not self.emission_: + tiker = str(self.tx_token().ticker) + hf = HashFast.fromString(str(self.tx_token().tokenEmissionHash)) + ledger = self.net.getLedger() + ems = ledger.tokenEmissionFind(tiker, hf) + if not ems: + return None + self.emission_ = CellframeEmission(ems) + + return self.emission_ + + +class CellframeNetwork: + + def __init__(self, name, chains, group_alias = None): + + self.name = name + self.net = Net.byName(name) + self.group_alias = group_alias or name + + if not self.net: + raise RuntimeError("No such net: {}".format(name)) + + for chain in chains: + setattr(self, chain, self.net.getChainByName(chain)) + + def set_mempool_notification_callback(self, chain, callback): + callback_name = "{}".format(self.name) + logIt.notice("New mempool notifier for {}".format(callback_name)) + + def callback_wraper(op_code, group, key, value, net_name): + callback(op_code, group, key, value, net_name, self, chain) + + chain.addMempoolNotify(callback_wraper, callback_name) + + + def set_gdbsync_notification_callback(self, callback): + callback_name = "{}".format(self.name) + + logIt.notice("New gdb notifier for {}".format(callback_name)) + + def callback_wraper(op_code, group, key, value, net_name): + callback(self, op_code, group, key, value, net_name) + + self.net.addNotify(callback_wraper, self.name) + + def set_atom_notification_callback(self, chain, callback): + callback_name = "{}".format(self.name) + logIt.notice("New atom notifier for {}".format(callback_name)) + + def callback_wraper(atom, size, callback_name): + callback(atom, size, callback_name, self, chain) + + chain.addAtomNotify(callback_wraper, callback_name) + + def set_ledger_tx_notification_callback(self, callback): + ledger = self.net.getLedger() + + def callback_wrapper(ledger, tx, argv): + callback(ledger, tx, argv, self) + ledger.txAddNotify(callback_wrapper, self.net) + + def load_cert(certname): + return Crypto.Cert.load(certname) + + def extract_emission_from_mempool_nofitication(self, chain, value): + ems = Mempool.emissionExtract(chain, value) + if ems: + return CellframeEmission(ems) + else: + return None + + def create_base_transaction(self, emission, certs): + return Mempool.baseTxCreate(self.main, emission.datum.hash, self.zerochain, emission.datum.value, emission.datum.ticker, + emission.datum.addr, certs) + + def get_emission_by_tsd(self, tsd_dict): + + atom_count = self.zerochain.countAtom() + atoms = self.zerochain.getAtoms(atom_count, 1, True) + + emissions = {} + + for atom in atoms: + + event = DAG.fromAtom(atom[0], atom[1]) + + if not event.datum.isDatumTokenEmission(): + continue + + token_emission = event.datum.getDatumTokenEmission() + if not token_emission: + continue + + results = [] + for key,value in tsd_dict.items(): + + tsd = token_emission.getTSD(key) + + + if not tsd and value == None: + results.append(True) + continue + try: + if tsd and tsd.decode("utf-8") == value: + results.append(True) + continue + except: + pass + + results.append(False) + + if all(results): + emissions[str(event.datum.hash)] = CellframeEmission(token_emission) + + return emissions + + def get_emission_from_mempool_by_tsd(self, tsd_dict): + + datums = Mempool.list(self.net, self.zerochain).values() + + emissions = {} + + for datum in datums: + + if not datum.isDatumTokenEmission(): + continue + + token_emission = datum.getDatumTokenEmission() + if not token_emission: + continue + + results = [] + for key,value in tsd_dict.items(): + + tsd = token_emission.getTSD(key) + + if not tsd and value == None: + results.append(True) + continue + try: + if tsd and tsd.decode("utf-8") == value: + results.append(True) + continue + except: + pass + + results.append(False) + + if all(results): + emissions[str(datum.hash)] = CellframeEmission(token_emission) + + return emissions + + def base_transactions_from_blocks(self, emission_hash=None): + + iterator = self.main.createAtomItem(False) + + ptr = self.main.atomIterGetFirst(iterator) + + if not ptr: + logIt.error("Can't iterate over blocks in {}!".format(self.name)) + return [] + + aptr, size = ptr + #iterate over blocks: atom-pointer should not be none, and size shoud be >0 + while aptr: + + if size <= 0: #skip such blocks + aptr, size = self.main.atomIterGetNext(iter) + continue + + block = Block.fromAtom(aptr, size) + + if not block.datums: + aptr, size = self.main.atomIterGetNext(iterator) + continue + + for datum in block.datums: + if datum.isDatumTX(): + try: + #if emshash provided - filter items + basedatum = CellframeBaseTransactionDatum( datum.getDatumTX(), block) + if emission_hash: + if basedatum.emission_hash == emission_hash: + yield basedatum + else: + continue + else: + yield basedatum + + except: + continue + + aptr, size = self.main.atomIterGetNext(iterator) + + + def get_transactions_to_wallet_from_blocks(self, address): + + def nextBlockDatums(): + iterator = self.createAtomItem(False) + aptr, size = self.main.atomIterGetFirst(iterator) + #iterate over blocks: atom-pointer should not be none, and size shoud be >0 + while aptr: + + if size <= 0: #skip such blocks + aptr, size = self.main.atomIterGetNext(iter) + continue + + block = Block.fromAtom(aptr, size) + + if block.datums: + yield block, block.datums + + + aptr, size = self.main.atomIterGetNext(iterator) + + def isDatumToAddress(datum_with_block): + try: + txn_out = next(filter(lambda x: isinstance(x, TxOut), datum_with_block.datum.getItems())) + return str(txn_out.addr) == address + except Exception as e: + return False + + transactions_to_wallet = [] + + class DatumWithBlock: + def __init__(self, datum, block): + self.datum = datum + self.block = block + + for block, datums in nextBlockDatums(): + tx_datums = [DatumWithBlock(datum.getDatumTX(), block) for datum in filter(lambda datum: datum.isDatumTX(), datums)] + transactions_to_wallet.extend(list(filter(isDatumToAddress, tx_datums))) + + return transactions_to_wallet + + + def create_emission(self, wallet, token_symbol, value, tsd): + + addr = CellFrame.Chain.ChainAddr.fromStr(wallet) + ems = DatumEmission(str(value), token_symbol, addr) + + for key,value in tsd.items(): + if isinstance(value, dict): + ems.addTSD(key, json_dump(value).encode("utf-8")) + elif isinstance(value, list): + ems.addTSD(key, json_dump(value).encode("utf-8")) + else: + ems.addTSD(key, str(value).encode("utf-8")) + + return CellframeEmission(ems) + + def place_emission(self, ems, chain): + return Mempool.emissionPlace(chain, ems.datum) + + def place_datum(self, datum, chain): + return Mempool.addDatum(chain, datum) + + def remove_key_from_mempool(self, key, chain): + Mempool.remove(chain, key) + + def mempool_list(self, chain): + return Mempool.list(self.net, chain) + + def mempool_get_emission(self, key): + return Mempool.emissionGet(self.zerochain, key) + + def mempool_proc(self, hash, chain): + Mempool.proc(hash, chain) + + def all_tx_from_ledger(self): + res = [] + legder = self.net.getLedger() + count = legder.count() + + txs = legder.getTransactions(count,1,False) + + if not txs: + return [], legder + + return txs, legder + + def all_tx_from_blocks(self): + + iterator = self.main.createAtomItem(False) + + ptr = self.main.atomIterGetFirst(iterator) + + if not ptr: + logIt.error("Can't iterate over blocks in {}!".format(self.name)) + return [] + + aptr, size = ptr + #iterate over blocks: atom-pointer should not be none, and size shoud be >0 + while aptr: + + if size <= 0: #skip such blocks + aptr, size = self.main.atomIterGetNext(iter) + continue + + block = Block.fromAtom(aptr, size) + + if not block.datums: + aptr, size = self.main.atomIterGetNext(iterator) + continue + + for datum in block.datums: + if datum.isDatumTX(): + yield datum.getDatumTX() + else: + continue + + aptr, size = self.main.atomIterGetNext(iterator) diff --git a/contract.py b/contract.py new file mode 100644 index 0000000..62ea591 --- /dev/null +++ b/contract.py @@ -0,0 +1,154 @@ +from web3 import Web3, HTTPProvider +from web3.middleware import geth_poa_middleware +from web3.auto import w3 +from eth_account.messages import encode_defunct + +from web3.exceptions import TransactionNotFound +from requests.exceptions import ConnectionError +from web3.datastructures import AttributeDict + + +class ContractProvider: + def __init__(self, name, + provider_urls, + contract_address, + abi, + commision_wallet=None, + cfnet=None, + chain_name=None, + event_keys_map = None, + native_token_index = None, + pair_token_index = None, + nft_contract = False, + nft_token = None, network_id = None): + self.name = name + + self.net = cfnet + self.commision_wallet = commision_wallet + self.contract_address = contract_address + self.__last_attempt_block = "latest" + self.abi = abi + self.urls = provider_urls + + self.chain_name = chain_name + self.event_keys_map = event_keys_map + + self.native_token_index = native_token_index + self.pair_token_index = pair_token_index + + self.__reserves = None + self.__supply = None + self.__tokens = None + self.__token_symbols = None + + self.is_nft = nft_contract + self.nft_token = nft_token + self.network_id = network_id + + @property + def w3(self): + for url in self.urls: + web3 = Web3(HTTPProvider(url)) + if web3.isConnected(): + web3.middleware_onion.inject(geth_poa_middleware, layer=0) + return web3 + raise ConnectionError("No valid links provided - failed to connect") + + @property + def contract(self): + contract = self.w3.eth.contract(address=self.contract_address, abi=self.abi) + return contract + + @property + def functions(self): + return self.contract.functions + + + @property + def reserves(self): + if self.is_nft: + return 0 + return self.functions.getReserves().call() + + @property + def supply(self): + if self.is_nft: + return 0 + return self.functions.totalSupply().call() + + @property + def native_token_ticker(self): + + #just nft - native ticker form ERC20 abi + if self.is_nft: + self.__token_symbols = [self.contract.functions.symbol().call(), "Unknown"] + return self.__token_symbols[self.native_token_index] + + if not self.__tokens: + self.__tokens = [self.functions.token0().call(), self.functions.token1().call()] + + if not self.__token_symbols: + t0provider = ContractProvider(name = "Token0-lp", provider_urls=self.urls, contract_address=self.__tokens[0], abi = self.abi) + t1provider = ContractProvider(name = "Token1-lp", provider_urls=self.urls, contract_address=self.__tokens[1], abi = self.abi) + + t1 = t0provider.functions.symbol().call() + t2 = t1provider.functions.symbol().call() + + self.__token_symbols = [t1, t2] + + return self.__token_symbols[self.native_token_index] + + @property + def pair_token_ticker(self): + if self.is_nft: + return "NFT" + + if not self.__tokens: + self.__tokens = [self.functions.token0().call(), self.functions.token1().call()] + + if not self.__token_symbols: + t0provider = ContractProvider(name = "Token0-lp", provider_urls=self.urls, contract_address=self.__tokens[0], abi = self.abi) + t1provider = ContractProvider(name = "Token1-lp", provider_urls=self.urls, contract_address=self.__tokens[1], abi = self.abi) + + t1 = t0provider.functions.symbol().call() + t2 = t1provider.functions.symbol().call() + + self.__token_symbols = [t1, t2] + + return self.__token_symbols[self.pair_token_index] + + + def all_events(self, event_name, **kwargs): + return self.contract.events[event_name].createFilter(**kwargs).get_all_entries() + + def all_events_from_last_try(self, event_name): + block = self.w3.eth.blockNumber + events = self.contract.events[event_name].createFilter(fromBlock=self.__last_attempt_block).get_all_entries() + #logIt.notice("{} events from {} to {} block: {}".format(self.name, self.__last_attempt_block, block, len(events))) + self.__last_attempt_block = block + return events + + def event_from_tx_logs(self, tx_hash, event_name): + receipt = self.w3.eth.get_transaction_receipt(tx_hash) + return self.contract.events[event_name]().processReceipt(receipt) + + def get_transaction(self, txhash): + tx = self.w3.eth.get_transaction(txhash) + contract_data = self.contract.decode_function_input(tx.input)[1] + return tx, contract_data + + def normalized_event_data(self, ev): + + if not self.event_keys_map: + raise RuntimeError("Can't normilize event data, no mapping") + + result = {} + for key in self.event_keys_map.keys(): + result[key] = ev["args"][self.event_keys_map[key]] + + if not "_block" in ev["args"]: + result["_block"] = self.w3.eth.blockNumber + + else: + result["_block"] = ev["args"]["_block"] + return result diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..e405482 --- /dev/null +++ b/helpers.py @@ -0,0 +1,54 @@ +import json + +def json_dump(data): + return json.dumps(data) + +def json_load(data): + try: + return json.loads(data) + except: + return None + + +def find_tx_out(tx, out_type): + try: + return next(filter(lambda x: isinstance(x, out_type), tx.getItems())) + except: + return None + +def get_tx_items(tx, out_type): + try: + return list(filter(lambda x: isinstance(x, out_type), tx.getItems())) + except: + return None + +def get_tsd_data(tsd_list, type_tsd): + try: + return next(filter(lambda x: x.type == type_tsd, tsd_list)).data + except: + return None + +def get_tx_outs(tx, out_type): + try: + return list(filter(lambda x: isinstance(x, out_type), tx.getItems())) + except: + return None + +class ListCache: + def __init__(self, capacity): + self.capacity = capacity + self.data = [] + + + def add(self, data): + self.data.append(data) + if len(self.data) > self.capacity: + self.data.pop(0) + + def data(self): + return self.data + +def net_by_name(NETS, netname): + for net in NETS: + if net.name == netname: + return net -- GitLab