예제 #1
0
def FiftyTest(rpc: RPC) -> None:
    with open("e2e/Vectors/Transactions/Fifty.json", "r") as file:
        vectors: Dict[str, Any] = json.loads(file.read())
        Liver(rpc, vectors["blockchain"],
              Transactions.fromJSON(vectors["transactions"])).live()
        Syncer(rpc, vectors["blockchain"],
               Transactions.fromJSON(vectors["transactions"])).sync()
예제 #2
0
def MultiInputClaimTest(rpc: RPC) -> None:
    file: IO[Any] = open("e2e/Vectors/Transactions/MultiInputClaim.json", "r")
    vectors: Dict[str, Any] = json.loads(file.read())
    file.close()

    Liver(rpc, vectors["blockchain"],
          Transactions.fromJSON(vectors["transactions"])).live()
    Syncer(rpc, vectors["blockchain"],
           Transactions.fromJSON(vectors["transactions"])).sync()
예제 #3
0
파일: FiftyTest.py 프로젝트: vporton/Meros
def FiftyTest(rpc: RPC) -> None:
    file: IO[Any] = open("e2e/Vectors/Transactions/Fifty.json", "r")
    vectors: Dict[str, Any] = json.loads(file.read())
    file.close()

    #Create and execute a Liver/Syncer.
    Liver(rpc, vectors["blockchain"],
          Transactions.fromJSON(vectors["transactions"])).live()
    Syncer(rpc, vectors["blockchain"],
           Transactions.fromJSON(vectors["transactions"])).sync()
예제 #4
0
def HundredSeventyFiveTest(
  rpc: RPC
) -> None:
  file: IO[Any] = open("e2e/Vectors/Merit/HundredSeventyFive.json", "r")
  vectors: Dict[str, Any] = json.loads(file.read())
  file.close()

  transactions: Transactions = Transactions.fromJSON(vectors["transactions"])
  verif: SignedVerification = SignedVerification.fromSignedJSON(vectors["verification"])

  def sendDatasAndVerif() -> None:
    for tx in transactions.txs:
      if rpc.meros.liveTransaction(transactions.txs[tx]) != rpc.meros.live.recv():
        raise TestError("Meros didn't send us back the Data.")

    if rpc.meros.signedElement(verif) != rpc.meros.live.recv():
      raise TestError("Meros didn't send us back the Verification.")

  Liver(
    rpc,
    vectors["blockchain"],
    transactions,
    callbacks={
      1: sendDatasAndVerif
    }
  ).live([verif.hash])
예제 #5
0
def ImpossibleFamilyTest(
  rpc: RPC
) -> None:
  vectors: Dict[str, Any]
  with open("e2e/Vectors/Consensus/Families/ImpossibleFamily.json", "r") as file:
    vectors = json.loads(file.read())
  sends: List[Send] = [Send.fromJSON(send) for send in vectors["sends"]]

  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.")

  def verifyPossibleWon() -> None:
    if rpc.call("consensus", "getStatus", [sends[1].hash.hex()])["verified"]:
      raise TestError("Meros verified an impossible Transaction.")
    if not rpc.call("consensus", "getStatus", [sends[0].hash.hex()])["verified"]:
      raise TestError("Meros didn't verify the only possible Transaction.")

  Liver(
    rpc,
    vectors["blockchain"],
    Transactions.fromJSON(vectors["transactions"]),
    callbacks={
      42: sendSends,
      48: verifyPossibleWon
    }
  ).live()
def UnionedFamiliesSingleWinnerTest(
  rpc: RPC
) -> None:
  vectors: Dict[str, Any]
  with open("e2e/Vectors/Consensus/Families/UnionedFamiliesSingleWinner.json", "r") as file:
    vectors = json.loads(file.read())
  sends: List[Send] = [Send.fromJSON(send) for send in vectors["sends"]]

  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.")

  def verifyUnionizingWon() -> None:
    for send in sends[:-1]:
      if rpc.call("consensus", "getStatus", {"hash": send.hash.hex()})["verified"]:
        raise TestError("Meros verified a transaction which was beaten by a unionizing transaction.")
    if not rpc.call("consensus", "getStatus", {"hash": sends[-1].hash.hex()})["verified"]:
      raise TestError("Meros didn't verify the verified unionizing transaction.")

  Liver(
    rpc,
    vectors["blockchain"],
    Transactions.fromJSON(vectors["transactions"]),
    callbacks={
      45: sendSends,
      51: verifyUnionizingWon
    }
  ).live()
예제 #7
0
def VCompetingTest(rpc: RPC) -> None:
    vectors: Dict[str, Any]
    with open("e2e/Vectors/Consensus/Verification/Competing.json",
              "r") as file:
        vectors = json.loads(file.read())

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

    #Function to verify the right Transaction was confirmed.
    def verifyConfirmation() -> None:
        if not rpc.call("consensus", "getStatus",
                        {"hash": vectors["verified"]})["verified"]:
            raise TestError(
                "Didn't verify the Send which should have been verified.")

        if rpc.call("consensus", "getStatus",
                    {"hash": vectors["beaten"]})["verified"]:
            raise TestError(
                "Did verify the Send which should have been beaten.")

    Liver(rpc,
          vectors["blockchain"],
          transactions,
          callbacks={
              19: verifyConfirmation
          }).live()
    Syncer(rpc, vectors["blockchain"], transactions).sync()
예제 #8
0
def UnionedFamiliesMultipleWinnersTest(rpc: RPC) -> None:
    vectors: Dict[str, Any]
    with open(
            "e2e/Vectors/Consensus/Families/UnionedFamiliesMultipleWinners.json",
            "r") as file:
        vectors = json.loads(file.read())
    sends: List[Send] = [Send.fromJSON(send) for send in vectors["sends"]]

    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.")

    def verifyMultipleWon() -> None:
        for send in [sends[1], *sends[3:]]:
            if rpc.call("consensus", "getStatus",
                        [send.hash.hex()])["verified"]:
                raise TestError(
                    "Meros verified a transaction which was beaten by another transaction."
                )
        for send in [sends[0], sends[2]]:
            if not rpc.call("consensus", "getStatus",
                            [send.hash.hex()])["verified"]:
                raise TestError(
                    "Meros didn't verify the verified transaction for each original family."
                )

    Liver(rpc,
          vectors["blockchain"],
          Transactions.fromJSON(vectors["transactions"]),
          callbacks={
              44: sendSends,
              50: verifyMultipleWon
          }).live()
