Example #1
0
def mineBlock(rpc: RPC, nick: int = 0) -> None:
    privKey: PrivateKey = PrivateKey(nick)
    template: Dict[str, Any] = rpc.call(
        "merit", "getBlockTemplate",
        {"miner": privKey.toPublicKey().serialize().hex()})
    header: bytes = bytes.fromhex(template["header"])[:-4]
    header += (rpc.call(
        "merit", "getBlock",
        {"block": rpc.call("merit", "getHeight") - 1})["header"]["time"] +
               1200).to_bytes(4, "little")

    proof: int = -1
    tempHash: bytes = bytes()
    signature: bytes = bytes()
    while ((proof == -1)
           or ((int.from_bytes(tempHash, "little") * template["difficulty"]) >
               int.from_bytes(bytes.fromhex("FF" * 32), "little"))):
        proof += 1
        tempHash = RandomX(header + proof.to_bytes(4, "little"))
        signature = privKey.sign(tempHash).serialize()
        tempHash = RandomX(tempHash + signature)

    rpc.call(
        "merit", "publishBlock", {
            "id":
            template["id"],
            "header":
            header.hex() + proof.to_bytes(4, "little").hex() + signature.hex()
        })
    if rpc.meros.live.recv() != (MessageType.BlockHeader.toByte() + header +
                                 proof.to_bytes(4, "little") + signature):
        raise TestError("Meros didn't broadcast back the BlockHeader.")
Example #2
0
def verifyBlockchain(
  rpc: RPC,
  blockchain: Blockchain
) -> None:
  sleep(2)

  if rpc.call("merit", "getHeight") != len(blockchain.blocks):
    raise TestError("Height doesn't match.")
  if blockchain.difficulty() != rpc.call("merit", "getDifficulty"):
    raise TestError("Difficulty doesn't match.")

  for b in range(len(blockchain.blocks)):
    ourBlock: Dict[str, Any] = blockchain.blocks[b].toJSON()
    blockJSON: Dict[str, Any] = rpc.call("merit", "getBlock", {"block": b})
    #Contextual info Python doesn't track.
    del blockJSON["removals"]
    if blockJSON != ourBlock:
      raise TestError("Block doesn't match.")

    #Test when indexing by the hash instead of the nonce.
    blockJSON = rpc.call(
      "merit",
      "getBlock",
      {"block": blockchain.blocks[b].header.hash.hex().upper()}
    )
    del blockJSON["removals"]
    if blockJSON != ourBlock:
      raise TestError("Block doesn't match.")
Example #3
0
def verifyBlockchain(rpc: RPC, blockchain: Blockchain) -> None:
    sleep(2)

    if rpc.call("merit", "getHeight") != len(blockchain.blocks):
        raise TestError("Height doesn't match.")
    if blockchain.difficulty() != rpc.call("merit", "getDifficulty"):
        raise TestError("Difficulty doesn't match.")

    for b in range(len(blockchain.blocks)):
        ourBlock: Dict[str, Any] = blockchain.blocks[b].toJSON()
        #Info Python saves so it can properly load from the vectors yet the Meros RPC excludes.
        del ourBlock["header"]["packets"]

        blockJSON: Dict[str, Any] = rpc.call("merit", "getBlock", [b])
        #Contextual info Python doesn't track.
        del blockJSON["removals"]
        if blockJSON != ourBlock:
            raise TestError("Block doesn't match.")

        #Test when indexing by the hash instead of the nonce.
        blockJSON = rpc.call("merit", "getBlock",
                             [blockchain.blocks[b].header.hash.hex().upper()])
        del blockJSON["removals"]
        if blockJSON != ourBlock:
            raise TestError("Block doesn't match.")
Example #4
0
def verifyBlockchain(
  rpc: RPC,
  blockchain: Blockchain
) -> None:
  #Sleep to ensure data races aren't a problem.
  sleep(2)

  #Verify the height.
  if rpc.call("merit", "getHeight") != len(blockchain.blocks):
    raise TestError("Height doesn't match.")

  #Verify the difficulty.
  if blockchain.difficulty() != int(rpc.call("merit", "getDifficulty"), 16):
    raise TestError("Difficulty doesn't match.")

  #Verify the Blocks.
  for b in range(len(blockchain.blocks)):
    if rpc.call("merit", "getBlock", [b]) != blockchain.blocks[b].toJSON():
      raise TestError("Block doesn't match.")

    if rpc.call(
      "merit",
      "getBlock",
      [blockchain.blocks[b].header.hash.hex().upper()]
    ) != blockchain.blocks[b].toJSON():
      raise TestError("Block doesn't match.")
def HundredSixSignedElementsTest(rpc: RPC) -> None:
    #Solely used to get the genesis Block hash.
    blockchain: Blockchain = Blockchain()

    blsPrivKey: PrivateKey = PrivateKey(0)
    sig: Signature = blsPrivKey.sign(bytes())

    #Create a Data.
    #This is required so the Verification isn't terminated early for having an unknown hash.
    data: bytes = bytes.fromhex(rpc.call("personal", "data", ["AA"]))

    #Create a signed Verification, SendDifficulty, and DataDifficulty.
    elements: List[SignedElement] = [
        SignedVerification(data, 1, sig),
        SignedSendDifficulty(0, 0, 1, sig),
        SignedDataDifficulty(0, 0, 1, sig)
    ]

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

        #Send the Element.
        rpc.meros.signedElement(elem)

        #Sleep for thirty seconds to make sure Meros realizes our connection is dead.
        sleep(30)

        #Verify the node didn't crash.
        try:
            if rpc.call("merit", "getHeight") != 1:
                raise Exception()
        except Exception:
            raise TestError(
                "Node crashed after being sent a malformed Element.")
