def FiftyTest(rpc: RPC) -> None: with open("e2e/Vectors/Transactions/Fifty.json", "r") as file: vectors: Dict[str, Any] = json.loads(file.read()) Liver(rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"])).live() Syncer(rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"])).sync()
def MultiInputClaimTest(rpc: RPC) -> None: file: IO[Any] = open("e2e/Vectors/Transactions/MultiInputClaim.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() Liver(rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"])).live() Syncer(rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"])).sync()
def FiftyTest(rpc: RPC) -> None: file: IO[Any] = open("e2e/Vectors/Transactions/Fifty.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() #Create and execute a Liver/Syncer. Liver(rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"])).live() Syncer(rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"])).sync()
def HundredSeventyFiveTest( rpc: RPC ) -> None: file: IO[Any] = open("e2e/Vectors/Merit/HundredSeventyFive.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) verif: SignedVerification = SignedVerification.fromSignedJSON(vectors["verification"]) def sendDatasAndVerif() -> None: for tx in transactions.txs: if rpc.meros.liveTransaction(transactions.txs[tx]) != rpc.meros.live.recv(): raise TestError("Meros didn't send us back the Data.") if rpc.meros.signedElement(verif) != rpc.meros.live.recv(): raise TestError("Meros didn't send us back the Verification.") Liver( rpc, vectors["blockchain"], transactions, callbacks={ 1: sendDatasAndVerif } ).live([verif.hash])
def ImpossibleFamilyTest( rpc: RPC ) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Consensus/Families/ImpossibleFamily.json", "r") as file: vectors = json.loads(file.read()) sends: List[Send] = [Send.fromJSON(send) for send in vectors["sends"]] 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 verifyPossibleWon() -> None: if rpc.call("consensus", "getStatus", [sends[1].hash.hex()])["verified"]: raise TestError("Meros verified an impossible Transaction.") if not rpc.call("consensus", "getStatus", [sends[0].hash.hex()])["verified"]: raise TestError("Meros didn't verify the only possible Transaction.") Liver( rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"]), callbacks={ 42: sendSends, 48: verifyPossibleWon } ).live()
def UnionedFamiliesSingleWinnerTest( rpc: RPC ) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Consensus/Families/UnionedFamiliesSingleWinner.json", "r") as file: vectors = json.loads(file.read()) sends: List[Send] = [Send.fromJSON(send) for send in vectors["sends"]] 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 verifyUnionizingWon() -> None: for send in sends[:-1]: if rpc.call("consensus", "getStatus", {"hash": send.hash.hex()})["verified"]: raise TestError("Meros verified a transaction which was beaten by a unionizing transaction.") if not rpc.call("consensus", "getStatus", {"hash": sends[-1].hash.hex()})["verified"]: raise TestError("Meros didn't verify the verified unionizing transaction.") Liver( rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"]), callbacks={ 45: sendSends, 51: verifyUnionizingWon } ).live()
def VCompetingTest(rpc: RPC) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Consensus/Verification/Competing.json", "r") as file: vectors = json.loads(file.read()) transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) #Function to verify the right Transaction was confirmed. def verifyConfirmation() -> None: if not rpc.call("consensus", "getStatus", {"hash": vectors["verified"]})["verified"]: raise TestError( "Didn't verify the Send which should have been verified.") if rpc.call("consensus", "getStatus", {"hash": vectors["beaten"]})["verified"]: raise TestError( "Did verify the Send which should have been beaten.") Liver(rpc, vectors["blockchain"], transactions, callbacks={ 19: verifyConfirmation }).live() Syncer(rpc, vectors["blockchain"], transactions).sync()
def UnionedFamiliesMultipleWinnersTest(rpc: RPC) -> None: vectors: Dict[str, Any] with open( "e2e/Vectors/Consensus/Families/UnionedFamiliesMultipleWinners.json", "r") as file: vectors = json.loads(file.read()) sends: List[Send] = [Send.fromJSON(send) for send in vectors["sends"]] 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 verifyMultipleWon() -> None: for send in [sends[1], *sends[3:]]: if rpc.call("consensus", "getStatus", [send.hash.hex()])["verified"]: raise TestError( "Meros verified a transaction which was beaten by another transaction." ) for send in [sends[0], sends[2]]: if not rpc.call("consensus", "getStatus", [send.hash.hex()])["verified"]: raise TestError( "Meros didn't verify the verified transaction for each original family." ) Liver(rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"]), callbacks={ 44: sendSends, 50: verifyMultipleWon }).live()
def RespondsWithRequestedCapacityTest( rpc: RPC ) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Merit/TwoHundredSeventyFour/RespondsWithRequestedCapacity.json", "r") as file: vectors = json.loads(file.read()) def requestWithCapacity() -> None: block: Block = Block.fromJSON(vectors["blockchain"][-1]) #Request 3/6 Transactions. rpc.meros.sync.send(MessageType.BlockBodyRequest.toByte() + block.header.hash + (3).to_bytes(4, "little")) if rpc.meros.sync.recv() != (MessageType.BlockBody.toByte() + block.body.serialize(block.header.sketchSalt, 3)): raise TestError("Meros didn't respond with the requested capacity.") #Request 8/6 Transactions. rpc.meros.sync.send(MessageType.BlockBodyRequest.toByte() + block.header.hash + (8).to_bytes(4, "little")) if rpc.meros.sync.recv() != (MessageType.BlockBody.toByte() + block.body.serialize(block.header.sketchSalt, 6)): raise TestError("Meros didn't respond with the requested capacity (normalized).") Liver( rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"]), callbacks={ 2: requestWithCapacity } ).live()
def HundredTwoTest( rpc: RPC ) -> None: file: IO[Any] = open("e2e/Vectors/Consensus/Verification/HundredTwo.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) #Verifies the Transaction is added, it has the right holders, the holders Merit surpasses the threshold, yet it isn't verified. def verify() -> None: for tx in transactions.txs: status: Dict[str, Any] = rpc.call("consensus", "getStatus", [tx.hex()]) if set(status["verifiers"]) != set([0, 1]): raise TestError("Meros doesn't have the right list of verifiers for this Transaction.") if status["merit"] != 80: raise TestError("Meros doesn't have the right amount of Merit for this Transaction.") if rpc.call("merit", "getMerit", [0])["merit"] + rpc.call("merit", "getMerit", [1])["merit"] < status["threshold"]: raise TestError("Merit sum of holders is less than the threshold.") if status["verified"]: raise TestError("Meros verified the Transaction which won't have enough Merit by the time the Transaction finalizes.") Liver(rpc, vectors["blockchain"], transactions, callbacks={100: verify}).live()
def DescendantHighestUnverifiedParentTest(rpc: RPC) -> None: vectors: Dict[str, Any] with open( "e2e/Vectors/Consensus/Families/DescendantHighestUnverifiedParent.json", "r") as file: vectors = json.loads(file.read()) sends: List[Send] = [Send.fromJSON(send) for send in vectors["sends"]] 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 verifyDescendantLost() -> None: for send in sends[1:]: if rpc.call("consensus", "getStatus", {"hash": send.hash.hex()})["verified"]: raise TestError( "Meros verified a beaten transaction or one of its children (one of which is impossible)." ) if not rpc.call("consensus", "getStatus", {"hash": sends[0].hash.hex()})["verified"]: raise TestError( "Meros either didn't verify the descendant or its parent.") Liver(rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"]), callbacks={ 45: sendSends, 51: verifyDescendantLost }).live()
def MultiInputClaimTest(rpc: RPC) -> None: with open("e2e/Vectors/Transactions/MultiInputClaim.json", "r") as file: vectors: Dict[str, Any] = json.loads(file.read()) transactions: Transactions = Transactions.fromJSON( vectors["transactions"]) Liver(rpc, vectors["blockchain"], transactions).live() Syncer(rpc, vectors["blockchain"], transactions).sync()
def TGUReorgTest(rpc: RPC) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/RPC/Transactions/GetUTXOs.json", "r") as file: vectors = json.loads(file.read()) transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) 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)) #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.") verify(rpc, send.hash) if rpc.call("transactions", "getUTXOs", {"address": address}) != [{ "hash": send.hash.hex().upper(), "nonce": 0 }]: raise TestError( "Meros didn't consider a confirmed Transaction's outputs as UTXOs." ) #Spend it, with a newer Mint as an input as well so we can prune it without pruning the original. newerSend: Send = createSend( rpc, [Claim.fromJSON(vectors["newerMintClaim"])], recipientPub) _: Send = createSend(rpc, [send, newerSend], bytes(32), recipient) if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError("Meros thinks the recipient has UTXOs.") #Remove the spending Send by pruning its ancestor (a Mint). reorg(rpc, Blockchain.fromJSON(vectors["blocksWithoutNewerMint"])) #Meros should add back its parent as an UTXO. if rpc.call("transactions", "getUTXOs", {"address": address}) != [{ "hash": send.hash.hex().upper(), "nonce": 0 }]: raise TestError( "Meros didn't consider a Transaction without spenders as an UTXO." ) #Remove the original Send and verify its outputs are no longer considered UTXOs. reorg(rpc, Blockchain.fromJSON(vectors["blocksWithoutOlderMint"])) if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros didn't remove the outputs of a pruned Transaction as UTXOs." ) raise SuccessError() #Send Blocks so we have a Merit Holder who can instantly verify Transactions, not to mention Mints. with raises(SuccessError): Liver(rpc, vectors["blockchain"], transactions, {50: test}).live()
def TGUFinalizesTest( rpc: RPC ) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/RPC/Transactions/GetUTXOs.json", "r") as file: vectors = json.loads(file.read()) transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) def test() -> None: recipient: Ristretto.SigningKey = Ristretto.SigningKey(b'\1' * 32) recipientPub: bytes = recipient.get_verifying_key() address: str = bech32_encode("mr", convertbits(bytes([0]) + recipientPub, 8, 5)) otherRecipient: bytes = Ristretto.SigningKey(b'\2' * 32).get_verifying_key() 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) #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.") if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError("Meros didn't consider a Transaction's inputs as spent.") #Verify with another party, so it won't be majority verified, yet will still have a Verification. mineBlock(rpc, 1) verify(rpc, spendingSend.hash, 1) #Verify it didn't create a UTXO. if rpc.call("transactions", "getUTXOs", {"address": otherAddress}) != []: raise TestError("Unverified Transaction created a UTXO.") #Finalize. for _ in range(6): mineBlock(rpc) #Check the UTXOs were created. if rpc.call("transactions", "getUTXOs", {"address": otherAddress}) != [{"hash": spendingSend.hash.hex().upper(), "nonce": 0}]: raise TestError("Meros didn't consider a finalized Transaction's outputs as UTXOs.") raise SuccessError() #Send Blocks so we have a Merit Holder who can instantly verify Transactions, not to mention Mints. with raises(SuccessError): Liver(rpc, vectors["blockchain"], transactions, {50: test}).live()
def InvalidCompetingTest(rpc: RPC) -> None: file: IO[Any] = open( "e2e/Vectors/Consensus/MeritRemoval/InvalidCompeting.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() #Transactions. transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) #MeritRemoval. #pylint: disable=no-member removal: SignedMeritRemoval = SignedMeritRemoval.fromSignedJSON( vectors["removal"]) #Create and execute a Liver to handle the MeritRemoval. def sendMeritRemoval() -> None: #Send and verify the MeritRemoval. removalBytes: bytes = rpc.meros.signedElement(removal) sent: int = 0 while True: if sent == 2: break msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.TransactionRequest: rpc.meros.syncTransaction(transactions.txs[msg[1:33]]) sent += 1 else: raise TestError("Unexpected message sent: " + msg.hex().upper()) if removalBytes != rpc.meros.live.recv(): raise TestError("Meros didn't send us the Merit Removal.") verifyMeritRemoval(rpc, 1, 1, removal.holder, True) #Verify the MeritRemoval and the Blockchain. def verify() -> None: verifyMeritRemoval(rpc, 1, 1, removal.holder, False) verifyBlockchain(rpc, Blockchain.fromJSON(vectors["blockchain"])) raise SuccessError( "MeritRemoval and Blockchain were properly handled.") Liver(rpc, vectors["blockchain"], transactions, callbacks={ 1: sendMeritRemoval, 2: verify }).live()
def HundredFortySevenTest(rpc: RPC) -> None: file: IO[Any] = open("e2e/Vectors/Transactions/ClaimedMint.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() #Merit. merit: Merit = Merit.fromJSON(vectors["blockchain"]) #Transactions. transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) #Ed25519 keys. privKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) pubKey: ed25519.VerifyingKey = privKey.get_verifying_key() #Grab the Claim hash, claim: bytes = merit.blockchain.blocks[-1].body.packets[0].hash #Create a Send which underflows. send: Send = Send( [(claim, 0)], [(pubKey.to_bytes(), 18446744073709551231), (pubKey.to_bytes(), 385 + Claim.fromTransaction(transactions.txs[claim]).amount)]) send.sign(privKey) send.beat(SpamFilter(3)) #Custom function to send the last Block and verify it errors at the right place. def checkFail() -> None: #Send the Send. rpc.meros.liveTransaction(send) #Handle sync requests. while True: #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 an invalid Transaction." ) except Exception: raise TestError("Meros sent a keep-alive.") #Create and execute a Liver. Liver(rpc, vectors["blockchain"], transactions, callbacks={ 12: checkFail }).live()
def HundredFortyTwoTest(rpc: RPC) -> None: file: IO[Any] = open( "e2e/Vectors/Consensus/Verification/HundredFortyTwo.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() #Transactions. transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) #Function to verify the Transaction includes unarchived Merit before finalization. def verifyUnarchivedMerit() -> None: #Send the verification which won't be archived. if rpc.meros.signedElement( SignedVerification.fromSignedJSON( vectors["verification"])) != rpc.meros.live.recv(): raise TestError("Meros didn't send back the SignedVerification.") status: Dict[str, Any] = rpc.call("consensus", "getStatus", [vectors["transaction"]]) if sorted(status["verifiers"]) != [0, 1]: raise TestError( "Status didn't include verifiers which have yet to be archived." ) if status["merit"] != 7: raise TestError( "Status didn't include Merit which has yet to be archived.") #Function to verify the Transaction doesn't include unarchived Merit after finalization. def verifyArchivedMerit() -> None: status: Dict[str, Any] = rpc.call("consensus", "getStatus", [vectors["transaction"]]) if status["verifiers"] != [0]: raise TestError( "Status included verifiers which were never archived.") if status["merit"] != 1: raise TestError("Status included Merit which was never archived.") #Create and execute a Liver. Liver(rpc, vectors["blockchain"], transactions, callbacks={ 7: verifyUnarchivedMerit, 8: verifyArchivedMerit }).live()
def HundredThirtyFiveTest( rpc: RPC ) -> None: file: IO[Any] = open("e2e/Vectors/Consensus/MeritRemoval/HundredThirtyFive.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() #Datas. datas: List[Data] = [ Data.fromJSON(vectors["datas"][0]), Data.fromJSON(vectors["datas"][1]), Data.fromJSON(vectors["datas"][2]) ] #Transactions. transactions: Transactions = Transactions() for data in datas: transactions.add(data) #First MeritRemoval. mr: SignedMeritRemoval = SignedMeritRemoval.fromSignedJSON(vectors["removal"]) def sendMeritRemoval() -> None: #Send the Datas. for data in datas: if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Meros didn't send us the Data.") #Send and verify the original MeritRemoval. if rpc.meros.signedElement(mr) != rpc.meros.live.recv(): raise TestError("Meros didn't send us the Merit Removal.") verifyMeritRemoval(rpc, 1, 1, mr.holder, True) Liver( rpc, vectors["blockchain"], transactions, callbacks={ 1: sendMeritRemoval } ).live()
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 HundredThirtyThreeTest( rpc: RPC ) -> None: file: IO[Any] = open("e2e/Vectors/Consensus/MeritRemoval/HundredThirtyThree.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() #Datas. datas: List[Data] = [ Data.fromJSON(vectors["datas"][0]), Data.fromJSON(vectors["datas"][1]), Data.fromJSON(vectors["datas"][2]) ] #Transactions. transactions: Transactions = Transactions() for data in datas: transactions.add(data) def testBlockchain( i: int ) -> None: def sendMeritRemoval() -> None: #Send the Datas. for data in datas: if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Meros didn't send us the Data.") #Send the Block containing the modified Merit Removal. block: Block = Block.fromJSON(vectors["blockchains"][i][-1]) rpc.meros.liveBlockHeader(block.header) #Flag of if the Block's Body synced. blockBodySynced: bool = False #Handle sync requests. reqHash: bytes = bytes() while True: if blockBodySynced: #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 height is 2. #The genesis Block and the Block granting Merit. try: if rpc.call("merit", "getHeight") != 2: raise Exception() except Exception: raise TestError("Node added a Block containg a repeat MeritRemoval.") #Since the node didn't add the Block, raise SuccessError. raise SuccessError("Node didn't add a Block containing a repeat MeritRemoval.") 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. blockBodySynced = True rpc.meros.blockBody(block) else: raise TestError("Unexpected message sent: " + msg.hex().upper()) Liver( rpc, vectors["blockchains"][i], transactions, callbacks={ 1: sendMeritRemoval } ).live() with raises(SuccessError): for b in range(2): testBlockchain(b)
def PersonalSendTest(rpc: RPC) -> None: #Load the vectors. #Uses the WatchWallet test's vectors for the reasons noted above. 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 the first address from outside the Wallet. First address is now funded. sendHash: bytes = createSend( rpc, claims[0], decodeAddress(rpc.call("personal", "getAddress"))) #Send to the second address with all of the funds. Second address is now funded. #Tests send's minimal case (single input, no change). nextAddr: str = rpc.call("personal", "getAddress") sends: List[str] = [ rpc.call( "personal", "send", { "outputs": [{ "address": nextAddr, "amount": str(claims[0].amount) }] }) ] checkSend( rpc, sends[-1], { "inputs": [{ "hash": sendHash.hex().upper(), "nonce": 0 }], "outputs": [{ "key": decodeAddress(nextAddr).hex().upper(), "amount": str(claims[0].amount) }] }) verify(rpc, bytes.fromhex(sends[-1])) #Send to the third address with some of the funds. Third and change addresses are now funded. #Tests send's capability to create a change output. mnemonic: str = rpc.call("personal", "getMnemonic") nextAddr = rpc.call("personal", "getAddress") sends.append( rpc.call( "personal", "send", { "outputs": [{ "address": nextAddr, "amount": str(claims[0].amount - 1) }] })) checkSend( rpc, sends[-1], { "inputs": [{ "hash": sends[-2], "nonce": 0 }], "outputs": [{ "key": decodeAddress(nextAddr).hex().upper(), "amount": str(claims[0].amount - 1) }, { "key": getChangePublicKey(mnemonic, "", 0).hex().upper(), "amount": "1" }] }) verify(rpc, bytes.fromhex(sends[-1])) #Send all funds out of Wallet. #Tests MuSig signing and change UTXO detection. privKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) pubKey: bytes = privKey.get_verifying_key() sends.append( rpc.call( "personal", "send", { "outputs": [{ "address": bech32_encode("mr", convertbits(bytes([0]) + pubKey, 8, 5)), "amount": str(claims[0].amount) }] })) checkSend( rpc, sends[-1], { "inputs": [{ "hash": sends[-2], "nonce": 0 }, { "hash": sends[-2], "nonce": 1 }], "outputs": [{ "key": pubKey.hex().upper(), "amount": str(claims[0].amount) }] }) verify(rpc, bytes.fromhex(sends[-1])) #Clear Wallet. Set a password this time around to make sure the password is properly carried. #Send two instances of funds to the first address. rpc.call("personal", "setWallet", {"password": "******"}) mnemonic = rpc.call("personal", "getMnemonic") nodeKey: bytes = decodeAddress(rpc.call("personal", "getAddress")) send: Send = Send([(bytes.fromhex(sends[-1]), 0)], [(nodeKey, claims[0].amount // 2), (nodeKey, claims[0].amount // 2)]) send.sign(Ristretto.SigningKey(b'\0' * 32)) send.beat(SpamFilter(3)) if rpc.meros.liveTransaction(send) != rpc.meros.live.recv(): raise TestError("Meros didn't send back a Send.") verify(rpc, send.hash) sends = [send.hash.hex().upper()] #Send to self. #Tests send's capability to handle multiple UTXOs per key/lack of aggregation when all keys are the same/multiple output Sends. nextAddr = rpc.call("personal", "getAddress") changeKey: bytes = getChangePublicKey(mnemonic, "test", 0) sends.append( rpc.call( "personal", "send", { "outputs": [{ "address": nextAddr, "amount": str(claims[0].amount - 1) }], "password": "******" })) checkSend( rpc, sends[-1], { "inputs": [{ "hash": sends[-2], "nonce": 0 }, { "hash": sends[-2], "nonce": 1 }], "outputs": [{ "key": decodeAddress(nextAddr).hex().upper(), "amount": str(claims[0].amount - 1) }, { "key": changeKey.hex().upper(), "amount": "1" }] }) verify(rpc, bytes.fromhex(sends[-1])) #Externally send to the second/change address. #Enables entering multiple instances of each key into MuSig, which is significant as we originally only used the unique keys. sends.append( createSend(rpc, claims[1], decodeAddress(nextAddr)).hex().upper()) sends.append(createSend(rpc, claims[2], changeKey).hex().upper()) #Check personal_getUTXOs. utxos: List[Dict[str, Any]] = [{ "hash": sends[-3], "nonce": 0, "address": nextAddr }, { "hash": sends[-3], "nonce": 1, "address": bech32_encode("mr", convertbits(bytes([0]) + changeKey, 8, 5)) }, { "hash": sends[-2], "nonce": 0, "address": nextAddr }, { "hash": sends[-1], "nonce": 0, "address": bech32_encode("mr", convertbits(bytes([0]) + changeKey, 8, 5)) }] if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos): raise TestError("personal_getUTXOs was incorrect.") for utxo in utxos: del utxo["address"] #Send to any address with all funds minus one. #Test MuSig signing, multiple inputs per key on account chains, change output creation to the next change key... sends.append( rpc.call( "personal", "send", { "outputs": [{ "address": nextAddr, "amount": str(claims[0].amount + claims[1].amount + claims[2].amount - 1) }], "password": "******" })) checkSend( rpc, sends[-1], { "inputs": utxos, "outputs": [{ "key": decodeAddress(nextAddr).hex().upper(), "amount": str(claims[0].amount + claims[1].amount + claims[2].amount - 1) }, { "key": getChangePublicKey(mnemonic, "test", 1).hex().upper(), "amount": "1" }] }) verify(rpc, bytes.fromhex(sends[-1])) #Mine a Block so we can reboot the node without losing data. blsPrivKey: PrivateKey = PrivateKey( bytes.fromhex(rpc.call("personal", "getMeritHolderKey"))) for _ in range(6): template: Dict[str, Any] = rpc.call( "merit", "getBlockTemplate", {"miner": blsPrivKey.toPublicKey().serialize().hex()}) proof: int = -1 tempHash: bytes = bytes() tempSignature: 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( bytes.fromhex(template["header"]) + proof.to_bytes(4, "little")) tempSignature = blsPrivKey.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() }) #Reboot the node and verify it still tracks the same change address. #Also reload the Wallet and verify it still tracks the same change address. #Really should be part of address discovery; we just have the opportunity right here. #Due to the timing of how the codebase was developed, and a personal frustration for how long this has taken... rpc.quit() sleep(3) rpc.meros = Meros(rpc.meros.db, rpc.meros.tcp, rpc.meros.rpc) if rpc.call("personal", "getTransactionTemplate", {"outputs": [{ "address": nextAddr, "amount": "1" }]})["outputs"][1]["key"] != getChangePublicKey( mnemonic, "test", 2).hex().upper(): raise TestError( "Rebooting the node caused the WalletDB to stop tracking the next change address." ) rpc.call("personal", "setAccount", rpc.call("personal", "getAccount")) if rpc.call("personal", "getTransactionTemplate", {"outputs": [{ "address": nextAddr, "amount": "1" }]})["outputs"][1]["key"] != getChangePublicKey( mnemonic, "test", 2).hex().upper(): raise TestError( "Reloading the Wallet caused the WalletDB to stop tracking the next change address." ) raise SuccessError() #Use a late enough block we can instantly verify transactions. with raises(SuccessError): Liver(rpc, vectors["blockchain"], transactions, {50: test}).live()
def BeatenTest(rpc: RPC) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Consensus/Beaten.json", "r") as file: vectors = json.loads(file.read()) sends: List[Send] = [Send.fromJSON(send) for send in vectors["sends"]] verif: SignedVerification = SignedVerification.fromSignedJSON( vectors["verification"]) #Used to get the Block Template. blsPubKey: str = PrivateKey(0).toPublicKey().serialize().hex() 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.") #Sanity check to verify the Block Template contains the Verification. def verifyTemplate() -> None: if bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": blsPubKey })["header"])[36:68] != BlockHeader.createContents( [VerificationPacket(sends[2].hash, [1])]): raise TestError( "Meros didn't add a SignedVerification to the Block Template.") 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", {"hash": 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", {"hash": sends[2].hash.hex()})["verifiers"] != [0]) or (bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": 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", {"hash": 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", "publishTransaction", { "type": "Send", "transaction": 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) Liver(rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"]), callbacks={ 42: sendSends, 43: verifyTemplate, 48: verifyBeaten }).live()
def PublishTransactionTest(rpc: RPC) -> None: privKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) pubKey: bytes = privKey.get_verifying_key() sentToKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\1' * 32) sendFilter: SpamFilter = SpamFilter(3) dataFilter: SpamFilter = SpamFilter(5) vectors: Dict[str, Any] with open("e2e/Vectors/Transactions/ClaimedMint.json", "r") as file: vectors = json.loads(file.read()) transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) if len(transactions.txs) != 1: raise Exception("Transactions DAG doesn't have just the Claim.") claim: Claim = Claim.fromTransaction(next(iter(transactions.txs.values()))) send: Send = Send([(claim.hash, 0)], [(sentToKey.get_verifying_key(), claim.amount)]) send.sign(privKey) send.beat(sendFilter) data: Data = Data(bytes(32), pubKey) data.sign(privKey) data.beat(dataFilter) 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, claim.amount)]) sendSentWithoutWork.sign(sentToKey) sendSentWithoutWork.beat(sendFilter) dataSentWithoutWork: Data = Data(bytes(32), sentToKey.get_verifying_key()) 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()) invalidData.sign(sentToKey) sig: bytes = invalidData.signature newData: bytearray = bytearray(invalidData.data) newData[-1] = newData[-1] ^ 1 #Reconstruct to rehash. invalidData = Data(bytes(32), bytes(newData)) invalidData.signature = sig 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." ) Liver(rpc, vectors["blockchain"][:-1], transactions, { 7: publishAndVerify }).live()
def TGUImmediatelyTest(rpc: RPC) -> 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)) vectors: Dict[str, Any] with open("e2e/Vectors/RPC/Transactions/GetUTXOs.json", "r") as file: vectors = json.loads(file.read()) transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) send: Send = Send.fromJSON(vectors["send"]) spendingSend: Send = Send.fromJSON(vectors["spendingSend"]) def start() -> None: #Send the 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." ) #Immediately spend it. if rpc.meros.liveTransaction(spendingSend) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Send.") if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros didn't consider a Transaction's inputs as spent.") #Verify the Send and make sure it's not considered as a valid UTXO. verify(rpc, send.hash) if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros considered a just confirmed Transaction with a spender's outputs as UTXOs." ) def verified() -> None: #Verify the spender and verify the state is unchanged. verify(rpc, spendingSend.hash) if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros didn't consider a verified Transaction's inputs as spent." ) def finalizedSend() -> None: #Sanity check the spending TX has yet to also finalize. if rpc.call("consensus", "getStatus", {"hash": spendingSend.hash.hex()})["finalized"]: raise Exception( "Test meant to only finalize the first Send, not both.") #Verify the state is unchanged. if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros didn't consider a verified Transaction's inputs as spent after the input finalized." ) 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." ) Liver(rpc, vectors["blockchain"], transactions, { 50: start, 51: verified, 56: finalizedSend, 57: finalizedSpendingSend }).live()
def SameInputTest(rpc: RPC) -> None: file: IO[Any] = open("e2e/Vectors/Transactions/SameInput/Claim.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() merit: Merit = Merit.fromJSON(vectors["blockchain"]) transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) #Custom function to send the last Block and verify it errors at the right place. def checkFail() -> None: #Grab the Block. block: Block = merit.blockchain.blocks[8] #Send the Block. rpc.meros.liveBlockHeader(block.header) #Handle sync requests. while True: 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) 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." ) #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: bytes = msg[1:33] if reqHash not in transactions.txs: raise TestError( "Meros asked for a non-existent Transaction.") rpc.meros.syncTransaction(transactions.txs[reqHash]) #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 an invalid Transaction." ) except Exception: raise TestError("Meros sent a keep-alive.") else: raise TestError("Unexpected message sent: " + msg.hex().upper()) with raises(SuccessError): Liver(rpc, vectors["blockchain"], transactions, callbacks={ 7: checkFail }).live()
from e2e.Classes.Merit.Block import Block from e2e.Classes.Merit.Merit import Blockchain #Ed25519 lib. import ed25519 #Blake2b standard function. from hashlib import blake2b #JSON standard lib. import json cmFile: IO[Any] = open("e2e/Vectors/Transactions/ClaimedMint.json", "r") cmVectors: Dict[str, Any] = json.loads(cmFile.read()) #Transactions. transactions: Transactions = Transactions.fromJSON(cmVectors["transactions"]) #Blockchain. blockchain: Blockchain = Blockchain.fromJSON(cmVectors["blockchain"]) cmFile.close() #Spam Filter. sendFilter: SpamFilter = SpamFilter(3) #Ed25519 keys. edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKeys: List[ed25519.VerifyingKey] = [ edPrivKey.get_verifying_key(), ed25519.SigningKey(b'\1' * 32).get_verifying_key() ] #BLS keys.
def AddressRecoveryTest(rpc: RPC) -> None: mnemonic: str = rpc.call("personal", "getMnemonic") vectors: Dict[str, Any] with open("e2e/Vectors/RPC/Transactions/GetUTXOs.json", "r") as file: vectors = json.loads(file.read()) transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) def test() -> None: #Send to the new address and get the next address. dest: str = rpc.call("personal", "getAddress") last: Send = createSend(rpc, Claim.fromJSON(vectors["newerMintClaim"]), dest) utxos: List[Dict[str, Any]] = rpc.call("personal", "getUTXOs") if utxos != [{ "address": dest, "hash": last.hash.hex().upper(), "nonce": 0 }]: raise TestError( "personal_getUTXOs didn't return the correct UTXOs.") #Set a different mnemonic to verify the tracked addresses is cleared. rpc.call("personal", "setWallet") if rpc.call("personal", "getUTXOs") != []: raise TestError( "Setting a new Mnemonic didn't clear the tracked addresses.") #Reload the Mnemonic and verify the UTXOs are correct. rpc.call("personal", "setWallet", {"mnemonic": mnemonic}) if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos): #This error message points out how no addresses are really being discovered yet; this is account zero's address. #That said, if the test started at the next address, there'd be a gap. #As that's an extra factor, this is tested before gaps are. raise TestError("Meros didn't recover the very first address.") #Now send to the next address and check accuracy. dest = rpc.call("personal", "getAddress") last = createSend(rpc, last, dest) utxos.append({ "address": dest, "hash": last.hash.hex().upper(), "nonce": 0 }) if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos): raise TestError("Meros didn't track an implicitly gotten address.") rpc.call("personal", "setWallet", {"mnemonic": mnemonic}) if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos): raise TestError( "Meros didn't recover the first address after the initial address." ) #Send funds to the address after the next address; tests a gap when discovering addresses. last = createSend(rpc, last, getAddress(mnemonic, "", 3)) if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos): raise TestError( "Meros magically recognized UTXOs as belonging to this Wallet or someone implemented an address cache." ) utxos.append({ "address": getAddress(mnemonic, "", 3), "hash": last.hash.hex().upper(), "nonce": 0 }) rpc.call("personal", "setWallet", {"mnemonic": mnemonic}) if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos): raise TestError( "Meros didn't discover a used address in the Wallet when there was a gap." ) #Finally, anything 10+ unused addresses out shouldn't be recovered. last = createSend(rpc, last, getAddress(mnemonic, "", 14)) rpc.call("personal", "setWallet", {"mnemonic": mnemonic}) if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos): raise TestError( "Meros recovered an address's UTXOs despite it being 10 unused addresses out." ) #Explicitly generating this address should start tracking it though. rpc.call("personal", "getAddress", {"index": getIndex(mnemonic, "", 14)}) utxos.append({ "address": getAddress(mnemonic, "", 14), "hash": last.hash.hex().upper(), "nonce": 0 }) if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos): raise TestError( "personal_getUTXOs didn't track an address explicitly indexed." ) #If asked for an address, Meros should return the 5th (skip 4). #It's the first unused address AFTER used addresss EXCEPT ones explicitly requested. #This can, in the future, be juwst the first unused address/include ones explicitly requested (see DerivationTest for commentary on that). #This is really meant to ensure consistent behavior until we properly decide otherwise. if rpc.call("personal", "getAddress") != getAddress(mnemonic, "", 4): raise TestError( "Meros didn't return the next unused address (with conditions; see comment)." ) #Mine a Block to flush the Transactions and Verifications to disk. sleep(65) rpc.meros.liveConnect(Blockchain().blocks[0].header.hash) mineBlock(rpc) #Existing values used to test getAddress/getUTXOs consistency. #The former is thoroughly tested elsewhere, making it quite redundant. existing: Dict[str, Any] = { "getAddress": rpc.call("personal", "getAddress"), "getUTXOs": rpc.call("personal", "getUTXOs") } #Reboot the node and verify consistency. rpc.quit() sleep(3) rpc.meros = Meros(rpc.meros.db, rpc.meros.tcp, rpc.meros.rpc) if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs( existing["getUTXOs"]): raise TestError( "Rebooting the node caused the WalletDB to improperly reload UTXOs." ) if rpc.call("personal", "getAddress") != existing["getAddress"]: raise TestError( "Rebooting the node caused the WalletDB to improperly reload the next address." ) #Used so Liver doesn't run its own post-test checks. #Since we added our own Blocks, those will fail. raise SuccessError() #Used so we don't have to write a sync loop. with raises(SuccessError): Liver(rpc, vectors["blockchain"], transactions, {50: test}).live()
def BroadcastTest( rpc: RPC ) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Transactions/ClaimedMint.json", "r") as file: vectors = json.loads(file.read()) transactions: Transactions = Transactions.fromJSON(vectors["transactions"]) claim: Claim = Claim.fromTransaction(iter(transactions.txs.values()).__next__()) mint: bytes = claim.inputs[0][0] 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.") #Create and execute a Liver. Liver(rpc, vectors["blockchain"], transactions, callbacks={8: test}).live()
#Ed25519 lib. import ed25519 #Blake2b standard function. from hashlib import blake2b #JSON standard lib. import json #Blank Blocks. bbFile: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r") blankBlocks: List[Dict[str, Any]] = json.loads(bbFile.read()) bbFile.close() #Transactions. transactions: Transactions = Transactions() #Merit. merit: Merit = Merit() #SpamFilter. dataFilter: SpamFilter = SpamFilter(5) #Ed25519 keys. edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() #BLS keys. blsPrivKey: PrivateKey = PrivateKey(blake2b(b'\0', digest_size=32).digest()) blsPubKey: PublicKey = blsPrivKey.toPublicKey() #Add 1 Blank Block.
def WatchWalletTest(rpc: RPC) -> None: #Keys to send funds to later. keys: List[bytes] = [ Ristretto.SigningKey(i.to_bytes(1, "little") * 32).get_verifying_key() 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.") #Also test balance getting. if rpc.call("personal", "getBalance") != str( sum([ int( rpc.call("transactions", "getTransaction", {"hash": send.hex()})["outputs"][0]["amount"]) for send in sends[:2] ])): raise TestError("WatchWallet Meros couldn't get its balance.") #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.") #Again test the balance. if rpc.call("personal", "getBalance") != str( sum([ int( rpc.call("transactions", "getTransaction", {"hash": send.hex()})["outputs"][0]["amount"]) for send in sends[:3] ])): raise TestError("WatchWallet Meros couldn't get its balance.") #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()