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.")
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)
from e2e.Classes.Consensus.VerificationPacket import VerificationPacket from e2e.Classes.Consensus.SpamFilter import SpamFilter from e2e.Vectors.Generation.PrototypeChain import PrototypeChain transactions: Transactions = Transactions() dataFilter: SpamFilter = SpamFilter(5) edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() proto: PrototypeChain = PrototypeChain(1, keepUnlocked=False) #Create the Data and a successor. first: Data = Data(bytes(32), edPubKey.to_bytes()) first.sign(edPrivKey) first.beat(dataFilter) transactions.add(first) second: Data = Data(first.hash, bytes(1)) second.sign(edPrivKey) second.beat(dataFilter) transactions.add(second) proto.add(packets=[ VerificationPacket(first.hash, [0]), VerificationPacket(second.hash, [0]) ]) for _ in range(5):
from e2e.Classes.Consensus.Verification import SignedVerification from e2e.Classes.Consensus.VerificationPacket import VerificationPacket from e2e.Classes.Consensus.SpamFilter import SpamFilter from e2e.Vectors.Generation.PrototypeChain import PrototypeChain edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) edPubKey: bytes = edPrivKey.get_verifying_key() transactions: Transactions = Transactions() spamFilter: SpamFilter = SpamFilter(5) proto = PrototypeChain(1, False) proto.add(1) data: Data = Data(bytes(32), edPubKey) data.sign(edPrivKey) data.beat(spamFilter) transactions.add(data) verif: SignedVerification = SignedVerification(data.hash) verif.sign(1, PrivateKey(1)) proto.add(1, packets=[VerificationPacket(data.hash, [0])]) for _ in range(5): proto.add(1) with open("e2e/Vectors/Consensus/Verification/HundredFortyTwo.json", "w") as vectors: vectors.write( json.dumps({
def verifyMnemonicAndAccount(rpc: RPC, mnemonic: str = "", password: str = "") -> None: #If a Mnemonic wasn't specified, grab the node's. if mnemonic == "": mnemonic = rpc.call("personal", "getMnemonic") #Verify Mnemonic equivalence. if mnemonic != rpc.call("personal", "getMnemonic"): raise TestError("Node had a different Mnemonic.") #Validate it. if not Bip39MnemonicValidator(mnemonic).Validate(): raise TestError("Mnemonic checksum was incorrect.") #Verify derivation from seed to wallet. seed: bytes = Bip39SeedGenerator(mnemonic).Generate(password) #Check the Merit Holder key. if rpc.call("personal", "getMeritHolderKey") != PrivateKey( seed[:32]).serialize().hex().upper(): raise TestError("Meros generated a different Merit Holder Key.") #Verify getting the Merit Holder nick errors. try: rpc.call("personal", "getMeritHolderNick") except TestError as e: if e.message != "-2 Wallet doesn't have a Merit Holder nickname assigned.": raise TestError("getMeritHolderNick didn't error.") #Hash the seed again for the wallet seed (first is the Merit Holder seed). seed = sha256(seed).digest() #Derive the first account. extendedKey: bytes chainCode: bytes try: extendedKey, chainCode = BIP32.deriveKeyAndChainCode( seed, [44 + (1 << 31), 5132 + (1 << 31), 0 + (1 << 31)]) except Exception: raise TestError( "Meros gave us an invalid Mnemonic to derive (or the test generated an unusable one)." ) if rpc.call("personal", "getAccount") != { "key": RistrettoScalar( extendedKey[:32]).toPoint().serialize().hex().upper(), "chainCode": chainCode.hex().upper() }: raise TestError("Meros generated a different account public key.") #Also test that the correct public key is used when creating Datas. #It should be the first public key of the external chain for account 0. data: str = rpc.call("personal", "data", { "data": "a", "password": password }) initial: Data = Data( bytes(32), RistrettoScalar(getPrivateKey(mnemonic, password, 0)[:32]).toPoint().serialize()) #Checks via the initial Data. if bytes.fromhex( rpc.call("transactions", "getTransaction", {"hash": data})["inputs"][0]["hash"]) != initial.hash: raise TestError( "Meros used the wrong key to create the Data Transactions.")
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): datas[-1].sign(ed25519.SigningKey(b'\0' * 32)) datas[-1].beat(SpamFilter(5)) transactions.add(datas[-1]) datas.append(Data(datas[-1].hash, b'\0')) del datas[-1] merit.add( PrototypeBlock(merit.blockchain.blocks[-1].header.time + 1200, packets=[ VerificationPacket(claim.hash, [0]), VerificationPacket(send.hash, [0, 1, 2]), VerificationPacket(datas[0].hash, [0, 2]), VerificationPacket(datas[1].hash, [0, 1, 3]),
[(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()) ] for _ in range(4): datas[-1].sign(Ristretto.SigningKey(b'\0' * 32)) datas[-1].beat(SpamFilter(5)) transactions.add(datas[-1]) datas.append(Data(datas[-1].hash, b'\0')) del datas[-1] merit.add( PrototypeBlock( merit.blockchain.blocks[-1].header.time + 1200, packets=[ VerificationPacket(claim.hash, [0]), VerificationPacket(send.hash, [0, 1, 2]), VerificationPacket(datas[0].hash, [0, 2]),
from e2e.Vectors.Generation.PrototypeChain import PrototypeBlock merit: Merit = Merit() blocks: List[Dict[str, Any]] = [] transactions: Transactions = Transactions() dataFilter: SpamFilter = SpamFilter(5) edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() blsPrivKey: PrivateKey = PrivateKey(0) #Generate a Data to verify for the VerificationPacket Block. data: Data = Data(bytes(32), edPubKey.to_bytes()) data.sign(edPrivKey) data.beat(dataFilter) transactions.add(data) packet: VerificationPacket = VerificationPacket(data.hash, [1]) blocks.append( PrototypeBlock(merit.blockchain.blocks[-1].header.time + 1200, packets=[VerificationPacket(data.hash, [1])], minerID=blsPrivKey).finish(0, merit).toJSON()) #Generate the SendDifficulty Block. blocks.append( PrototypeBlock(merit.blockchain.blocks[-1].header.time + 1200, elements=[SendDifficulty(0, 0, 1)], minerID=blsPrivKey).finish(0, merit).toJSON())
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." )
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()
from e2e.Classes.Transactions.Transactions import Data, Transactions from e2e.Classes.Consensus.VerificationPacket import VerificationPacket from e2e.Classes.Consensus.SpamFilter import SpamFilter from e2e.Vectors.Generation.PrototypeChain import PrototypeChain edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) dataFilter: SpamFilter = SpamFilter(5) transactions: Transactions = Transactions() proto: PrototypeChain = PrototypeChain(1) #Create five Datas. #Six in total, thanks to the Block Data. data: Data = Data(bytes(32), edPrivKey.get_verifying_key()) for i in range(5): data.sign(edPrivKey) data.beat(dataFilter) transactions.add(data) data = Data(data.hash, b"\0") #Create a Block verifying all of them. proto.add(0, [VerificationPacket(tx.hash, [0]) for tx in transactions.txs.values()]) with open("e2e/Vectors/Merit/TwoHundredSeventyFour/RespondsWithRequestedCapacity.json", "w") as vectors: vectors.write(json.dumps({ "blockchain": proto.toJSON(), "transactions": transactions.toJSON() }))
def test() -> None: recipient: ed25519.SigningKey = ed25519.SigningKey(b'\1' * 32) recipientPub: bytes = recipient.get_verifying_key().to_bytes() address: str = bech32_encode( "mr", convertbits(bytes([0]) + recipientPub, 8, 5)) otherRecipient: bytes = ed25519.SigningKey( b'\2' * 32).get_verifying_key().to_bytes() otherAddress: str = bech32_encode( "mr", convertbits(bytes([0]) + otherRecipient, 8, 5)) #Create a Send. send: Send = Send.fromJSON(vectors["send"]) if rpc.meros.liveTransaction(send) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Send.") if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros considered an unconfirmed Transaction's outputs as UTXOs." ) verify(rpc, send.hash) #Finalize the parent. for _ in range(6): mineBlock(rpc) #Spend it. spendingSend: Send = Send.fromJSON(vectors["spendingSend"]) if rpc.meros.liveTransaction(spendingSend) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Send.") verify(rpc, spendingSend.hash) if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros didn't consider a verified Transaction's inputs as spent." ) if rpc.call("transactions", "getUTXOs", {"address": otherAddress}) != [ { "hash": spendingSend.hash.hex().upper(), "nonce": 0 } ]: raise TestError( "Meros didn't consider a verified Transaction's outputs as UTXOs." ) #Unverify the spending Send. This would also unverify the parent if it wasn't finalized. #This is done via causing a Merit Removal. #Uses two competing Datas to not change the Send's status to competing. datas: List[Data] = [Data(bytes(32), recipientPub)] for _ in range(2): datas.append(Data(datas[0].hash, datas[-1].hash)) for data in datas: data.sign(recipient) data.beat(SpamFilter(5)) if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Meros didn't broadcast back a Data.") verify(rpc, data.hash, mr=(datas[-1].hash == data.hash)) #Verify the MeritRemoval happened and the spending Send is no longer verified. #These first two checks are more likely to symbolize a failure in testing methodology than Meros. if not rpc.call("merit", "getMerit", {"nick": 0})["malicious"]: raise TestError("Meros didn't create a Merit Removal.") if not rpc.call("consensus", "getStatus", {"hash": send.hash.hex()})["verified"]: raise TestError("Finalized Transaction became unverified.") if rpc.call("consensus", "getStatus", {"hash": spendingSend.hash.hex()})["verified"]: raise TestError( "Meros didn't unverify a Transaction which is currently below the required threshold." ) #Even after unverification, since the Transaction still exists, the input shouldn't be considered a UTXO. if rpc.call("transactions", "getUTXOs", {"address": address}) != []: raise TestError( "Meros didn't consider a unverified yet existing Transaction's inputs as spent." ) #That said, its outputs should no longer be considered a UTXO. if rpc.call("transactions", "getUTXOs", {"address": otherAddress}) != []: raise TestError( "Meros considered a unverified Transaction's outputs as UTXOs." ) raise SuccessError()
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()
def GetBlockTest(rpc: RPC) -> None: blockchain: Blockchain claim: Claim send: Send datas: List[Data] txKey: Callable[[Dict[str, Any]], str] = lambda tx: tx["hash"] def verify() -> None: for b in range(len(blockchain.blocks)): block: Dict[str, Any] = rpc.call( "merit", "getBlock", {"block": blockchain.blocks[b].header.hash.hex().upper()}, False) if rpc.call("merit", "getBlock", {"block": b}, False) != block: raise TestError( "Meros reported different Blocks depending on if nonce/hash indexing." ) #Python doesn't keep track of the removals. #That said, they should all be empty except for the last one. if b != (len(blockchain.blocks) - 1): if block["removals"] != []: raise TestError("Meros reported the Block had removals.") del block["removals"] if blockchain.blocks[b].toJSON() != block: raise TestError( "Meros's JSON serialization of Blocks differs from Python's." ) #Test the key serialization of the first Block. #The final Block uses a nick, hence the value in this. if rpc.call("merit", "getBlock", {"block": 1}, False)["header"]["miner"] != PrivateKey( 0).toPublicKey().serialize().hex().upper(): raise TestError("Meros didn't serialize a miner's key properly.") #Manually test the final, and most complex, block. final: Dict[str, Any] = rpc.call("merit", "getBlock", {"block": len(blockchain.blocks) - 1}, False) final["transactions"].sort(key=txKey) final["removals"].sort() if final != { "hash": blockchain.blocks[-1].header.hash.hex().upper(), "header": { "version": blockchain.blocks[-1].header.version, "last": blockchain.blocks[-1].header.last.hex().upper(), "contents": blockchain.blocks[-1].header.contents.hex().upper(), "packets": blockchain.blocks[-1].header.packetsQuantity, "sketchSalt": blockchain.blocks[-1].header.sketchSalt.hex().upper(), "sketchCheck": blockchain.blocks[-1].header.sketchCheck.hex().upper(), "miner": blockchain.blocks[-1].header.minerKey.hex().upper() if blockchain.blocks[-1].header.newMiner else blockchain.blocks[-1].header.minerNick, "time": blockchain.blocks[-1].header.time, "proof": blockchain.blocks[-1].header.proof, "signature": blockchain.blocks[-1].header.signature.hex().upper() }, "transactions": sorted([{ "hash": claim.hash.hex().upper(), "holders": [0] }, { "hash": send.hash.hex().upper(), "holders": [0, 1, 2] }, { "hash": datas[0].hash.hex().upper(), "holders": [0, 2] }, { "hash": datas[1].hash.hex().upper(), "holders": [0, 1, 3] }, { "hash": datas[2].hash.hex().upper(), "holders": [0, 1, 2, 3, 4] }, { "hash": datas[3].hash.hex().upper(), "holders": [0, 1, 2, 3] }], key=txKey), "elements": [ { "descendant": "DataDifficulty", "holder": 3, "nonce": 0, "difficulty": 8 }, { "descendant": "SendDifficulty", "holder": 0, "nonce": 0, "difficulty": 1 }, { "descendant": "DataDifficulty", "holder": 3, "nonce": 0, "difficulty": 4 }, { "descendant": "DataDifficulty", "holder": 4, "nonce": 2, "difficulty": 1 }, { "descendant": "SendDifficulty", "holder": 4, "nonce": 1, "difficulty": 3 }, { "descendant": "SendDifficulty", "holder": 2, "nonce": 1, "difficulty": 2 }, { "descendant": "DataDifficulty", "holder": 0, "nonce": 0, "difficulty": 7 }, ], "removals": [0, 3], "aggregate": blockchain.blocks[-1].body.aggregate.serialize().hex().upper() }: raise TestError("Final Block wasn't correct.") #Test invalid calls. try: rpc.call("merit", "getBlock", {"block": 100}, False) raise Exception("") except Exception as e: if str(e) != "-2 Block not found.": raise TestError( "getBlock didn't error when we used a non-existent nonce.") try: rpc.call("merit", "getBlock", {"block": -5}, False) raise Exception("") except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError( "getBlock didn't error when we used a negative (signed) integer for a nonce." ) try: rpc.call( "merit", "getBlock", { "block": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, False) raise Exception("") except Exception as e: if str(e) != "-2 Block not found.": raise TestError( "getBlock didn't error when we used a non-existent hash.") try: rpc.call("merit", "getBlock", {"block": ""}, False) raise Exception("") except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError( "getBlock didn't error when we used an invalid hash.") with open("e2e/Vectors/RPC/Merit/GetBlock.json", "r") as file: vectors: Dict[str, Any] = json.loads(file.read()) blockchain = Blockchain.fromJSON(vectors["blockchain"]) claim = Claim.fromJSON(vectors["claim"]) send = Send.fromJSON(vectors["send"]) datas = [Data.fromJSON(data) for data in vectors["datas"]] transactions: Transactions = Transactions.fromJSON( vectors["transactions"]) Liver(rpc, vectors["blockchain"], transactions, { (len(blockchain.blocks) - 1): verify }).live()
def verifyMnemonicAndAccount(rpc: RPC, mnemonic: str = "", password: str = "") -> None: #If a Mnemonic wasn't specified, grab the node's. if mnemonic == "": mnemonic = rpc.call("personal", "getMnemonic") #Verify Mnemonic equivalence. if mnemonic != rpc.call("personal", "getMnemonic"): raise TestError("Node had a different Mnemonic.") #Validate it. if not Bip39MnemonicValidator(mnemonic).Validate(): raise TestError("Mnemonic checksum was incorrect.") #Verify derivation from seed to wallet. seed: bytes = Bip39SeedGenerator(mnemonic).Generate(password) #Check the Merit Holder key. if rpc.call("personal", "getMeritHolderKey") != PrivateKey( seed[:32]).serialize().hex().upper(): raise TestError("Meros generated a different Merit Holder Key.") #Verify getting the Merit Holder nick errors. try: rpc.call("personal", "getMeritHolderNick") except TestError as e: if e.message != "-2 Wallet doesn't have a Merit Holder nickname assigned.": raise TestError("getMeritHolderNick didn't error.") #Hash the seed again for the wallet seed (first is the Merit Holder seed). seed = sha256(seed).digest() #Derive the first account. extendedKey: bytes chainCode: bytes try: extendedKey, chainCode = BIP32.deriveKeyAndChainCode( seed, [44 + (1 << 31), 5132 + (1 << 31), 0 + (1 << 31)]) except Exception: raise TestError( "Meros gave us an invalid Mnemonic to derive (or the test generated an unusable one)." ) #For some reason, pylint decided to add in detection of stdlib members. #It doesn't do it properly, and thinks encodepoint returns a string. #It returns bytes, which does have hex as a method. #pylint: disable=no-member if rpc.call("personal", "getAccount") != { "key": ed.encodepoint( ed.scalarmult( ed.B, ed.decodeint(extendedKey[:32]) % ed.l)).hex().upper(), "chainCode": chainCode.hex().upper() }: #The Nim tests ensure accurate BIP 32 derivation thanks to vectors. #That leaves BIP 39/44 in the air. #This isn't technically true due to an ambiguity/the implementation we used the vectors of, yet it's true enough for this comment. raise TestError("Meros generated a different account public key.") #Also test that the correct public key is used when creating Datas. #It should be the first public key of the external chain for account 0. data: str = rpc.call("personal", "data", { "data": "a", "password": password }) initial: Data = Data( bytes(32), ed.encodepoint( ed.scalarmult( ed.B, ed.decodeint(getPrivateKey(mnemonic, password, 0)[:32]) % ed.l))) #Checks via the initial Data. if bytes.fromhex( rpc.call("transactions", "getTransaction", {"hash": data})["inputs"][0]["hash"]) != initial.hash: raise TestError( "Meros used the wrong key to create the Data Transactions.")