def createSend(rpc: RPC, last: Union[Claim, Send], toAddress: str) -> Send: funded: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) if isinstance(last, Claim): send: Send = Send([(last.hash, 0)], [(decodeAddress(toAddress), 1), (funded.get_verifying_key(), last.amount - 1)]) else: send: Send = Send( [(last.hash, 1)], [(decodeAddress(toAddress), 1), (funded.get_verifying_key(), last.outputs[1][1] - 1)]) send.sign(funded) send.beat(SpamFilter(3)) sleep(65) rpc.meros.liveConnect(Blockchain().blocks[0].header.hash) if rpc.meros.liveTransaction(send) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Send.") sv: SignedVerification = SignedVerification(send.hash) sv.sign(0, PrivateKey(0)) if rpc.meros.signedElement(sv) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Verification.") return send
def createSend(rpc: RPC, last: Union[Claim, Send], toAddress: str) -> Send: funded: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) if isinstance(last, Claim): send: Send = Send([(last.hash, 0)], [(decodeAddress(toAddress), 1), (funded.get_verifying_key(), last.amount - 1)]) else: send: Send = Send( [(last.hash, 1)], [(decodeAddress(toAddress), 1), (funded.get_verifying_key(), last.outputs[1][1] - 1)]) send.sign(funded) send.beat(SpamFilter(3)) if rpc.meros.liveTransaction(send) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Send.") return send
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.")