def RespondsWithRequestedCapacityTest(
  rpc: RPC
) -> None:
  vectors: Dict[str, Any]
  with open("e2e/Vectors/Merit/TwoHundredSeventyFour/RespondsWithRequestedCapacity.json", "r") as file:
    vectors = json.loads(file.read())

  def requestWithCapacity() -> None:
    block: Block = Block.fromJSON(vectors["blockchain"][-1])

    #Request 3/6 Transactions.
    rpc.meros.sync.send(MessageType.BlockBodyRequest.toByte() + block.header.hash + (3).to_bytes(4, "little"))
    if rpc.meros.sync.recv() != (MessageType.BlockBody.toByte() + block.body.serialize(block.header.sketchSalt, 3)):
      raise TestError("Meros didn't respond with the requested capacity.")

    #Request 8/6 Transactions.
    rpc.meros.sync.send(MessageType.BlockBodyRequest.toByte() + block.header.hash + (8).to_bytes(4, "little"))
    if rpc.meros.sync.recv() != (MessageType.BlockBody.toByte() + block.body.serialize(block.header.sketchSalt, 6)):
      raise TestError("Meros didn't respond with the requested capacity (normalized).")

  Liver(
    rpc,
    vectors["blockchain"],
    Transactions.fromJSON(vectors["transactions"]),
    callbacks={
      2: requestWithCapacity
    }
  ).live()
예제 #10
0
def HundredTwoTest(
  rpc: RPC
) -> None:
  file: IO[Any] = open("e2e/Vectors/Consensus/Verification/HundredTwo.json", "r")
  vectors: Dict[str, Any] = json.loads(file.read())
  file.close()

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

  #Verifies the Transaction is added, it has the right holders, the holders Merit surpasses the threshold, yet it isn't verified.
  def verify() -> None:
    for tx in transactions.txs:
      status: Dict[str, Any] = rpc.call("consensus", "getStatus", [tx.hex()])
      if set(status["verifiers"]) != set([0, 1]):
        raise TestError("Meros doesn't have the right list of verifiers for this Transaction.")

      if status["merit"] != 80:
        raise TestError("Meros doesn't have the right amount of Merit for this Transaction.")

      if rpc.call("merit", "getMerit", [0])["merit"] + rpc.call("merit", "getMerit", [1])["merit"] < status["threshold"]:
        raise TestError("Merit sum of holders is less than the threshold.")

      if status["verified"]:
        raise TestError("Meros verified the Transaction which won't have enough Merit by the time the Transaction finalizes.")

  Liver(rpc, vectors["blockchain"], transactions, callbacks={100: verify}).live()
예제 #11
0
def DescendantHighestUnverifiedParentTest(rpc: RPC) -> None:
    vectors: Dict[str, Any]
    with open(
            "e2e/Vectors/Consensus/Families/DescendantHighestUnverifiedParent.json",
            "r") as file:
        vectors = json.loads(file.read())
    sends: List[Send] = [Send.fromJSON(send) for send in vectors["sends"]]

    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.")

    def verifyDescendantLost() -> None:
        for send in sends[1:]:
            if rpc.call("consensus", "getStatus",
                        {"hash": send.hash.hex()})["verified"]:
                raise TestError(
                    "Meros verified a beaten transaction or one of its children (one of which is impossible)."
                )
        if not rpc.call("consensus", "getStatus",
                        {"hash": sends[0].hash.hex()})["verified"]:
            raise TestError(
                "Meros either didn't verify the descendant or its parent.")

    Liver(rpc,
          vectors["blockchain"],
          Transactions.fromJSON(vectors["transactions"]),
          callbacks={
              45: sendSends,
              51: verifyDescendantLost
          }).live()
예제 #12
0
def MultiInputClaimTest(rpc: RPC) -> None:
    with open("e2e/Vectors/Transactions/MultiInputClaim.json", "r") as file:
        vectors: Dict[str, Any] = json.loads(file.read())
        transactions: Transactions = Transactions.fromJSON(
            vectors["transactions"])
        Liver(rpc, vectors["blockchain"], transactions).live()
        Syncer(rpc, vectors["blockchain"], transactions).sync()
예제 #13
0
def TGUReorgTest(rpc: RPC) -> None:
    vectors: Dict[str, Any]
    with open("e2e/Vectors/RPC/Transactions/GetUTXOs.json", "r") as file:
        vectors = json.loads(file.read())
    transactions: Transactions = Transactions.fromJSON(vectors["transactions"])

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

        #Create a Send.
        send: Send = Send.fromJSON(vectors["send"])
        if rpc.meros.liveTransaction(send) != rpc.meros.live.recv():
            raise TestError("Meros didn't broadcast back a Send.")
        verify(rpc, send.hash)
        if rpc.call("transactions", "getUTXOs",
                    {"address": address}) != [{
                        "hash": send.hash.hex().upper(),
                        "nonce": 0
                    }]:
            raise TestError(
                "Meros didn't consider a confirmed Transaction's outputs as UTXOs."
            )
        #Spend it, with a newer Mint as an input as well so we can prune it without pruning the original.
        newerSend: Send = createSend(
            rpc, [Claim.fromJSON(vectors["newerMintClaim"])], recipientPub)
        _: Send = createSend(rpc, [send, newerSend], bytes(32), recipient)
        if rpc.call("transactions", "getUTXOs", {"address": address}) != []:
            raise TestError("Meros thinks the recipient has UTXOs.")

        #Remove the spending Send by pruning its ancestor (a Mint).
        reorg(rpc, Blockchain.fromJSON(vectors["blocksWithoutNewerMint"]))
        #Meros should add back its parent as an UTXO.
        if rpc.call("transactions", "getUTXOs",
                    {"address": address}) != [{
                        "hash": send.hash.hex().upper(),
                        "nonce": 0
                    }]:
            raise TestError(
                "Meros didn't consider a Transaction without spenders as an UTXO."
            )
        #Remove the original Send and verify its outputs are no longer considered UTXOs.
        reorg(rpc, Blockchain.fromJSON(vectors["blocksWithoutOlderMint"]))
        if rpc.call("transactions", "getUTXOs", {"address": address}) != []:
            raise TestError(
                "Meros didn't remove the outputs of a pruned Transaction as UTXOs."
            )

        raise SuccessError()

    #Send Blocks so we have a Merit Holder who can instantly verify Transactions, not to mention Mints.
    with raises(SuccessError):
        Liver(rpc, vectors["blockchain"], transactions, {50: test}).live()
