Skip to content
Snippets Groups Projects
cellframenet.py 15.9 KiB
Newer Older
boo's avatar
boo committed
from typing import Iterator

dpuzyrkov's avatar
dpuzyrkov committed
import CellFrame
boo's avatar
boo committed
from CellFrame.Chain import ChainAtomPtr
dpuzyrkov's avatar
dpuzyrkov committed
from CellFrame.Network import Net
boo's avatar
boo committed
from CellFrame.Common import Datum, DatumTx, DatumToken, DatumEmission, DatumAnchor, DatumDecree
dpuzyrkov's avatar
dpuzyrkov committed
from DAP import Crypto
from DAP.Crypto import Cert, Sign
boo's avatar
boo committed
from CellFrame.Chain import Mempool, Wallet, Chain
from CellFrame.Consensus import DAG, Block
from CellFrame.Common import TxOut, TxIn, TxToken, TxSig, TxOutCondSubtypeSrvStakeLock, TxInCond, \
    TxOutExt
dpuzyrkov's avatar
dpuzyrkov committed
from DAP.Crypto import HashFast
from DAP.Core import logIt
from datetime import datetime
Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed
from CellFrame.Chain import ChainAddr
dpuzyrkov's avatar
dpuzyrkov committed
import hashlib
dpuzyrkov's avatar
dpuzyrkov committed
import json
Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed
from pycfhelpers.helpers import json_dump, find_tx_out, get_tx_items
dpuzyrkov's avatar
dpuzyrkov committed

dpuzyrkov's avatar
dpuzyrkov committed
class TSD:
boo's avatar
boo committed
    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
dpuzyrkov's avatar
dpuzyrkov committed
    TYPE_EMISSION_CENTER_UID = 0x0011
    TYPE_EMISSION_CENTER_VER = 0x0012
dpuzyrkov's avatar
dpuzyrkov committed

class CellframeEmission:
boo's avatar
boo committed
    def __init__(self, datum, event=None):
dpuzyrkov's avatar
dpuzyrkov committed
        self.datum = datum
        self.hash = str(self.datum.hash)
Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed
        self.event = event
dpuzyrkov's avatar
dpuzyrkov committed
        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()
dpuzyrkov's avatar
dpuzyrkov committed
    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)
boo's avatar
boo committed


# if datum not base-tx - exception
class CellframeBaseTransactionDatum:
dpuzyrkov's avatar
dpuzyrkov committed
    def __init__(self, datum, net, block=None):
        self.block = block
        self.datum = datum
        self.hash = str(datum.hash)
        self.created = datum.dateCreated
boo's avatar
boo committed
        self.net = net
        # base tx : has txToken item
dpuzyrkov's avatar
dpuzyrkov committed
        if not self.tx_token():
boo's avatar
boo committed
            raise RuntimeError("Datum {} not base tx".format(self.datum))
dpuzyrkov's avatar
dpuzyrkov committed

        self.to_address = str(self.tx_out().addr)
        self.amount = self.tx_out().value
dpuzyrkov's avatar
dpuzyrkov committed
        self.emission_hash = str(self.tx_token().tokenEmissionHash)
dpuzyrkov's avatar
dpuzyrkov committed
        self.emission_ = None

    def tx_out(self):
        try:
boo's avatar
boo committed
            return next(filter(lambda x: isinstance(x, (TxOut,)), self.datum.getItems()))
dpuzyrkov's avatar
dpuzyrkov committed
        except:
            return None

    def tx_in(self):
        try:
boo's avatar
boo committed
            return next(filter(lambda x: isinstance(x, (TxIn,)), self.datum.getItems()))
dpuzyrkov's avatar
dpuzyrkov committed
        except:
            return None
dpuzyrkov's avatar
dpuzyrkov committed
    def tx_token(self):
        try:
boo's avatar
boo committed
            return next(filter(lambda x: isinstance(x, (TxToken,)), self.datum.getItems()))
dpuzyrkov's avatar
dpuzyrkov committed
        except:
            return None

    def tx_sig(self):
        try:
boo's avatar
boo committed
            return next(filter(lambda x: isinstance(x, (TxSig,)), self.datum.getItems()))
dpuzyrkov's avatar
dpuzyrkov committed
        except:
            return None

    def emission(self):
        if not self.emission_:
            #tiker = str(self.tx_token().ticker)
dpuzyrkov's avatar
dpuzyrkov committed
            hf = HashFast.fromString(str(self.tx_token().tokenEmissionHash))
