Example #1
0
    def verifyBeaten() -> None:
        #Verify beaten was set. The fourth Transaction is also beaten, yet should be pruned.
        #That's why we don't check its status.
        for send in sends[1:3]:
            if not rpc.call("consensus", "getStatus",
                            [send.hash.hex()])["beaten"]:
                raise TestError(
                    "Meros didn't mark a child and its descendant as beaten.")

        #Check the pending Verification for the beaten descendant was deleted.
        if ((rpc.call("consensus", "getStatus",
                      [sends[2].hash.hex()])["verifiers"] != [0])
                or (bytes.fromhex(
                    rpc.call("merit", "getBlockTemplate",
                             [blsPubKey])["header"])[36:68] != bytes(32))):
            raise TestError("Block template still has the Verification.")

        #Verify the fourth Transaction was pruned.
        with raises(TestError):
            rpc.call("transactions", "getTransaction", [sends[3].hash.hex()])

        #Verify neither the second or third Transaction tree can be appended to.
        #Publishes a never seen-before Send for the descendant.
        #Re-broadcasts the pruned Transaction for the parent.
        for send in sends[3:]:
            #Most of these tests use a socket connection for this.
            #This has identical effects, returns an actual error instead of a disconnect,
            #and doesn't force us to wait a minute for our old socket to be cleared.
            with raises(TestError):
                rpc.call("transactions", "publishSend",
                         [send.serialize().hex()])

        #Not loaded above as it can only be loqaded after the chain starts, which is done by the Liver.
        #RandomX cache keys and all that.
        blockWBeatenVerif: Block = Block.fromJSON(
            vectors["blockWithBeatenVerification"])

        #The following code used to test behavior which was removed, in order to be more forgiving for nodes a tad behind.

        #Verify we can't add that SignedVerification now.
        #rpc.meros.signedElement(verif)
        #try:
        #  rpc.meros.live.recv()
        #  #Hijacks a random Exception type for our purposes.
        #  raise MessageException("Meros didn't disconnect us after we sent a Verification for a beaten Transaction.")
        #except TestError:
        #  pass
        #except MessageException as e:
        #  raise TestError(e.message)
        #sleep(65)
        #rpc.meros.liveConnect(blockWBeatenVerif.header.last)

        #Verify we can't add a Block containing that Verification.
        rpc.meros.liveBlockHeader(blockWBeatenVerif.header)

        #BlockBody sync request.
        rpc.meros.handleBlockBody(blockWBeatenVerif)

        #Sketch hash sync request.
        hashReqs: bytes = rpc.meros.sync.recv()[37:]
        for h in range(0, len(hashReqs), 8):
            for packet in blockWBeatenVerif.body.packets:
                if int.from_bytes(hashReqs[h:h + 8],
                                  byteorder="little") == Sketch.hash(
                                      blockWBeatenVerif.header.sketchSalt,
                                      packet):
                    rpc.meros.packet(packet)
                    break

        try:
            rpc.meros.live.recv()
            raise MessageException(
                "Meros didn't disconnect us after we sent a Block containing a Verification of a beaten Transaction."
            )
        except TestError:
            pass
        except MessageException as e:
            raise TestError(e.message)

        sleep(65)
        rpc.meros.liveConnect(blockWBeatenVerif.header.last)
        rpc.meros.syncConnect(blockWBeatenVerif.header.last)
Example #2
0
 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)
Example #3
0
    def handleSync() -> None:
        reqHash: bytes = bytes()
        bH: int = 0
        bB: int = 1
        while True:
            if bB == 3:
                break

            msg: bytes = rpc.meros.sync.recv()
            if MessageType(msg[0]) == MessageType.BlockListRequest:
                reqHash = msg[3:35]
                for b in range(len(blockchain.blocks)):
                    if blockchain.blocks[b].header.hash == reqHash:
                        blockList: List[bytes] = []
                        for bl in range(1, msg[2] + 2):
                            if msg[1] == 0:
                                if b - bl < 0:
                                    break
                                blockList.append(
                                    blockchain.blocks[b - bl].header.hash)

                            elif msg[1] == 1:
                                blockList.append(
                                    blockchain.blocks[b + bl].header.hash)

                            else:
                                raise TestError(
                                    "Meros asked for an invalid direction in a BlockListRequest."
                                )

                        if blockList == []:
                            rpc.meros.dataMissing()
                            break

                        rpc.meros.blockList(blockList)
                        break

                    if b == len(blockchain.blocks):
                        rpc.meros.dataMissing()

            elif MessageType(msg[0]) == MessageType.BlockHeaderRequest:
                reqHash = msg[1:33]
                if reqHash != blockchain.blocks[2 - bH].header.hash:
                    raise TestError(
                        "Meros asked for a Block Header that didn't belong to the next Block."
                    )

                #Send the BlockHeader.
                rpc.meros.syncBlockHeader(blockchain.blocks[2 - bH].header)
                bH += 1

            elif MessageType(msg[0]) == MessageType.BlockBodyRequest:
                reqHash = msg[1:33]
                if reqHash != blockchain.blocks[bB].header.hash:
                    raise TestError(
                        "Meros asked for a Block Body that didn't belong to the next Block."
                    )

                rpc.meros.blockBody(blockchain.blocks[bB])
                bB += 1

            else:
                raise TestError("Unexpected message sent: " +
                                msg.hex().upper())

        #Verify the Blockchain.
        verifyBlockchain(rpc, blockchain)