예제 #14
0
def TGUFinalizesTest(
  rpc: RPC
) -> None:
  vectors: Dict[str, Any]
  with open("e2e/Vectors/RPC/Transactions/GetUTXOs.json", "r") as file:
    vectors = json.loads(file.read())
  transactions: Transactions = Transactions.fromJSON(vectors["transactions"])

  def test() -> None:
    recipient: Ristretto.SigningKey = Ristretto.SigningKey(b'\1' * 32)
    recipientPub: bytes = recipient.get_verifying_key()
    address: str = bech32_encode("mr", convertbits(bytes([0]) + recipientPub, 8, 5))

    otherRecipient: bytes = Ristretto.SigningKey(b'\2' * 32).get_verifying_key()
    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)

    #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.")
    if rpc.call("transactions", "getUTXOs", {"address": address}) != []:
      raise TestError("Meros didn't consider a Transaction's inputs as spent.")

    #Verify with another party, so it won't be majority verified, yet will still have a Verification.
    mineBlock(rpc, 1)
    verify(rpc, spendingSend.hash, 1)
    #Verify it didn't create a UTXO.
    if rpc.call("transactions", "getUTXOs", {"address": otherAddress}) != []:
      raise TestError("Unverified Transaction created a UTXO.")

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

    #Check the UTXOs were created.
    if rpc.call("transactions", "getUTXOs", {"address": otherAddress}) != [{"hash": spendingSend.hash.hex().upper(), "nonce": 0}]:
      raise TestError("Meros didn't consider a finalized Transaction's outputs as UTXOs.")

    raise SuccessError()

  #Send Blocks so we have a Merit Holder who can instantly verify Transactions, not to mention Mints.
  with raises(SuccessError):
    Liver(rpc, vectors["blockchain"], transactions, {50: test}).live()
예제 #15
0
def InvalidCompetingTest(rpc: RPC) -> None:
    file: IO[Any] = open(
        "e2e/Vectors/Consensus/MeritRemoval/InvalidCompeting.json", "r")
    vectors: Dict[str, Any] = json.loads(file.read())
    file.close()

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

    #MeritRemoval.
    #pylint: disable=no-member
    removal: SignedMeritRemoval = SignedMeritRemoval.fromSignedJSON(
        vectors["removal"])

    #Create and execute a Liver to handle the MeritRemoval.
    def sendMeritRemoval() -> None:
        #Send and verify the MeritRemoval.
        removalBytes: bytes = rpc.meros.signedElement(removal)

        sent: int = 0
        while True:
            if sent == 2:
                break

            msg: bytes = rpc.meros.sync.recv()
            if MessageType(msg[0]) == MessageType.TransactionRequest:
                rpc.meros.syncTransaction(transactions.txs[msg[1:33]])
                sent += 1
            else:
                raise TestError("Unexpected message sent: " +
                                msg.hex().upper())

        if removalBytes != rpc.meros.live.recv():
            raise TestError("Meros didn't send us the Merit Removal.")
        verifyMeritRemoval(rpc, 1, 1, removal.holder, True)

    #Verify the MeritRemoval and the Blockchain.
    def verify() -> None:
        verifyMeritRemoval(rpc, 1, 1, removal.holder, False)
        verifyBlockchain(rpc, Blockchain.fromJSON(vectors["blockchain"]))
        raise SuccessError(
            "MeritRemoval and Blockchain were properly handled.")

    Liver(rpc,
          vectors["blockchain"],
          transactions,
          callbacks={
              1: sendMeritRemoval,
              2: verify
          }).live()
예제 #16
0
def HundredFortySevenTest(rpc: RPC) -> None:
    file: IO[Any] = open("e2e/Vectors/Transactions/ClaimedMint.json", "r")
    vectors: Dict[str, Any] = json.loads(file.read())
    file.close()

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

    #Ed25519 keys.
    privKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32)
    pubKey: ed25519.VerifyingKey = privKey.get_verifying_key()

    #Grab the Claim hash,
    claim: bytes = merit.blockchain.blocks[-1].body.packets[0].hash

    #Create a Send which underflows.
    send: Send = Send(
        [(claim, 0)],
        [(pubKey.to_bytes(), 18446744073709551231),
         (pubKey.to_bytes(),
          385 + Claim.fromTransaction(transactions.txs[claim]).amount)])
    send.sign(privKey)
    send.beat(SpamFilter(3))

    #Custom function to send the last Block and verify it errors at the right place.
    def checkFail() -> None:
        #Send the Send.
        rpc.meros.liveTransaction(send)

        #Handle sync requests.
        while True:
            #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 an invalid Transaction."
                )
            except Exception:
                raise TestError("Meros sent a keep-alive.")

    #Create and execute a Liver.
    Liver(rpc, vectors["blockchain"], transactions, callbacks={
        12: checkFail
    }).live()
