Running PulseChain Ethereum fork full node on Akashnet

WARNING: this write-up was written for PulseChain v1, make sure to adjust it to the changes for PulseChain v2 in https://gitlab.com/pulsechaincom/pulsechain-testnet

This write-up will guide you to deploy PulseChain Ethereum-fork full node on Akashnet.

IMPORTANT: this will not connect you to the real PulseChain nodes yet, the bootnodes, network configs (config.toml, genesis.json) are missing. Wait for the complete instructions provided by the PulseChain devs here https://gitlab.com/pulsechaincom/go-pulse/-/blob/16178c59/README.md#a-full-node-on-the-pulsechain-testnet
But before getting that, you can still learn to deploy it and connect to the Ethereum network.
Make sure to deploy geth on a provider with the SSD. One can't run geth on the HDD, because geth stores data on a disk in a horrible way which is outlined in their FAQ https://geth.ethereum.org/docs/faq


I used Ubuntu 20.04.2 LTS Linux distribution and the following software:
- akash: v0.12.1 (to work with the Akashnet);
- geth: v0.2.0-6abf18d00 (to deploy it on the Akashnet);

Meet your new friends:
- Akashnet is a decentralized cloud of future;
- PulseChain is Energy Efficient, Cheaper, Faster, Fee-Burning Ethereum fork.

The idea is simple - leverage the Akashnet decloud in order to deploy PulseChain container.

It's up to 86% cheaper to run an instance on Akashnet than on Amazon AWS https://akashlytics.com/price-compare (at the time I wrote this write-up)

Ok, I'm not good at talking much, so to save your and my time let's jump straight to the CLI.

1. Install akash client

AKASH_NET="https://raw.githubusercontent.com/ovrclk/net/master/mainnet"
AKASH_VERSION="$(curl -s "$AKASH_NET/version.txt")"
curl https://raw.githubusercontent.com/ovrclk/akash/master/godownloader.sh | sh -s -- "v$AKASH_VERSION"
Please also install jq tool as it will be used in this write-up.

2. Create your akash wallet

akash keys add default

This command will output the mnemonic phrase, make sure to save it in a safe place.
It will also output your address, in my case the address was akash1h24fljt7p0nh82cq0za0uhsct3sfwsfu9w3c9h.

export AKASH_ACCOUNT_ADDRESS=akash1h24fljt7p0nh82cq0za0uhsct3sfwsfu9w3c9h
More about the Akash wallet https://docs.akash.network/guides/wallet

3. Fund your akash wallet

You will need about 10 AKT (Akash Token) to get you started.
You can purchase them at one of the exchanges mentioned here https://akash.network/token/

You can query the balance of your wallet

export AKASH_NET="https://raw.githubusercontent.com/ovrclk/net/master/mainnet"
export AKASH_NODE="$(curl -s "$AKASH_NET/rpc-nodes.txt" | shuf -n 1)"

akash \
  --node "$AKASH_NODE" \
  query bank balances "$AKASH_ACCOUNT_ADDRESS"
Denomination: 1 akt = 1000000 uakt (akt*10^6), or you can add -o json | jq -r '(.balances[0].amount | tonumber / pow(10; 6))' on the command line ;-)
I'm using production akashnet-2 Akash network. For more on different Akash networks please refer to https://docs.akash.network/guides/version

4. Create certificate file

To create a deployment, a certificate must first be created. To do this, run:

export AKASH_CHAIN_ID="$(curl -s "$AKASH_NET/chain-id.txt")"
akash tx cert create client \
  --from default \
  --chain-id $AKASH_CHAIN_ID \
  --node $AKASH_NODE \
  --gas-prices="0.025uakt" --gas="auto" --gas-adjustment=1.15
certificate needs to be created only once per account and can be used across all deployments. More about the certificate https://docs.akash.network/design/mtls

5. Prepare the deployment manifest file

Create a file named go-pulse-geth.yml with the following contents:

---
version: "2.0"

