def verifyBeaten() -> None: #Verify beaten was set. The fourth Transaction is also beaten, yet should be pruned. #That's why we don't check its status. for send in sends[1:3]: if not rpc.call("consensus", "getStatus", [send.hash.hex()])["beaten"]: raise TestError( "Meros didn't mark a child and its descendant as beaten.") #Check the pending Verification for the beaten descendant was deleted. if ((rpc.call("consensus", "getStatus", [sends[2].hash.hex()])["verifiers"] != [0]) or (bytes.fromhex( rpc.call("merit", "getBlockTemplate", [blsPubKey])["header"])[36:68] != bytes(32))): raise TestError("Block template still has the Verification.") #Verify the fourth Transaction was pruned. with raises(TestError): rpc.call("transactions", "getTransaction", [sends[3].hash.hex()]) #Verify neither the second or third Transaction tree can be appended to. #Publishes a never seen-before Send for the descendant. #Re-broadcasts the pruned Transaction for the parent. for send in sends[3:]: #Most of these tests use a socket connection for this. #This has identical effects, returns an actual error instead of a disconnect, #and doesn't force us to wait a minute for our old socket to be cleared. with raises(TestError): rpc.call("transactions", "publishSend", [send.serialize().hex()]) #Not loaded above as it can only be loqaded after the chain starts, which is done by the Liver. #RandomX cache keys and all that. blockWBeatenVerif: Block = Block.fromJSON( vectors["blockWithBeatenVerification"]) #The following code used to test behavior which was removed, in order to be more forgiving for nodes a tad behind. #Verify we can't add that SignedVerification now. #rpc.meros.signedElement(verif) #try: # rpc.meros.live.recv() # #Hijacks a random Exception type for our purposes. # raise MessageException("Meros didn't disconnect us after we sent a Verification for a beaten Transaction.") #except TestError: # pass #except MessageException as e: # raise TestError(e.message) #sleep(65) #rpc.meros.liveConnect(blockWBeatenVerif.header.last) #Verify we can't add a Block containing that Verification. rpc.meros.liveBlockHeader(blockWBeatenVerif.header) #BlockBody sync request. rpc.meros.handleBlockBody(blockWBeatenVerif) #Sketch hash sync request. hashReqs: bytes = rpc.meros.sync.recv()[37:] for h in range(0, len(hashReqs), 8): for packet in blockWBeatenVerif.body.packets: if int.from_bytes(hashReqs[h:h + 8], byteorder="little") == Sketch.hash( blockWBeatenVerif.header.sketchSalt, packet): rpc.meros.packet(packet) break try: rpc.meros.live.recv() raise MessageException( "Meros didn't disconnect us after we sent a Block containing a Verification of a beaten Transaction." ) except TestError: pass except MessageException as e: raise TestError(e.message) sleep(65) rpc.meros.liveConnect(blockWBeatenVerif.header.last) rpc.meros.syncConnect(blockWBeatenVerif.header.last)
def sendBlock(toSend: Block) -> None: rpc.meros.liveBlockHeader(toSend.header) if rpc.meros.sync.recv() != (MessageType.BlockBodyRequest.toByte() + toSend.header.hash): raise TestError("Meros didn't ask for this Block's body.") rpc.meros.blockBody(toSend)
def handleSync() -> None: reqHash: bytes = bytes() bH: int = 0 bB: int = 1 while True: if bB == 3: break msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.BlockListRequest: reqHash = msg[3:35] for b in range(len(blockchain.blocks)): if blockchain.blocks[b].header.hash == reqHash: blockList: List[bytes] = [] for bl in range(1, msg[2] + 2): if msg[1] == 0: if b - bl < 0: break blockList.append( blockchain.blocks[b - bl].header.hash) elif msg[1] == 1: blockList.append( blockchain.blocks[b + bl].header.hash) else: raise TestError( "Meros asked for an invalid direction in a BlockListRequest." ) if blockList == []: rpc.meros.dataMissing() break rpc.meros.blockList(blockList) break if b == len(blockchain.blocks): rpc.meros.dataMissing() elif MessageType(msg[0]) == MessageType.BlockHeaderRequest: reqHash = msg[1:33] if reqHash != blockchain.blocks[2 - bH].header.hash: raise TestError( "Meros asked for a Block Header that didn't belong to the next Block." ) #Send the BlockHeader. rpc.meros.syncBlockHeader(blockchain.blocks[2 - bH].header) bH += 1 elif MessageType(msg[0]) == MessageType.BlockBodyRequest: reqHash = msg[1:33] if reqHash != blockchain.blocks[bB].header.hash: raise TestError( "Meros asked for a Block Body that didn't belong to the next Block." ) rpc.meros.blockBody(blockchain.blocks[bB]) bB += 1 else: raise TestError("Unexpected message sent: " + msg.hex().upper()) #Verify the Blockchain. verifyBlockchain(rpc, blockchain)
def TwoHundredThirtyTwoTest( rpc: RPC ) -> None: chains: Dict[str, List[Dict[str, Any]]] with open("e2e/Vectors/Merit/Reorganizations/TwoHundredThirtyTwo.json", "r") as file: chains = json.loads(file.read()) main: Blockchain = Blockchain.fromJSON(chains["main"]) alt: Blockchain = Blockchain.fromJSON(chains["alt"]) def sendBlock( toSend: Block ) -> None: rpc.meros.liveBlockHeader(toSend.header) rpc.meros.handleBlockBody(toSend) if toSend.body.packets: if rpc.meros.sync.recv() != ( MessageType.SketchHashRequests.toByte() + toSend.header.hash + (1).to_bytes(4, byteorder="little") + Sketch.hash(toSend.header.sketchSalt, toSend.body.packets[0]).to_bytes(8, byteorder="little") ): raise TestError("Meros didn't ask for this BlockBody's VerificationPacket.") rpc.meros.packet(toSend.body.packets[0]) #Make the initial connection and sync the main chain. rpc.meros.liveConnect(main.blocks[0].header.hash) rpc.meros.syncConnect(main.blocks[0].header.hash) sendBlock(main.blocks[1]) sendBlock(main.blocks[2]) #Trigger the reorganization to the alternate chain. #We only want the revert aspect of this. rpc.meros.liveBlockHeader(alt.blocks[3].header) if MessageType(rpc.meros.sync.recv()[0]) != MessageType.BlockListRequest: raise TestError("Meros didn't ask for the Block List of the alternate chain.") rpc.meros.blockList([alt.blocks[1].header.hash]) if rpc.meros.sync.recv() != (MessageType.BlockHeaderRequest.toByte() + alt.blocks[2].header.hash): raise TestError("Meros didn't ask for the other BlockHeader in this alternate chain.") rpc.meros.syncBlockHeader(alt.blocks[2].header) #Cause the re-organization to fail. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() sleep(35) #Reboot the node to reload the database. rpc.meros.quit() #Reset the RPC's tracking variables. rpc.meros.calledQuit = False rpc.meros.process = Popen(["./build/Meros", "--data-dir", rpc.meros.dataDir, "--log-file", rpc.meros.log, "--db", rpc.meros.db, "--network", "devnet", "--token", "TEST_TOKEN", "--tcp-port", str(rpc.meros.tcp), "--rpc-port", str(rpc.meros.rpc), "--no-gui"]) while True: try: connection: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connection.connect(("127.0.0.1", rpc.meros.rpc)) connection.shutdown(socket.SHUT_RDWR) connection.close() break except ConnectionRefusedError: sleep(1) rpc.meros.liveConnect(main.blocks[0].header.hash) rpc.meros.syncConnect(main.blocks[0].header.hash) sendBlock(main.blocks[2]) verifyBlockchain(rpc, main)
def sendSends() -> None: for s in range(len(sends)): if rpc.meros.liveTransaction(sends[s]) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast a Send.")
def sendAndVerify() -> None: rpc.meros.liveTransaction(send) rpc.meros.liveTransaction(data) rpc.meros.live.recv() rpc.meros.live.recv() #We now have a Mint, a Claim, a Send, a Data, a lion, a witch, and a wardrobe. #Check the Mint. mint: Mint = Merit.fromJSON(vectors["blockchain"]).mints[0] #pylint: disable=invalid-name EXPECTED_MINT: Dict[str, Any] = { "descendant": "Mint", "inputs": [], "outputs": [{ "amount": str(txOutput[1]), "nick": txOutput[0] } for txOutput in mint.outputs], "hash": mint.hash.hex().upper() } #Also sanity check against the in-house JSON. if mint.toJSON() != EXPECTED_MINT: raise TestError("Python's Mint toJSON doesn't match the spec.") if rpc.call("transactions", "getTransaction", {"hash": mint.hash.hex()}, False) != EXPECTED_MINT: raise TestError("getTransaction didn't report the Mint properly.") #Check the Claim. #pylint: disable=invalid-name EXPECTED_CLAIM: Dict[str, Any] = { "descendant": "Claim", "inputs": [{ "hash": txInput[0].hex().upper(), "nonce": txInput[1] } for txInput in claim.inputs], "outputs": [{ "amount": str(claim.amount), "key": claim.output.hex().upper() }], "hash": claim.hash.hex().upper(), "signature": claim.signature.hex().upper() } if claim.amount == 0: raise Exception( "Python didn't instantiate the Claim with an amount, leading to invalid testing methodology." ) if claim.toJSON() != EXPECTED_CLAIM: raise TestError("Python's Claim toJSON doesn't match the spec.") if rpc.call("transactions", "getTransaction", {"hash": claim.hash.hex()}, False) != EXPECTED_CLAIM: raise TestError("getTransaction didn't report the Claim properly.") #Check the Send. #pylint: disable=invalid-name EXPECTED_SEND: Dict[str, Any] = { "descendant": "Send", "inputs": [{ "hash": txInput[0].hex().upper(), "nonce": txInput[1] } for txInput in send.inputs], "outputs": [{ "amount": str(txOutput[1]), "key": txOutput[0].hex().upper() } for txOutput in send.outputs], "hash": send.hash.hex().upper(), "signature": send.signature.hex().upper(), "proof": send.proof } if send.toJSON() != EXPECTED_SEND: raise TestError("Python's Send toJSON doesn't match the spec.") if rpc.call("transactions", "getTransaction", {"hash": send.hash.hex()}, False) != EXPECTED_SEND: raise TestError("getTransaction didn't report the Send properly.") #Check the Data. #pylint: disable=invalid-name EXPECTED_DATA: Dict[str, Any] = { "descendant": "Data", "inputs": [{ "hash": data.txInput.hex().upper() }], "outputs": [], "hash": data.hash.hex().upper(), "data": data.data.hex().upper(), "signature": data.signature.hex().upper(), "proof": data.proof } if data.toJSON() != EXPECTED_DATA: raise TestError("Python's Data toJSON doesn't match the spec.") if rpc.call("transactions", "getTransaction", {"hash": data.hash.hex()}, False) != EXPECTED_DATA: raise TestError("getTransaction didn't report the Data properly.") #Non-existent hash; should cause an IndexError nonExistentHash: str = data.hash.hex() if data.hash[0] == "0": nonExistentHash = "1" + nonExistentHash[1:] else: nonExistentHash = "0" + nonExistentHash[1:] try: rpc.call("transactions", "getTransaction", {"hash": nonExistentHash}, False) except TestError as e: if str(e) != "-2 Transaction not found.": raise TestError( "getTransaction didn't raise IndexError on a non-existent hash." ) #Invalid argument; should cause a ParamError #This is still a hex value try: rpc.call("transactions", "getTransaction", {"hash": "00" + data.hash.hex()}, False) raise TestError( "Meros didn't error when we asked for a 33-byte hex value.") except TestError as e: if str(e) != "-32602 Invalid params.": raise TestError( "getTransaction didn't raise on invalid parameters.")
def checkFail() -> None: #This Block should cause the node to disconnect us AFTER it syncs our Transaction. syncedTX: bool = False block: Block = merit.blockchain.blocks[2] rpc.meros.liveBlockHeader(block.header) #Handle sync requests. reqHash: bytes = bytes() while True: if syncedTX: #Try receiving from the Live socket, where Meros sends keep-alives. try: if len(rpc.meros.live.recv()) != 0: raise Exception() except TestError: raise SuccessError( "Node disconnected us after we sent a parsable, yet invalid, Transaction." ) except Exception: raise TestError("Meros sent a keep-alive.") msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.BlockBodyRequest: reqHash = msg[1:33] if reqHash != block.header.hash: raise TestError( "Meros asked for a Block Body that didn't belong to the Block we just sent it." ) #Send the BlockBody. rpc.meros.blockBody(block) elif MessageType(msg[0]) == MessageType.SketchHashesRequest: if not block.body.packets: raise TestError( "Meros asked for Sketch Hashes from a Block without any." ) reqHash = msg[1:33] if reqHash != block.header.hash: raise TestError( "Meros asked for Sketch Hashes that didn't belong to the Block we just sent it." ) #Create the haashes. hashes: List[int] = [] for packet in block.body.packets: hashes.append(Sketch.hash(block.header.sketchSalt, packet)) #Send the Sketch Hashes. rpc.meros.sketchHashes(hashes) elif MessageType(msg[0]) == MessageType.SketchHashRequests: if not block.body.packets: raise TestError( "Meros asked for Verification Packets from a Block without any." ) reqHash = msg[1:33] if reqHash != block.header.hash: raise TestError( "Meros asked for Verification Packets that didn't belong to the Block we just sent it." ) #Create a lookup of hash to packets. packets: Dict[int, VerificationPacket] = {} for packet in block.body.packets: packets[Sketch.hash(block.header.sketchSalt, packet)] = packet #Look up each requested packet and respond accordingly. for h in range(int.from_bytes(msg[33:37], byteorder="little")): sketchHash: int = int.from_bytes(msg[37 + (h * 8):45 + (h * 8)], byteorder="little") if sketchHash not in packets: raise TestError( "Meros asked for a non-existent Sketch Hash.") rpc.meros.packet(packets[sketchHash]) elif MessageType(msg[0]) == MessageType.TransactionRequest: reqHash = msg[1:33] if reqHash not in transactions.txs: raise TestError( "Meros asked for a non-existent Transaction.") rpc.meros.syncTransaction(transactions.txs[reqHash]) syncedTX = True else: raise TestError("Unexpected message sent: " + msg.hex().upper())
def publishAndVerify() -> None: if not rpc.call("transactions", "publishTransaction", { "type": "Claim", "transaction": claim.serialize().hex() }, False): raise TestError( "Publishing a valid Transaction didn't return true.") if rpc.meros.live.recv()[1:] != claim.serialize(): raise TestError( "publishTransaction didn't broadcast the Transaction.") if not rpc.call("transactions", "publishTransaction", { "type": "Send", "transaction": send.serialize().hex() }, False): raise TestError( "Publishing a valid Transaction didn't return true.") if rpc.meros.live.recv()[1:] != send.serialize(): raise TestError( "publishTransaction didn't broadcast the Transaction.") if not rpc.call("transactions", "publishTransaction", { "type": "Data", "transaction": data.serialize().hex() }, False): raise TestError( "Publishing a valid Transaction didn't return true.") if rpc.meros.live.recv()[1:] != data.serialize(): raise TestError( "publishTransaction didn't broadcast the Transaction.") #Verify all three were entered properly. verifyTransaction(rpc, claim) verifyTransaction(rpc, send) verifyTransaction(rpc, data) #Create a new Send/Data and publish them without work. sendSentWithoutWork: Send = Send([(send.hash, 0)], [(pubKey.to_bytes(), claim.amount)]) sendSentWithoutWork.sign(sentToKey) sendSentWithoutWork.beat(sendFilter) dataSentWithoutWork: Data = Data( bytes(32), sentToKey.get_verifying_key().to_bytes()) dataSentWithoutWork.sign(sentToKey) dataSentWithoutWork.beat(dataFilter) if not rpc.call( "transactions", "publishTransactionWithoutWork", { "type": "Send", "transaction": sendSentWithoutWork.serialize()[:-4].hex() }, True): raise TestError( "Publishing a valid Transaction without work didn't return true." ) if rpc.meros.live.recv()[1:] != sendSentWithoutWork.serialize(): raise TestError( "publishTransaction didn't broadcast the Transaction.") if not rpc.call( "transactions", "publishTransactionWithoutWork", { "type": "Data", "transaction": dataSentWithoutWork.serialize()[:-4].hex() }, True): raise TestError( "Publishing a valid Transaction without work didn't return true." ) if rpc.meros.live.recv()[1:] != dataSentWithoutWork.serialize(): raise TestError( "publishTransaction didn't broadcast the Transaction.") #Call verify now, which will test ours with work against Meros's with generated work. #Both should terminate on the earliest valid proof, making them identical. verifyTransaction(rpc, sendSentWithoutWork) verifyTransaction(rpc, dataSentWithoutWork) #Re-publishing a Transaction should still return true. if not rpc.call("transactions", "publishTransaction", { "type": "Data", "transaction": data.serialize().hex() }, False): raise TestError( "Publishing an existing Transaction didn't return true.") if MessageType(rpc.meros.live.recv()[0]) == MessageType.Data: raise TestError( "publishTransaction broadcasted an existing Transaction.") #No arguments. try: rpc.call("transactions", "publishTransaction") except TestError as e: if str(e) != "-32602 Invalid params.": raise TestError( "publishTransaction didn't error when passed no arguments." ) #Invalid type. try: rpc.call("transactions", "publishTransaction", { "type": "", "transaction": data.serialize().hex() }, False) raise TestError("") except TestError as e: if str(e) != "-3 Invalid Transaction type specified.": raise TestError( "publishTransaction didn't error when passed an invalid type." ) #Data sent with Send as a type. try: rpc.call("transactions", "publishTransaction", { "type": "Send", "transaction": data.serialize().hex() }, False) raise TestError("") except TestError as e: if str( e ) != "-3 Transaction is invalid: parseSend handed the wrong amount of data.": raise TestError( "publishTransaction didn't error when passed a non-parsable Send (a Data)." ) #Invalid Data (signature). invalidData: Data = Data(bytes(32), sentToKey.get_verifying_key().to_bytes()) newData: bytearray = bytearray(invalidData.data) newData[-1] = newData[-1] ^ 1 invalidData.data = bytes(newData) invalidData.sign(sentToKey) invalidData.beat(dataFilter) try: rpc.call("transactions", "publishTransaction", { "type": "Data", "transaction": invalidData.serialize().hex() }, False) raise TestError("") except TestError as e: if str( e ) != "-3 Transaction is invalid: Data has an invalid Signature.": raise TestError( "publishTransaction didn't error when passed an invalid Transaction." ) #Spam. spamData: Data = data if spamData.proof == 0: spamData = dataSentWithoutWork if spamData.proof == 0: raise Exception("Neither Data is considered as Spam.") spamData.proof = 0 try: rpc.call("transactions", "publishTransaction", { "type": "Data", "transaction": spamData.serialize().hex() }, False) raise TestError("") except TestError as e: if str(e) != "2 Transaction didn't beat the spam filter.": raise TestError( "publishTransaction didn't error when passed a Transaction which didn't beat its difficulty." ) #Test publishTransactionWithoutWork requires authorization. try: rpc.call( "transactions", "publishTransactionWithoutWork", { "type": "Data", "transaction": dataSentWithoutWork.serialize()[:-4].hex() }, False) raise TestError("") except Exception as e: if str(e) != "HTTP status isn't 200: 401": raise TestError( "Called publishTransactionWithoutWork despite not being authed." )
def sendDatas() -> None: for d in range(len(datas)): if rpc.meros.liveTransaction(datas[d]) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast a Data.")
def TwoHundredFourtyTest(rpc: RPC) -> None: #Grab the keys. blsPrivKey: PrivateKey = PrivateKey( bytes.fromhex(rpc.call("personal", "getMeritHolderKey"))) blsPubKey: PublicKey = blsPrivKey.toPublicKey() #Blockchain used to calculate the difficulty. blockchain: Blockchain = Blockchain() #Mine enough blocks to lose Merit. for b in range(9): template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", {"miner": blsPubKey.serialize().hex()}) template["header"] = bytes.fromhex(template["header"]) header: BlockHeader = BlockHeader( 0, blockchain.last(), bytes(32), 0, bytes(4), bytes(32), 0, blockchain.blocks[-1].header.time + 1200) if b == 0: header.newMiner = True header.minerKey = blsPubKey.serialize() header.mine(blsPrivKey, blockchain.difficulty()) blockchain.add(Block(header, BlockBody())) rpc.meros.liveConnect(blockchain.blocks[0].header.hash) rpc.meros.syncConnect(blockchain.blocks[0].header.hash) for b in range(1, 10): headerMsg: bytes = rpc.meros.liveBlockHeader( blockchain.blocks[b].header) rpc.meros.handleBlockBody(blockchain.blocks[b]) if rpc.meros.live.recv() != headerMsg: raise TestError("Meros didn't broadcast back the Block Header.") if MessageType( rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError( "Meros didn't sign a verification of the Block's data.") try: rpc.meros.live.connection.shutdown(socket.SHUT_RDWR) rpc.meros.live.connection.close() rpc.meros.sync.connection.shutdown(socket.SHUT_RDWR) rpc.meros.sync.connection.close() except OSError: pass #Verify our Merit is locked. if rpc.call("merit", "getMerit", {"nick": 0})["status"] != "Locked": raise Exception("Our Merit isn't locked so this test is invalid.") template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", {"miner": blsPubKey.serialize().hex()}) template["header"] = bytes.fromhex(template["header"]) header: BlockHeader = BlockHeader( 0, template["header"][4:36], template["header"][36:68], int.from_bytes(template["header"][68:72], byteorder="little"), template["header"][72:76], template["header"][76:108], 0, int.from_bytes(template["header"][-4:], byteorder="little")) if not any(header.contents): raise TestError("Meros didn't try to unlock its Merit.") header.mine(blsPrivKey, blockchain.difficulty()) #Don't add the last block because we never provided it with a proper body. rpc.call( "merit", "publishBlock", { "id": template["id"], "header": (template["header"] + header.proof.to_bytes(4, byteorder="little") + header.signature).hex() }) #To verify the entire chain, we just need to verify this last header. #This is essential as our chain isn't equivalent. ourHeader: Dict[str, Any] = header.toJSON() if rpc.call("merit", "getBlock", {"block": header.hash.hex()})["header"] != ourHeader: raise TestError("Header wasn't added to the blockchain.")
def sendMeritRemoval() -> None: #Send and verify the MeritRemoval. if rpc.meros.signedElement(removal) != rpc.meros.live.recv(): raise TestError("Meros didn't send us the Merit Removal.") verifyMeritRemoval(rpc, 2, 2, removal.holder, True)
def verifyUnlocked(_: int) -> None: if rpc.call("merit", "getMerit", [0])["status"] != "Unlocked": raise TestError("Meros didn't keep Merit unlocked.")
def getBlockTemplateTest(rpc: RPC) -> None: edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() blockchain: Blockchain = Blockchain() #Get multiple templates to verify they share an ID if they're requested within the same second. templates: List[Dict[str, Any]] = [] startTime: float = nextSecond() for k in range(5): templates.append( rpc.call("merit", "getBlockTemplate", {"miner": getMiner(k)}, False)) if int(startTime) != int(time.time()): #Testing https://github.com/MerosCrypto/Meros/issues/278 has a much more forgiving timer of < 1 second each. #That said, this test was written on the fair assumption of all the above taking place in a single second. raise Exception( "getBlockTemplate is incredibly slow, to the point an empty Block template takes > 0.2 seconds to grab, invalidating this test." ) for k, template in zip(range(5), templates): if template["id"] != int(startTime): raise TestError("Template ID isn't the time.") #Also check general accuracy. if bytes.fromhex(template["key"]) != blockchain.genesis: raise TestError("Template has the wrong RandomX key.") bytesHeader: bytes = bytes.fromhex(template["header"]) serializedHeader: bytes = BlockHeader( 0, blockchain.blocks[0].header.hash, bytes(32), 0, bytes(4), bytes(32), PrivateKey(k).toPublicKey().serialize(), int(startTime)).serialize()[:-52] #Skip over the randomized sketch salt. if (bytesHeader[:72] + bytesHeader[76:]) != (serializedHeader[:72] + serializedHeader[76:]): raise TestError("Template has an invalid header.") #Difficulty modified as this is a new miner. if template["difficulty"] != (blockchain.difficulty() * 11 // 10): raise TestError("Template's difficulty is wrong.") currTime: int = int(nextSecond()) template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False) if template["id"] != currTime: raise TestError("Template ID wasn't advanced with the time.") #Override the ID to enable easy comparison against a historical template. template["id"] = int(startTime) if int.from_bytes(bytes.fromhex(template["header"])[-4:], "little") != currTime: raise TestError("The header has the wrong time.") template["header"] = ( bytes.fromhex(template["header"])[:72] + #Use the header we'll compare to's salt. bytes.fromhex(templates[0]["header"])[72:76] + bytes.fromhex(template["header"])[76:-4] + #Also use its time. int(startTime).to_bytes(4, "little")).hex().upper() if template != templates[0]: raise TestError( "Template, minus the time difference, doesn't match the originally provided template." ) #Test that the templates are deleted whenever a new Block appears. #This is done by checking the error given when we use an old template. with open("e2e/Vectors/Merit/BlankBlocks.json", "r") as file: block: Block = Block.fromJSON(json.loads(file.read())[0]) blockchain.add(block) rpc.meros.liveConnect(blockchain.blocks[0].header.hash) rpc.meros.syncConnect(blockchain.blocks[0].header.hash) rpc.meros.liveBlockHeader(block.header) rpc.meros.rawBlockBody(block, 0) time.sleep(1) #Sanity check. if rpc.call("merit", "getHeight", auth=False) != 2: raise Exception("Didn't successfully send Meros the Block.") #Get a new template so Meros realizes the template situation has changed. rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False) try: rpc.call("merit", "publishBlock", { "id": int(startTime), "header": "" }, False) raise Exception("") except Exception as e: if str(e) != "-2 Invalid ID.": raise TestError("Meros didn't delete old template IDs.") #Test VerificationPacket inclusion. data: Data = Data(bytes(32), edPubKey.to_bytes()) data.sign(edPrivKey) data.beat(SpamFilter(5)) verif: SignedVerification = SignedVerification(data.hash) verif.sign(0, PrivateKey(0)) packet = VerificationPacket(data.hash, [0]) rpc.meros.liveTransaction(data) rpc.meros.signedElement(verif) time.sleep(1) if bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False)["header"])[36:68] != BlockHeader.createContents( [packet]): raise TestError( "Meros didn't include the Verification in its new template.") #Test Element inclusion. sendDiff: SignedSendDifficulty = SignedSendDifficulty(0, 0) sendDiff.sign(0, PrivateKey(0)) rpc.meros.signedElement(sendDiff) time.sleep(1) if bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False)["header"])[36:68] != BlockHeader.createContents( [packet], [sendDiff]): raise TestError( "Meros didn't include the Element in its new template.") #The 88 test checks for the non-inclusion of Verifications with unmentioned predecessors. #Test for non-inclusion of Elements with unmentioned predecessors. sendDiffChild: SignedSendDifficulty = SignedSendDifficulty(0, 2) sendDiffChild.sign(0, PrivateKey(0)) rpc.meros.signedElement(sendDiffChild) time.sleep(1) if bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False)["header"])[36:68] != BlockHeader.createContents( [packet], [sendDiff]): raise TestError( "Meros did include an Element with an unmentioned parent in its new template." ) #If we send a block with a time in the future, yet within FTL (of course), make sure Meros can still generate a template. #Naively using the current time will create a negative clock, which isn't allowed. #Start by closing the sockets to give us time to work. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() #Sleep to reset the connection state. time.sleep(35) #Create and mine the Block. header: BlockHeader = BlockHeader( 0, blockchain.blocks[-1].header.hash, bytes(32), 0, bytes(4), bytes(32), PrivateKey(0).toPublicKey().serialize(), 0, ) miningStart: int = 0 #If this block takes longer than 10 seconds to mine, try another. #Low future time (20 seconds) is chosen due to feasibility + supporting lowering the FTL in the future. while time.time() > miningStart + 10: miningStart = int(time.time()) header = BlockHeader( 0, blockchain.blocks[-1].header.hash, bytes(32), 0, bytes(4), bytes(32), #Mod by something is due to a 2-byte limit (IIRC -- Kayaba). #100 is just clean. +11 ensures an offset from the above, which really shouldn't be necessary. #If we did need one, +1 should work, as we only ever work with PrivateKey(0) on the blockchain. PrivateKey((miningStart % 100) + 10).toPublicKey().serialize(), int(time.time()) + 20, ) header.mine(PrivateKey((miningStart % 100) + 10), blockchain.difficulty() * 11 // 10) blockchain.add(Block(header, BlockBody())) #Send it and verify it. rpc.meros.liveConnect(blockchain.blocks[0].header.hash) rpc.meros.syncConnect(blockchain.blocks[0].header.hash) rpc.meros.liveBlockHeader(header) rpc.meros.rawBlockBody(Block(header, BlockBody()), 0) rpc.meros.live.connection.close() rpc.meros.sync.connection.close() time.sleep(1) #Ensure a stable template ID. currTime = int(nextSecond()) template = rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False) if template["id"] != currTime: raise TestError( "Template ID isn't the time when the previous Block is in the future." ) if int.from_bytes(bytes.fromhex(template["header"])[-4:], "little") != (header.time + 1): raise TestError( "Meros didn't handle generating a template off a Block in the future properly." ) #Verify a Block with three Elements from a holder, where two form a Merit Removal. #Only the two which cause a MeritRemoval should be included. #Mine a Block to a new miner and clear the current template with it (might as well). #Also allows us to test template clearing. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", {"miner": getMiner(1)}, False) #Mine the Block. proof: int = -1 tempHash: bytes = bytes() tempSignature: bytes = bytes() while ((proof == -1) or ((int.from_bytes(tempHash, "little") * (blockchain.difficulty() * 11 // 10)) > int.from_bytes( bytes.fromhex("FF" * 32), "little"))): proof += 1 tempHash = RandomX( bytes.fromhex(template["header"]) + proof.to_bytes(4, "little")) tempSignature = PrivateKey(1).sign(tempHash).serialize() tempHash = RandomX(tempHash + tempSignature) rpc.call( "merit", "publishBlock", { "id": template["id"], "header": template["header"] + proof.to_bytes(4, "little").hex() + tempSignature.hex() }) time.sleep(1) #Verify the template was cleared. currTime = int(nextSecond()) bytesHeader: bytes = bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False)["header"]) serializedHeader: bytes = BlockHeader( 0, tempHash, bytes(32), 0, bytes(4), bytes(32), 0, #Ensures that the previous time manipulation doesn't come back to haunt us. max(currTime, blockchain.blocks[-1].header.time + 1)).serialize()[:-52] #Skip over the randomized sketch salt and time (which we don't currently have easy access to). if (bytesHeader[:72] + bytesHeader[76:-4]) != (serializedHeader[:72] + serializedHeader[76:-4]): raise TestError("Template wasn't cleared.") #Sleep so we can reconnect. time.sleep(35) rpc.meros.liveConnect(blockchain.blocks[0].header.hash) #Finally create the Elements. dataDiff: SignedDataDifficulty = SignedDataDifficulty(1, 0) dataDiff.sign(2, PrivateKey(1)) rpc.meros.signedElement(dataDiff) sendDiffs: List[SignedSendDifficulty] = [ SignedSendDifficulty(1, 1), SignedSendDifficulty(2, 1) ] for sd in sendDiffs: sd.sign(2, PrivateKey(1)) rpc.meros.signedElement(sd) time.sleep(1) #`elem for elem` is used below due to Pyright not handling inheritance properly when nested. #pylint: disable=unnecessary-comprehension if bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False)["header"])[36:68] != BlockHeader.createContents( [], [elem for elem in sendDiffs[::-1]]): raise TestError( "Meros didn't include just the malicious Elements in its new template." )
def sendAlternateTip() -> None: rpc.meros.liveBlockHeader(alt.blocks[-1].header) req: bytes = rpc.meros.sync.recv() if MessageType(req[0]) != MessageType.BlockListRequest: raise TestError( "Meros didn't request the list of previous BlockHeaders.") if req[-32:] != alt.blocks[-2].header.hash: raise TestError( "Meros didn't request the list of previous BlockHeaders for THIS header." ) blockList: List[bytes] = [] b: int = len(alt.blocks) - 3 while b != (len(alt.blocks) - 35): blockList.append(alt.blocks[b].header.hash) b -= 1 rpc.meros.blockList(blockList) diff = -30 while diff != -1: if rpc.meros.sync.recv() != ( MessageType.BlockHeaderRequest.toByte() + alt.blocks[diff].header.hash): raise TestError("Meros didn't request a previous BlockHeader.") rpc.meros.syncBlockHeader(alt.blocks[diff].header) diff += 1 #Advance the chain far enough to switch to the new key. diff = -30 while diff != -11: rpc.meros.handleBlockBody(alt.blocks[diff]) diff += 1 #Cause the reorganization to fail. if MessageType( rpc.meros.sync.recv()[0]) != MessageType.BlockBodyRequest: raise TestError("Meros didn't request a BlockBody.") rpc.meros.dataMissing() sleep(65) rpc.meros.liveConnect(main.blocks[0].header.hash) rpc.meros.syncConnect(main.blocks[0].header.hash) #Sync back the regular chain. rpc.meros.liveBlockHeader(main.blocks[400].header) if MessageType( rpc.meros.sync.recv()[0]) != MessageType.BlockListRequest: raise TestError("Meros didn't request the Block list.") blockList = [] b = 398 while b != 370: blockList.append(main.blocks[b].header.hash) b -= 1 rpc.meros.blockList(blockList) for b in range(381, 401): if b != 400: if rpc.meros.sync.recv() != ( MessageType.BlockHeaderRequest.toByte() + main.blocks[b].header.hash): raise TestError("Meros didn't request the BlockHeader.") rpc.meros.syncBlockHeader(main.blocks[b].header) rpc.meros.handleBlockBody(main.blocks[b])
def finalizedSpendingSend() -> None: #Verify the state is unchanged. if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros didn't consider a finalized Transaction's inputs as spent." )
def sync(self) -> None: self.rpc.meros.syncConnect( self.merit.blockchain.blocks[self.settings["height"]].header.hash) reqHash: bytes = bytes() while True: #Break out of the for loop if the sync finished. #This means we sent every Block, every Element, every Transaction... if ((self.blockHashes == set()) and (self.packets == {}) and (self.txs == set())): break msg: bytes = self.rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.BlockListRequest: reqHash = msg[-32:] for b in range(len(self.merit.blockchain.blocks)): if self.merit.blockchain.blocks[b].header.hash == reqHash: if self.blocks[-1].header.hash != reqHash: self.blocks.append(self.merit.blockchain.blocks[b]) blockList: List[bytes] = [] for bl in range(1, msg[1] + 2): if b - bl < 0: break blockList.append( self.merit.blockchain.blocks[b - bl].header.hash) if b - bl != 0: self.blocks.append( self.merit.blockchain.blocks[b - bl]) if blockList == []: self.rpc.meros.dataMissing() break self.rpc.meros.blockList(blockList) break if b == self.settings["height"]: self.rpc.meros.dataMissing() elif MessageType(msg[0]) == MessageType.BlockHeaderRequest: reqHash = msg[1:33] if (self.txs != set()) or (self.packets != {}): raise TestError( "Meros asked for a new Block before syncing the last Block's Transactions and Packets." ) if reqHash != self.blocks[-1].header.hash: raise TestError( "Meros asked for a BlockHeader other than the next Block's on the last BlockList." ) self.rpc.meros.syncBlockHeader(self.blocks[-1].header) elif MessageType(msg[0]) == MessageType.BlockBodyRequest: reqHash = msg[1:33] if reqHash != self.blocks[-1].header.hash: raise TestError( "Meros asked for a BlockBody other than the next Block's on the last BlockList." ) if int.from_bytes(msg[33:37], "little") != len( self.blocks[-1].body.packets): raise Exception( "Meros didn't request 100% of packets in the BlockBody when syncing." ) self.rpc.meros.rawBlockBody(self.blocks[-1], len(self.blocks[-1].body.packets)) self.blockHashes.remove(self.blocks[-1].header.hash) #Set the packets/transactions which should be synced. self.packets = {} for packet in self.blocks[-1].body.packets: if not ((packet.hash in self.rpc.meros.sentTXs) or (packet.hash == (Data( self.merit.blockchain.genesis, self.blocks[-1].header.last).hash))): self.txs.add(packet.hash) self.packets[Sketch.hash(self.blocks[-1].header.sketchSalt, packet)] = packet #Update the list of mentioned Transactions. noVCMRs: bool = True if (self.packets == {}) and noVCMRs: del self.blocks[-1] elif MessageType(msg[0]) == MessageType.SketchHashRequests: reqHash = msg[1:33] if not self.packets: raise TestError( "Meros asked for Verification Packets from a Block without any." ) if reqHash != self.blocks[-1].header.hash: raise TestError( "Meros asked for Verification Packets that didn't belong to the Block we just sent it." ) #Look up each requested packet and respond accordingly. for h in range(int.from_bytes(msg[33:37], byteorder="little")): sketchHash: int = int.from_bytes(msg[37 + (h * 8):45 + (h * 8)], byteorder="little") if sketchHash not in self.packets: raise TestError( "Meros asked for a non-existent Sketch Hash.") self.rpc.meros.packet(self.packets[sketchHash]) del self.packets[sketchHash] if (self.packets == {}) and (self.txs == set()): del self.blocks[-1] elif MessageType(msg[0]) == MessageType.TransactionRequest: reqHash = msg[1:33] if self.transactions is None: raise TestError( "Meros asked for a Transaction when we have none.") if reqHash not in self.transactions.txs: raise TestError( "Meros asked for a Transaction we don't have.") if reqHash not in self.txs: raise TestError( "Meros asked for a Transaction we haven't mentioned.") self.rpc.meros.syncTransaction(self.transactions.txs[reqHash]) self.txs.remove(reqHash) if (self.packets == {}) and (self.txs == set()): del self.blocks[-1] else: raise TestError("Unexpected message sent: " + msg.hex().upper()) #Verify the Blockchain. verifyBlockchain(self.rpc, self.merit.blockchain) #Verify the Transactions. if self.transactions is not None: verifyTransactions(self.rpc, self.transactions) #Playback their messages. #Verifies Meros can respond as it can receive. if self.settings["playback"]: self.rpc.meros.sync.playback() #Reset the node. self.rpc.reset()
def sync( self ) -> None: #Handshake with the node. self.rpc.meros.syncConnect(self.merit.blockchain.blocks[self.settings["height"]].header.hash) #Handle sync requests. reqHash: bytes = bytes() while True: #Break out of the for loop if the sync finished. #This means we sent every Block, every Element, every Transaction... if ( (self.blockHashes == set()) and (self.packets == {}) and (self.txs == set()) ): break msg: bytes = self.rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.BlockListRequest: reqHash = msg[3 : 35] for b in range(len(self.merit.blockchain.blocks)): if self.merit.blockchain.blocks[b].header.hash == reqHash: blockList: List[bytes] = [] for bl in range(1, msg[2] + 2): if msg[1] == 0: if b - bl < 0: break blockList.append(self.merit.blockchain.blocks[b - bl].header.hash) if b - bl != 0: self.blocks.append(self.merit.blockchain.blocks[b - bl]) elif msg[1] == 1: if b + bl > self.settings["height"]: break blockList.append(self.merit.blockchain.blocks[b + bl].header.hash) self.blocks.append(self.merit.blockchain.blocks[b + bl]) else: raise TestError("Meros asked for an invalid direction in a BlockListRequest.") if blockList == []: self.rpc.meros.dataMissing() break self.rpc.meros.blockList(blockList) break if b == self.settings["height"]: self.rpc.meros.dataMissing() elif MessageType(msg[0]) == MessageType.BlockHeaderRequest: if (self.txs != set()) or (self.packets != {}): raise TestError("Meros asked for a new Block before syncing the last Block's Transactions and Packets.") reqHash = msg[1 : 33] if reqHash != self.blocks[-1].header.hash: raise TestError("Meros asked for a BlockHeader other than the next Block's on the last BlockList.") self.rpc.meros.syncBlockHeader(self.blocks[-1].header) elif MessageType(msg[0]) == MessageType.BlockBodyRequest: reqHash = msg[1 : 33] if reqHash != self.blocks[-1].header.hash: raise TestError("Meros asked for a BlockBody other than the next Block's on the last BlockList.") self.rpc.meros.blockBody(self.blocks[-1]) self.blockHashes.remove(self.blocks[-1].header.hash) #Set packets/transactions. self.packets = {} for packet in self.blocks[-1].body.packets: if packet.hash not in self.synced: self.txs.add(packet.hash) self.packets[Sketch.hash(self.blocks[-1].header.sketchSalt, packet)] = packet #Update the list of mentioned Transactions. noVCMRs: bool = True for elem in self.blocks[-1].body.elements: if isinstance(elem, MeritRemoval): if isinstance(elem.e1, (Verification, VerificationPacket)): self.txs.add(elem.e1.hash) noVCMRs = False if isinstance(elem.e2, (Verification, VerificationPacket)): self.txs.add(elem.e2.hash) noVCMRs = False if (self.packets == {}) and noVCMRs: del self.blocks[-1] elif MessageType(msg[0]) == MessageType.SketchHashesRequest: reqHash = msg[1 : 33] if reqHash != self.blocks[-1].header.hash: raise TestError("Meros asked for Sketch Hashes that didn't belong to the header we just sent it.") #Get the haashes. hashes: List[int] = list(self.packets) #Send the Sketch Hashes. self.rpc.meros.sketchHashes(hashes) elif MessageType(msg[0]) == MessageType.SketchHashRequests: if not self.packets: raise TestError("Meros asked for Verification Packets from a Block without any.") reqHash = msg[1 : 33] if reqHash != self.blocks[-1].header.hash: raise TestError("Meros asked for Verification Packets that didn't belong to the Block we just sent it.") #Look up each requested packet and respond accordingly. for h in range(int.from_bytes(msg[33 : 37], byteorder="big")): sketchHash: int = int.from_bytes(msg[37 + (h * 8) : 45 + (h * 8)], byteorder="big") if sketchHash not in self.packets: raise TestError("Meros asked for a non-existent Sketch Hash.") self.rpc.meros.packet(self.packets[sketchHash]) del self.packets[sketchHash] elif MessageType(msg[0]) == MessageType.TransactionRequest: reqHash = msg[1 : 33] if self.transactions is None: raise TestError("Meros asked for a Transaction when we have none.") if reqHash not in self.transactions.txs: raise TestError("Meros asked for a Transaction we don't have.") if reqHash not in self.txs: raise TestError("Meros asked for a Transaction we haven't mentioned.") self.rpc.meros.syncTransaction(self.transactions.txs[reqHash]) self.synced.add(reqHash) self.txs.remove(reqHash) if self.txs == set(): del self.blocks[-1] else: raise TestError("Unexpected message sent: " + msg.hex().upper()) #Verify the Blockchain. verifyBlockchain(self.rpc, self.merit.blockchain) #Verify the Transactions. if self.transactions is not None: verifyTransactions(self.rpc, self.transactions) if self.settings["playback"]: #Playback their messages. self.rpc.meros.sync.playback()
def HundredSeventySevenTest(rpc: RPC) -> None: #Grab the keys. blsPrivKey: PrivateKey = PrivateKey( bytes.fromhex(rpc.call("personal", "getMiner"))) blsPubKey: PublicKey = blsPrivKey.toPublicKey() #Faux Blockchain used to calculate the difficulty. blockchain: Blockchain = Blockchain() #Mine 8 Blocks. #The first creates the initial Data. #The next 6 pop it from the Epochs. #One more is to verify the next is popped as well. for b in range(0, 8): template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", [blsPubKey.serialize().hex()]) template["header"] = bytes.fromhex(template["header"]) header: BlockHeader = BlockHeader( 0, template["header"][4:36], template["header"][36:68], int.from_bytes(template["header"][68:72], byteorder="little"), template["header"][72:76], template["header"][76:108], 0, int.from_bytes(template["header"][-4:], byteorder="little")) if b == 0: header.newMiner = True header.minerKey = blsPubKey.serialize() else: header.newMiner = False header.minerNick = 0 if header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") header.mine(blsPrivKey, blockchain.difficulty()) #Sleep for just over two thirty-second cycles to make sure we can connect to the node. sleep(65) rpc.meros.liveConnect(blockchain.blocks[-1].header.hash) blockchain.add(Block(header, BlockBody())) rpc.call("merit", "publishBlock", [ template["id"], (template["header"] + header.proof.to_bytes(4, byteorder="little") + header.signature).hex() ]) if rpc.meros.live.recv() != rpc.meros.liveBlockHeader(header): raise TestError("Meros didn't broadcast the BlockHeader.") #If there's supposed to be a Mint, verify Meros claimed it. if b >= 6: #Artificially create the Mint since the Blockchain won't create one without a recreated BlockBody. #It's faster to create a faux Mint than to handle the BlockBodies. mint: Mint = Mint(header.hash, [(0, 50000)]) claim: Claim = Claim([(mint, 0)], bytes(32)) claim.sign(blsPrivKey) if rpc.meros.live.recv()[0:-80] != (MessageType.Claim.toByte() + claim.serialize())[0:-80]: raise TestError("Meros didn't claim its Mint.") if MessageType(rpc.meros.live.recv() [0]) != MessageType.SignedVerification: raise TestError("Meros didn't verify its Claim.") #Create the matching Data. data: Data = Data(blockchain.genesis, header.hash) #Make sure Meros broadcasts a valid Verification for it. verif: SignedVerification = SignedVerification(data.hash) verif.sign(0, blsPrivKey) if rpc.meros.live.recv() != rpc.meros.signedElement(verif): raise TestError("Meros didn't verify the Block's Data.") #Close the live connection so we don't have to worry about it being disconnected for inactivity. try: rpc.meros.live.connection.shutdown(socket.SHUT_RDWR) rpc.meros.live.connection.close() except OSError: pass sleep(3)
def test() -> None: recipient: ed25519.SigningKey = ed25519.SigningKey(b'\1' * 32) recipientPub: bytes = recipient.get_verifying_key().to_bytes() address: str = bech32_encode( "mr", convertbits(bytes([0]) + recipientPub, 8, 5)) otherRecipient: bytes = ed25519.SigningKey( b'\2' * 32).get_verifying_key().to_bytes() otherAddress: str = bech32_encode( "mr", convertbits(bytes([0]) + otherRecipient, 8, 5)) #Create a Send. send: Send = Send.fromJSON(vectors["send"]) if rpc.meros.liveTransaction(send) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Send.") if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros considered an unconfirmed Transaction's outputs as UTXOs." ) verify(rpc, send.hash) #Finalize the parent. for _ in range(6): mineBlock(rpc) #Spend it. spendingSend: Send = Send.fromJSON(vectors["spendingSend"]) if rpc.meros.liveTransaction(spendingSend) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Send.") verify(rpc, spendingSend.hash) if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros didn't consider a verified Transaction's inputs as spent." ) if rpc.call("transactions", "getUTXOs", {"address": otherAddress}) != [ { "hash": spendingSend.hash.hex().upper(), "nonce": 0 } ]: raise TestError( "Meros didn't consider a verified Transaction's outputs as UTXOs." ) #Unverify the spending Send. This would also unverify the parent if it wasn't finalized. #This is done via causing a Merit Removal. #Uses two competing Datas to not change the Send's status to competing. datas: List[Data] = [Data(bytes(32), recipientPub)] for _ in range(2): datas.append(Data(datas[0].hash, datas[-1].hash)) for data in datas: data.sign(recipient) data.beat(SpamFilter(5)) if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Data.") verify(rpc, data.hash, mr=(datas[-1].hash == data.hash)) #Verify the MeritRemoval happened and the spending Send is no longer verified. #These first two checks are more likely to symbolize a failure in testing methodology than Meros. if not rpc.call("merit", "getMerit", {"nick": 0})["malicious"]: raise TestError("Meros didn't create a Merit Removal.") if not rpc.call("consensus", "getStatus", {"hash": send.hash.hex()})["verified"]: raise TestError("Finalized Transaction became unverified.") if rpc.call("consensus", "getStatus", {"hash": spendingSend.hash.hex()})["verified"]: raise TestError( "Meros didn't unverify a Transaction which is currently below the required threshold." ) #Even after unverification, since the Transaction still exists, the input shouldn't be considered a UTXO. if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros didn't consider a unverified yet existing Transaction's inputs as spent." ) #That said, its outputs should no longer be considered a UTXO. if rpc.call("transactions", "getUTXOs", {"address": otherAddress}) != []: raise TestError( "Meros considered a unverified Transaction's outputs as UTXOs." ) raise SuccessError()
def verify(rpc: RPC, tx: bytes) -> None: sv: SignedVerification = SignedVerification(tx) sv.sign(0, PrivateKey(0)) if rpc.meros.signedElement(sv) != rpc.meros.live.recv(): raise TestError("Meros didn't send back a Verification.")
def HundredSixBlockElementsTest( rpc: RPC ) -> None: #Load the vectors. file: IO[Any] = open("e2e/Vectors/Consensus/HundredSix/BlockElements.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() #Blockchain. Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() #Transactions. transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) #Parse the Blocks from the vectors. blocks: List[Block] = [] for block in vectors["blocks"]: blocks.append(Block.fromJSON(block)) for block in blocks: #Handshake with the node. rpc.meros.liveConnect(blockchain.blocks[0].header.hash) rpc.meros.syncConnect(blockchain.blocks[0].header.hash) #Send the Block. rpc.meros.liveBlockHeader(block.header) #Flag of if the Block's Body synced. doneSyncing: bool = False #Handle sync requests. reqHash: bytes = bytes() while True: if doneSyncing: #Sleep for a second so Meros handles the Block. sleep(1) #Try receiving from the Live socket, where Meros sends keep-alives. try: if len(rpc.meros.live.recv()) != 0: raise Exception() except TestError: #Verify the node didn't crash. try: if rpc.call("merit", "getHeight") != 1: raise Exception() except Exception: raise TestError("Node crashed after being sent a malformed Element.") #Since the node didn't crash, break out of this loop to trigger the next test case. break except Exception: raise TestError("Meros sent a keep-alive.") msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.BlockBodyRequest: reqHash = msg[1 : 33] if reqHash != block.header.hash: raise TestError("Meros asked for a Block Body that didn't belong to the Block we just sent it.") #Send the BlockBody. rpc.meros.blockBody(block) if len(block.body.packets) == 0: doneSyncing = True elif MessageType(msg[0]) == MessageType.SketchHashRequests: if not block.body.packets: raise TestError("Meros asked for Verification Packets from a Block without any.") reqHash = msg[1 : 33] if reqHash != block.header.hash: raise TestError("Meros asked for Verification Packets that didn't belong to the Block we just sent it.") #Create a lookup of hash to packets. packets: Dict[int, VerificationPacket] = {} for packet in block.body.packets: packets[Sketch.hash(block.header.sketchSalt, packet)] = packet #Look up each requested packet and respond accordingly. for h in range(int.from_bytes(msg[33 : 37], byteorder="big")): sketchHash: int = int.from_bytes(msg[37 + (h * 8) : 45 + (h * 8)], byteorder="big") if sketchHash not in packets: raise TestError("Meros asked for a non-existent Sketch Hash.") rpc.meros.packet(packets[sketchHash]) doneSyncing = True elif MessageType(msg[0]) == MessageType.TransactionRequest: reqHash = msg[1 : 33] if reqHash not in transactions.txs: raise TestError("Meros asked for a non-existent Transaction.") rpc.meros.syncTransaction(transactions.txs[reqHash]) else: raise TestError("Unexpected message sent: " + msg.hex().upper()) #Reset the node. rpc.reset()
def WatchWalletTest(rpc: RPC) -> None: #Keys to send funds to later. keys: List[bytes] = [ ed25519.SigningKey(i.to_bytes(1, "little") * 32).get_verifying_key().to_bytes() for i in range(5) ] #Backup the Mnemonic so we can independently derive this data and verify it. mnemonic: str = rpc.call("personal", "getMnemonic") #Node's Wallet's keys. nodeKeys: List[bytes] = [getPublicKey(mnemonic, "", i) for i in range(4)] nodeAddresses: List[str] = [getAddress(mnemonic, "", i) for i in range(4)] #Convert this to a WatchWallet node. account: Dict[str, Any] = rpc.call("personal", "getAccount") rpc.call("personal", "setAccount", account) if rpc.call("personal", "getAccount") != account: raise TestError("Meros set a different account.") #Verify it has the correct initial address. if rpc.call("personal", "getAddress") != nodeAddresses[0]: raise TestError("WatchWallet has an incorrect initial address.") #Load the vectors. #This test requires 3 Claims be available. vectors: Dict[str, Any] with open("e2e/Vectors/RPC/Personal/WatchWallet.json", "r") as file: vectors = json.loads(file.read()) transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) #The order of the Claims isn't relevant to this test. claims: List[Claim] = [] for tx in transactions.txs.values(): claims.append(Claim.fromTransaction(tx)) def test() -> None: #Send to it. sends: List[bytes] = [createSend(rpc, claims[0], nodeKeys[0])] verify(rpc, sends[-1]) #Test the most basic template possible. checkTemplate( rpc, mnemonic, { "outputs": [{ "address": bech32_encode("mr", convertbits( bytes([0]) + keys[0], 8, 5)), "amount": "1" }] }, [{ "hash": sends[-1].hex().upper(), "nonce": 0, "change": False, "index": 0 }], [{ "key": keys[0].hex().upper(), "amount": "1" }, { "key": getChangePublicKey(mnemonic, "", 0).hex().upper(), "amount": str(claims[0].amount - 1) }]) #Verify it has the correct next address. if rpc.call("personal", "getAddress") != nodeAddresses[1]: raise TestError("WatchWallet has an incorrect next address.") #Send to it. sends.append(createSend(rpc, claims[1], nodeKeys[1])) verify(rpc, sends[-1]) #Get and send to one more, yet don't verify it yet. if rpc.call("personal", "getAddress") != nodeAddresses[2]: raise TestError("WatchWallet has an incorrect next next address.") sends.append(createSend(rpc, claims[2], nodeKeys[2])) #Verify it can get UTXOs properly. if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs( [{ "address": getAddress(mnemonic, "", i), "hash": sends[i].hex().upper(), "nonce": 0 } for i in range(2)]): raise TestError("WatchWallet Meros couldn't get its UTXOs.") #Verify the third Send. verify(rpc, sends[-1]) #Close the sockets for now. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() #Verify getUTXOs again. Redundant thanks to the extensive getUTXO testing elsewhere, yet valuable. if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs( [{ "address": getAddress(mnemonic, "", i), "hash": sends[i].hex().upper(), "nonce": 0 } for i in range(3)]): raise TestError("WatchWallet Meros couldn't get its UTXOs.") #Verify it can craft a Transaction Template properly. claimsAmount: int = sum(claim.amount for claim in claims) amounts: List[int] = [ claimsAmount // 4, claimsAmount // 4, claimsAmount // 5 ] amounts.append(claimsAmount - sum(amounts)) req: Dict[str, Any] = { "outputs": [{ "address": bech32_encode("mr", convertbits(bytes([0]) + keys[0], 8, 5)), "amount": str(amounts[0]) }, { "address": bech32_encode("mr", convertbits(bytes([0]) + keys[1], 8, 5)), "amount": str(amounts[1]) }, { "address": bech32_encode("mr", convertbits(bytes([0]) + keys[2], 8, 5)), "amount": str(amounts[2]) }] } inputs: List[Dict[str, Any]] = [{ "hash": sends[i].hex().upper(), "nonce": 0, "change": False, "index": i } for i in range(3)] outputs: List[Dict[str, Any]] = [{ "key": keys[i].hex().upper(), "amount": str(amounts[i]) } for i in range(3)] + [{ "key": getChangePublicKey(mnemonic, "", 0).hex().upper(), "amount": str(amounts[-1]) }] checkTemplate(rpc, mnemonic, req, inputs, outputs) #Specify only to use specific addresses and verify Meros does so. req["from"] = [nodeAddresses[1], nodeAddresses[2]] #Correct the amounts so this is feasible. del req["outputs"][-1] del outputs[-2] #Remove the change output amount and actual output amount. for _ in range(2): del amounts[-1] claimsAmount -= claims[-1].amount #Correct the change output. outputs[-1]["amount"] = str(claimsAmount - sum(amounts)) del inputs[0] checkTemplate(rpc, mnemonic, req, inputs, outputs) del req["from"] #Use the change address in question and verify the next template uses the next change address. #This is done via creating a Send which doesn't spend all of inputs value. #Also tests Meros handles Sends, and therefore templates, which don't use all funds. change: bytes = getChangePublicKey(mnemonic, "", 0) #Convert to a Wallet in order to do so. rpc.call("personal", "setWallet", {"mnemonic": mnemonic}) send: Dict[str, Any] = rpc.call( "transactions", "getTransaction", { "hash": rpc.call( "personal", "send", { "outputs": [{ "address": bech32_encode( "mr", convertbits(bytes([0]) + change, 8, 5)), "amount": "1" }] }) }) #Convert back. rpc.call("personal", "setAccount", account) #Reconnect. sleep(65) rpc.meros.liveConnect(Blockchain().blocks[0].header.hash) rpc.meros.syncConnect(Blockchain().blocks[0].header.hash) #Verify the Send so the Wallet doesn't lose Meros from consideration. verify(rpc, bytes.fromhex(send["hash"])) #Verify the Send's accuracy. if len(send["inputs"]) != 1: raise TestError("Meros used more inputs than neccessary.") if send["outputs"] != [ { "key": change.hex().upper(), "amount": "1" }, #Uses the existing, unused, change address as change. #While this Transaction will make it used, that isn't detected. #This isn't worth programming around due to the lack of implications except potentially minor metadata. { "key": change.hex().upper(), "amount": str(claims[0].amount - 1) } ]: raise TestError("Send outputs weren't as expected.") if rpc.call("personal", "getTransactionTemplate", req)["outputs"][-1]["key"] != getChangePublicKey( mnemonic, "", 1).hex().upper(): raise TestError("Meros didn't move to the next change address.") #Specify an explicit change address. req["change"] = nodeAddresses[3] if rpc.call("personal", "getTransactionTemplate", req)["outputs"][-1]["key"] != nodeKeys[3].hex().upper(): raise TestError( "Meros didn't handle an explicitly set change address.") #Verify RPC methods which require the private key error properly. #Tests via getMnemonic and data. try: rpc.call("personal", "getMnemonic") raise TestError() except Exception as e: if str(e) != "-3 This is a WatchWallet node; no Mnemonic is set.": raise TestError( "getMnemonic didn't error as expected when Meros didn't have a Wallet." ) try: rpc.call("personal", "data", {"data": "abc"}) raise TestError() except Exception as e: if str(e) != "-3 This is a WatchWallet node; no Mnemonic is set.": raise TestError( "data didn't error as expected when Meros didn't have a Wallet." ) #Also test getMeritHolderKey, as no Merit Holder key should exist. try: rpc.call("personal", "getMeritHolderKey") raise TestError() except Exception as e: if str( e ) != "-3 Node is running as a WatchWallet and has no Merit Holder.": raise TestError( "data didn't error as expected when Meros didn't have a Wallet." ) #Try calling getTransactionTemplate spending way too much Meros. try: rpc.call( "personal", "getTransactionTemplate", { "outputs": [{ "address": bech32_encode("mr", convertbits(bytes([0]) + keys[0], 8, 5)), "amount": str(claimsAmount * 100) }] }) raise TestError() except Exception as e: if str(e) != "1 Wallet doesn't have enough Meros.": raise TestError( "Meros didn't error as expected when told to spend more Meros than it has." ) #Try calling getTransactionTemplate with no outputs. try: rpc.call("personal", "getTransactionTemplate", {"outputs": []}) raise TestError() except Exception as e: if str(e) != "-3 No outputs were provided.": raise TestError( "Meros didn't error as expected when told to create a template with no outputs." ) #Try calling getTransactionTemplate with a 0 value output. try: rpc.call( "personal", "getTransactionTemplate", { "outputs": [{ "address": bech32_encode("mr", convertbits(bytes([0]) + keys[0], 8, 5)), "amount": "0" }] }) raise TestError() except Exception as e: if str(e) != "-3 0 value output was provided.": raise TestError( "Meros didn't error as expected when told to create a template with a 0 value output." ) #Use a late enough block we can instantly verify transactions. Liver(rpc, vectors["blockchain"], transactions, {50: test}).live()
def restOfTest() -> None: #Move expected into scope. expected: str = getAddress(mnemonic, password, 0) #Send to the new address, then call getAddress again. Verify a new address appears. last: Send = createSend( rpc, Claim.fromTransaction(iter(transactions.txs.values()).__next__()), expected) hashes: List[bytes] = [last.hash] expected = getAddress(mnemonic, password, 1) if rpc.call("personal", "getAddress") != expected: raise TestError( "Meros didn't move to the next address once the existing one was used." ) #Send to the new unused address, spending the funds before calling getAddress again. #Checks address usage isn't defined as having an UTXO, yet rather any TXO. #Also confirm the spending TX with full finalization before checking. #Ensures the TXO isn't unspent by any definition. last = createSend(rpc, last, expected) hashes.append(last.hash) #Spending TX. send: Send = Send([(hashes[-1], 0)], [(bytes(32), 1)]) send.signature = ed.sign(b"MEROS" + send.hash, getPrivateKey(mnemonic, password, 1)) send.beat(SpamFilter(3)) if rpc.meros.liveTransaction(send) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back the spending Send.") hashes.append(send.hash) #In order to finalize, we need to mine 6 Blocks once this Transaction and its parent have Verifications. for txHash in hashes: sv: SignedVerification = SignedVerification(txHash) sv.sign(0, PrivateKey(0)) if rpc.meros.signedElement(sv) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Verification.") #Close the sockets while we mine. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() #Mine these to the Wallet on the node so we can test getMeritHolderNick. privKey: PrivateKey = PrivateKey( bytes.fromhex(rpc.call("personal", "getMeritHolderKey"))) blockchain: Blockchain = Blockchain.fromJSON(vectors["blockchain"]) for _ in range(6): template: Dict[str, Any] = rpc.call( "merit", "getBlockTemplate", {"miner": privKey.toPublicKey().serialize().hex()}) proof: int = -1 tempHash: bytes = bytes() tempSignature: bytes = bytes() while ((proof == -1) or ((int.from_bytes(tempHash, "little") * (blockchain.difficulty() * 11 // 10)) > int.from_bytes( bytes.fromhex("FF" * 32), "little"))): proof += 1 tempHash = RandomX( bytes.fromhex(template["header"]) + proof.to_bytes(4, "little")) tempSignature = privKey.sign(tempHash).serialize() tempHash = RandomX(tempHash + tempSignature) rpc.call( "merit", "publishBlock", { "id": template["id"], "header": template["header"] + proof.to_bytes(4, "little").hex() + tempSignature.hex() }) blockchain.add( Block.fromJSON( rpc.call("merit", "getBlock", {"block": len(blockchain.blocks)}))) #Verify a new address is returned. expected = getAddress(mnemonic, password, 2) if rpc.call("personal", "getAddress") != expected: raise TestError( "Meros didn't move to the next address once the existing one was used." ) #Get a new address after sending to the address after it. #Use both, and then call getAddress. #getAddress should detect X is used, move to Y, detect Y is used, and move to Z. #It shouldn't assume the next address after an used address is unused. #Actually has two Ys as one iteration of the code only ran for the next address; not all future addresses. #Send to the next next addresses. for i in range(2): addy: str = getAddress(mnemonic, password, 3 + i) #Reopen the sockets. This isn't done outside of the loop due to the time deriving the final address can take. #Due to how slow the reference Python code is, it is necessary to redo the socket connections. sleep(65) rpc.meros.liveConnect(Blockchain().blocks[0].header.hash) rpc.meros.syncConnect(Blockchain().blocks[0].header.hash) last = createSend(rpc, last, addy) if MessageType(rpc.meros.live.recv() [0]) != MessageType.SignedVerification: raise TestError( "Meros didn't create and broadcast a SignedVerification for this Send." ) if i == 0: #Close them again. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() #Verify getAddress returns the existing next address. if rpc.call("personal", "getAddress") != expected: raise TestError( "Sending to the address after this address caused Meros to consider this address used." ) #Send to the next address. last = createSend(rpc, last, expected) if MessageType( rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError( "Meros didn't create and broadcast a SignedVerification for this Send." ) #Verify getAddress returns the address after the next next addresses. expected = getAddress(mnemonic, password, 5) if rpc.call("personal", "getAddress") != expected: raise TestError( "Meros didn't return the correct next address after using multiple addresses in a row." ) #Now that we have mined a Block as part of this test, ensure the Merit Holder nick is set. if rpc.call("personal", "getMeritHolderNick") != 1: raise TestError( "Merit Holder nick wasn't made available despite having one.") #Sanity check off Mnemonic. if rpc.call("personal", "getMnemonic") != mnemonic: raise TestError("getMnemonic didn't return the correct Mnemonic.") #Existing values used to test getMnemonic/getMeritHolderKey/getMeritHolderNick/getAccount/getAddress consistency. existing: Dict[str, Any] = { #Should be equal to the mnemonic variable, which is verified in a check above. "getMnemonic": rpc.call("personal", "getMnemonic"), "getMeritHolderKey": rpc.call("personal", "getMeritHolderKey"), "getMeritHolderNick": rpc.call("personal", "getMeritHolderNick"), "getAccount": rpc.call("personal", "getAccount"), #Should be equal to expected, which is also verified in a check above. "getAddress": rpc.call("personal", "getAddress") } #Set a new seed and verify the Merit Holder nick is cleared. rpc.call("personal", "setWallet") try: rpc.call("personal", "getMeritHolderNick") raise TestError("") except TestError as e: if str( e ) != "-2 Wallet doesn't have a Merit Holder nickname assigned.": raise TestError( "getMeritHolderNick returned something or an unexpected error when a new Mnemonic was set." ) #Set back the old seed and verify consistency. rpc.call("personal", "setWallet", { "mnemonic": mnemonic, "password": password }) for method in existing: if rpc.call("personal", method) != existing[method]: raise TestError( "Setting an old seed caused the WalletDB to improperly reload." ) #Verify calling getAddress returns the expected address. if rpc.call("personal", "getAddress") != expected: raise TestError( "Meros returned an address that wasn't next after reloading the seed." ) #Reboot the node and verify consistency. rpc.quit() sleep(3) rpc.meros = Meros(rpc.meros.db, rpc.meros.tcp, rpc.meros.rpc) for method in existing: if rpc.call("personal", method) != existing[method]: raise TestError( "Rebooting the node caused the WalletDB to improperly reload." ) #Used so Liver doesn't run its own post-test checks. #Since we added our own Blocks, those will fail. raise SuccessError()
def TElementTest(rpc: RPC) -> None: #BLS key. blsPrivKey: PrivateKey = PrivateKey( blake2b(b'\0', digest_size=32).digest()) blsPubKey: str = blsPrivKey.toPublicKey().serialize().hex() #Blocks. file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r") blocks: List[Dict[str, Any]] = json.loads(file.read()) file.close() #Merit. merit: Merit = Merit() #Handshake with the node. rpc.meros.liveConnect(merit.blockchain.blocks[0].header.hash) rpc.meros.syncConnect(merit.blockchain.blocks[0].header.hash) #Send the first Block. block: Block = Block.fromJSON(blocks[0]) merit.blockchain.add(block) rpc.meros.liveBlockHeader(block.header) #Handle sync requests. reqHash: bytes = bytes() while True: msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.BlockBodyRequest: reqHash = msg[1:33] if reqHash != block.header.hash: raise TestError( "Meros asked for a Block Body that didn't belong to the Block we just sent it." ) #Send the BlockBody. rpc.meros.blockBody(block) break else: raise TestError("Unexpected message sent: " + msg.hex().upper()) if MessageType(rpc.meros.live.recv()[0]) != MessageType.BlockHeader: raise TestError( "Meros didn't broadcast the Block Header it just added.") #Create and transmit a DataDifficulty. dataDiff: SignedDataDifficulty = SignedDataDifficulty(0, 0, 0) dataDiff.sign(0, blsPrivKey) rpc.meros.signedElement(dataDiff) sleep(0.5) #Verify the block template has the DataDifficulty. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", [blsPubKey]) template["header"] = bytes.fromhex(template["header"]) if template["header"][36:68] != BlockHeader.createContents([], [dataDiff]): raise TestError("Block template doesn't have the Data Difficulty.") #Mine the Block. block = Block( BlockHeader( 0, block.header.hash, BlockHeader.createContents([], [dataDiff]), 1, template["header"][-43:-39], BlockHeader.createSketchCheck(template["header"][-43:-39], []), 0, int.from_bytes(template["header"][-4:], byteorder="big"), ), BlockBody([], [dataDiff], dataDiff.signature)) if block.header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") if block.body.serialize(block.header.sketchSalt) != bytes.fromhex( template["body"]): raise TestError("Failed to recreate the body.") block.mine(blsPrivKey, merit.blockchain.difficulty()) merit.blockchain.add(block) #Publish it. rpc.call("merit", "publishBlock", [ template["id"], (template["header"] + block.header.proof.to_bytes(4, byteorder="big") + block.header.signature + block.body.serialize(block.header.sketchSalt)).hex() ]) #Create and transmit a new DataDifficulty. dataDiff = SignedDataDifficulty(3, 1, 0) dataDiff.sign(0, blsPrivKey) rpc.meros.signedElement(dataDiff) sleep(0.5) #Verify the block template has the DataDifficulty. template = rpc.call("merit", "getBlockTemplate", [blsPubKey]) template["header"] = bytes.fromhex(template["header"]) if template["header"][36:68] != BlockHeader.createContents([], [dataDiff]): raise TestError("Block template doesn't have the new Data Difficulty.") #Create and transmit a new DataDifficulty reusing an existing nonce. signatures: List[Signature] = [dataDiff.signature] dataDiff = SignedDataDifficulty(4, 1, 0) dataDiff.sign(0, blsPrivKey) signatures.append(dataDiff.signature) rpc.meros.signedElement(dataDiff) sleep(0.5) #Verify the block template has a MeritRemoval. mr: MeritRemoval = MeritRemoval(SignedDataDifficulty(3, 1, 0), SignedDataDifficulty(4, 1, 0), False) template = rpc.call("merit", "getBlockTemplate", [blsPubKey]) template["header"] = bytes.fromhex(template["header"]) if template["header"][36:68] != BlockHeader.createContents([], [mr]): raise TestError("Block template doesn't have the Merit Removal.") #Mine the Block. block = Block( BlockHeader( 0, block.header.hash, BlockHeader.createContents([], [mr]), 1, template["header"][-43:-39], BlockHeader.createSketchCheck(template["header"][-43:-39], []), 0, int.from_bytes(template["header"][-4:], byteorder="big")), BlockBody([], [mr], Signature.aggregate(signatures))) if block.header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") if block.body.serialize(block.header.sketchSalt) != bytes.fromhex( template["body"]): raise TestError("Failed to recreate the body.") block.mine(blsPrivKey, merit.blockchain.difficulty()) merit.blockchain.add(block) #Publish it. rpc.call("merit", "publishBlock", [ template["id"], (template["header"] + block.header.proof.to_bytes(4, byteorder="big") + block.header.signature + block.body.serialize(block.header.sketchSalt)).hex() ]) #Verify the Blockchain. verifyBlockchain(rpc, merit.blockchain)
def HundredFiftyFiveTest(rpc: RPC) -> None: edPrivKeys: List[ed25519.SigningKey] = [ ed25519.SigningKey(b'\0' * 32), ed25519.SigningKey(b'\1' * 32) ] edPubKeys: List[ed25519.VerifyingKey] = [ edPrivKeys[0].get_verifying_key(), edPrivKeys[1].get_verifying_key() ] blsPrivKey: PrivateKey = PrivateKey( bytes.fromhex(rpc.call("personal", "getMeritHolderKey"))) blsPubKey: bytes = blsPrivKey.toPublicKey().serialize() blockchain: Blockchain = Blockchain() dataFilter: SpamFilter = SpamFilter(5) #Handshake with the node. rpc.meros.liveConnect(blockchain.blocks[0].header.hash) rpc.meros.syncConnect(blockchain.blocks[0].header.hash) #Call getBlockTemplate just to get an ID. #Skips the need to write a sync loop for the BlockBody. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", {"miner": blsPubKey.hex()}) #Mine a Block. block = Block( BlockHeader(0, blockchain.blocks[0].header.hash, bytes(32), 0, bytes(4), bytes(32), blsPubKey, blockchain.blocks[0].header.time + 1200, 0), BlockBody()) block.mine(blsPrivKey, blockchain.difficulty()) blockchain.add(block) rpc.call("merit", "publishBlock", { "id": template["id"], "header": block.header.serialize().hex() }) if MessageType(rpc.meros.live.recv()[0]) != MessageType.BlockHeader: raise TestError("Meros didn't broadcast the Block we just published.") #Ignore the Verification for the Block's Data. if MessageType(rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError( "Meros didn't send the SignedVerification for the Block's Data.") datas: List[Data] = [ Data(bytes(32), edPubKeys[0].to_bytes()), Data(bytes(32), edPubKeys[1].to_bytes()) ] for d in range(len(datas)): datas[d].sign(edPrivKeys[d]) datas[d].beat(dataFilter) #Send the Data and verify Meros sends it back. if rpc.meros.liveTransaction(datas[d]) != rpc.meros.live.recv(): raise TestError("Meros didn't send back the Data.") #Verify Meros sends back a Verification. res: bytes = rpc.meros.live.recv() if MessageType(res[0]) != MessageType.SignedVerification: raise TestError("Meros didn't send a SignedVerification.") verif: SignedVerification = SignedVerification(datas[d].hash) verif.sign(0, blsPrivKey) if res[1:] != verif.signedSerialize(): raise TestError( "Meros didn't send the correct SignedVerification.")
def verifyMentionedWon() -> None: if not rpc.call("consensus", "getStatus", {"hash": datas[2].hash.hex()})["verified"]: raise TestError("Meros didn't verify the only Transaction on chain which has finalized.")
def HundredTwentyFourTest(rpc: RPC) -> None: file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r") vectors: List[Dict[str, Any]] = json.loads(file.read()) file.close() blockchain: Blockchain = Blockchain() for i in range(2): blockchain.add(Block.fromJSON(vectors[i])) rpc.meros.liveConnect(blockchain.blocks[0].header.hash) rpc.meros.syncConnect(blockchain.blocks[0].header.hash) def handleSync() -> None: reqHash: bytes = bytes() bH: int = 0 bB: int = 1 while True: if bB == 3: break msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.BlockListRequest: reqHash = msg[3:35] for b in range(len(blockchain.blocks)): if blockchain.blocks[b].header.hash == reqHash: blockList: List[bytes] = [] for bl in range(1, msg[2] + 2): if msg[1] == 0: if b - bl < 0: break blockList.append( blockchain.blocks[b - bl].header.hash) elif msg[1] == 1: blockList.append( blockchain.blocks[b + bl].header.hash) else: raise TestError( "Meros asked for an invalid direction in a BlockListRequest." ) if blockList == []: rpc.meros.dataMissing() break rpc.meros.blockList(blockList) break if b == len(blockchain.blocks): rpc.meros.dataMissing() elif MessageType(msg[0]) == MessageType.BlockHeaderRequest: reqHash = msg[1:33] if reqHash != blockchain.blocks[2 - bH].header.hash: raise TestError( "Meros asked for a Block Header that didn't belong to the next Block." ) #Send the BlockHeader. rpc.meros.syncBlockHeader(blockchain.blocks[2 - bH].header) bH += 1 elif MessageType(msg[0]) == MessageType.BlockBodyRequest: reqHash = msg[1:33] if reqHash != blockchain.blocks[bB].header.hash: raise TestError( "Meros asked for a Block Body that didn't belong to the next Block." ) rpc.meros.blockBody(blockchain.blocks[bB]) bB += 1 else: raise TestError("Unexpected message sent: " + msg.hex().upper()) #Verify the Blockchain. verifyBlockchain(rpc, blockchain) #Send another Handshake with the latest block as the tip. rpc.meros.live.send( MessageType.Handshake.toByte() + (254).to_bytes(1, "little") + (254).to_bytes(1, "little") + b'\0\0\0' + blockchain.last(), False) #Verify Meros responds with their tail (the genesis). if rpc.meros.live.recv() != MessageType.BlockchainTail.toByte( ) + blockchain.blocks[0].header.hash: raise TestError("Meros didn't respond with the genesis.") #Handle the sync. handleSync() #Reset Meros and do the same with Syncing. rpc.reset() rpc.meros.syncConnect(blockchain.blocks[0].header.hash) rpc.meros.sync.send( MessageType.Syncing.toByte() + (254).to_bytes(1, "little") + (254).to_bytes(1, "little") + b'\0\0\0' + blockchain.last(), False) if rpc.meros.sync.recv() != MessageType.BlockchainTail.toByte( ) + blockchain.blocks[0].header.hash: raise TestError("Meros didn't respond with the genesis.") handleSync()
def BusyTest(meros: Meros) -> None: #Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() #Handshake with the node. meros.syncConnect(blockchain.blocks[0].header.hash) #Create two new server sockets. def createServerSocket() -> socket.socket: result: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) result.bind(("127.0.0.1", 0)) result.listen(2) return result busyServer: socket.socket = createServerSocket() server: socket.socket = createServerSocket() #Receive Syncing until Meros asks for peers. while True: res = meros.sync.recv(True) if MessageType(res[0]) == MessageType.Syncing: meros.sync.send(MessageType.BlockchainTail.toByte() + blockchain.blocks[0].header.hash) elif MessageType(res[0]) == MessageType.PeersRequest: break #Craft a Peers message of our own server. meros.sync.send(MessageType.Peers.toByte() + bytes.fromhex("017F000001") + busyServer.getsockname()[1].to_bytes(2, "little")) #Use select to obtain a non-blocking accept. busy: int = 0 buf: bytes for _ in select.select([busyServer], [], [], 5000): #Accept a new connection. client, _ = busyServer.accept() #Verify Meros's Handshake. buf = client.recv(38) if MessageType( buf[0]) not in {MessageType.Handshake, MessageType.Syncing}: busyServer.close() raise TestError( "Meros didn't start its connection with a Handshake.") if buf[1:] != ( (254).to_bytes(1, "little") + (254).to_bytes(1, "little") + (128).to_bytes(1, "little") + meros.tcp.to_bytes(2, "little") + blockchain.blocks[0].header.hash): busyServer.close() raise TestError("Meros had an invalid Handshake.") #Send back Busy. client.send(MessageType.Busy.toByte() + bytes.fromhex("017F000001") + server.getsockname()[1].to_bytes(2, "little")) busy += 1 if busy == 2: busyServer.close() break #Make sure Meros connects to the server we redirected to. with raises(SuccessError): for _ in select.select([server], [], [], 5000): #Accept a new connection. client, _ = server.accept() #Verify Meros's Handshake. buf = client.recv(38) if MessageType(buf[0]) not in { MessageType.Handshake, MessageType.Syncing }: server.close() raise TestError( "Meros didn't start its connection with a Handshake.") if buf[1:] != ( (254).to_bytes(1, "little") + (254).to_bytes(1, "little") + (128).to_bytes(1, "little") + meros.tcp.to_bytes(2, "little") + blockchain.blocks[0].header.hash): server.close() raise TestError("Meros had an invalid Handshake.") server.close() raise SuccessError( "Meros connected to the server we redirected it to with a Busy message." ) #Raise a TestError. busyServer.close() server.close() raise TestError("Meros didn't connect to the redirected server.")
def ULimitTest( #Required so a Meros node is spawned. #pylint: disable=unused-argument meros: Meros ) -> None: #Sleep 60 seconds so Meros can correct its FD count. sleep(60) #Solely used for the genesis Block hash. blockchain: Blockchain = Blockchain() #Create peers until Meros sends us busy. sockets: List[MerosSocket] = [] while True: #Only create live sockets to trigger new peers for each socket. try: sockets.append( MerosSocket(meros.tcp, 254, 254, True, blockchain.blocks[0].header.hash)) except BusyError as e: if e.handshake != (MessageType.Busy.toByte() + bytes(1)): raise TestError("Meros sent an invalid Busy message.") break #Trigger busy 32 more times to verify Meros doesn't still allocate file handles. for _ in range(32): try: MerosSocket(meros.tcp, 254, 254, True, blockchain.blocks[0].header.hash) except BusyError as e: if e.handshake != (MessageType.Busy.toByte() + bytes(1)): raise TestError("Meros sent an invalid Busy message.") continue raise TestError("Meros didn't send Busy despite being at capacity.") #Disconnect the last 50 sockets. for _ in range(50): sockets[-1].connection.shutdown(socket.SHUT_RDWR) sockets[-1].connection.close() del sockets[-1] #Send a Handshake over every remaining socket every 20 seconds for a minute. #Then Meros should update the amount of files it has open and accept 50 new sockets. for _ in range(3): for lSocket in sockets: lSocket.send(MessageType.Handshake.toByte() + (254).to_bytes(1, "little") + (254).to_bytes(1, "little") + b'\0\0\0' + blockchain.blocks[0].header.hash) sleep(20) #Connect 50 sockets and verify Meros doesn't think it's still at capacity. for _ in range(50): try: sockets.append( MerosSocket(meros.tcp, 254, 254, True, blockchain.blocks[0].header.hash)) except BusyError: raise TestError("Meros thought it was at capcity when it wasn't.") #Verify connecting one more socket returns Busy. try: MerosSocket(meros.tcp, 254, 254, True, blockchain.blocks[0].header.hash) except BusyError: return raise TestError("Meros accepted a socket despite being at capcity.")
def sendSends() -> None: for send in sends[:4]: if rpc.meros.liveTransaction(send) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast a Send.") if rpc.meros.signedElement(verif) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast a Verification.")