예제 #17
0
def HundredFortyTwoTest(rpc: RPC) -> None:
    file: IO[Any] = open(
        "e2e/Vectors/Consensus/Verification/HundredFortyTwo.json", "r")
    vectors: Dict[str, Any] = json.loads(file.read())
    file.close()

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

    #Function to verify the Transaction includes unarchived Merit before finalization.
    def verifyUnarchivedMerit() -> None:
        #Send the verification which won't be archived.
        if rpc.meros.signedElement(
                SignedVerification.fromSignedJSON(
                    vectors["verification"])) != rpc.meros.live.recv():
            raise TestError("Meros didn't send back the SignedVerification.")

        status: Dict[str, Any] = rpc.call("consensus", "getStatus",
                                          [vectors["transaction"]])
        if sorted(status["verifiers"]) != [0, 1]:
            raise TestError(
                "Status didn't include verifiers which have yet to be archived."
            )
        if status["merit"] != 7:
            raise TestError(
                "Status didn't include Merit which has yet to be archived.")

    #Function to verify the Transaction doesn't include unarchived Merit after finalization.
    def verifyArchivedMerit() -> None:
        status: Dict[str, Any] = rpc.call("consensus", "getStatus",
                                          [vectors["transaction"]])
        if status["verifiers"] != [0]:
            raise TestError(
                "Status included verifiers which were never archived.")
        if status["merit"] != 1:
            raise TestError("Status included Merit which was never archived.")

    #Create and execute a Liver.
    Liver(rpc,
          vectors["blockchain"],
          transactions,
          callbacks={
              7: verifyUnarchivedMerit,
              8: verifyArchivedMerit
          }).live()
예제 #18
0
def HundredThirtyFiveTest(
  rpc: RPC
) -> None:
  file: IO[Any] = open("e2e/Vectors/Consensus/MeritRemoval/HundredThirtyFive.json", "r")
  vectors: Dict[str, Any] = json.loads(file.read())
  file.close()

  #Datas.
  datas: List[Data] = [
    Data.fromJSON(vectors["datas"][0]),
    Data.fromJSON(vectors["datas"][1]),
    Data.fromJSON(vectors["datas"][2])
  ]

  #Transactions.
  transactions: Transactions = Transactions()
  for data in datas:
    transactions.add(data)

  #First MeritRemoval.
  mr: SignedMeritRemoval = SignedMeritRemoval.fromSignedJSON(vectors["removal"])

  def sendMeritRemoval() -> None:
    #Send the Datas.
    for data in datas:
      if rpc.meros.liveTransaction(data) != rpc.meros.live.recv():
        raise TestError("Meros didn't send us the Data.")

    #Send and verify the original MeritRemoval.
    if rpc.meros.signedElement(mr) != rpc.meros.live.recv():
      raise TestError("Meros didn't send us the Merit Removal.")
    verifyMeritRemoval(rpc, 1, 1, mr.holder, True)

  Liver(
    rpc,
    vectors["blockchain"],
    transactions,
    callbacks={
      1: sendMeritRemoval
    }
  ).live()
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()
예제 #20
0
def HundredThirtyThreeTest(
  rpc: RPC
) -> None:
  file: IO[Any] = open("e2e/Vectors/Consensus/MeritRemoval/HundredThirtyThree.json", "r")
  vectors: Dict[str, Any] = json.loads(file.read())
  file.close()

  #Datas.
  datas: List[Data] = [
    Data.fromJSON(vectors["datas"][0]),
    Data.fromJSON(vectors["datas"][1]),
    Data.fromJSON(vectors["datas"][2])
  ]

  #Transactions.
  transactions: Transactions = Transactions()
  for data in datas:
    transactions.add(data)

  def testBlockchain(
    i: int
  ) -> None:
    def sendMeritRemoval() -> None:
      #Send the Datas.
      for data in datas:
        if rpc.meros.liveTransaction(data) != rpc.meros.live.recv():
          raise TestError("Meros didn't send us the Data.")

      #Send the Block containing the modified Merit Removal.
      block: Block = Block.fromJSON(vectors["blockchains"][i][-1])
      rpc.meros.liveBlockHeader(block.header)

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

      #Handle sync requests.
      reqHash: bytes = bytes()
      while True:
        if blockBodySynced:
          #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 height is 2.
            #The genesis Block and the Block granting Merit.
            try:
              if rpc.call("merit", "getHeight") != 2:
                raise Exception()
            except Exception:
              raise TestError("Node added a Block containg a repeat MeritRemoval.")

            #Since the node didn't add the Block, raise SuccessError.
            raise SuccessError("Node didn't add a Block containing a repeat MeritRemoval.")
          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.
          blockBodySynced = True
          rpc.meros.blockBody(block)

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

    Liver(
      rpc,
      vectors["blockchains"][i],
      transactions,
      callbacks={
        1: sendMeritRemoval
      }
    ).live()

  with raises(SuccessError):
    for b in range(2):
      testBlockchain(b)
