def HundredEightySevenTest( meros: Meros ) -> None: file: IO[Any] = open("e2e/Vectors/Merit/HundredEightySeven.json", "r") vectors: List[Dict[str, Any]] = json.loads(file.read()) file.close() meros.liveConnect(Blockchain().last()) meros.syncConnect(Blockchain().last()) block: Block = Block.fromJSON(vectors[0]) sent: bytes = meros.liveBlockHeader(block.header) if meros.sync.recv() != MessageType.BlockBodyRequest.toByte() + block.header.hash: raise TestError("Meros didn't request the matching BlockBody.") meros.blockBody(block) if meros.live.recv() != sent: raise TestError("Meros didn't broadcast a BlockHeader.") meros.liveBlockHeader(Block.fromJSON(vectors[1]).header) with raises(SuccessError): try: if len(meros.live.recv()) != 0: raise Exception() except TestError: sleep(1) if meros.process.poll() is not None: raise TestError("Node crashed trying to handle a BlockHeader which re-registers a key.") raise SuccessError("Node disconnected us after we sent a BlockHeader which re-registers a key.") except Exception: raise TestError("Meros didn't disconnect us after we sent a BlockHeader which re-registers a key; it also didn't crash.")
def LANPeersTest(meros: Meros) -> None: #Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() #Handshake with the node. meros.syncConnect(blockchain.blocks[0].header.hash) #Verify that sending a PeersRequest returns 0 peers. meros.peersRequest() if len(meros.sync.recv(True)) != 2: raise TestError("Meros sent peers.") #Create a new connection which identifies as a server. serverConnection: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverConnection.connect(("127.0.0.1", meros.tcp)) serverConnection.send( MessageType.Syncing.toByte() + (254).to_bytes(1, "little") + (254).to_bytes(1, "little") + (128).to_bytes(1, "little") + (6000).to_bytes(2, "little") + blockchain.blocks[0].header.hash, False) serverConnection.recv(38) sleep(1) #Verify Meros ignores us as a peer since we're only available over the local network. meros.peersRequest() res: bytes = meros.sync.recv(True) if len(res) != 2: raise TestError("Meros sent peers.") #Close the new connection. serverConnection.close()
def reset(self) -> None: self.quit() sleep(3) try: remove("./data/e2e/devnet-" + self.meros.db) except FileNotFoundError: pass try: remove("./data/e2e/devnet-" + self.meros.db + "-lock") except FileNotFoundError: pass self.meros = Meros(self.meros.db, self.meros.tcp, self.meros.rpc)
def meros( #pylint: disable=redefined-outer-name dataDir: str, request: Any, worker_id: str) -> Meros: #If xdist is disabled, the worker_id will return "master" and #"gw1", "gw2", ... otherwise index: int = 1 if worker_id.startswith("gw"): index += int(worker_id[2:]) result: Meros = Meros(request.node.module.__name__, request.param + (2 * index), request.param + (2 * index) + 1, dataDir) request.addfinalizer(result.quit) return result
def reset(self) -> None: #Quit Meros. self.quit() sleep(2) #Remove the existing DB files. remove("./data/e2e/devnet-" + self.meros.db) remove("./data/e2e/devnet-" + self.meros.db + "-lock") #Launch Meros. self.meros = Meros(self.meros.db, self.meros.tcp, self.meros.rpc) sleep(5) #Reconnect. self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect(("127.0.0.1", self.meros.rpc))
def reset(self) -> None: self.quit() sleep(3) try: remove("./data/e2e/devnet-" + self.meros.db) except FileNotFoundError: pass try: remove("./data/e2e/devnet-" + self.meros.db + "-lock") except FileNotFoundError: pass self.meros = Meros(self.meros.db, self.meros.tcp, self.meros.rpc) sleep(5) self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.connect(("127.0.0.1", self.meros.rpc))
def TwoHundredSeventyThreeTest(meros: Meros) -> None: blockchain: Blockchain = Blockchain() vectors: Dict[str, Any] with open("e2e/Vectors/Merit/TwoHundredSeventyThree.json", "r") as file: vectors = json.loads(file.read()) header: BlockHeader = BlockHeader.fromJSON(vectors) meros.liveConnect(blockchain.last()) meros.syncConnect(blockchain.last()) #Sanity check on the behavior of select. readable, _, _ = select([meros.live.connection, meros.sync.connection], [], [], 65) if len(readable) != 1: raise Exception( "Misuse of select; multiple sockets reported readable.") if MessageType(meros.live.recv()[0]) != MessageType.Handshake: raise Exception( "Misuse of select; it didn't return the live socket trying to Handshake. Keep-alives could also be broken." ) meros.live.send(MessageType.BlockchainTail.toByte() + blockchain.last()) #Send the header. meros.liveBlockHeader(header) #Meros should disconnect us immediately. If it doesn't, it'll either send a keep-alive or a BlockBodyRequest. #One is inefficient as it doesn't properly protect against spam attacks. #One is invalid completely. readable, _, _ = select([meros.live.connection, meros.sync.connection], [], [], 65) #On Linux, both sockets immediately appear as readable. #That is why we iterate, instead of just checking length == 0. for s in readable: try: temp: str = s.recv(1) if len(temp) != 0: raise TestError( "Meros tried to send us something instead of immediately disconnecting us." ) except TestError as e: raise e except Exception: pass
def LANPeersTest(meros: Meros) -> None: #Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() #Handshake with the node. meros.syncConnect(blockchain.blocks[0].header.hash) #Verify that sending a PeersRequest returns 0 peers. meros.peersRequest() if len(meros.sync.recv(True)) != 2: raise TestError("Meros sent peers.") #Create a server socket. server: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("", 0)) server.listen(1) #Connect again. #Meros, if it doesn't realize we're a LAN peer, will try to connect to the above server to verify. #Since we're a LAN peer, it shouldn't bother. #Doesn't use MerosSocket so we can specify the port. serverConnection: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverConnection.connect(("127.0.0.1", meros.tcp)) serverConnection.send( MessageType.Syncing.toByte() + meros.protocol.to_bytes(1, "little") + meros.network.to_bytes(1, "little") + (0).to_bytes(1, "little") + server.getsockname()[1].to_bytes(2, "little") + blockchain.blocks[0].header.hash, False) serverConnection.recv(38) sleep(1) #Verify Meros ignores us as a peer since we're only available over the local network. meros.peersRequest() res: bytes = meros.sync.recv(True) if len(res) != 2: raise TestError("Meros sent peers.") #Close the new connection. serverConnection.close()
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()
def PersonalDataTest(rpc: RPC) -> None: #Create a Data. firstData: str = rpc.call("personal", "data", {"data": "a"}) initial: str = checkData(rpc, firstData, b"a") #Meros should've also created an initial Data. if checkData(rpc, initial, decodeAddress(rpc.call( "personal", "getAddress"))) != bytes(32).hex(): raise TestError("Initial Data didn't have a 0-hash input.") #Create a Data using hex data. Also tests upper case hex. if checkData(rpc, rpc.call("personal", "data", { "data": "AABBCC", "hex": True }), b"\xAA\xBB\xCC") != firstData: raise TestError( "Newly created Data wasn't a descendant of the existing Data.") #Should support using 256 bytes of Data. Also tests lower case hex. checkData( rpc, rpc.call("personal", "data", { "data": bytes([0xaa] * 256).hex(), "hex": True }), bytes([0xaa] * 256)) #Should properly error when we input no data. All Datas must have at least 1 byte of Data. try: rpc.call("personal", "data", {"data": ""}) raise Exception() except Exception as e: if str(e) != "-3 Data is too small or too large.": raise TestError("Meros didn't handle Data that was too small.") #Should properly error when we supply more than 256 bytes of data. try: rpc.call("personal", "data", {"data": "a" * 257}) raise Exception() except Exception as e: if str(e) != "-3 Data is too small or too large.": raise TestError("Meros didn't handle Data that was too large.") #Should properly error when we supply non-hex data with the hex flag. try: rpc.call("personal", "data", {"data": "zz", "hex": True}) raise Exception() except Exception as e: if str(e) != "-3 Invalid hex char `z` (ord 122).": raise TestError("Meros didn't properly handle invalid hex.") #Should properly error when we supply non-even hex data. try: rpc.call("personal", "data", {"data": "a", "hex": True}) raise Exception() except Exception as e: if str(e) != "-3 Incorrect hex string len.": raise TestError("Meros didn't properly handle non-even hex.") #Test Datas when the Wallet has a password. rpc.call("personal", "setWallet", {"password": "******"}) #Shouldn't work due to the lack of a password. try: rpc.call("personal", "data", {"data": "abc"}) raise Exception() except Exception as e: if str(e) != "-3 Invalid password.": raise TestError( "Meros didn't properly handle creating a Data without a password." ) #Should work due to the existence of a password. lastData: str = rpc.call("personal", "data", { "data": "abc", "password": "******" }) checkData(rpc, lastData, b"abc") #Reboot the node and verify we can create a new Data without issue. rpc.quit() sleep(3) rpc.meros = Meros(rpc.meros.db, rpc.meros.tcp, rpc.meros.rpc) if checkData( rpc, rpc.call("personal", "data", { "data": "def", "password": "******" }), b"def") != lastData: raise TestError("Couldn't create a new Data after rebooting.")
def OneHundredPercentSketchTest(meros: Meros) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Merit/Sketches/OneHundredPercent.json", "r") as file: vectors = json.loads(file.read()) blockchain: Blockchain = Blockchain.fromJSON(vectors["blockchain"]) meros.liveConnect(blockchain.blocks[0].header.hash) meros.syncConnect(blockchain.blocks[0].header.hash) header: bytes = meros.liveBlockHeader(blockchain.blocks[1].header) meros.handleBlockBody(blockchain.blocks[1]) if meros.live.recv() != header: raise TestError( "Meros didn't broadcast a BlockHeader for a Block it just added.") for data in vectors["datas"]: if meros.liveTransaction(Data.fromJSON(data)) != meros.live.recv(): raise TestError("Meros didn't broadcast back a Data Transaction.") for verif in vectors["verifications"]: if meros.signedElement( SignedVerification.fromSignedJSON(verif)) != meros.live.recv(): raise TestError("Meros didn't broadcast back a Verification.") header = meros.liveBlockHeader(blockchain.blocks[2].header) meros.handleBlockBody(blockchain.blocks[2]) if meros.live.recv() != header: raise TestError( "Meros didn't broadcast a BlockHeader for a Block it just added.")
def BusyTest(meros: Meros) -> None: #Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() #Handshake with the node. meros.syncConnect(blockchain.blocks[0].header.hash) #Create two new server sockets. def createServerSocket() -> socket.socket: result: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) result.bind(("127.0.0.1", 0)) result.listen(2) return result busyServer: socket.socket = createServerSocket() server: socket.socket = createServerSocket() #Receive Syncing until Meros asks for peers. while True: res = meros.sync.recv() if MessageType(res[0]) == MessageType.Syncing: meros.sync.send(MessageType.BlockchainTail.toByte() + blockchain.blocks[0].header.hash) elif MessageType(res[0]) == MessageType.PeersRequest: break #Craft a Peers message of our own server. meros.sync.send(MessageType.Peers.toByte() + bytes.fromhex("017F000001") + busyServer.getsockname()[1].to_bytes(2, "little")) #Use select to obtain a non-blocking accept. busy: int = 0 buf: bytes for _ in select.select([busyServer], [], [], 5000): #Accept a new connection. client, _ = busyServer.accept() #Verify Meros's Handshake. buf = client.recv(38) if MessageType( buf[0]) not in {MessageType.Handshake, MessageType.Syncing}: busyServer.close() raise TestError( "Meros didn't start its connection with a Handshake.") if buf[1:] != ( (254).to_bytes(1, "little") + (254).to_bytes(1, "little") + (128).to_bytes(1, "little") + meros.tcp.to_bytes(2, "little") + blockchain.blocks[0].header.hash): busyServer.close() raise TestError("Meros had an invalid Handshake.") #Send back Busy. client.send(MessageType.Busy.toByte() + bytes.fromhex("017F000001") + server.getsockname()[1].to_bytes(2, "little")) busy += 1 if busy == 2: busyServer.close() break #Make sure Meros connects to the server we redirected to. with raises(SuccessError): for _ in select.select([server], [], [], 5000): #Accept a new connection. client, _ = server.accept() #Verify Meros's Handshake. buf = client.recv(38) if MessageType(buf[0]) not in { MessageType.Handshake, MessageType.Syncing }: server.close() raise TestError( "Meros didn't start its connection with a Handshake.") if buf[1:] != ( (254).to_bytes(1, "little") + (254).to_bytes(1, "little") + (128).to_bytes(1, "little") + meros.tcp.to_bytes(2, "little") + blockchain.blocks[0].header.hash): server.close() raise TestError("Meros had an invalid Handshake.") server.close() raise SuccessError( "Meros connected to the server we redirected it to with a Busy message." ) #Raise a TestError. busyServer.close() server.close() raise TestError("Meros didn't connect to the redirected server.")
#Run every test. for test in tests: if not testsToRun: break if test.__name__ not in testsToRun: continue testsToRun.remove(test.__name__) print("\033[0;37mRunning " + test.__name__ + ".") #Message to display on a Node crash. crash: str = "\033[5;31m" + test.__name__ + " caused the node to crash!\033[0;31m" #Meros instance. meros: Meros = Meros(test.__name__, port, port + 1) sleep(5) rpc: RPC = RPC(meros) try: test(rpc) raise SuccessError() except SuccessError as e: ress.append("\033[0;32m" + test.__name__ + " succeeded.") except EmptyError as e: ress.append("\033[0;33m" + test.__name__ + " is empty.") except NodeError as e: ress.append(crash) except TestError as e: ress.append("\033[0;31m" + test.__name__ + " failed: " + str(e)) except Exception as e:
def restOfTest() -> None: #Move expected into scope. expected: str = getAddress(mnemonic, password, 0) #Send to the new address, then call getAddress again. Verify a new address appears. last: Send = createSend( rpc, Claim.fromTransaction(iter(transactions.txs.values()).__next__()), expected) hashes: List[bytes] = [last.hash] expected = getAddress(mnemonic, password, 1) if rpc.call("personal", "getAddress") != expected: raise TestError( "Meros didn't move to the next address once the existing one was used." ) #Send to the new unused address, spending the funds before calling getAddress again. #Checks address usage isn't defined as having an UTXO, yet rather any TXO. #Also confirm the spending TX with full finalization before checking. #Ensures the TXO isn't unspent by any definition. last = createSend(rpc, last, expected) hashes.append(last.hash) #Spending TX. send: Send = Send([(hashes[-1], 0)], [(bytes(32), 1)]) send.signature = ed.sign(b"MEROS" + send.hash, getPrivateKey(mnemonic, password, 1)) send.beat(SpamFilter(3)) if rpc.meros.liveTransaction(send) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back the spending Send.") hashes.append(send.hash) #In order to finalize, we need to mine 6 Blocks once this Transaction and its parent have Verifications. for txHash in hashes: sv: SignedVerification = SignedVerification(txHash) sv.sign(0, PrivateKey(0)) if rpc.meros.signedElement(sv) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Verification.") #Close the sockets while we mine. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() #Mine these to the Wallet on the node so we can test getMeritHolderNick. privKey: PrivateKey = PrivateKey( bytes.fromhex(rpc.call("personal", "getMeritHolderKey"))) blockchain: Blockchain = Blockchain.fromJSON(vectors["blockchain"]) for _ in range(6): template: Dict[str, Any] = rpc.call( "merit", "getBlockTemplate", {"miner": privKey.toPublicKey().serialize().hex()}) proof: int = -1 tempHash: bytes = bytes() tempSignature: bytes = bytes() while ((proof == -1) or ((int.from_bytes(tempHash, "little") * (blockchain.difficulty() * 11 // 10)) > int.from_bytes( bytes.fromhex("FF" * 32), "little"))): proof += 1 tempHash = RandomX( bytes.fromhex(template["header"]) + proof.to_bytes(4, "little")) tempSignature = privKey.sign(tempHash).serialize() tempHash = RandomX(tempHash + tempSignature) rpc.call( "merit", "publishBlock", { "id": template["id"], "header": template["header"] + proof.to_bytes(4, "little").hex() + tempSignature.hex() }) blockchain.add( Block.fromJSON( rpc.call("merit", "getBlock", {"block": len(blockchain.blocks)}))) #Verify a new address is returned. expected = getAddress(mnemonic, password, 2) if rpc.call("personal", "getAddress") != expected: raise TestError( "Meros didn't move to the next address once the existing one was used." ) #Get a new address after sending to the address after it. #Use both, and then call getAddress. #getAddress should detect X is used, move to Y, detect Y is used, and move to Z. #It shouldn't assume the next address after an used address is unused. #Actually has two Ys as one iteration of the code only ran for the next address; not all future addresses. #Send to the next next addresses. for i in range(2): addy: str = getAddress(mnemonic, password, 3 + i) #Reopen the sockets. This isn't done outside of the loop due to the time deriving the final address can take. #Due to how slow the reference Python code is, it is necessary to redo the socket connections. sleep(65) rpc.meros.liveConnect(Blockchain().blocks[0].header.hash) rpc.meros.syncConnect(Blockchain().blocks[0].header.hash) last = createSend(rpc, last, addy) if MessageType(rpc.meros.live.recv() [0]) != MessageType.SignedVerification: raise TestError( "Meros didn't create and broadcast a SignedVerification for this Send." ) if i == 0: #Close them again. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() #Verify getAddress returns the existing next address. if rpc.call("personal", "getAddress") != expected: raise TestError( "Sending to the address after this address caused Meros to consider this address used." ) #Send to the next address. last = createSend(rpc, last, expected) if MessageType( rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError( "Meros didn't create and broadcast a SignedVerification for this Send." ) #Verify getAddress returns the address after the next next addresses. expected = getAddress(mnemonic, password, 5) if rpc.call("personal", "getAddress") != expected: raise TestError( "Meros didn't return the correct next address after using multiple addresses in a row." ) #Now that we have mined a Block as part of this test, ensure the Merit Holder nick is set. if rpc.call("personal", "getMeritHolderNick") != 1: raise TestError( "Merit Holder nick wasn't made available despite having one.") #Sanity check off Mnemonic. if rpc.call("personal", "getMnemonic") != mnemonic: raise TestError("getMnemonic didn't return the correct Mnemonic.") #Existing values used to test getMnemonic/getMeritHolderKey/getMeritHolderNick/getAccount/getAddress consistency. existing: Dict[str, Any] = { #Should be equal to the mnemonic variable, which is verified in a check above. "getMnemonic": rpc.call("personal", "getMnemonic"), "getMeritHolderKey": rpc.call("personal", "getMeritHolderKey"), "getMeritHolderNick": rpc.call("personal", "getMeritHolderNick"), "getAccount": rpc.call("personal", "getAccount"), #Should be equal to expected, which is also verified in a check above. "getAddress": rpc.call("personal", "getAddress") } #Set a new seed and verify the Merit Holder nick is cleared. rpc.call("personal", "setWallet") try: rpc.call("personal", "getMeritHolderNick") raise TestError("") except TestError as e: if str( e ) != "-2 Wallet doesn't have a Merit Holder nickname assigned.": raise TestError( "getMeritHolderNick returned something or an unexpected error when a new Mnemonic was set." ) #Set back the old seed and verify consistency. rpc.call("personal", "setWallet", { "mnemonic": mnemonic, "password": password }) for method in existing: if rpc.call("personal", method) != existing[method]: raise TestError( "Setting an old seed caused the WalletDB to improperly reload." ) #Verify calling getAddress returns the expected address. if rpc.call("personal", "getAddress") != expected: raise TestError( "Meros returned an address that wasn't next after reloading the seed." ) #Reboot the node and verify consistency. rpc.quit() sleep(3) rpc.meros = Meros(rpc.meros.db, rpc.meros.tcp, rpc.meros.rpc) for method in existing: if rpc.call("personal", method) != existing[method]: raise TestError( "Rebooting the node caused the WalletDB to improperly reload." ) #Used so Liver doesn't run its own post-test checks. #Since we added our own Blocks, those will fail. raise SuccessError()
def MissingOneSketchTest(meros: Meros) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Merit/Sketches/MissingOne.json", "r") as file: vectors = json.loads(file.read()) blockchain: Blockchain = Blockchain.fromJSON(vectors["blockchain"]) meros.liveConnect(blockchain.blocks[0].header.hash) meros.syncConnect(blockchain.blocks[0].header.hash) header: bytes = meros.liveBlockHeader(blockchain.blocks[1].header) meros.handleBlockBody(blockchain.blocks[1]) if meros.live.recv() != header: raise TestError( "Meros didn't broadcast a BlockHeader for a Block it just added.") for data in vectors["datas"]: if meros.liveTransaction(Data.fromJSON(data)) != meros.live.recv(): raise TestError("Meros didn't broadcast back a Data Transaction.") for verif in vectors["verifications"]: if meros.signedElement( SignedVerification.fromSignedJSON(verif)) != meros.live.recv(): raise TestError("Meros didn't broadcast back a Verification.") header = meros.liveBlockHeader(blockchain.blocks[2].header) meros.handleBlockBody(blockchain.blocks[2], 1) if MessageType(meros.sync.recv()[0]) != MessageType.SketchHashRequests: raise TestError("Meros didn't request the packet it's missing.") meros.packet(VerificationPacket.fromJSON(vectors["packet"])) if meros.live.recv() != header: raise TestError( "Meros didn't broadcast a BlockHeader for a Block it just added.")
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()
def MatchesHeaderQuantityTest(meros: Meros) -> None: #Create an instance of Merit to make sure the RandomX VM key was set. Merit() blocks: List[Block] txs: List[Data] = [] verifs: List[SignedVerification] = [] with open( "e2e/Vectors/Merit/TwoHundredSeventyFour/MatchesHeaderQuantity.json", "r") as file: vectors: Dict[str, Any] = json.loads(file.read()) blocks = [Block.fromJSON(block) for block in vectors["blocks"]] txs = [Data.fromJSON(tx) for tx in vectors["transactions"]] verifs = [ SignedVerification.fromSignedJSON(verif) for verif in vectors["verifications"] ] #Connect. meros.liveConnect(blocks[0].header.last) meros.syncConnect(blocks[0].header.last) #Send a single Block to earn Merit. meros.liveBlockHeader(blocks[0].header) meros.handleBlockBody(blocks[0]) #Send the header. meros.liveBlockHeader(blocks[1].header) #Fail Sketch Resolution, and send a different amount of sketch hashes. meros.handleBlockBody(blocks[1], 0) if MessageType(meros.sync.recv()[0]) != MessageType.SketchHashesRequest: raise TestError( "Meros didn't request the hashes after failing sketch resolution.") #Send a quantity of sketch hashes that doesn't match the header. meros.sketchHashes([ Sketch.hash(blocks[1].header.sketchSalt, VerificationPacket(tx.hash, [0])) for tx in txs ]) try: if len(meros.sync.recv()) == 0: raise TestError() raise Exception() except TestError: pass except Exception: raise TestError("Meros tried to further sync an invalid Block Body.") #Sleep so we can reconnect. sleep(65) #Repeat setup. meros.liveConnect(blocks[0].header.last) meros.syncConnect(blocks[0].header.last) #Send two Transactions. for i in range(2): meros.liveTransaction(txs[i]) meros.signedElement(verifs[i]) #Send the header and a large enough sketch to cause resolution. meros.liveBlockHeader(blocks[1].header) meros.handleBlockBody(blocks[1], 3) #Should now have been disconnected thanks to having 5 hashes. try: if len(meros.sync.recv()) == 0: raise TestError() raise Exception() except TestError: pass except Exception: raise TestError("Meros tried to further sync an invalid Block Body.")