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)) #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()
def syncUnknown() -> None: claim: Claim = Claim([(merit.mints[0], 0)], pubKey.to_bytes()) claim.sign(PrivateKey(0)) #Create a series of Sends, forming a diamond. #Cross sendB and sendC to actually force this to work in an ordered fashion to pass. sendA: Send = Send([(claim.hash, 0)], [(pubKey.to_bytes(), claim.amount // 2), (pubKey.to_bytes(), claim.amount // 2)]) sendB: Send = Send([(sendA.hash, 0)], [(pubKey.to_bytes(), sendA.outputs[0][1] // 2), (pubKey.to_bytes(), sendA.outputs[0][1] // 2)]) sendC: Send = Send( [(sendA.hash, 1), (sendB.hash, 1)], [(pubKey.to_bytes(), sendA.outputs[1][1] + sendB.outputs[1][1])]) sendD: Send = Send([(sendB.hash, 0), (sendC.hash, 0)], [(pubKey.to_bytes(), claim.amount)]) for send in [sendA, sendB, sendC, sendD]: send.sign(privKey) send.beat(sendFilter) #Send the tail of the diamond, which should cause an ordered top-down sync. sent: bytes = rpc.meros.liveTransaction(sendD) for tx in [ sendC, sendB, sendA, claim, sendA, claim, sendB, sendA, claim ]: if rpc.meros.sync.recv() != ( MessageType.TransactionRequest.toByte() + tx.hash): raise TestError("Meros didn't request one of the inputs.") rpc.meros.syncTransaction(tx) if rpc.meros.live.recv() != sent: raise TestError("Meros didn't broadcast the Send.") #Do the same for a few Data Transactions. datas: List[Data] = [Data(bytes(32), pubKey.to_bytes())] datas.append(Data(datas[-1].hash, bytes(1))) datas.append(Data(datas[-1].hash, bytes(1))) for data in datas: data.sign(privKey) data.beat(dataFilter)
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 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()
from e2e.Classes.Merit.Merit import Block, Merit from e2e.Vectors.Generation.PrototypeChain import PrototypeBlock, PrototypeChain edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: bytes = edPrivKey.get_verifying_key().to_bytes() transactions: Transactions = Transactions() sendFilter: SpamFilter = SpamFilter(3) proto: PrototypeChain = PrototypeChain(40, keepUnlocked=True) proto.add(1) merit: Merit = Merit.fromJSON(proto.toJSON()) #Create a Claim. claim: Claim = Claim([(merit.mints[-1], 0)], edPubKey) claim.sign(PrivateKey(0)) transactions.add(claim) merit.add( PrototypeBlock(merit.blockchain.blocks[-1].header.time + 1200, packets=[VerificationPacket(claim.hash, list(range(2))) ]).finish(0, merit)) sends: List[Send] = [ #Transaction which will win. Send([(claim.hash, 0)], [(bytes(32), claim.amount)]), #Transaction which will be beaten. Send([(claim.hash, 0)], [(edPubKey, claim.amount // 2), (edPubKey, claim.amount // 2)]) ]
from e2e.Classes.Merit.Merit import Merit from e2e.Vectors.Generation.PrototypeChain import PrototypeBlock, PrototypeChain proto: PrototypeChain = PrototypeChain(7) proto.add(1) proto.add(2) proto.add(3) proto.add(4) proto.add(elements=[SendDifficulty(1, 0, 2), SendDifficulty(1, 0, 4)]) merit: Merit = Merit.fromJSON(proto.toJSON()) transactions: Transactions = Transactions() claim: Claim = Claim([(merit.mints[-1], 0)], ed25519.SigningKey(b'\0' * 32).get_verifying_key().to_bytes()) claim.sign(PrivateKey(0)) transactions.add(claim) send: Send = Send([(claim.hash, 0)], [(ed25519.SigningKey( b'\1' * 32).get_verifying_key().to_bytes(), claim.amount)]) send.sign(ed25519.SigningKey(b'\0' * 32)) send.beat(SpamFilter(3)) transactions.add(send) datas: List[Data] = [ Data(bytes(32), ed25519.SigningKey(b'\0' * 32).get_verifying_key().to_bytes()) ] for _ in range(4):
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()
from e2e.Classes.Merit.Merit import Merit from e2e.Vectors.Generation.PrototypeChain import PrototypeBlock, PrototypeChain proto: PrototypeChain = PrototypeChain(7) proto.add(1) proto.add(2) proto.add(3) proto.add(4) proto.add(elements=[SendDifficulty(1, 0, 2), SendDifficulty(1, 0, 4)]) merit: Merit = Merit.fromJSON(proto.toJSON()) transactions: Transactions = Transactions() claim: Claim = Claim( [(merit.mints[-1], 0)], Ristretto.SigningKey(b'\0' * 32).get_verifying_key() ) claim.sign(PrivateKey(0)) transactions.add(claim) send: Send = Send( [(claim.hash, 0)], [(Ristretto.SigningKey(b'\1' * 32).get_verifying_key(), claim.amount)] ) send.sign(Ristretto.SigningKey(b'\0' * 32)) send.beat(SpamFilter(3)) transactions.add(send) datas: List[Data] = [ Data(bytes(32), Ristretto.SigningKey(b'\0' * 32).get_verifying_key()) ]
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 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 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 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.Consensus.SpamFilter import SpamFilter from e2e.Classes.Consensus.VerificationPacket import VerificationPacket from e2e.Classes.Merit.Merit import Merit from e2e.Vectors.Generation.PrototypeChain import PrototypeBlock, PrototypeChain merit: Merit = Merit.fromJSON(PrototypeChain(47).toJSON()) transactions: Transactions = Transactions() privKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) pubKey: bytes = privKey.get_verifying_key() recipientPriv: Ristretto.SigningKey = Ristretto.SigningKey(b'\1' * 32) recipientPub: bytes = recipientPriv.get_verifying_key() olderClaim: Claim = Claim([(merit.mints[-1], 0)], pubKey) olderClaim.sign(PrivateKey(0)) transactions.add(olderClaim) merit.add( PrototypeBlock( merit.blockchain.blocks[-1].header.time + 1200, packets=[VerificationPacket(olderClaim.hash, [0])] ).finish(0, merit) ) merit.add(PrototypeBlock(merit.blockchain.blocks[-1].header.time + 1200).finish(0, merit)) newerClaim: Claim = Claim([(merit.mints[-1], 0)], pubKey) newerClaim.sign(PrivateKey(0)) transactions.add(newerClaim)
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()