예제 #21
0
def PersonalSendTest(rpc: RPC) -> None:
    #Load the vectors.
    #Uses the WatchWallet test's vectors for the reasons noted above.
    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 the first address from outside the Wallet. First address is now funded.
        sendHash: bytes = createSend(
            rpc, claims[0], decodeAddress(rpc.call("personal", "getAddress")))

        #Send to the second address with all of the funds. Second address is now funded.
        #Tests send's minimal case (single input, no change).
        nextAddr: str = rpc.call("personal", "getAddress")
        sends: List[str] = [
            rpc.call(
                "personal", "send", {
                    "outputs": [{
                        "address": nextAddr,
                        "amount": str(claims[0].amount)
                    }]
                })
        ]
        checkSend(
            rpc, sends[-1], {
                "inputs": [{
                    "hash": sendHash.hex().upper(),
                    "nonce": 0
                }],
                "outputs": [{
                    "key": decodeAddress(nextAddr).hex().upper(),
                    "amount": str(claims[0].amount)
                }]
            })
        verify(rpc, bytes.fromhex(sends[-1]))

        #Send to the third address with some of the funds. Third and change addresses are now funded.
        #Tests send's capability to create a change output.
        mnemonic: str = rpc.call("personal", "getMnemonic")
        nextAddr = rpc.call("personal", "getAddress")
        sends.append(
            rpc.call(
                "personal", "send", {
                    "outputs": [{
                        "address": nextAddr,
                        "amount": str(claims[0].amount - 1)
                    }]
                }))
        checkSend(
            rpc, sends[-1], {
                "inputs": [{
                    "hash": sends[-2],
                    "nonce": 0
                }],
                "outputs":
                [{
                    "key": decodeAddress(nextAddr).hex().upper(),
                    "amount": str(claims[0].amount - 1)
                }, {
                    "key": getChangePublicKey(mnemonic, "", 0).hex().upper(),
                    "amount": "1"
                }]
            })
        verify(rpc, bytes.fromhex(sends[-1]))

        #Send all funds out of Wallet.
        #Tests MuSig signing and change UTXO detection.
        privKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32)
        pubKey: bytes = privKey.get_verifying_key()
        sends.append(
            rpc.call(
                "personal", "send", {
                    "outputs": [{
                        "address":
                        bech32_encode("mr",
                                      convertbits(bytes([0]) + pubKey, 8, 5)),
                        "amount":
                        str(claims[0].amount)
                    }]
                }))
        checkSend(
            rpc, sends[-1], {
                "inputs": [{
                    "hash": sends[-2],
                    "nonce": 0
                }, {
                    "hash": sends[-2],
                    "nonce": 1
                }],
                "outputs": [{
                    "key": pubKey.hex().upper(),
                    "amount": str(claims[0].amount)
                }]
            })
        verify(rpc, bytes.fromhex(sends[-1]))

        #Clear Wallet. Set a password this time around to make sure the password is properly carried.
        #Send two instances of funds to the first address.
        rpc.call("personal", "setWallet", {"password": "******"})
        mnemonic = rpc.call("personal", "getMnemonic")
        nodeKey: bytes = decodeAddress(rpc.call("personal", "getAddress"))
        send: Send = Send([(bytes.fromhex(sends[-1]), 0)],
                          [(nodeKey, claims[0].amount // 2),
                           (nodeKey, claims[0].amount // 2)])
        send.sign(Ristretto.SigningKey(b'\0' * 32))
        send.beat(SpamFilter(3))
        if rpc.meros.liveTransaction(send) != rpc.meros.live.recv():
            raise TestError("Meros didn't send back a Send.")
        verify(rpc, send.hash)
        sends = [send.hash.hex().upper()]

        #Send to self.
        #Tests send's capability to handle multiple UTXOs per key/lack of aggregation when all keys are the same/multiple output Sends.
        nextAddr = rpc.call("personal", "getAddress")
        changeKey: bytes = getChangePublicKey(mnemonic, "test", 0)
        sends.append(
            rpc.call(
                "personal", "send", {
                    "outputs": [{
                        "address": nextAddr,
                        "amount": str(claims[0].amount - 1)
                    }],
                    "password":
                    "******"
                }))
        checkSend(
            rpc, sends[-1], {
                "inputs": [{
                    "hash": sends[-2],
                    "nonce": 0
                }, {
                    "hash": sends[-2],
                    "nonce": 1
                }],
                "outputs": [{
                    "key": decodeAddress(nextAddr).hex().upper(),
                    "amount": str(claims[0].amount - 1)
                }, {
                    "key": changeKey.hex().upper(),
                    "amount": "1"
                }]
            })
        verify(rpc, bytes.fromhex(sends[-1]))

        #Externally send to the second/change address.
        #Enables entering multiple instances of each key into MuSig, which is significant as we originally only used the unique keys.
        sends.append(
            createSend(rpc, claims[1], decodeAddress(nextAddr)).hex().upper())
        sends.append(createSend(rpc, claims[2], changeKey).hex().upper())

        #Check personal_getUTXOs.
        utxos: List[Dict[str, Any]] = [{
            "hash": sends[-3],
            "nonce": 0,
            "address": nextAddr
        }, {
            "hash":
            sends[-3],
            "nonce":
            1,
            "address":
            bech32_encode("mr", convertbits(bytes([0]) + changeKey, 8, 5))
        }, {
            "hash": sends[-2],
            "nonce": 0,
            "address": nextAddr
        }, {
            "hash":
            sends[-1],
            "nonce":
            0,
            "address":
            bech32_encode("mr", convertbits(bytes([0]) + changeKey, 8, 5))
        }]
        if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos):
            raise TestError("personal_getUTXOs was incorrect.")
        for utxo in utxos:
            del utxo["address"]

        #Send to any address with all funds minus one.
        #Test MuSig signing, multiple inputs per key on account chains, change output creation to the next change key...
        sends.append(
            rpc.call(
                "personal", "send", {
                    "outputs": [{
                        "address":
                        nextAddr,
                        "amount":
                        str(claims[0].amount + claims[1].amount +
                            claims[2].amount - 1)
                    }],
                    "password":
                    "******"
                }))
        checkSend(
            rpc, sends[-1], {
                "inputs":
                utxos,
                "outputs": [{
                    "key":
                    decodeAddress(nextAddr).hex().upper(),
                    "amount":
                    str(claims[0].amount + claims[1].amount +
                        claims[2].amount - 1)
                }, {
                    "key":
                    getChangePublicKey(mnemonic, "test", 1).hex().upper(),
                    "amount":
                    "1"
                }]
            })
        verify(rpc, bytes.fromhex(sends[-1]))

        #Mine a Block so we can reboot the node without losing data.
        blsPrivKey: PrivateKey = PrivateKey(
            bytes.fromhex(rpc.call("personal", "getMeritHolderKey")))
        for _ in range(6):
            template: Dict[str, Any] = rpc.call(
                "merit", "getBlockTemplate",
                {"miner": blsPrivKey.toPublicKey().serialize().hex()})
            proof: int = -1
            tempHash: bytes = bytes()
            tempSignature: 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(
                    bytes.fromhex(template["header"]) +
                    proof.to_bytes(4, "little"))
                tempSignature = blsPrivKey.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()
                })

        #Reboot the node and verify it still tracks the same change address.
        #Also reload the Wallet and verify it still tracks the same change address.
        #Really should be part of address discovery; we just have the opportunity right here.
        #Due to the timing of how the codebase was developed, and a personal frustration for how long this has taken...
        rpc.quit()
        sleep(3)
        rpc.meros = Meros(rpc.meros.db, rpc.meros.tcp, rpc.meros.rpc)
        if rpc.call("personal", "getTransactionTemplate",
                    {"outputs": [{
                        "address": nextAddr,
                        "amount": "1"
                    }]})["outputs"][1]["key"] != getChangePublicKey(
                        mnemonic, "test", 2).hex().upper():
            raise TestError(
                "Rebooting the node caused the WalletDB to stop tracking the next change address."
            )
        rpc.call("personal", "setAccount", rpc.call("personal", "getAccount"))
        if rpc.call("personal", "getTransactionTemplate",
                    {"outputs": [{
                        "address": nextAddr,
                        "amount": "1"
                    }]})["outputs"][1]["key"] != getChangePublicKey(
                        mnemonic, "test", 2).hex().upper():
            raise TestError(
                "Reloading the Wallet caused the WalletDB to stop tracking the next change address."
            )

        raise SuccessError()

    #Use a late enough block we can instantly verify transactions.
    with raises(SuccessError):
        Liver(rpc, vectors["blockchain"], transactions, {50: test}).live()
예제 #22
0
def BeatenTest(rpc: RPC) -> None:
    vectors: Dict[str, Any]
    with open("e2e/Vectors/Consensus/Beaten.json", "r") as file:
        vectors = json.loads(file.read())
    sends: List[Send] = [Send.fromJSON(send) for send in vectors["sends"]]
    verif: SignedVerification = SignedVerification.fromSignedJSON(
        vectors["verification"])

    #Used to get the Block Template.
    blsPubKey: str = PrivateKey(0).toPublicKey().serialize().hex()

    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.")

    #Sanity check to verify the Block Template contains the Verification.
    def verifyTemplate() -> None:
        if bytes.fromhex(
                rpc.call("merit", "getBlockTemplate",
                         {"miner": blsPubKey
                          })["header"])[36:68] != BlockHeader.createContents(
                              [VerificationPacket(sends[2].hash, [1])]):
            raise TestError(
                "Meros didn't add a SignedVerification to the Block Template.")

    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",
                            {"hash": 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",
                      {"hash": sends[2].hash.hex()})["verifiers"] != [0])
                or (bytes.fromhex(
                    rpc.call("merit", "getBlockTemplate",
                             {"miner": 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",
                     {"hash": 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", "publishTransaction", {
                    "type": "Send",
                    "transaction": 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)

    Liver(rpc,
          vectors["blockchain"],
          Transactions.fromJSON(vectors["transactions"]),
          callbacks={
              42: sendSends,
              43: verifyTemplate,
              48: verifyBeaten
          }).live()
예제 #23
0
def PublishTransactionTest(rpc: RPC) -> None:
    privKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32)
    pubKey: bytes = privKey.get_verifying_key()

    sentToKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\1' * 32)

    sendFilter: SpamFilter = SpamFilter(3)
    dataFilter: SpamFilter = SpamFilter(5)

    vectors: Dict[str, Any]
    with open("e2e/Vectors/Transactions/ClaimedMint.json", "r") as file:
        vectors = json.loads(file.read())

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

    if len(transactions.txs) != 1:
        raise Exception("Transactions DAG doesn't have just the Claim.")
    claim: Claim = Claim.fromTransaction(next(iter(transactions.txs.values())))

    send: Send = Send([(claim.hash, 0)],
                      [(sentToKey.get_verifying_key(), claim.amount)])
    send.sign(privKey)
    send.beat(sendFilter)

    data: Data = Data(bytes(32), pubKey)
    data.sign(privKey)
    data.beat(dataFilter)

    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, claim.amount)])
        sendSentWithoutWork.sign(sentToKey)
        sendSentWithoutWork.beat(sendFilter)

        dataSentWithoutWork: Data = Data(bytes(32),
                                         sentToKey.get_verifying_key())
        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())
        invalidData.sign(sentToKey)
        sig: bytes = invalidData.signature

        newData: bytearray = bytearray(invalidData.data)
        newData[-1] = newData[-1] ^ 1
        #Reconstruct to rehash.
        invalidData = Data(bytes(32), bytes(newData))
        invalidData.signature = sig
        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."
                )

    Liver(rpc, vectors["blockchain"][:-1], transactions, {
        7: publishAndVerify
    }).live()
