DEV Community

smith@mcf.rocks
smith@mcf.rocks

Posted on

Dusk - GraphQL RPC

Explore the Dusk node's RPC capabilities, especially GraphQL... in under 10 minutes.

Use the setup as described previously to run a local node, regenerate the initial state, the file examples/genesis.toml has been edited to give 100,000 coins to our wallet.

# cd rusk
# rm /tmp/example.state
# cargo r --release -p rusk -- recovery-state \
  --init examples/genesis.toml \
  -o /tmp/example.state
Enter fullscreen mode Exit fullscreen mode

Start the node:

# export DUSK_CONSENSUS_KEYS_PASS=password
# ./rusk/target/release/rusk \
  -s /tmp/example.state \
  --http-listen-addr 0.0.0.0:4321
Enter fullscreen mode Exit fullscreen mode

We will use the same wallet as before, but the wallet software has local caching, so after resetting state we need to clear that cache and restart the wallet:

# rm ~/.dusk/rusk-wallet/cache
# ./wallet-cli/target/release/rusk-wallet \
  --state http://127.0.0.1:4321 \
  --prover http://127.0.0.1:4321
Enter fullscreen mode Exit fullscreen mode

When restarting the wallet there will be some delay while it reads the chain to find the notes associated with the wallet.

Make an RPC call to the node, like this:

# curl -X POST http://127.0.0.1:4321/2/Chain \
  -H 'Content-Type: application/json' \
  -d '{"topic":"info","data":""}'
> {"bootstrapping_nodes":[],"chain_id":null,"kadcast_address":"127.0.0.1:9000","version":"0.7.0","version_build":"0.7.0 (16cd2d58 2023-12-31)"}
Enter fullscreen mode Exit fullscreen mode

This call returns some basic information including version and build. There are some parts that are noteworthy; the digit in the url (in this case 2) can be 1, 2, or 3 (contract, host, debugger); the final part of the url, in the event the digit is 2 can be "chain" or "rusk". If "chain" then the topic passed can be any of "info", "gas", "alive_nodes", "propagate_tx", or "gql". The data field contains arguments required for the call (for "info", there are no arguments)...

Where the topic is "gql" we are passing a Graph Query Language call. This is what we are interested in.

Passing the topic without data returns the schema:

# curl -s -X POST http://127.0.0.1:4321/2/Chain \
  -d '{"topic":"gql","data":""}' \
  -H 'Content-Type: application/json'

type Block {
    header: Header!
    transactions: [SpentTransaction!]!
    reward: Int!
    fees: Int!
    gasSpent: Int!
}

type CallData {
    contractId: String!
    fnName: String!
    data: String!
}

type Header {
    version: Int!
    height: Int!
    prevBlockHash: String!
    timestamp: Int!
    hash: String!
    stateHash: String!
    generatorBlsPubkey: String!
    txRoot: String!
    gasLimit: Int!
    seed: String!
    iteration: Int!
}

type Query {
    block(height: Float, hash: String): Block
    tx(hash: String!): SpentTransaction
    transactions(last: Int!): [SpentTransaction!]!
    blockTxs(last: Int, range: [Int!], contract: String): [SpentTransaction!]!
    blocks(last: Int, range: [Int!]): [Block!]!
    mempoolTxs: [Transaction!]!
    mempoolTx(hash: String!): Transaction
}

type SpentTransaction {
    tx: Transaction!
    err: String
    gasSpent: Int!
    blockHash: String!
    blockHeight: Int!
    blockTimestamp: Int!
    id: String!
    raw: String!
}

type Transaction {
    raw: String!
    id: String!
    gasLimit: Int!
    gasPrice: Int!
    callData: CallData
}

schema {
    query: Query
}
Enter fullscreen mode Exit fullscreen mode

You can see the queries that are available. For example:

transactions(last: Int!): [SpentTransaction!]!
Enter fullscreen mode Exit fullscreen mode

This query returns the last N transactions. We can test this by running the query before any transactions are made:

# curl -X POST http://127.0.0.1:4321/2/Chain \
  -d '{"topic":"gql","data":"query {transactions(last: 10){id}} "}' \
  -H 'Content-Type: application/json'
