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 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 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 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 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 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 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 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 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 StateTest(rpc: RPC) -> None: #Blocks. file: IO[Any] = open("e2e/Vectors/Merit/StateBlocks.json", "r") blocks: List[Dict[str, Any]] = json.loads(file.read()) file.close() #Blockchain. blockchain: Blockchain = Blockchain.fromJSON(blocks) #State. state: State = State() def checkState(block: int) -> None: state.add(blockchain, block) for miner in state.unlocked: if rpc.call("merit", "getMerit", [miner]) != { "unlocked": True, "malicious": False, "merit": state.unlocked[miner] }: raise TestError("Merit doesn't match.") Liver(rpc, blocks, everyBlock=checkState).live()
def THFSLessWorkTest( rpc: RPC ) -> None: chains: Dict[str, List[Dict[str, Any]]] with open("e2e/Vectors/Merit/Reorganizations/LongerChainMoreWork.json", "r") as file: chains = json.loads(file.read()) #Load the alternate blockchain. alt: Blockchain = Blockchain.fromJSON(chains["alt"]) #Load a chain of the fork point. forkPoint: Blockchain = Blockchain.fromJSON(chains["main"][0 : 15]) #Send the alternate tip. def sendAlternateTip() -> None: 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[-32:] != 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 = -14 while diff != -1: if rpc.meros.sync.recv() != (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 #Meros will now attempt the re-org, having verified the work. #Break the chain early via a data missing. diff = -14 while diff != 0: if diff == -10: if rpc.meros.sync.recv()[:-4] != (MessageType.BlockBodyRequest.toByte() + alt.blocks[diff].header.hash): raise TestError("Meros didn't request a previous BlockBody.") rpc.meros.dataMissing() sleep(35) for socket in [rpc.meros.live, rpc.meros.sync]: socket.connection.close() #We could just edit the condition above, yet this keeps parity with the other reorg tests. break else: rpc.meros.handleBlockBody(alt.blocks[diff]) diff += 1 #Verify Meros at least went back to the fork point. #Ideally, it'd go back to the original chain. #Or if we synced enough blocks where we still have a chain with more work, we should remain on it. verifyBlockchain(rpc, forkPoint) raise SuccessError("Meros reverted back to the fork point.") with raises(SuccessError): Liver( rpc, chains["main"], callbacks={ 25: sendAlternateTip } ).live()
#Ed25519 lib. import ed25519 #Blake2b standard function. from hashlib import blake2b #JSON standard lib. import json cmFile: IO[Any] = open("e2e/Vectors/Transactions/ClaimedMint.json", "r") cmVectors: Dict[str, Any] = json.loads(cmFile.read()) #Transactions. transactions: Transactions = Transactions.fromJSON(cmVectors["transactions"]) #Blockchain. blockchain: Blockchain = Blockchain.fromJSON(cmVectors["blockchain"]) cmFile.close() #SpamFilter. sendFilter: SpamFilter = SpamFilter(3) #Ed25519 keys. edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() #BLS keys. blsPrivKeys: List[PrivateKey] = [ PrivateKey(blake2b(b'\0', digest_size=32).digest()), PrivateKey(blake2b(b'\1', digest_size=32).digest()) ] blsPubKeys: List[PublicKey] = [
def TwoHundredTwentyEightTest( 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 Blockchains. main: Blockchain = Blockchain.fromJSON(chains["main"]) alt: Blockchain = Blockchain.fromJSON(chains["alt"]) def send16AndInvalidAlt() -> None: #Send the Block after the fork from the main chain. header: bytes = rpc.meros.liveBlockHeader(main.blocks[16].header) req: bytes = rpc.meros.sync.recv() if req != (MessageType.BlockBodyRequest.toByte() + main.blocks[16].header.hash): raise TestError("Meros didn't request the BlockBody for this Block from the main chain.") rpc.meros.blockBody(main.blocks[16]) if rpc.meros.live.recv() != header: raise TestError("Meros didn't send back the BlockHeader.") #Send the headers of the alt chain to trigger a re-org. header = rpc.meros.liveBlockHeader(alt.blocks[-1].header) req = 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 #Meros should now revert and attempt the re-org. #Disconnect to make sure it fails. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() #Sleep long enough to get counted as disconnected and removed from the list of connections. sleep(35) #Reconnect. rpc.meros.live = MerosSocket(rpc.meros.tcp, 254, 254, True, main.blocks[15].header.hash) rpc.meros.sync = MerosSocket(rpc.meros.tcp, 254, 254, False, main.blocks[15].header.hash) #The Liver will now send Block 16 again. The trick is whether or not it can add 16. #If it can't, because it didn't prune the Data, this issue is still valid. #This is based off the ShorterChainMoreWork test. #While that test syncs the full chain A, it then syncs the alt chain. #As this chain tests a failed re-org attempt, we can stay on the main chain and fully verify it. #So instead of syncing the full chain, we go to right before the fork, and manually sync the forked Block. #Then we trigger the re-org, cause it to fail, preserving the revert. #Then the Liver syncs the rest of the main chain, unaware of this. Liver( rpc, chains["main"], callbacks={ 15: send16AndInvalidAlt } ).live()
def verify() -> None: verifyMeritRemoval(rpc, 1, 1, removal.holder, False) verifyBlockchain(rpc, Blockchain.fromJSON(vectors["blockchain"])) raise SuccessError( "MeritRemoval and Blockchain were properly handled.")
def ChainReorgDifferentKeyTest(rpc: RPC) -> None: file: IO[Any] = open("e2e/Vectors/Merit/RandomX/ChainReorgSameKey.json", "r") chains: Dict[str, List[Dict[str, Any]]] = json.loads(file.read()) file.close() #Load the blockchains. main: Blockchain = Blockchain.fromJSON(chains["main"]) alt: Blockchain = Blockchain.fromJSON(chains["alt"]) #Send the alternate tip. def sendAlternateTip() -> None: 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 != (len(alt.blocks) - 35): blockList.append(alt.blocks[b].header.hash) b -= 1 rpc.meros.blockList(blockList) diff = -20 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 #Advance the chain far enough to switch to the new key. diff = -20 while diff != -11: 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 #Cause the reorganization to fail. if MessageType( rpc.meros.sync.recv()[0]) != MessageType.BlockBodyRequest: raise TestError("Meros didn't request a BlockBody.") rpc.meros.dataMissing() sleep(65) rpc.meros.liveConnect(main.blocks[0].header.hash) rpc.meros.syncConnect(main.blocks[0].header.hash) #Sync back the regular chain. rpc.meros.liveBlockHeader(main.blocks[400].header) if MessageType( rpc.meros.sync.recv()[0]) != MessageType.BlockListRequest: raise TestError("Meros didn't request the Block list.") blockList = [] b = 398 while b != 380: blockList.append(main.blocks[b].header.hash) b -= 1 rpc.meros.blockList(blockList) for b in range(391, 400): if rpc.meros.sync.recv() != ( MessageType.BlockHeaderRequest.toByte() + main.blocks[b].header.hash): raise TestError("Meros didn't request the BlockHeader.") rpc.meros.syncBlockHeader(main.blocks[b].header) for b in range(391, 401): if rpc.meros.sync.recv() != (MessageType.BlockBodyRequest.toByte() + main.blocks[b].header.hash): raise TestError("Meros didn't request the BlockBody.") rpc.meros.blockBody(main.blocks[b]) Liver(rpc, chains["main"], callbacks={400: sendAlternateTip}).live()
from e2e.Classes.Merit.Block import Block from e2e.Classes.Merit.Blockchain import Blockchain #Blake2b standard function. from hashlib import blake2b #JSON standard lib. import json #Miner Private Key. privKey: PrivateKey = PrivateKey(blake2b(b'\0', digest_size=32).digest()) #Blockchains. bbFile: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r") blocks: List[Dict[str, Any]] = json.loads(bbFile.read()) main: Blockchain = Blockchain.fromJSON(blocks) #Only add the first 15 to the alt chain. alt: Blockchain = Blockchain() for b in range(15): alt.add(Block.fromJSON(blocks[b])) #Generate an alternative five Blocks. for i in range(1, 5): #Create the next Block. block = Block( BlockHeader( 0, alt.last(), bytes(32), 1, bytes(4),
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 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 TwoHundredThirtyTwoTest(rpc: RPC) -> None: file: IO[Any] = open( "e2e/Vectors/Merit/Reorganizations/TwoHundredThirtyTwo.json", "r") chains: Dict[str, List[Dict[str, Any]]] = json.loads(file.read()) main: Blockchain = Blockchain.fromJSON(chains["main"]) alt: Blockchain = Blockchain.fromJSON(chains["alt"]) file.close() def sendBlock(toSend: Block) -> None: rpc.meros.liveBlockHeader(toSend.header) if rpc.meros.sync.recv() != (MessageType.BlockBodyRequest.toByte() + toSend.header.hash): raise TestError("Meros didn't ask for this Block's body.") rpc.meros.blockBody(toSend) if toSend.body.packets: if rpc.meros.sync.recv() != ( MessageType.SketchHashRequests.toByte() + toSend.header.hash + (1).to_bytes(4, byteorder="little") + Sketch.hash( toSend.header.sketchSalt, toSend.body.packets[0]).to_bytes( 8, byteorder="little")): raise TestError( "Meros didn't ask for this BlockBody's VerificationPacket." ) rpc.meros.packet(toSend.body.packets[0]) #Make the initial connection and sync 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]) #Trigger the reorganization to the alternate chain. #We only want the revert aspect of this. rpc.meros.liveBlockHeader(alt.blocks[3].header) if rpc.meros.sync.recv()[0] != MessageType.BlockListRequest.toByte()[0]: raise TestError( "Meros didn't ask for the Block List of the alternate chain.") rpc.meros.blockList([alt.blocks[2].header.hash, alt.blocks[1].header.hash]) if rpc.meros.sync.recv() != (MessageType.BlockHeaderRequest.toByte() + alt.blocks[2].header.hash): raise TestError( "Meros didn't ask for the other BlockHeader in this alternate chain." ) rpc.meros.syncBlockHeader(alt.blocks[2].header) #Cause the re-organization to fail. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() rpc.socket.close() sleep(35) #Reboot the node to reload the database. rpc.meros.quit() rpc.meros.calledQuit = False rpc.meros.process = Popen([ "./build/Meros", "--data-dir", rpc.meros.dataDir, "--log-file", rpc.meros.log, "--db", rpc.meros.db, "--network", "devnet", "--tcp-port", str(rpc.meros.tcp), "--rpc-port", str(rpc.meros.rpc), "--no-gui" ]) while True: try: connection: socket.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connection.connect(("127.0.0.1", rpc.meros.rpc)) connection.shutdown(socket.SHUT_RDWR) connection.close() break except ConnectionRefusedError: sleep(1) rpc.meros.liveConnect(main.blocks[0].header.hash) rpc.meros.syncConnect(main.blocks[0].header.hash) sendBlock(main.blocks[2]) rpc.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) rpc.socket.connect(("127.0.0.1", rpc.meros.rpc)) verifyBlockchain(rpc, main)
def TwoHundredSixtyOneTest(rpc: RPC) -> None: merit: Merit = Merit() blsPrivKey: PrivateKey = PrivateKey( bytes.fromhex(rpc.call("personal", "getMeritHolderKey"))) blsPubKey: str = blsPrivKey.toPublicKey().serialize().hex() #Get a template. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", {"miner": blsPubKey}) template["header"] = bytes.fromhex(template["header"]) #Mine it. #Ignores the template except for the ID needed to publish it. #We could publish it over the socket to an identical effect, practically. #That said, this is more accurate to flow. block: Block = PrototypeBlock(merit.blockchain.blocks[-1].header.time + 1200, minerID=blsPrivKey).finish(0, merit) merit.add(block) #Connect in order to receive their Verification of the Block's Data. rpc.meros.liveConnect(merit.blockchain.blocks[0].header.hash) rpc.meros.syncConnect(merit.blockchain.blocks[0].header.hash) #Publish it. rpc.call("merit", "publishBlock", { "id": template["id"], "header": block.header.serialize().hex() }) if MessageType(rpc.meros.live.recv()[0]) != MessageType.BlockHeader: raise TestError("Meros didn't broadcast a published Block.") #Receive their Verification. if MessageType(rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError("Meros didn't verify the Block's Data.") #Reorg past the chain with them as the nick. with open("e2e/Vectors/Merit/BlankBlocks.json", "r") as file: blankBlocks: Blockchain = Blockchain.fromJSON(json.loads(file.read())) rpc.meros.liveBlockHeader(blankBlocks.blocks[2].header) if MessageType( rpc.meros.sync.recv()[0]) != MessageType.BlockListRequest: raise TestError( "Meros didn't request the Block List needed to reorg.") rpc.meros.blockList([blankBlocks.blocks[0].header.hash]) if MessageType( rpc.meros.sync.recv()[0]) != MessageType.BlockHeaderRequest: raise TestError( "Meros didn't request the next Block Header on the list.") rpc.meros.syncBlockHeader(blankBlocks.blocks[1].header) for b in range(2): rpc.meros.handleBlockBody(blankBlocks.blocks[b + 1]) #Close the connection to give us time to mine Blocks without worrying about the handshake. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() sleep(65) #Mine Blocks so we can re-org back to the original chain. merit.add( PrototypeBlock(merit.blockchain.blocks[-1].header.time + 1200, minerID=PrivateKey(1)).finish(0, merit)) merit.add( PrototypeBlock(merit.blockchain.blocks[-1].header.time + 1200, minerID=1).finish(0, merit)) #Reconnect. rpc.meros.liveConnect(merit.blockchain.blocks[0].header.hash) rpc.meros.syncConnect(merit.blockchain.blocks[0].header.hash) #Send the header for the original chain. rpc.meros.liveBlockHeader(merit.blockchain.blocks[3].header) if MessageType(rpc.meros.sync.recv()[0]) != MessageType.BlockListRequest: raise TestError("Meros didn't request the Block List needed to reorg.") rpc.meros.blockList([ merit.blockchain.blocks[1].header.hash, merit.blockchain.blocks[0].header.hash ]) for h in range(2): if MessageType( rpc.meros.sync.recv()[0]) != MessageType.BlockHeaderRequest: raise TestError( "Meros didn't request the next Block Header on the list.") rpc.meros.syncBlockHeader(merit.blockchain.blocks[h + 1].header) for b in range(3): rpc.meros.handleBlockBody(merit.blockchain.blocks[b + 1]) if MessageType(rpc.meros.live.recv()[0]) != MessageType.BlockHeader: raise TestError("Meros didn't broadcast a Block it just synced.") if MessageType(rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError( "Meros didn't verify the Block's Data after the re-org.")