boo's avatar
boo committed
            ledger = self.net.getLedger()
dpuzyrkov's avatar
dpuzyrkov committed
            ems = ledger.tokenEmissionFind(tiker, hf)
            if not ems:
                return None
            self.emission_ = CellframeEmission(ems)
dpuzyrkov's avatar
dpuzyrkov committed
        return self.emission_


class CellframeNetwork:
boo's avatar
boo committed
    main: Chain
    zerochain: Chain

    def __init__(self, name, chains, group_alias=None, commision_wallet=None):
dpuzyrkov's avatar
dpuzyrkov committed

        self.name = name
        self.net = Net.byName(name)
        self.group_alias = group_alias or name
Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed
        self.commision_wallet = commision_wallet
dpuzyrkov's avatar
dpuzyrkov committed

        if not self.net:
            raise RuntimeError("No such net: {}".format(name))
dpuzyrkov's avatar
dpuzyrkov committed
        for chain in chains:
            setattr(self, chain, self.net.getChainByName(chain))
Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed
    @staticmethod
    def wallet_from_signature(sigbytes):
        sign = Sign.fromBytes(sigbytes)
        return sign.getAddr()
Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed
    @staticmethod
    def netid_from_wallet(wallet):
        return ChainAddr.fromStr(str(wallet)).getNetId().long()
Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed
    def tx_sender_wallet(self, tx):
        sigitem = get_tx_items(tx, TxSig)
        if not sigitem:
            return None

boo's avatar
boo committed
        # first signature is a sender signature
Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed
        return sigitem[0].sign.getAddr(self.net)

    def ledger_tx_by_hash(self, txh):
        hf = HashFast.fromString(txh)

boo's avatar
boo committed
        # only ledger-accepted
Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed
        ledger = self.net.getLedger()
        tx = ledger.txFindByHash(hf)

        return tx

Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed
    def netid(self):
Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed
        return self.net.id.long()
Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed

dpuzyrkov's avatar
dpuzyrkov committed
    def set_mempool_notification_callback(self, chain, callback):
        callback_name = "{}".format(self.name)
        logIt.notice("New mempool notifier for {}".format(callback_name))
dpuzyrkov's avatar
dpuzyrkov committed
        def callback_wraper(op_code, group, key, value, net_name):
boo's avatar
boo committed
            # op_code a | d
            # group - table name
            # key - hash записи
            #
dpuzyrkov's avatar
dpuzyrkov committed
            callback(op_code, group, key, value, net_name, self, chain)

boo's avatar
boo committed
        chain.addMempoolNotify(callback_wraper, callback_name)
dpuzyrkov's avatar
dpuzyrkov committed

boo's avatar
boo committed
    def set_gdbsync_notification_callback(self, callback):
boo's avatar
boo committed
        # Любая таблица в GDB
dpuzyrkov's avatar
dpuzyrkov committed
        callback_name = "{}".format(self.name)

        logIt.notice("New gdb notifier for {}".format(callback_name))
dpuzyrkov's avatar
dpuzyrkov committed
        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):
boo's avatar
boo committed
        # New atom Block | Event
        #
dpuzyrkov's avatar
dpuzyrkov committed
        callback_name = "{}".format(self.name)
        logIt.notice("New atom notifier for {}".format(callback_name))
dpuzyrkov's avatar
dpuzyrkov committed
        def callback_wraper(atom, size, callback_name):
            callback(atom, size, callback_name, self, chain)

boo's avatar
boo committed
        chain.addAtomNotify(callback_wraper, callback_name)
dpuzyrkov's avatar
dpuzyrkov committed

    def set_ledger_tx_notification_callback(self, callback):
        ledger = self.net.getLedger()

        def callback_wrapper(ledger, tx, argv):
            callback(ledger, tx, argv, self)
dpuzyrkov's avatar
dpuzyrkov committed
        ledger.txAddNotify(callback_wrapper, self.net)
pavel.sidorenko's avatar
pavel.sidorenko committed
    def set_ledger_bridge_tx_notification_callback(self, callback):
        ledger = self.net.getLedger()

        def callback_wrapper(ledger, tx, argv):
            callback(ledger, tx, argv, self)
pavel.sidorenko's avatar
pavel.sidorenko committed
        logIt.notice("New bridgedTxNotify for {}".format(self.net))
        ledger.bridgedTxNotifyAdd(callback_wrapper, self.net)
dpuzyrkov's avatar
dpuzyrkov committed

    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