예제 #24
0
def TGUImmediatelyTest(rpc: RPC) -> 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))

    vectors: Dict[str, Any]
    with open("e2e/Vectors/RPC/Transactions/GetUTXOs.json", "r") as file:
        vectors = json.loads(file.read())
    transactions: Transactions = Transactions.fromJSON(vectors["transactions"])

    send: Send = Send.fromJSON(vectors["send"])
    spendingSend: Send = Send.fromJSON(vectors["spendingSend"])

    def start() -> None:
        #Send the 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."
            )

        #Immediately spend it.
        if rpc.meros.liveTransaction(spendingSend) != rpc.meros.live.recv():
            raise TestError("Meros didn't broadcast back a Send.")
        if rpc.call("transactions", "getUTXOs", {"address": address}) != []:
            raise TestError(
                "Meros didn't consider a Transaction's inputs as spent.")

        #Verify the Send and make sure it's not considered as a valid UTXO.
        verify(rpc, send.hash)
        if rpc.call("transactions", "getUTXOs", {"address": address}) != []:
            raise TestError(
                "Meros considered a just confirmed Transaction with a spender's outputs as UTXOs."
            )

    def verified() -> None:
        #Verify the spender and verify the state is unchanged.
        verify(rpc, spendingSend.hash)
        if rpc.call("transactions", "getUTXOs", {"address": address}) != []:
            raise TestError(
                "Meros didn't consider a verified Transaction's inputs as spent."
            )

    def finalizedSend() -> None:
        #Sanity check the spending TX has yet to also finalize.
        if rpc.call("consensus", "getStatus",
                    {"hash": spendingSend.hash.hex()})["finalized"]:
            raise Exception(
                "Test meant to only finalize the first Send, not both.")

        #Verify the state is unchanged.
        if rpc.call("transactions", "getUTXOs", {"address": address}) != []:
            raise TestError(
                "Meros didn't consider a verified Transaction's inputs as spent after the input finalized."
            )

    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."
            )

    Liver(rpc, vectors["blockchain"], transactions, {
        50: start,
        51: verified,
        56: finalizedSend,
        57: finalizedSpendingSend
    }).live()