def TwoHundredThirtyTwoTest(
  rpc: RPC
) -> None:
  chains: Dict[str, List[Dict[str, Any]]]
  with open("e2e/Vectors/Merit/Reorganizations/TwoHundredThirtyTwo.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)
    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 MessageType(rpc.meros.sync.recv()[0]) != MessageType.BlockListRequest:
    raise TestError("Meros didn't ask for the Block List of the alternate chain.")
  rpc.meros.blockList([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()
  sleep(35)

  #Reboot the node to reload the database.
  rpc.meros.quit()

  #Reset the RPC's tracking variables.
  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", "--token", "TEST_TOKEN", "--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])

  verifyBlockchain(rpc, main)
 def sendSends() -> None:
   for s in range(len(sends)):
     if rpc.meros.liveTransaction(sends[s]) != rpc.meros.live.recv():
       raise TestError("Meros didn't broadcast a Send.")
Example #6
0
    def sendAndVerify() -> None:
        rpc.meros.liveTransaction(send)
        rpc.meros.liveTransaction(data)
        rpc.meros.live.recv()
        rpc.meros.live.recv()

        #We now have a Mint, a Claim, a Send, a Data, a lion, a witch, and a wardrobe.

        #Check the Mint.
        mint: Mint = Merit.fromJSON(vectors["blockchain"]).mints[0]
        #pylint: disable=invalid-name
        EXPECTED_MINT: Dict[str, Any] = {
            "descendant":
            "Mint",
            "inputs": [],
            "outputs": [{
                "amount": str(txOutput[1]),
                "nick": txOutput[0]
            } for txOutput in mint.outputs],
            "hash":
            mint.hash.hex().upper()
        }
        #Also sanity check against the in-house JSON.
        if mint.toJSON() != EXPECTED_MINT:
            raise TestError("Python's Mint toJSON doesn't match the spec.")
        if rpc.call("transactions", "getTransaction",
                    {"hash": mint.hash.hex()}, False) != EXPECTED_MINT:
            raise TestError("getTransaction didn't report the Mint properly.")

        #Check the Claim.
        #pylint: disable=invalid-name
        EXPECTED_CLAIM: Dict[str, Any] = {
            "descendant":
            "Claim",
            "inputs": [{
                "hash": txInput[0].hex().upper(),
                "nonce": txInput[1]
            } for txInput in claim.inputs],
            "outputs": [{
                "amount": str(claim.amount),
                "key": claim.output.hex().upper()
            }],
            "hash":
            claim.hash.hex().upper(),
            "signature":
            claim.signature.hex().upper()
        }
        if claim.amount == 0:
            raise Exception(
                "Python didn't instantiate the Claim with an amount, leading to invalid testing methodology."
            )
        if claim.toJSON() != EXPECTED_CLAIM:
            raise TestError("Python's Claim toJSON doesn't match the spec.")
        if rpc.call("transactions", "getTransaction",
                    {"hash": claim.hash.hex()}, False) != EXPECTED_CLAIM:
            raise TestError("getTransaction didn't report the Claim properly.")

        #Check the Send.
        #pylint: disable=invalid-name
        EXPECTED_SEND: Dict[str, Any] = {
            "descendant":
            "Send",
            "inputs": [{
                "hash": txInput[0].hex().upper(),
                "nonce": txInput[1]
            } for txInput in send.inputs],
            "outputs": [{
                "amount": str(txOutput[1]),
                "key": txOutput[0].hex().upper()
            } for txOutput in send.outputs],
            "hash":
            send.hash.hex().upper(),
            "signature":
            send.signature.hex().upper(),
            "proof":
            send.proof
        }
        if send.toJSON() != EXPECTED_SEND:
            raise TestError("Python's Send toJSON doesn't match the spec.")
        if rpc.call("transactions", "getTransaction",
                    {"hash": send.hash.hex()}, False) != EXPECTED_SEND:
            raise TestError("getTransaction didn't report the Send properly.")

        #Check the Data.
        #pylint: disable=invalid-name
        EXPECTED_DATA: Dict[str, Any] = {
            "descendant": "Data",
            "inputs": [{
                "hash": data.txInput.hex().upper()
            }],
            "outputs": [],
            "hash": data.hash.hex().upper(),
            "data": data.data.hex().upper(),
            "signature": data.signature.hex().upper(),
            "proof": data.proof
        }
        if data.toJSON() != EXPECTED_DATA:
            raise TestError("Python's Data toJSON doesn't match the spec.")
        if rpc.call("transactions", "getTransaction",
                    {"hash": data.hash.hex()}, False) != EXPECTED_DATA:
            raise TestError("getTransaction didn't report the Data properly.")

        #Non-existent hash; should cause an IndexError
        nonExistentHash: str = data.hash.hex()
        if data.hash[0] == "0":
            nonExistentHash = "1" + nonExistentHash[1:]
        else:
            nonExistentHash = "0" + nonExistentHash[1:]
        try:
            rpc.call("transactions", "getTransaction",
                     {"hash": nonExistentHash}, False)
        except TestError as e:
            if str(e) != "-2 Transaction not found.":
                raise TestError(
                    "getTransaction didn't raise IndexError on a non-existent hash."
                )

        #Invalid argument; should cause a ParamError
        #This is still a hex value
        try:
            rpc.call("transactions", "getTransaction",
                     {"hash": "00" + data.hash.hex()}, False)
            raise TestError(
                "Meros didn't error when we asked for a 33-byte hex value.")
        except TestError as e:
            if str(e) != "-32602 Invalid params.":
                raise TestError(
                    "getTransaction didn't raise on invalid parameters.")
Example #7
0
    def checkFail() -> None:
        #This Block should cause the node to disconnect us AFTER it syncs our Transaction.
        syncedTX: bool = False

        block: Block = merit.blockchain.blocks[2]
        rpc.meros.liveBlockHeader(block.header)

        #Handle sync requests.
        reqHash: bytes = bytes()
        while True:
            if syncedTX:
                #Try receiving from the Live socket, where Meros sends keep-alives.
                try:
                    if len(rpc.meros.live.recv()) != 0:
                        raise Exception()
                except TestError:
                    raise SuccessError(
                        "Node disconnected us after we sent a parsable, yet invalid, Transaction."
                    )
                except Exception:
                    raise TestError("Meros sent a keep-alive.")

            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)

            elif MessageType(msg[0]) == MessageType.SketchHashesRequest:
                if not block.body.packets:
                    raise TestError(
                        "Meros asked for Sketch Hashes from a Block without any."
                    )

                reqHash = msg[1:33]
                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))

                #Send the Sketch Hashes.
                rpc.meros.sketchHashes(hashes)

            elif MessageType(msg[0]) == MessageType.SketchHashRequests:
                if not block.body.packets:
                    raise TestError(
                        "Meros asked for Verification Packets from a Block without any."
                    )

                reqHash = msg[1:33]
                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.")
                    rpc.meros.packet(packets[sketchHash])

            elif MessageType(msg[0]) == MessageType.TransactionRequest:
                reqHash = msg[1:33]

                if reqHash not in transactions.txs:
                    raise TestError(
                        "Meros asked for a non-existent Transaction.")

                rpc.meros.syncTransaction(transactions.txs[reqHash])
                syncedTX = True

            else:
                raise TestError("Unexpected message sent: " +
                                msg.hex().upper())
    def publishAndVerify() -> None:
        if not rpc.call("transactions", "publishTransaction", {
                "type": "Claim",
                "transaction": claim.serialize().hex()
        }, False):
            raise TestError(
                "Publishing a valid Transaction didn't return true.")
        if rpc.meros.live.recv()[1:] != claim.serialize():
            raise TestError(
                "publishTransaction didn't broadcast the Transaction.")

        if not rpc.call("transactions", "publishTransaction", {
                "type": "Send",
                "transaction": send.serialize().hex()
        }, False):
            raise TestError(
                "Publishing a valid Transaction didn't return true.")
        if rpc.meros.live.recv()[1:] != send.serialize():
            raise TestError(
                "publishTransaction didn't broadcast the Transaction.")

        if not rpc.call("transactions", "publishTransaction", {
                "type": "Data",
                "transaction": data.serialize().hex()
        }, False):
            raise TestError(
                "Publishing a valid Transaction didn't return true.")
        if rpc.meros.live.recv()[1:] != data.serialize():
            raise TestError(
                "publishTransaction didn't broadcast the Transaction.")

        #Verify all three were entered properly.
        verifyTransaction(rpc, claim)
        verifyTransaction(rpc, send)
        verifyTransaction(rpc, data)

        #Create a new Send/Data and publish them without work.
        sendSentWithoutWork: Send = Send([(send.hash, 0)],
                                         [(pubKey.to_bytes(), claim.amount)])
        sendSentWithoutWork.sign(sentToKey)
        sendSentWithoutWork.beat(sendFilter)

        dataSentWithoutWork: Data = Data(
            bytes(32),
            sentToKey.get_verifying_key().to_bytes())
        dataSentWithoutWork.sign(sentToKey)
        dataSentWithoutWork.beat(dataFilter)

        if not rpc.call(
                "transactions", "publishTransactionWithoutWork", {
                    "type": "Send",
                    "transaction": sendSentWithoutWork.serialize()[:-4].hex()
                }, True):
            raise TestError(
                "Publishing a valid Transaction without work didn't return true."
            )
        if rpc.meros.live.recv()[1:] != sendSentWithoutWork.serialize():
            raise TestError(
                "publishTransaction didn't broadcast the Transaction.")

        if not rpc.call(
                "transactions", "publishTransactionWithoutWork", {
                    "type": "Data",
                    "transaction": dataSentWithoutWork.serialize()[:-4].hex()
                }, True):
            raise TestError(
                "Publishing a valid Transaction without work didn't return true."
            )
        if rpc.meros.live.recv()[1:] != dataSentWithoutWork.serialize():
            raise TestError(
                "publishTransaction didn't broadcast the Transaction.")

        #Call verify now, which will test ours with work against Meros's with generated work.
        #Both should terminate on the earliest valid proof, making them identical.
        verifyTransaction(rpc, sendSentWithoutWork)
        verifyTransaction(rpc, dataSentWithoutWork)

        #Re-publishing a Transaction should still return true.
        if not rpc.call("transactions", "publishTransaction", {
                "type": "Data",
                "transaction": data.serialize().hex()
        }, False):
            raise TestError(
                "Publishing an existing Transaction didn't return true.")
        if MessageType(rpc.meros.live.recv()[0]) == MessageType.Data:
            raise TestError(
                "publishTransaction broadcasted an existing Transaction.")

        #No arguments.
        try:
            rpc.call("transactions", "publishTransaction")
        except TestError as e:
            if str(e) != "-32602 Invalid params.":
                raise TestError(
                    "publishTransaction didn't error when passed no arguments."
                )

        #Invalid type.
        try:
            rpc.call("transactions", "publishTransaction", {
                "type": "",
                "transaction": data.serialize().hex()
            }, False)
            raise TestError("")
        except TestError as e:
            if str(e) != "-3 Invalid Transaction type specified.":
                raise TestError(
                    "publishTransaction didn't error when passed an invalid type."
                )

        #Data sent with Send as a type.
        try:
            rpc.call("transactions", "publishTransaction", {
                "type": "Send",
                "transaction": data.serialize().hex()
            }, False)
            raise TestError("")
        except TestError as e:
            if str(
                    e
            ) != "-3 Transaction is invalid: parseSend handed the wrong amount of data.":
                raise TestError(
                    "publishTransaction didn't error when passed a non-parsable Send (a Data)."
                )

        #Invalid Data (signature).
        invalidData: Data = Data(bytes(32),
                                 sentToKey.get_verifying_key().to_bytes())
        newData: bytearray = bytearray(invalidData.data)
        newData[-1] = newData[-1] ^ 1
        invalidData.data = bytes(newData)
        invalidData.sign(sentToKey)
        invalidData.beat(dataFilter)
        try:
            rpc.call("transactions", "publishTransaction", {
                "type": "Data",
                "transaction": invalidData.serialize().hex()
            }, False)
            raise TestError("")
        except TestError as e:
            if str(
                    e
            ) != "-3 Transaction is invalid: Data has an invalid Signature.":
                raise TestError(
                    "publishTransaction didn't error when passed an invalid Transaction."
                )

        #Spam.
        spamData: Data = data
        if spamData.proof == 0:
            spamData = dataSentWithoutWork
        if spamData.proof == 0:
            raise Exception("Neither Data is considered as Spam.")
        spamData.proof = 0

        try:
            rpc.call("transactions", "publishTransaction", {
                "type": "Data",
                "transaction": spamData.serialize().hex()
            }, False)
            raise TestError("")
        except TestError as e:
            if str(e) != "2 Transaction didn't beat the spam filter.":
                raise TestError(
                    "publishTransaction didn't error when passed a Transaction which didn't beat its difficulty."
                )

        #Test publishTransactionWithoutWork requires authorization.
        try:
            rpc.call(
                "transactions", "publishTransactionWithoutWork", {
                    "type": "Data",
                    "transaction": dataSentWithoutWork.serialize()[:-4].hex()
                }, False)
            raise TestError("")
        except Exception as e:
            if str(e) != "HTTP status isn't 200: 401":
                raise TestError(
                    "Called publishTransactionWithoutWork despite not being authed."
                )
