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 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 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 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 DescendantHighestVerifiedParentTest( rpc: RPC ) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Consensus/Families/DescendantHighestVerifiedParent.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 verifyDescendantWon() -> None: for send in sends[::3]: if rpc.call("consensus", "getStatus", [send.hash.hex()])["verified"]: raise TestError("Meros verified a beaten, or potentially impossible, transaction.") for send in sends[1:3]: if not rpc.call("consensus", "getStatus", [send.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: verifyDescendantWon } ).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 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 HundredSeventyFiveTest(rpc: RPC) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Merit/HundredSeventyFive.json", "r") as file: vectors = json.loads(file.read()) 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={ 7: sendDatasAndVerif }).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()
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 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()
def TGUUnverifyTest(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)) otherRecipient: bytes = ed25519.SigningKey( b'\2' * 32).get_verifying_key().to_bytes() otherAddress: str = bech32_encode( "mr", convertbits(bytes([0]) + otherRecipient, 8, 5)) #Create a Send. send: Send = Send.fromJSON(vectors["send"]) if rpc.meros.liveTransaction(send) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Send.") if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros considered an unconfirmed Transaction's outputs as UTXOs." ) verify(rpc, send.hash) #Finalize the parent. for _ in range(6): mineBlock(rpc) #Spend it. spendingSend: Send = Send.fromJSON(vectors["spendingSend"]) if rpc.meros.liveTransaction(spendingSend) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Send.") verify(rpc, spendingSend.hash) if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros didn't consider a verified Transaction's inputs as spent." ) if rpc.call("transactions", "getUTXOs", {"address": otherAddress}) != [ { "hash": spendingSend.hash.hex().upper(), "nonce": 0 } ]: raise TestError( "Meros didn't consider a verified Transaction's outputs as UTXOs." ) #Unverify the spending Send. This would also unverify the parent if it wasn't finalized. #This is done via causing a Merit Removal. #Uses two competing Datas to not change the Send's status to competing. datas: List[Data] = [Data(bytes(32), recipientPub)] for _ in range(2): datas.append(Data(datas[0].hash, datas[-1].hash)) for data in datas: data.sign(recipient) data.beat(SpamFilter(5)) if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Data.") verify(rpc, data.hash, mr=(datas[-1].hash == data.hash)) #Verify the MeritRemoval happened and the spending Send is no longer verified. #These first two checks are more likely to symbolize a failure in testing methodology than Meros. if not rpc.call("merit", "getMerit", {"nick": 0})["malicious"]: raise TestError("Meros didn't create a Merit Removal.") if not rpc.call("consensus", "getStatus", {"hash": send.hash.hex()})["verified"]: raise TestError("Finalized Transaction became unverified.") if rpc.call("consensus", "getStatus", {"hash": spendingSend.hash.hex()})["verified"]: raise TestError( "Meros didn't unverify a Transaction which is currently below the required threshold." ) #Even after unverification, since the Transaction still exists, the input shouldn't be considered a UTXO. if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros didn't consider a unverified yet existing Transaction's inputs as spent." ) #That said, its outputs should no longer be considered a UTXO. if rpc.call("transactions", "getUTXOs", {"address": otherAddress}) != []: raise TestError( "Meros considered a unverified Transaction's outputs as UTXOs." ) raise SuccessError() #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 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 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 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 CompetingFinalizedTest(rpc: RPC) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Transactions/CompetingFinalized.json", "r") as file: vectors = json.loads(file.read()) 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: #This Block should cause the node to disconnect us AFTER it syncs our Transaction. syncedTX: bool = False block: Block = merit.blockchain.blocks[-1] rpc.meros.liveBlockHeader(block.header) rpc.meros.handleBlockBody(block) #Handle sync requests. reqHash: bytes = bytes() while True: if syncedTX: #Try receiving from the Live socket, where Meros sends keep-alives. try: if len(rpc.meros.live.recv()) != 0: raise Exception() except TestError: raise SuccessError( "Node disconnected us after we sent an invalid Transaction." ) except Exception: raise TestError("Meros sent a keep-alive.") msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.SketchHashesRequest: if not block.body.packets: raise TestError( "Meros asked for Sketch Hashes from a Block without any." ) reqHash = msg[1:33] if reqHash != block.header.hash: raise TestError( "Meros asked for Sketch Hashes that didn't belong to the Block we just sent it." ) #Create the haashes. hashes: List[int] = [] for packet in block.body.packets: hashes.append(Sketch.hash(block.header.sketchSalt, packet)) #Send the Sketch Hashes. rpc.meros.sketchHashes(hashes) elif MessageType(msg[0]) == MessageType.SketchHashRequests: if not block.body.packets: raise TestError( "Meros asked for Verification Packets from a Block without any." ) reqHash = msg[1:33] if reqHash != block.header.hash: raise TestError( "Meros asked for Verification Packets that didn't belong to the Block we just sent it." ) #Create a lookup of hash to packets. packets: Dict[int, VerificationPacket] = {} for packet in block.body.packets: packets[Sketch.hash(block.header.sketchSalt, packet)] = packet #Look up each requested packet and respond accordingly. for h in range(int.from_bytes(msg[33:37], byteorder="little")): sketchHash: int = int.from_bytes(msg[37 + (h * 8):45 + (h * 8)], byteorder="little") if sketchHash not in packets: raise TestError( "Meros asked for a non-existent Sketch Hash.") rpc.meros.packet(packets[sketchHash]) elif MessageType(msg[0]) == MessageType.TransactionRequest: reqHash = msg[1:33] if reqHash not in transactions.txs: raise TestError( "Meros asked for a non-existent Transaction.") rpc.meros.syncTransaction(transactions.txs[reqHash]) syncedTX = True else: raise TestError("Unexpected message sent: " + msg.hex().upper()) with raises(SuccessError): Liver(rpc, vectors["blockchain"], transactions, callbacks={ 7: checkFail }).live()
def GetBlockTest(rpc: RPC) -> None: blockchain: Blockchain claim: Claim send: Send datas: List[Data] txKey: Callable[[Dict[str, Any]], str] = lambda tx: tx["hash"] def verify() -> None: for b in range(len(blockchain.blocks)): block: Dict[str, Any] = rpc.call( "merit", "getBlock", {"block": blockchain.blocks[b].header.hash.hex().upper()}, False) if rpc.call("merit", "getBlock", {"block": b}, False) != block: raise TestError( "Meros reported different Blocks depending on if nonce/hash indexing." ) #Python doesn't keep track of the removals. #That said, they should all be empty except for the last one. if b != (len(blockchain.blocks) - 1): if block["removals"] != []: raise TestError("Meros reported the Block had removals.") del block["removals"] if blockchain.blocks[b].toJSON() != block: raise TestError( "Meros's JSON serialization of Blocks differs from Python's." ) #Test the key serialization of the first Block. #The final Block uses a nick, hence the value in this. if rpc.call("merit", "getBlock", {"block": 1}, False)["header"]["miner"] != PrivateKey( 0).toPublicKey().serialize().hex().upper(): raise TestError("Meros didn't serialize a miner's key properly.") #Manually test the final, and most complex, block. final: Dict[str, Any] = rpc.call("merit", "getBlock", {"block": len(blockchain.blocks) - 1}, False) final["transactions"].sort(key=txKey) final["removals"].sort() if final != { "hash": blockchain.blocks[-1].header.hash.hex().upper(), "header": { "version": blockchain.blocks[-1].header.version, "last": blockchain.blocks[-1].header.last.hex().upper(), "contents": blockchain.blocks[-1].header.contents.hex().upper(), "packets": blockchain.blocks[-1].header.packetsQuantity, "sketchSalt": blockchain.blocks[-1].header.sketchSalt.hex().upper(), "sketchCheck": blockchain.blocks[-1].header.sketchCheck.hex().upper(), "miner": blockchain.blocks[-1].header.minerKey.hex().upper() if blockchain.blocks[-1].header.newMiner else blockchain.blocks[-1].header.minerNick, "time": blockchain.blocks[-1].header.time, "proof": blockchain.blocks[-1].header.proof, "signature": blockchain.blocks[-1].header.signature.hex().upper() }, "transactions": sorted([{ "hash": claim.hash.hex().upper(), "holders": [0] }, { "hash": send.hash.hex().upper(), "holders": [0, 1, 2] }, { "hash": datas[0].hash.hex().upper(), "holders": [0, 2] }, { "hash": datas[1].hash.hex().upper(), "holders": [0, 1, 3] }, { "hash": datas[2].hash.hex().upper(), "holders": [0, 1, 2, 3, 4] }, { "hash": datas[3].hash.hex().upper(), "holders": [0, 1, 2, 3] }], key=txKey), "elements": [ { "descendant": "DataDifficulty", "holder": 3, "nonce": 0, "difficulty": 8 }, { "descendant": "SendDifficulty", "holder": 0, "nonce": 0, "difficulty": 1 }, { "descendant": "DataDifficulty", "holder": 3, "nonce": 0, "difficulty": 4 }, { "descendant": "DataDifficulty", "holder": 4, "nonce": 2, "difficulty": 1 }, { "descendant": "SendDifficulty", "holder": 4, "nonce": 1, "difficulty": 3 }, { "descendant": "SendDifficulty", "holder": 2, "nonce": 1, "difficulty": 2 }, { "descendant": "DataDifficulty", "holder": 0, "nonce": 0, "difficulty": 7 }, ], "removals": [0, 3], "aggregate": blockchain.blocks[-1].body.aggregate.serialize().hex().upper() }: raise TestError("Final Block wasn't correct.") #Test invalid calls. try: rpc.call("merit", "getBlock", {"block": 100}, False) raise Exception("") except Exception as e: if str(e) != "-2 Block not found.": raise TestError( "getBlock didn't error when we used a non-existent nonce.") try: rpc.call("merit", "getBlock", {"block": -5}, False) raise Exception("") except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError( "getBlock didn't error when we used a negative (signed) integer for a nonce." ) try: rpc.call( "merit", "getBlock", { "block": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, False) raise Exception("") except Exception as e: if str(e) != "-2 Block not found.": raise TestError( "getBlock didn't error when we used a non-existent hash.") try: rpc.call("merit", "getBlock", {"block": ""}, False) raise Exception("") except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError( "getBlock didn't error when we used an invalid hash.") with open("e2e/Vectors/RPC/Merit/GetBlock.json", "r") as file: vectors: Dict[str, Any] = json.loads(file.read()) blockchain = Blockchain.fromJSON(vectors["blockchain"]) claim = Claim.fromJSON(vectors["claim"]) send = Send.fromJSON(vectors["send"]) datas = [Data.fromJSON(data) for data in vectors["datas"]] transactions: Transactions = Transactions.fromJSON( vectors["transactions"]) Liver(rpc, vectors["blockchain"], transactions, { (len(blockchain.blocks) - 1): verify }).live()
def GetTransactionTest(rpc: RPC) -> None: privKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) pubKey: bytes = privKey.get_verifying_key() 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)], [(Ristretto.SigningKey(b'\1' * 32).get_verifying_key(), claim.amount)]) send.sign(privKey) send.beat(sendFilter) data: Data = Data(bytes(32), pubKey) data.sign(privKey) data.beat(dataFilter) def sendAndVerify() -> None: rpc.meros.liveTransaction(send) rpc.meros.liveTransaction(data) rpc.meros.live.recv() rpc.meros.live.recv() #We now have a Mint, a Claim, a Send, a Data, a lion, a witch, and a wardrobe. #Check the Mint. mint: Mint = Merit.fromJSON(vectors["blockchain"]).mints[0] #pylint: disable=invalid-name EXPECTED_MINT: Dict[str, Any] = { "descendant": "Mint", "inputs": [], "outputs": [{ "amount": str(txOutput[1]), "nick": txOutput[0] } for txOutput in mint.outputs], "hash": mint.hash.hex().upper() } #Also sanity check against the in-house JSON. if mint.toJSON() != EXPECTED_MINT: raise TestError("Python's Mint toJSON doesn't match the spec.") if rpc.call("transactions", "getTransaction", {"hash": mint.hash.hex()}, False) != EXPECTED_MINT: raise TestError("getTransaction didn't report the Mint properly.") #Check the Claim. #pylint: disable=invalid-name EXPECTED_CLAIM: Dict[str, Any] = { "descendant": "Claim", "inputs": [{ "hash": txInput[0].hex().upper(), "nonce": txInput[1] } for txInput in claim.inputs], "outputs": [{ "amount": str(claim.amount), "key": claim.output.hex().upper() }], "hash": claim.hash.hex().upper(), "signature": claim.signature.hex().upper() } if claim.amount == 0: raise Exception( "Python didn't instantiate the Claim with an amount, leading to invalid testing methodology." ) if claim.toJSON() != EXPECTED_CLAIM: raise TestError("Python's Claim toJSON doesn't match the spec.") if rpc.call("transactions", "getTransaction", {"hash": claim.hash.hex()}, False) != EXPECTED_CLAIM: raise TestError("getTransaction didn't report the Claim properly.") #Check the Send. #pylint: disable=invalid-name EXPECTED_SEND: Dict[str, Any] = { "descendant": "Send", "inputs": [{ "hash": txInput[0].hex().upper(), "nonce": txInput[1] } for txInput in send.inputs], "outputs": [{ "amount": str(txOutput[1]), "key": txOutput[0].hex().upper() } for txOutput in send.outputs], "hash": send.hash.hex().upper(), "signature": send.signature.hex().upper(), "proof": send.proof } if send.toJSON() != EXPECTED_SEND: raise TestError("Python's Send toJSON doesn't match the spec.") if rpc.call("transactions", "getTransaction", {"hash": send.hash.hex()}, False) != EXPECTED_SEND: raise TestError("getTransaction didn't report the Send properly.") #Check the Data. #pylint: disable=invalid-name EXPECTED_DATA: Dict[str, Any] = { "descendant": "Data", "inputs": [{ "hash": data.txInput.hex().upper() }], "outputs": [], "hash": data.hash.hex().upper(), "data": data.data.hex().upper(), "signature": data.signature.hex().upper(), "proof": data.proof } if data.toJSON() != EXPECTED_DATA: raise TestError("Python's Data toJSON doesn't match the spec.") if rpc.call("transactions", "getTransaction", {"hash": data.hash.hex()}, False) != EXPECTED_DATA: raise TestError("getTransaction didn't report the Data properly.") #Non-existent hash; should cause an IndexError nonExistentHash: str = data.hash.hex() if data.hash[0] == "0": nonExistentHash = "1" + nonExistentHash[1:] else: nonExistentHash = "0" + nonExistentHash[1:] try: rpc.call("transactions", "getTransaction", {"hash": nonExistentHash}, False) except TestError as e: if str(e) != "-2 Transaction not found.": raise TestError( "getTransaction didn't raise IndexError on a non-existent hash." ) #Invalid argument; should cause a ParamError #This is still a hex value try: rpc.call("transactions", "getTransaction", {"hash": "00" + data.hash.hex()}, False) raise TestError( "Meros didn't error when we asked for a 33-byte hex value.") except TestError as e: if str(e) != "-32602 Invalid params.": raise TestError( "getTransaction didn't raise on invalid parameters.") Liver(rpc, vectors["blockchain"], transactions, {8: sendAndVerify}).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 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 OOOPacketTest(rpc: RPC) -> None: with open("e2e/Vectors/Merit/OutOfOrder/Packets.json", "r") as file: vectors: Dict[str, Any] = json.loads(file.read()) Liver(rpc, vectors["blockchain"], Transactions.fromJSON(vectors["transactions"])).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()
def GetAddressTest(rpc: RPC) -> None: password: str = "password since it shouldn't be relevant" rpc.call("personal", "setWallet", {"password": password}) mnemonic: str = rpc.call("personal", "getMnemonic") #Test getAddress. Doesn't test specific indexing, as that's handled by DerivationTest. #Not only does getAddress need to correctly derive addresses along the external chain, it needs to return new addresses. #That said, new is defined by use; use on the network. If it has a TX sent to it, it's used. #This is different than checking for UTXOs because that means any address no longer having UTXOs would be considered new again. expected: str = getAddress(mnemonic, password, 0) if rpc.call("personal", "getAddress") != expected: raise TestError( "getAddress didn't return the next unused address (the first one)." ) #It should be returned again given it's still unused. if rpc.call("personal", "getAddress") != expected: raise TestError( "getAddress didn't return the same address when there was a lack of usage." ) #Reboot the node and verify consistency around the initial address. #Added due to an edge case that appeared. rpc.quit() sleep(3) rpc.meros = Meros(rpc.meros.db, rpc.meros.tcp, rpc.meros.rpc) if rpc.call("personal", "getAddress") != expected: raise TestError( "getAddress didn't return the initial address after a reboot.") #Send enough Blocks to have funds available to continue testing. 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"]) def restOfTest() -> None: #Move expected into scope. expected: str = getAddress(mnemonic, password, 0) #Send to the new address, then call getAddress again. Verify a new address appears. last: Send = createSend( rpc, Claim.fromTransaction(iter(transactions.txs.values()).__next__()), expected) hashes: List[bytes] = [last.hash] expected = getAddress(mnemonic, password, 1) if rpc.call("personal", "getAddress") != expected: raise TestError( "Meros didn't move to the next address once the existing one was used." ) #Send to the new unused address, spending the funds before calling getAddress again. #Checks address usage isn't defined as having an UTXO, yet rather any TXO. #Also confirm the spending TX with full finalization before checking. #Ensures the TXO isn't unspent by any definition. last = createSend(rpc, last, expected) hashes.append(last.hash) #Spending TX. send: Send = Send([(hashes[-1], 0)], [(bytes(32), 1)]) send.signature = ed.sign(b"MEROS" + send.hash, getPrivateKey(mnemonic, password, 1)) send.beat(SpamFilter(3)) if rpc.meros.liveTransaction(send) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back the spending Send.") hashes.append(send.hash) #In order to finalize, we need to mine 6 Blocks once this Transaction and its parent have Verifications. for txHash in hashes: sv: SignedVerification = SignedVerification(txHash) sv.sign(0, PrivateKey(0)) if rpc.meros.signedElement(sv) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Verification.") #Close the sockets while we mine. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() #Mine these to the Wallet on the node so we can test getMeritHolderNick. privKey: PrivateKey = PrivateKey( bytes.fromhex(rpc.call("personal", "getMeritHolderKey"))) blockchain: Blockchain = Blockchain.fromJSON(vectors["blockchain"]) for _ in range(6): template: Dict[str, Any] = rpc.call( "merit", "getBlockTemplate", {"miner": privKey.toPublicKey().serialize().hex()}) proof: int = -1 tempHash: bytes = bytes() tempSignature: bytes = bytes() while ((proof == -1) or ((int.from_bytes(tempHash, "little") * (blockchain.difficulty() * 11 // 10)) > int.from_bytes( bytes.fromhex("FF" * 32), "little"))): proof += 1 tempHash = RandomX( bytes.fromhex(template["header"]) + proof.to_bytes(4, "little")) tempSignature = privKey.sign(tempHash).serialize() tempHash = RandomX(tempHash + tempSignature) rpc.call( "merit", "publishBlock", { "id": template["id"], "header": template["header"] + proof.to_bytes(4, "little").hex() + tempSignature.hex() }) blockchain.add( Block.fromJSON( rpc.call("merit", "getBlock", {"block": len(blockchain.blocks)}))) #Verify a new address is returned. expected = getAddress(mnemonic, password, 2) if rpc.call("personal", "getAddress") != expected: raise TestError( "Meros didn't move to the next address once the existing one was used." ) #Get a new address after sending to the address after it. #Use both, and then call getAddress. #getAddress should detect X is used, move to Y, detect Y is used, and move to Z. #It shouldn't assume the next address after an used address is unused. #Actually has two Ys as one iteration of the code only ran for the next address; not all future addresses. #Send to the next next addresses. for i in range(2): addy: str = getAddress(mnemonic, password, 3 + i) #Reopen the sockets. This isn't done outside of the loop due to the time deriving the final address can take. #Due to how slow the reference Python code is, it is necessary to redo the socket connections. sleep(65) rpc.meros.liveConnect(Blockchain().blocks[0].header.hash) rpc.meros.syncConnect(Blockchain().blocks[0].header.hash) last = createSend(rpc, last, addy) if MessageType(rpc.meros.live.recv() [0]) != MessageType.SignedVerification: raise TestError( "Meros didn't create and broadcast a SignedVerification for this Send." ) if i == 0: #Close them again. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() #Verify getAddress returns the existing next address. if rpc.call("personal", "getAddress") != expected: raise TestError( "Sending to the address after this address caused Meros to consider this address used." ) #Send to the next address. last = createSend(rpc, last, expected) if MessageType( rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError( "Meros didn't create and broadcast a SignedVerification for this Send." ) #Verify getAddress returns the address after the next next addresses. expected = getAddress(mnemonic, password, 5) if rpc.call("personal", "getAddress") != expected: raise TestError( "Meros didn't return the correct next address after using multiple addresses in a row." ) #Now that we have mined a Block as part of this test, ensure the Merit Holder nick is set. if rpc.call("personal", "getMeritHolderNick") != 1: raise TestError( "Merit Holder nick wasn't made available despite having one.") #Sanity check off Mnemonic. if rpc.call("personal", "getMnemonic") != mnemonic: raise TestError("getMnemonic didn't return the correct Mnemonic.") #Existing values used to test getMnemonic/getMeritHolderKey/getMeritHolderNick/getAccount/getAddress consistency. existing: Dict[str, Any] = { #Should be equal to the mnemonic variable, which is verified in a check above. "getMnemonic": rpc.call("personal", "getMnemonic"), "getMeritHolderKey": rpc.call("personal", "getMeritHolderKey"), "getMeritHolderNick": rpc.call("personal", "getMeritHolderNick"), "getAccount": rpc.call("personal", "getAccount"), #Should be equal to expected, which is also verified in a check above. "getAddress": rpc.call("personal", "getAddress") } #Set a new seed and verify the Merit Holder nick is cleared. rpc.call("personal", "setWallet") try: rpc.call("personal", "getMeritHolderNick") raise TestError("") except TestError as e: if str( e ) != "-2 Wallet doesn't have a Merit Holder nickname assigned.": raise TestError( "getMeritHolderNick returned something or an unexpected error when a new Mnemonic was set." ) #Set back the old seed and verify consistency. rpc.call("personal", "setWallet", { "mnemonic": mnemonic, "password": password }) for method in existing: if rpc.call("personal", method) != existing[method]: raise TestError( "Setting an old seed caused the WalletDB to improperly reload." ) #Verify calling getAddress returns the expected address. if rpc.call("personal", "getAddress") != expected: raise TestError( "Meros returned an address that wasn't next after reloading the seed." ) #Reboot the node and verify consistency. rpc.quit() sleep(3) rpc.meros = Meros(rpc.meros.db, rpc.meros.tcp, rpc.meros.rpc) for method in existing: if rpc.call("personal", method) != existing[method]: raise TestError( "Rebooting the node caused the WalletDB to improperly reload." ) #Used so Liver doesn't run its own post-test checks. #Since we added our own Blocks, those will fail. raise SuccessError() #Used so we don't have to write a sync loop. with raises(SuccessError): Liver(rpc, vectors["blockchain"], transactions, {8: restOfTest}).live()