예제 #25
0
def SameInputTest(rpc: RPC) -> None:
    file: IO[Any] = open("e2e/Vectors/Transactions/SameInput/Claim.json", "r")
    vectors: Dict[str, Any] = json.loads(file.read())
    file.close()

    merit: Merit = Merit.fromJSON(vectors["blockchain"])
    transactions: Transactions = Transactions.fromJSON(vectors["transactions"])

    #Custom function to send the last Block and verify it errors at the right place.
    def checkFail() -> None:
        #Grab the Block.
        block: Block = merit.blockchain.blocks[8]

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

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

                rpc.meros.blockBody(block)

            elif MessageType(msg[0]) == MessageType.SketchHashRequests:
                if msg[1:33] != 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: bytes = msg[1:33]
                if reqHash not in transactions.txs:
                    raise TestError(
                        "Meros asked for a non-existent Transaction.")

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

                #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 an invalid Transaction."
                    )
                except Exception:
                    raise TestError("Meros sent a keep-alive.")

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

    with raises(SuccessError):
        Liver(rpc,
              vectors["blockchain"],
              transactions,
              callbacks={
                  7: checkFail
              }).live()
예제 #26
0
파일: Competing.py 프로젝트: vporton/Meros
from e2e.Classes.Merit.Block import Block
from e2e.Classes.Merit.Merit import Blockchain

#Ed25519 lib.
import ed25519

#Blake2b standard function.
from hashlib import blake2b

#JSON standard lib.
import json

cmFile: IO[Any] = open("e2e/Vectors/Transactions/ClaimedMint.json", "r")
cmVectors: Dict[str, Any] = json.loads(cmFile.read())
#Transactions.
transactions: Transactions = Transactions.fromJSON(cmVectors["transactions"])
#Blockchain.
blockchain: Blockchain = Blockchain.fromJSON(cmVectors["blockchain"])
cmFile.close()

#Spam Filter.
sendFilter: SpamFilter = SpamFilter(3)

#Ed25519 keys.
edPrivKey: ed25519.SigningKey = ed25519.SigningKey(b'\0' * 32)
edPubKeys: List[ed25519.VerifyingKey] = [
    edPrivKey.get_verifying_key(),
    ed25519.SigningKey(b'\1' * 32).get_verifying_key()
]

#BLS keys.
예제 #27
0
def AddressRecoveryTest(rpc: RPC) -> None:
    mnemonic: str = rpc.call("personal", "getMnemonic")

    vectors: Dict[str, Any]
    with open("e2e/Vectors/RPC/Transactions/GetUTXOs.json", "r") as file:
        vectors = json.loads(file.read())
    transactions: Transactions = Transactions.fromJSON(vectors["transactions"])

    def test() -> None:
        #Send to the new address and get the next address.
        dest: str = rpc.call("personal", "getAddress")
        last: Send = createSend(rpc, Claim.fromJSON(vectors["newerMintClaim"]),
                                dest)

        utxos: List[Dict[str, Any]] = rpc.call("personal", "getUTXOs")
        if utxos != [{
                "address": dest,
                "hash": last.hash.hex().upper(),
                "nonce": 0
        }]:
            raise TestError(
                "personal_getUTXOs didn't return the correct UTXOs.")

        #Set a different mnemonic to verify the tracked addresses is cleared.
        rpc.call("personal", "setWallet")
        if rpc.call("personal", "getUTXOs") != []:
            raise TestError(
                "Setting a new Mnemonic didn't clear the tracked addresses.")

        #Reload the Mnemonic and verify the UTXOs are correct.
        rpc.call("personal", "setWallet", {"mnemonic": mnemonic})
        if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos):
            #This error message points out how no addresses are really being discovered yet; this is account zero's address.
            #That said, if the test started at the next address, there'd be a gap.
            #As that's an extra factor, this is tested before gaps are.
            raise TestError("Meros didn't recover the very first address.")

        #Now send to the next address and check accuracy.
        dest = rpc.call("personal", "getAddress")
        last = createSend(rpc, last, dest)
        utxos.append({
            "address": dest,
            "hash": last.hash.hex().upper(),
            "nonce": 0
        })
        if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos):
            raise TestError("Meros didn't track an implicitly gotten address.")
        rpc.call("personal", "setWallet", {"mnemonic": mnemonic})
        if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos):
            raise TestError(
                "Meros didn't recover the first address after the initial address."
            )

        #Send funds to the address after the next address; tests a gap when discovering addresses.
        last = createSend(rpc, last, getAddress(mnemonic, "", 3))
        if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos):
            raise TestError(
                "Meros magically recognized UTXOs as belonging to this Wallet or someone implemented an address cache."
            )
        utxos.append({
            "address": getAddress(mnemonic, "", 3),
            "hash": last.hash.hex().upper(),
            "nonce": 0
        })
        rpc.call("personal", "setWallet", {"mnemonic": mnemonic})
        if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos):
            raise TestError(
                "Meros didn't discover a used address in the Wallet when there was a gap."
            )

        #Finally, anything 10+ unused addresses out shouldn't be recovered.
        last = createSend(rpc, last, getAddress(mnemonic, "", 14))
        rpc.call("personal", "setWallet", {"mnemonic": mnemonic})
        if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos):
            raise TestError(
                "Meros recovered an address's UTXOs despite it being 10 unused addresses out."
            )

        #Explicitly generating this address should start tracking it though.
        rpc.call("personal", "getAddress",
                 {"index": getIndex(mnemonic, "", 14)})
        utxos.append({
            "address": getAddress(mnemonic, "", 14),
            "hash": last.hash.hex().upper(),
            "nonce": 0
        })
        if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(utxos):
            raise TestError(
                "personal_getUTXOs didn't track an address explicitly indexed."
            )

        #If asked for an address, Meros should return the 5th (skip 4).
        #It's the first unused address AFTER used addresss EXCEPT ones explicitly requested.
        #This can, in the future, be juwst the first unused address/include ones explicitly requested (see DerivationTest for commentary on that).
        #This is really meant to ensure consistent behavior until we properly decide otherwise.
        if rpc.call("personal", "getAddress") != getAddress(mnemonic, "", 4):
            raise TestError(
                "Meros didn't return the next unused address (with conditions; see comment)."
            )

        #Mine a Block to flush the Transactions and Verifications to disk.
        sleep(65)
        rpc.meros.liveConnect(Blockchain().blocks[0].header.hash)
        mineBlock(rpc)

        #Existing values used to test getAddress/getUTXOs consistency.
        #The former is thoroughly tested elsewhere, making it quite redundant.
        existing: Dict[str, Any] = {
            "getAddress": rpc.call("personal", "getAddress"),
            "getUTXOs": rpc.call("personal", "getUTXOs")
        }

        #Reboot the node and verify consistency.
        rpc.quit()
        sleep(3)
        rpc.meros = Meros(rpc.meros.db, rpc.meros.tcp, rpc.meros.rpc)
        if sortUTXOs(rpc.call("personal", "getUTXOs")) != sortUTXOs(
                existing["getUTXOs"]):
            raise TestError(
                "Rebooting the node caused the WalletDB to improperly reload UTXOs."
            )
        if rpc.call("personal", "getAddress") != existing["getAddress"]:
            raise TestError(
                "Rebooting the node caused the WalletDB to improperly reload the next address."
            )

        #Used so Liver doesn't run its own post-test checks.
        #Since we added our own Blocks, those will fail.
        raise SuccessError()

    #Used so we don't have to write a sync loop.
    with raises(SuccessError):
        Liver(rpc, vectors["blockchain"], transactions, {50: test}).live()