Example #9
0
 def sendDatas() -> None:
     for d in range(len(datas)):
         if rpc.meros.liveTransaction(datas[d]) != rpc.meros.live.recv():
             raise TestError("Meros didn't broadcast a Data.")
Example #10
0
def TwoHundredFourtyTest(rpc: RPC) -> None:
    #Grab the keys.
    blsPrivKey: PrivateKey = PrivateKey(
        bytes.fromhex(rpc.call("personal", "getMeritHolderKey")))
    blsPubKey: PublicKey = blsPrivKey.toPublicKey()

    #Blockchain used to calculate the difficulty.
    blockchain: Blockchain = Blockchain()

    #Mine enough blocks to lose Merit.
    for b in range(9):
        template: Dict[str,
                       Any] = rpc.call("merit", "getBlockTemplate",
                                       {"miner": blsPubKey.serialize().hex()})
        template["header"] = bytes.fromhex(template["header"])

        header: BlockHeader = BlockHeader(
            0, blockchain.last(), bytes(32), 0, bytes(4), bytes(32), 0,
            blockchain.blocks[-1].header.time + 1200)
        if b == 0:
            header.newMiner = True
            header.minerKey = blsPubKey.serialize()
        header.mine(blsPrivKey, blockchain.difficulty())
        blockchain.add(Block(header, BlockBody()))

    rpc.meros.liveConnect(blockchain.blocks[0].header.hash)
    rpc.meros.syncConnect(blockchain.blocks[0].header.hash)
    for b in range(1, 10):
        headerMsg: bytes = rpc.meros.liveBlockHeader(
            blockchain.blocks[b].header)
        rpc.meros.handleBlockBody(blockchain.blocks[b])
        if rpc.meros.live.recv() != headerMsg:
            raise TestError("Meros didn't broadcast back the Block Header.")
        if MessageType(
                rpc.meros.live.recv()[0]) != MessageType.SignedVerification:
            raise TestError(
                "Meros didn't sign a verification of the Block's data.")

    try:
        rpc.meros.live.connection.shutdown(socket.SHUT_RDWR)
        rpc.meros.live.connection.close()
        rpc.meros.sync.connection.shutdown(socket.SHUT_RDWR)
        rpc.meros.sync.connection.close()
    except OSError:
        pass

    #Verify our Merit is locked.
    if rpc.call("merit", "getMerit", {"nick": 0})["status"] != "Locked":
        raise Exception("Our Merit isn't locked so this test is invalid.")

    template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate",
                                        {"miner": 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:72], byteorder="little"),
        template["header"][72:76], template["header"][76:108], 0,
        int.from_bytes(template["header"][-4:], byteorder="little"))
    if not any(header.contents):
        raise TestError("Meros didn't try to unlock its Merit.")
    header.mine(blsPrivKey, blockchain.difficulty())
    #Don't add the last block because we never provided it with a proper body.

    rpc.call(
        "merit", "publishBlock", {
            "id":
            template["id"],
            "header": (template["header"] +
                       header.proof.to_bytes(4, byteorder="little") +
                       header.signature).hex()
        })

    #To verify the entire chain, we just need to verify this last header.
    #This is essential as our chain isn't equivalent.
    ourHeader: Dict[str, Any] = header.toJSON()
    if rpc.call("merit", "getBlock",
                {"block": header.hash.hex()})["header"] != ourHeader:
        raise TestError("Header wasn't added to the blockchain.")