Example #6
0
def test(rpc: RPC, address: Union[bytes, str], invalid: bool,
         msg: str) -> None:
    try:
        if isinstance(address, bytes):
            address = encodeAddress(address)
        rpc.call("personal", "send", [address, "1"])
        #Raise a TestError with a different code than expected to ensure the below check is run and fails.
        raise TestError("0 ")
    except TestError as e:
        if int(e.message.split(" ")[0]) != (-3 if invalid else 1):
            raise TestError(msg)
Example #7
0
def verifyMeritRemoval(rpc: RPC, total: int, merit: int, holder: int,
                       pending: bool) -> None:
    sleep(1)

    if rpc.call("merit",
                "getTotalMerit") != total if pending else total - merit:
        raise TestError("Total Merit doesn't match.")

    if rpc.call("merit", "getMerit", {"nick": holder}) != {
            "status": "Unlocked",
            "malicious": pending,
            "merit": merit if pending else 0
    }:
        raise TestError("Holder's Merit doesn't match.")
Example #8
0
def verifyBlockchain(rpc: RPC, blockchain: Blockchain) -> None:
    sleep(2)

    if rpc.call("merit", "getHeight") != len(blockchain.blocks):
        raise TestError("Height doesn't match.")
    if blockchain.difficulty() != int(rpc.call("merit", "getDifficulty"), 16):
        raise TestError("Difficulty doesn't match.")

    for b in range(len(blockchain.blocks)):
        if rpc.call("merit", "getBlock", [b]) != blockchain.blocks[b].toJSON():
            raise TestError("Block doesn't match.")
        if rpc.call("merit", "getBlock",
                    [blockchain.blocks[b].header.hash.hex().upper()
                     ]) != blockchain.blocks[b].toJSON():
            raise TestError("Block doesn't match.")
def PersonalAuthorizationTest(rpc: RPC) -> None:
    #Test all these methods require authorization.
    #Doesn't test personal_data as that's not officially part of this test; just in it as a side note on key usage.
    #The actual personal_data test should handle that check.
    for method in [
            "setWallet", "setAccount", "getMnemonic", "getMeritHolderKey",
            "getMeritHolderNick", "getAccount", "getAddress", "send", "data",
            "getUTXOs", "getTransactionTemplate"
    ]:
        try:
            rpc.call("personal", method, auth=False)
            raise Exception()
        except Exception as e:
            if str(e) != "HTTP status isn't 200: 401":
                raise TestError("Could call personal_" + method +
                                " without authorization.")
Example #10
0
def rpc(
        #pylint: disable=redefined-outer-name
        meros: Meros,
        request: Any) -> RPC:
    result: RPC = RPC(meros)
    request.addfinalizer(result.quit)
    return result
Example #11
0
def checkTemplate(rpc: RPC, mnemonic: str, req: Dict[str, Any],
                  inputs: List[Dict[str, Any]],
                  outputs: List[Dict[str, Any]]) -> None:
    template: Dict[str, Any] = rpc.call("personal", "getTransactionTemplate",
                                        req)
    if template["type"] != "Send":
        raise TestError("Template isn't of type Send.")
    if sortUTXOs(template["inputs"]) != sortUTXOs(inputs):
        raise TestError("Template inputs aren't as expected.")
    if template["outputs"] != outputs:
        raise TestError("Template outputs are incorrect.")

    keys: List[bytes] = []
    for inputJSON in template["inputs"]:
        key: bytes
        if inputJSON["change"]:
            key = getChangePublicKey(mnemonic, "", inputJSON["index"])
        else:
            key = getPublicKey(mnemonic, "", inputJSON["index"])
        if key not in keys:
            keys.append(key)

    if template["publicKey"] != Ristretto.aggregate(
        [Ristretto.RistrettoPoint(key)
         for key in keys]).serialize().hex().upper():
        if len(keys) == 1:
            raise TestError(
                "Template public key isn't correct when only a single key is present."
            )
        raise TestError("Public key aggregation isn't correct.")
Example #12
0
def VUnknownSignedTest(rpc: RPC) -> None:
    file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r")
    chain: Blockchain = Blockchain.fromJSON(json.loads(file.read()))
    file.close()

    #Send a single block so we have a miner.
    rpc.meros.liveConnect(chain.blocks[0].header.hash)
    rpc.meros.syncConnect(chain.blocks[0].header.hash)
    header: bytes = rpc.meros.liveBlockHeader(chain.blocks[1].header)
    if MessageType(rpc.meros.sync.recv()[0]) != MessageType.BlockBodyRequest:
        raise TestError("Meros didn't ask for the body.")
    rpc.meros.blockBody(chain.blocks[1])
    if rpc.meros.live.recv() != header:
        raise TestError("Meros didn't broadcast the header.")

    #Create a valid Data.
    #Uneccessary at this time, but good preparation for the future.
    privKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32)
    data: Data = Data(bytes(32), privKey.get_verifying_key().to_bytes())
    data.sign(privKey)
    data.beat(SpamFilter(5))

    #Sign the Data.
    verif: SignedVerification = SignedVerification(data.hash)
    verif.sign(0, PrivateKey(0))

    #Run twice. The first shouldn't send the Transaction. The second should.
    for i in range(2):
        rpc.meros.signedElement(verif)
        if MessageType(
                rpc.meros.sync.recv()[0]) != MessageType.TransactionRequest:
            raise TestError("Meros didn't request the transaction.")

        if i == 0:
            #When we send DataMissing, we should be disconnected within a few seconds.
            rpc.meros.dataMissing()
            start: int = int(time())
            try:
                rpc.meros.sync.recv()
            except Exception:
                #More than a few seconds is allowed as Meros's own SyncRequest must timeout.
                if int(time()) - start > 10:
                    raise TestError(
                        "Meros didn't disconnect us for sending a Verification of a non-existent Transaction."
                    )
            #Clear our invalid connections.
            rpc.meros.live.connection.close()
            rpc.meros.sync.connection.close()
            sleep(65)
            #Init new ones.
            rpc.meros.liveConnect(chain.blocks[0].header.hash)
            rpc.meros.syncConnect(chain.blocks[0].header.hash)

        else:
            rpc.meros.syncTransaction(data)
            sleep(2)
            if not rpc.call("consensus", "getStatus",
                            [data.hash.hex()])["verifiers"]:
                raise TestError("Meros didn't add the Verification.")
