Example #1
0
  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))

    #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()
    def syncUnknown() -> None:
        claim: Claim = Claim([(merit.mints[0], 0)], pubKey.to_bytes())
        claim.sign(PrivateKey(0))

        #Create a series of Sends, forming a diamond.
        #Cross sendB and sendC to actually force this to work in an ordered fashion to pass.
        sendA: Send = Send([(claim.hash, 0)],
                           [(pubKey.to_bytes(), claim.amount // 2),
                            (pubKey.to_bytes(), claim.amount // 2)])
        sendB: Send = Send([(sendA.hash, 0)],
                           [(pubKey.to_bytes(), sendA.outputs[0][1] // 2),
                            (pubKey.to_bytes(), sendA.outputs[0][1] // 2)])
        sendC: Send = Send(
            [(sendA.hash, 1), (sendB.hash, 1)],
            [(pubKey.to_bytes(), sendA.outputs[1][1] + sendB.outputs[1][1])])
        sendD: Send = Send([(sendB.hash, 0), (sendC.hash, 0)],
                           [(pubKey.to_bytes(), claim.amount)])
        for send in [sendA, sendB, sendC, sendD]:
            send.sign(privKey)
            send.beat(sendFilter)

        #Send the tail of the diamond, which should cause an ordered top-down sync.
        sent: bytes = rpc.meros.liveTransaction(sendD)
        for tx in [
                sendC, sendB, sendA, claim, sendA, claim, sendB, sendA, claim
        ]:
            if rpc.meros.sync.recv() != (
                    MessageType.TransactionRequest.toByte() + tx.hash):
                raise TestError("Meros didn't request one of the inputs.")
            rpc.meros.syncTransaction(tx)
        if rpc.meros.live.recv() != sent:
            raise TestError("Meros didn't broadcast the Send.")

        #Do the same for a few Data Transactions.
        datas: List[Data] = [Data(bytes(32), pubKey.to_bytes())]
        datas.append(Data(datas[-1].hash, bytes(1)))
        datas.append(Data(datas[-1].hash, bytes(1)))
        for data in datas:
            data.sign(privKey)
            data.beat(dataFilter)
Example #3
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()
Example #4
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()
Example #5
0
from e2e.Classes.Merit.Merit import Block, Merit

from e2e.Vectors.Generation.PrototypeChain import PrototypeBlock, PrototypeChain

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

transactions: Transactions = Transactions()
sendFilter: SpamFilter = SpamFilter(3)

proto: PrototypeChain = PrototypeChain(40, keepUnlocked=True)
proto.add(1)
merit: Merit = Merit.fromJSON(proto.toJSON())

#Create a Claim.
claim: Claim = Claim([(merit.mints[-1], 0)], edPubKey)
claim.sign(PrivateKey(0))
transactions.add(claim)

merit.add(
    PrototypeBlock(merit.blockchain.blocks[-1].header.time + 1200,
                   packets=[VerificationPacket(claim.hash, list(range(2)))
                            ]).finish(0, merit))

sends: List[Send] = [
    #Transaction which will win.
    Send([(claim.hash, 0)], [(bytes(32), claim.amount)]),
    #Transaction which will be beaten.
    Send([(claim.hash, 0)], [(edPubKey, claim.amount // 2),
                             (edPubKey, claim.amount // 2)])
]
Example #6
0
from e2e.Classes.Merit.Merit import Merit

from e2e.Vectors.Generation.PrototypeChain import PrototypeBlock, PrototypeChain

proto: PrototypeChain = PrototypeChain(7)
proto.add(1)
proto.add(2)
proto.add(3)
proto.add(4)
proto.add(elements=[SendDifficulty(1, 0, 2), SendDifficulty(1, 0, 4)])
merit: Merit = Merit.fromJSON(proto.toJSON())
transactions: Transactions = Transactions()

claim: Claim = Claim([(merit.mints[-1], 0)],
                     ed25519.SigningKey(b'\0' *
                                        32).get_verifying_key().to_bytes())
claim.sign(PrivateKey(0))
transactions.add(claim)

send: Send = Send([(claim.hash, 0)], [(ed25519.SigningKey(
    b'\1' * 32).get_verifying_key().to_bytes(), claim.amount)])
send.sign(ed25519.SigningKey(b'\0' * 32))
send.beat(SpamFilter(3))
transactions.add(send)

datas: List[Data] = [
    Data(bytes(32),
         ed25519.SigningKey(b'\0' * 32).get_verifying_key().to_bytes())
]
for _ in range(4):
Example #7
0
    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()
Example #8
0
from e2e.Classes.Merit.Merit import Merit

from e2e.Vectors.Generation.PrototypeChain import PrototypeBlock, PrototypeChain

proto: PrototypeChain = PrototypeChain(7)
proto.add(1)
proto.add(2)
proto.add(3)
proto.add(4)
proto.add(elements=[SendDifficulty(1, 0, 2), SendDifficulty(1, 0, 4)])
merit: Merit = Merit.fromJSON(proto.toJSON())
transactions: Transactions = Transactions()

claim: Claim = Claim(
  [(merit.mints[-1], 0)],
  Ristretto.SigningKey(b'\0' * 32).get_verifying_key()
)
claim.sign(PrivateKey(0))
transactions.add(claim)

send: Send = Send(
  [(claim.hash, 0)],
  [(Ristretto.SigningKey(b'\1' * 32).get_verifying_key(), claim.amount)]
)
send.sign(Ristretto.SigningKey(b'\0' * 32))
send.beat(SpamFilter(3))
transactions.add(send)

datas: List[Data] = [
  Data(bytes(32), Ristretto.SigningKey(b'\0' * 32).get_verifying_key())
]
Example #9
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()
Example #10
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()
Example #11
0
    def restOfTest() -> None:
        #Move expected into scope.
        expected: str = getAddress(mnemonic, password, 0)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        #Used so Liver doesn't run its own post-test checks.
        #Since we added our own Blocks, those will fail.
        raise SuccessError()
Example #12
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()
Example #13
0
from e2e.Classes.Consensus.SpamFilter import SpamFilter
from e2e.Classes.Consensus.VerificationPacket import VerificationPacket
from e2e.Classes.Merit.Merit import Merit

from e2e.Vectors.Generation.PrototypeChain import PrototypeBlock, PrototypeChain

merit: Merit = Merit.fromJSON(PrototypeChain(47).toJSON())
transactions: Transactions = Transactions()

privKey: Ristretto.SigningKey = Ristretto.SigningKey(b'\0' * 32)
pubKey: bytes = privKey.get_verifying_key()

recipientPriv: Ristretto.SigningKey = Ristretto.SigningKey(b'\1' * 32)
recipientPub: bytes = recipientPriv.get_verifying_key()

olderClaim: Claim = Claim([(merit.mints[-1], 0)], pubKey)
olderClaim.sign(PrivateKey(0))
transactions.add(olderClaim)

merit.add(
  PrototypeBlock(
    merit.blockchain.blocks[-1].header.time + 1200,
    packets=[VerificationPacket(olderClaim.hash, [0])]
  ).finish(0, merit)
)
merit.add(PrototypeBlock(merit.blockchain.blocks[-1].header.time + 1200).finish(0, merit))

newerClaim: Claim = Claim([(merit.mints[-1], 0)], pubKey)
newerClaim.sign(PrivateKey(0))
transactions.add(newerClaim)
Example #14
0
def GetBlockTest(rpc: RPC) -> None:
    blockchain: Blockchain
    claim: Claim
    send: Send
    datas: List[Data]

    txKey: Callable[[Dict[str, Any]], str] = lambda tx: tx["hash"]

    def verify() -> None:
        for b in range(len(blockchain.blocks)):
            block: Dict[str, Any] = rpc.call(
                "merit", "getBlock",
                {"block": blockchain.blocks[b].header.hash.hex().upper()},
                False)
            if rpc.call("merit", "getBlock", {"block": b}, False) != block:
                raise TestError(
                    "Meros reported different Blocks depending on if nonce/hash indexing."
                )

            #Python doesn't keep track of the removals.
            #That said, they should all be empty except for the last one.
            if b != (len(blockchain.blocks) - 1):
                if block["removals"] != []:
                    raise TestError("Meros reported the Block had removals.")
            del block["removals"]

            if blockchain.blocks[b].toJSON() != block:
                raise TestError(
                    "Meros's JSON serialization of Blocks differs from Python's."
                )

        #Test the key serialization of the first Block.
        #The final Block uses a nick, hence the value in this.
        if rpc.call("merit", "getBlock", {"block": 1},
                    False)["header"]["miner"] != PrivateKey(
                        0).toPublicKey().serialize().hex().upper():
            raise TestError("Meros didn't serialize a miner's key properly.")

        #Manually test the final, and most complex, block.
        final: Dict[str, Any] = rpc.call("merit", "getBlock",
                                         {"block": len(blockchain.blocks) - 1},
                                         False)
        final["transactions"].sort(key=txKey)
        final["removals"].sort()
        if final != {
                "hash":
                blockchain.blocks[-1].header.hash.hex().upper(),
                "header": {
                    "version":
                    blockchain.blocks[-1].header.version,
                    "last":
                    blockchain.blocks[-1].header.last.hex().upper(),
                    "contents":
                    blockchain.blocks[-1].header.contents.hex().upper(),
                    "packets":
                    blockchain.blocks[-1].header.packetsQuantity,
                    "sketchSalt":
                    blockchain.blocks[-1].header.sketchSalt.hex().upper(),
                    "sketchCheck":
                    blockchain.blocks[-1].header.sketchCheck.hex().upper(),
                    "miner":
                    blockchain.blocks[-1].header.minerKey.hex().upper()
                    if blockchain.blocks[-1].header.newMiner else
                    blockchain.blocks[-1].header.minerNick,
                    "time":
                    blockchain.blocks[-1].header.time,
                    "proof":
                    blockchain.blocks[-1].header.proof,
                    "signature":
                    blockchain.blocks[-1].header.signature.hex().upper()
                },
                "transactions":
                sorted([{
                    "hash": claim.hash.hex().upper(),
                    "holders": [0]
                }, {
                    "hash": send.hash.hex().upper(),
                    "holders": [0, 1, 2]
                }, {
                    "hash": datas[0].hash.hex().upper(),
                    "holders": [0, 2]
                }, {
                    "hash": datas[1].hash.hex().upper(),
                    "holders": [0, 1, 3]
                }, {
                    "hash": datas[2].hash.hex().upper(),
                    "holders": [0, 1, 2, 3, 4]
                }, {
                    "hash": datas[3].hash.hex().upper(),
                    "holders": [0, 1, 2, 3]
                }],
                       key=txKey),
                "elements": [
                    {
                        "descendant": "DataDifficulty",
                        "holder": 3,
                        "nonce": 0,
                        "difficulty": 8
                    },
                    {
                        "descendant": "SendDifficulty",
                        "holder": 0,
                        "nonce": 0,
                        "difficulty": 1
                    },
                    {
                        "descendant": "DataDifficulty",
                        "holder": 3,
                        "nonce": 0,
                        "difficulty": 4
                    },
                    {
                        "descendant": "DataDifficulty",
                        "holder": 4,
                        "nonce": 2,
                        "difficulty": 1
                    },
                    {
                        "descendant": "SendDifficulty",
                        "holder": 4,
                        "nonce": 1,
                        "difficulty": 3
                    },
                    {
                        "descendant": "SendDifficulty",
                        "holder": 2,
                        "nonce": 1,
                        "difficulty": 2
                    },
                    {
                        "descendant": "DataDifficulty",
                        "holder": 0,
                        "nonce": 0,
                        "difficulty": 7
                    },
                ],
                "removals": [0, 3],
                "aggregate":
                blockchain.blocks[-1].body.aggregate.serialize().hex().upper()
        }:
            raise TestError("Final Block wasn't correct.")

        #Test invalid calls.
        try:
            rpc.call("merit", "getBlock", {"block": 100}, False)
            raise Exception("")
        except Exception as e:
            if str(e) != "-2 Block not found.":
                raise TestError(
                    "getBlock didn't error when we used a non-existent nonce.")

        try:
            rpc.call("merit", "getBlock", {"block": -5}, False)
            raise Exception("")
        except Exception as e:
            if str(e) != "-32602 Invalid params.":
                raise TestError(
                    "getBlock didn't error when we used a negative (signed) integer for a nonce."
                )

        try:
            rpc.call(
                "merit", "getBlock", {
                    "block":
                    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
                }, False)
            raise Exception("")
        except Exception as e:
            if str(e) != "-2 Block not found.":
                raise TestError(
                    "getBlock didn't error when we used a non-existent hash.")

        try:
            rpc.call("merit", "getBlock", {"block": ""}, False)
            raise Exception("")
        except Exception as e:
            if str(e) != "-32602 Invalid params.":
                raise TestError(
                    "getBlock didn't error when we used an invalid hash.")

    with open("e2e/Vectors/RPC/Merit/GetBlock.json", "r") as file:
        vectors: Dict[str, Any] = json.loads(file.read())
        blockchain = Blockchain.fromJSON(vectors["blockchain"])
        claim = Claim.fromJSON(vectors["claim"])
        send = Send.fromJSON(vectors["send"])
        datas = [Data.fromJSON(data) for data in vectors["datas"]]
        transactions: Transactions = Transactions.fromJSON(
            vectors["transactions"])
        Liver(rpc, vectors["blockchain"], transactions, {
            (len(blockchain.blocks) - 1): verify
        }).live()