def fromSignedJSON(jsonArg: Dict[str, Any]) -> Any: json: Dict[str, Any] = dict(jsonArg) json["elements"] = list(json["elements"]) json["elements"][0] = dict(json["elements"][0]) json["elements"][1] = dict(json["elements"][1]) json["elements"][0]["holder"] = json["holder"] json["elements"][1]["holder"] = json["holder"] e1: MeritRemovalElement = SignedVerification(bytes(32), 0) if json["elements"][0]["descendant"] == "Verification": e1 = SignedVerification.fromSignedJSON(json["elements"][0]) elif json["elements"][0]["descendant"] == "VerificationPacket": e1 = SignedMeritRemovalVerificationPacket.fromSignedJSON( json["elements"][0]) elif json["elements"][0]["descendant"] == "SendDifficulty": e1 = SignedSendDifficulty.fromSignedJSON(json["elements"][0]) elif json["elements"][0]["descendant"] == "DataDifficulty": e1 = SignedDataDifficulty.fromSignedJSON(json["elements"][0]) e2: MeritRemovalElement = SignedVerification(bytes(32), 0) if json["elements"][1]["descendant"] == "Verification": e2 = SignedVerification.fromSignedJSON(json["elements"][1]) elif json["elements"][1]["descendant"] == "VerificationPacket": e2 = SignedMeritRemovalVerificationPacket.fromSignedJSON( json["elements"][1]) elif json["elements"][1]["descendant"] == "SendDifficulty": e2 = SignedSendDifficulty.fromSignedJSON(json["elements"][1]) elif json["elements"][1]["descendant"] == "DataDifficulty": e2 = SignedDataDifficulty.fromSignedJSON(json["elements"][1]) return SignedMeritRemoval(e1, e2, json["holder"])
def voteAndVerify() -> None: #Check the difficulties for a holder who has yet to vote. try: rpc.call("consensus", "getSendDifficulty", {"holder": 0}, False) raise TestError("") except TestError as e: if str(e) != "-2 Holder doesn't have a SendDifficulty.": raise TestError("getSendDifficulty didn't raise when asked about a holder who has yet to vote.") try: rpc.call("consensus", "getDataDifficulty", {"holder": 0}, False) raise TestError("") except TestError as e: if str(e) != "-2 Holder doesn't have a DataDifficulty.": raise TestError("getDataDifficulty didn't raise when asked about a holder who has yet to vote.") #Create the votes. sendDiff: SignedSendDifficulty = SignedSendDifficulty(6, 0) sendDiff.sign(0, PrivateKey(0)) dataDiff: SignedDataDifficulty = SignedDataDifficulty(10, 1) dataDiff.sign(0, PrivateKey(0)) #Send them. rpc.meros.signedElement(sendDiff) rpc.meros.signedElement(dataDiff) rpc.meros.live.recv() rpc.meros.live.recv() #Check them. if rpc.call("consensus", "getSendDifficulty", {"holder": 0}, False) != 6: raise TestError("getSendDifficulty didn't reply with the holder's current difficulty.") if rpc.call("consensus", "getDataDifficulty", {"holder": 0}, False) != 10: raise TestError("getDataDifficulty didn't reply with the holder's current difficulty.")
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 signElement(elem: Element) -> Signature: if isinstance(elem, Verification): verif: SignedVerification = SignedVerification(elem.hash, elem.holder) verif.sign(elem.holder, PrivateKey(elem.holder)) return verif.signature if isinstance(elem, SignedMeritRemovalVerificationPacket): return elem.signature if isinstance(elem, SendDifficulty): sendDiff: SignedSendDifficulty = SignedSendDifficulty( elem.difficulty, elem.nonce) sendDiff.sign(elem.holder, PrivateKey(elem.holder)) return sendDiff.signature if isinstance(elem, DataDifficulty): dataDiff: SignedDataDifficulty = SignedDataDifficulty( elem.difficulty, elem.nonce) dataDiff.sign(elem.holder, PrivateKey(elem.holder)) return dataDiff.signature if isinstance(elem, MeritRemoval): result: Signature = signElement(elem.e2) if not elem.partial: result = Signature.aggregate([result, signElement(elem.e1)]) return result raise GenerationError( "Tried to sign an Element in a Block we didn't recognize the type of.")
def HundredTwentyTest( rpc: RPC ) -> None: file: IO[Any] = open("e2e/Vectors/Consensus/MeritRemoval/HundredTwenty.json", "r") vectors: Dict[str, Any] = json.loads(file.read()) file.close() #DataDifficulty for the mempool. mempoolDataDiff: SignedDataDifficulty = SignedDataDifficulty.fromSignedJSON(vectors["mempoolDataDiff"]) #DataDifficulty for the Blockchain. blockchainDataDiff: DataDifficulty = DataDifficulty.fromJSON(vectors["blockchainDataDiff"]) def sendDataDifficulty() -> None: #Send the Data Difficulty for the mempool. rpc.meros.signedElement(mempoolDataDiff) #Verify its sent back. if rpc.meros.live.recv() != ( MessageType.SignedDataDifficulty.toByte() + mempoolDataDiff.signedSerialize() ): raise TestError("Meros didn't send us the mempool Data Difficulty.") def receiveMeritRemoval() -> None: #We should receive a MeritRemoval, which is partial. #The unsigned Element should be the Block's DataDifficulty. #The signed Element should be the mempool's DataDifficulty. if rpc.meros.live.recv() != ( MessageType.SignedMeritRemoval.toByte() + PartialMeritRemoval( blockchainDataDiff, mempoolDataDiff, 0 ).signedSerialize() ): raise TestError("Meros didn't create the partial Merit Removal.") #Verify Meros didn't just broadcast it, yet also added it. verifyMeritRemoval(rpc, 2, 2, 0, True) Liver( rpc, vectors["blockchain"], callbacks={ 1: sendDataDifficulty, 2: receiveMeritRemoval } ).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 signElement(elem: Element) -> Signature: if isinstance(elem, Verification): verif: SignedVerification = SignedVerification(elem.hash, elem.holder) verif.sign(elem.holder, PrivateKey(elem.holder)) return verif.signature if isinstance(elem, SendDifficulty): sendDiff: SignedSendDifficulty = SignedSendDifficulty( elem.difficulty, elem.nonce) sendDiff.sign(elem.holder, PrivateKey(elem.holder)) return sendDiff.signature if isinstance(elem, DataDifficulty): dataDiff: SignedDataDifficulty = SignedDataDifficulty( elem.difficulty, elem.nonce) dataDiff.sign(elem.holder, PrivateKey(elem.holder)) return dataDiff.signature raise GenerationError( "Tried to sign an Element in a Block we didn't recognize the type of.")
blsPubKey: PublicKey = blsPrivKey.toPublicKey() #Generate a Block granting the holder Merit. block = Block( BlockHeader(0, blockchain.last(), bytes(32), 1, bytes(4), bytes(32), blsPubKey.serialize(), blockchain.blocks[-1].header.time + 1200), BlockBody()) #Mine it. block.mine(blsPrivKey, blockchain.difficulty()) #Add it. blockchain.add(block) print("Generated Hundred Twenty Block " + str(len(blockchain.blocks)) + ".") #Create a DataDifficulty. dataDiff: SignedDataDifficulty = SignedDataDifficulty(3, 0) dataDiff.sign(0, blsPrivKey) #Create a conflicting DataDifficulty with the same nonce. dataDiffConflicting = SignedDataDifficulty(1, 0) dataDiffConflicting.sign(0, blsPrivKey) #Generate a Block containing the competing Data difficulty. block = Block( BlockHeader(0, blockchain.last(), BlockHeader.createContents([], [dataDiffConflicting]), 1, bytes(4), bytes(32), 0, blockchain.blocks[-1].header.time + 1200), BlockBody([], [dataDiffConflicting], dataDiffConflicting.signature)) #Mine it. block.mine(blsPrivKey, blockchain.difficulty())
from e2e.Libs.BLS import PrivateKey, PublicKey from e2e.Classes.Consensus.DataDifficulty import SignedDataDifficulty from e2e.Classes.Consensus.MeritRemoval import SignedMeritRemoval from e2e.Vectors.Generation.PrototypeChain import PrototypeChain blsPrivKey: PrivateKey = PrivateKey(0) blsPubKey: PublicKey = blsPrivKey.toPublicKey() proto: PrototypeChain = PrototypeChain(1, False) #Create conflicting Data Difficulties. dataDiffs: List[SignedDataDifficulty] = [ SignedDataDifficulty(3, 0), SignedDataDifficulty(4, 0) ] dataDiffs[0].sign(0, blsPrivKey) dataDiffs[1].sign(0, blsPrivKey) #Create a MeritRemoval out of the conflicting Data Difficulties. mr: SignedMeritRemoval = SignedMeritRemoval(dataDiffs[0], dataDiffs[1]) proto.add(elements=[mr]) #Create a MeritRemoval with the Elements swapped. swapped: SignedMeritRemoval = SignedMeritRemoval(dataDiffs[1], dataDiffs[0]) proto.add(elements=[swapped]) result: Dict[str, Any] = {"blockchain": proto.toJSON()} vectors: IO[Any] = open(
proto: PrototypeChain = PrototypeChain(1, False) blsPrivKey: PrivateKey = PrivateKey(0) blsPubKey: PublicKey = blsPrivKey.toPublicKey() edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() #Create the SendDifficulty MR. sendDiff: SignedSendDifficulty = SignedSendDifficulty(4, 0) sendDiff.sign(0, blsPrivKey) sendDiffMR: SignedMeritRemoval = SignedMeritRemoval(sendDiff, sendDiff) #Create the DataDifficulty MR. dataDiff: SignedDataDifficulty = SignedDataDifficulty(4, 0) dataDiff.sign(0, blsPrivKey) dataDiffMR: SignedMeritRemoval = SignedMeritRemoval(dataDiff, dataDiff) #Create the Verification MR. data: Data = Data(bytes(32), edPubKey.to_bytes()) data.sign(edPrivKey) data.beat(SpamFilter(5)) verif: SignedVerification = SignedVerification(data.hash) verif.sign(0, blsPrivKey) #Transform the Verification to a SignedMeritRemovalVerificationPacket. packet: SignedMeritRemovalVerificationPacket = SignedMeritRemovalVerificationPacket( SignedVerificationPacket(data.hash), [blsPubKey.serialize()], verif.signature
def TElementTest( rpc: RPC ) -> None: file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r") blocks: List[Dict[str, Any]] = json.loads(file.read()) file.close() merit: Merit = Merit() blsPrivKey: PrivateKey = PrivateKey(0) blsPubKey: str = blsPrivKey.toPublicKey().serialize().hex() #Handshake with the node. rpc.meros.liveConnect(merit.blockchain.blocks[0].header.hash) rpc.meros.syncConnect(merit.blockchain.blocks[0].header.hash) #Send the first Block. block: Block = Block.fromJSON(blocks[0]) merit.blockchain.add(block) rpc.meros.liveBlockHeader(block.header) #Handle sync requests. reqHash: bytes = bytes() while True: msg: bytes = rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.BlockBodyRequest: reqHash = msg[1 : 33] if reqHash != block.header.hash: raise TestError("Meros asked for a Block Body that didn't belong to the Block we just sent it.") rpc.meros.blockBody(block) break else: raise TestError("Unexpected message sent: " + msg.hex().upper()) if MessageType(rpc.meros.live.recv()[0]) != MessageType.BlockHeader: raise TestError("Meros didn't broadcast the Block Header it just added.") #Create and transmit a DataDifficulty. dataDiff: SignedDataDifficulty = SignedDataDifficulty(0, 0, 0) dataDiff.sign(0, blsPrivKey) rpc.meros.signedElement(dataDiff) sleep(0.5) #Verify the block template has the DataDifficulty. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", [blsPubKey]) template["header"] = bytes.fromhex(template["header"]) if template["header"][36 : 68] != BlockHeader.createContents([], [dataDiff]): raise TestError("Block template doesn't have the Data Difficulty.") #Mine the Block. block = Block( BlockHeader( 0, block.header.hash, BlockHeader.createContents([], [dataDiff]), 1, template["header"][-43 : -39], BlockHeader.createSketchCheck(template["header"][-43 : -39], []), 0, int.from_bytes(template["header"][-4:], byteorder="little"), ), BlockBody([], [dataDiff], dataDiff.signature) ) if block.header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") if block.body.serialize(block.header.sketchSalt) != bytes.fromhex(template["body"]): raise TestError("Failed to recreate the body.") block.mine(blsPrivKey, merit.blockchain.difficulty()) merit.blockchain.add(block) #Publish it. rpc.call( "merit", "publishBlock", [ template["id"], ( template["header"] + block.header.proof.to_bytes(4, byteorder="little") + block.header.signature + block.body.serialize(block.header.sketchSalt) ).hex() ] ) #Create and transmit a new DataDifficulty. dataDiff = SignedDataDifficulty(3, 1, 0) dataDiff.sign(0, blsPrivKey) rpc.meros.signedElement(dataDiff) sleep(0.5) #Verify the block template has the DataDifficulty. template = rpc.call("merit", "getBlockTemplate", [blsPubKey]) template["header"] = bytes.fromhex(template["header"]) if template["header"][36 : 68] != BlockHeader.createContents([], [dataDiff]): raise TestError("Block template doesn't have the new Data Difficulty.") #Create and transmit a new DataDifficulty reusing an existing nonce. signatures: List[Signature] = [dataDiff.signature] dataDiff = SignedDataDifficulty(4, 1, 0) dataDiff.sign(0, blsPrivKey) signatures.append(dataDiff.signature) rpc.meros.signedElement(dataDiff) sleep(0.5) #Verify the block template has a MeritRemoval. mr: MeritRemoval = MeritRemoval( SignedDataDifficulty(3, 1, 0), SignedDataDifficulty(4, 1, 0), False ) template = rpc.call("merit", "getBlockTemplate", [blsPubKey]) template["header"] = bytes.fromhex(template["header"]) if template["header"][36 : 68] != BlockHeader.createContents([], [mr]): raise TestError("Block template doesn't have the Merit Removal.") #Mine the Block. block = Block( BlockHeader( 0, block.header.hash, BlockHeader.createContents([], [mr]), 1, template["header"][-43 : -39], BlockHeader.createSketchCheck(template["header"][-43 : -39], []), 0, int.from_bytes(template["header"][-4:], byteorder="little") ), BlockBody([], [mr], Signature.aggregate(signatures)) ) if block.header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") if block.body.serialize(block.header.sketchSalt) != bytes.fromhex(template["body"]): raise TestError("Failed to recreate the body.") block.mine(blsPrivKey, merit.blockchain.difficulty()) merit.blockchain.add(block) rpc.call( "merit", "publishBlock", [ template["id"], ( template["header"] + block.header.proof.to_bytes(4, byteorder="little") + block.header.signature + block.body.serialize(block.header.sketchSalt) ).hex() ] ) verifyBlockchain(rpc, merit.blockchain)
def getBlockTemplateTest(rpc: RPC) -> None: edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) edPubKey: bytes = edPrivKey.get_verifying_key() blockchain: Blockchain = Blockchain() #Get multiple templates to verify they share an ID if they're requested within the same second. templates: List[Dict[str, Any]] = [] startTime: float = nextSecond() for k in range(5): templates.append( rpc.call("merit", "getBlockTemplate", {"miner": getMiner(k)}, False)) if int(startTime) != int(time.time()): #Testing https://github.com/MerosCrypto/Meros/issues/278 has a much more forgiving timer of < 1 second each. #That said, this test was written on the fair assumption of all the above taking place in a single second. raise Exception( "getBlockTemplate is incredibly slow, to the point an empty Block template takes > 0.2 seconds to grab, invalidating this test." ) for k, template in zip(range(5), templates): if template["id"] != int(startTime): raise TestError("Template ID isn't the time.") #Also check general accuracy. if bytes.fromhex(template["key"]) != blockchain.genesis: raise TestError("Template has the wrong RandomX key.") bytesHeader: bytes = bytes.fromhex(template["header"]) serializedHeader: bytes = BlockHeader( 0, blockchain.blocks[0].header.hash, bytes(32), 0, bytes(4), bytes(32), PrivateKey(k).toPublicKey().serialize(), int(startTime)).serialize()[:-52] #Skip over the randomized sketch salt. if (bytesHeader[:72] + bytesHeader[76:]) != (serializedHeader[:72] + serializedHeader[76:]): raise TestError("Template has an invalid header.") #Difficulty modified as this is a new miner. if template["difficulty"] != (blockchain.difficulty() * 11 // 10): raise TestError("Template's difficulty is wrong.") currTime: int = int(nextSecond()) template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False) if template["id"] != currTime: raise TestError("Template ID wasn't advanced with the time.") #Override the ID to enable easy comparison against a historical template. template["id"] = int(startTime) if int.from_bytes(bytes.fromhex(template["header"])[-4:], "little") != currTime: raise TestError("The header has the wrong time.") template["header"] = ( bytes.fromhex(template["header"])[:72] + #Use the header we'll compare to's salt. bytes.fromhex(templates[0]["header"])[72:76] + bytes.fromhex(template["header"])[76:-4] + #Also use its time. int(startTime).to_bytes(4, "little")).hex().upper() if template != templates[0]: raise TestError( "Template, minus the time difference, doesn't match the originally provided template." ) #Test that the templates are deleted whenever a new Block appears. #This is done by checking the error given when we use an old template. with open("e2e/Vectors/Merit/BlankBlocks.json", "r") as file: block: Block = Block.fromJSON(json.loads(file.read())[0]) blockchain.add(block) rpc.meros.liveConnect(blockchain.blocks[0].header.hash) rpc.meros.syncConnect(blockchain.blocks[0].header.hash) rpc.meros.liveBlockHeader(block.header) rpc.meros.rawBlockBody(block, 0) time.sleep(1) #Sanity check. if rpc.call("merit", "getHeight", auth=False) != 2: raise Exception("Didn't successfully send Meros the Block.") #Get a new template so Meros realizes the template situation has changed. rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False) try: rpc.call("merit", "publishBlock", { "id": int(startTime), "header": "" }, False) raise Exception("") except Exception as e: if str(e) != "-2 Invalid ID.": raise TestError("Meros didn't delete old template IDs.") #Test VerificationPacket inclusion. data: Data = Data(bytes(32), edPubKey) data.sign(edPrivKey) data.beat(SpamFilter(5)) verif: SignedVerification = SignedVerification(data.hash) verif.sign(0, PrivateKey(0)) packet = VerificationPacket(data.hash, [0]) rpc.meros.liveTransaction(data) rpc.meros.signedElement(verif) time.sleep(1) if bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False)["header"])[36:68] != BlockHeader.createContents( [packet]): raise TestError( "Meros didn't include the Verification in its new template.") #Test Element inclusion. sendDiff: SignedSendDifficulty = SignedSendDifficulty(0, 0) sendDiff.sign(0, PrivateKey(0)) rpc.meros.signedElement(sendDiff) time.sleep(1) if bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False)["header"])[36:68] != BlockHeader.createContents( [packet], [sendDiff]): raise TestError( "Meros didn't include the Element in its new template.") #The 88 test checks for the non-inclusion of Verifications with unmentioned predecessors. #Test for non-inclusion of Elements with unmentioned predecessors. sendDiffChild: SignedSendDifficulty = SignedSendDifficulty(0, 2) sendDiffChild.sign(0, PrivateKey(0)) rpc.meros.signedElement(sendDiffChild) time.sleep(1) if bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False)["header"])[36:68] != BlockHeader.createContents( [packet], [sendDiff]): raise TestError( "Meros did include an Element with an unmentioned parent in its new template." ) #If we send a block with a time in the future, yet within FTL (of course), make sure Meros can still generate a template. #Naively using the current time will create a negative clock, which isn't allowed. #Start by closing the sockets to give us time to work. rpc.meros.live.connection.close() rpc.meros.sync.connection.close() #Sleep to reset the connection state. time.sleep(35) #Create and mine the Block. header: BlockHeader = BlockHeader( 0, blockchain.blocks[-1].header.hash, bytes(32), 0, bytes(4), bytes(32), PrivateKey(0).toPublicKey().serialize(), 0, ) miningStart: int = 0 #If this block takes longer than 10 seconds to mine, try another. #Low future time (20 seconds) is chosen due to feasibility + supporting lowering the FTL in the future. while time.time() > miningStart + 10: miningStart = int(time.time()) header = BlockHeader( 0, blockchain.blocks[-1].header.hash, bytes(32), 0, bytes(4), bytes(32), #Mod by something is due to a 2-byte limit (IIRC -- Kayaba). #100 is just clean. +11 ensures an offset from the above, which really shouldn't be necessary. #If we did need one, +1 should work, as we only ever work with PrivateKey(0) on the blockchain. PrivateKey((miningStart % 100) + 10).toPublicKey().serialize(), int(time.time()) + 20, ) header.mine(PrivateKey((miningStart % 100) + 10), blockchain.difficulty() * 11 // 10) blockchain.add(Block(header, BlockBody())) #Send it and verify it. rpc.meros.liveConnect(blockchain.blocks[0].header.hash) rpc.meros.syncConnect(blockchain.blocks[0].header.hash) rpc.meros.liveBlockHeader(header) rpc.meros.rawBlockBody(Block(header, BlockBody()), 0) rpc.meros.live.connection.close() rpc.meros.sync.connection.close() time.sleep(1) #Ensure a stable template ID. currTime = int(nextSecond()) template = rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False) if template["id"] != currTime: raise TestError( "Template ID isn't the time when the previous Block is in the future." ) if int.from_bytes(bytes.fromhex(template["header"])[-4:], "little") != (header.time + 1): raise TestError( "Meros didn't handle generating a template off a Block in the future properly." ) #Verify a Block with three Elements from a holder, where two form a Merit Removal. #Only the two which cause a MeritRemoval should be included. #Mine a Block to a new miner and clear the current template with it (might as well). #Also allows us to test template clearing. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", {"miner": getMiner(1)}, False) #Mine the Block. 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 = PrivateKey(1).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() }) time.sleep(1) #Verify the template was cleared. currTime = int(nextSecond()) bytesHeader: bytes = bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False)["header"]) serializedHeader: bytes = BlockHeader( 0, tempHash, bytes(32), 0, bytes(4), bytes(32), 0, #Ensures that the previous time manipulation doesn't come back to haunt us. max(currTime, blockchain.blocks[-1].header.time + 1)).serialize()[:-52] #Skip over the randomized sketch salt and time (which we don't currently have easy access to). if (bytesHeader[:72] + bytesHeader[76:-4]) != (serializedHeader[:72] + serializedHeader[76:-4]): raise TestError("Template wasn't cleared.") #Sleep so we can reconnect. time.sleep(35) rpc.meros.liveConnect(blockchain.blocks[0].header.hash) #Finally create the Elements. dataDiff: SignedDataDifficulty = SignedDataDifficulty(1, 0) dataDiff.sign(2, PrivateKey(1)) rpc.meros.signedElement(dataDiff) sendDiffs: List[SignedSendDifficulty] = [ SignedSendDifficulty(1, 1), SignedSendDifficulty(2, 1) ] for sd in sendDiffs: sd.sign(2, PrivateKey(1)) rpc.meros.signedElement(sd) time.sleep(1) #`elem for elem` is used below due to Pyright not handling inheritance properly when nested. #pylint: disable=unnecessary-comprehension if bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": getMiner(0)}, False)["header"])[36:68] != BlockHeader.createContents( [], [elem for elem in sendDiffs[::-1]]): raise TestError( "Meros didn't include just the malicious Elements in its new template." )
def TElementTest(rpc: RPC) -> None: merit: Merit = Merit() blsPrivKey: PrivateKey = PrivateKey(0) blsPubKey: str = blsPrivKey.toPublicKey().serialize().hex() #Handshake with the node. rpc.meros.liveConnect(merit.blockchain.blocks[0].header.hash) rpc.meros.syncConnect(merit.blockchain.blocks[0].header.hash) #Send the first Block. block: Block with open("e2e/Vectors/Merit/BlankBlocks.json", "r") as file: block = Block.fromJSON(json.loads(file.read())[0]) merit.blockchain.add(block) rpc.meros.liveBlockHeader(block.header) rpc.meros.handleBlockBody(block) if MessageType(rpc.meros.live.recv()[0]) != MessageType.BlockHeader: raise TestError( "Meros didn't broadcast the Block Header it just added.") #Create and transmit a DataDifficulty. dataDiff: SignedDataDifficulty = SignedDataDifficulty(0, 0, 0) dataDiff.sign(0, blsPrivKey) rpc.meros.signedElement(dataDiff) sleep(1.5) #Verify the block template has the DataDifficulty. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", {"miner": blsPubKey}) template["header"] = bytes.fromhex(template["header"]) if template["header"][36:68] != BlockHeader.createContents([], [dataDiff]): raise TestError("Block template doesn't have the Data Difficulty.") #Mine the Block. block = Block( BlockHeader( 0, block.header.hash, BlockHeader.createContents([], [dataDiff]), 0, template["header"][-43:-39], BlockHeader.createSketchCheck(template["header"][-43:-39], []), 0, int.from_bytes(template["header"][-4:], byteorder="little"), ), BlockBody([], [dataDiff], dataDiff.signature)) if block.header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") block.mine(blsPrivKey, merit.blockchain.difficulty()) merit.blockchain.add(block) #Publish it. rpc.call( "merit", "publishBlock", { "id": template["id"], "header": (template["header"] + block.header.proof.to_bytes(4, byteorder="little") + block.header.signature).hex() }) #Create and transmit a new DataDifficulty. dataDiff = SignedDataDifficulty(3, 0, 0) dataDiff.sign(0, blsPrivKey) rpc.meros.signedElement(dataDiff) sleep(1.5) #Verify the block template has a MeritRemoval. #Thanks to implicit Merit Removals, this just means it has the new difficulty. template = rpc.call("merit", "getBlockTemplate", {"miner": blsPubKey}) template["header"] = bytes.fromhex(template["header"]) if template["header"][36:68] != BlockHeader.createContents([], [dataDiff]): raise TestError("Block template doesn't have the Merit Removal.") #Mine the Block. block = Block( BlockHeader( 0, block.header.hash, BlockHeader.createContents([], [dataDiff]), 0, template["header"][-43:-39], BlockHeader.createSketchCheck(template["header"][-43:-39], []), 0, int.from_bytes(template["header"][-4:], byteorder="little")), BlockBody([], [dataDiff], dataDiff.signature)) if block.header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") block.mine(blsPrivKey, merit.blockchain.difficulty()) merit.blockchain.add(block) rpc.call( "merit", "publishBlock", { "id": template["id"], "header": (template["header"] + block.header.proof.to_bytes(4, byteorder="little") + block.header.signature).hex() }) verifyBlockchain(rpc, merit.blockchain)
blsPrivKey: PrivateKey = PrivateKey(blake2b(b'\0', digest_size=32).digest()) blsPubKey: PublicKey = blsPrivKey.toPublicKey() #Mine 24 more Blocks so a vote is earned on the Block after this. for _ in range(24): block = Block( BlockHeader(0, blockchain.last(), bytes(32), 0, bytes(4), bytes(32), 0, blockchain.blocks[-1].header.time + 1200), BlockBody()) block.mine(blsPrivKey, blockchain.difficulty()) blockchain.add(block) #Now that we have a vote, submit a Block which has two DataDifficulty updates in the same Block. #The first Element should have a higher nonce than the second. #This will verify Meros properly handles out-of-order Elements included in a Block. dataDiffs: List[SignedDataDifficulty] = [ SignedDataDifficulty(1, 1), SignedDataDifficulty(2, 0) ] for dataDiff in dataDiffs: dataDiff.sign(0, blsPrivKey) for _ in range(2): block = Block( BlockHeader( 0, blockchain.last(), #Used so the type checkers realize List[SignedDataDifficulty] is a viable List[Element]. #pylint: disable=unnecessary-comprehension BlockHeader.createContents([], [e for e in dataDiffs]), 0, bytes(4),
blsPubKey: PublicKey = blsPrivKey.toPublicKey() #Generate a Block granting the holder Merit. block = Block( BlockHeader(0, blockchain.last(), bytes(32), 1, bytes(4), bytes(32), blsPubKey.serialize(), blockchain.blocks[-1].header.time + 1200), BlockBody()) #Mine it. block.mine(blsPrivKey, blockchain.difficulty()) #Add it. blockchain.add(block) print("Generated Repeat Block " + str(len(blockchain.blocks)) + ".") #Create a DataDifficulty. dataDiff: SignedDataDifficulty = SignedDataDifficulty(3, 0) dataDiff.sign(0, blsPrivKey) #Generate a Block containing the DataDifficulty. block = Block( BlockHeader(0, blockchain.last(), BlockHeader.createContents([], [dataDiff]), 1, bytes(4), bytes(32), 0, blockchain.blocks[-1].header.time + 1200), BlockBody([], [dataDiff], dataDiff.signature)) #Mine it. block.mine(blsPrivKey, blockchain.difficulty()) #Add it. blockchain.add(block) print("Generated Repeat Block " + str(len(blockchain.blocks)) + ".")
import json from e2e.Libs.BLS import PrivateKey, PublicKey from e2e.Classes.Consensus.DataDifficulty import SignedDataDifficulty from e2e.Classes.Consensus.MeritRemoval import SignedMeritRemoval from e2e.Vectors.Generation.PrototypeChain import PrototypeChain proto: PrototypeChain = PrototypeChain(1, False) blsPrivKey: PrivateKey = PrivateKey(0) blsPubKey: PublicKey = blsPrivKey.toPublicKey() #Create a DataDifficulty. dataDiff: SignedDataDifficulty = SignedDataDifficulty(3, 0) dataDiff.sign(0, blsPrivKey) #Create a conflicting DataDifficulty with the same nonce. dataDiffConflicting: SignedDataDifficulty = SignedDataDifficulty(1, 0) dataDiffConflicting.sign(0, blsPrivKey) #Create a MeritRemoval out of the two of them. mr: SignedMeritRemoval = SignedMeritRemoval(dataDiff, dataDiffConflicting) proto.add(elements=[dataDiff, dataDiffConflicting]) with open("e2e/Vectors/Consensus/MeritRemoval/SameNonce.json", "w") as vectors: vectors.write( json.dumps({ "blockchain": proto.toJSON(), "removal": mr.toSignedJSON()