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