services:
  geth:
    image: andrey01/go-pulse-geth:0.2.0-6abf18d00
    expose:
      ## 8545/tcp RPC port
      ## SECURITY RISK: someone can transfer all your funds out of your wallet,
      ##                in case if you unlock your wallet there!
      ##- port: 8545
      ##  as: 8545
      ##  proto: tcp
      ##  to:
      ##    - global: true
      ### 8546/tcp WS port
      ##- port: 8546
      ##  as: 8546
      ##  proto: tcp
      ##  to:
      ##    - global: true
      # 30303 TCP and UDP, used by the P2P protocol running the network
      - port: 30303
        as: 30303
        proto: tcp
        to:
          - global: true
      - port: 30303
        as: 30303
        proto: udp
        to:
          - global: true
    args:
      - "--syncmode=fast"
      - "--cache=2048" # make sure you have requested more RAM for the container.
      #- "--mine"
      #- "--miner.etherbase=0xabcdef...."
      ##- "--rpc"
      ##- "--rpcapi=admin,eth,net,web3"
      ##- "--rpcaddr=0.0.0.0"
      ##- "--rpcport=8545"
      ##- "--rpcvhosts=*"
      ##- "--ws"
      ##- "--wsaddr=0.0.0.0"
      ##- "--wsport=8546"
      ##- "--wsorigins=*"

profiles:
  compute:
    geth:
      resources:
        cpu:
          units: 2
        memory:
          size: 4Gi
        storage:
          size: 1024Gi
  placement:
    akash:
      #signedBy:
      #  anyOf:
      #    # - "akash1365yvmc4s7awdyj3n2sav7xfx76adc6dnmlx63" ## AKASH verified hosts only
      pricing:
        geth: 
          denom: uakt
          amount: 500

deployment:
  geth:
    akash:
      profile: geth
      count: 1

Pricing - Note that there are bids from multiple different providers. In this case, both providers happen to be willing to accept a price of 1 uAKT. This means that the lease can be created using 1 uAKT or 0.000001 AKT per block to execute the container.
pricing sets the limits so providers know how to bid on your deployment. i.e. amount of uakt per block (each block happens every 6 seconds) you are willing to pay.
So in case with 1uakt, you are going to be paying:
((1*((60/6)*60*24*30.436875))/10^6)*2.68 = $1.17 per month having 1akt worth $2.68 as of 25th July 2021.

signedBy is a "validation" thing, after a provider is approved by akash they get signed by akash to ensure security and reliability. If you don't care about which provider will be running your container, then just keep it commented.

More on SDL (Stack Definition Language) used to write the Akash manifest https://docs.akash.network/documentation/sdl

You can use my container andrey01/go-pulse-geth:0.2.0-6abf18d00, or you can build your own, that's how you can do it:

$ git clone git@gitlab.com:pulsechaincom/go-pulse.git
$ cd go-pulse
$ git log --oneline -1 
6abf18d00 (HEAD -> master, upstream/master) Merge branch 'readme' into 'master'
$ docker build -t go-pulse-geth:0.2.0-6abf18d00 .
$ docker tag go-pulse-geth:0.2.0-6abf18d00 andrey01/go-pulse-geth:0.2.0-6abf18d00
$ docker push andrey01/go-pulse-geth:0.2.0-6abf18d00

6. Create your deployment

akash tx deployment create go-pulse-geth.yml \
  --from default \
  --node $AKASH_NODE \
  --chain-id $AKASH_CHAIN_ID \
  --gas-prices="0.025uakt" --gas="auto" --gas-adjustment=1.15

If this command ends with Error: RPC error -32603 - Internal error: timed out waiting for tx to be included in a block then no worries, you can query the akash blockchain in order to find your deployment ID:

$ akash query txs \
  --events "message.sender=$AKASH_ACCOUNT_ADDRESS&message.action=create-deployment" \
  --page 1 --limit 999999 \
  | jq -r '.txs[] | [ .tx.body.messages[].id[] ] | @csv' | \
  tail -5
"akash1h24fljt7p0nh82cq0za0uhsct3sfwsfu9w3c9h","1933977"

Query the deployment:

I'm explicitly not "export"'ing the following 3 "SEQ" variables as to avoid 'failed to execute message; message index: 0: Deployment closed' error on 'akash tx deployment create' later. If you get this error, then simply unset AKASH_DSEQ.
$ AKASH_DSEQ=1933977
$ AKASH_GSEQ=1
$ AKASH_OSEQ=1

