def NodeThresholdTest(rpc: RPC) -> None: edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) dataFilter: SpamFilter = SpamFilter(5) datas: List[Data] = [Data(bytes(32), edPrivKey.get_verifying_key())] datas[-1].sign(edPrivKey) datas[-1].beat(dataFilter) def verifyThreshold(b: int) -> None: rpc.meros.liveTransaction(datas[-1]) datas.append(Data(datas[-1].hash, b"a")) datas[-1].sign(edPrivKey) datas[-1].beat(dataFilter) #Swallow the new Data(s). if b == 1: rpc.meros.live.recv() rpc.meros.live.recv() #Check the threshold. threshold: int = rpc.call("consensus", "getStatus", {"hash": datas[-2].hash.hex()})["threshold"] if b < 9: if threshold != ((max(b + 6, 5) // 5 * 4) + 1): raise TestError( "Meros didn't calculate the right node threshold. That said, this isn't defined by the protocol." ) elif threshold != 5: raise TestError("Meros didn't lower the node threshold.") with open("e2e/Vectors/Merit/BlankBlocks.json", "r") as file: Liver(rpc, json.loads(file.read())[:9], everyBlock=verifyThreshold).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 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 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 verifyThreshold(b: int) -> None: rpc.meros.liveTransaction(datas[-1]) datas.append(Data(datas[-1].hash, b"a")) datas[-1].sign(edPrivKey) datas[-1].beat(dataFilter) #Swallow the new Data(s). if b == 1: rpc.meros.live.recv() rpc.meros.live.recv() #Check the threshold. threshold: int = rpc.call("consensus", "getStatus", {"hash": datas[-2].hash.hex()})["threshold"] if b < 9: if threshold != ((max(b + 6, 5) // 5 * 4) + 1): raise TestError( "Meros didn't calculate the right node threshold. That said, this isn't defined by the protocol." ) elif threshold != 5: raise TestError("Meros didn't lower the node threshold.")
def DataTest(rpc: RPC) -> None: #Get the genesis hash. genesis: bytes = Blockchain().blocks[0].header.hash #Create the Spam Filter. spamFilter: SpamFilter = SpamFilter(5) #Create the Data. data: Data = Data(bytes(32), pubKey.to_bytes()) data.sign(privKey) data.beat(spamFilter) #Handshake with the node. rpc.meros.liveConnect(genesis) #Send the Data. rpc.meros.liveTransaction(data) #Sleep for 100 milliseconds. sleep(0.1) #Verify the Data. verifyTransaction(rpc, data)
def live(self, ignorePackets: List[bytes] = []) -> None: #Handshake with the node. self.rpc.meros.liveConnect(self.merit.blockchain.blocks[0].header.hash) self.rpc.meros.syncConnect(self.merit.blockchain.blocks[0].header.hash) #Send each Block. for b in range(1, len(self.merit.blockchain.blocks)): block: Block = self.merit.blockchain.blocks[b] #Set loop variables with pending data. pendingBody: bool = True pendingPackets: List[bytes] = [] pendingTXs: List[bytes] = [] for packet in block.body.packets: if packet.hash in ignorePackets: continue pendingPackets.append(packet.hash) #Don't include sent Transactions or independently created Block Data. if not ((packet.hash in self.rpc.meros.sentTXs) or (packet.hash == (Data(self.merit.blockchain.genesis, block.header.last).hash))): pendingTXs.append(packet.hash) for elem in block.body.elements: if isinstance(elem, MeritRemoval): if (isinstance(elem.e1, (Verification, VerificationPacket)) and (elem.e1.hash not in self.rpc.meros.sentTXs) and (elem.e1.hash not in pendingTXs)): pendingTXs.append(elem.e1.hash) if (isinstance(elem.e2, (Verification, VerificationPacket)) and (elem.e2.hash not in self.rpc.meros.sentTXs) and (elem.e2.hash not in pendingTXs)): pendingTXs.append(elem.e2.hash) self.rpc.meros.liveBlockHeader(block.header) reqHash: bytes = bytes() while True: #If we sent every bit of data, break. if ((not pendingBody) and (not pendingPackets) and (not pendingTXs)): break #Receive the next message. msg: bytes = self.rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.BlockBodyRequest: reqHash = msg[1:33] if not pendingBody: raise TestError( "Meros asked for the same Block Body multiple times." ) if reqHash != block.header.hash: raise TestError( "Meros asked for a Block Body that didn't belong to the Block we just sent it." ) self.rpc.meros.blockBody(block) pendingBody = False elif MessageType(msg[0]) == MessageType.SketchHashesRequest: reqHash = msg[1:33] if not block.body.packets: raise TestError( "Meros asked for Sketch Hashes from a Block without any." ) if reqHash != block.header.hash: raise TestError( "Meros asked for Sketch Hashes that didn't belong to the Block we just sent it." ) #Create the haashes. hashes: List[int] = [] for packet in block.body.packets: hashes.append( Sketch.hash(block.header.sketchSalt, packet)) self.rpc.meros.sketchHashes(hashes) elif MessageType(msg[0]) == MessageType.SketchHashRequests: reqHash = msg[1:33] if not block.body.packets: raise TestError( "Meros asked for Verification Packets from a Block without any." ) if reqHash != block.header.hash: raise TestError( "Meros asked for Verification Packets that didn't belong to the Block we just sent it." ) #Create a lookup of hash to packets. 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.") self.rpc.meros.packet(packets[sketchHash]) #Delete the VerificationPacket from pending. del pendingPackets[pendingPackets.index( packets[sketchHash].hash)] #Make sure Meros asked for every packet. if pendingPackets: raise TestError( "Meros didn't ask for every Verification Packet.") elif MessageType(msg[0]) == MessageType.TransactionRequest: reqHash = msg[1:33] if self.transactions is None: raise TestError( "Meros asked for a Transaction when we have none.") if reqHash not in pendingTXs: raise TestError( "Meros asked for a non-existent Transaction, a Transaction part of a different Block, or an already sent Transaction." ) self.rpc.meros.syncTransaction( self.transactions.txs[reqHash]) #Delete the Transaction from pending. del pendingTXs[pendingTXs.index(reqHash)] else: raise TestError("Unexpected message sent: " + msg.hex().upper()) #Receive the BlockHeader from Meros. if MessageType( self.rpc.meros.live.recv()[0]) != MessageType.BlockHeader: raise TestError("Meros didn't broadcast the new BlockHeader.") #Add any new nicks to the lookup table. if self.merit.blockchain.blocks[b].header.newMiner: self.merit.state.nicks.append( self.merit.blockchain.blocks[b].header.minerKey) #If there's a callback at this height, call it. if b in self.callbacks: self.callbacks[b]() #Execute the every-Block callback, if it exists. if self.everyBlock is not None: self.everyBlock(b) #Verify the Blockchain. verifyBlockchain(self.rpc, self.merit.blockchain) #Verify the Transactions. if self.transactions is not None: verifyTransactions(self.rpc, self.transactions) #Reset the node. self.rpc.reset()
dataFilter: SpamFilter = SpamFilter(5) #Ed25519 keys. edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() #BLS keys. blsPrivKey: PrivateKey = PrivateKey(blake2b(b'\0', digest_size=32).digest()) blsPubKey: PublicKey = blsPrivKey.toPublicKey() #Add 5 Blank Blocks. for i in range(5): merit.add(Block.fromJSON(blankBlocks[i])) #Create the Data. data: Data = Data(bytes(32), edPubKey.to_bytes()) data.sign(edPrivKey) data.beat(dataFilter) transactions.add(data) #Verify it. verif: SignedVerification = SignedVerification(data.hash) verif.sign(0, blsPrivKey) #Generate another 6 Blocks. #Next block should have a packet. block: Block = Block( BlockHeader( 0, merit.blockchain.last(), BlockHeader.createContents([VerificationPacket(verif.hash, [0])]), 1, bytes(4),
from e2e.Classes.Consensus.Verification import SignedVerification from e2e.Classes.Consensus.VerificationPacket import VerificationPacket from e2e.Classes.Consensus.SpamFilter import SpamFilter from e2e.Vectors.Generation.PrototypeChain import PrototypeChain dataFilter: SpamFilter = SpamFilter(5) edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) edPubKey: bytes = edPrivKey.get_verifying_key() proto: PrototypeChain = PrototypeChain(40, keepUnlocked=True) proto.add(1) datas: List[Data] = [Data(bytes(32), edPubKey)] for d in range(2): datas.append(Data(datas[0].hash, d.to_bytes(1, "little"))) for data in datas: data.sign(edPrivKey) data.beat(dataFilter) verif: SignedVerification = SignedVerification(datas[1].hash) verif.sign(0, PrivateKey(0)) proto.add( packets=[ VerificationPacket(datas[0].hash, [0]), VerificationPacket(datas[2].hash, [1]) ] )
def StringBasedTypesTest( rpc: RPC ) -> None: edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) edPubKey: bytes = edPrivKey.get_verifying_key() blsPubKey: PublicKey = PrivateKey(0).toPublicKey() #hex. #Test 0x-prefixed and no-0x both work without issue. data: Data = Data(bytes(32), edPubKey) data.sign(edPrivKey) rpc.call("transactions", "publishTransactionWithoutWork", {"type": "Data", "transaction": "0x" + data.serialize()[:-4].hex()}) data = Data(data.hash, b"abc") data.sign(edPrivKey) rpc.call("transactions", "publishTransactionWithoutWork", {"type": "Data", "transaction": data.serialize()[:-4].hex()}) #Test non-hex data is properly handled. try: rpc.call("transactions", "publishTransactionWithoutWork", {"type": "Data", "transaction": "az"}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted non-hex data for a hex argument.") #Hash. rpc.call("transactions", "getTransaction", {"hash": data.hash.hex()}) rpc.call("transactions", "getTransaction", {"hash": "0x" + data.hash.hex()}) #Also call the upper form, supplementing the above hex tests (as Hash routes through hex). rpc.call("transactions", "getTransaction", {"hash": data.hash.hex().upper()}) rpc.call("transactions", "getTransaction", {"hash": "0x" + data.hash.hex().upper()}) #Improper length. try: rpc.call("transactions", "getTransaction", {"hash": data.hash.hex().upper()[:-2]}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted a hex string with an improper length as a Hash.") #BLSPublicKey. try: rpc.call("merit", "getNickname", {"key": blsPubKey.serialize().hex()}) raise TestError() except Exception as e: if str(e) != "-2 Key doesn't have a nickname assigned.": raise TestError("Meros didn't accept a valid BLSPublicKey.") try: rpc.call("merit", "getNickname", {"key": blsPubKey.serialize().hex()[:-2]}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted a hex string with an improper length as a BLSPublicKey.") #Missing flags. try: rpc.call("merit", "getNickname", {"key": "0" + blsPubKey.serialize().hex()[1]}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted an invalid BLSPublicKey as a BLSPublicKey.") #EdPublicKey. rpc.call("personal", "setAccount", {"key": edPubKey.hex(), "chainCode": bytes(32).hex()}) try: rpc.call("personal", "setAccount", {"key": edPubKey[:-2].hex(), "chainCode": bytes(32).hex()}) raise TestError() except Exception as e: if str(e) != "-32602 Invalid params.": raise TestError("Meros accepted a hex string with an improper length as an EdPublicKey.")
def EightyEightTest( rpc: RPC ) -> None: edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() blsPrivKey: PrivateKey = PrivateKey(0) blsPubKey: str = blsPrivKey.toPublicKey().serialize().hex() file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r") blocks: List[Dict[str, Any]] = json.loads(file.read()) file.close() merit: Merit = Merit() dataFilter: SpamFilter = SpamFilter(5) #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.") #Send the BlockBody. 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 two Datas. datas: List[Data] = [Data(bytes(32), edPubKey.to_bytes())] datas.append(Data(datas[0].hash, b"Hello there! General Kenobi.")) for data in datas: #Sign them and have them beat the spam filter. data.sign(edPrivKey) data.beat(dataFilter) #Transmit them. rpc.meros.liveTransaction(data) #Verify both. verifs: List[SignedVerification] = [ SignedVerification(datas[0].hash), SignedVerification(datas[1].hash) ] for verif in verifs: verif.sign(0, blsPrivKey) #Only transmit the second. rpc.meros.signedElement(verifs[1]) sleep(0.5) #Verify the block template has no verifications. if bytes.fromhex( rpc.call("merit", "getBlockTemplate", [blsPubKey])["header"] )[36 : 68] != bytes(32): raise TestError("Block template has Verification Packets.") #Transmit the first signed verification. rpc.meros.signedElement(verifs[0]) sleep(0.5) #Verify the block template has both verifications. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", [blsPubKey]) template["header"] = bytes.fromhex(template["header"]) packets: List[VerificationPacket] = [VerificationPacket(datas[0].hash, [0]), VerificationPacket(datas[1].hash, [0])] if template["header"][36 : 68] != BlockHeader.createContents(packets): raise TestError("Block template doesn't have both Verification Packets.") #Mine the Block. block = Block( BlockHeader( 0, block.header.hash, BlockHeader.createContents(packets), 1, template["header"][-43 : -39], BlockHeader.createSketchCheck(template["header"][-43 : -39], packets), 0, int.from_bytes(template["header"][-4:], byteorder="little") ), BlockBody( packets, [], Signature.aggregate([verifs[0].signature, verifs[1].signature]) ) ) if block.header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") if block.body.serialize( block.header.sketchSalt, len(packets) ) != 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, len(packets)) ).hex() ] ) verifyBlockchain(rpc, merit.blockchain)
from e2e.Classes.Transactions.Data import Data from e2e.Classes.Consensus.Verification import SignedVerification from e2e.Classes.Consensus.VerificationPacket import VerificationPacket from e2e.Classes.Consensus.SpamFilter import SpamFilter from e2e.Vectors.Generation.PrototypeChain import PrototypeChain privKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) pubKey: bytes = privKey.get_verifying_key() spamFilter: SpamFilter = SpamFilter(5) proto: PrototypeChain = PrototypeChain(1, keepUnlocked=False) data: Data = Data(bytes(32), pubKey) datas: List[Dict[str, Any]] = [] verifs: List[Dict[str, Any]] = [] for i in range(5): data.sign(privKey) data.beat(spamFilter) datas.append(data.toJSON()) if i != 4: verif: SignedVerification = SignedVerification(data.hash) verif.sign(0, PrivateKey(0)) verifs.append(verif.toSignedJSON()) data = Data(data.hash, bytes(1)) proto.add( 0,
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 Prune Unaddable Block " + str(len(blockchain.blocks)) + ".") #Create the original Data. datas: List[Data] = [Data(bytes(32), edPubKey.to_bytes())] datas[0].sign(edPrivKey) datas[0].beat(dataFilter) #Verify it. verifs: List[SignedVerification] = [SignedVerification(datas[0].hash)] verifs[0].sign(0, blsPrivKey) #Create two competing Datas yet only verify the first. for d in range(2): datas.append(Data(datas[0].hash, d.to_bytes(1, "big"))) datas[1 + d].sign(edPrivKey) datas[1 + d].beat(dataFilter) verifs.append(SignedVerification(datas[1].hash)) verifs[1].sign(0, blsPrivKey)
import json import e2e.Libs.Ristretto.Ristretto as Ristretto from e2e.Classes.Transactions.Data import Data from e2e.Classes.Consensus.VerificationPacket import VerificationPacket from e2e.Classes.Consensus.SpamFilter import SpamFilter from e2e.Vectors.Generation.PrototypeChain import PrototypeChain dataFilter: SpamFilter = SpamFilter(5) edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) edPubKey: bytes = edPrivKey.get_verifying_key() proto: PrototypeChain = PrototypeChain(1, keepUnlocked=False) data: Data = Data(bytes(32), edPubKey) data.signature = edPrivKey.sign(b"INVALID") data.beat(dataFilter) proto.add(packets=[VerificationPacket(data.hash, [0])]) with open("e2e/Vectors/Consensus/Verification/Parsable.json", "w") as vectors: vectors.write( json.dumps({ "blockchain": proto.toJSON(), "data": data.toJSON() }))
def sync(self) -> None: self.rpc.meros.syncConnect( self.merit.blockchain.blocks[self.settings["height"]].header.hash) reqHash: bytes = bytes() while True: #Break out of the for loop if the sync finished. #This means we sent every Block, every Element, every Transaction... if ((self.blockHashes == set()) and (self.packets == {}) and (self.txs == set())): break msg: bytes = self.rpc.meros.sync.recv() if MessageType(msg[0]) == MessageType.BlockListRequest: reqHash = msg[-32:] for b in range(len(self.merit.blockchain.blocks)): if self.merit.blockchain.blocks[b].header.hash == reqHash: if self.blocks[-1].header.hash != reqHash: self.blocks.append(self.merit.blockchain.blocks[b]) blockList: List[bytes] = [] for bl in range(1, msg[1] + 2): if b - bl < 0: break blockList.append( self.merit.blockchain.blocks[b - bl].header.hash) if b - bl != 0: self.blocks.append( self.merit.blockchain.blocks[b - bl]) if blockList == []: self.rpc.meros.dataMissing() break self.rpc.meros.blockList(blockList) break if b == self.settings["height"]: self.rpc.meros.dataMissing() elif MessageType(msg[0]) == MessageType.BlockHeaderRequest: reqHash = msg[1:33] if (self.txs != set()) or (self.packets != {}): raise TestError( "Meros asked for a new Block before syncing the last Block's Transactions and Packets." ) if reqHash != self.blocks[-1].header.hash: raise TestError( "Meros asked for a BlockHeader other than the next Block's on the last BlockList." ) self.rpc.meros.syncBlockHeader(self.blocks[-1].header) elif MessageType(msg[0]) == MessageType.BlockBodyRequest: reqHash = msg[1:33] if reqHash != self.blocks[-1].header.hash: raise TestError( "Meros asked for a BlockBody other than the next Block's on the last BlockList." ) if int.from_bytes(msg[33:37], "little") != len( self.blocks[-1].body.packets): raise Exception( "Meros didn't request 100% of packets in the BlockBody when syncing." ) self.rpc.meros.rawBlockBody(self.blocks[-1], len(self.blocks[-1].body.packets)) self.blockHashes.remove(self.blocks[-1].header.hash) #Set the packets/transactions which should be synced. self.packets = {} for packet in self.blocks[-1].body.packets: if not ((packet.hash in self.rpc.meros.sentTXs) or (packet.hash == (Data( self.merit.blockchain.genesis, self.blocks[-1].header.last).hash))): self.txs.add(packet.hash) self.packets[Sketch.hash(self.blocks[-1].header.sketchSalt, packet)] = packet #Update the list of mentioned Transactions. noVCMRs: bool = True if (self.packets == {}) and noVCMRs: del self.blocks[-1] elif MessageType(msg[0]) == MessageType.SketchHashRequests: reqHash = msg[1:33] if not self.packets: raise TestError( "Meros asked for Verification Packets from a Block without any." ) if reqHash != self.blocks[-1].header.hash: raise TestError( "Meros asked for Verification Packets that didn't belong to the Block we just sent it." ) #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 self.packets: raise TestError( "Meros asked for a non-existent Sketch Hash.") self.rpc.meros.packet(self.packets[sketchHash]) del self.packets[sketchHash] if (self.packets == {}) and (self.txs == set()): del self.blocks[-1] elif MessageType(msg[0]) == MessageType.TransactionRequest: reqHash = msg[1:33] if self.transactions is None: raise TestError( "Meros asked for a Transaction when we have none.") if reqHash not in self.transactions.txs: raise TestError( "Meros asked for a Transaction we don't have.") if reqHash not in self.txs: raise TestError( "Meros asked for a Transaction we haven't mentioned.") self.rpc.meros.syncTransaction(self.transactions.txs[reqHash]) self.txs.remove(reqHash) if (self.packets == {}) and (self.txs == set()): del self.blocks[-1] else: raise TestError("Unexpected message sent: " + msg.hex().upper()) #Verify the Blockchain. verifyBlockchain(self.rpc, self.merit.blockchain) #Verify the Transactions. if self.transactions is not None: verifyTransactions(self.rpc, self.transactions) #Playback their messages. #Verifies Meros can respond as it can receive. if self.settings["playback"]: self.rpc.meros.sync.playback() #Reset the node. self.rpc.reset()
from e2e.Classes.Consensus.SpamFilter import SpamFilter from e2e.Vectors.Generation.PrototypeChain import PrototypeChain proto: PrototypeChain = PrototypeChain(1, False) edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() blsPrivKey: PrivateKey = PrivateKey(0) blsPubKey: PublicKey = blsPrivKey.toPublicKey() spamFilter: SpamFilter = SpamFilter(5) #Create the initial Data and two competing Datas. datas: List[Data] = [Data(bytes(32), edPubKey.to_bytes())] datas.append(Data(datas[0].hash, b"Initial Data.")) datas.append(Data(datas[0].hash, b"Second Data.")) for data in datas: data.sign(edPrivKey) data.beat(spamFilter) #Create Verifications for all 3. verifs: List[SignedVerification] = [] for data in datas: verifs.append(SignedVerification(data.hash, 0)) verifs[-1].sign(0, blsPrivKey) #Create a MeritRemoval out of the conflicting Verifications. mr: SignedMeritRemoval = SignedMeritRemoval(verifs[1], verifs[2])
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 Invalid Competing Block " + str(len(blockchain.blocks)) + ".") #Create a Data using a bogus input. data: Data = Data(bytes.fromhex("11" * 32), bytes(1)) transactions.add(data) #Create a competing Data using the same input. competingData: Data = Data(bytes.fromhex("11" * 32), bytes(2)) transactions.add(competingData) #Verify the Datas. verif: SignedVerification = SignedVerification(data.hash) verif.sign(0, blsPrivKey) competingVerif: SignedVerification = SignedVerification(competingData.hash) competingVerif.sign(0, blsPrivKey) #Create a MeritRemoval out of the conflicting Verifications. mr: SignedMeritRemoval = SignedMeritRemoval(verif, competingVerif)
def EightyEightTest(rpc: RPC) -> None: edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) edPubKey: bytes = edPrivKey.get_verifying_key() blsPrivKey: PrivateKey = PrivateKey(0) blsPubKey: str = blsPrivKey.toPublicKey().serialize().hex() merit: Merit = Merit() dataFilter: SpamFilter = SpamFilter(5) #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 two Datas. datas: List[Data] = [Data(bytes(32), edPubKey)] datas.append(Data(datas[0].hash, b"Hello there! General Kenobi.")) for data in datas: #Sign them and have them beat the spam filter. data.sign(edPrivKey) data.beat(dataFilter) #Transmit them. rpc.meros.liveTransaction(data) #Verify both. verifs: List[SignedVerification] = [ SignedVerification(datas[0].hash), SignedVerification(datas[1].hash) ] for verif in verifs: verif.sign(0, blsPrivKey) #Only transmit the second. rpc.meros.signedElement(verifs[1]) sleep(1.5) #Verify the block template has no verifications. if bytes.fromhex( rpc.call("merit", "getBlockTemplate", {"miner": blsPubKey})["header"])[36:68] != bytes(32): raise TestError("Block template has Verification Packets.") #Transmit the first signed verification. rpc.meros.signedElement(verifs[0]) sleep(1.5) #Verify the block template has both verifications. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", {"miner": blsPubKey}) template["header"] = bytes.fromhex(template["header"]) packets: List[VerificationPacket] = [ VerificationPacket(datas[0].hash, [0]), VerificationPacket(datas[1].hash, [0]) ] if template["header"][36:68] != BlockHeader.createContents(packets): raise TestError( "Block template doesn't have both Verification Packets.") #Mine the Block. block = Block( BlockHeader( 0, block.header.hash, BlockHeader.createContents(packets), len(packets), template["header"][-43:-39], BlockHeader.createSketchCheck(template["header"][-43:-39], packets), 0, int.from_bytes(template["header"][-4:], byteorder="little")), BlockBody( packets, [], Signature.aggregate([verifs[0].signature, verifs[1].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)
def HundredFiftyFiveTest(rpc: RPC) -> None: edPrivKeys: List[ed25519.SigningKey] = [ ed25519.SigningKey(b'\0' * 32), ed25519.SigningKey(b'\1' * 32) ] edPubKeys: List[ed25519.VerifyingKey] = [ edPrivKeys[0].get_verifying_key(), edPrivKeys[1].get_verifying_key() ] blsPrivKey: PrivateKey = PrivateKey( bytes.fromhex(rpc.call("personal", "getMiner"))) blsPubKey: bytes = blsPrivKey.toPublicKey().serialize() blockchain: Blockchain = Blockchain() dataFilter: SpamFilter = SpamFilter(5) #Handshake with the node. rpc.meros.liveConnect(blockchain.blocks[0].header.hash) rpc.meros.syncConnect(blockchain.blocks[0].header.hash) #Call getBlockTemplate just to get an ID. #Skips the need to write a sync loop for the BlockBody. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", [blsPubKey.hex()]) #Mine a Block. block = Block( BlockHeader(0, blockchain.blocks[0].header.hash, bytes(32), 1, bytes(4), bytes(32), blsPubKey, blockchain.blocks[0].header.time + 1200, 0), BlockBody()) block.mine(blsPrivKey, blockchain.difficulty()) blockchain.add(block) rpc.call("merit", "publishBlock", [template["id"], block.serialize().hex()]) if MessageType(rpc.meros.live.recv()[0]) != MessageType.BlockHeader: raise TestError("Meros didn't broadcast the Block we just published.") #Ignore the Verification for the Block's Data. if MessageType(rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError( "Meros didn't send the SignedVerification for the Block's Data.") datas: List[Data] = [ Data(bytes(32), edPubKeys[0].to_bytes()), Data(bytes(32), edPubKeys[1].to_bytes()) ] for d in range(len(datas)): datas[d].sign(edPrivKeys[d]) datas[d].beat(dataFilter) #Send the Data and verify Meros sends it back. if rpc.meros.liveTransaction(datas[d]) != rpc.meros.live.recv(): raise TestError("Meros didn't send back the Data.") #Verify Meros sends back a Verification. res: bytes = rpc.meros.live.recv() if MessageType(res[0]) != MessageType.SignedVerification: raise TestError("Meros didn't send a SignedVerification.") verif: SignedVerification = SignedVerification(datas[d].hash) verif.sign(0, blsPrivKey) if res[1:] != verif.signedSerialize(): raise TestError( "Meros didn't send the correct SignedVerification.")
transactions: Transactions = Transactions() edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() blsPrivKey: PrivateKey = PrivateKey(blake2b(b'\0', digest_size=32).digest()) blsPubKey: PublicKey = blsPrivKey.toPublicKey() block: Block = Block( BlockHeader(0, blockchain.last(), bytes(32), 1, bytes(4), bytes(32), blsPubKey.serialize(), blockchain.blocks[-1].header.time + 1200), BlockBody()) block.mine(blsPrivKey, blockchain.difficulty()) blockchain.add(block) data = Data(b"", b"") svs: List[SignedVerification] = [] packets: List[VerificationPacket] = [] for d in range(0, 3): if d == 0: data = Data(bytes(32), edPubKey.to_bytes()) else: data = Data(data.hash, edPubKey.to_bytes()) data.sign(edPrivKey) data.beat(dataFilter) transactions.add(data) svs.append(SignedVerification(data.hash)) svs[-1].sign(0, blsPrivKey) packets.append(VerificationPacket(data.hash, [0]))
from e2e.Classes.Transactions.Data import Data from e2e.Classes.Consensus.Verification import SignedVerification from e2e.Classes.Consensus.VerificationPacket import VerificationPacket from e2e.Classes.Consensus.SpamFilter import SpamFilter from e2e.Vectors.Generation.PrototypeChain import PrototypeChain privKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) pubKey: ed25519.VerifyingKey = privKey.get_verifying_key() spamFilter: SpamFilter = SpamFilter(5) proto: PrototypeChain = PrototypeChain(1, keepUnlocked=False) data: Data = Data(bytes(32), pubKey.to_bytes()) datas: List[Dict[str, Any]] = [] verifs: List[Dict[str, Any]] = [] for i in range(5): data.sign(privKey) data.beat(spamFilter) datas.append(data.toJSON()) if i != 4: verif: SignedVerification = SignedVerification(data.hash) verif.sign(0, PrivateKey(0)) verifs.append(verif.toSignedJSON()) data = Data(data.hash, bytes(1)) proto.add(0, [VerificationPacket(bytes.fromhex(data["hash"]), [0]) for data in datas])
from e2e.Classes.Transactions.Data import Data from e2e.Classes.Consensus.Verification import SignedVerification from e2e.Classes.Consensus.VerificationPacket import VerificationPacket from e2e.Classes.Consensus.SpamFilter import SpamFilter from e2e.Vectors.Generation.PrototypeChain import PrototypeChain privKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) pubKey: ed25519.VerifyingKey = privKey.get_verifying_key() spamFilter: SpamFilter = SpamFilter(5) proto: PrototypeChain = PrototypeChain(1, keepUnlocked=False) data: Data = Data(bytes(32), pubKey.to_bytes()) datas: List[Dict[str, Any]] = [] verifs: List[Dict[str, Any]] = [] for _ in range(2): data.sign(privKey) data.beat(spamFilter) datas.append(data.toJSON()) verif: SignedVerification = SignedVerification(data.hash) verif.sign(0, PrivateKey(0)) verifs.append(verif.toSignedJSON()) data = Data(data.hash, bytes(2)) privKey = ed25519.SigningKey(b'\1' * 32) pubKey = privKey.get_verifying_key() data = Data(bytes(32), pubKey.to_bytes())
def finish(self, keepUnlocked: int, existing: Merit) -> Block: genesis: bytes = existing.blockchain.genesis prev: BlockHeader = existing.blockchain.blocks[-1].header diff: int = existing.blockchain.difficulty() #Create the signatures for every packet/element. signatures: List[Signature] = [] for packet in self.packets: for holder in packet.holders: verif: SignedVerification = SignedVerification( packet.hash, holder) verif.sign(holder, PrivateKey(holder)) signatures.append(verif.signature) for element in self.elements: signatures.append(signElement(element)) #Only add the Data Verification if: #1) We're supposed to make sure Merit Holders are always Unlocked #2) The last Block created a Data #3) The Merit Holder has Merit. if (keepUnlocked != 0) and (prev.last != genesis): #Create the Data from the last Block. blockData: Data = Data(genesis, prev.hash) #Create Verifications for said Data with every Private Key. #Ensures no one has their Merit locked. #pylint: disable=unnecessary-comprehension self.packets.append(VerificationPacket(blockData.hash, [])) for i in range(keepUnlocked): if ( #Miners who are just being created don't have Merit. ((i == (keepUnlocked - 1)) and (isinstance(self.minerID, PrivateKey))) or (existing.state.balances[i] == 0)): continue self.packets[-1].holders.append(i) verif: SignedVerification = SignedVerification( blockData.hash, i) verif.sign(i, PrivateKey(i)) signatures.append(verif.signature) #Remove this packet if there's no holders. if not self.packets[-1].holders: del self.packets[-1] #Set the aggregate. aggregate = Signature.aggregate(signatures) #Create the actual Block. minerID: Union[bytes, int] = 0 if isinstance(self.minerID, int): minerID = self.minerID else: minerID = self.minerID.toPublicKey().serialize() result: Block = Block( BlockHeader( 0, prev.hash, BlockHeader.createContents(self.packets, self.elements), len(self.packets), bytes(4), BlockHeader.createSketchCheck(bytes(4), self.packets), minerID, self.time), BlockBody(self.packets, self.elements, aggregate)) if isinstance(self.minerID, int): result.mine(PrivateKey(self.minerID), diff) else: result.mine(self.minerID, diff) return result
import e2e.Libs.Ristretto.Ristretto as Ristretto from e2e.Libs.Minisketch import Sketch from e2e.Classes.Transactions.Data import Data from e2e.Classes.Transactions.Transactions import Transactions from e2e.Classes.Consensus.VerificationPacket import VerificationPacket from e2e.Vectors.Generation.PrototypeChain import PrototypeChain edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) edPubKey: bytes = edPrivKey.get_verifying_key() proto: PrototypeChain = PrototypeChain(1, keepUnlocked=False) datas: List[Data] = [Data(bytes(32), edPubKey)] counter: int = 0 datas.append(Data(datas[0].hash, counter.to_bytes(4, byteorder="little"))) while (Sketch.hash(bytes(4), VerificationPacket(datas[0].hash, [0])) <= Sketch.hash(bytes(4), VerificationPacket(datas[1].hash, [0]))): counter += 1 datas[1] = Data(datas[0].hash, counter.to_bytes(4, byteorder="little")) proto.add(packets=[ VerificationPacket(datas[1].hash, [0]), VerificationPacket(datas[0].hash, [0]) ]) transactions: Transactions = Transactions() for data in datas:
#Add a 5th Block to another verifier. block: Block = Block( BlockHeader(0, merit.blockchain.last(), bytes(32), 1, bytes(4), bytes(32), blsPubKeys[1].serialize(), merit.blockchain.blocks[-1].header.time + 1200), BlockBody()) #Mine it. block.mine(blsPrivKeys[1], merit.blockchain.difficulty()) #Add it. merit.add(block) print("Generated Aggregated Claim Block " + str(len(merit.blockchain.blocks) - 1) + ".") #Create the Datas. datas: List[Data] = [Data(bytes(32), edPubKey.to_bytes())] datas.append(Data(datas[-1].hash, bytes(1))) datas.append(Data(datas[-1].hash, bytes(1))) datas.append(Data(datas[-1].hash, bytes(1))) for data in datas: data.sign(edPrivKey) data.beat(dataFilter) transactions.add(data) #Verify them. verifs: List[List[SignedVerification]] = [] for data in datas: verifs.append( [SignedVerification(data.hash), SignedVerification(data.hash)]) for v in range(2):
def HundredSeventySevenTest( rpc: RPC ) -> None: #Grab the keys. blsPrivKey: PrivateKey = PrivateKey(bytes.fromhex(rpc.call("personal", "getMiner"))) blsPubKey: PublicKey = blsPrivKey.toPublicKey() #Faux Blockchain used to calculate the difficulty. blockchain: Blockchain = Blockchain() #Mine 8 Blocks. #The first creates the initial Data. #The next 6 pop it from the Epochs. #One more is to verify the next is popped as well. for b in range(0, 8): template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", [blsPubKey.serialize().hex()]) template["header"] = bytes.fromhex(template["header"]) header: BlockHeader = BlockHeader( 0, template["header"][4 : 36], template["header"][36 : 68], int.from_bytes(template["header"][68 : 70], byteorder="big"), template["header"][70 : 74], template["header"][74 : 106], 0, int.from_bytes(template["header"][-4:], byteorder="big") ) if b == 0: header.newMiner = True header.minerKey = blsPubKey.serialize() else: header.newMiner = False header.minerNick = 0 if header.serializeHash()[:-4] != template["header"]: raise TestError("Failed to recreate the header.") header.mine(blsPrivKey, blockchain.difficulty()) #Sleep for just over two thirty-second cycles to make sure we can connect to the node. sleep(65) rpc.meros.liveConnect(blockchain.blocks[-1].header.hash) blockchain.add(Block(header, BlockBody())) rpc.call( "merit", "publishBlock", [ template["id"], ( template["header"] + header.proof.to_bytes(4, byteorder="big") + header.signature + bytes.fromhex(template["body"]) ).hex() ] ) if rpc.meros.live.recv() != rpc.meros.liveBlockHeader(header): raise TestError("Meros didn't broadcast the BlockHeader.") #If there's supposed to be a Mint, verify Meros claimed it. if b >= 6: #Artificially create the Mint since the Blockchain won't create one without a recreated BlockBody. #It's faster to create a faux Mint than to handle the BlockBodies. mint: Mint = Mint(header.hash, [(0, 50000)]) claim: Claim = Claim([(mint.hash, 0)], bytes(32)) claim.sign([blsPrivKey]) if rpc.meros.live.recv()[0 : -80] != (MessageType.Claim.toByte() + claim.serialize())[0 : -80]: raise TestError("Meros didn't claim its Mint.") if MessageType(rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError("Meros didn't verify its Claim.") #Create the matching Data. data: Data = Data(blockchain.genesis, header.hash) #Make sure Meros broadcasts a valid Verification for it. verif: SignedVerification = SignedVerification(data.hash) verif.sign(0, blsPrivKey) if rpc.meros.live.recv() != rpc.meros.signedElement(verif): raise TestError("Meros didn't verify the Block's Data.") #Close the live connection so we don't have to worry about it being disconnected for inactivity. try: rpc.meros.live.connection.shutdown(socket.SHUT_RDWR) rpc.meros.live.connection.close() except OSError: pass sleep(3)
def TwoHundredThirtyFiveTest(rpc: RPC) -> None: blockchain: Blockchain = Blockchain() dataFilter: SpamFilter = SpamFilter(5) edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() #Mine one Block to the node. blsPrivKey: PrivateKey = PrivateKey( bytes.fromhex(rpc.call("personal", "getMiner"))) blsPubKey: bytes = blsPrivKey.toPublicKey().serialize() #Call getBlockTemplate just to get an ID. #Skips the need to write a sync loop for the BlockBody. template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", [blsPubKey.hex()]) #Mine a Block. block = Block( BlockHeader(0, blockchain.blocks[0].header.hash, bytes(32), 1, bytes(4), bytes(32), blsPubKey, blockchain.blocks[0].header.time + 1200, 0), BlockBody()) block.mine(blsPrivKey, blockchain.difficulty()) blockchain.add(block) rpc.call("merit", "publishBlock", [template["id"], block.serialize().hex()]) #Send Meros a Data and receive its Verification to make sure it's verifying Transactions in the first place. data: Data = Data(bytes(32), edPubKey.to_bytes()) data.sign(edPrivKey) data.beat(dataFilter) rpc.meros.liveConnect(blockchain.blocks[0].header.hash) if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Meros didn't send back the Data.") if MessageType(rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError("Meros didn't send us its SignedVerification.") #Close our connection and mine 8 Blocks so its Merit is locked. rpc.meros.live.connection.close() for _ in range(8): block = Block( BlockHeader(0, blockchain.blocks[-1].header.hash, bytes(32), 1, bytes(4), bytes(32), 0, blockchain.blocks[-1].header.time + 1200, 0), BlockBody()) #Reusing its key is fine as mining doesn't count as participation. block.mine(blsPrivKey, blockchain.difficulty()) blockchain.add(block) #Sleep 30 seconds to make sure Meros noted we disconnected, and then reconnect. sleep(30) rpc.meros.liveConnect(blockchain.blocks[0].header.hash) rpc.meros.syncConnect(blockchain.blocks[0].header.hash) #Sync the Blocks. for b in range(8): header: bytes = rpc.meros.liveBlockHeader(blockchain.blocks[b + 2].header) if rpc.meros.sync.recv() != (MessageType.BlockBodyRequest.toByte() + blockchain.blocks[b + 2].header.hash): raise TestError("Meros didn't request the BlockBody.") rpc.meros.blockBody(blockchain.blocks[b + 2]) if rpc.meros.live.recv() != header: raise TestError("Meros didn't send back the header.") if MessageType( rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError("Meros didn't verify this Block's data.") #Verify its Merit is locked. #Theoretically, all code after this check is unecessary. #Meros verifies a Block's Data after updating its State. #Therefore, if the above last Block had its Data verified, this issue should be closed. #That said, the timing is a bit too tight for comfort. #Better safe than sorry. Hence why the code after this check exists. if rpc.call("merit", "getMerit", [0])["status"] != "Locked": raise TestError("Merit wasn't locked when it was supposed to be.") #Send it a Transaction and make sure Meros verifies it, despite having its Merit locked. data = Data(data.hash, edPubKey.to_bytes()) data.sign(edPrivKey) data.beat(dataFilter) if rpc.meros.liveTransaction(data) != rpc.meros.live.recv(): raise TestError("Meros didn't send back the Data.") if MessageType(rpc.meros.live.recv()[0]) != MessageType.SignedVerification: raise TestError("Meros didn't send us its SignedVerification.")
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." )
dataFilter: SpamFilter = SpamFilter(5) #Ed25519 keys. edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32) edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key() #BLS keys. blsPrivKey: PrivateKey = PrivateKey(blake2b(b'\0', digest_size=32).digest()) blsPubKey: PublicKey = blsPrivKey.toPublicKey() #Add 1 Blank Block. for i in range(1): merit.add(Block.fromJSON(blankBlocks[i])) #Create the Data and a successor. first: Data = Data(bytes(32), edPubKey.to_bytes()) first.sign(edPrivKey) first.beat(dataFilter) transactions.add(first) second: Data = Data(first.hash, bytes(1)) second.sign(edPrivKey) second.beat(dataFilter) transactions.add(second) #Verify them. firstVerif: SignedVerification = SignedVerification(first.hash) firstVerif.sign(0, blsPrivKey) secondVerif: SignedVerification = SignedVerification(second.hash) secondVerif.sign(0, blsPrivKey)
from e2e.Vectors.Generation.PrototypeChain import PrototypeBlock blsPrivKey: PrivateKey = PrivateKey(0) edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32) edPubKey: bytes = edPrivKey.get_verifying_key() dataFilter: SpamFilter = SpamFilter(5) #Create a Block to earn Merit. blocks: List[Block] = [] blocks.append(PrototypeBlock(1200, minerID=blsPrivKey).finish(0, Merit())) #Create five Datas and matching Verifications. #The test only sends a couple of the Verifications; that said, it's easy to generate the Block signature thanks to this. txs: List[Data] = [Data(bytes(32), edPubKey)] verifs: List[SignedVerification] = [] for i in range(5): txs[-1].sign(edPrivKey) txs[-1].beat(dataFilter) verifs.append(SignedVerification(txs[-1].hash)) verifs[-1].sign(0, blsPrivKey) txs.append(Data(txs[-1].hash, b"\0")) del txs[-1] #Create the final Block. #Done manually as it has invalid data. packets: List[VerificationPacket] = [ VerificationPacket(tx.hash, [0]) for tx in txs ]