Example #13
0
def verifyDataDifficulty(
  rpc: RPC,
  dataDiff: int
) -> None:
  sleep(1)

  if rpc.call("consensus", "getDataDifficulty") != dataDiff:
    raise TestError("Data Difficulty doesn't match.")
Example #14
0
def verifySendDifficulty(
  rpc: RPC,
  sendDiff: int
) -> None:
  #Sleep to ensure data races aren't a problem.
  sleep(1)

  if rpc.call("consensus", "getSendDifficulty") != sendDiff:
    raise TestError("Send Difficulty doesn't match.")
Example #15
0
def test(rpc: RPC, address: Union[bytes, str], invalid: bool,
         msg: str) -> None:
    if isinstance(address, bytes):
        address = encodeAddress(address)

    try:
        rpc.call("transactions", "getBalance", {"address": address}, False)
        #If the call passed, and the address is invalid, raise.
        if invalid:
            raise MessageException(msg)
    except TestError as e:
        if int(e.message.split(" ")[0]) != -32602:
            raise Exception(
                "Non-ParamError was raised by this RPC call, which shouldn't be able to raise anything else."
            )
        if not invalid:
            raise TestError(msg)
    except MessageException as e:
        raise TestError(e.message)
Example #16
0
def verifyMeritRemoval(
  rpc: RPC,
  total: int,
  merit: int,
  holder: int,
  pending: bool
) -> None:
  sleep(1)

  #Verify the total Merit.
  if rpc.call("merit", "getTotalMerit") != total if pending else total - merit:
    raise TestError("Total Merit doesn't match.")

  #Verify the holder's Merit.
  if rpc.call("merit", "getMerit", [holder]) != {
    "unlocked": True,
    "malicious": pending,
    "merit": merit if pending else 0
  }:
    raise TestError("Holder's Merit doesn't match.")
Example #17
0
def GetDifficultyTest(
  rpc: RPC
) -> None:
  #Check the global difficulty.
  if rpc.call("consensus", "getSendDifficulty", auth=False) != 3:
    raise TestError("getSendDifficulty didn't reply properly.")
  if rpc.call("consensus", "getDataDifficulty", auth=False) != 5:
    raise TestError("getDataDifficulty didn't reply properly.")

  #Check the difficulties for a holder who doesn't exist.
  try:
    rpc.call("consensus", "getSendDifficulty", {"holder": 0}, False)
    raise TestError("")
  except TestError as e:
    if str(e) != "-2 Holder doesn't have a SendDifficulty.":
      raise TestError("getSendDifficulty didn't raise when asked about a non-existent holder.")

  try:
    rpc.call("consensus", "getDataDifficulty", {"holder": 0}, False)
    raise TestError("")
  except TestError as e:
    if str(e) != "-2 Holder doesn't have a DataDifficulty.":
      raise TestError("getDataDifficulty didn't raise when asked about a non-existent holder.")

  def voteAndVerify() ->  None:
    #Check the difficulties for a holder who has yet to vote.
    try:
      rpc.call("consensus", "getSendDifficulty", {"holder": 0}, False)
      raise TestError("")
    except TestError as e:
      if str(e) != "-2 Holder doesn't have a SendDifficulty.":
        raise TestError("getSendDifficulty didn't raise when asked about a holder who has yet to vote.")
    try:
      rpc.call("consensus", "getDataDifficulty", {"holder": 0}, False)
      raise TestError("")
    except TestError as e:
      if str(e) != "-2 Holder doesn't have a DataDifficulty.":
        raise TestError("getDataDifficulty didn't raise when asked about a holder who has yet to vote.")

    #Create the votes.
    sendDiff: SignedSendDifficulty = SignedSendDifficulty(6, 0)
    sendDiff.sign(0, PrivateKey(0))

    dataDiff: SignedDataDifficulty = SignedDataDifficulty(10, 1)
    dataDiff.sign(0, PrivateKey(0))

    #Send them.
    rpc.meros.signedElement(sendDiff)
    rpc.meros.signedElement(dataDiff)
    rpc.meros.live.recv()
    rpc.meros.live.recv()

    #Check them.
    if rpc.call("consensus", "getSendDifficulty", {"holder": 0}, False) != 6:
      raise TestError("getSendDifficulty didn't reply with the holder's current difficulty.")
    if rpc.call("consensus", "getDataDifficulty", {"holder": 0}, False) != 10:
      raise TestError("getDataDifficulty didn't reply with the holder's current difficulty.")

  with open("e2e/Vectors/Merit/BlankBlocks.json", "r") as file:
    Liver(rpc, json.loads(file.read())[:1], callbacks={1: voteAndVerify}).live()
