def filterTX(self, typep, l): tx = [] if len(l) < 3: return setOK(tx) query = [l[i:i + 3] for i in range(0, len(l), 3)] for addrx in query: resps = c_peer.sendGETToPeers("address/" + addrx[0] + "/transactions") # to keep it simple, just take first one valid reply code = 500 text = {} for rsp in resps: if len(rsp) != 0: (text, code) = rsp break if code == 200: trxn = {} if typep < 2: trxn = {'address': addrx[0]} elif typep < 4: trxn = {'keyName': addrx[1] + "/" + addrx[0]} elif typep < 6: trxn = {'publicKey': addrx[2] + "/" + addrx[0]} trxn['transactions'] = [] for trx in text['transactions']: if "minedInBlockIndex" in trx: if (typep == 0) or (typep == 2) or (typep == 4): trxn['transactions'].append(trx) else: if (typep == 1) or (typep == 3) or (typep == 5): trxn['transactions'].append(trx) if len(trxn['transactions']) > 0: tx.append(trxn) return setOK(tx)
def getMinerCandidate(self, minerAddress): #TODO share same block for different miner later on to save memory if m_cfg['chainLoaded'] is False: sleep(1) return errMsg("No chain loaded yet, retry ....") err = verifyAddr(minerAddress) if len(err) > 0: return errMsg(err) if minerAddress in m_BufferMinerCandidates: cand = m_BufferMinerCandidates[minerAddress]['mineCandidate'] if cand['index'] == m_candidateBlock['index']: if m_BufferMinerCandidates[minerAddress]['minerBlock']['blockDataHash'] == m_candidateBlock['blockDataHash']: return setOK(cand) # if nothing has changed, return same block # as candidate blocks change with every new TX and different miners might deal # with different blocks, we must keep the miner specific block in case a miner succeeds #if 'prevBlockHash' not in m_candidateBlock: self.c_blockchainHandler.prepareNewCandidateBlock() minerSpecificBlock = deepcopy(m_candidateBlock) # TODO need house-keeping if miners disappear and don't come back, # else we get a queue overflow attack by fake miners candidateMiner = deepcopy(m_candidateMiner) candidateMiner['rewardAddress'] = minerAddress minerSpecificBlock['minedBy'] = minerAddress fees = minBlockReward bindex = len(m_Blocks) for tx in m_candidateBlock['transactions']: if len(tx) > 0: #otherwise it is empty coinbase fees = fees + tx['fee'] else: if fees != minBlockReward: return errMsg("Invalid minimum CoinBase fee TX in block", 404) coinBase = deepcopy(m_coinBase) candidateMiner['index'] = bindex coinBase['minedInBlockIndex'] = bindex minerSpecificBlock['index'] = candidateMiner['index'] candidateMiner['expectedReward'] = fees coinBase['value'] = fees coinBase['to'] = minerAddress coinBase['dateCreated'] = getTime() coinBase['transactionDataHash'] = sha256ToHex(m_transaction_order, coinBase) minerSpecificBlock['transactions'].insert(0, coinBase) candidateMiner['transactionsIncluded'] = len(minerSpecificBlock['transactions']) #inlcudes coinbase # now the block is done, hash it for miner # need to calculate now the hash for this specific miner based candidateBlock # the hash for the miner has to be in specific order of data candidateMiner['blockDataHash'] = makeBlockDataHash(m_candidateBlock, False) print("Generate new candidate for miner: " + minerAddress + " with Hash: " + candidateMiner['blockDataHash'] + " reward: " + str(fees)) m_BufferMinerCandidates[minerAddress] = {} m_BufferMinerCandidates[minerAddress]['mineCandidate'] = deepcopy(candidateMiner) m_BufferMinerCandidates[minerAddress]['minerBlock'] = minerSpecificBlock return setOK(candidateMiner)
def visualCfg(): try: values = request.get_json() if values['active'] is True: pattern = re.compile(values['pattern']) m_visualCFG['active'] = True m_visualCFG['pattern'] = pattern return setOK(values) m_visualCFG['active'] = False m_Delay.clear() return setOK("Tracking switched off") except Exception: return errMsg("JSON not decodeable or missing item")
def listPeers(self): response = {} for peer in m_cfg['activePeers']: response.update({m_cfg['activePeers'][peer]['nodeId']: peer}) for peer in m_cfg['shareToPeers']: response.update({m_cfg['shareToPeers'][peer]['nodeId']: peer}) return setOK(response)
def getAllKeys(self, params): wal = 'unidentified' try: wal = params['wallet'] pattern = re.compile(regexWallet) if not pattern.match(wal): return errMsg("Invalid wallet name.") cmd = "SELECT" sel = False if "py" in params['sel']: cmd = cmd + " 'pubkey:' || pubKey," sel = True if "ay" in params['sel']: cmd = cmd + " 'addr:' || address," sel = True if "ny" in params['sel']: cmd = cmd + " 'name:' || KName" sel = True if sel is False: cmd = cmd + " pubKey, address, KName" else: if cmd.endswith(","): cmd = cmd[0:-1] cmd = cmd + " FROM Wallet WHERE WName='" + wal + "'" return setOK({"keyList": self.doSelect(cmd)}) except Exception: return errMsg("Collecting keys for wallet " + wal + " failed.")
def nodeSpecificGETNow(self, url, linkInfo): urlID = url[1:5] #if (urlID == 'send'): # return send() if urlID == 'info': infow = { 'about': m_info['about'], 'database': m_db['DATABASE'], 'chainID': m_info['chainId'], 'nodeUrl': m_info['nodeUrl'], 'nodeId': m_info['nodeId'] } return setOK(infow) if urlID == 'wall': if url.startswith("/wallet/list/wallet"): return c_walletInterface.getAllWallets(linkInfo['user']) elif url.startswith("/wallet/list/keys/s"): return c_walletInterface.getAllKeys(linkInfo) elif url.startswith("/wallet/list/balance"): return c_walletInterface.getKeyBalance(linkInfo) elif url.startswith("/wallet/list/allbalances"): return c_walletInterface.getAllBalance(linkInfo) elif url.startswith("/wallet/list/allbalance"): return c_walletInterface.getWalletBalance(linkInfo) elif url.startswith("/wallet/list/allkeybalance"): return c_walletInterface.getWalletKeyBalance(linkInfo) elif url.startswith("/wallet/list/allTXs/"): return c_walletInterface.getAllTX(linkInfo) elif url.startswith("/wallet/list/allTX/"): return c_walletInterface.getWalletTX(linkInfo) if urlID == 'addr': return setOK(linkInfo) #return send() # identify your url and then proceed #linkInfo is a json object containing the information from the URL response = { 'NodeType': m_info['type'], 'info': "This API is not yet implemented....", 'requestedUrl': url, 'linkInfo': linkInfo } ## put your logic here and create the reply as next line return errMsg(response)
def genTX(self, data): ret = self.checkTX(data) if len(ret) > 0: return errMsg(ret) m_data['TXList'].append(data) return setOK( str(len(m_data['TXList'])) + " TXs registered. Most recent type: creating TX")
def getAllBalances(): ret = {} for balAddr in m_AllBalances: bal = m_AllBalances[balAddr]['curBalance'] if bal != 0: ret.update({balAddr: bal}) # no need to sort by address or so return setOK(ret)
def debug(): m_ret = {"cfg": deepcopy(m_cfg)} addCfg(m_ret) if isBCNode(): m_ret.update({"TX": deepcopy(m_pendingTX)}) m_ret.update({"minerCandidates": deepcopy(m_BufferMinerCandidates)}) m_ret.update({"balances": deepcopy(m_AllBalances)}) m_ret.update({"blocks": deepcopy(m_Blocks)}) return setOK(m_ret)
def getKeyBalance(self, params): keys = self.getDataFor([params['sel'], params['key']], params['wallet'], "", params['user']) if len(keys) > 4: respText, respCode = self.collectKeyBalance(keys[4]) if respCode == 200: return setOK(respText) else: return errMsg(respText['errorMsg'], respCode) return errMsg("Invalid parameters provided")
def getTXForHash(self, hash): if (len(hash) != len(defHash)): return errMsg("Invalid Hash Len") response = self.getPendTXByHash(hash) # if a given hash is found already, no other exists, else keep searching if (len(hash) == 0) or (len(response) == 0): response.extend(self.getConfirmedTXByHash(hash)) if (len(response) > 0): return setOK(response) return errMsg("No transactions found for: " + hash, 404)
def peersConnect(self, source, values, isConnect): m, l, f = checkRequiredFields(['peerUrl'], values, [], False) if len(m) > 0: return errMsg("Missing field 'peerUrl' ") url = values['peerUrl'] newURL = getValidURL(url, True) if newURL == "": return errMsg("Invalid URL: " + url) if isConnect: err = self.addPeerOption(url, source) if len(err) == 0: return setOK("Connection to peer registered") if err.startswith("Already connected"): return errMsg("Already connected to peer: " + url, 409) else: err = self.removePeerOption(url) if len(err) == 0: return setOK("Connection to peer removed") return errMsg(err)
def setID(self, data): if m_data['chainRef'] != "": return errMsg("Current chainRef set: " + m_data['chainRef']) if project.classes.c_walletInterface.hasWallet( 'genesis' + data['chainRef']) is True: return errMsg("Wallet reference already exists") m_data.clear() m_data.update(deepcopy(m_dataInit)) m_data['chainRef'] = data['chainRef'] return setOK("chain ID set to: " + data['chainRef'])
def updGX(self, data): if m_data['chainRef'] == "": return errMsg("Missing chainRef ") if data['chainRef'] != m_data['chainRef']: return errMsg("Current chainRef not matching: " + m_data['chainRef']) for jtx in data['TXList']: if jtx['chainRef'] != m_data['chainRef']: return errMsg("One of the TX has invalid chainRef: " + jtx['chainRef']) m_data.clear() m_data.update(data) return setOK("Data updated without major verifications")
def getAllBalance(self, params): try: user = params['user'] bal = deepcopy(m_balanceData) for key in self.doSelect( "SELECT DISTINCT address FROM Wallet WHERE User='******'"): val, respCode = self.collectKeyBalance(key) if respCode == 200: self.sumBalance(bal, val) else: return errMsg(val, respCode) return setOK(bal) except Exception: return errMsg("Seems to have peer issues....")
def getWalletBalance(self, params): try: wal = params['wallet'] user = params['user'] bal = deepcopy(m_balanceData) for key in self.doSelect( "SELECT address FROM Wallet WHERE WName='" + wal + "' AND User='******'"): val, respCode = self.collectKeyBalance(key) if respCode == 200: self.sumBalance(bal, val) w_cfg["lastBal"] = "" + str(bal['confirmedBalance']) + "/ " + str( bal['pendingBalance']) return setOK(bal) except Exception: return errMsg("Seems to have peer issues....")
def clrNode(): values = request.get_json() #Check that the required fields are in the POST'ed data. required = ['node'] python_obj = json.loads(values) for k in required: if k not in python_obj: return 'Invalid param', 400 #TODO what was clrNode and setNode() doing? oldNodes = setNode(python_obj['node'], m_info['nodeURL']) for oldNode in oldNodes: if oldNode in m_cfg['nodes']: m_cfg['nodes'].remove(oldNode) return setOK(m_cfg)
def visualGet(): try: dat = {} for item in m_Delay: if 'delayID' in item: dat = deepcopy(item) item['releaseID'] = dat['delayID'] del item['delayID'] break dat['activePeers'] = m_cfg['activePeers'] dat['shareToPeers'] = m_cfg['shareToPeers'] dat['peerOption'] = m_cfg['peerOption'] addCfg(dat) return setOK(dat) except Exception: print("visualGet Failed") return errMsg("Request failed")
def getWalletKeyBalance(self, params): try: wal = params['wallet'] user = params['user'] bal = {} for addr in self.doSelect( "SELECT address FROM Wallet WHERE WName='" + wal + "' AND User='******'"): val, respCode = self.collectKeyBalance(addr) if respCode == 200: bal2 = deepcopy(m_balanceData) bal2['confirmedBalance'] = val['confirmedBalance'] bal2['pendingBalance'] = val['pendingBalance'] bal[addr] = bal2 return setOK(bal) except Exception: return errMsg("Seems to have peer issues....")
def receivedNewTransaction(trans, share): # Checks for missing / invalid fields / invalid field values colErr = verifyBasicTX( trans, False, m_transaction) # such can never be coinbase, so False! if colErr == "": trx = deepcopy(trans) passOn = deepcopy(trans) del trx["senderSignature"] # this must be excluded from the hash hash = sha256ToHex(m_transaction_order, trx) #TODO Validates the transaction public key, validates the signature trans["transactionDataHash"] = hash # Checks for collisions -> duplicated transactions are skipped if hash in m_pendingTX: return errMsg("TX is duplicate of in pending TX") if isUniqueTXInBlocks(hash) is False: return errMsg("TX is duplicate of TX in existing block") if ('transferSuccessful' not in trans) or (trans['transferSuccessful'] is True): tmpBal = getBalance(trans['from']) if tmpBal['confirmedBalance'] + tmpBal['pendingBalance'] < trans[ 'value'] + trans['fee']: return errMsg("Not enough balance") # Puts the transaction in the "pending transactions" pool m_pendingTX.update({trans['transactionDataHash']: deepcopy(trans)}) if "transferSuccessful" not in trans: trans["transferSuccessful"] = True trans["minedInBlockIndex"] = len(m_Blocks) m_candidateBlock['transactions'].append(deepcopy(trans)) m_info['pendingTransactions'] = len(m_pendingTX) response = {"transactionDataHash": hash} m_BufferMinerCandidates.clear() if share is True: # Sends the transaction to all peer nodes through the REST API # It goes from peer to peer until it reaches the entire network #TODO do we still need this 'fromPeer'? c_peer.sendAsynchPOSTToPeers("/transactions/send", passOn) return setOK(response, 201) #201 as per slide 38 return # nothing returned, nothing sent return errMsg(colErr)
def getBlockHash(self, params): try: hfrom = +params['from'] hto = +params['to'] hlength = +params['cnt'] if (hfrom < 0) or (hfrom >= len(m_Blocks)) or (hto < hfrom) or \ (hlength < 0) or (hlength > len(defHash)): return errMsg("Inconsistent request") repl = [] if hlength == 0: hlength = len(defHash) for x in range(hfrom, hto+1): if x >= len(m_Blocks): break repl.append([x, m_Blocks[x]['blockHash'][0:hlength]]) return setOK(repl) except Exception: return errMsg("Invalid parameters")
def getAllKeys(self, params): wal = 'unidentified' try: wal = params['wallet'] pattern = re.compile(regexWallet) if not pattern.match(wal): return errMsg("Invalid wallet name.") cmd = "SELECT" sel = False if "py" in params['sel']: cmd = cmd + " 'pubkey:' || pubKey," sel = True if "ay" in params['sel']: cmd = cmd + " 'addr:' || address," sel = True if "ny" in params['sel']: cmd = cmd + " 'name:' || KName" sel = True if sel is False: cmd = cmd + " pubKey, address, KName" else: if cmd.endswith(","): cmd = cmd[0:-1] cmd = cmd + " FROM Wallet WHERE WName='" + wal + "'" ret = project.classes.c_walletInterface.doSelect(cmd) repl = [] for k in ret: if k.startswith("addr"): repl.append(k) else: if k[-1] == "#": repl.append(k + "no") else: repl.append(k[0:k.rindex("#")] + "#yes") return setOK({"keyList": repl}) except Exception: return errMsg("Collecting keys for wallet " + wal + " failed.")
def visualRelease(idx): try: found = False rel = {} for item in m_Delay: if ('releaseID' in item) and (item['releaseID'] == idx): rel = item found = True break if found is True: m_Delay.remove(rel) if rel['asynchPOST'] is True: requests.post(url=rel['url'], json=rel['json'], headers={'accept': 'application/json'}) return setOK(rel) return errMsg("Unexpected Release for " + str(id)) except Exception: print("visualRelease Failed") return errMsg("Request failed")
def getTXForAddress(self, address): #TODO move to models reply = {"address": "undefined", "transactions": []} if (len(address) != len(defAdr)): errMsg("Inavlid Address Len") response = self.getPendTXByAddress( address ) #order not clearly defined in slide29, pending more important # if a given hash is found already, no other exists, else keep searching #there is no pending, so return all # if (len(hash) == 0) or (len(response) == 0): response.extend(self.getConfirmedTXByAddress(address)) if (len(response) > 0): reply['address'] = address #we must sort the list by date time in ascending order reply['transactions'] = sorted(response, key=lambda k: k['dateCreated']) return setOK(reply) return errMsg("No transactions found for: " + address, 404)
def nodeSpecificPOST(self, url, linkInfo, json, request): ret = { 'NodeType': m_info['type'], 'info': "This URL/API is not available/broken" } try: for x in json: if not re.match("[0-9a-zA-Z]+", x): return errMsg("Invalid JSON key: " + str(x)) if isinstance(json[x], str): if not re.match("[0-9a-zA-Z \.%!@#$\-_+=;:,/?<>]*", json[x]): return errMsg("Invalid character in JSON data: " + str(x)) elif isinstance(json[x], list): for xx in json[x]: if isinstance(xx, str): if not re.match("[0-9a-zA-Z \.%!@#$\-_+=;:,/?<>]*", xx): return errMsg( "Invalid character in JSON data: " + str(xx)) elif not isinstance(json[x], int): return errMsg("Invalid character in JSON data: " + str(json[x])) # This is only applicable to POST, and is a shortcut to stop endless broadcast # of the same message for urlJson in m_peerSkip: if urlJson['url'] == url: m, l, f = checkRequiredFields(json, urlJson['json'], urlJson['json'], True) if len(m) + len(f) == 0: #TODO what text here? return setOK("Acknowledge... ") # for bigger network, make this bigger as well? if len(m_peerSkip) > 10: del m_peerSkip[0] if self.permittedURLPOST(url) is False: return errMsg("This URL/API is invalid or not available. " + url) d("Add delay url: '" + url + "' before we had " + str(len(m_isPOST))) m_isPOST.append(url) while (len(m_isPOST) > 1) or (m_cfg['chainInit'] is True): if self.delay(url, 2) is False: break # for some reason we decide to ignore the loop while len(m_simpleLock) > 0: if self.delay(url, 3) is False: break # for some reason we decide to ignore the loop self.release = False if isBCNode(): ret = self.c_blockInterface.nodeSpecificPOSTNow( url, linkInfo, json, request) elif isWallet(): ret = self.c_walletInterface.nodeSpecificPOSTNow( url, linkInfo, json, request) elif isFaucet(): ret = self.c_faucetInterface.nodeSpecificPOSTNow( url, linkInfo, json, request) elif isGenesis(): ret = self.c_genesisInterface.nodeSpecificPOSTNow( url, linkInfo, json, request) except Exception: d("*********************************************") d("*********************************************") print("POST exception caught, isPoststack " + str(len(m_isPOST))) d("*********************************************") d("*********************************************") if url in m_isPOST: m_isPOST.remove(url) d("Removed delay url: '" + url + "' back to " + str(len(m_isPOST))) self.release = True return ret
def addKeysToWallet(self, data, wal): mes, code = self.addKeysToWalletBasic(data, wal) if code == 200: return setOK(mes) return errMsg(mes, code)
def checkChainSituation(self, source, blockInfo): #We arrive here either because a block notification was sent, #or because our peer got an info claiming the peer has longer chain. # here we decide on the situation of whether blocks are simply added on top # or if something more compliucated is needed, e.g. go back the stack to find # common block and then recover shared TXs etc. etc. #These are shared among info and block notification # "nodeUrl": sender # "blocksCount": 25, "cumulativeDifficulty": 127 # added by PDPCOin : blockHash for notification #only in info: # "confirmedTransactions": 208 try: d("checking status due to " + source) peer = blockInfo['nodeUrl'] if blockInfo['blocksCount'] < len(m_Blocks): d("stay with local chain anyway as it is longer than for " + peer) self.asynchNotifyPeers() return errMsg("Notified chain shorter than local current, current is:" + str(len(m_Blocks))) if source == "notification": if 'blockHash' in blockInfo: #PDPCCoin specific shortcut if blockInfo['blockHash'] == m_Blocks[-1]['blockHash']: d("is the same, probably rebound...") return setOK("Thank you for the notification.") while m_cfg['checkingChain'] is True: d("Already checking chain status, so complete the first one") #return errMsg("Please wait for current synchronisation to complete...") sleep(1) m_cfg['checkingChain'] = True if blockInfo['blocksCount'] == len(m_Blocks): d("blocks on par, check next step with "+peer) #this means we have conflict on the same top block, probably parallel mined if blockInfo['cumulativeDifficulty'] < m_info['cumulativeDifficulty']: self.asynchNotifyPeers() m_cfg['checkingChain'] = False d("local difficulty higher, no change") return errMsg("Peers chain cumulativeDifficulty lower than local current, current is:" + str(m_info['cumulativeDifficulty'])) else: # we have same height and same or lower difficulty, so we need to roll the dice for now # based on hash #get the actual block from peer res, stat = self.getNextBlock(peer, -1) if stat == 200: # yoursbetter must not be based on the claim but based # on the block data and its difficulty versus my cumulative # else an attacker might cheat with high claim but low delivery # 0) we ignore your cumDiff # a) myDiff versus your blockDifficulty # b) mycum-myDiff+yourDiff == your claimed cumDif d("got peer block as requested with OK") # We repeat the check here in case we had info instead of notification! if res['blockHash'] == m_Blocks[-1]['blockHash']: d("anyway the same") m_cfg['checkingChain'] = False return setOK("Thank you for the notification.") # need to include difficulty in this decision yoursBetter = blockInfo['cumulativeDifficulty'] > m_info['cumulativeDifficulty'] if yoursBetter is False: yoursBetter = res['difficulty'] > m_Blocks[-1]['difficulty'] if yoursBetter is False: d("same block difficulty") #we are confirmed same same in all, so lets roll the deterministic dice #by crossing the two inputs instead of checking umber of TX or value etc., #all of which could lead to easier rigging than dice lstDice = "" indexMy=0 indexYou=0 dice = "x" xst = [res['blockDataHash'], m_Blocks[-1]['blockHash'], m_Blocks[-1]['blockDataHash'], res['blockHash']] xst.sort() for lst in xst: lstDice = lstDice + lst while indexMy == indexYou: lstDice = lstDice+dice d("roll the dice...") dice = sha256StrToHex(lstDice)[0] d("Dice value:"+str(dice)) indexMy = m_Blocks[-1]['blockHash'].index(dice) indexYou = res['blockHash'].index(dice) d(str(indexMy) + " vs " + str(indexYou)) if (indexYou > indexMy): yoursBetter = True d("dice said yoursBetter :" + str(yoursBetter)) if yoursBetter is True: if res['prevBlockHash'] != m_Blocks[-1]['prevBlockHash']: d("hashes different need to settle backtrack") return self.handleChainBackTracking(peer) d("!!!we conceeded, add peers block from "+peer) restor = m_Blocks[-1] confirmRevertBalances(restor['transactions']) del m_Blocks[-1] err = self.checkAndAddBlock(res, True) if len(err) > 0: d("something was wrong, restore own previous block") self.checkAndAddBlock(restor, True) m_cfg['checkingChain'] = False return errMsg("Invalid block received") else: self.asynchNotifyPeers() d("local copy maintained after all") i=0 m_cfg['checkingChain'] = False return setOK("Thank you for the notification.") m_cfg['checkingChain'] = False d("The reply did not have the correct fields") return errMsg("No proper reply, ignored") else: d("local chain appears shorter anyway, so just ask for up to block "+str(blockInfo['blocksCount'])) # the peer claims to be ahead of us with at leats one block, so catch up until something happens # easy case just add the new block on top, and each block is checked fully, no backtrack res, stat = self.getNextBlock(peer, 0) if stat == 200: # backtrack into the stack!!! if res['prevBlockHash'] != m_Blocks[-1]['blockHash']: d("hashes different need to settle backtrack") return self.handleChainBackTracking(peer) if source == 'notification': d("Sender want a reply, so process") err = self.getMissingBlocksFromPeer(blockInfo['nodeUrl'], blockInfo['blocksCount'], True, res) m_cfg['checkingChain'] = False if len(err) > 0: return errMsg(err) return setOK("Thank you for the notification.") else: d("this is info internal, so create thread and don't care result") threadx = Thread(target=self.getMissingBlocksFromPeer, args=(blockInfo['nodeUrl'], blockInfo['blocksCount'], False, res)) threadx.start() return setOK("No one sees this answer anyway, but in case, we are processing blocks") m_cfg['checkingChain'] = False return errMsg("Invalid block received") # for info this is ignored anyway except Exception: m_cfg['checkingChain'] = False return errMsg("Processing error occurred")
def getJSONBlockByNumber(self, blockNr): blk = self.getBlockByNumber(blockNr) if len(blk) > 0: return setOK(blk) return errMsg('BlockNumber not valid or not existent: '+str(blockNr))
def getBlockBalances(self, para): bal = {} err = self.getBalanceFromToBlock(0, para['to'], bal) if err != "": return errMsg(err) return setOK(bal)
def getAllWallets(self, user): return setOK({"walletList": self.listAllWallets(user)})