ethers.js: Unable to catch events with provider when using Ganache-Core for test

I am writing a test to listen to events on the blockchain, most especially the ‘block’ event. My tools are Ethers.js and Ganache-core. Using the provider.on(event, callback) function does not work. here is a copy of my code and what I was testing:

const ethers = require('ethers');
const getProvider = new ethers.providers.Web3Provider(ganache.provider({mnemonic: some random phrase}));

describe('listen to blockchain transactions', () => {
 beforeAll(async (done) => {
        provider = getProvider();
  });
 afterAll(async () => {
        provider.stop();
 });

it('should listen to transactions between user accounts', async () => {
        const accounts = await provider.listAccounts();
        const userAccount1 = accounts[1];
        const userAccount2 = accounts[2];
        const chainId = (await provider.ready).chainId;
        let transaction = {
            nonce: 0,
            gasLimit: 21000,
            gasPrice: ethers.utils.bigNumberify("20000000000"),
            to: userAccount2,
            value: ethers.utils.parseEther("10.0"),
            data: "0x",
            chainId: chainId
        };
        const privateKey = await accountService.getAccountSigningKey(userAccount1);
        const wallet = new ethers.Wallet(privateKey);
        const signedTransaction = await wallet.sign(transaction);
        const transactionResponse = await provider.sendTransaction(signedTransaction);
        provider.on('block', async (blockHeight) => {
            console.log('blockHeight: ', blockHeight) //Nothing logs, this callback is not executed    
        });
    });
})

Could there be a better way to do this? Kindly recommend.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 18 (6 by maintainers)

Most upvoted comments

The way contract.on("Transfer", cb); works is it polls the chain in intervals (default 4 secs).

it('test case', async () => {
  const contract = ...; // declare

  contract.on("Transfer", cb); // 0ms

  await contract.transfer(...args); // 100ms
}) // contract variable scope is finished, it is cleared

As you can see your test case runs for such a small time, then moves to another test case. If you want to make that work, you might try waiting until the polling interval (also shortening the polling interval).

await new Promise(res => setTimeout(res, 4000)); // waits for 4 secs

However, to test events, I’d suggest to instead use an alternate method that doesn’t need you to wait.

it('testcase', async() => {
  const tx = await contract.transfer(...args); // 100ms
  const rc = await tx.wait(); // 0ms, as tx is already confirmed
  const event = rc.events.find(event => event.event === 'Transfer');
  const [from, to, value] = event.args;
  console.log(from, to, value);
})

Ok… I will address this somehow… I need to think of a better way to do this.

The problem is that in a PoA network, which is what Ganache basically is, there are no blocks mined if there is nothing happening. And since ethers only triggers an event processing loop when a block happens, this never happens in a non-chatty PoA network.

Is there a way to make ganache mine empty blocks periodically? This should also “help” the situation, but isn’t a real fix. I’ll think about this for a bit too…

Ganache does offer this feature with the --blockTime flag: npx ganache-cli --blockTime n will mine a block every n seconds.

If you don’t want to do this, you can also manually mine a block by sending evm_mine. Here’s a blog post that uses this technique.