Example #11
0
 def sendMeritRemoval() -> None:
     #Send and verify the MeritRemoval.
     if rpc.meros.signedElement(removal) != rpc.meros.live.recv():
         raise TestError("Meros didn't send us the Merit Removal.")
     verifyMeritRemoval(rpc, 2, 2, removal.holder, True)
Example #12
0
 def verifyUnlocked(_: int) -> None:
     if rpc.call("merit", "getMerit", [0])["status"] != "Unlocked":
         raise TestError("Meros didn't keep Merit unlocked.")
Example #13
0
def getBlockTemplateTest(rpc: RPC) -> None:
    edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32)
    edPubKey: ed25519.VerifyingKey = 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.to_bytes())
    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."
        )
Example #14
0
    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 != (len(alt.blocks) - 35):
            blockList.append(alt.blocks[b].header.hash)
            b -= 1
        rpc.meros.blockList(blockList)

        diff = -30
        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

        #Advance the chain far enough to switch to the new key.
        diff = -30
        while diff != -11:
            rpc.meros.handleBlockBody(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 != 370:
            blockList.append(main.blocks[b].header.hash)
            b -= 1
        rpc.meros.blockList(blockList)

        for b in range(381, 401):
            if b != 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)

            rpc.meros.handleBlockBody(main.blocks[b])
Example #15
0
 def finalizedSpendingSend() -> None:
     #Verify the state is unchanged.
     if rpc.call("transactions", "getUTXOs", {"address": address}) != []:
         raise TestError(
             "Meros didn't consider a finalized Transaction's inputs as spent."
         )
Example #16
0
    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()
Example #17
0
  def sync(
    self
  ) -> None:
    #Handshake with the node.
    self.rpc.meros.syncConnect(self.merit.blockchain.blocks[self.settings["height"]].header.hash)

    #Handle sync requests.
    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[3 : 35]
        for b in range(len(self.merit.blockchain.blocks)):
          if self.merit.blockchain.blocks[b].header.hash == reqHash:
            blockList: List[bytes] = []
            for bl in range(1, msg[2] + 2):
              if msg[1] == 0:
                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])

              elif msg[1] == 1:
                if b + bl > self.settings["height"]:
                  break

                blockList.append(self.merit.blockchain.blocks[b + bl].header.hash)
                self.blocks.append(self.merit.blockchain.blocks[b + bl])

              else:
                raise TestError("Meros asked for an invalid direction in a BlockListRequest.")

            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:
        if (self.txs != set()) or (self.packets != {}):
          raise TestError("Meros asked for a new Block before syncing the last Block's Transactions and Packets.")

        reqHash = msg[1 : 33]
        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.")

        self.rpc.meros.blockBody(self.blocks[-1])
        self.blockHashes.remove(self.blocks[-1].header.hash)

        #Set packets/transactions.
        self.packets = {}
        for packet in self.blocks[-1].body.packets:
          if packet.hash not in self.synced:
            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
        for elem in self.blocks[-1].body.elements:
          if isinstance(elem, MeritRemoval):
            if isinstance(elem.e1, (Verification, VerificationPacket)):
              self.txs.add(elem.e1.hash)
              noVCMRs = False
            if isinstance(elem.e2, (Verification, VerificationPacket)):
              self.txs.add(elem.e2.hash)
              noVCMRs = False

        if (self.packets == {}) and noVCMRs:
          del self.blocks[-1]

      elif MessageType(msg[0]) == MessageType.SketchHashesRequest:
        reqHash = msg[1 : 33]
        if reqHash != self.blocks[-1].header.hash:
          raise TestError("Meros asked for Sketch Hashes that didn't belong to the header we just sent it.")

        #Get the haashes.
        hashes: List[int] = list(self.packets)

        #Send the Sketch Hashes.
        self.rpc.meros.sketchHashes(hashes)

      elif MessageType(msg[0]) == MessageType.SketchHashRequests:
        if not self.packets:
          raise TestError("Meros asked for Verification Packets from a Block without any.")

        reqHash = msg[1 : 33]
        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="big")):
          sketchHash: int = int.from_bytes(msg[37 + (h * 8) : 45 + (h * 8)], byteorder="big")
          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]

      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.synced.add(reqHash)
        self.txs.remove(reqHash)

        if 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)

    if self.settings["playback"]:
      #Playback their messages.
      self.rpc.meros.sync.playback()
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:72], byteorder="little"),
            template["header"][72:76], template["header"][76:108], 0,
            int.from_bytes(template["header"][-4:], byteorder="little"))
        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="little") +
             header.signature).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, 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)
