Exemplo n.º 1
0
    def got_network_response_chunk_slot(self, response, chunk_index):
        if response.get('error'):
            return self.fail_metadata_info("Download chunk data error!\n%r" %
                                           (response['error'].get('message')))
        raw = response.get('result')

        tx = Transaction(raw)
        self.handle_chunk_tx(tx, chunk_index)
def extract_contract_data(tx):
    transaction = Transaction(tx)
    address = get_contract_address(transaction.outputs())
    candidates = get_candidates(transaction.outputs())
    for c in candidates:
        will = LastWillContract(c)
        if will.address.to_ui_string() == address:
            return will
    def got_network_response_slot(self):
        self.download_finished = True

        resp = self.json_response
        if resp.get('error'):
            return self.fail_genesis_info("Download error!\n%r"%(resp['error'].get('message')))
        raw = resp.get('result')

        tx = Transaction(raw)
        self.handle_genesis_tx(tx)
Exemplo n.º 4
0
 def tx_from_text(self, txt):
     from electroncash.transaction import tx_from_str
     try:
         txt_tx = tx_from_str(txt)
         tx = Transaction(txt_tx, sign_schnorr=self.wallet.is_schnorr_enabled())
         tx.deserialize()
         return tx
     except:
         traceback.print_exc(file=sys.stdout)
         self.show_critical(_("Electron Cash was unable to parse your transaction"))
         return
def extract_contract_data(wallet, tx, utxo):
    contracts=[]
    print(tx)
    for i in range(len(tx)):
        transaction=Transaction(tx[i])
        address, v = get_contract_info(transaction.outputs())
        candidates = get_candidates(transaction.outputs())
        for c in candidates:
            will = LastWillContract(c, tx[i],v)
            if will.address.to_ui_string()==address:
                contracts.append((utxo[i], will, find_my_role(c, wallet)))
    remove_duplicates(contracts)
    return contracts
Exemplo n.º 6
0
 def _synch_full(self, wallet):
     c = self._get_contacts(wallet)
     if not c:
         # short-circuit abort function early if no contacts exist.
         return dict()
     h = self._get_history(wallet)
     seen = dict()  # Address -> dict of tx_hash_str -> hitem tuple
     for hitem in h:
         if self.stopFlag.is_set():
             # early return, another thread requested a stop
             return None
         # loop through ALL the history and see if relevant tx's exist for contacts we care about
         tx_hash = hitem[0]
         tx = wallet.transactions.get(tx_hash)
         if tx and tx.raw:
             tx = Transaction(tx.raw)  # take a copy
             ins = tx.inputs()  # implicit deserialize
             for x in ins:
                 xa = x['address']
                 if isinstance(xa, PublicKey):
                     xa = xa.toAddress()
                 if isinstance(xa, Address) and xa in c:
                     dct = seen.get(xa, dict())
                     wasEmpty = not dct
                     if tx_hash not in dct:
                         dct[tx_hash] = hitem
                         if wasEmpty: seen[xa] = dct
             outs = tx.outputs()
             for x in outs:
                 typ, xa, dummy = x
                 if isinstance(xa, Address) and xa in c:
                     dct = seen.get(xa, dict())
                     wasEmpty = not dct
                     if tx_hash not in dct:
                         dct[tx_hash] = hitem
                         if wasEmpty: seen[xa] = dct
     storable = dict()
     for addr, d in seen.items():
         addrstr = addr.to_storage_string()
         storable[addrstr] = d
     return storable