$ akash query deployment get \
  --owner $AKASH_ACCOUNT_ADDRESS \
  --node $AKASH_NODE \
  --dseq $AKASH_DSEQ

deployment:
  created_at: "1933981"
  deployment_id:
    dseq: "1933977"
    owner: akash1h24fljt7p0nh82cq0za0uhsct3sfwsfu9w3c9h
  state: active
  version: Zqi3K8lyZ/XLGwe2gM9cho6VzAoKnO72FgnXlIVF/NA=
escrow_account:
  balance:
    amount: "5000000"
    denom: uakt
  id:
    scope: deployment
    xid: akash1h24fljt7p0nh82cq0za0uhsct3sfwsfu9w3c9h/1933977
  owner: akash1h24fljt7p0nh82cq0za0uhsct3sfwsfu9w3c9h
  settled_at: "1934322"
  state: open
  transferred:
    amount: "0"
    denom: uakt
groups:
- created_at: "1933981"
  group_id:
    dseq: "1933977"
    gseq: 1
    owner: akash1h24fljt7p0nh82cq0za0uhsct3sfwsfu9w3c9h
  group_spec:
    name: akash
    requirements:
      attributes: []
      signed_by:
        all_of: []
        any_of: []
    resources:
    - count: 1
      price:
        amount: "50"
        denom: uakt
      resources:
        cpu:
          attributes: []
          units:
            val: "2000"
        endpoints:
        - kind: RANDOM_PORT
        - kind: RANDOM_PORT
        memory:
          attributes: []
          quantity:
            val: "4294967296"
        storage:
          attributes: []
          quantity:
            val: "1099511627776"
  state: open

As you may have noticed the 5 AKT are now under the escrow_account and your akash wallet balance was reduced by that value. These tokens are going to be used by this deployment as soon as you create a lease. And they will be returned as soon as you close the lease / deployment (minus few of them which were used for the lease itself).

More on deployment https://docs.akash.network/guides/deploy

7. Query the market for bids

Now that we want to actually run our deployment, we will need to see what market offers, accept the bid we feel comfortable with, create the lease and send manifest of our deployment to get it up and running.

$ akash query market bid list \
  --owner=$AKASH_ACCOUNT_ADDRESS \
  --node $AKASH_NODE \
  --dseq $AKASH_DSEQ \
  --state open

bids:
- bid:
    bid_id:
      dseq: "1933977"
      gseq: 1
      oseq: 1
      owner: akash1h24fljt7p0nh82cq0za0uhsct3sfwsfu9w3c9h
      provider: akash1nxq8gmsw2vlz3m68qvyvcf3kh6q269ajvqw6y0
    created_at: "1933982"
    price:
      amount: "17"
      denom: uakt
    state: open
  escrow_account:
    balance:
      amount: "50000000"
      denom: uakt
    id:
      scope: bid
      xid: akash1h24fljt7p0nh82cq0za0uhsct3sfwsfu9w3c9h/1933977/1/1/akash1nxq8gmsw2vlz3m68qvyvcf3kh6q269ajvqw6y0
    owner: akash1nxq8gmsw2vlz3m68qvyvcf3kh6q269ajvqw6y0
    settled_at: "1933982"
    state: open
    transferred:
      amount: "0"
      denom: uakt
pagination:
  next_key: null
  total: "0"

Set the AKASH_PROVIDER environment variable to the one you picked from the available market bids:

AKASH_PROVIDER=akash1nxq8gmsw2vlz3m68qvyvcf3kh6q269ajvqw6y0

If akash query market bid list isn't showing any bids, make sure you unset AKASH_PROVIDER environment variable!


8. Create lease / Accept the bid

Create a lease for the bid from the chosen provider above by running:

akash tx market lease create \
  --chain-id $AKASH_CHAIN_ID \
  --node $AKASH_NODE \
  --owner $AKASH_ACCOUNT_ADDRESS \
  --dseq $AKASH_DSEQ \
  --gseq $AKASH_GSEQ \
  --oseq $AKASH_OSEQ \
  --provider $AKASH_PROVIDER \
  --from default \
  --gas-prices="0.025uakt" --gas="auto" --gas-adjustment=1.15
Please note that once the lease is created, the provider will begin debiting your deployment's escrow account, even if you have not completed the deployment process by uploading the manifest in the following step.