Example #18
0
def IntegerBoundTest(
  rpc: RPC
) -> None:
  #uint16.
  try:
    rpc.call("merit", "getPublicKey", {"nick": -1})
    raise TestError()
  except Exception as e:
    if str(e) != "-32602 Invalid params.":
      raise TestError("Meros accepted a negative integer for an unsigned integer.")

  try:
    rpc.call("merit", "getPublicKey", {"nick": 65536})
    raise TestError()
  except Exception as e:
    if str(e) != "-32602 Invalid params.":
      raise TestError("Meros accepted a too large unsigned integer.")

  #uint.
  try:
    rpc.call("merit", "getBlock", {"block": -1})
    raise TestError()
  except Exception as e:
    if str(e) != "-32602 Invalid params.":
      raise TestError("Meros accepted a negative integer for an unsigned integer.")

  try:
    rpc.call("merit", "getBlock", {"block": (2 ** 63)})
    raise TestError()
  except Exception as e:
    if str(e) != "-32700 Parse error.":
      raise TestError("Meros parsed an integer outside of the int64 bounds.")

  try:
    rpc.call("merit", "getBlock", {"block": (2 ** 64)})
    raise TestError()
  except Exception as e:
    if str(e) != "-32700 Parse error.":
      raise TestError("Meros parsed an integer outside of the uint64 bounds.")
Example #19
0
def HundredSixtyTwoTest(rpc: RPC) -> None:
    #Create the first Datas.
    mnemonic: str = rpc.call("personal", "getMnemonic")
    abcData: str = rpc.call("personal", "data", {"data": "abc"})

    #Create a Data on a different account.
    rpc.call("personal", "setWallet")
    defData: str = rpc.call("personal", "data", {"data": "def"})

    #Verify the def Data was created.
    if rpc.call("transactions", "getTransaction",
                {"hash": defData})["descendant"] != "Data":
        raise TestError(
            "Meros didn't create a Data for an imported account when the existing account had Datas."
        )

    #Switch back to the old Mnemonic.
    rpc.call("personal", "setWallet", {"mnemonic": mnemonic})

    #Ensure we can create new Datas on it as well, meaning switching to a Mnemonic ports the chain.
    ghiDataHash: str = rpc.call("personal", "data", {"data": "ghi"})
    ghiData: Dict[str, Any] = rpc.call("transactions", "getTransaction",
                                       {"hash": ghiDataHash})
    del ghiData["signature"]
    del ghiData["proof"]
    if ghiData != {
            "descendant": "Data",
            "inputs": [{
                "hash": abcData
            }],
            "outputs": [],
            "hash": ghiDataHash,
            "data": b"ghi".hex()
    }:
        raise TestError(
            "Data created for an imported account with Datas isn't correct.")
Example #20
0
def HundredSixSignedElementsTest(
  rpc: RPC
) -> None:
  #Solely used to get the genesis Block hash.
  blockchain: Blockchain = Blockchain()

  edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32)
  blsPrivKey: PrivateKey = PrivateKey(0)
  sig: Signature = blsPrivKey.sign(bytes())

  #Create a Data for the Verification.
  data: Data = Data(bytes(32), edPrivKey.get_verifying_key())
  data.sign(edPrivKey)
  data.beat(SpamFilter(5))

  #Create a signed Verification, SendDifficulty, and DataDifficulty.
  elements: List[SignedElement] = [
    SignedVerification(data.hash, 1, sig),
    SignedSendDifficulty(0, 0, 1, sig),
    SignedDataDifficulty(0, 0, 1, sig)
  ]

  dataSent: bool = False
  for elem in elements:
    #Handshake with the node.
    rpc.meros.liveConnect(blockchain.blocks[0].header.hash)

    #Send the Data if we have yet to.
    if not dataSent:
      if rpc.meros.liveTransaction(data) != rpc.meros.live.recv():
        raise TestError("Data wasn't rebroadcasted.")
      dataSent = True

    #Send the Element.
    rpc.meros.signedElement(elem)

    #Sleep for thirty seconds to make sure Meros realizes our connection is dead.
    sleep(30)

    #Verify the node didn't crash.
    try:
      if rpc.call("merit", "getHeight") != 1:
        raise Exception()
    except Exception:
      raise TestError("Node crashed after being sent a malformed Element.")
Example #21
0
def checkSend(rpc: RPC, sendHash: str, expected: Dict[str, Any]) -> None:
    send: Dict[str, Any] = rpc.call("transactions", "getTransaction",
                                    {"hash": sendHash})
    serialized: bytes = Send.fromJSON(send).serialize()
    del send["signature"]
    del send["proof"]

    expected["descendant"] = "Send"
    expected["hash"] = sendHash
    if sortUTXOs(send["inputs"]) != sortUTXOs(expected["inputs"]):
        raise TestError("Send inputs weren't as expected.")
    del send["inputs"]
    del expected["inputs"]
    if send != expected:
        raise TestError("Send wasn't as expected.")

    if rpc.meros.live.recv() != (MessageType.Send.toByte() + serialized):
        raise TestError("Meros didn't broadcast a Send it created.")
Example #22
0
def checkData(rpc: RPC, dataHash: str, expected: bytes) -> str:
    data: Dict[str, Any] = rpc.call("transactions", "getTransaction",
                                    {"hash": dataHash})

    if len(data["inputs"]) != 1:
        raise TestError("Data had multiple inputs.")
    res: str = data["inputs"][0]["hash"]
    del data["inputs"]
    del data["signature"]
    del data["proof"]

    if data != {
            "descendant": "Data",
            "outputs": [],
            "hash": dataHash,
            "data": expected.hex().upper()
    }:
        raise TestError("Data wasn't as expected.")

    return res
