def mineBlock(rpc: RPC, nick: int = 0) -> None: privKey: PrivateKey = PrivateKey(nick) template: Dict[str, Any] = rpc.call( "merit", "getBlockTemplate", {"miner": privKey.toPublicKey().serialize().hex()}) header: bytes = bytes.fromhex(template["header"])[:-4] header += (rpc.call( "merit", "getBlock", {"block": rpc.call("merit", "getHeight") - 1})["header"]["time"] + 1200).to_bytes(4, "little") proof: int = -1 tempHash: bytes = bytes() signature: bytes = bytes() while ((proof == -1) or ((int.from_bytes(tempHash, "little") * template["difficulty"]) > int.from_bytes(bytes.fromhex("FF" * 32), "little"))): proof += 1 tempHash = RandomX(header + proof.to_bytes(4, "little")) signature = privKey.sign(tempHash).serialize() tempHash = RandomX(tempHash + signature) rpc.call( "merit", "publishBlock", { "id": template["id"], "header": header.hex() + proof.to_bytes(4, "little").hex() + signature.hex() }) if rpc.meros.live.recv() != (MessageType.BlockHeader.toByte() + header + proof.to_bytes(4, "little") + signature): raise TestError("Meros didn't broadcast back the BlockHeader.")
def verifyBlockchain( rpc: RPC, blockchain: Blockchain ) -> None: sleep(2) if rpc.call("merit", "getHeight") != len(blockchain.blocks): raise TestError("Height doesn't match.") if blockchain.difficulty() != rpc.call("merit", "getDifficulty"): raise TestError("Difficulty doesn't match.") for b in range(len(blockchain.blocks)): ourBlock: Dict[str, Any] = blockchain.blocks[b].toJSON() blockJSON: Dict[str, Any] = rpc.call("merit", "getBlock", {"block": b}) #Contextual info Python doesn't track. del blockJSON["removals"] if blockJSON != ourBlock: raise TestError("Block doesn't match.") #Test when indexing by the hash instead of the nonce. blockJSON = rpc.call( "merit", "getBlock", {"block": blockchain.blocks[b].header.hash.hex().upper()} ) del blockJSON["removals"] if blockJSON != ourBlock: raise TestError("Block doesn't match.")
def verifyBlockchain(rpc: RPC, blockchain: Blockchain) -> None: sleep(2) if rpc.call("merit", "getHeight") != len(blockchain.blocks): raise TestError("Height doesn't match.") if blockchain.difficulty() != rpc.call("merit", "getDifficulty"): raise TestError("Difficulty doesn't match.") for b in range(len(blockchain.blocks)): ourBlock: Dict[str, Any] = blockchain.blocks[b].toJSON() #Info Python saves so it can properly load from the vectors yet the Meros RPC excludes. del ourBlock["header"]["packets"] blockJSON: Dict[str, Any] = rpc.call("merit", "getBlock", [b]) #Contextual info Python doesn't track. del blockJSON["removals"] if blockJSON != ourBlock: raise TestError("Block doesn't match.") #Test when indexing by the hash instead of the nonce. blockJSON = rpc.call("merit", "getBlock", [blockchain.blocks[b].header.hash.hex().upper()]) del blockJSON["removals"] if blockJSON != ourBlock: raise TestError("Block doesn't match.")
def verifyBlockchain( rpc: RPC, blockchain: Blockchain ) -> None: #Sleep to ensure data races aren't a problem. sleep(2) #Verify the height. if rpc.call("merit", "getHeight") != len(blockchain.blocks): raise TestError("Height doesn't match.") #Verify the difficulty. if blockchain.difficulty() != int(rpc.call("merit", "getDifficulty"), 16): raise TestError("Difficulty doesn't match.") #Verify the Blocks. for b in range(len(blockchain.blocks)): if rpc.call("merit", "getBlock", [b]) != blockchain.blocks[b].toJSON(): raise TestError("Block doesn't match.") if rpc.call( "merit", "getBlock", [blockchain.blocks[b].header.hash.hex().upper()] ) != blockchain.blocks[b].toJSON(): raise TestError("Block doesn't match.")
def HundredSixSignedElementsTest(rpc: RPC) -> None: #Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() blsPrivKey: PrivateKey = PrivateKey(0) sig: Signature = blsPrivKey.sign(bytes()) #Create a Data. #This is required so the Verification isn't terminated early for having an unknown hash. data: bytes = bytes.fromhex(rpc.call("personal", "data", ["AA"])) #Create a signed Verification, SendDifficulty, and DataDifficulty. elements: List[SignedElement] = [ SignedVerification(data, 1, sig), SignedSendDifficulty(0, 0, 1, sig), SignedDataDifficulty(0, 0, 1, sig) ] for elem in elements: #Handshake with the node. rpc.meros.liveConnect(blockchain.blocks[0].header.hash) #Send the Element. rpc.meros.signedElement(elem) #Sleep for thirty seconds to make sure Meros realizes our connection is dead. sleep(30) #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.")
def test(rpc: RPC, address: Union[bytes, str], invalid: bool, msg: str) -> None: try: if isinstance(address, bytes): address = encodeAddress(address) rpc.call("personal", "send", [address, "1"]) #Raise a TestError with a different code than expected to ensure the below check is run and fails. raise TestError("0 ") except TestError as e: if int(e.message.split(" ")[0]) != (-3 if invalid else 1): raise TestError(msg)
def verifyMeritRemoval(rpc: RPC, total: int, merit: int, holder: int, pending: bool) -> None: sleep(1) if rpc.call("merit", "getTotalMerit") != total if pending else total - merit: raise TestError("Total Merit doesn't match.") if rpc.call("merit", "getMerit", {"nick": holder}) != { "status": "Unlocked", "malicious": pending, "merit": merit if pending else 0 }: raise TestError("Holder's Merit doesn't match.")
def verifyBlockchain(rpc: RPC, blockchain: Blockchain) -> None: sleep(2) if rpc.call("merit", "getHeight") != len(blockchain.blocks): raise TestError("Height doesn't match.") if blockchain.difficulty() != int(rpc.call("merit", "getDifficulty"), 16): raise TestError("Difficulty doesn't match.") for b in range(len(blockchain.blocks)): if rpc.call("merit", "getBlock", [b]) != blockchain.blocks[b].toJSON(): raise TestError("Block doesn't match.") if rpc.call("merit", "getBlock", [blockchain.blocks[b].header.hash.hex().upper() ]) != blockchain.blocks[b].toJSON(): raise TestError("Block doesn't match.")
def PersonalAuthorizationTest(rpc: RPC) -> None: #Test all these methods require authorization. #Doesn't test personal_data as that's not officially part of this test; just in it as a side note on key usage. #The actual personal_data test should handle that check. for method in [ "setWallet", "setAccount", "getMnemonic", "getMeritHolderKey", "getMeritHolderNick", "getAccount", "getAddress", "send", "data", "getUTXOs", "getTransactionTemplate" ]: try: rpc.call("personal", method, auth=False) raise Exception() except Exception as e: if str(e) != "HTTP status isn't 200: 401": raise TestError("Could call personal_" + method + " without authorization.")
def rpc( #pylint: disable=redefined-outer-name meros: Meros, request: Any) -> RPC: result: RPC = RPC(meros) request.addfinalizer(result.quit) return result
def checkTemplate(rpc: RPC, mnemonic: str, req: Dict[str, Any], inputs: List[Dict[str, Any]], outputs: List[Dict[str, Any]]) -> None: template: Dict[str, Any] = rpc.call("personal", "getTransactionTemplate", req) if template["type"] != "Send": raise TestError("Template isn't of type Send.") if sortUTXOs(template["inputs"]) != sortUTXOs(inputs): raise TestError("Template inputs aren't as expected.") if template["outputs"] != outputs: raise TestError("Template outputs are incorrect.") keys: List[bytes] = [] for inputJSON in template["inputs"]: key: bytes if inputJSON["change"]: key = getChangePublicKey(mnemonic, "", inputJSON["index"]) else: key = getPublicKey(mnemonic, "", inputJSON["index"]) if key not in keys: keys.append(key) if template["publicKey"] != Ristretto.aggregate( [Ristretto.RistrettoPoint(key) for key in keys]).serialize().hex().upper(): if len(keys) == 1: raise TestError( "Template public key isn't correct when only a single key is present." ) raise TestError("Public key aggregation isn't correct.")
def VUnknownSignedTest(rpc: RPC) -> None: file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r") chain: Blockchain = Blockchain.fromJSON(json.loads(file.read())) file.close() #Send a single block so we have a miner. rpc.meros.liveConnect(chain.blocks[0].header.hash) rpc.meros.syncConnect(chain.blocks[0].header.hash) header: bytes = rpc.meros.liveBlockHeader(chain.blocks[1].header) if MessageType(rpc.meros.sync.recv()[0]) != MessageType.BlockBodyRequest: raise TestError("Meros didn't ask for the body.") rpc.meros.blockBody(chain.blocks[1]) if rpc.meros.live.recv() != header: raise TestError("Meros didn't broadcast the header.") #Create a valid Data. #Uneccessary at this time, but good preparation for the future. privKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) data: Data = Data(bytes(32), privKey.get_verifying_key().to_bytes()) data.sign(privKey) data.beat(SpamFilter(5)) #Sign the Data. verif: SignedVerification = SignedVerification(data.hash) verif.sign(0, PrivateKey(0)) #Run twice. The first shouldn't send the Transaction. The second should. for i in range(2): rpc.meros.signedElement(verif) if MessageType( rpc.meros.sync.recv()[0]) != MessageType.TransactionRequest: raise TestError("Meros didn't request the transaction.") if i == 0: #When we send DataMissing, we should be disconnected within a few seconds. rpc.meros.dataMissing() start: int = int(time()) try: rpc.meros.sync.recv() except Exception: #More than a few seconds is allowed as Meros's own SyncRequest must timeout. if int(time()) - start > 10: raise TestError( "Meros didn't disconnect us for sending a Verification of a non-existent Transaction." ) #Clear our invalid connections. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() sleep(65) #Init new ones. rpc.meros.liveConnect(chain.blocks[0].header.hash) rpc.meros.syncConnect(chain.blocks[0].header.hash) else: rpc.meros.syncTransaction(data) sleep(2) if not rpc.call("consensus", "getStatus", [data.hash.hex()])["verifiers"]: raise TestError("Meros didn't add the Verification.")
def verifyDataDifficulty( rpc: RPC, dataDiff: int ) -> None: sleep(1) if rpc.call("consensus", "getDataDifficulty") != dataDiff: raise TestError("Data Difficulty doesn't match.")
def verifySendDifficulty( rpc: RPC, sendDiff: int ) -> None: #Sleep to ensure data races aren't a problem. sleep(1) if rpc.call("consensus", "getSendDifficulty") != sendDiff: raise TestError("Send Difficulty doesn't match.")
def test(rpc: RPC, address: Union[bytes, str], invalid: bool, msg: str) -> None: if isinstance(address, bytes): address = encodeAddress(address) try: rpc.call("transactions", "getBalance", {"address": address}, False) #If the call passed, and the address is invalid, raise. if invalid: raise MessageException(msg) except TestError as e: if int(e.message.split(" ")[0]) != -32602: raise Exception( "Non-ParamError was raised by this RPC call, which shouldn't be able to raise anything else." ) if not invalid: raise TestError(msg) except MessageException as e: raise TestError(e.message)
def verifyMeritRemoval( rpc: RPC, total: int, merit: int, holder: int, pending: bool ) -> None: sleep(1) #Verify the total Merit. if rpc.call("merit", "getTotalMerit") != total if pending else total - merit: raise TestError("Total Merit doesn't match.") #Verify the holder's Merit. if rpc.call("merit", "getMerit", [holder]) != { "unlocked": True, "malicious": pending, "merit": merit if pending else 0 }: raise TestError("Holder's Merit doesn't match.")
def GetDifficultyTest( rpc: RPC ) -> None: #Check the global difficulty. if rpc.call("consensus", "getSendDifficulty", auth=False) != 3: raise TestError("getSendDifficulty didn't reply properly.") if rpc.call("consensus", "getDataDifficulty", auth=False) != 5: raise TestError("getDataDifficulty didn't reply properly.") #Check the difficulties for a holder who doesn't exist. try: rpc.call("consensus", "getSendDifficulty", {"holder": 0}, False) raise TestError("") except TestError as e: if str(e) != "-2 Holder doesn't have a SendDifficulty.": raise TestError("getSendDifficulty didn't raise when asked about a non-existent holder.") try: rpc.call("consensus", "getDataDifficulty", {"holder": 0}, False) raise TestError("") except TestError as e: if str(e) != "-2 Holder doesn't have a DataDifficulty.": raise TestError("getDataDifficulty didn't raise when asked about a non-existent holder.") def voteAndVerify() -> None: #Check the difficulties for a holder who has yet to vote. try: rpc.call("consensus", "getSendDifficulty", {"holder": 0}, False) raise TestError("") except TestError as e: if str(e) != "-2 Holder doesn't have a SendDifficulty.": raise TestError("getSendDifficulty didn't raise when asked about a holder who has yet to vote.") try: rpc.call("consensus", "getDataDifficulty", {"holder": 0}, False) raise TestError("") except TestError as e: if str(e) != "-2 Holder doesn't have a DataDifficulty.": raise TestError("getDataDifficulty didn't raise when asked about a holder who has yet to vote.") #Create the votes. sendDiff: SignedSendDifficulty = SignedSendDifficulty(6, 0) sendDiff.sign(0, PrivateKey(0)) dataDiff: SignedDataDifficulty = SignedDataDifficulty(10, 1) dataDiff.sign(0, PrivateKey(0)) #Send them. rpc.meros.signedElement(sendDiff) rpc.meros.signedElement(dataDiff) rpc.meros.live.recv() rpc.meros.live.recv() #Check them. if rpc.call("consensus", "getSendDifficulty", {"holder": 0}, False) != 6: raise TestError("getSendDifficulty didn't reply with the holder's current difficulty.") if rpc.call("consensus", "getDataDifficulty", {"holder": 0}, False) != 10: raise TestError("getDataDifficulty didn't reply with the holder's current difficulty.") with open("e2e/Vectors/Merit/BlankBlocks.json", "r") as file: Liver(rpc, json.loads(file.read())[:1], callbacks={1: voteAndVerify}).live()
def IntegerBoundTest( rpc: RPC ) -> None: #uint16. try: rpc.call("merit", "getPublicKey", {"nick": -1}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted a negative integer for an unsigned integer.") try: rpc.call("merit", "getPublicKey", {"nick": 65536}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted a too large unsigned integer.") #uint. try: rpc.call("merit", "getBlock", {"block": -1}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted a negative integer for an unsigned integer.") try: rpc.call("merit", "getBlock", {"block": (2 ** 63)}) raise TestError() except Exception as e: if str(e) != "-32700 Parse error.": raise TestError("Meros parsed an integer outside of the int64 bounds.") try: rpc.call("merit", "getBlock", {"block": (2 ** 64)}) raise TestError() except Exception as e: if str(e) != "-32700 Parse error.": raise TestError("Meros parsed an integer outside of the uint64 bounds.")
def HundredSixtyTwoTest(rpc: RPC) -> None: #Create the first Datas. mnemonic: str = rpc.call("personal", "getMnemonic") abcData: str = rpc.call("personal", "data", {"data": "abc"}) #Create a Data on a different account. rpc.call("personal", "setWallet") defData: str = rpc.call("personal", "data", {"data": "def"}) #Verify the def Data was created. if rpc.call("transactions", "getTransaction", {"hash": defData})["descendant"] != "Data": raise TestError( "Meros didn't create a Data for an imported account when the existing account had Datas." ) #Switch back to the old Mnemonic. rpc.call("personal", "setWallet", {"mnemonic": mnemonic}) #Ensure we can create new Datas on it as well, meaning switching to a Mnemonic ports the chain. ghiDataHash: str = rpc.call("personal", "data", {"data": "ghi"}) ghiData: Dict[str, Any] = rpc.call("transactions", "getTransaction", {"hash": ghiDataHash}) del ghiData["signature"] del ghiData["proof"] if ghiData != { "descendant": "Data", "inputs": [{ "hash": abcData }], "outputs": [], "hash": ghiDataHash, "data": b"ghi".hex() }: raise TestError( "Data created for an imported account with Datas isn't correct.")
def HundredSixSignedElementsTest( rpc: RPC ) -> None: #Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) blsPrivKey: PrivateKey = PrivateKey(0) sig: Signature = blsPrivKey.sign(bytes()) #Create a Data for the Verification. data: Data = Data(bytes(32), edPrivKey.get_verifying_key()) data.sign(edPrivKey) data.beat(SpamFilter(5)) #Create a signed Verification, SendDifficulty, and DataDifficulty. elements: List[SignedElement] = [ SignedVerification(data.hash, 1, sig), SignedSendDifficulty(0, 0, 1, sig), SignedDataDifficulty(0, 0, 1, sig) ] dataSent: bool = False for elem in elements: #Handshake with the node. rpc.meros.liveConnect(blockchain.blocks[0].header.hash) #Send the Data if we have yet to. if not dataSent: if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Data wasn't rebroadcasted.") dataSent = True #Send the Element. rpc.meros.signedElement(elem) #Sleep for thirty seconds to make sure Meros realizes our connection is dead. sleep(30) #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.")
def checkSend(rpc: RPC, sendHash: str, expected: Dict[str, Any]) -> None: send: Dict[str, Any] = rpc.call("transactions", "getTransaction", {"hash": sendHash}) serialized: bytes = Send.fromJSON(send).serialize() del send["signature"] del send["proof"] expected["descendant"] = "Send" expected["hash"] = sendHash if sortUTXOs(send["inputs"]) != sortUTXOs(expected["inputs"]): raise TestError("Send inputs weren't as expected.") del send["inputs"] del expected["inputs"] if send != expected: raise TestError("Send wasn't as expected.") if rpc.meros.live.recv() != (MessageType.Send.toByte() + serialized): raise TestError("Meros didn't broadcast a Send it created.")
def checkData(rpc: RPC, dataHash: str, expected: bytes) -> str: data: Dict[str, Any] = rpc.call("transactions", "getTransaction", {"hash": dataHash}) if len(data["inputs"]) != 1: raise TestError("Data had multiple inputs.") res: str = data["inputs"][0]["hash"] del data["inputs"] del data["signature"] del data["proof"] if data != { "descendant": "Data", "outputs": [], "hash": dataHash, "data": expected.hex().upper() }: raise TestError("Data wasn't as expected.") return res
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 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 PartialArchiveTest(rpc: RPC) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Consensus/Verification/PartialArchive.json", "r") as file: vectors = json.loads(file.read()) data: Data = Data.fromJSON(vectors["data"]) svs: List[SignedVerification] = [ SignedVerification.fromSignedJSON(vectors["verifs"][0]), SignedVerification.fromSignedJSON(vectors["verifs"][1]) ] key: PrivateKey = PrivateKey( bytes.fromhex(rpc.call("personal", "getMiner"))) def sendDataAndVerifications() -> None: if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError( "Meros didn't rebroadcast a Transaction we sent it.") for sv in svs: if rpc.meros.signedElement(sv) != rpc.meros.live.recv(): raise TestError( "Meros didn't rebroadcast a SignedVerification we sent it." ) #As we don't have a quality RPC route for this, we need to use getTemplate. if bytes.fromhex( rpc.call("merit", "getBlockTemplate", [key.toPublicKey().serialize().hex() ])["header"])[36:68] != BlockHeader.createContents( [VerificationPacket(data.hash, [0, 1])]): raise TestError( "New Block template doesn't have a properly created packet.") def verifyRecreation() -> None: template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", [key.toPublicKey().serialize().hex()]) if bytes.fromhex( template["header"])[36:68] != BlockHeader.createContents( [VerificationPacket(data.hash, [1])]): raise TestError( "New Block template doesn't have a properly recreated packet.") #Mining it further verifies the internal state. header: bytes = bytes.fromhex(template["header"]) proof: int = 0 sig: bytes while True: initial: bytes = RandomX(header + proof.to_bytes(4, byteorder="little")) sig = key.sign(initial).serialize() final: bytes = RandomX(initial + sig) if (int.from_bytes(final, "little") * template["difficulty"]) < int.from_bytes( bytes.fromhex("FF" * 32), "little"): break proof += 1 rpc.call("merit", "publishBlock", [ template["id"], (header + proof.to_bytes(4, byteorder="little") + sig).hex() ]) raise SuccessError( "Stop Liver from trying to verify the vector chain which doesn't have this Block." ) #We may not want to use Liver here. #There's a very small Block count and we can't let it terminate (hence the SE). with raises(SuccessError): Liver(rpc, vectors["blockchain"], callbacks={ 2: sendDataAndVerifications, 3: verifyRecreation }).live()
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 PersonalDataTest(rpc: RPC) -> None: #Create a Data. firstData: str = rpc.call("personal", "data", {"data": "a"}) initial: str = checkData(rpc, firstData, b"a") #Meros should've also created an initial Data. if checkData(rpc, initial, decodeAddress(rpc.call( "personal", "getAddress"))) != bytes(32).hex(): raise TestError("Initial Data didn't have a 0-hash input.") #Create a Data using hex data. Also tests upper case hex. if checkData(rpc, rpc.call("personal", "data", { "data": "AABBCC", "hex": True }), b"\xAA\xBB\xCC") != firstData: raise TestError( "Newly created Data wasn't a descendant of the existing Data.") #Should support using 256 bytes of Data. Also tests lower case hex. checkData( rpc, rpc.call("personal", "data", { "data": bytes([0xaa] * 256).hex(), "hex": True }), bytes([0xaa] * 256)) #Should properly error when we input no data. All Datas must have at least 1 byte of Data. try: rpc.call("personal", "data", {"data": ""}) raise Exception() except Exception as e: if str(e) != "-3 Data is too small or too large.": raise TestError("Meros didn't handle Data that was too small.") #Should properly error when we supply more than 256 bytes of data. try: rpc.call("personal", "data", {"data": "a" * 257}) raise Exception() except Exception as e: if str(e) != "-3 Data is too small or too large.": raise TestError("Meros didn't handle Data that was too large.") #Should properly error when we supply non-hex data with the hex flag. try: rpc.call("personal", "data", {"data": "zz", "hex": True}) raise Exception() except Exception as e: if str(e) != "-3 Invalid hex char `z` (ord 122).": raise TestError("Meros didn't properly handle invalid hex.") #Should properly error when we supply non-even hex data. try: rpc.call("personal", "data", {"data": "a", "hex": True}) raise Exception() except Exception as e: if str(e) != "-3 Incorrect hex string len.": raise TestError("Meros didn't properly handle non-even hex.") #Test Datas when the Wallet has a password. rpc.call("personal", "setWallet", {"password": "******"}) #Shouldn't work due to the lack of a password. try: rpc.call("personal", "data", {"data": "abc"}) raise Exception() except Exception as e: if str(e) != "-3 Invalid password.": raise TestError( "Meros didn't properly handle creating a Data without a password." ) #Should work due to the existence of a password. lastData: str = rpc.call("personal", "data", { "data": "abc", "password": "******" }) checkData(rpc, lastData, b"abc") #Reboot the node and verify we can create a new Data without issue. rpc.quit() sleep(3) rpc.meros = Meros(rpc.meros.db, rpc.meros.tcp, rpc.meros.rpc) if checkData( rpc, rpc.call("personal", "data", { "data": "def", "password": "******" }), b"def") != lastData: raise TestError("Couldn't create a new Data after rebooting.")
def StringBasedTypesTest( rpc: RPC ) -> None: edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) edPubKey: bytes = edPrivKey.get_verifying_key() blsPubKey: PublicKey = PrivateKey(0).toPublicKey() #hex. #Test 0x-prefixed and no-0x both work without issue. data: Data = Data(bytes(32), edPubKey) data.sign(edPrivKey) rpc.call("transactions", "publishTransactionWithoutWork", {"type": "Data", "transaction": "0x" + data.serialize()[:-4].hex()}) data = Data(data.hash, b"abc") data.sign(edPrivKey) rpc.call("transactions", "publishTransactionWithoutWork", {"type": "Data", "transaction": data.serialize()[:-4].hex()}) #Test non-hex data is properly handled. try: rpc.call("transactions", "publishTransactionWithoutWork", {"type": "Data", "transaction": "az"}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted non-hex data for a hex argument.") #Hash. rpc.call("transactions", "getTransaction", {"hash": data.hash.hex()}) rpc.call("transactions", "getTransaction", {"hash": "0x" + data.hash.hex()}) #Also call the upper form, supplementing the above hex tests (as Hash routes through hex). rpc.call("transactions", "getTransaction", {"hash": data.hash.hex().upper()}) rpc.call("transactions", "getTransaction", {"hash": "0x" + data.hash.hex().upper()}) #Improper length. try: rpc.call("transactions", "getTransaction", {"hash": data.hash.hex().upper()[:-2]}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted a hex string with an improper length as a Hash.") #BLSPublicKey. try: rpc.call("merit", "getNickname", {"key": blsPubKey.serialize().hex()}) raise TestError() except Exception as e: if str(e) != "-2 Key doesn't have a nickname assigned.": raise TestError("Meros didn't accept a valid BLSPublicKey.") try: rpc.call("merit", "getNickname", {"key": blsPubKey.serialize().hex()[:-2]}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted a hex string with an improper length as a BLSPublicKey.") #Missing flags. try: rpc.call("merit", "getNickname", {"key": "0" + blsPubKey.serialize().hex()[1]}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted an invalid BLSPublicKey as a BLSPublicKey.") #EdPublicKey. rpc.call("personal", "setAccount", {"key": edPubKey.hex(), "chainCode": bytes(32).hex()}) try: rpc.call("personal", "setAccount", {"key": edPubKey[:-2].hex(), "chainCode": bytes(32).hex()}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted a hex string with an improper length as an EdPublicKey.")
def TwoHundredThirtyTwoTest(rpc: RPC) -> None: file: IO[Any] = open( "e2e/Vectors/Merit/Reorganizations/TwoHundredThirtyTwo.json", "r") chains: Dict[str, List[Dict[str, Any]]] = json.loads(file.read()) main: Blockchain = Blockchain.fromJSON(chains["main"]) alt: Blockchain = Blockchain.fromJSON(chains["alt"]) file.close() 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) 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 rpc.meros.sync.recv()[0] != MessageType.BlockListRequest.toByte()[0]: raise TestError( "Meros didn't ask for the Block List of the alternate chain.") rpc.meros.blockList([alt.blocks[2].header.hash, 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() rpc.socket.close() sleep(35) #Reboot the node to reload the database. rpc.meros.quit() 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", "--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]) rpc.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) rpc.socket.connect(("127.0.0.1", rpc.meros.rpc)) verifyBlockchain(rpc, main)
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)