9. Send the manifest to deploy PulseChain node

Now, to get your PulseChain deployment started, you need to send your manifest to the provider:

akash provider send-manifest go-pulse-geth.yml \
  --node $AKASH_NODE \
  --dseq $AKASH_DSEQ \
  --provider $AKASH_PROVIDER \
  --from default

After few moments, you will be able to see your deployment:

$ akash provider lease-status \
  --node $AKASH_NODE \
  --dseq $AKASH_DSEQ \
  --provider $AKASH_PROVIDER \
  --from default
{
  "services": {
    "geth": {
      "name": "geth",
      "available": 1,
      "total": 1,
      "uris": null,
      "observed_generation": 1,
      "replicas": 1,
      "updated_replicas": 1,
      "ready_replicas": 1,
      "available_replicas": 1
    }
  },
  "forwarded_ports": {
    "geth": [
      {
        "host": "provider.akash.nixaid.com",
        "port": 30303,
        "externalPort": 30396,
        "proto": "TCP",
        "available": 1,
        "name": "geth"
      },
      {
        "host": "provider.akash.nixaid.com",
        "port": 30303,
        "externalPort": 31438,
        "proto": "UDP",
        "available": 1,
        "name": "geth"
      }
    ]
  }
}

The only caveat is that it is not possible to export non-http/https (80/443) TCP ports, so-called nodePort ports directly as-is, that's why you are seeing the actual ports are the externalPort.
The Akashnet providers use Kubernetes under the hood for actually running your containers. The Kubernetes control plane allocates a port from a range specified by --service-node-port-range flag (default: 30000-32767)(nodePort)
This means you should always check to what ports your Akash deployment gets assigned by the provider's Kubernetes.
If you really need the exact ports to be exposed, you can negotiate with the Akash provider or use some load balancer / reverse proxy (i.e. nginx / haprorxy / traefik) to forward to these ports.

10. See the logs of your PulseChain container on Akashnet

You can see your PulseChain node's mnemonic seed phrase there.