Exemplo n.º 7
0
 def showTransaction_desc_(self, txraw, desc) -> None:
     tx = Transaction(txraw, sign_schnorr=parent().prefs_use_schnorr)
     tx.deserialize()
     tx_hash, status_, label_, can_broadcast, amount, fee, height, conf, timestamp, exp_n = wallet().get_tx_info(tx)
     #print("send: status_",status_,"label_",label_,"amount",amount,"conf",conf)
     size = tx.estimated_size()
     conf = 0 if conf is None else conf
     timestamp = time.time() if timestamp is None else timestamp
     status, status_str = (status_, _("Unsigned")) #wallet().get_tx_status(tx_hash, height, conf, timestamp)
     doFX = fx() and fx().is_enabled()
     ccy = fx().get_currency() if doFX else None
     fiat_amount_str = str(self.fiat.text) if doFX else None 
     #HistoryEntry = namedtuple("HistoryEntry", "tx tx_hash status_str label v_str balance_str date ts conf status value fiat_amount fiat_balance fiat_amount_str fiat_balance_str ccy status_image")
     entry = HistoryEntry(tx,tx_hash,status_str,str(desc),self.amt.text,"",timestamp_to_datetime(time.time() if conf <= 0 else timestamp),timestamp,conf,status,amount,None,None,fiat_amount_str,None,ccy,None)
     def newLabel(l):
         self.descDel.text = l
         
     self.navigationController.pushViewController_animated_(txdetail.CreateTxDetailWithEntry(entry, on_label = newLabel), True)
     
     # For modal do the following:
     #self.presentViewController_animated_completion_(txdetail.CreateTxDetailWithEntry(entry, on_label = newLabel, asModalNav = True), True, None)
     '''
Exemplo n.º 8
0
 def on_qr(self, data):
     from electroncash.bitcoin import base_decode
     data = data.strip()
     if Address.is_valid(data):
         self.set_URI(data)
         return
     if data.startswith('bitcoincash:'):
         self.set_URI(data)
         return
     # try to decode transaction
     from electroncash.transaction import Transaction
     from electroncash.util import bh2u
     try:
         text = bh2u(base_decode(data, None, base=43))
         tx = Transaction(text)
         tx.deserialize()
         if self.wallet:
             my_coins = self.wallet.get_spendable_coins(
                 None, self.electrum_config)
             my_outpoints = [
                 vin['prevout_hash'] + ':' + str(vin['prevout_n'])
                 for vin in my_coins
             ]
             for i, txin in enumerate(tx.inputs()):
                 outpoint = txin['prevout_hash'] + ':' + str(
                     txin['prevout_n'])
                 if outpoint in my_outpoints:
                     my_index = my_outpoints.index(outpoint)
                     tx._inputs[i]['value'] = my_coins[my_index]['value']
     except:
         tx = None
     if tx:
         self.tx_dialog(tx)
         return
     # show error
     self.show_error("Unable to decode QR data")
Exemplo n.º 9
0
def _setup_transaction_detail_view(vc : ObjCInstance) -> None:
    entry = utils.nspy_get_byname(vc, 'tx_entry')
    tx, tx_hash, status_str, label, v_str, balance_str, date, ts, conf, status, value, fiat_amount, fiat_balance, fiat_amount_str, fiat_balance_str, ccy, img, *dummy2 = entry
    parent = gui.ElectrumGui.gui
    wallet = parent.wallet
    base_unit = parent.base_unit()
    format_amount = parent.format_amount
    if not wallet:
        utils.NSLog("TxDetail: Wallet not open.. aborting early (tx_hash=%s)",tx_hash)
        return
    if tx is None:
        tx = wallet.transactions.get(tx_hash, None)
        if tx is not None and tx.raw:
            tx = Transaction(tx.raw, sign_schnorr=parent.prefs_use_schnorr)
            tx.deserialize()
    if tx is None:
        utils.NSLog("*** ERROR: Cannot find tx for hash: %s",tx_hash)
        return
    tx_hash, status_, label_, can_broadcast, amount, fee, height, conf, timestamp, exp_n = wallet.get_tx_info(tx)
    size = tx.estimated_size()
    can_sign = not tx.is_complete() and wallet and wallet.can_sign(tx) #and (wallet.can_sign(tx) # or bool(self.main_window.tx_external_keypairs))

    wasNew = False
    if not vc.viewIfLoaded:
        NSBundle.mainBundle.loadNibNamed_owner_options_("TxDetail",vc,None)
        wasNew = True
        if vc.maxTVHeight < 1.0:
            vc.maxTVHeight = vc.inputsTVHeightCS.constant

    # grab all the views
    # Transaction ID:
    txTit = vc.txTit
    txHash =  vc.txHash
    copyBut = vc.cpyBut
    qrBut =  vc.qrBut
    # Description:
    descTit = vc.descTit
    descTf = vc.descTf
    # Status:
    statusTit = vc.statusTit
    statusIV = vc.statusIV
    statusLbl = vc.statusLbl
    # Date:
    dateTit = vc.dateTit
    dateLbl = vc.dateLbl
    # Amount received/sent:
    amtTit = vc.amtTit
    amtLbl = vc.amtLbl
    # Size:
    sizeTit = vc.sizeTit
    sizeLbl = vc.sizeLbl
    # Fee:
    feeTit = vc.feeTit
    feeLbl = vc.feeLbl
    # Locktime:
    lockTit = vc.lockTit
    lockLbl = vc.lockLbl
    # ⓢ Schnorr Signed label
    schnorrLbl = vc.schnorrLbl
    # Inputs
    inputsTV = vc.inputsTV
    # Outputs
    outputsTV = vc.outputsTV

    # Setup data for all the stuff
    txTit.text = _("Transaction ID:").translate({ord(':') : None})
    tx_hash_str = tx_hash if tx_hash is not None and tx_hash != "None" and tx_hash != "Unknown" and tx_hash != _("Unknown") else _('Unknown')
    rbbs = []
    vc.bottomView.setHidden_(True)
    vc.bottomBut.handleControlEvent_withBlock_(UIControlEventPrimaryActionTriggered, None) # clear previous events
    if can_sign:
        vc.noBlkXplo = True
        vc.bottomView.setHidden_(False)
        def fun() -> None: vc.onSign()
        vc.bottomBut.handleControlEvent_withBlock_(UIControlEventPrimaryActionTriggered, fun)
        vc.bottomBut.setTitle_forState_(_('Sign'), UIControlStateNormal)
        if not img:
            img = StatusImages[-1]
    if can_broadcast:
        vc.noBlkXplo = True
        vc.bottomView.setHidden_(False)
        def fun() -> None: vc.onBroadcast()
        vc.bottomBut.handleControlEvent_withBlock_(UIControlEventPrimaryActionTriggered, None) # clear previous events
        vc.bottomBut.handleControlEvent_withBlock_(UIControlEventPrimaryActionTriggered, fun)
        vc.bottomBut.setTitle_forState_(_('Broadcast'), UIControlStateNormal)
        if not img:
            img = StatusImages[-2]

    if tx_hash_str == _("Unknown") or tx_hash is None: #unsigned tx
        copyBut.setHidden_(True)
        qrBut.setHidden_(True)
        txHash.setHidden_(True)
        txHash.userInteractionEnabled = False
        vc.noTxHashView.setHidden_(False)
        vc.noTxHashLbl.text = _("You need to sign this transaction in order for it to get a transaction ID.") if can_sign else _("This transaction is not signed and thus lacks a transaction ID.")
        vc.notsigned = True
        rbbs.append(UIBarButtonItem.alloc().initWithImage_style_target_action_(UIImage.imageNamed_("barbut_actions"), UIBarButtonItemStyleBordered, vc, SEL(b'onShareSave:')).autorelease())
    else:
        copyBut.setHidden_(False)
        qrBut.setHidden_(False)
        txHash.setHidden_(False)
        vc.noTxHashView.setHidden_(True)
        vc.notsigned = False
        txHash.linkText = tx_hash_str
        txHash.userInteractionEnabled = True

        def onTxLinkTap(ll : objc_id) -> None:
            vc.onTxLink_(ObjCInstance(ll).gr)
        txHash.linkTarget = Block(onTxLinkTap)
        rbbs.append(UIBarButtonItem.alloc().initWithImage_style_target_action_(UIImage.imageNamed_("barbut_actions"), UIBarButtonItemStyleBordered, vc, SEL(b'onTxLink:')).autorelease())

    if amount is None: # unrelated to this wallet.. hide the description textfield.. also affects messaging below.. see viewDidLayoutSubviews
        vc.unrelated = True
    else:
        vc.unrelated = False

    vc.navigationItem.rightBarButtonItems = rbbs

    descTit.text = _("Description")
    descTf.text = label
    descTf.placeholder = _("Tap to add a description")
    descTf.clearButtonMode = UITextFieldViewModeWhileEditing
    utils.uitf_redo_attrs(descTf)

    statusTit.setText_withKerning_(_("Status:").translate({ord(':') : None}), utils._kern)
    if not img:
        #try and auto-determine the appropriate image if it has some confirmations and img is still null
        try:
            c = min(int(conf), 6)
            if c >= 0: img = StatusImages[c+3]
        except:
            pass
        if not img: img = UIImage.imageNamed_("empty.png")
    ff = str(status_) #status_str
    vc.canRefresh = False
    try:
        if int(conf) > 0:
           ff = "%s %s"%(str(conf), _('confirmations'))
        vc.canRefresh = conf >= 0 # if we got here means refresh has meaning.. it's not an external tx or if it is, it now is on the network, so enable refreshing
    except:
        pass
    statusLbl.text = _(ff)
    if vc.canRefresh and conf >= 1: img = StatusImages[min(len(StatusImages)-1,3+min(6,conf))]
    statusIV.image = img


    if timestamp or exp_n:
        if timestamp:
            dateTit.setText_withKerning_(_("Date"), utils._kern)
            #dateLbl.text = str(date)
            dateLbl.attributedText = utils.makeFancyDateAttrString(str(date))
        elif exp_n:
            dateTit.setText_withKerning_(_("Expected conf."), utils._kern)
            dateLbl.text = '%d blocks'%(exp_n) if exp_n > 0 else _('unknown (low fee)')
        vc.noBlkXplo = False
        dateTit.alpha = 1.0
        dateLbl.alpha = 1.0
    else:
        # wtf? what to do here?
        dateTit.setText_withKerning_(_("Date"), utils._kern)
        dateLbl.text = ""
        dateTit.alpha = 0.5
        dateLbl.alpha = 0.5

    myAmtStr = ''
    if vc.unrelated:
        amtTit.setText_withKerning_(_("Amount"), utils._kern)
        amtLbl.text = _("Transaction unrelated to your wallet")
    elif amount > 0:
        amtTit.setText_withKerning_(_("Amount received:").translate({ord(':') : None}), utils._kern)
        myAmtStr = ('%s %s%s'%(format_amount(amount),base_unit,
                               (" " + fiat_amount_str + " " + ccy + "") if fiat_amount_str else '',
                               ))
    else:
        amtTit.setText_withKerning_( _("Amount sent:").translate({ord(':') : None}), utils._kern )
        myAmtStr = ('%s %s%s'%(format_amount(-amount),base_unit,
                               (" " + fiat_amount_str.replace('-','') + " " + ccy + "") if fiat_amount_str else '',
                               ))
    if myAmtStr:
        l = myAmtStr.split()
        am = l[0]
        unt = ' ' + l[1] if len(l) else ''
        rest = ' ' + ' '.join(l[2:]) if len(l) > 2 else ''
        ats = NSMutableAttributedString.alloc().initWithString_attributes_(am, {NSFontAttributeName : UIFont.systemFontOfSize_weight_(16.0, UIFontWeightBold)}).autorelease()
        if unt:
            ats.appendAttributedString_(NSAttributedString.alloc().initWithString_attributes_(unt, {NSFontAttributeName : UIFont.systemFontOfSize_weight_(16.0, UIFontWeightBold)}).autorelease())
        if rest:
            ats.appendAttributedString_(NSAttributedString.alloc().initWithString_attributes_(rest, {NSFontAttributeName : UIFont.systemFontOfSize_weight_(14.0, UIFontWeightRegular)}).autorelease())
        amtLbl.attributedText = ats

    sizeTit.setText_withKerning_( _("Size:").translate({ord(':') : None}), utils._kern )
    if size:
        sizeLbl.text = ('%d bytes' % (size))
    else:
        sizeLbl.text = _("Unknown")

    feeTit.setText_withKerning_( _("Fee"), utils._kern )
    fee_str = '%s' % (format_amount(fee) + ' ' + base_unit if fee is not None else _('unknown'))
    if fee is not None:
        fee_str += '  ( %s ) '%  parent.format_fee_rate(fee/size*1000)
    feeLbl.text = fee_str

    lockTit.setText_withKerning_(_("Locktime"), utils._kern)
    if tx.locktime > 0:
        lockLbl.text = str(tx.locktime)
        lockTit.setHidden_(False)
        lockLbl.setHidden_(False)
    else:
        lockTit.setHidden_(True)
        lockLbl.setHidden_(True)

    n_inp, n_outp = len(tx.inputs()), len(tx.outputs())
    # auto-adjust height of table views
    vc.inputsTVHeightCS.constant = min(_TxInputsOutputsHeaderHeight + _TxInputsOutputsCellHeight*n_inp, vc.maxTVHeight)
    vc.outputsTVHeightCS.constant = min(_TxInputsOutputsHeaderHeight + _TxInputsOutputsCellHeight*n_outp, vc.maxTVHeight)

    # refreshes the tableview with data
    if wasNew:
        if ts is None: ts = time.time()
        tvc = CreateTxInputsOutputsTVC(vc, tx, inputsTV, outputsTV, float(ts))
    else:
        inputsTV.reloadData()
        outputsTV.reloadData()

    if any(tx.is_schnorr_signed(i) for i in range(n_inp)):
        schnorrLbl.text = SCHNORR_SIGIL + " " + _('Schnorr Signed')
        schnorrLbl.setHidden_(False)
    else:
        schnorrLbl.setHidden_(True)
    def test_inputs(self):

        # used as a final assertion on the number of results and errors
        results_list = []
        error_list = []

        try:
            with open(txintests_local) as f:
                testlist = json.load(f)
            print("Got script tests from %s; will not download." %
                  (txintests_local, ))
        except IOError:
            print("Couldn't get script tests from %s; downloading from %s..." %
                  (txintests_local, txintests_url))
            testlist = requests.get(txintests_url).json()

        print("Starting %d tests on SLP's input validation" % len(testlist))
        for test in testlist:
            description = test['description']

            given_validity = {}
            txes = {}
            for d in test['when']:
                tx = Transaction(d['tx'])
                txid = tx.txid()
                txes[txid] = tx
                if d['valid'] is True:
                    given_validity[txid] = 1
                elif d['valid'] is False:
                    given_validity[txid] = 2
                else:
                    raise ValueError(d['valid'])

            for d in test['should']:
                tx = Transaction(d['tx'])
                txid = tx.txid()
                txes[txid] = tx
                d['txid'] = txid

            graph_context, graph_context_nft1 = slp_validator_0x01.GraphContext(
            ), slp_validator_0x01_nft1.GraphContext_NFT1()

            for i, d in enumerate(test['should']):
                txid = d['txid']
                with self.subTest(description=description, i=i):
                    try:
                        slp_msg = slp.SlpMessage.parseSlpOutputScript(
                            txes[txid].outputs()[0][1])
                        if slp_msg.token_type == 1:
                            graph, job_mgr = graph_context.setup_job(
                                txes[txid], reset=True)
                        elif slp_msg.token_type == 65 or slp_msg.token_type == 129:
                            graph, job_mgr = graph_context_nft1.setup_job(
                                txes[txid], reset=True)
                        else:
                            raise slp.SlpUnsupportedSlpTokenType(
                                slp_msg.token_type)
                    except slp.SlpInvalidOutputMessage:  # If output 0 is not OP_RETURN
                        self.assertEqual(d['valid'], False)
                        continue
                    except slp.SlpUnsupportedSlpTokenType:
                        self.assertEqual(d['valid'], False)
                        continue

                    def fetch_hook(txids, job):
                        l = []
                        for txid in txids:
                            try:
                                l.append(txes[txid])
                            except KeyError:
                                pass
                        ### Would call proxy here
                        return l

                    if slp_msg.token_type == 1:
                        job = slp_validator_0x01.ValidationJob(
                            graph,
                            txid,
                            None,
                            fetch_hook=fetch_hook,
                            validitycache=given_validity)
                    elif slp_msg.token_type == 65:
                        network = MockNetwork(txes)
                        storage = WalletStorage(os.path.curdir,
                                                manual_upgrades=True,
                                                in_memory_only=True)
                        wallet = Slp_ImportedAddressWallet(storage)
                        wallet.slp_graph_0x01_nft = graph_context_nft1
                        job = slp_validator_0x01_nft1.ValidationJobNFT1Child(
                            graph,
                            txid,
                            network,
                            fetch_hook=fetch_hook,
                            validitycache=given_validity,
                            ref=wallet)
                    elif slp_msg.token_type == 129:
                        job = slp_validator_0x01_nft1.ValidationJob(
                            graph,
                            txid,
                            None,
                            fetch_hook=fetch_hook,
                            validitycache=given_validity)

                    q = Queue()
                    job.add_callback(q.put)
                    job_mgr.add_job(job)
                    while True:
                        try:
                            q.get(timeout=1)
                        except Empty:
                            err = RuntimeError(
                                "Timeout during validation unit test")
                            error_list.append(err)
                            raise err
                        if not job.paused and not job.running:  # and job.stop_reason != 'inconclusive':
                            n = next(iter(job.nodes.values()))
                            results_list.append(n.validity)
                            if d['valid'] is True:
                                self.assertEqual(n.validity, 1)
                            elif d['valid'] is False:
                                if test.get(
                                        'allow_inconclusive', False
                                ):  # "allow_inconclusive" allows for ending with an "unvalidated" state for harder corner-cases
                                    self.assertIn(n.validity, (0, 2, 3, 4))
                                else:
                                    self.assertIn(n.validity, (2, 3, 4))
                            else:
                                err = ValueError(d['valid'])
                                error_list.append(err)
                                raise err
                            break
                        else:
                            if len(job.callbacks) > 1:
                                err = Exception(
                                    "shouldn't have more than 1 callback")
                                error_list.append(err)
                                raise err
                            job.callbacks.clear()
                            job.add_callback(q.put, allow_run_cb_now=False)

        self.assertEqual(len(results_list), 59)
        self.assertEqual(len(error_list), 0)
Exemplo n.º 11
0
 def _build_history_entry(h_item, statusImagesOverride, forceNoFX):
     sImages = StatusImages if not statusImagesOverride or len(
         statusImagesOverride) < len(StatusImages) else statusImagesOverride
     parent = gui.ElectrumGui.gui
     wallet = parent.wallet
     daemon = parent.daemon
     if wallet is None or daemon is None:
         utils.NSLog(
             "buid_history_entry: wallet and/or daemon was None, returning early"
         )
         return None
     fx = daemon.fx if daemon.fx and daemon.fx.show_history() else None
     ccy = ''
     tx_hash, height, conf, timestamp, value, balance = h_item
     status, status_str = wallet.get_tx_status(tx_hash, height, conf,
                                               timestamp)
     has_invoice = wallet.invoices.paid.get(tx_hash)
     v_str = parent.format_amount(value, True, whitespaces=True)
     balance_str = parent.format_amount(balance, whitespaces=True)
     label = wallet.get_label(tx_hash)
     date = timestamp_to_datetime(time.time() if conf <= 0 else timestamp)
     ts = timestamp if conf > 0 else time.time()
     fiat_amount = 0
     fiat_balance = 0
     fiat_amount_str = ''
     fiat_balance_str = ''
     if fx: fx.history_used_spot = False
     if not forceNoFX and fx:
         if not ccy:
             ccy = fx.get_currency()
         try:
             hdate = timestamp_to_datetime(
                 time.time() if conf <= 0 else timestamp)
             hamount = fx.historical_value(value, hdate)
             htext = fx.historical_value_str(value,
                                             hdate) if hamount else ''
             fiat_amount = hamount if hamount else fiat_amount
             fiat_amount_str = htext if htext else fiat_amount_str
             hamount = fx.historical_value(balance, hdate) if balance else 0
             htext = fx.historical_value_str(balance,
                                             hdate) if hamount else ''
             fiat_balance = hamount if hamount else fiat_balance
             fiat_balance_str = htext if htext else fiat_balance_str
         except:
             utils.NSLog(
                 "Exception in get_history computing fiat amounts!\n%s",
                 str(sys.exc_info()[1]))
             #import traceback
             #traceback.print_exc(file=sys.stderr)
             fiat_amount = fiat_balance = 0
             fiat_amount_str = fiat_balance_str = ''
     if status >= 0 and status < len(sImages):
         img = sImages[status]
     else:
         img = None
     tx = wallet.transactions.get(tx_hash, None)
     if tx is not None and tx.raw:
         # NB: save a copy of the tx in this hentry, because it may get
         # deserialized later, and if we were to deserialize the tx that's
         # in the wallet dict, we'd eat memory.
         tx = Transaction(tx.raw)
     entry = HistoryEntry(tx, tx_hash, status_str, label, v_str,
                          balance_str, date, ts, conf, status, value,
                          fiat_amount, fiat_balance, fiat_amount_str,
                          fiat_balance_str, ccy, img)
     return entry
    def test_inputs(self):
        try:
            with open(txintests_local) as f:
                testlist = json.load(f)
            print("Got script tests from %s; will not download." %
                  (txintests_local, ))
        except IOError:
            print("Couldn't get script tests from %s; downloading from %s..." %
                  (txintests_local, txintests_url))
            testlist = requests.get(txintests_url).json()

        print("Starting %d tests on SLP's input validation" % len(testlist))
        for test in testlist:
            description = test['description']

            given_validity = {}
            #should_validity = {}
            txes = {}
            for d in test['when']:
                tx = Transaction(d['tx'])
                txid = tx.txid()
                txes[txid] = tx
                if d['valid'] is True:
                    given_validity[txid] = 1
                elif d['valid'] is False:
                    given_validity[txid] = 2
                else:
                    raise ValueError(d['valid'])

            for d in test['should']:
                tx = Transaction(d['tx'])
                txid = tx.txid()
                txes[txid] = tx
                d['txid'] = txid
                #if d['valid'] is True:
                #should_validity[txid] = 1
                #elif d['valid'] is False:
                #should_validity[txid] = 2
                #else:
                #raise ValueError(d['valid'])

            graph_context, graph_context_nft1 = slp_validator_0x01.GraphContext(
            ), slp_validator_0x01_nft1.GraphContext_NFT1()

            for i, d in enumerate(test['should']):
                txid = d['txid']
                with self.subTest(description=description, i=i):
                    try:
                        slp_msg = slp.SlpMessage.parseSlpOutputScript(
                            txes[txid].outputs()[0][1])
                        if slp_msg.token_type == 1:
                            graph, job_mgr = graph_context.setup_job(
                                txes[txid], reset=True)
                        elif slp_msg.token_type == 65 or slp_msg.token_type == 129:
                            graph, job_mgr = graph_context_nft1.setup_job(
                                txes[txid], reset=True)
                        else:
                            raise slp.SlpUnsupportedSlpTokenType(
                                slp_msg.token_type)
                    except slp.SlpInvalidOutputMessage:  # If output 0 is not OP_RETURN
                        self.assertEqual(d['valid'], False)
                        continue
                    except slp.SlpUnsupportedSlpTokenType:
                        self.assertEqual(d['valid'], False)
                        continue

                    def fetch_hook(txids, job):
                        l = []
                        for txid in txids:
                            try:
                                l.append(txes[txid])
                            except KeyError:
                                #raise Exception('KEY ERROR ' + txid)
                                pass
                        ### Call proxy here!
                        return l

                    if slp_msg.token_type == 1:
                        job = slp_validator_0x01.ValidationJob(
                            graph,
                            txid,
                            None,
                            fetch_hook=fetch_hook,
                            validitycache=given_validity)
                    elif slp_msg.token_type == 65:
                        network = MockNetwork(txes)
                        storage = WalletStorage(os.path.curdir,
                                                manual_upgrades=True,
                                                in_memory_only=True)
                        wallet = Slp_ImportedAddressWallet(storage)
                        wallet.slp_graph_0x01_nft = graph_context_nft1
                        #raise Exception(txid)
                        job = slp_validator_0x01_nft1.ValidationJobNFT1Child(
                            graph,
                            txid,
                            network,
                            fetch_hook=fetch_hook,
                            validitycache=None,
                            ref=wallet)
                    elif slp_msg.token_type == 129:
                        job = slp_validator_0x01_nft1.ValidationJob(
                            graph,
                            txid,
                            None,
                            fetch_hook=fetch_hook,
                            validitycache=given_validity)
                    #if txid == '8a08b78ae434de0b1a26e56ae7e78bb11b20f8240eb3d97371fd46a609df7fc3':
                    #graph.debugging = True
                    #job.debugging_graph_state = True
                    q = Queue()
                    job.add_callback(q.put)
                    job_mgr.add_job(job)
                    while True:
                        try:
                            q.get(timeout=3)  # unlimited timeout
                        except Empty:
                            raise RuntimeError(
                                "Timeout during validation unit test")
                        # if isinstance(job, ValidationJobNFT1Child) and not job.paused:# and job.stop_reason != 'inconclusive':
                        #     raise Exception(job.stop_reason)
                        if not job.paused and not job.running:  # and job.stop_reason != 'inconclusive':
                            n = next(iter(job.nodes.values()))
                            if d['valid'] is True:
                                self.assertEqual(n.validity, 1)
                            elif d['valid'] is False:
                                if test.get(
                                        'allow_inconclusive', False
                                ):  # "allow_inconclusive" allows for ending with an "unvalidated" state for harder corner-cases
                                    self.assertIn(n.validity, (0, 2, 3, 4))
                                else:
                                    self.assertIn(n.validity, (2, 3, 4))
                            else:
                                raise ValueError(d['valid'])
                            break
                        else:
                            if len(job.callbacks) > 1:
                                raise Exception(
                                    "shouldn't have more than 1 callback")
                            job.callbacks.clear()
                            job.add_callback(q.put, allow_run_cb_now=False)