def HundredEightySevenTest( meros: Meros ) -> None: file: IO[Any] = open("e2e/Vectors/Merit/HundredEightySeven.json", "r") vectors: List[Dict[str, Any]] = json.loads(file.read()) file.close() meros.liveConnect(Blockchain().last()) meros.syncConnect(Blockchain().last()) block: Block = Block.fromJSON(vectors[0]) sent: bytes = meros.liveBlockHeader(block.header) if meros.sync.recv() != MessageType.BlockBodyRequest.toByte() + block.header.hash: raise TestError("Meros didn't request the matching BlockBody.") meros.blockBody(block) if meros.live.recv() != sent: raise TestError("Meros didn't broadcast a BlockHeader.") meros.liveBlockHeader(Block.fromJSON(vectors[1]).header) with raises(SuccessError): try: if len(meros.live.recv()) != 0: raise Exception() except TestError: sleep(1) if meros.process.poll() is not None: raise TestError("Node crashed trying to handle a BlockHeader which re-registers a key.") raise SuccessError("Node disconnected us after we sent a BlockHeader which re-registers a key.") except Exception: raise TestError("Meros didn't disconnect us after we sent a BlockHeader which re-registers a key; it also didn't crash.")
def TwoHundredFourTest(rpc: RPC) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Consensus/TwoHundredFour.json", "r") as file: vectors = json.loads(file.read()) #Instantiate a Blockchain to set the RandomX key. chain: Blockchain = Blockchain() blank: Block = Block.fromJSON(vectors["blank"]) if len(vectors["blocks"]) != 1: raise Exception( "Misread this test's vectors, creating an invalid test due to that." ) block: Block = Block.fromJSON(vectors["blocks"][0]) rpc.meros.liveConnect(chain.last()) rpc.meros.syncConnect(chain.last()) #Send a blank block so Meros acknowledges a holder. sentHeader: bytes = rpc.meros.liveBlockHeader(blank.header) rpc.meros.handleBlockBody(blank) if rpc.meros.live.recv() != sentHeader: raise TestError( "Meros didn't rebroadcast the header for a blank Block.") rpc.meros.liveBlockHeader(block.header) rpc.meros.handleBlockBody(block) msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) != MessageType.SketchHashRequests: raise TestError("Unexpected message sent: " + msg.hex().upper()) if msg[1:33] != block.header.hash: raise TestError( "Meros asked for Verification Packets that didn't belong to the Block we just sent it." ) if int.from_bytes(msg[33:37], byteorder="little") != 1: raise TestError("Meros didn't ask for one VerificationPacket.") if int.from_bytes(msg[37:45], byteorder="little") != Sketch.hash( block.header.sketchSalt, block.body.packets[0]): raise TestError("Meros didn't ask for the VerificationPacket.") rpc.meros.packet(block.body.packets[0]) #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. sleep(1) if rpc.meros.process.poll() is not None: raise TestError( "Node crashed trying to handle a VerificationPacket with no holders." ) except Exception: raise TestError( "Meros didn't disconnect us after sending a VerificationPacket with no holders; it also didn't crash." )
def test() -> None: #Data. dataHash: str = rpc.call("personal", "data", {"data": "abc"}) #Read the initial Data. if MessageType(rpc.meros.live.recv()[0]) != MessageType.Data: raise TestError("Meros didn't broadcast the initial Data.") #Read the actual Data. serializedData: bytes = rpc.meros.live.recv() if serializedData[1:] != Data.fromJSON(rpc.call("transactions", "getTransaction", {"hash": dataHash})).serialize(): raise TestError("Meros didn't broadcast the created Data.") res: Any = rpc.call("network", "broadcast", {"transaction": dataHash}) if not (isinstance(res, bool) and res): raise TestError("Broadcast didn't return true.") if rpc.meros.live.recv() != serializedData: raise TestError("Meros didn't broadcast a Transaction when told to.") #Block. header: BlockHeader = Block.fromJSON(vectors["blockchain"][0]).header rpc.call("network", "broadcast", {"block": header.hash.hex()}) if rpc.meros.live.recv() != (MessageType.BlockHeader.toByte() + header.serialize()): raise TestError("Meros didn't broadcast the Blockheader.") #Data and Block. rpc.call("network", "broadcast", {"block": header.hash.hex(), "transaction": dataHash}) if rpc.meros.live.recv() != serializedData: raise TestError("Meros didn't broadcast a Transaction when told to.") if rpc.meros.live.recv() != (MessageType.BlockHeader.toByte() + header.serialize()): raise TestError("Meros didn't broadcast the Blockheader.") #Non-existent Transaction. try: rpc.call("network", "broadcast", {"transaction": bytes(32).hex()}) raise TestError() except TestError as e: if str(e) != "-2 Transaction not found.": raise TestError("Meros didn't error when told to broadcast a non-existent Transaction.") #Non-existent Block. try: rpc.call("network", "broadcast", {"block": bytes(32).hex()}) raise TestError() except TestError as e: if str(e) != "-2 Block not found.": raise TestError("Meros didn't error when told to broadcast a non-existent Block.") #Mint. try: rpc.call("network", "broadcast", {"transaction": mint.hex()}) raise TestError() except TestError as e: if str(e) != "-3 Transaction is a Mint.": raise TestError("Meros didn't error when told to broadcast a Mint.") #Genesis Block. try: rpc.call("network", "broadcast", {"block": Blockchain().blocks[0].header.hash.hex()}) raise TestError() except TestError as e: if str(e) != "-3 Block is the genesis Block.": raise TestError("Meros didn't error when told to broadcast the genesis Block.")
def MultiplePacketsTest(rpc: RPC) -> None: #Spawn a Blockchain just to set the RandomX key. _: Blockchain = Blockchain() vectors: Dict[str, Any] with open("e2e/Vectors/Merit/MultiplePackets.json", "r") as file: vectors = json.loads(file.read()) data: Data = Data.fromJSON(vectors["data"]) block: Block = Block.fromJSON(vectors["blockchain"][-1]) def sendDataAndBlock() -> None: #Send the Data. if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Meros didn't send back the Data.") rpc.meros.liveBlockHeader(block.header) rpc.meros.handleBlockBody(block) msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) != MessageType.SketchHashRequests: raise TestError("Meros didn't request the packets for this Block.") 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]) try: if MessageType( rpc.meros.live.recv()[0]) == MessageType.BlockHeader: raise TestError("Meros added the Block.") except Exception as e: if str(e) != "Meros added the Block.": raise SuccessError() with raises(SuccessError): Liver(rpc, vectors["blockchain"], callbacks={ 2: sendDataAndBlock }).live()
def TwoHundredThirtyFiveTest(rpc: RPC) -> None: blockchain: Blockchain = Blockchain() dataFilter: SpamFilter = SpamFilter(5) edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() #Mine one Block to the node. blsPrivKey: PrivateKey = PrivateKey( bytes.fromhex(rpc.call("personal", "getMiner"))) blsPubKey: bytes = blsPrivKey.toPublicKey().serialize() #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", [blsPubKey.hex()]) #Mine a Block. block = Block( BlockHeader(0, blockchain.blocks[0].header.hash, bytes(32), 1, 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", [template["id"], block.serialize().hex()]) #Send Meros a Data and receive its Verification to make sure it's verifying Transactions in the first place. data: Data = Data(bytes(32), edPubKey.to_bytes()) data.sign(edPrivKey) data.beat(dataFilter) rpc.meros.liveConnect(blockchain.blocks[0].header.hash) if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Meros didn't send back the Data.") if MessageType(rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError("Meros didn't send us its SignedVerification.") #Close our connection and mine 8 Blocks so its Merit is locked. rpc.meros.live.connection.close() for _ in range(8): block = Block( BlockHeader(0, blockchain.blocks[-1].header.hash, bytes(32), 1, bytes(4), bytes(32), 0, blockchain.blocks[-1].header.time + 1200, 0), BlockBody()) #Reusing its key is fine as mining doesn't count as participation. block.mine(blsPrivKey, blockchain.difficulty()) blockchain.add(block) #Sleep 30 seconds to make sure Meros noted we disconnected, and then reconnect. sleep(30) rpc.meros.liveConnect(blockchain.blocks[0].header.hash) rpc.meros.syncConnect(blockchain.blocks[0].header.hash) #Sync the Blocks. for b in range(8): header: bytes = rpc.meros.liveBlockHeader(blockchain.blocks[b + 2].header) if rpc.meros.sync.recv() != (MessageType.BlockBodyRequest.toByte() + blockchain.blocks[b + 2].header.hash): raise TestError("Meros didn't request the BlockBody.") rpc.meros.blockBody(blockchain.blocks[b + 2]) if rpc.meros.live.recv() != header: raise TestError("Meros didn't send back the header.") if MessageType( rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError("Meros didn't verify this Block's data.") #Verify its Merit is locked. #Theoretically, all code after this check is unecessary. #Meros verifies a Block's Data after updating its State. #Therefore, if the above last Block had its Data verified, this issue should be closed. #That said, the timing is a bit too tight for comfort. #Better safe than sorry. Hence why the code after this check exists. if rpc.call("merit", "getMerit", [0])["status"] != "Locked": raise TestError("Merit wasn't locked when it was supposed to be.") #Send it a Transaction and make sure Meros verifies it, despite having its Merit locked. data = Data(data.hash, edPubKey.to_bytes()) data.sign(edPrivKey) data.beat(dataFilter) if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Meros didn't send back the Data.") if MessageType(rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError("Meros didn't send us its SignedVerification.")
def HundredSixBlockElementsTest(rpc: RPC) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Consensus/HundredSix/BlockElements.json", "r") as file: vectors = json.loads(file.read()) #Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) 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) rpc.meros.handleBlockBody(block) #Flag of if the Block's Body synced. doneSyncing: bool = len(block.body.packets) == 0 #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.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]) 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 so we can test the next invalid Block. rpc.reset()
def finish(self, keepUnlocked: int, existing: Merit) -> Block: genesis: bytes = existing.blockchain.genesis prev: BlockHeader = existing.blockchain.blocks[-1].header diff: int = existing.blockchain.difficulty() #Create the signatures for every packet/element. signatures: List[Signature] = [] for packet in self.packets: for holder in packet.holders: verif: SignedVerification = SignedVerification( packet.hash, holder) verif.sign(holder, PrivateKey(holder)) signatures.append(verif.signature) for element in self.elements: signatures.append(signElement(element)) #Only add the Data Verification if: #1) We're supposed to make sure Merit Holders are always Unlocked #2) The last Block created a Data #3) The Merit Holder has Merit. if (keepUnlocked != 0) and (prev.last != genesis): #Create the Data from the last Block. blockData: Data = Data(genesis, prev.hash) #Create Verifications for said Data with every Private Key. #Ensures no one has their Merit locked. #pylint: disable=unnecessary-comprehension self.packets.append(VerificationPacket(blockData.hash, [])) for i in range(keepUnlocked): if ( #Miners who are just being created don't have Merit. ((i == (keepUnlocked - 1)) and (isinstance(self.minerID, PrivateKey))) or (existing.state.balances[i] == 0)): continue self.packets[-1].holders.append(i) verif: SignedVerification = SignedVerification( blockData.hash, i) verif.sign(i, PrivateKey(i)) signatures.append(verif.signature) #Remove this packet if there's no holders. if not self.packets[-1].holders: del self.packets[-1] #Set the aggregate. aggregate = Signature.aggregate(signatures) #Create the actual Block. minerID: Union[bytes, int] = 0 if isinstance(self.minerID, int): minerID = self.minerID else: minerID = self.minerID.toPublicKey().serialize() result: Block = Block( BlockHeader( 0, prev.hash, BlockHeader.createContents(self.packets, self.elements), len(self.packets), bytes(4), BlockHeader.createSketchCheck(bytes(4), self.packets), minerID, self.time), BlockBody(self.packets, self.elements, aggregate)) if isinstance(self.minerID, int): result.mine(PrivateKey(self.minerID), diff) else: result.mine(self.minerID, diff) return result
def HundredTwentyFourTest( rpc: RPC ) -> None: #Load the vectors. file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r") vectors: List[Dict[str, Any]] = json.loads(file.read()) file.close() #Blockchain. Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() #Parse the Blocks from the vectors. for i in range(2): blockchain.add(Block.fromJSON(vectors[i])) #Handshake with the node. rpc.meros.liveConnect(blockchain.blocks[0].header.hash) rpc.meros.syncConnect(blockchain.blocks[0].header.hash) def handleSync() -> None: #Handle sync requests. 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.") #Send the 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, "big") + (254).to_bytes(1, "big") + 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 its Blockchain's Tail.") #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, "big") + (254).to_bytes(1, "big") + 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 its Blockchain's Tail.") handleSync()
def EightyEightTest( rpc: RPC ) -> None: edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() blsPrivKey: PrivateKey = PrivateKey(0) blsPubKey: str = blsPrivKey.toPublicKey().serialize().hex() file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r") blocks: List[Dict[str, Any]] = json.loads(file.read()) file.close() merit: Merit = Merit() dataFilter: SpamFilter = SpamFilter(5) #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 two Datas. datas: List[Data] = [Data(bytes(32), edPubKey.to_bytes())] datas.append(Data(datas[0].hash, b"Hello there! General Kenobi.")) for data in datas: #Sign them and have them beat the spam filter. data.sign(edPrivKey) data.beat(dataFilter) #Transmit them. rpc.meros.liveTransaction(data) #Verify both. verifs: List[SignedVerification] = [ SignedVerification(datas[0].hash), SignedVerification(datas[1].hash) ] for verif in verifs: verif.sign(0, blsPrivKey) #Only transmit the second. rpc.meros.signedElement(verifs[1]) sleep(0.5) #Verify the block template has no verifications. if bytes.fromhex( rpc.call("merit", "getBlockTemplate", [blsPubKey])["header"] )[36 : 68] != bytes(32): raise TestError("Block template has Verification Packets.") #Transmit the first signed verification. rpc.meros.signedElement(verifs[0]) sleep(0.5) #Verify the block template has both verifications. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", [blsPubKey]) template["header"] = bytes.fromhex(template["header"]) packets: List[VerificationPacket] = [VerificationPacket(datas[0].hash, [0]), VerificationPacket(datas[1].hash, [0])] if template["header"][36 : 68] != BlockHeader.createContents(packets): raise TestError("Block template doesn't have both Verification Packets.") #Mine the Block. block = Block( BlockHeader( 0, block.header.hash, BlockHeader.createContents(packets), 1, template["header"][-43 : -39], BlockHeader.createSketchCheck(template["header"][-43 : -39], packets), 0, int.from_bytes(template["header"][-4:], byteorder="little") ), BlockBody( packets, [], Signature.aggregate([verifs[0].signature, verifs[1].signature]) ) ) if block.header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") if block.body.serialize( block.header.sketchSalt, len(packets) ) != bytes.fromhex(template["body"]): raise TestError("Failed to recreate the body.") block.mine(blsPrivKey, merit.blockchain.difficulty()) merit.blockchain.add(block) rpc.call( "merit", "publishBlock", [ template["id"], ( template["header"] + block.header.proof.to_bytes(4, byteorder="little") + block.header.signature + block.body.serialize(block.header.sketchSalt, len(packets)) ).hex() ] ) 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", "getMiner"))) 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", [blsPubKey.hex()]) #Mine a Block. block = Block( BlockHeader(0, blockchain.blocks[0].header.hash, bytes(32), 1, 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", [template["id"], block.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 TElementTest( rpc: RPC ) -> None: file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r") blocks: List[Dict[str, Any]] = json.loads(file.read()) file.close() merit: Merit = Merit() blsPrivKey: PrivateKey = PrivateKey(0) blsPubKey: str = blsPrivKey.toPublicKey().serialize().hex() #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.") 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="little"), ), 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="little") + 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="little") ), 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) rpc.call( "merit", "publishBlock", [ template["id"], ( template["header"] + block.header.proof.to_bytes(4, byteorder="little") + block.header.signature + block.body.serialize(block.header.sketchSalt) ).hex() ] ) verifyBlockchain(rpc, merit.blockchain)
def TElementTest(rpc: RPC) -> None: merit: Merit = Merit() blsPrivKey: PrivateKey = PrivateKey(0) blsPubKey: str = blsPrivKey.toPublicKey().serialize().hex() #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 with open("e2e/Vectors/Merit/BlankBlocks.json", "r") as file: block = Block.fromJSON(json.loads(file.read())[0]) merit.blockchain.add(block) rpc.meros.liveBlockHeader(block.header) rpc.meros.handleBlockBody(block) 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(1.5) #Verify the block template has the DataDifficulty. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", {"miner": 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]), 0, template["header"][-43:-39], BlockHeader.createSketchCheck(template["header"][-43:-39], []), 0, int.from_bytes(template["header"][-4:], byteorder="little"), ), BlockBody([], [dataDiff], dataDiff.signature)) if block.header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") block.mine(blsPrivKey, merit.blockchain.difficulty()) merit.blockchain.add(block) #Publish it. rpc.call( "merit", "publishBlock", { "id": template["id"], "header": (template["header"] + block.header.proof.to_bytes(4, byteorder="little") + block.header.signature).hex() }) #Create and transmit a new DataDifficulty. dataDiff = SignedDataDifficulty(3, 0, 0) dataDiff.sign(0, blsPrivKey) rpc.meros.signedElement(dataDiff) sleep(1.5) #Verify the block template has a MeritRemoval. #Thanks to implicit Merit Removals, this just means it has the new difficulty. template = rpc.call("merit", "getBlockTemplate", {"miner": blsPubKey}) template["header"] = bytes.fromhex(template["header"]) if template["header"][36:68] != BlockHeader.createContents([], [dataDiff]): raise TestError("Block template doesn't have the Merit Removal.") #Mine the Block. block = Block( BlockHeader( 0, block.header.hash, BlockHeader.createContents([], [dataDiff]), 0, template["header"][-43:-39], BlockHeader.createSketchCheck(template["header"][-43:-39], []), 0, int.from_bytes(template["header"][-4:], byteorder="little")), BlockBody([], [dataDiff], dataDiff.signature)) if block.header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") block.mine(blsPrivKey, merit.blockchain.difficulty()) merit.blockchain.add(block) rpc.call( "merit", "publishBlock", { "id": template["id"], "header": (template["header"] + block.header.proof.to_bytes(4, byteorder="little") + block.header.signature).hex() }) verifyBlockchain(rpc, merit.blockchain)
def TwoHundredFourTest( rpc: RPC ) -> None: file: IO[Any] = open("e2e/Vectors/Consensus/TwoHundredFour.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() #Instantiate a Blockchain to set the RandomX key. chain: Blockchain = Blockchain() blank: Block = Block.fromJSON(vectors["blank"]) for blockJSON in vectors["blocks"]: block: Block = Block.fromJSON(blockJSON) rpc.meros.liveConnect(chain.last()) rpc.meros.syncConnect(chain.last()) #Send a blank block so Meros acknowledges a holder. sentHeader: bytes = rpc.meros.liveBlockHeader(blank.header) if rpc.meros.sync.recv() != MessageType.BlockBodyRequest.toByte() + blank.header.hash: raise TestError("Meros didn't request the body for a blank Block.") rpc.meros.blockBody(blank) if rpc.meros.live.recv() != sentHeader: raise TestError("Meros didn't rebroadcast the header for a blank Block.") with raises(SuccessError): rpc.meros.liveBlockHeader(block.header) sentProblem: bool = False while True: if sentProblem: #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. sleep(1) if rpc.meros.process.poll() is not None: raise TestError("Node crashed trying to handle a VerificationPacket with no holders.") raise SuccessError("Node disconnected us after we sent a VerificationPacket with no holders.") except Exception: raise TestError("Meros didn't disconnect us after sending a VerificationPacket with no holders; it also didn't crash.") msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.BlockBodyRequest: if msg[1 : 33] != block.header.hash: raise TestError("Meros asked for a Block Body that didn't belong to the Block we just sent it.") rpc.meros.blockBody(block) #If this Block has no packets, we've already sent the problem. sentProblem = not block.body.packets elif MessageType(msg[0]) == MessageType.SketchHashRequests: if msg[1 : 33] != block.header.hash: raise TestError("Meros asked for Verification Packets that didn't belong to the Block we just sent it.") if int.from_bytes(msg[33 : 37], byteorder="little") != 1: raise TestError("Meros didn't ask for one VerificationPacket.") if int.from_bytes(msg[37 : 45], byteorder="little") != Sketch.hash(block.header.sketchSalt, block.body.packets[0]): raise TestError("Meros didn't ask for the VerificationPacket.") rpc.meros.packet(block.body.packets[0]) sentProblem = True else: raise TestError("Unexpected message sent: " + msg.hex().upper()) #Reset the node for the next test case. rpc.reset()
def EightyEightTest(rpc: RPC) -> None: edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) edPubKey: bytes = edPrivKey.get_verifying_key() blsPrivKey: PrivateKey = PrivateKey(0) blsPubKey: str = blsPrivKey.toPublicKey().serialize().hex() merit: Merit = Merit() dataFilter: SpamFilter = SpamFilter(5) #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 with open("e2e/Vectors/Merit/BlankBlocks.json", "r") as file: block = Block.fromJSON(json.loads(file.read())[0]) merit.blockchain.add(block) rpc.meros.liveBlockHeader(block.header) rpc.meros.handleBlockBody(block) if MessageType(rpc.meros.live.recv()[0]) != MessageType.BlockHeader: raise TestError( "Meros didn't broadcast the Block Header it just added.") #Create two Datas. datas: List[Data] = [Data(bytes(32), edPubKey)] datas.append(Data(datas[0].hash, b"Hello there! General Kenobi.")) for data in datas: #Sign them and have them beat the spam filter. data.sign(edPrivKey) data.beat(dataFilter) #Transmit them. rpc.meros.liveTransaction(data) #Verify both. verifs: List[SignedVerification] = [ SignedVerification(datas[0].hash), SignedVerification(datas[1].hash) ] for verif in verifs: verif.sign(0, blsPrivKey) #Only transmit the second. rpc.meros.signedElement(verifs[1]) sleep(1.5) #Verify the block template has no verifications. if bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": blsPubKey})["header"])[36:68] != bytes(32): raise TestError("Block template has Verification Packets.") #Transmit the first signed verification. rpc.meros.signedElement(verifs[0]) sleep(1.5) #Verify the block template has both verifications. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", {"miner": blsPubKey}) template["header"] = bytes.fromhex(template["header"]) packets: List[VerificationPacket] = [ VerificationPacket(datas[0].hash, [0]), VerificationPacket(datas[1].hash, [0]) ] if template["header"][36:68] != BlockHeader.createContents(packets): raise TestError( "Block template doesn't have both Verification Packets.") #Mine the Block. block = Block( BlockHeader( 0, block.header.hash, BlockHeader.createContents(packets), len(packets), template["header"][-43:-39], BlockHeader.createSketchCheck(template["header"][-43:-39], packets), 0, int.from_bytes(template["header"][-4:], byteorder="little")), BlockBody( packets, [], Signature.aggregate([verifs[0].signature, verifs[1].signature]))) if block.header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") block.mine(blsPrivKey, merit.blockchain.difficulty()) merit.blockchain.add(block) rpc.call( "merit", "publishBlock", { "id": template["id"], "header": (template["header"] + block.header.proof.to_bytes(4, byteorder="little") + block.header.signature).hex() }) verifyBlockchain(rpc, merit.blockchain)
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 HundredTwentyFourTest(rpc: RPC) -> None: vectors: List[Dict[str, Any]] with open("e2e/Vectors/Merit/BlankBlocks.json", "r") as file: vectors = json.loads(file.read()) 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[-32:] 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 b - bl < 0: break blockList.append(blockchain.blocks[b - bl].header.hash) 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.rawBlockBody(blockchain.blocks[bB], len(blockchain.blocks[bB].body.packets)) 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() + (0).to_bytes(1, "little") + (127).to_bytes(1, "little") + b'\0\0\0' + blockchain.last()) #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() + (0).to_bytes(1, "little") + (127).to_bytes(1, "little") + b'\0\0\0' + blockchain.last()) if rpc.meros.sync.recv() != MessageType.BlockchainTail.toByte( ) + blockchain.blocks[0].header.hash: raise TestError("Meros didn't respond with the genesis.") handleSync()