dpuzyrkov's avatar
dpuzyrkov committed
    def create_base_transaction(self, emission, certs, fee, native_tw=None):
dpuzyrkov's avatar
dpuzyrkov committed

        if native_tw:
boo's avatar
boo committed
            w = Wallet.openFile(native_tw)
            return Mempool.baseTxCreate(self.main, emission.datum.hash, self.zerochain, emission.datum.value,
                                        emission.datum.ticker,
                                        emission.datum.addr, fee, w)
dpuzyrkov's avatar
dpuzyrkov committed
        else:
boo's avatar
boo committed
            return Mempool.baseTxCreate(self.main, emission.datum.hash, self.zerochain, emission.datum.value,
                                        emission.datum.ticker,
                                        emission.datum.addr, fee, certs)
dpuzyrkov's avatar
dpuzyrkov committed

    def get_emission_by_tsd(self, tsd_dict):
dpuzyrkov's avatar
dpuzyrkov committed
        atom_count = self.zerochain.countAtom()
boo's avatar
boo committed
        atoms = self.zerochain.getAtoms(atom_count, 1, True)

dpuzyrkov's avatar
dpuzyrkov committed
        emissions = {}

        for atom in atoms:

boo's avatar
boo committed
            # event = DAG.fromAtom(atom[0], atom[1])
            event = DAG.fromAtom(atom)
dpuzyrkov's avatar
dpuzyrkov committed
            if not event.datum.isDatumTokenEmission():
                continue
dpuzyrkov's avatar
dpuzyrkov committed
            token_emission = event.datum.getDatumTokenEmission()
            if not token_emission:
                continue

            results = []
boo's avatar
boo committed
            for key, value in tsd_dict.items():

                tsd = token_emission.getTSD(key)

dpuzyrkov's avatar
dpuzyrkov committed
                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)
dpuzyrkov's avatar
dpuzyrkov committed
            if all(results):
Dmitry Puzyrkov's avatar
Dmitry Puzyrkov committed
                emissions[str(event.datum.hash)] = CellframeEmission(token_emission, event)
dpuzyrkov's avatar
dpuzyrkov committed
        return emissions
dpuzyrkov's avatar
dpuzyrkov committed
    def get_emission_from_mempool_by_tsd(self, tsd_dict):
dpuzyrkov's avatar
dpuzyrkov committed
        datums = Mempool.list(self.net, self.zerochain).values()

        emissions = {}

        for datum in datums:

            if not datum.isDatumTokenEmission():
                continue
dpuzyrkov's avatar
dpuzyrkov committed
            token_emission = datum.getDatumTokenEmission()
            if not token_emission:
                continue
dpuzyrkov's avatar
dpuzyrkov committed
            results = []
boo's avatar
boo committed
            for key, value in tsd_dict.items():

                tsd = token_emission.getTSD(key)

dpuzyrkov's avatar
dpuzyrkov committed
                if not tsd and value == None:
                    results.append(True)
                    continue
boo's avatar
boo committed
                try:
dpuzyrkov's avatar
dpuzyrkov committed
                    if tsd and tsd.decode("utf-8") == value:
                        results.append(True)
                        continue
                except:
                    pass

                results.append(False)
dpuzyrkov's avatar
dpuzyrkov committed
            if all(results):
                emissions[str(datum.hash)] = CellframeEmission(token_emission)
dpuzyrkov's avatar
dpuzyrkov committed
        return emissions

    def base_transactions_from_blocks(self, emission_hash=None):

        iterator = self.main.createAtomItem(False)
dpuzyrkov's avatar
dpuzyrkov committed
        ptr = self.main.atomIterGetFirst(iterator)
dpuzyrkov's avatar
dpuzyrkov committed
        if not ptr:
            logIt.error("Can't iterate over blocks in {}!".format(self.name))
            return []

        aptr, size = ptr
boo's avatar
boo committed
        # iterate over blocks: atom-pointer should not be none, and size shoud be >0
        while aptr:

            if size <= 0:  # skip such blocks
dpuzyrkov's avatar
dpuzyrkov committed
                aptr, size = self.main.atomIterGetNext(iter)
                continue
dpuzyrkov's avatar
dpuzyrkov committed
            block = Block.fromAtom(aptr, size)
dpuzyrkov's avatar
dpuzyrkov committed
            if not block.datums:
                aptr, size = self.main.atomIterGetNext(iterator)
                continue

            for datum in block.datums:
                if datum.isDatumTX():
                    try:
boo's avatar
boo committed
                        # if emshash provided - filter items
                        basedatum = CellframeBaseTransactionDatum(datum.getDatumTX(), block)
                        if emission_hash:
                            if basedatum.emission_hash == emission_hash:
dpuzyrkov's avatar
dpuzyrkov committed
                                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)
boo's avatar
boo committed
            # iterate over blocks: atom-pointer should not be none, and size shoud be >0
            while aptr:

                if size <= 0:  # skip such blocks
dpuzyrkov's avatar
dpuzyrkov committed
                    aptr, size = self.main.atomIterGetNext(iter)
                    continue
dpuzyrkov's avatar
dpuzyrkov committed
                block = Block.fromAtom(aptr, size)
dpuzyrkov's avatar
dpuzyrkov committed
                if block.datums:
                    yield block, block.datums

                aptr, size = self.main.atomIterGetNext(iterator)
dpuzyrkov's avatar
dpuzyrkov committed
        def isDatumToAddress(datum_with_block):
            try:
boo's avatar
boo committed
                txn_out = next(filter(lambda x: isinstance(x, (TxOut,)), datum_with_block.datum.getItems()))
dpuzyrkov's avatar
dpuzyrkov committed
                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():
boo's avatar
boo committed
            tx_datums = [DatumWithBlock(datum.getDatumTX(), block) for datum in
                         filter(lambda datum: datum.isDatumTX(), datums)]
dpuzyrkov's avatar
dpuzyrkov committed
            transactions_to_wallet.extend(list(filter(isDatumToAddress, tx_datums)))

boo's avatar
boo committed
        return transactions_to_wallet
dpuzyrkov's avatar
dpuzyrkov committed

    def create_emission(self, wallet, token_symbol, value, tsd):

        addr = CellFrame.Chain.ChainAddr.fromStr(wallet)
        ems = DatumEmission(str(value), token_symbol, addr)

boo's avatar
boo committed
        for key, value in tsd.items():
dpuzyrkov's avatar
dpuzyrkov committed
            if isinstance(value, dict):
boo's avatar
boo committed
                ems.addTSD(key, json_dump(value).encode("utf-8"))
            elif isinstance(value, list):
                ems.addTSD(key, json_dump(value).encode("utf-8"))
dpuzyrkov's avatar
dpuzyrkov committed
            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)
dpuzyrkov's avatar
dpuzyrkov committed
    def mempool_list(self, chain):
        return Mempool.list(self.net, chain)
dpuzyrkov's avatar
dpuzyrkov committed
    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()
boo's avatar
boo committed

        txs = legder.getTransactions(count, 1, False)

        if not txs:
dpuzyrkov's avatar
dpuzyrkov committed
            return [], legder

        return txs, legder

boo's avatar
boo committed
    @staticmethod
    def get_datums_from_atom(chain: Chain, atom: ChainAtomPtr) -> list[Datum]:
        # logIt.message(f"{chain.getCSName()=}")
        if chain.getCSName() == "esbocs":
            block = Block.fromAtom(atom)
            return block.datums
dpuzyrkov's avatar
dpuzyrkov committed

boo's avatar
boo committed
        if chain.getCSName() == "dag_poa":
            event = DAG.fromAtom(atom)
            return [event.datum]

    def get_datums_from_chains(self, chains: tuple[str] = ("main", "zerochain")) -> Iterator[Datum]:
        logIt.warning("get_datums_from_chains()")
        for chain_name in chains:
            chain: Chain = getattr(self, chain_name)

            iterator = chain.createAtomIter(False)
            ptr = chain.atomIterGetFirst(iterator)

            if not ptr:
                logIt.message("not ptr")
                return []

            atom, size = ptr
            logIt.message("...")
            while atom:
                if size <= 0:
                    atom, size = chain.atomIterGetNext(iterator)
dpuzyrkov's avatar
dpuzyrkov committed
                    continue
boo's avatar
boo committed
                datums = self.get_datums_from_atom(chain, atom)

                if not datums:
                    atom, size = chain.atomIterGetNext(iterator)
                    continue

                for datum in datums:
                    yield datum

                atom, size = chain.atomIterGetNext(iterator)

    def all_tx_from_blocks(self) -> Iterator[DatumTx]:
        logIt.warning("all_tx_from_blocks()")
        for datum in self.get_datums_from_chains(chains=("main",)):
            if datum.isDatumTX():
                yield datum.getDatumTX()
            else:
                continue