> {"transactions":[]}
Enter fullscreen mode Exit fullscreen mode

Now do two transfers from the wallet to some other address, for example 3uVEjrfzdhjN1a5o48SpiVU9AFzF5o9FZ9mFR9GfeV7U3nfDpDGfVrLJveFPokHsQiXUjjrQHYZSWNz4PY2UQ4xU, note the transaction hashes:

Transfer 100 - txid 8ffa359e732ccad500b16996662a6edb2033d3ecfa868cf68cbadc4ca4e7760d

Transfer 200 - txid
95f58e4b8e7fc5658540cd01064ae93ab07d34c1a91d5a848d32384a3951b100

And finally, execute the query again:

# curl -X POST http://127.0.0.1:4321/2/Chain \
  -d '{"topic":"gql","data":"query {transactions(last: 10){id}} "}' \
  -H 'Content-Type: application/json'
> {"transactions":[{"id":"95f58e4b8e7fc5658540cd01064ae93ab07d34c1a91d5a848d32384a3951b100"},{"id":"8ffa359e732ccad500b16996662a6edb2033d3ecfa868cf68cbadc4ca4e7760d"}]}
Enter fullscreen mode Exit fullscreen mode

The transactions can be seen, newest first.

At this time the node is ephemeral, so if it is restarted, this information is no longer available. The node should soon be runnable in "persistent" mode, in which case this would not apply.

You can see the query part of the curl call, this string is in Graph Query Language, I have hidden the rest of the curl call using a small script:

# cat ./run_query

#!/usr/bin/perl -w
my $cmd = q#curl -s -X POST http://127.0.0.1:4321/2/Chain -d '{"topic":"gql","data":"#;
$cmd .= $ARGV[0];
$cmd .= q#"}' -H 'Content-Type: application/json'#;
print `$cmd`;
Enter fullscreen mode Exit fullscreen mode

From this point on, I just give that string, not the entire call, which remains constant. Also we can use the Linux command jq to format the json output.

The query just executed returns "SpentTransaction" objects, we returned only the "id" field. It is only allowed to return fields, not objects.

Here we return the Block Height and Block hash that the most recent transaction was mined:

# ./run_query "query {transactions(last: 1){blockHeight, blockHash}}"  | jq
{
  "transactions": [
    {
      "blockHash": "0925fc9ff94d445ffecead0e0bfeda70ca184e74d101fbc41a7cc4e5d4412060",
      "blockHeight": 184
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

From the schema, we see objects can include nested objects. Here we return attributes from the enclosed Transaction object; the Gas Limit and Gas Price:

# ./run_query "query {transactions(last: 1){tx{gasLimit, gasPrice}}}"  | jq
{
  "transactions": [
    {
      "tx": {
        "gasLimit": 500000000,
        "gasPrice": 1
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

NB: the Gas Price is not in Dusk, but in the smallest fraction of the token, called Lux.

Let's try a different query. Plugging in the Block Height returned previously, we know what Block Hash to expect:

# ./run_query "query {block(height: 184){header{hash}}}" | jq
{
  "block": {
    "header": {
      "hash": "0925fc9ff94d445ffecead0e0bfeda70ca184e74d101fbc41a7cc4e5d4412060"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Or using the Block Hash to get the block reward:

# ./run_query 'query {block(hash: \"0925fc9ff94d445ffecead0e0bfeda70ca184e74d101fbc41a7cc4e5d4412060\"){reward}}' | jq
{
  "block": {
    "reward": 16000000000
  }
}
Enter fullscreen mode Exit fullscreen mode

Or fees:

# ./run_query 'query {block(hash: \"0925fc9ff94d445ffecead0e0bfeda70ca184e74d101fbc41a7cc4e5d4412060\"){fees}}' | jq
{
  "block": {
    "fees": 283239
  }
}
Enter fullscreen mode Exit fullscreen mode

Or the Ids of transactions in a block:

# ./run_query "query {block(height: 184){transactions{id}}}" | jq
{
  "block": {
    "transactions": [
      {
        "id": "95f58e4b8e7fc5658540cd01064ae93ab07d34c1a91d5a848d32384a3951b100"
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

That's all folks.

Top comments (0)