def HundredSixBlockElementsTest(rpc: RPC) -> None:
    vectors: Dict[str, Any]
    with open("e2e/Vectors/Consensus/HundredSix/BlockElements.json",
              "r") as file:
        vectors = json.loads(file.read())

    #Solely used to get the genesis Block hash.
    blockchain: Blockchain = Blockchain()
    transactions: Transactions = Transactions.fromJSON(vectors["transactions"])

    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)
        rpc.meros.handleBlockBody(block)

        #Flag of if the Block's Body synced.
        doneSyncing: bool = len(block.body.packets) == 0

        #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.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])

                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 so we can test the next invalid Block.
        rpc.reset()
Example #24
0
def TwoHundredThirtyFiveTest(rpc: RPC) -> None:
    blockchain: Blockchain = Blockchain()
    dataFilter: SpamFilter = SpamFilter(5)

    edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32)
    edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key()

    #Mine one Block to the node.
    blsPrivKey: PrivateKey = PrivateKey(
        bytes.fromhex(rpc.call("personal", "getMiner")))
    blsPubKey: bytes = blsPrivKey.toPublicKey().serialize()

    #Call getBlockTemplate just to get an ID.
    #Skips the need to write a sync loop for the BlockBody.
    template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate",
                                        [blsPubKey.hex()])

    #Mine a Block.
    block = Block(
        BlockHeader(0, blockchain.blocks[0].header.hash, bytes(32), 1,
                    bytes(4), bytes(32), blsPubKey,
                    blockchain.blocks[0].header.time + 1200, 0), BlockBody())
    block.mine(blsPrivKey, blockchain.difficulty())
    blockchain.add(block)

    rpc.call("merit", "publishBlock",
             [template["id"], block.serialize().hex()])

    #Send Meros a Data and receive its Verification to make sure it's verifying Transactions in the first place.
    data: Data = Data(bytes(32), edPubKey.to_bytes())
    data.sign(edPrivKey)
    data.beat(dataFilter)

    rpc.meros.liveConnect(blockchain.blocks[0].header.hash)
    if rpc.meros.liveTransaction(data) != rpc.meros.live.recv():
        raise TestError("Meros didn't send back the Data.")
    if MessageType(rpc.meros.live.recv()[0]) != MessageType.SignedVerification:
        raise TestError("Meros didn't send us its SignedVerification.")

    #Close our connection and mine 8 Blocks so its Merit is locked.
    rpc.meros.live.connection.close()
    for _ in range(8):
        block = Block(
            BlockHeader(0, blockchain.blocks[-1].header.hash, bytes(32), 1,
                        bytes(4), bytes(32), 0,
                        blockchain.blocks[-1].header.time + 1200, 0),
            BlockBody())
        #Reusing its key is fine as mining doesn't count as participation.
        block.mine(blsPrivKey, blockchain.difficulty())
        blockchain.add(block)

    #Sleep 30 seconds to make sure Meros noted we disconnected, and then reconnect.
    sleep(30)
    rpc.meros.liveConnect(blockchain.blocks[0].header.hash)
    rpc.meros.syncConnect(blockchain.blocks[0].header.hash)

    #Sync the Blocks.
    for b in range(8):
        header: bytes = rpc.meros.liveBlockHeader(blockchain.blocks[b +
                                                                    2].header)
        if rpc.meros.sync.recv() != (MessageType.BlockBodyRequest.toByte() +
                                     blockchain.blocks[b + 2].header.hash):
            raise TestError("Meros didn't request the BlockBody.")
        rpc.meros.blockBody(blockchain.blocks[b + 2])
        if rpc.meros.live.recv() != header:
            raise TestError("Meros didn't send back the header.")
        if MessageType(
                rpc.meros.live.recv()[0]) != MessageType.SignedVerification:
            raise TestError("Meros didn't verify this Block's data.")

    #Verify its Merit is locked.
    #Theoretically, all code after this check is unecessary.
    #Meros verifies a Block's Data after updating its State.
    #Therefore, if the above last Block had its Data verified, this issue should be closed.
    #That said, the timing is a bit too tight for comfort.
    #Better safe than sorry. Hence why the code after this check exists.
    if rpc.call("merit", "getMerit", [0])["status"] != "Locked":
        raise TestError("Merit wasn't locked when it was supposed to be.")

    #Send it a Transaction and make sure Meros verifies it, despite having its Merit locked.
    data = Data(data.hash, edPubKey.to_bytes())
    data.sign(edPrivKey)
    data.beat(dataFilter)

    if rpc.meros.liveTransaction(data) != rpc.meros.live.recv():
        raise TestError("Meros didn't send back the Data.")
    if MessageType(rpc.meros.live.recv()[0]) != MessageType.SignedVerification:
        raise TestError("Meros didn't send us its SignedVerification.")
