GSN Starter Kit hands on experience

Hey @nventuro, I am glad this is helpful. Today I hope it will be last day trying to implement GSN. It’s taking several days at this point. Almost there!

Regarding the hardcoded values, I totally understand why did you go with this solution. It makes a lot of sense. I think though it could be more flexible given the gsn-helpers aim to be general gsn helpers that can be used to control the gsn from any custom network. At the end I deployed my own RelayHub using the tabookey truffle migration script. Then I replaced the ABI (should be identical), and the contract address inside the gsn-helpers/src/data.js.relayHub.address. I think that hardcoded address was causing issues at some stage.

I found another place where the hub address is hardcoded, I think it’s on purpose as well right?:

57

Yea although I really don’t like JS and i favour Go by substantial margin :), writing the server in Node, in this context, seems like a great idea. Thanks for sharing with me. Will keep eye on the new version to update the Lightstreams stack later on.

I would like to ask you for little help because I am super stack but I think I did already majority of actions:

  • I deployed a RelayHub
  • I staked/funded it
  • I deployed and initialized a contract implementing GSN (recipient)
  • I funded the previously deployed recipient

How do I inject the GsnProvider into Web3? From your Readme:

  // Register the recipient in the hub
  await fundRecipient(this.web3, { recipient: this.recipient.options.address });
  
  // Create gsn provider and plug it into the recipient
  const gsnProvider = new GSNProvider(PROVIDER_URL);
  this.recipient.setProvider(gsnProvider);

But I don’t see the GSNProvider to be defined anywhere in the whole project. Going to check what magic does the gsn-starter-kit does in the code.

1 Like

Update, seems like I found it in the another repo with self explanatory name: gsn-provider

import { GSNProvider, GSNDevProvider } from '@openzeppelin/gsn-provider';
1 Like

Hm anyway it doesn’t work. this.recipient is not just the Ethereum contract instance itself. It seems to be decorated by something bc when I call: this.recipient.setProvider(gsnProvider); I get a error ofc: TypeError: factory.setProvider is not a function.

Investigating more how the starter-kit is doing it. It’s using:

  const context = useWeb3Network('http://127.0.0.1:8545', {
    gsn: {
      dev: false,
      signKey,
    },
  });

in the App.js but that’s a hidden dependency on React:

const { useWeb3Network } = require('@openzeppelin/network');
  const context = useWeb3Network('http://127.0.0.1:8545', {
    gsn: {
      dev: false,
      signKey,
    },
  });

Error: Cannot find module 'react’

Even if I would install React, I can’t use the function from Truffle test:

it('should deploy an ACL from user without funds', async () => {
    const context = useWeb3Network('http://127.0.0.1:8545', {
      gsn: {
        dev: false,
      },
    });
    web3 = context.lib;

    const tx = await factory.newACL(emptyAcc, {from: emptyAcc});

     Invariant Violation: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app

Back to square one. How do I inject the GsnProvider into Web3 or interact with my contracts in a way it goes through the Relayer? Any idea @abcoathup @nventuro please?

1 Like

I'm going to also cc: @ylv-io on this one.

2 Likes

ok. @ylv-io @Dennison I spent another 8h on this and I think I am in the last step but I really have no clue anymore what’s wrong. It’s hanging on the last line of code:

require('dotenv').config({ path: `${__dirname}/.env` });

const chai = require('chai');
chai.use(require('chai-as-promised'));
const assert = chai.assert;

const { fundRecipient } = require('@openzeppelin/gsn-helpers');
const { fromConnection, useEphemeralKey } = require('@openzeppelin/network');
const { utils } = require('@openzeppelin/gsn-provider');
const { isRelayHubDeployedForRecipient, getRecipientFunds } = utils;

const ACLFactory = artifacts.require("AclFactory");

contract('AclFactory', (accounts) => {
  const ROOT_ACCOUNT = process.env.NETWORK === 'ganache' ? accounts[0] : process.env.ACCOUNT;

  let factory;

  it('should deploy a factory', async () => {
    factory = await ACLFactory.new();
  });

  it('should be able to initialize the GSN for the factory', async () => {
    let tx = await factory.initialize();
  });

  it('should fund the recipient (ACLFactory)', async () => {
    const factoryAddr = await factory.address;

    const tx = await fundRecipient(web3, {
      recipient: factoryAddr,
      relayHubAddress: "0x511BEd1C29BC163C96B7d4ddE28c7E0590Aa44F5",
      amount: web3.utils.toWei("1", "ether"),
      from: accounts[0]
    });
  });

  it('should deploy an ACL from user without any funds for FREE', async () => {
    const emptyAcc = await web3.eth.accounts.create("secret");
    console.log(emptyAcc);

    const isDeployed = await isRelayHubDeployedForRecipient(web3, factory.address);
    console.log(isDeployed);

    const recFunds = await getRecipientFunds(web3, factory.address);
    console.log(recFunds);

    const localCtx = await fromConnection(
      'http://127.0.0.1:8645', {
      gsn: { dev: false, signKey: emptyAcc.privateKey }
    });

    web3 = localCtx.lib;

    const factoryAbi = [
      {
        "constant": true,
        "inputs": [],
        "name": "getHubAddr",
        "outputs": [
          {
            "name": "",
            "type": "address"
          }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [
          {
            "name": "context",
            "type": "bytes"
          }
        ],
        "name": "preRelayedCall",
        "outputs": [
          {
            "name": "",
            "type": "bytes32"
          }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "relayHubVersion",
        "outputs": [
          {
            "name": "",
            "type": "string"
          }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [
          {
            "name": "context",
            "type": "bytes"
          },
          {
            "name": "success",
            "type": "bool"
          },
          {
            "name": "actualCharge",
            "type": "uint256"
          },
          {
            "name": "preRetVal",
            "type": "bytes32"
          }
        ],
        "name": "postRelayedCall",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "anonymous": false,
        "inputs": [
          {
            "indexed": true,
            "name": "oldRelayHub",
            "type": "address"
          },
          {
            "indexed": true,
            "name": "newRelayHub",
            "type": "address"
          }
        ],
        "name": "RelayHubChanged",
        "type": "event"
      },
      {
        "constant": false,
        "inputs": [
          {
            "name": "_owner",
            "type": "address"
          }
        ],
        "name": "newACL",
        "outputs": [
          {
            "name": "",
            "type": "address"
          }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [],
        "name": "initialize",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [
          {
            "name": "",
            "type": "address"
          },
          {
            "name": "",
            "type": "address"
          },
          {
            "name": "",
            "type": "bytes"
          },
          {
            "name": "",
            "type": "uint256"
          },
          {
            "name": "",
            "type": "uint256"
          },
          {
            "name": "",
            "type": "uint256"
          },
          {
            "name": "",
            "type": "uint256"
          },
          {
            "name": "",
            "type": "bytes"
          },
          {
            "name": "",
            "type": "uint256"
          }
        ],
        "name": "acceptRelayedCall",
        "outputs": [
          {
            "name": "",
            "type": "uint256"
          },
          {
            "name": "",
            "type": "bytes"
          }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": false,
        "inputs": [],
        "name": "setRelayHubAddress",
        "outputs": [],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "getRecipientBalance",
        "outputs": [
          {
            "name": "",
            "type": "uint256"
          }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
      },
      {
        "constant": true,
        "inputs": [],
        "name": "version",
        "outputs": [
          {
            "name": "",
            "type": "uint256"
          }
        ],
        "payable": false,
        "stateMutability": "pure",
        "type": "function"
      }
    ];
    const factoryContract = new localCtx.lib.eth.Contract(factoryAbi, factory.address);

    let res = await factoryContract.methods.newACL(emptyAcc.address).send({from: emptyAcc.address});
    console.log(res);
  });
});

Output:

{ address: '0x4C0187138B6c110916458F50D2708f488E549Dcf',
  privateKey:
   '0x8b958ecb84926eb919162dac17548d77aed657ee94efc51251c4a02caf8a8494',
  signTransaction: [Function: signTransaction],
  sign: [Function: sign],
  encrypt: [Function: encrypt] }

Error: Timeout of 300000ms exceeded.

I am close to jumping from balcony, fortunately I live in first floor…

Any idea?

2 Likes

Hey @lukaslukac!
Thanks again for your patience.

I am close to jumping from balcony, fortunately I live in first floor…
:laughing:

You code looks fine. My question is how do you start relayer?
Checkout this script we use for testing relayer with ganache. Is your setup similar? Are you sure you relayer registered at RelayHub?

1 Like

Hey @ylv-io, there is no more patience just pure madness :smiley:

I followed this script recommended in this thread: https://gist.github.com/spalladino/f516b46bdefb5e025c5d28daf7ad1491

ps -ax
/var/www/gsn/bin/RelayHttpServer -Url https://gsn.sirius.lightstreams.io -Port 8091 -Workdir /var/www/gsn/data -EthereumNodeUrl https://node.sirius.lig...
URL=https://gsn.sirius.lightstreams.io
LOCAL_PORT=8091
WORKDIR=/var/www/gsn/data
NODE_URL=https://node.sirius.lightstreams.io
RELAY_HUB=0x511BEd1C29BC163C96B7d4ddE28c7E0590Aa44F5
GAS_PRICE_PERCENT=0

https://gsn.sirius.lightstreams.io/getaddr

2 Likes

Your relayer is replying with ready: false. Have you properly registered it? You can do this with the gsn-helpers:

npx oz-gsn register-relayer --relayUrl https://gsn.sirius.lightstreams.io

Note that you don't fund a RelayHub instance, instead you:

  • fund recipients so that they can receive calls
  • stake and register relayers so that they can participate in the system (this is done by the manager of the relayer)
  • send ether to the relayer account so that it can actually send transactions (also done by the relayer manager)

The register-relayer command will take care of the last two steps for you.

You mentioned truffle tests, are you by any chance also using truffle contracts? Our guides are written with web3 contract objects in mind, but it is nonetheless possible to get truffle contracts working with the GSN.

2 Likes

Hey @lukaslukac!
As @nventuro says, ready: false is the issue. I summon @jcarpanelli to help you out. He knows everything how to setup relayer.
Side question, why do you need to setup local relayer available by url? For local dev we have GSNDevProvider and we have relayers on Ropsten, Kovan and Mainnet already set up.

1 Like

Okay let’s focus on making the relayer READY @nventuro .

Here is the output again from register-relayer with some extra logging:

Even though everything went OK, after 30s it dies with error. Any more ideas what’s wrong? I executed the exact command as u suggested.

@ylv-io I am not setting a local relayer. I am setting up a global Relayer for Lightstreams test network called “Sirius” so all deployed test DApps can benefit from free TXs:

2 Likes

Here are the logs from my Relayer Go server:

To notice: the balance was successfully increased and apparently is now waiting for TXs, not sure why it’s still not “Ready”. Gonna deep more into that Go code again…

2 Likes

Looks like an issue on the relayer itself, given that the register-relayer script was succesful. I’m not familiar with Lightstreams and differences it may have with the Ethereum network, but debugging the relayer code seems like the way to go.

1 Like

Good morning, In theory there shouldn’t be any differences. From the tabookey’s Relayer’s Go code, all endpoints and behaviour is standard.

At this point I have 2 options:

  • Improve the Go HTTP Relayer code and make sure everything around the "ready"attribute works well with extra logging and better design
  • Wait until you guys write it in Node JS and improve the relayer code/logging etc

@nventuro Do you plan to do the re-write anytime soon? I saw you were adding more commits and improving generally the gsn-helpers these days as well which is great.

1 Like

@nventuro @ylv-io or anybody please, do you a have full source and compilable Relayer server? I can’t improve it or debug it without having all the code.

If you open https://github.com/tabookey/tabookey-gasless/tree/v0.4.0-beta2/server and check for the usages of “Ready”, the part of code that is probably failing is: refreshBlockchainView. And there is some filtering logic that is nowhere defined and I bet a week of my happiness and mental peace that this behaviour is SILENTLY failing.

NOTE the usage but no definition. Unless some fancy regex magic for building dynamic methods and compiling them was used or Github search is betraying me.

2 Likes

Hey @lukaslukac!
I have never touched Relayer server code. Is it in https://github.com/tabookey/tabookey-gasless/blob/v0.4.0-beta2/server/src folder, like https://github.com/tabookey/tabookey-gasless/blob/v0.4.0-beta2/server/src/RelayHttpServer.go?

1 Like

That’s my question. Doesn’t seems to be complete source code or I am overlooking something. Also it has no package manager and global dependencies etc :frowning:

1 Like

@lukaslukac all of the source code is in the server directory, as well as tests, tools to make the autogenerated files (the Go wrappers for the contracts), and a Makefile to crosscompile the server on all major platforms and architectures.

1 Like

Oh OMG! Totally true. Apologies. I overlooked the part of Makefile for the go bindings generation. How could I forget about that? :man_facepalming: :man_facepalming: :man_facepalming: Thanks for hint! There is indeed a lot of build scripts! Thx @nventuro!

1 Like

Your profile pic fits perfectly

1 Like