def loadSys(self,sysIn): self.c_blockchainHandler.clearChainLoadGenesis() myUrl = m_info['nodeUrl'] myNodeID = m_info['nodeId'] m_info.clear() m_info.update(sysIn['m_info']) m_info["nodeUrl"] = myUrl m_info["nodeId"] = myNodeID m_cfg.clear() m_cfg.update(sysIn['m_cfg']) m_peerInfo.clear() m_peerInfo.update(sysIn['m_peerInfo']) m_Blocks.clear() for block in sysIn['m_Blocks']: if (len(m_Blocks) == 0): # TODO verify all fields are the same not only there!!! m, l, f = checkRequiredFields(block, m_genesisSet[0], [], False) if (len(m) == 0): # TODO revert if loading failed!?!?! m_Blocks.append(block) continue else: ret = self.c_blockchainHandler.verifyThenAddBlock(block) if (len(ret) > 0): # TODO revert if loading failed!?!?! return errMsg(ret) m_pendingTX.update(sysIn['m_pendingTX']) m_BufferMinerCandidates.update(sysIn['m_BufferMinerCandidates']) m_stats.update(sysIn['m_stats'])
def isDataValid(resp_text): m, l, f = checkRequiredFields(resp_text, m_candidateMiner, [], True) if (len(m) > 0) or (l != 0): d("required fields not matched") return False #TODO shoul dthis be further limited to half of zero and at least 5??? if (resp_text['difficulty'] >= len( cfg['zero_string'])) or (resp_text['difficulty'] < 0): d("Difficulty not possible") return False if (resp_text['rewardAddress'] != cfg['address']) or (resp_text['index'] <= 0): d("Wrong reward address") return False if len(defHash) != len(resp_text['blockDataHash']): d("Wrong Hash length in blockDataHash" + resp_text['blockDataHash']) return False if resp_text['expectedReward'] < minBlockReward: d("Wrong Hash or too low expectedRewards: " + str(resp_text['expectedReward'])) return False if resp_text['transactionsIncluded'] <= 0: d("No transaction at all") return False return True
def receivedBlockNotificationFromPeer(self, blockInfo): #TODO do we want to check if NodeUrl and real sender are identical?? try: m, l, f = checkRequiredFields(blockInfo, m_informsPeerNewBlock, [], False) if (len(m) == 0) and (l == 0): return self.checkChainSituation('notification', blockInfo) except Exception: i=0 return errMsg("Invalid block structure")
def getAllBlocksOneReadDirect(self): # coming here we are sure that we have back-up or nothing to loose! allPeersInfo = c_peer.sendGETToPeers("info") ret = -1 while (ret != 200) and (len(allPeersInfo)>0): #some peers exists and may have blocks, so we follow them maxIdx = -1 maxDiff = -1 cnt = -1 for detail, detail200 in allPeersInfo: cnt = cnt + 1 if detail200 != 200: #TODO actually should remove!!!! continue m, l, f = checkRequiredFields(detail, m_info, [], False) if (isSameChain(detail) is True) and (len(m) == 0): if maxDiff < detail['cumulativeDifficulty']: maxDiff = detail['cumulativeDifficulty'] maxIdx = cnt if maxIdx >= 0: bestPeer = allPeersInfo[maxIdx][0]['nodeUrl'] #bestPeer = bestPeer[0] #bestPeer = bestPeer['nodeUrl'] blockList, ret = c_peer.sendGETToPeer(bestPeer+"/blocks") if ret == 200: isFirst = True for block in blockList: if isFirst is True: #TODO compare block with my genesis # if any verification fails, set ret to -1 isFirst = False m_info['cumulativeDifficulty'] = block['difficulty'] continue if ret == 200: if len(self.verifyThenAddBlock(block)) != 0: self.clearChainLoadGenesis() break else: self.clearChainLoadGenesis() break m_info['blocksCount'] = len(m_Blocks) continue # with ret being 200, this ends the loop!!! del allPeersInfo[maxIdx] continue
def getNextBlock(self, peer, offset): if (len(peer) > 0) and (peer[-1] != "/"): peer = peer + "/" base = "blocks/" url = base + str(len(m_Blocks)+offset) try: res, stat = c_peer.sendGETToPeer(peer + url) except Exception: d("peer failed" + peer) return "Block not available at " + peer, -1 # do not change it is checked outside if stat == 200: d("got peer block as requested with OK") m, l, f = checkRequiredFields(res, m_genesisSet[0], [], False) if len(m) == 0: if res['index'] == len(m_Blocks)+offset: return res, stat return "Block not available/valid at " + peer, -1 # do not change it is checked outside
def suitablePeer(self, peer, fastTrack=False): try: s1 = self.doGET(peer + "/info", fastTrack) reply = json.loads(s1.text) m, l, f = checkRequiredFields(reply, m_info, ["chainId", "about"], False) if (len(f) > 0) or ( len(m) > 0): # we skip to check any additional fields, is that OK print("Peer " + peer + " reply not accepted, considered not alive") # as it is invalid peer, don't try again m_cfg['peers'][peer][ 'wrongType'] = m_cfg['peers'][peer]['wrongType'] + 1 del m_cfg['peers'][peer] m_info['peers'] = len(m_cfg['peers']) return {'wrongChain': True} if peer != reply['nodeUrl']: return {'Inconsistency in nodeURL': True} if not self.ensureBCNode(reply['type']): return {'wrongType': True} if isBCNode(): if m_cfg['chainLoaded'] is True: if (reply['blocksCount'] > len(m_Blocks)) or ( reply['blockHash'] != m_Blocks[-1]['blockHash']): # ignore the return data from the situational check as here is primarily peer # and informing the blockmanager about potential peers is secondary here d("info showed longer block or different hash:" + str(reply['blocksCount']) + " " + str(len(m_Blocks)) + " " + reply['blockHash'] + " " + m_Blocks[-1]['blockHash']) project.classes.c_blockchainNode.c_blockchainHandler.checkChainSituation( 'info', reply) if reply['blocksCount'] < len(m_Blocks): # we found a laggard, let him know we are further project.classes.c_blockchainNode.c_blockchainHandler.asynchNotifyPeers( ) return reply except Exception: return {'fail': True}
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 verifyBlockAndAllTXOn(block, checkUnique): # make sure block has all fields and the correct chanId m, l, f = checkRequiredFields(block, m_completeBlock, ['chainId'], False) isGenesis = (block['index'] == 0) if isGenesis: # special case for genesis bloc if (len(m) != 1) or (m[0] != "prevBlockHash") or (l != -1) or (len(f) > 0): return "Invalid Genesis block, should only not have 'prevBlockHash' but says " + str( m) else: if len(m) == 0: if block['prevBlockHash'] != m_Blocks[block["index"] - 1]['blockHash']: return "Block hash does not match previous block hash " else: return "Missing field(s): " + str(m) if (l != 0) or (len(f) > 0): return "Invalid block fields" #TODO check the hash of the blockhash! #blockHash = sha256ToHex(m_Miner_order, block) or use the update as we need to inlccude each TX below!!! isCoinBase = True # Setting true here ensures that there is at least a coinbase there and not skipped for trans in block['transactions']: ret = verifyBasicTX(trans, isCoinBase, m_staticTransactionRef) #TODO add TX to block has! if (len(ret) > 0): return ret # we only ned to check block hashes as here we have no pending TXs if (checkUnique is True) and (isUniqueTXInBlocks( trans['transactionDataHash']) is False): return "Duplicate TX detected" isCoinBase = isGenesis # genesis block verification only has coinbase like entries return ""
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 verifyBasicTX(trans, isCoinBase, ref): m, l, f = checkRequiredFields(trans, ref, [], False) colErr = "" for x in m: if colErr == "": colErr = "Missing field(s): '" + x + "'" else: colErr = colErr + "and '" + x + "'" # the entire check on the length has now been removed, because any addiitonal fields # within the TX are allowed, instead of being strict to follow only the exact specification, # such as to accept new forks which add new fields, but we do not accept missing fields # and by accepting addiiotnal fields, the check for unsuccessful is also not needed anymore # if l != 0: # # only for cases of rejected TX we need to additionally check the two separate fields # # if (l != 2): # # colErr = colErr + " Invalid number of fields in transaction: " + str(l) + str(trans) # # else: # # if ("transferSuccessful" not in trans) or ("transactionDataHash" not in trans): # # colErr = colErr + " Problem with TX successfulField " + str(trans) # # else: # # l = 0 # not used so far, but prepare for any changes later # # note we only check for known issues. If the TX has other more fields, so be it # # because it could be from a fork # # the only currently known issue is that more fields appear in unsuccessful TX, so this must be confirmed # # a unsuccessful TX can have 2 or 3 more fields, depending the status it had when it was rejected # # original version had only allowed 2 and did not consider the case that it could have been mined # # and then the 'minedInBlock' also appears, so now we don't check length but just the two critical fields # if isCoinBase is False: # if 'transferSuccessful' in trans: # if (trans['transferSuccessful'] is True) or ("transactionDataHash" not in trans): # colErr = colErr + "Problem with recognising TX as unsuccessful" if colErr == "": # final checks if len(trans['senderSignature']) != 2: colErr = colErr + " Invalid number of elements in 'senderSignature' field" else: # len0 = len(trans['senderSignature'][0]) # len1 = len(trans['senderSignature'][1]) # len2 = len(defSig) if (len(trans['senderSignature'][0]) != len(trans['senderSignature'][1])) or\ (len(trans['senderSignature'][0]) != len(defSig)): colErr = colErr + " Invalid/Unpadded 'senderSignature' length" else: if isCoinBase: if (trans['senderSignature'][0] != defSig) or (trans['senderSignature'][1] != defSig): colErr = colErr + "Invalid senderSignature" else: if (trans['senderSignature'][0] == defSig) or (trans['senderSignature'][1] == defSig): colErr = colErr + "Invalid senderSignature" if not isinstance(trans['fee'], int): colErr = colErr + "Fees must be integer, you sent: " + str( trans['fee']) else: if isCoinBase: if trans['fee'] != 0: colErr = colErr + "Coinbase fee should be zero, you sent: " + str( trans['fee']) else: if trans['fee'] < m_stats['m_minFee']: colErr = colErr + "Minimun fee 10 micro-coins, you sent: " + str( trans['fee']) if not isinstance(trans['value'], int): colErr = colErr + "Value must be integer, you sent: " + str( trans['value']) else: # slide 39 confirm that 0 value transactions are allowed if trans['value'] < 0: colErr = colErr + "Minimum value 0 micro-coins, you sent: " + str( trans['value']) if isCoinBase: # TODO any other coinbase checks colErr = colErr + verifyPubKey(trans['senderPubKey'], True) if trans['from'] != defAdr: colErr = colErr + "Invalid from in Coinbase" else: colErr = colErr + verifyAddr(trans['from'], trans['senderPubKey']) colErr = colErr + verifyAddr(trans['to']) if len(colErr) > 0: d("Verification problem: " + colErr) return colErr