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 TwoHundredThirtySevenTest(rpc: RPC) -> None: chains: Dict[str, List[Dict[str, Any]]] with open("e2e/Vectors/Merit/Reorganizations/TwoHundredThirtySeven.json", "r") as file: chains = json.loads(file.read()) main: Blockchain = Blockchain.fromJSON(chains["main"]) alt: Blockchain = Blockchain.fromJSON(chains["alt"]) def sendBlock(toSend: Block) -> None: rpc.meros.liveBlockHeader(toSend.header) rpc.meros.handleBlockBody(toSend) #Send 0 through 3 of the main chain. rpc.meros.liveConnect(main.blocks[0].header.hash) rpc.meros.syncConnect(main.blocks[0].header.hash) sendBlock(main.blocks[1]) sendBlock(main.blocks[2]) sendBlock(main.blocks[3]) #Send the alt chain, which won't have enough work to trigger a reorganization. rpc.meros.liveBlockHeader(alt.blocks[2].header) if MessageType(rpc.meros.sync.recv()[0]) != MessageType.BlockListRequest: raise TestError( "Meros didn't request for the Block List for this alternate chain." ) rpc.meros.blockList([alt.blocks[0].header.hash]) if MessageType(rpc.meros.sync.recv()[0]) != MessageType.BlockHeaderRequest: raise TestError( "Meros didn't request a BlockHeader in this alternate chain.") rpc.meros.syncBlockHeader(alt.blocks[1].header) #Verify Meros can sync the final Block. sendBlock(main.blocks[4])
def HundredEightySevenTest( meros: Meros ) -> None: file: IO[Any] = open("e2e/Vectors/Merit/HundredEightySeven.json", "r") vectors: List[Dict[str, Any]] = json.loads(file.read()) file.close() meros.liveConnect(Blockchain().last()) meros.syncConnect(Blockchain().last()) block: Block = Block.fromJSON(vectors[0]) sent: bytes = meros.liveBlockHeader(block.header) if meros.sync.recv() != MessageType.BlockBodyRequest.toByte() + block.header.hash: raise TestError("Meros didn't request the matching BlockBody.") meros.blockBody(block) if meros.live.recv() != sent: raise TestError("Meros didn't broadcast a BlockHeader.") meros.liveBlockHeader(Block.fromJSON(vectors[1]).header) with raises(SuccessError): try: if len(meros.live.recv()) != 0: raise Exception() except TestError: sleep(1) if meros.process.poll() is not None: raise TestError("Node crashed trying to handle a BlockHeader which re-registers a key.") raise SuccessError("Node disconnected us after we sent a BlockHeader which re-registers a key.") except Exception: raise TestError("Meros didn't disconnect us after we sent a BlockHeader which re-registers a key; it also didn't crash.")
def verifyBlockchain( rpc: RPC, blockchain: Blockchain ) -> None: sleep(2) if rpc.call("merit", "getHeight") != len(blockchain.blocks): raise TestError("Height doesn't match.") if blockchain.difficulty() != rpc.call("merit", "getDifficulty"): raise TestError("Difficulty doesn't match.") for b in range(len(blockchain.blocks)): ourBlock: Dict[str, Any] = blockchain.blocks[b].toJSON() blockJSON: Dict[str, Any] = rpc.call("merit", "getBlock", {"block": b}) #Contextual info Python doesn't track. del blockJSON["removals"] if blockJSON != ourBlock: raise TestError("Block doesn't match.") #Test when indexing by the hash instead of the nonce. blockJSON = rpc.call( "merit", "getBlock", {"block": blockchain.blocks[b].header.hash.hex().upper()} ) del blockJSON["removals"] if blockJSON != ourBlock: raise TestError("Block doesn't match.")
def HundredSixSignedElementsTest(rpc: RPC) -> None: #Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() blsPrivKey: PrivateKey = PrivateKey(0) sig: Signature = blsPrivKey.sign(bytes()) #Create a Data. #This is required so the Verification isn't terminated early for having an unknown hash. data: bytes = bytes.fromhex(rpc.call("personal", "data", ["AA"])) #Create a signed Verification, SendDifficulty, and DataDifficulty. elements: List[SignedElement] = [ SignedVerification(data, 1, sig), SignedSendDifficulty(0, 0, 1, sig), SignedDataDifficulty(0, 0, 1, sig) ] for elem in elements: #Handshake with the node. rpc.meros.liveConnect(blockchain.blocks[0].header.hash) #Send the Element. rpc.meros.signedElement(elem) #Sleep for thirty seconds to make sure Meros realizes our connection is dead. sleep(30) #Verify the node didn't crash. try: if rpc.call("merit", "getHeight") != 1: raise Exception() except Exception: raise TestError( "Node crashed after being sent a malformed Element.")
def verifyBlockchain(rpc: RPC, blockchain: Blockchain) -> None: sleep(2) if rpc.call("merit", "getHeight") != len(blockchain.blocks): raise TestError("Height doesn't match.") if blockchain.difficulty() != rpc.call("merit", "getDifficulty"): raise TestError("Difficulty doesn't match.") for b in range(len(blockchain.blocks)): ourBlock: Dict[str, Any] = blockchain.blocks[b].toJSON() #Info Python saves so it can properly load from the vectors yet the Meros RPC excludes. del ourBlock["header"]["packets"] blockJSON: Dict[str, Any] = rpc.call("merit", "getBlock", [b]) #Contextual info Python doesn't track. del blockJSON["removals"] if blockJSON != ourBlock: raise TestError("Block doesn't match.") #Test when indexing by the hash instead of the nonce. blockJSON = rpc.call("merit", "getBlock", [blockchain.blocks[b].header.hash.hex().upper()]) del blockJSON["removals"] if blockJSON != ourBlock: raise TestError("Block doesn't match.")
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 DepthOneTest(rpc: RPC) -> None: file: IO[Any] = open("e2e/Vectors/Merit/Reorganizations/DepthOne.json", "r") chains: Dict[str, List[Dict[str, Any]]] = json.loads(file.read()) file.close() #Load the alternate blockchain. alt: Blockchain = Blockchain.fromJSON(chains["alt"]) #Send the alternate tip. def sendAlternateTip() -> None: header: bytes = rpc.meros.liveBlockHeader(alt.blocks[-1].header) req: bytes = rpc.meros.sync.recv() if req != (MessageType.BlockBodyRequest.toByte() + alt.blocks[-1].header.hash): raise TestError("Meros didn't request the BlockBody.") rpc.meros.blockBody(alt.blocks[-1]) if rpc.meros.live.recv() != header: raise TestError("Meros didn't send back the BlockHeader.") #Verify the alternate Blockchain. verifyBlockchain(rpc, alt) #Raise SuccessError so the Liver doesn't fail when verifying the original chain. raise SuccessError("Meros re-organized to the alternate chain.") with raises(SuccessError): Liver(rpc, chains["main"], callbacks={3: sendAlternateTip}).live()
def verifyBlockchain( rpc: RPC, blockchain: Blockchain ) -> None: #Sleep to ensure data races aren't a problem. sleep(2) #Verify the height. if rpc.call("merit", "getHeight") != len(blockchain.blocks): raise TestError("Height doesn't match.") #Verify the difficulty. if blockchain.difficulty() != int(rpc.call("merit", "getDifficulty"), 16): raise TestError("Difficulty doesn't match.") #Verify the Blocks. for b in range(len(blockchain.blocks)): if rpc.call("merit", "getBlock", [b]) != blockchain.blocks[b].toJSON(): raise TestError("Block doesn't match.") if rpc.call( "merit", "getBlock", [blockchain.blocks[b].header.hash.hex().upper()] ) != blockchain.blocks[b].toJSON(): raise TestError("Block doesn't match.")
def add(self, nick: int = 0, packets: List[VerificationPacket] = [], elements: List[Element] = []) -> None: #Determine if this is a new miner or not. miner: Union[PrivateKey, int] self.miners.append(self.miners[-1]) if nick > self.miners[-1]: raise GenerationError( "Told to mine a Block with a miner nick which doesn't exist.") if nick == self.miners[-1]: miner = PrivateKey(nick) self.miners[-1] += 1 else: miner = nick timeBase: int if len(self.blocks) == 0: timeBase = Blockchain().blocks[0].header.time else: timeBase = self.blocks[-1].time #Create and add the PrototypeBlock. self.blocks.append( PrototypeBlock( timeBase + self.timeOffset, #Create copies of the lists used as arguments to ensure we don't mutate the arguments. list(packets), list(elements), miner))
def OneHundredPercentSketchTest(meros: Meros) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Merit/Sketches/OneHundredPercent.json", "r") as file: vectors = json.loads(file.read()) blockchain: Blockchain = Blockchain.fromJSON(vectors["blockchain"]) meros.liveConnect(blockchain.blocks[0].header.hash) meros.syncConnect(blockchain.blocks[0].header.hash) header: bytes = meros.liveBlockHeader(blockchain.blocks[1].header) meros.handleBlockBody(blockchain.blocks[1]) if meros.live.recv() != header: raise TestError( "Meros didn't broadcast a BlockHeader for a Block it just added.") for data in vectors["datas"]: if meros.liveTransaction(Data.fromJSON(data)) != meros.live.recv(): raise TestError("Meros didn't broadcast back a Data Transaction.") for verif in vectors["verifications"]: if meros.signedElement( SignedVerification.fromSignedJSON(verif)) != meros.live.recv(): raise TestError("Meros didn't broadcast back a Verification.") header = meros.liveBlockHeader(blockchain.blocks[2].header) meros.handleBlockBody(blockchain.blocks[2]) if meros.live.recv() != header: raise TestError( "Meros didn't broadcast a BlockHeader for a Block it just added.")
def 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 LANPeersTest(meros: Meros) -> None: #Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() #Handshake with the node. meros.syncConnect(blockchain.blocks[0].header.hash) #Verify that sending a PeersRequest returns 0 peers. meros.peersRequest() if len(meros.sync.recv(True)) != 2: raise TestError("Meros sent peers.") #Create a new connection which identifies as a server. serverConnection: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverConnection.connect(("127.0.0.1", meros.tcp)) serverConnection.send( MessageType.Syncing.toByte() + (254).to_bytes(1, "little") + (254).to_bytes(1, "little") + (128).to_bytes(1, "little") + (6000).to_bytes(2, "little") + blockchain.blocks[0].header.hash, False) serverConnection.recv(38) sleep(1) #Verify Meros ignores us as a peer since we're only available over the local network. meros.peersRequest() res: bytes = meros.sync.recv(True) if len(res) != 2: raise TestError("Meros sent peers.") #Close the new connection. serverConnection.close()
def HundredTwentyFiveTest(rpc: RPC) -> None: #Meros allows connections from its own IP if they identify as 127.0.0.1. #We need to connect either through the LAN or through the public IP for this test to be valid. #The following code grabs the computer's 192 IP. lanIPFinder = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) lanIPFinder.connect(("meroscrypto.io", 443)) lanIP = lanIPFinder.getsockname()[0] lanIPFinder.close() if not (lanIP.split(".")[0] in {"10", "172", "192"}): raise Exception("Failed to get the LAN IP.") #Blockchain. Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() #Connect to Meros. connection: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connection.connect((lanIP, rpc.meros.tcp)) try: connection.send( MessageType.Syncing.toByte() + (254).to_bytes(1, "big") + (254).to_bytes(1, "big") + (128).to_bytes(1, "big") + (6000).to_bytes(2, "big") + blockchain.blocks[0].header.hash, False) if len(connection.recv(38)) == 0: raise Exception("") except Exception: raise SuccessError( "Meros closed a connection from the same IP as itself which wasn't 127.0.0.1." ) raise TestError( "Meros allowed a connection from the same IP as itself which wasn't 127.0.0.1." )
def MissingOneSketchTest(meros: Meros) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Merit/Sketches/MissingOne.json", "r") as file: vectors = json.loads(file.read()) blockchain: Blockchain = Blockchain.fromJSON(vectors["blockchain"]) meros.liveConnect(blockchain.blocks[0].header.hash) meros.syncConnect(blockchain.blocks[0].header.hash) header: bytes = meros.liveBlockHeader(blockchain.blocks[1].header) meros.handleBlockBody(blockchain.blocks[1]) if meros.live.recv() != header: raise TestError( "Meros didn't broadcast a BlockHeader for a Block it just added.") for data in vectors["datas"]: if meros.liveTransaction(Data.fromJSON(data)) != meros.live.recv(): raise TestError("Meros didn't broadcast back a Data Transaction.") for verif in vectors["verifications"]: if meros.signedElement( SignedVerification.fromSignedJSON(verif)) != meros.live.recv(): raise TestError("Meros didn't broadcast back a Verification.") header = meros.liveBlockHeader(blockchain.blocks[2].header) meros.handleBlockBody(blockchain.blocks[2], 1) if MessageType(meros.sync.recv()[0]) != MessageType.SketchHashRequests: raise TestError("Meros didn't request the packet it's missing.") meros.packet(VerificationPacket.fromJSON(vectors["packet"])) if meros.live.recv() != header: raise TestError( "Meros didn't broadcast a BlockHeader for a Block it just added.")
def ShorterChainMoreWorkTest( rpc: RPC ) -> None: file: IO[Any] = open("e2e/Vectors/Merit/Reorganizations/ShorterChainMoreWork.json", "r") chains: Dict[str, List[Dict[str, Any]]] = json.loads(file.read()) file.close() #Load the alternate blockchain. alt: Blockchain = Blockchain.fromJSON(chains["alt"]) #Send the alternate tip. def sendAlternateTip() -> None: header: bytes = rpc.meros.liveBlockHeader(alt.blocks[-1].header) req: bytes = rpc.meros.sync.recv() if MessageType(req[0]) != MessageType.BlockListRequest: raise TestError("Meros didn't request the list of previous BlockHeaders.") if req[3 : 35] != alt.blocks[-2].header.hash: raise TestError("Meros didn't request the list of previous BlockHeaders for THIS header.") blockList: List[bytes] = [] b: int = len(alt.blocks) - 3 while b != -1: blockList.append(alt.blocks[b].header.hash) b -= 1 rpc.meros.blockList(blockList) diff = -4 while diff != -1: req = rpc.meros.sync.recv() if req != (MessageType.BlockHeaderRequest.toByte() + alt.blocks[diff].header.hash): raise TestError("Meros didn't request a previous BlockHeader.") rpc.meros.syncBlockHeader(alt.blocks[diff].header) diff += 1 diff = -4 while diff != 0: req = rpc.meros.sync.recv() if req != (MessageType.BlockBodyRequest.toByte() + alt.blocks[diff].header.hash): raise TestError("Meros didn't request a previous BlockBody.") rpc.meros.blockBody(alt.blocks[diff]) diff += 1 if rpc.meros.live.recv() != header: raise TestError("Meros didn't send back the BlockHeader.") #Verify the alternate Blockchain. verifyBlockchain(rpc, alt) #Raise SuccessError so the Liver doesn't fail when verifying the original chain. raise SuccessError("Meros re-organized to the alternate chain.") with raises(SuccessError): Liver( rpc, chains["main"], callbacks={ 25: sendAlternateTip } ).live()
def StateTest(rpc: RPC) -> None: file: IO[Any] = open("e2e/Vectors/Merit/State.json", "r") blocks: List[Dict[str, Any]] = json.loads(file.read()) file.close() blockchain: Blockchain = Blockchain.fromJSON(blocks) state: State = State() def checkState(block: int) -> None: state.add(blockchain, block) meritSum: int = 0 for miner in range(len(state.balances)): meritSum += state.balances[miner] if rpc.call("merit", "getMerit", [miner]) != { "status": "Unlocked", "malicious": False, "merit": state.balances[miner] }: raise TestError("Merit doesn't match.") if meritSum != min(block, state.lifetime): raise TestError("Merit sum is invalid.") Liver(rpc, blocks, everyBlock=checkState).live()
def VUnknownSignedTest(rpc: RPC) -> None: file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r") chain: Blockchain = Blockchain.fromJSON(json.loads(file.read())) file.close() #Send a single block so we have a miner. rpc.meros.liveConnect(chain.blocks[0].header.hash) rpc.meros.syncConnect(chain.blocks[0].header.hash) header: bytes = rpc.meros.liveBlockHeader(chain.blocks[1].header) if MessageType(rpc.meros.sync.recv()[0]) != MessageType.BlockBodyRequest: raise TestError("Meros didn't ask for the body.") rpc.meros.blockBody(chain.blocks[1]) if rpc.meros.live.recv() != header: raise TestError("Meros didn't broadcast the header.") #Create a valid Data. #Uneccessary at this time, but good preparation for the future. privKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) data: Data = Data(bytes(32), privKey.get_verifying_key().to_bytes()) data.sign(privKey) data.beat(SpamFilter(5)) #Sign the Data. verif: SignedVerification = SignedVerification(data.hash) verif.sign(0, PrivateKey(0)) #Run twice. The first shouldn't send the Transaction. The second should. for i in range(2): rpc.meros.signedElement(verif) if MessageType( rpc.meros.sync.recv()[0]) != MessageType.TransactionRequest: raise TestError("Meros didn't request the transaction.") if i == 0: #When we send DataMissing, we should be disconnected within a few seconds. rpc.meros.dataMissing() start: int = int(time()) try: rpc.meros.sync.recv() except Exception: #More than a few seconds is allowed as Meros's own SyncRequest must timeout. if int(time()) - start > 10: raise TestError( "Meros didn't disconnect us for sending a Verification of a non-existent Transaction." ) #Clear our invalid connections. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() sleep(65) #Init new ones. rpc.meros.liveConnect(chain.blocks[0].header.hash) rpc.meros.syncConnect(chain.blocks[0].header.hash) else: rpc.meros.syncTransaction(data) sleep(2) if not rpc.call("consensus", "getStatus", [data.hash.hex()])["verifiers"]: raise TestError("Meros didn't add the Verification.")
def TwoHundredFourTest(rpc: RPC) -> None: vectors: Dict[str, Any] with open("e2e/Vectors/Consensus/TwoHundredFour.json", "r") as file: vectors = json.loads(file.read()) #Instantiate a Blockchain to set the RandomX key. chain: Blockchain = Blockchain() blank: Block = Block.fromJSON(vectors["blank"]) if len(vectors["blocks"]) != 1: raise Exception( "Misread this test's vectors, creating an invalid test due to that." ) block: Block = Block.fromJSON(vectors["blocks"][0]) rpc.meros.liveConnect(chain.last()) rpc.meros.syncConnect(chain.last()) #Send a blank block so Meros acknowledges a holder. sentHeader: bytes = rpc.meros.liveBlockHeader(blank.header) rpc.meros.handleBlockBody(blank) if rpc.meros.live.recv() != sentHeader: raise TestError( "Meros didn't rebroadcast the header for a blank Block.") rpc.meros.liveBlockHeader(block.header) rpc.meros.handleBlockBody(block) msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) != MessageType.SketchHashRequests: raise TestError("Unexpected message sent: " + msg.hex().upper()) if msg[1:33] != block.header.hash: raise TestError( "Meros asked for Verification Packets that didn't belong to the Block we just sent it." ) if int.from_bytes(msg[33:37], byteorder="little") != 1: raise TestError("Meros didn't ask for one VerificationPacket.") if int.from_bytes(msg[37:45], byteorder="little") != Sketch.hash( block.header.sketchSalt, block.body.packets[0]): raise TestError("Meros didn't ask for the VerificationPacket.") rpc.meros.packet(block.body.packets[0]) #Try receiving from the Live socket, where Meros sends keep-alives. try: if len(rpc.meros.live.recv()) != 0: raise Exception() except TestError: #Verify the node didn't crash. sleep(1) if rpc.meros.process.poll() is not None: raise TestError( "Node crashed trying to handle a VerificationPacket with no holders." ) except Exception: raise TestError( "Meros didn't disconnect us after sending a VerificationPacket with no holders; it also didn't crash." )
def BlockListTest(rpc: RPC) -> None: with open("e2e/Vectors/Merit/BlankBlocks.json", "r") as file: vectors: List[Dict[str, Any]] = json.loads(file.read()) chain: Blockchain = Blockchain.fromJSON(vectors) amount1: int = 25 amount2: int = 6 def constructResponse(amount: int, lastBlock: int = -1) -> bytes: if lastBlock == -1: lastBlock = len(chain.blocks) lastBlock = min(amount, lastBlock) quantity: bytes = (lastBlock - 1).to_bytes(1, byteorder="little") hashes: List[bytes] = [ block.header.hash for block in chain.blocks[:lastBlock] ] return quantity + b"".join(reversed(hashes)) def beforeGenesis() -> None: rpc.meros.blockListRequest(1, chain.blocks[0].header.hash) blockList: bytes = rpc.meros.sync.recv() if blockList != MessageType.DataMissing.toByte(): raise TestError( "Meros did not return a DataMissing response to a BlockListRequest of the Block before genesis." ) def lessThanRequested() -> None: rpc.meros.blockListRequest(amount2, chain.blocks[amount2 - 1].header.hash) blockList: bytes = rpc.meros.sync.recv() if blockList[1:] != constructResponse(amount1, amount2 - 1): raise TestError( "Meros didn't properly return fewer blocks when a BlockListRequest requests more blocks than exist." ) if (len(blockList[2:]) / 32) != (amount2 - 1): raise Exception( "Testing methodology error; considered the right amount of Blocks valid (not less than requested)." ) def recHash() -> None: rpc.meros.blockListRequest(amount1, chain.blocks[amount1].header.hash) blockList: bytes = rpc.meros.sync.recv() if blockList[1:] != constructResponse(amount1): raise TestError( "Meros returned a different BlockList than expected in response to a BlockListRequest." ) Liver(rpc, vectors, callbacks={ 1: beforeGenesis, (amount2 - 1): lessThanRequested, amount1: recHash }).live()
def DifficultyTest(rpc: RPC) -> None: #Declared here so we can access the difficulties from this callback. blockchain: Blockchain def checkDifficulty(block: int) -> None: if rpc.call("merit", "getDifficulty") != blockchain.difficulties[block]: raise TestError("Difficulty doesn't match.") with open("e2e/Vectors/Merit/Difficulty.json", "r") as file: blocks: List[Dict[str, Any]] = json.loads(file.read()) blockchain = Blockchain.fromJSON(blocks) Liver(rpc, blocks, everyBlock=checkDifficulty).live()
def DataTest(rpc: RPC) -> None: privKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) pubKey: bytes = privKey.get_verifying_key() genesis: bytes = Blockchain().blocks[0].header.hash spamFilter: SpamFilter = SpamFilter(5) data: Data = Data(bytes(32), pubKey) data.sign(privKey) data.beat(spamFilter) rpc.meros.liveConnect(genesis) rpc.meros.liveTransaction(data) verifyTransaction(rpc, data)
def fromJSON(json: List[Dict[str, Any]]) -> Any: result: Merit = Merit.__new__(Merit) result.blockchain = Blockchain.fromJSON(json) result.state = State() result.epochs = Epochs() result.mints = [] for b in range(1, len(result.blockchain.blocks)): mint: Optional[Mint] = result.epochs.shift(result.state, result.blockchain, b) if mint is not None: result.mints.append(mint) result.state.add(result.blockchain, b) return result
def DifficultyTest(rpc: RPC) -> None: #Blocks. file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r") blocks: List[Dict[str, Any]] = json.loads(file.read()) file.close() #Blockchain. blockchain: Blockchain = Blockchain.fromJSON(blocks) def checkDifficulty(block: int) -> None: if int(rpc.call("merit", "getDifficulty"), 16) != blockchain.difficulties[block]: raise TestError("Difficulty doesn't match.") Liver(rpc, blocks, everyBlock=checkDifficulty).live()
def verifyBlockchain(rpc: RPC, blockchain: Blockchain) -> None: sleep(2) if rpc.call("merit", "getHeight") != len(blockchain.blocks): raise TestError("Height doesn't match.") if blockchain.difficulty() != int(rpc.call("merit", "getDifficulty"), 16): raise TestError("Difficulty doesn't match.") for b in range(len(blockchain.blocks)): if rpc.call("merit", "getBlock", [b]) != blockchain.blocks[b].toJSON(): raise TestError("Block doesn't match.") if rpc.call("merit", "getBlock", [blockchain.blocks[b].header.hash.hex().upper() ]) != blockchain.blocks[b].toJSON(): raise TestError("Block doesn't match.")
def MultiplePacketsTest(rpc: RPC) -> None: #Spawn a Blockchain just to set the RandomX key. _: Blockchain = Blockchain() vectors: Dict[str, Any] with open("e2e/Vectors/Merit/MultiplePackets.json", "r") as file: vectors = json.loads(file.read()) data: Data = Data.fromJSON(vectors["data"]) block: Block = Block.fromJSON(vectors["blockchain"][-1]) def sendDataAndBlock() -> None: #Send the Data. if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Meros didn't send back the Data.") rpc.meros.liveBlockHeader(block.header) rpc.meros.handleBlockBody(block) msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) != MessageType.SketchHashRequests: raise TestError("Meros didn't request the packets for this Block.") packets: Dict[int, VerificationPacket] = {} for packet in block.body.packets: packets[Sketch.hash(block.header.sketchSalt, packet)] = packet #Look up each requested packet and respond accordingly. for h in range(int.from_bytes(msg[33:37], byteorder="little")): sketchHash: int = int.from_bytes(msg[37 + (h * 8):45 + (h * 8)], byteorder="little") if sketchHash not in packets: raise TestError("Meros asked for a non-existent Sketch Hash.") rpc.meros.packet(packets[sketchHash]) try: if MessageType( rpc.meros.live.recv()[0]) == MessageType.BlockHeader: raise TestError("Meros added the Block.") except Exception as e: if str(e) != "Meros added the Block.": raise SuccessError() with raises(SuccessError): Liver(rpc, vectors["blockchain"], callbacks={ 2: sendDataAndBlock }).live()
def HundredSixSignedElementsTest( rpc: RPC ) -> None: #Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) blsPrivKey: PrivateKey = PrivateKey(0) sig: Signature = blsPrivKey.sign(bytes()) #Create a Data for the Verification. data: Data = Data(bytes(32), edPrivKey.get_verifying_key()) data.sign(edPrivKey) data.beat(SpamFilter(5)) #Create a signed Verification, SendDifficulty, and DataDifficulty. elements: List[SignedElement] = [ SignedVerification(data.hash, 1, sig), SignedSendDifficulty(0, 0, 1, sig), SignedDataDifficulty(0, 0, 1, sig) ] dataSent: bool = False for elem in elements: #Handshake with the node. rpc.meros.liveConnect(blockchain.blocks[0].header.hash) #Send the Data if we have yet to. if not dataSent: if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Data wasn't rebroadcasted.") dataSent = True #Send the Element. rpc.meros.signedElement(elem) #Sleep for thirty seconds to make sure Meros realizes our connection is dead. sleep(30) #Verify the node didn't crash. try: if rpc.call("merit", "getHeight") != 1: raise Exception() except Exception: raise TestError("Node crashed after being sent a malformed Element.")
def ChunkedEncodingTest(rpc: RPC) -> None: request: requests.Response = requests.post("http://127.0.0.1:" + str(rpc.meros.rpc), data=reqIter()) if request.status_code != 200: raise TestError("HTTP status isn't 200: " + str(request.status_code)) if request.json() != [{ "jsonrpc": "2.0", "id": 1, "result": 1 }, { "jsonrpc": "2.0", "id": 0, "result": Blockchain().difficulty() }]: raise TestError( "Meros didn't respond to a batch request (sent as chunks) properly." )
def TwoHundredSeventyThreeTest(meros: Meros) -> None: blockchain: Blockchain = Blockchain() vectors: Dict[str, Any] with open("e2e/Vectors/Merit/TwoHundredSeventyThree.json", "r") as file: vectors = json.loads(file.read()) header: BlockHeader = BlockHeader.fromJSON(vectors) meros.liveConnect(blockchain.last()) meros.syncConnect(blockchain.last()) #Sanity check on the behavior of select. readable, _, _ = select([meros.live.connection, meros.sync.connection], [], [], 65) if len(readable) != 1: raise Exception( "Misuse of select; multiple sockets reported readable.") if MessageType(meros.live.recv()[0]) != MessageType.Handshake: raise Exception( "Misuse of select; it didn't return the live socket trying to Handshake. Keep-alives could also be broken." ) meros.live.send(MessageType.BlockchainTail.toByte() + blockchain.last()) #Send the header. meros.liveBlockHeader(header) #Meros should disconnect us immediately. If it doesn't, it'll either send a keep-alive or a BlockBodyRequest. #One is inefficient as it doesn't properly protect against spam attacks. #One is invalid completely. readable, _, _ = select([meros.live.connection, meros.sync.connection], [], [], 65) #On Linux, both sockets immediately appear as readable. #That is why we iterate, instead of just checking length == 0. for s in readable: try: temp: str = s.recv(1) if len(temp) != 0: raise TestError( "Meros tried to send us something instead of immediately disconnecting us." ) except TestError as e: raise e except Exception: pass
def LANPeersTest(meros: Meros) -> None: #Solely used to get the genesis Block hash. blockchain: Blockchain = Blockchain() #Handshake with the node. meros.syncConnect(blockchain.blocks[0].header.hash) #Verify that sending a PeersRequest returns 0 peers. meros.peersRequest() if len(meros.sync.recv(True)) != 2: raise TestError("Meros sent peers.") #Create a server socket. server: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(("", 0)) server.listen(1) #Connect again. #Meros, if it doesn't realize we're a LAN peer, will try to connect to the above server to verify. #Since we're a LAN peer, it shouldn't bother. #Doesn't use MerosSocket so we can specify the port. serverConnection: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverConnection.connect(("127.0.0.1", meros.tcp)) serverConnection.send( MessageType.Syncing.toByte() + meros.protocol.to_bytes(1, "little") + meros.network.to_bytes(1, "little") + (0).to_bytes(1, "little") + server.getsockname()[1].to_bytes(2, "little") + blockchain.blocks[0].header.hash, False) serverConnection.recv(38) sleep(1) #Verify Meros ignores us as a peer since we're only available over the local network. meros.peersRequest() res: bytes = meros.sync.recv(True) if len(res) != 2: raise TestError("Meros sent peers.") #Close the new connection. serverConnection.close()