Example #25
0
def PartialArchiveTest(rpc: RPC) -> None:
    vectors: Dict[str, Any]
    with open("e2e/Vectors/Consensus/Verification/PartialArchive.json",
              "r") as file:
        vectors = json.loads(file.read())

    data: Data = Data.fromJSON(vectors["data"])
    svs: List[SignedVerification] = [
        SignedVerification.fromSignedJSON(vectors["verifs"][0]),
        SignedVerification.fromSignedJSON(vectors["verifs"][1])
    ]

    key: PrivateKey = PrivateKey(
        bytes.fromhex(rpc.call("personal", "getMiner")))

    def sendDataAndVerifications() -> None:
        if rpc.meros.liveTransaction(data) != rpc.meros.live.recv():
            raise TestError(
                "Meros didn't rebroadcast a Transaction we sent it.")
        for sv in svs:
            if rpc.meros.signedElement(sv) != rpc.meros.live.recv():
                raise TestError(
                    "Meros didn't rebroadcast a SignedVerification we sent it."
                )

        #As we don't have a quality RPC route for this, we need to use getTemplate.
        if bytes.fromhex(
                rpc.call("merit", "getBlockTemplate",
                         [key.toPublicKey().serialize().hex()
                          ])["header"])[36:68] != BlockHeader.createContents(
                              [VerificationPacket(data.hash, [0, 1])]):
            raise TestError(
                "New Block template doesn't have a properly created packet.")

    def verifyRecreation() -> None:
        template: Dict[str,
                       Any] = rpc.call("merit", "getBlockTemplate",
                                       [key.toPublicKey().serialize().hex()])
        if bytes.fromhex(
                template["header"])[36:68] != BlockHeader.createContents(
                    [VerificationPacket(data.hash, [1])]):
            raise TestError(
                "New Block template doesn't have a properly recreated packet.")

        #Mining it further verifies the internal state.
        header: bytes = bytes.fromhex(template["header"])
        proof: int = 0
        sig: bytes
        while True:
            initial: bytes = RandomX(header +
                                     proof.to_bytes(4, byteorder="little"))
            sig = key.sign(initial).serialize()
            final: bytes = RandomX(initial + sig)
            if (int.from_bytes(final, "little") *
                    template["difficulty"]) < int.from_bytes(
                        bytes.fromhex("FF" * 32), "little"):
                break
            proof += 1

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

        raise SuccessError(
            "Stop Liver from trying to verify the vector chain which doesn't have this Block."
        )

    #We may not want to use Liver here.
    #There's a very small Block count and we can't let it terminate (hence the SE).
    with raises(SuccessError):
        Liver(rpc,
              vectors["blockchain"],
              callbacks={
                  2: sendDataAndVerifications,
                  3: verifyRecreation
              }).live()
Example #26
0
def HundredTwentyFourTest(
  rpc: RPC
) -> None:
  #Load the vectors.
  file: IO[Any] = open("e2e/Vectors/Merit/BlankBlocks.json", "r")
  vectors: List[Dict[str, Any]] = json.loads(file.read())
  file.close()

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

  #Parse the Blocks from the vectors.
  for i in range(2):
    blockchain.add(Block.fromJSON(vectors[i]))

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

  def handleSync() -> None:
    #Handle sync requests.
    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.")

        #Send the 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, "big") +
    (254).to_bytes(1, "big") +
    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 its Blockchain's Tail.")

  #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, "big") +
    (254).to_bytes(1, "big") +
    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 its Blockchain's Tail.")
  handleSync()
Example #27
0
def PersonalDataTest(rpc: RPC) -> None:
    #Create a Data.
    firstData: str = rpc.call("personal", "data", {"data": "a"})
    initial: str = checkData(rpc, firstData, b"a")

    #Meros should've also created an initial Data.
    if checkData(rpc, initial, decodeAddress(rpc.call(
            "personal", "getAddress"))) != bytes(32).hex():
        raise TestError("Initial Data didn't have a 0-hash input.")

    #Create a Data using hex data. Also tests upper case hex.
    if checkData(rpc,
                 rpc.call("personal", "data", {
                     "data": "AABBCC",
                     "hex": True
                 }), b"\xAA\xBB\xCC") != firstData:
        raise TestError(
            "Newly created Data wasn't a descendant of the existing Data.")

    #Should support using 256 bytes of Data. Also tests lower case hex.
    checkData(
        rpc,
        rpc.call("personal", "data", {
            "data": bytes([0xaa] * 256).hex(),
            "hex": True
        }), bytes([0xaa] * 256))

    #Should properly error when we input no data. All Datas must have at least 1 byte of Data.
    try:
        rpc.call("personal", "data", {"data": ""})
        raise Exception()
    except Exception as e:
        if str(e) != "-3 Data is too small or too large.":
            raise TestError("Meros didn't handle Data that was too small.")

    #Should properly error when we supply more than 256 bytes of data.
    try:
        rpc.call("personal", "data", {"data": "a" * 257})
        raise Exception()
    except Exception as e:
        if str(e) != "-3 Data is too small or too large.":
            raise TestError("Meros didn't handle Data that was too large.")

    #Should properly error when we supply non-hex data with the hex flag.
    try:
        rpc.call("personal", "data", {"data": "zz", "hex": True})
        raise Exception()
    except Exception as e:
        if str(e) != "-3 Invalid hex char `z` (ord 122).":
            raise TestError("Meros didn't properly handle invalid hex.")

    #Should properly error when we supply non-even hex data.
    try:
        rpc.call("personal", "data", {"data": "a", "hex": True})
        raise Exception()
    except Exception as e:
        if str(e) != "-3 Incorrect hex string len.":
            raise TestError("Meros didn't properly handle non-even hex.")

    #Test Datas when the Wallet has a password.
    rpc.call("personal", "setWallet", {"password": "******"})

    #Shouldn't work due to the lack of a password.
    try:
        rpc.call("personal", "data", {"data": "abc"})
        raise Exception()
    except Exception as e:
        if str(e) != "-3 Invalid password.":
            raise TestError(
                "Meros didn't properly handle creating a Data without a password."
            )

    #Should work due to the existence of a password.
    lastData: str = rpc.call("personal", "data", {
        "data": "abc",
        "password": "******"
    })
    checkData(rpc, lastData, b"abc")

    #Reboot the node and verify we can create a new Data without issue.
    rpc.quit()
    sleep(3)
    rpc.meros = Meros(rpc.meros.db, rpc.meros.tcp, rpc.meros.rpc)
    if checkData(
            rpc,
            rpc.call("personal", "data", {
                "data": "def",
                "password": "******"
            }), b"def") != lastData:
        raise TestError("Couldn't create a new Data after rebooting.")