예제 #28
0
def BroadcastTest(
  rpc: RPC
) -> None:
  vectors: Dict[str, Any]
  with open("e2e/Vectors/Transactions/ClaimedMint.json", "r") as file:
    vectors = json.loads(file.read())
  transactions: Transactions = Transactions.fromJSON(vectors["transactions"])
  claim: Claim = Claim.fromTransaction(iter(transactions.txs.values()).__next__())
  mint: bytes = claim.inputs[0][0]

  def test() -> None:
    #Data.
    dataHash: str = rpc.call("personal", "data", {"data": "abc"})
    #Read the initial Data.
    if MessageType(rpc.meros.live.recv()[0]) != MessageType.Data:
      raise TestError("Meros didn't broadcast the initial Data.")
    #Read the actual Data.
    serializedData: bytes = rpc.meros.live.recv()
    if serializedData[1:] != Data.fromJSON(rpc.call("transactions", "getTransaction", {"hash": dataHash})).serialize():
      raise TestError("Meros didn't broadcast the created Data.")
    res: Any = rpc.call("network", "broadcast", {"transaction": dataHash})
    if not (isinstance(res, bool) and res):
      raise TestError("Broadcast didn't return true.")
    if rpc.meros.live.recv() != serializedData:
      raise TestError("Meros didn't broadcast a Transaction when told to.")

    #Block.
    header: BlockHeader = Block.fromJSON(vectors["blockchain"][0]).header
    rpc.call("network", "broadcast", {"block": header.hash.hex()})
    if rpc.meros.live.recv() != (MessageType.BlockHeader.toByte() + header.serialize()):
      raise TestError("Meros didn't broadcast the Blockheader.")

    #Data and Block.
    rpc.call("network", "broadcast", {"block": header.hash.hex(), "transaction": dataHash})
    if rpc.meros.live.recv() != serializedData:
      raise TestError("Meros didn't broadcast a Transaction when told to.")
    if rpc.meros.live.recv() != (MessageType.BlockHeader.toByte() + header.serialize()):
      raise TestError("Meros didn't broadcast the Blockheader.")

    #Non-existent Transaction.
    try:
      rpc.call("network", "broadcast", {"transaction": bytes(32).hex()})
      raise TestError()
    except TestError as e:
      if str(e) != "-2 Transaction not found.":
        raise TestError("Meros didn't error when told to broadcast a non-existent Transaction.")

    #Non-existent Block.
    try:
      rpc.call("network", "broadcast", {"block": bytes(32).hex()})
      raise TestError()
    except TestError as e:
      if str(e) != "-2 Block not found.":
        raise TestError("Meros didn't error when told to broadcast a non-existent Block.")

    #Mint.
    try:
      rpc.call("network", "broadcast", {"transaction": mint.hex()})
      raise TestError()
    except TestError as e:
      if str(e) != "-3 Transaction is a Mint.":
        raise TestError("Meros didn't error when told to broadcast a Mint.")

    #Genesis Block.
    try:
      rpc.call("network", "broadcast", {"block": Blockchain().blocks[0].header.hash.hex()})
      raise TestError()
    except TestError as e:
      if str(e) != "-3 Block is the genesis Block.":
        raise TestError("Meros didn't error when told to broadcast the genesis Block.")

  #Create and execute a Liver.
  Liver(rpc, vectors["blockchain"], transactions, callbacks={8: test}).live()
예제 #29
0
#Ed25519 lib.
import ed25519

#Blake2b standard function.
from hashlib import blake2b

#JSON standard lib.
import json

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

#Transactions.
transactions: Transactions = Transactions()
#Merit.
merit: Merit = Merit()

#SpamFilter.
dataFilter: SpamFilter = SpamFilter(5)

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

#BLS keys.
blsPrivKey: PrivateKey = PrivateKey(blake2b(b'\0', digest_size=32).digest())
blsPubKey: PublicKey = blsPrivKey.toPublicKey()

#Add 1 Blank Block.
예제 #30
0
def WatchWalletTest(rpc: RPC) -> None:
    #Keys to send funds to later.
    keys: List[bytes] = [
        Ristretto.SigningKey(i.to_bytes(1, "little") * 32).get_verifying_key()
        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.")

        #Also test balance getting.
        if rpc.call("personal", "getBalance") != str(
                sum([
                    int(
                        rpc.call("transactions", "getTransaction",
                                 {"hash": send.hex()})["outputs"][0]["amount"])
                    for send in sends[:2]
                ])):
            raise TestError("WatchWallet Meros couldn't get its balance.")

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

        #Again test the balance.
        if rpc.call("personal", "getBalance") != str(
                sum([
                    int(
                        rpc.call("transactions", "getTransaction",
                                 {"hash": send.hex()})["outputs"][0]["amount"])
                    for send in sends[:3]
                ])):
            raise TestError("WatchWallet Meros couldn't get its balance.")

        #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()