Example #19
0
    def test() -> None:
        recipient: ed25519.SigningKey = ed25519.SigningKey(b'\1' * 32)
        recipientPub: bytes = recipient.get_verifying_key().to_bytes()
        address: str = bech32_encode(
            "mr", convertbits(bytes([0]) + recipientPub, 8, 5))

        otherRecipient: bytes = ed25519.SigningKey(
            b'\2' * 32).get_verifying_key().to_bytes()
        otherAddress: str = bech32_encode(
            "mr", convertbits(bytes([0]) + otherRecipient, 8, 5))

        #Create a Send.
        send: Send = Send.fromJSON(vectors["send"])
        if rpc.meros.liveTransaction(send) != rpc.meros.live.recv():
            raise TestError("Meros didn't broadcast back a Send.")
        if rpc.call("transactions", "getUTXOs", {"address": address}) != []:
            raise TestError(
                "Meros considered an unconfirmed Transaction's outputs as UTXOs."
            )
        verify(rpc, send.hash)

        #Finalize the parent.
        for _ in range(6):
            mineBlock(rpc)

        #Spend it.
        spendingSend: Send = Send.fromJSON(vectors["spendingSend"])
        if rpc.meros.liveTransaction(spendingSend) != rpc.meros.live.recv():
            raise TestError("Meros didn't broadcast back a Send.")
        verify(rpc, spendingSend.hash)
        if rpc.call("transactions", "getUTXOs", {"address": address}) != []:
            raise TestError(
                "Meros didn't consider a verified Transaction's inputs as spent."
            )
        if rpc.call("transactions", "getUTXOs", {"address": otherAddress}) != [
            {
                "hash": spendingSend.hash.hex().upper(),
                "nonce": 0
            }
        ]:
            raise TestError(
                "Meros didn't consider a verified Transaction's outputs as UTXOs."
            )

        #Unverify the spending Send. This would also unverify the parent if it wasn't finalized.
        #This is done via causing a Merit Removal.
        #Uses two competing Datas to not change the Send's status to competing.
        datas: List[Data] = [Data(bytes(32), recipientPub)]
        for _ in range(2):
            datas.append(Data(datas[0].hash, datas[-1].hash))
        for data in datas:
            data.sign(recipient)
            data.beat(SpamFilter(5))
            if rpc.meros.liveTransaction(data) != rpc.meros.live.recv():
                raise TestError("Meros didn't broadcast back a Data.")
            verify(rpc, data.hash, mr=(datas[-1].hash == data.hash))
        #Verify the MeritRemoval happened and the spending Send is no longer verified.
        #These first two checks are more likely to symbolize a failure in testing methodology than Meros.
        if not rpc.call("merit", "getMerit", {"nick": 0})["malicious"]:
            raise TestError("Meros didn't create a Merit Removal.")
        if not rpc.call("consensus", "getStatus",
                        {"hash": send.hash.hex()})["verified"]:
            raise TestError("Finalized Transaction became unverified.")
        if rpc.call("consensus", "getStatus",
                    {"hash": spendingSend.hash.hex()})["verified"]:
            raise TestError(
                "Meros didn't unverify a Transaction which is currently below the required threshold."
            )
        #Even after unverification, since the Transaction still exists, the input shouldn't be considered a UTXO.
        if rpc.call("transactions", "getUTXOs", {"address": address}) != []:
            raise TestError(
                "Meros didn't consider a unverified yet existing Transaction's inputs as spent."
            )
        #That said, its outputs should no longer be considered a UTXO.
        if rpc.call("transactions", "getUTXOs",
                    {"address": otherAddress}) != []:
            raise TestError(
                "Meros considered a unverified Transaction's outputs as UTXOs."
            )

        raise SuccessError()
Example #20
0
def verify(rpc: RPC, tx: bytes) -> None:
    sv: SignedVerification = SignedVerification(tx)
    sv.sign(0, PrivateKey(0))
    if rpc.meros.signedElement(sv) != rpc.meros.live.recv():
        raise TestError("Meros didn't send back a Verification.")
Example #21
0
def HundredSixBlockElementsTest(
  rpc: RPC
) -> None:
  #Load the vectors.
  file: IO[Any] = open("e2e/Vectors/Consensus/HundredSix/BlockElements.json", "r")
  vectors: Dict[str, Any] = json.loads(file.read())
  file.close()

  #Blockchain. Solely used to get the genesis Block hash.
  blockchain: Blockchain = Blockchain()

  #Transactions.
  transactions: Transactions = Transactions.fromJSON(vectors["transactions"])

  #Parse the Blocks from the vectors.
  blocks: List[Block] = []
  for block in vectors["blocks"]:
    blocks.append(Block.fromJSON(block))

  for block in blocks:
    #Handshake with the node.
    rpc.meros.liveConnect(blockchain.blocks[0].header.hash)
    rpc.meros.syncConnect(blockchain.blocks[0].header.hash)

    #Send the Block.
    rpc.meros.liveBlockHeader(block.header)

    #Flag of if the Block's Body synced.
    doneSyncing: bool = False

    #Handle sync requests.
    reqHash: bytes = bytes()
    while True:
      if doneSyncing:
        #Sleep for a second so Meros handles the Block.
        sleep(1)

        #Try receiving from the Live socket, where Meros sends keep-alives.
        try:
          if len(rpc.meros.live.recv()) != 0:
            raise Exception()
        except TestError:
          #Verify the node didn't crash.
          try:
            if rpc.call("merit", "getHeight") != 1:
              raise Exception()
          except Exception:
            raise TestError("Node crashed after being sent a malformed Element.")

          #Since the node didn't crash, break out of this loop to trigger the next test case.
          break
        except Exception:
          raise TestError("Meros sent a keep-alive.")

      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)

        if len(block.body.packets) == 0:
          doneSyncing = True

      elif MessageType(msg[0]) == MessageType.SketchHashRequests:
        if not block.body.packets:
          raise TestError("Meros asked for Verification Packets from a Block without any.")

        reqHash = msg[1 : 33]
        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="big")):
          sketchHash: int = int.from_bytes(msg[37 + (h * 8) : 45 + (h * 8)], byteorder="big")
          if sketchHash not in packets:
            raise TestError("Meros asked for a non-existent Sketch Hash.")
          rpc.meros.packet(packets[sketchHash])

        doneSyncing = True

      elif MessageType(msg[0]) == MessageType.TransactionRequest:
        reqHash = msg[1 : 33]

        if reqHash not in transactions.txs:
          raise TestError("Meros asked for a non-existent Transaction.")

        rpc.meros.syncTransaction(transactions.txs[reqHash])

      else:
        raise TestError("Unexpected message sent: " + msg.hex().upper())

    #Reset the node.
    rpc.reset()
