Exemple #1
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()
Exemple #2
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()
Exemple #3
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()
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()
Exemple #5
0
    def restOfTest() -> None:
        #Move expected into scope.
        expected: str = getAddress(mnemonic, password, 0)

        #Send to the new address, then call getAddress again. Verify a new address appears.
        last: Send = createSend(
            rpc,
            Claim.fromTransaction(iter(transactions.txs.values()).__next__()),
            expected)
        hashes: List[bytes] = [last.hash]

        expected = getAddress(mnemonic, password, 1)
        if rpc.call("personal", "getAddress") != expected:
            raise TestError(
                "Meros didn't move to the next address once the existing one was used."
            )

        #Send to the new unused address, spending the funds before calling getAddress again.
        #Checks address usage isn't defined as having an UTXO, yet rather any TXO.
        #Also confirm the spending TX with full finalization before checking.
        #Ensures the TXO isn't unspent by any definition.
        last = createSend(rpc, last, expected)
        hashes.append(last.hash)

        #Spending TX.
        send: Send = Send([(hashes[-1], 0)], [(bytes(32), 1)])
        send.signature = ed.sign(b"MEROS" + send.hash,
                                 getPrivateKey(mnemonic, password, 1))
        send.beat(SpamFilter(3))
        if rpc.meros.liveTransaction(send) != rpc.meros.live.recv():
            raise TestError("Meros didn't broadcast back the spending Send.")
        hashes.append(send.hash)

        #In order to finalize, we need to mine 6 Blocks once this Transaction and its parent have Verifications.
        for txHash in hashes:
            sv: SignedVerification = SignedVerification(txHash)
            sv.sign(0, PrivateKey(0))
            if rpc.meros.signedElement(sv) != rpc.meros.live.recv():
                raise TestError("Meros didn't broadcast back a Verification.")

        #Close the sockets while we mine.
        rpc.meros.live.connection.close()
        rpc.meros.sync.connection.close()

        #Mine these to the Wallet on the node so we can test getMeritHolderNick.
        privKey: PrivateKey = PrivateKey(
            bytes.fromhex(rpc.call("personal", "getMeritHolderKey")))
        blockchain: Blockchain = Blockchain.fromJSON(vectors["blockchain"])
        for _ in range(6):
            template: Dict[str, Any] = rpc.call(
                "merit", "getBlockTemplate",
                {"miner": privKey.toPublicKey().serialize().hex()})
            proof: int = -1
            tempHash: bytes = bytes()
            tempSignature: bytes = bytes()
            while ((proof == -1)
                   or ((int.from_bytes(tempHash, "little") *
                        (blockchain.difficulty() * 11 // 10)) > int.from_bytes(
                            bytes.fromhex("FF" * 32), "little"))):
                proof += 1
                tempHash = RandomX(
                    bytes.fromhex(template["header"]) +
                    proof.to_bytes(4, "little"))
                tempSignature = privKey.sign(tempHash).serialize()
                tempHash = RandomX(tempHash + tempSignature)

            rpc.call(
                "merit", "publishBlock", {
                    "id":
                    template["id"],
                    "header":
                    template["header"] + proof.to_bytes(4, "little").hex() +
                    tempSignature.hex()
                })
            blockchain.add(
                Block.fromJSON(
                    rpc.call("merit", "getBlock",
                             {"block": len(blockchain.blocks)})))

        #Verify a new address is returned.
        expected = getAddress(mnemonic, password, 2)
        if rpc.call("personal", "getAddress") != expected:
            raise TestError(
                "Meros didn't move to the next address once the existing one was used."
            )

        #Get a new address after sending to the address after it.
        #Use both, and then call getAddress.
        #getAddress should detect X is used, move to Y, detect Y is used, and move to Z.
        #It shouldn't assume the next address after an used address is unused.
        #Actually has two Ys as one iteration of the code only ran for the next address; not all future addresses.

        #Send to the next next addresses.
        for i in range(2):
            addy: str = getAddress(mnemonic, password, 3 + i)

            #Reopen the sockets. This isn't done outside of the loop due to the time deriving the final address can take.
            #Due to how slow the reference Python code is, it is necessary to redo the socket connections.
            sleep(65)
            rpc.meros.liveConnect(Blockchain().blocks[0].header.hash)
            rpc.meros.syncConnect(Blockchain().blocks[0].header.hash)

            last = createSend(rpc, last, addy)
            if MessageType(rpc.meros.live.recv()
                           [0]) != MessageType.SignedVerification:
                raise TestError(
                    "Meros didn't create and broadcast a SignedVerification for this Send."
                )

            if i == 0:
                #Close them again.
                rpc.meros.live.connection.close()
                rpc.meros.sync.connection.close()

        #Verify getAddress returns the existing next address.
        if rpc.call("personal", "getAddress") != expected:
            raise TestError(
                "Sending to the address after this address caused Meros to consider this address used."
            )

        #Send to the next address.
        last = createSend(rpc, last, expected)
        if MessageType(
                rpc.meros.live.recv()[0]) != MessageType.SignedVerification:
            raise TestError(
                "Meros didn't create and broadcast a SignedVerification for this Send."
            )

        #Verify getAddress returns the address after the next next addresses.
        expected = getAddress(mnemonic, password, 5)
        if rpc.call("personal", "getAddress") != expected:
            raise TestError(
                "Meros didn't return the correct next address after using multiple addresses in a row."
            )

        #Now that we have mined a Block as part of this test, ensure the Merit Holder nick is set.
        if rpc.call("personal", "getMeritHolderNick") != 1:
            raise TestError(
                "Merit Holder nick wasn't made available despite having one.")

        #Sanity check off Mnemonic.
        if rpc.call("personal", "getMnemonic") != mnemonic:
            raise TestError("getMnemonic didn't return the correct Mnemonic.")

        #Existing values used to test getMnemonic/getMeritHolderKey/getMeritHolderNick/getAccount/getAddress consistency.
        existing: Dict[str, Any] = {
            #Should be equal to the mnemonic variable, which is verified in a check above.
            "getMnemonic": rpc.call("personal", "getMnemonic"),
            "getMeritHolderKey": rpc.call("personal", "getMeritHolderKey"),
            "getMeritHolderNick": rpc.call("personal", "getMeritHolderNick"),
            "getAccount": rpc.call("personal", "getAccount"),
            #Should be equal to expected, which is also verified in a check above.
            "getAddress": rpc.call("personal", "getAddress")
        }

        #Set a new seed and verify the Merit Holder nick is cleared.
        rpc.call("personal", "setWallet")
        try:
            rpc.call("personal", "getMeritHolderNick")
            raise TestError("")
        except TestError as e:
            if str(
                    e
            ) != "-2 Wallet doesn't have a Merit Holder nickname assigned.":
                raise TestError(
                    "getMeritHolderNick returned something or an unexpected error when a new Mnemonic was set."
                )

        #Set back the old seed and verify consistency.
        rpc.call("personal", "setWallet", {
            "mnemonic": mnemonic,
            "password": password
        })
        for method in existing:
            if rpc.call("personal", method) != existing[method]:
                raise TestError(
                    "Setting an old seed caused the WalletDB to improperly reload."
                )

        #Verify calling getAddress returns the expected address.
        if rpc.call("personal", "getAddress") != expected:
            raise TestError(
                "Meros returned an address that wasn't next after reloading the seed."
            )

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

        #Used so Liver doesn't run its own post-test checks.
        #Since we added our own Blocks, those will fail.
        raise SuccessError()
Exemple #6
0
def GetTransactionTest(rpc: RPC) -> None:
    privKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32)
    pubKey: bytes = privKey.get_verifying_key()

    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)],
        [(Ristretto.SigningKey(b'\1' * 32).get_verifying_key(), claim.amount)])
    send.sign(privKey)
    send.beat(sendFilter)

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

    def sendAndVerify() -> None:
        rpc.meros.liveTransaction(send)
        rpc.meros.liveTransaction(data)
        rpc.meros.live.recv()
        rpc.meros.live.recv()

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

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

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

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

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

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

        #Invalid argument; should cause a ParamError
        #This is still a hex value
        try:
            rpc.call("transactions", "getTransaction",
                     {"hash": "00" + data.hash.hex()}, False)
            raise TestError(
                "Meros didn't error when we asked for a 33-byte hex value.")
        except TestError as e:
            if str(e) != "-32602 Invalid params.":
                raise TestError(
                    "getTransaction didn't raise on invalid parameters.")

    Liver(rpc, vectors["blockchain"], transactions, {8: sendAndVerify}).live()