Example #28
0
def StringBasedTypesTest(
  rpc: RPC
) -> None:
  edPrivKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32)
  edPubKey: bytes = edPrivKey.get_verifying_key()
  blsPubKey: PublicKey = PrivateKey(0).toPublicKey()

  #hex.
  #Test 0x-prefixed and no-0x both work without issue.
  data: Data = Data(bytes(32), edPubKey)
  data.sign(edPrivKey)
  rpc.call("transactions", "publishTransactionWithoutWork", {"type": "Data", "transaction": "0x" + data.serialize()[:-4].hex()})

  data = Data(data.hash, b"abc")
  data.sign(edPrivKey)
  rpc.call("transactions", "publishTransactionWithoutWork", {"type": "Data", "transaction": data.serialize()[:-4].hex()})

  #Test non-hex data is properly handled.
  try:
    rpc.call("transactions", "publishTransactionWithoutWork", {"type": "Data", "transaction": "az"})
    raise TestError()
  except Exception as e:
    if str(e) != "-32602 Invalid params.":
      raise TestError("Meros accepted non-hex data for a hex argument.")

  #Hash.
  rpc.call("transactions", "getTransaction", {"hash": data.hash.hex()})
  rpc.call("transactions", "getTransaction", {"hash": "0x" + data.hash.hex()})

  #Also call the upper form, supplementing the above hex tests (as Hash routes through hex).
  rpc.call("transactions", "getTransaction", {"hash": data.hash.hex().upper()})
  rpc.call("transactions", "getTransaction", {"hash": "0x" + data.hash.hex().upper()})

  #Improper length.
  try:
    rpc.call("transactions", "getTransaction", {"hash": data.hash.hex().upper()[:-2]})
    raise TestError()
  except Exception as e:
    if str(e) != "-32602 Invalid params.":
      raise TestError("Meros accepted a hex string with an improper length as a Hash.")

  #BLSPublicKey.
  try:
    rpc.call("merit", "getNickname", {"key": blsPubKey.serialize().hex()})
    raise TestError()
  except Exception as e:
    if str(e) != "-2 Key doesn't have a nickname assigned.":
      raise TestError("Meros didn't accept a valid BLSPublicKey.")
  try:
    rpc.call("merit", "getNickname", {"key": blsPubKey.serialize().hex()[:-2]})
    raise TestError()
  except Exception as e:
    if str(e) != "-32602 Invalid params.":
      raise TestError("Meros accepted a hex string with an improper length as a BLSPublicKey.")

  #Missing flags.
  try:
    rpc.call("merit", "getNickname", {"key": "0" + blsPubKey.serialize().hex()[1]})
    raise TestError()
  except Exception as e:
    if str(e) != "-32602 Invalid params.":
      raise TestError("Meros accepted an invalid BLSPublicKey as a BLSPublicKey.")

  #EdPublicKey.
  rpc.call("personal", "setAccount", {"key": edPubKey.hex(), "chainCode": bytes(32).hex()})
  try:
    rpc.call("personal", "setAccount", {"key": edPubKey[:-2].hex(), "chainCode": bytes(32).hex()})
    raise TestError()
  except Exception as e:
    if str(e) != "-32602 Invalid params.":
      raise TestError("Meros accepted a hex string with an improper length as an EdPublicKey.")
Example #29
0
def TwoHundredThirtyTwoTest(rpc: RPC) -> None:
    file: IO[Any] = open(
        "e2e/Vectors/Merit/Reorganizations/TwoHundredThirtyTwo.json", "r")
    chains: Dict[str, List[Dict[str, Any]]] = json.loads(file.read())
    main: Blockchain = Blockchain.fromJSON(chains["main"])
    alt: Blockchain = Blockchain.fromJSON(chains["alt"])
    file.close()

    def sendBlock(toSend: Block) -> None:
        rpc.meros.liveBlockHeader(toSend.header)
        if rpc.meros.sync.recv() != (MessageType.BlockBodyRequest.toByte() +
                                     toSend.header.hash):
            raise TestError("Meros didn't ask for this Block's body.")
        rpc.meros.blockBody(toSend)
        if toSend.body.packets:
            if rpc.meros.sync.recv() != (
                    MessageType.SketchHashRequests.toByte() +
                    toSend.header.hash +
                (1).to_bytes(4, byteorder="little") + Sketch.hash(
                    toSend.header.sketchSalt, toSend.body.packets[0]).to_bytes(
                        8, byteorder="little")):
                raise TestError(
                    "Meros didn't ask for this BlockBody's VerificationPacket."
                )
            rpc.meros.packet(toSend.body.packets[0])

    #Make the initial connection and sync the main chain.
    rpc.meros.liveConnect(main.blocks[0].header.hash)
    rpc.meros.syncConnect(main.blocks[0].header.hash)
    sendBlock(main.blocks[1])
    sendBlock(main.blocks[2])

    #Trigger the reorganization to the alternate chain.
    #We only want the revert aspect of this.
    rpc.meros.liveBlockHeader(alt.blocks[3].header)
    if rpc.meros.sync.recv()[0] != MessageType.BlockListRequest.toByte()[0]:
        raise TestError(
            "Meros didn't ask for the Block List of the alternate chain.")
    rpc.meros.blockList([alt.blocks[2].header.hash, alt.blocks[1].header.hash])
    if rpc.meros.sync.recv() != (MessageType.BlockHeaderRequest.toByte() +
                                 alt.blocks[2].header.hash):
        raise TestError(
            "Meros didn't ask for the other BlockHeader in this alternate chain."
        )
    rpc.meros.syncBlockHeader(alt.blocks[2].header)

    #Cause the re-organization to fail.
    rpc.meros.live.connection.close()
    rpc.meros.sync.connection.close()
    rpc.socket.close()
    sleep(35)

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

    rpc.meros.process = Popen([
        "./build/Meros", "--data-dir", rpc.meros.dataDir, "--log-file",
        rpc.meros.log, "--db", rpc.meros.db, "--network", "devnet",
        "--tcp-port",
        str(rpc.meros.tcp), "--rpc-port",
        str(rpc.meros.rpc), "--no-gui"
    ])
    while True:
        try:
            connection: socket.socket = socket.socket(socket.AF_INET,
                                                      socket.SOCK_STREAM)
            connection.connect(("127.0.0.1", rpc.meros.rpc))
            connection.shutdown(socket.SHUT_RDWR)
            connection.close()
            break
        except ConnectionRefusedError:
            sleep(1)

    rpc.meros.liveConnect(main.blocks[0].header.hash)
    rpc.meros.syncConnect(main.blocks[0].header.hash)
    sendBlock(main.blocks[2])

    rpc.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    rpc.socket.connect(("127.0.0.1", rpc.meros.rpc))
    verifyBlockchain(rpc, main)