Example #22
0
def WatchWalletTest(rpc: RPC) -> None:
    #Keys to send funds to later.
    keys: List[bytes] = [
        ed25519.SigningKey(i.to_bytes(1, "little") *
                           32).get_verifying_key().to_bytes() for i in range(5)
    ]

    #Backup the Mnemonic so we can independently derive this data and verify it.
    mnemonic: str = rpc.call("personal", "getMnemonic")

    #Node's Wallet's keys.
    nodeKeys: List[bytes] = [getPublicKey(mnemonic, "", i) for i in range(4)]
    nodeAddresses: List[str] = [getAddress(mnemonic, "", i) for i in range(4)]

    #Convert this to a WatchWallet node.
    account: Dict[str, Any] = rpc.call("personal", "getAccount")
    rpc.call("personal", "setAccount", account)
    if rpc.call("personal", "getAccount") != account:
        raise TestError("Meros set a different account.")

    #Verify it has the correct initial address.
    if rpc.call("personal", "getAddress") != nodeAddresses[0]:
        raise TestError("WatchWallet has an incorrect initial address.")

    #Load the vectors.
    #This test requires 3 Claims be available.
    vectors: Dict[str, Any]
    with open("e2e/Vectors/RPC/Personal/WatchWallet.json", "r") as file:
        vectors = json.loads(file.read())
    transactions: Transactions = Transactions.fromJSON(vectors["transactions"])

    #The order of the Claims isn't relevant to this test.
    claims: List[Claim] = []
    for tx in transactions.txs.values():
        claims.append(Claim.fromTransaction(tx))

    def test() -> None:
        #Send to it.
        sends: List[bytes] = [createSend(rpc, claims[0], nodeKeys[0])]
        verify(rpc, sends[-1])

        #Test the most basic template possible.
        checkTemplate(
            rpc, mnemonic, {
                "outputs": [{
                    "address":
                    bech32_encode("mr", convertbits(
                        bytes([0]) + keys[0], 8, 5)),
                    "amount":
                    "1"
                }]
            }, [{
                "hash": sends[-1].hex().upper(),
                "nonce": 0,
                "change": False,
                "index": 0
            }], [{
                "key": keys[0].hex().upper(),
                "amount": "1"
            }, {
                "key": getChangePublicKey(mnemonic, "", 0).hex().upper(),
                "amount": str(claims[0].amount - 1)
            }])

        #Verify it has the correct next address.
        if rpc.call("personal", "getAddress") != nodeAddresses[1]:
            raise TestError("WatchWallet has an incorrect next address.")

        #Send to it.
        sends.append(createSend(rpc, claims[1], nodeKeys[1]))
        verify(rpc, sends[-1])

        #Get and send to one more, yet don't verify it yet.
        if rpc.call("personal", "getAddress") != nodeAddresses[2]:
            raise TestError("WatchWallet has an incorrect next next address.")
        sends.append(createSend(rpc, claims[2], nodeKeys[2]))

        #Verify it can get UTXOs properly.
        if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(
            [{
                "address": getAddress(mnemonic, "", i),
                "hash": sends[i].hex().upper(),
                "nonce": 0
            } for i in range(2)]):
            raise TestError("WatchWallet Meros couldn't get its UTXOs.")

        #Verify the third Send.
        verify(rpc, sends[-1])

        #Close the sockets for now.
        rpc.meros.live.connection.close()
        rpc.meros.sync.connection.close()

        #Verify getUTXOs again. Redundant thanks to the extensive getUTXO testing elsewhere, yet valuable.
        if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(
            [{
                "address": getAddress(mnemonic, "", i),
                "hash": sends[i].hex().upper(),
                "nonce": 0
            } for i in range(3)]):
            raise TestError("WatchWallet Meros couldn't get its UTXOs.")

        #Verify it can craft a Transaction Template properly.
        claimsAmount: int = sum(claim.amount for claim in claims)
        amounts: List[int] = [
            claimsAmount // 4, claimsAmount // 4, claimsAmount // 5
        ]
        amounts.append(claimsAmount - sum(amounts))
        req: Dict[str, Any] = {
            "outputs": [{
                "address":
                bech32_encode("mr", convertbits(bytes([0]) + keys[0], 8, 5)),
                "amount":
                str(amounts[0])
            }, {
                "address":
                bech32_encode("mr", convertbits(bytes([0]) + keys[1], 8, 5)),
                "amount":
                str(amounts[1])
            }, {
                "address":
                bech32_encode("mr", convertbits(bytes([0]) + keys[2], 8, 5)),
                "amount":
                str(amounts[2])
            }]
        }
        inputs: List[Dict[str, Any]] = [{
            "hash": sends[i].hex().upper(),
            "nonce": 0,
            "change": False,
            "index": i
        } for i in range(3)]
        outputs: List[Dict[str, Any]] = [{
            "key": keys[i].hex().upper(),
            "amount": str(amounts[i])
        } for i in range(3)] + [{
            "key":
            getChangePublicKey(mnemonic, "", 0).hex().upper(),
            "amount":
            str(amounts[-1])
        }]
        checkTemplate(rpc, mnemonic, req, inputs, outputs)

        #Specify only to use specific addresses and verify Meros does so.
        req["from"] = [nodeAddresses[1], nodeAddresses[2]]

        #Correct the amounts so this is feasible.
        del req["outputs"][-1]
        del outputs[-2]
        #Remove the change output amount and actual output amount.
        for _ in range(2):
            del amounts[-1]
        claimsAmount -= claims[-1].amount
        #Correct the change output.
        outputs[-1]["amount"] = str(claimsAmount - sum(amounts))

        del inputs[0]
        checkTemplate(rpc, mnemonic, req, inputs, outputs)
        del req["from"]

        #Use the change address in question and verify the next template uses the next change address.
        #This is done via creating a Send which doesn't spend all of inputs value.
        #Also tests Meros handles Sends, and therefore templates, which don't use all funds.
        change: bytes = getChangePublicKey(mnemonic, "", 0)

        #Convert to a Wallet in order to do so.
        rpc.call("personal", "setWallet", {"mnemonic": mnemonic})
        send: Dict[str, Any] = rpc.call(
            "transactions", "getTransaction", {
                "hash":
                rpc.call(
                    "personal", "send", {
                        "outputs": [{
                            "address":
                            bech32_encode(
                                "mr", convertbits(bytes([0]) + change, 8, 5)),
                            "amount":
                            "1"
                        }]
                    })
            })
        #Convert back.
        rpc.call("personal", "setAccount", account)

        #Reconnect.
        sleep(65)
        rpc.meros.liveConnect(Blockchain().blocks[0].header.hash)
        rpc.meros.syncConnect(Blockchain().blocks[0].header.hash)

        #Verify the Send so the Wallet doesn't lose Meros from consideration.
        verify(rpc, bytes.fromhex(send["hash"]))

        #Verify the Send's accuracy.
        if len(send["inputs"]) != 1:
            raise TestError("Meros used more inputs than neccessary.")
        if send["outputs"] != [
            {
                "key": change.hex().upper(),
                "amount": "1"
            },
                #Uses the existing, unused, change address as change.
                #While this Transaction will make it used, that isn't detected.
                #This isn't worth programming around due to the lack of implications except potentially minor metadata.
            {
                "key": change.hex().upper(),
                "amount": str(claims[0].amount - 1)
            }
        ]:
            raise TestError("Send outputs weren't as expected.")

        if rpc.call("personal", "getTransactionTemplate",
                    req)["outputs"][-1]["key"] != getChangePublicKey(
                        mnemonic, "", 1).hex().upper():
            raise TestError("Meros didn't move to the next change address.")

        #Specify an explicit change address.
        req["change"] = nodeAddresses[3]
        if rpc.call("personal", "getTransactionTemplate",
                    req)["outputs"][-1]["key"] != nodeKeys[3].hex().upper():
            raise TestError(
                "Meros didn't handle an explicitly set change address.")

        #Verify RPC methods which require the private key error properly.
        #Tests via getMnemonic and data.
        try:
            rpc.call("personal", "getMnemonic")
            raise TestError()
        except Exception as e:
            if str(e) != "-3 This is a WatchWallet node; no Mnemonic is set.":
                raise TestError(
                    "getMnemonic didn't error as expected when Meros didn't have a Wallet."
                )

        try:
            rpc.call("personal", "data", {"data": "abc"})
            raise TestError()
        except Exception as e:
            if str(e) != "-3 This is a WatchWallet node; no Mnemonic is set.":
                raise TestError(
                    "data didn't error as expected when Meros didn't have a Wallet."
                )

        #Also test getMeritHolderKey, as no Merit Holder key should exist.
        try:
            rpc.call("personal", "getMeritHolderKey")
            raise TestError()
        except Exception as e:
            if str(
                    e
            ) != "-3 Node is running as a WatchWallet and has no Merit Holder.":
                raise TestError(
                    "data didn't error as expected when Meros didn't have a Wallet."
                )

        #Try calling getTransactionTemplate spending way too much Meros.
        try:
            rpc.call(
                "personal", "getTransactionTemplate", {
                    "outputs": [{
                        "address":
                        bech32_encode("mr",
                                      convertbits(bytes([0]) + keys[0], 8, 5)),
                        "amount":
                        str(claimsAmount * 100)
                    }]
                })
            raise TestError()
        except Exception as e:
            if str(e) != "1 Wallet doesn't have enough Meros.":
                raise TestError(
                    "Meros didn't error as expected when told to spend more Meros than it has."
                )

        #Try calling getTransactionTemplate with no outputs.
        try:
            rpc.call("personal", "getTransactionTemplate", {"outputs": []})
            raise TestError()
        except Exception as e:
            if str(e) != "-3 No outputs were provided.":
                raise TestError(
                    "Meros didn't error as expected when told to create a template with no outputs."
                )

        #Try calling getTransactionTemplate with a 0 value output.
        try:
            rpc.call(
                "personal", "getTransactionTemplate", {
                    "outputs": [{
                        "address":
                        bech32_encode("mr",
                                      convertbits(bytes([0]) + keys[0], 8, 5)),
                        "amount":
                        "0"
                    }]
                })
            raise TestError()
        except Exception as e:
            if str(e) != "-3 0 value output was provided.":
                raise TestError(
                    "Meros didn't error as expected when told to create a template with a 0 value output."
                )

    #Use a late enough block we can instantly verify transactions.
    Liver(rpc, vectors["blockchain"], transactions, {50: test}).live()