$ akash \
  --node "$AKASH_NODE" \
  provider lease-logs \
  --dseq "$AKASH_DSEQ" \
  --gseq "$AKASH_GSEQ" \
  --oseq "$AKASH_OSEQ" \
  --provider "$AKASH_PROVIDER" \
  --from default \
  --follow
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:19.890] Starting Geth on Ethereum mainnet... 
[geth-67b86dfcbb-t8ppz] WARN [07-25|16:16:19.891] Sanitizing cache to Go's GC limits       provided=2048 updated=682
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:19.892] Maximum peer count                       ETH=50 LES=0 total=50
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:19.892] Smartcard socket not found, disabling    err="stat /run/pcscd/pcscd.comm: no such file or directory"
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:19.895] Starting peer-to-peer node               instance=Geth/v1.0.6-6abf18d0-20210720/linux-amd64/go1.15.5
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:19.895] Allocated trie memory caches             clean=102.00MiB dirty=170.00MiB
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:19.895] Allocated cache and file handles         database=/root/.ethereum/geth/chaindata cache=341.00MiB handles=524288
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.594] Opened ancient database                  database=/root/.ethereum/geth/chaindata/ancient
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.595] Writing default main-net genesis block 
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.822] Persisted trie from memory database      nodes=12356 size=1.78MiB time=40.929224ms gcnodes=0 gcsize=0.00B gctime=0s livenodes=1 livesize=0.00B
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.822] Initialised chain configuration          config="{ChainID: 1 Homestead: 1150000 DAO: 1920000 DAOSupport: true EIP150: 2463000 EIP155: 2675000 EIP158: 2675000 Byzantium: 4370000 Constantinople: 7280000 Petersburg: 7280000 Istanbul: 9069000, Muir Glacier: 9200000, Ramanujan: <nil>, Niels: <nil>, MirrorSync: <nil>, PrimordialPulse: <nil>, Engine: ethash}"
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.823] Disk storage enabled for ethash caches   dir=/root/.ethereum/geth/ethash count=3
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.823] Disk storage enabled for ethash DAGs     dir=/root/.ethash               count=2
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.823] Initialising Ethereum protocol           versions="[65 64 63]" network=1 dbversion=<nil>
[geth-67b86dfcbb-t8ppz] WARN [07-25|16:16:20.823] Upgrade blockchain database version      from=<nil> to=7
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.823] Loaded most recent local header          number=0 hash=d4e567…cb8fa3 td=17179869184 age=52y3mo3w
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.823] Loaded most recent local full block      number=0 hash=d4e567…cb8fa3 td=17179869184 age=52y3mo3w
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.823] Loaded most recent local fast block      number=0 hash=d4e567…cb8fa3 td=17179869184 age=52y3mo3w
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.846] Regenerated local transaction journal    transactions=0 accounts=0
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.851] Allocated fast sync bloom                size=272.00MiB
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:20.921] Initialized fast sync bloom              items=12356 errorrate=0.000 elapsed=69.368ms
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:21.001] New local node record                    seq=1 id=151c6e3553cee58f ip=127.0.0.1 udp=30303 tcp=30303
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:21.003] Started P2P networking                   self=enode://8648620db3f7924aa5ebb6ee9282e487b5fe72f8c60a29de3a48dac715a54096d71dfe761b1c495d15f0f699b8774c8174b0a08b616ba57d9ab6579528c4c5ab@127.0.0.1:30303
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:21.010] IPC endpoint opened                      url=/root/.ethereum/geth.ipc
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:31.004] Looking for peers                        peercount=0 tried=3 static=0
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:41.008] Looking for peers                        peercount=0 tried=3 static=0
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:16:51.100] Looking for peers                        peercount=0 tried=4 static=0
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:01.103] Looking for peers                        peercount=0 tried=5 static=0
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:01.132] Block synchronisation started 
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:01.444] Imported new state entries               count=273 elapsed=2.247µs  processed=273 pending=4369 retry=0 duplicate=0 unexpected=0
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:01.487] Imported new state entries               count=384 elapsed=2.152µs  processed=657 pending=10513 retry=0 duplicate=0 unexpected=0
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:01.528] Imported new state entries               count=384 elapsed=1.701µs  processed=1041 pending=16657 retry=0 duplicate=0 unexpected=0
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:01.591] Imported new state entries               count=384 elapsed=2.128µs  processed=1425 pending=22795 retry=0 duplicate=0 unexpected=0
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:01.756] Imported new state entries               count=768 elapsed=868.707µs processed=2193 pending=25017 retry=0 duplicate=0 unexpected=0
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:01.933] Imported new state entries               count=768 elapsed=2.808ms   processed=2961 pending=24601 retry=0 duplicate=0 unexpected=0
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:02.335] Imported new state entries               count=768 elapsed=7.270ms   processed=3729 pending=24391 retry=0 duplicate=0 unexpected=0
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:02.358] Imported new block headers               count=192 elapsed=865.475ms number=192 hash=723899…123390 age=6y3w6d
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:02.361] Migrated ancient blocks                  count=1   elapsed=325.756µs
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:02.441] Imported new block headers               count=192 elapsed=79.563ms  number=384 hash=d3d5d5…c79cf3 age=6y3w6d
[geth-67b86dfcbb-t8ppz] INFO [07-25|16:17:02.929] Imported new state entries               count=1536 elapsed=15.404ms  processed=5265 pending=24216 retry=0 duplicate=0 unexpected=0
...

In case your geth isn't finding the peers, refer to https://geth.ethereum.org/docs/interface/peer-to-peer
They explain how peers are found and how to tackle the common problems with connectivity.

11. Terminating the deployment

When you are done testing, you can terminate  your deployment.

akash tx deployment close \
  --node $AKASH_NODE \
  --chain-id $AKASH_CHAIN_ID \
  --dseq $AKASH_DSEQ \
  --owner $AKASH_ACCOUNT_ADDRESS \
  --from default \
  --gas-prices="0.025uakt" --gas="auto" --gas-adjustment=1.15

12. Depositing more AKT for the deployment

If you are happy with your deployment and would not want it to stop one day, you can deposit more AKT tokens for the escrow account:

akash tx deployment deposit \
  --from default \
  --chain-id $AKASH_CHAIN_ID \
  --node $AKASH_NODE 1000000uakt \
  --dseq $AKASH_DSEQ \
  --gas-prices="0.025uakt" --gas="auto" --gas-adjustment=1.15