Example #30
0
def EightyEightTest(
  rpc: RPC
) -> None:
  edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32)
  edPubKey: ed25519.VerifyingKey = edPrivKey.get_verifying_key()

  blsPrivKey: PrivateKey = PrivateKey(0)
  blsPubKey: str = blsPrivKey.toPublicKey().serialize().hex()

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

  merit: Merit = Merit()
  dataFilter: SpamFilter = SpamFilter(5)

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

  #Send the first Block.
  block: Block = Block.fromJSON(blocks[0])
  merit.blockchain.add(block)
  rpc.meros.liveBlockHeader(block.header)

  #Handle sync requests.
  reqHash: bytes = bytes()
  while True:
    msg: bytes = rpc.meros.sync.recv()
    if MessageType(msg[0]) == MessageType.BlockBodyRequest:
      reqHash = msg[1 : 33]
      if reqHash != block.header.hash:
        raise TestError("Meros asked for a Block Body that didn't belong to the Block we just sent it.")

      #Send the BlockBody.
      rpc.meros.blockBody(block)
      break

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

  if MessageType(rpc.meros.live.recv()[0]) != MessageType.BlockHeader:
    raise TestError("Meros didn't broadcast the Block Header it just added.")

  #Create two Datas.
  datas: List[Data] = [Data(bytes(32), edPubKey.to_bytes())]
  datas.append(Data(datas[0].hash, b"Hello there! General Kenobi."))

  for data in datas:
    #Sign them and have them beat the spam filter.
    data.sign(edPrivKey)
    data.beat(dataFilter)

    #Transmit them.
    rpc.meros.liveTransaction(data)

  #Verify both.
  verifs: List[SignedVerification] = [
    SignedVerification(datas[0].hash),
    SignedVerification(datas[1].hash)
  ]
  for verif in verifs:
    verif.sign(0, blsPrivKey)

  #Only transmit the second.
  rpc.meros.signedElement(verifs[1])
  sleep(0.5)

  #Verify the block template has no verifications.
  if bytes.fromhex(
    rpc.call("merit", "getBlockTemplate", [blsPubKey])["header"]
  )[36 : 68] != bytes(32):
    raise TestError("Block template has Verification Packets.")

  #Transmit the first signed verification.
  rpc.meros.signedElement(verifs[0])
  sleep(0.5)

  #Verify the block template has both verifications.
  template: Dict[str, Any] = rpc.call("merit", "getBlockTemplate", [blsPubKey])
  template["header"] = bytes.fromhex(template["header"])
  packets: List[VerificationPacket] = [VerificationPacket(datas[0].hash, [0]), VerificationPacket(datas[1].hash, [0])]
  if template["header"][36 : 68] != BlockHeader.createContents(packets):
    raise TestError("Block template doesn't have both Verification Packets.")

  #Mine the Block.
  block = Block(
    BlockHeader(
      0,
      block.header.hash,
      BlockHeader.createContents(packets),
      1,
      template["header"][-43 : -39],
      BlockHeader.createSketchCheck(template["header"][-43 : -39], packets),
      0,
      int.from_bytes(template["header"][-4:], byteorder="little")
    ),
    BlockBody(
      packets,
      [],
      Signature.aggregate([verifs[0].signature, verifs[1].signature])
    )
  )
  if block.header.serializeHash()[:-4] != template["header"]:
    raise TestError("Failed to recreate the header.")
  if block.body.serialize(
    block.header.sketchSalt,
    len(packets)
  ) != bytes.fromhex(template["body"]):
    raise TestError("Failed to recreate the body.")

  block.mine(blsPrivKey, merit.blockchain.difficulty())
  merit.blockchain.add(block)

  rpc.call(
    "merit",
    "publishBlock",
    [
      template["id"],
      (
        template["header"] +
        block.header.proof.to_bytes(4, byteorder="little") +
        block.header.signature +
        block.body.serialize(block.header.sketchSalt, len(packets))
      ).hex()
    ]
  )

  verifyBlockchain(rpc, merit.blockchain)