Example #23
0
    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()
Example #24
0
def TElementTest(rpc: RPC) -> None:
    #BLS key.
    blsPrivKey: PrivateKey = PrivateKey(
        blake2b(b'\0', digest_size=32).digest())
    blsPubKey: str = blsPrivKey.toPublicKey().serialize().hex()

    #Blocks.
    file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r")
    blocks: List[Dict[str, Any]] = json.loads(file.read())
    file.close()

    #Merit.
    merit: Merit = Merit()

    #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 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="big"),
        ), 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="big") +
         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="big")),
        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)

    #Publish it.
    rpc.call("merit", "publishBlock", [
        template["id"],
        (template["header"] + block.header.proof.to_bytes(4, byteorder="big") +
         block.header.signature +
         block.body.serialize(block.header.sketchSalt)).hex()
    ])

    #Verify the Blockchain.
    verifyBlockchain(rpc, merit.blockchain)
Example #25
0
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", "getMeritHolderKey")))
    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",
                                        {"miner": blsPubKey.hex()})

    #Mine a Block.
    block = Block(
        BlockHeader(0, blockchain.blocks[0].header.hash, bytes(32), 0,
                    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", {
        "id": template["id"],
        "header": block.header.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.")
 def verifyMentionedWon() -> None:
   if not rpc.call("consensus", "getStatus", {"hash": datas[2].hash.hex()})["verified"]:
     raise TestError("Meros didn't verify the only Transaction on chain which has finalized.")
Example #27
0
def HundredTwentyFourTest(rpc: RPC) -> None:
    file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r")
    vectors: List[Dict[str, Any]] = json.loads(file.read())
    file.close()

    blockchain: Blockchain = Blockchain()
    for i in range(2):
        blockchain.add(Block.fromJSON(vectors[i]))

    rpc.meros.liveConnect(blockchain.blocks[0].header.hash)
    rpc.meros.syncConnect(blockchain.blocks[0].header.hash)

    def handleSync() -> None:
        reqHash: bytes = bytes()
        bH: int = 0
        bB: int = 1
        while True:
            if bB == 3:
                break

            msg: bytes = rpc.meros.sync.recv()
            if MessageType(msg[0]) == MessageType.BlockListRequest:
                reqHash = msg[3:35]
                for b in range(len(blockchain.blocks)):
                    if blockchain.blocks[b].header.hash == reqHash:
                        blockList: List[bytes] = []
                        for bl in range(1, msg[2] + 2):
                            if msg[1] == 0:
                                if b - bl < 0:
                                    break
                                blockList.append(
                                    blockchain.blocks[b - bl].header.hash)

                            elif msg[1] == 1:
                                blockList.append(
                                    blockchain.blocks[b + bl].header.hash)

                            else:
                                raise TestError(
                                    "Meros asked for an invalid direction in a BlockListRequest."
                                )

                        if blockList == []:
                            rpc.meros.dataMissing()
                            break

                        rpc.meros.blockList(blockList)
                        break

                    if b == len(blockchain.blocks):
                        rpc.meros.dataMissing()

            elif MessageType(msg[0]) == MessageType.BlockHeaderRequest:
                reqHash = msg[1:33]
                if reqHash != blockchain.blocks[2 - bH].header.hash:
                    raise TestError(
                        "Meros asked for a Block Header that didn't belong to the next Block."
                    )

                #Send the BlockHeader.
                rpc.meros.syncBlockHeader(blockchain.blocks[2 - bH].header)
                bH += 1

            elif MessageType(msg[0]) == MessageType.BlockBodyRequest:
                reqHash = msg[1:33]
                if reqHash != blockchain.blocks[bB].header.hash:
                    raise TestError(
                        "Meros asked for a Block Body that didn't belong to the next Block."
                    )

                rpc.meros.blockBody(blockchain.blocks[bB])
                bB += 1

            else:
                raise TestError("Unexpected message sent: " +
                                msg.hex().upper())

        #Verify the Blockchain.
        verifyBlockchain(rpc, blockchain)

    #Send another Handshake with the latest block as the tip.
    rpc.meros.live.send(
        MessageType.Handshake.toByte() + (254).to_bytes(1, "little") +
        (254).to_bytes(1, "little") + b'\0\0\0' + blockchain.last(), False)

    #Verify Meros responds with their tail (the genesis).
    if rpc.meros.live.recv() != MessageType.BlockchainTail.toByte(
    ) + blockchain.blocks[0].header.hash:
        raise TestError("Meros didn't respond with the genesis.")

    #Handle the sync.
    handleSync()

    #Reset Meros and do the same with Syncing.
    rpc.reset()

    rpc.meros.syncConnect(blockchain.blocks[0].header.hash)
    rpc.meros.sync.send(
        MessageType.Syncing.toByte() + (254).to_bytes(1, "little") +
        (254).to_bytes(1, "little") + b'\0\0\0' + blockchain.last(), False)
    if rpc.meros.sync.recv() != MessageType.BlockchainTail.toByte(
    ) + blockchain.blocks[0].header.hash:
        raise TestError("Meros didn't respond with the genesis.")
    handleSync()
Example #28
0
def BusyTest(meros: Meros) -> None:
    #Solely used to get the genesis Block hash.
    blockchain: Blockchain = Blockchain()

    #Handshake with the node.
    meros.syncConnect(blockchain.blocks[0].header.hash)

    #Create two new server sockets.
    def createServerSocket() -> socket.socket:
        result: socket.socket = socket.socket(socket.AF_INET,
                                              socket.SOCK_STREAM)
        result.bind(("127.0.0.1", 0))
        result.listen(2)
        return result

    busyServer: socket.socket = createServerSocket()
    server: socket.socket = createServerSocket()

    #Receive Syncing until Meros asks for peers.
    while True:
        res = meros.sync.recv(True)
        if MessageType(res[0]) == MessageType.Syncing:
            meros.sync.send(MessageType.BlockchainTail.toByte() +
                            blockchain.blocks[0].header.hash)
        elif MessageType(res[0]) == MessageType.PeersRequest:
            break

    #Craft a Peers message of our own server.
    meros.sync.send(MessageType.Peers.toByte() + bytes.fromhex("017F000001") +
                    busyServer.getsockname()[1].to_bytes(2, "little"))

    #Use select to obtain a non-blocking accept.
    busy: int = 0
    buf: bytes
    for _ in select.select([busyServer], [], [], 5000):
        #Accept a new connection.
        client, _ = busyServer.accept()

        #Verify Meros's Handshake.
        buf = client.recv(38)
        if MessageType(
                buf[0]) not in {MessageType.Handshake, MessageType.Syncing}:
            busyServer.close()
            raise TestError(
                "Meros didn't start its connection with a Handshake.")

        if buf[1:] != (
            (254).to_bytes(1, "little") + (254).to_bytes(1, "little") +
            (128).to_bytes(1, "little") + meros.tcp.to_bytes(2, "little") +
                blockchain.blocks[0].header.hash):
            busyServer.close()
            raise TestError("Meros had an invalid Handshake.")

        #Send back Busy.
        client.send(MessageType.Busy.toByte() + bytes.fromhex("017F000001") +
                    server.getsockname()[1].to_bytes(2, "little"))

        busy += 1
        if busy == 2:
            busyServer.close()
            break

    #Make sure Meros connects to the server we redirected to.
    with raises(SuccessError):
        for _ in select.select([server], [], [], 5000):
            #Accept a new connection.
            client, _ = server.accept()

            #Verify Meros's Handshake.
            buf = client.recv(38)
            if MessageType(buf[0]) not in {
                    MessageType.Handshake, MessageType.Syncing
            }:
                server.close()
                raise TestError(
                    "Meros didn't start its connection with a Handshake.")

            if buf[1:] != (
                (254).to_bytes(1, "little") + (254).to_bytes(1, "little") +
                (128).to_bytes(1, "little") + meros.tcp.to_bytes(2, "little") +
                    blockchain.blocks[0].header.hash):
                server.close()
                raise TestError("Meros had an invalid Handshake.")

            server.close()
            raise SuccessError(
                "Meros connected to the server we redirected it to with a Busy message."
            )

        #Raise a TestError.
        busyServer.close()
        server.close()
        raise TestError("Meros didn't connect to the redirected server.")
Example #29
0
def ULimitTest(
    #Required so a Meros node is spawned.
    #pylint: disable=unused-argument
    meros: Meros
) -> None:
    #Sleep 60 seconds so Meros can correct its FD count.
    sleep(60)

    #Solely used for the genesis Block hash.
    blockchain: Blockchain = Blockchain()

    #Create peers until Meros sends us busy.
    sockets: List[MerosSocket] = []
    while True:
        #Only create live sockets to trigger new peers for each socket.
        try:
            sockets.append(
                MerosSocket(meros.tcp, 254, 254, True,
                            blockchain.blocks[0].header.hash))
        except BusyError as e:
            if e.handshake != (MessageType.Busy.toByte() + bytes(1)):
                raise TestError("Meros sent an invalid Busy message.")
            break

    #Trigger busy 32 more times to verify Meros doesn't still allocate file handles.
    for _ in range(32):
        try:
            MerosSocket(meros.tcp, 254, 254, True,
                        blockchain.blocks[0].header.hash)
        except BusyError as e:
            if e.handshake != (MessageType.Busy.toByte() + bytes(1)):
                raise TestError("Meros sent an invalid Busy message.")
            continue
        raise TestError("Meros didn't send Busy despite being at capacity.")

    #Disconnect the last 50 sockets.
    for _ in range(50):
        sockets[-1].connection.shutdown(socket.SHUT_RDWR)
        sockets[-1].connection.close()
        del sockets[-1]

    #Send a Handshake over every remaining socket every 20 seconds for a minute.
    #Then Meros should update the amount of files it has open and accept 50 new sockets.
    for _ in range(3):
        for lSocket in sockets:
            lSocket.send(MessageType.Handshake.toByte() +
                         (254).to_bytes(1, "little") +
                         (254).to_bytes(1, "little") + b'\0\0\0' +
                         blockchain.blocks[0].header.hash)
        sleep(20)

    #Connect 50 sockets and verify Meros doesn't think it's still at capacity.
    for _ in range(50):
        try:
            sockets.append(
                MerosSocket(meros.tcp, 254, 254, True,
                            blockchain.blocks[0].header.hash))
        except BusyError:
            raise TestError("Meros thought it was at capcity when it wasn't.")

    #Verify connecting one more socket returns Busy.
    try:
        MerosSocket(meros.tcp, 254, 254, True,
                    blockchain.blocks[0].header.hash)
    except BusyError:
        return
    raise TestError("Meros accepted a socket despite being at capcity.")
Example #30
0
 def sendSends() -> None:
     for send in sends[:4]:
         if rpc.meros.liveTransaction(send) != rpc.meros.live.recv():
             raise TestError("Meros didn't broadcast a Send.")
     if rpc.meros.signedElement(verif) != rpc.meros.live.recv():
         raise TestError("Meros didn't broadcast a Verification.")