7

I'm developing a very simple React application wherein a user clicks a button and some ether gets sent from one account to another. Currently, the onClick sendEther function crafts a proper signed serialized transaction and (oftentimes) returns a transaction hash. However, the transaction never actually goes through to the Rinkeby test network (as accessed via Etherscan).

My hunch is that that I'm misusing infura (which I set up in my DOMContentLoaded event, see below).

//1. Set up

document.addEventListener('DOMContentLoaded', () => {
   const endpoint = 'https://rinkeby.infura.io/APIKEY';
   window.web3 = new Web3(new Web3.providers.HttpProvider(endpoint));
}); 

//2. React front end component.

sendEther() {
    const fromAccount = **acct1**;
    const toAccount   = **acct2**;

    const rawTransaction    = this.makeRawTransaction(fromAccount, toAccount);
    const signedTransaction = this.makeSignedTransaction(rawTransaction);
    const serializedTransaction = `0x${signedTransaction.serialize().toString('hex')}`;

    window.web3.eth.sendSignedTransaction(serializedTransaction, (error, result) => {
        if(!error) {
          console.log(`Transaction hash is: ${result}`);
          this.setState({
            etherscanUrl: `https://rinkeby.etherscan.io/tx/${result}`,
            error: null
          });

        } else {
          this.setState({ error: error.message })
          console.error(error);
        }
    });
  }

  makeSignedTransaction(rawTransaction) {
    const privateKey   = '**************';
    const privateKeyX  = new Buffer(privateKey, 'hex');
    const transaction  = new EthTx(rawTransaction);
    transaction.sign(privateKeyX);

    return transaction;
  }

  makeRawTransaction(fromAccount, toAccount) {
    const { exchangeRate } = this.props;
    const amount = (1 / exchangeRate) * 5;

    return ({
      nonce: window.web3.utils.toHex(window.web3.eth.getTransactionCount(fromAccount)),
      to: toAccount,
      gasPrice: window.web3.utils.toHex(100000000000),
      gasLimit: window.web3.utils.toHex(100000),
      value: window.web3.utils.toHex(window.web3.utils.toWei(`${amount}`, 'ether')),
      data: ''
    });
  }
  • Just a note: Etherscan is not omnipotent. Even if something doesn't show up in Etherscan doesn't mean it couldn't have succeeded. Maybe Etherscan just doesn't see it? – Lauri Peltonen Sep 10 '18 at 6:17
9
+25

This line ...

nonce: window.web3.utils.toHex(window.web3.eth.getTransactionCount(fromAccount)),

... relies on a reliable transaction count from Infura. The problem is it's not as reliable (or even close) as you need it to be. This method will produce unreliable, inconsistent results as you have observed.

Some background will help explain what is going on and how you can address it.

Ethereum uses a nonce for each account to protect against replay attacks. There are some subtle implementation details.

  1. Transactions from the same account will assuredly mine in nonce order.
  2. If a transaction fails to mine (lost, gas too low to be included in a block) then all transactions with a higher nonce will stall. A stalled transaction can usually be canceled (see #4).
  3. Software clients (nodes, wallets) are expected to track the nonce and attach it to transactions. This, so nodes will know the correct execution order even in the case that gossipped transactions arrive out of order.
  4. A transaction can be canceled by another transaction with the same nonce and higher gas if the replacement is mined first. A common method for canceling a transaction is to repeat the nonce and send 0 ETH, from the account to itself.
  5. At best, getTransactionCount() doesn't take into account the transactions that might exist in the pending queue.
  6. The pending queue is "unofficial" if such a thing can be said about anything in the distributed consensus. Nodes can only report the pending transactions they know about. You can and you will get different opinions from different nodes as proposed transactions traverse the network. Mining of blocks is the process that collapses the ambiguity.
  7. Infura is highly parallel, so even though you're banging on one endpoint, the response could come from any outward-facing node on a load-balanced basis.

All of which to say there are known reasons why your process is unreliable.

The usual approach is to make the software client the manager of the nonce. The easiest scenario to describe is the case where there are no pending transactions because the account has been quiet or it's a new account.

Starting at 0, or the transaction count, increment the nonce as you go without relying on nodes. You have to trust your own count more than you trust any signal from the network.

You can find more information over here: Concurrency patterns for account nonce

Hope it helps.

3

As Rob Hitchens explained, Infura is architected via a load balancer with multiple nodes behind it. This means that two of the same requests to Infura can result in different responses because each request could hit a different node. Sometimes you will get responses from stale blocks, nodes with different transactions in their mempools, or nodes that don't contain the newest block. An alternative to this problem is using a service such as Alchemy

  • Welcome to the Ethereum Stack Exchange! This was flagged as spam. Please check meta.stackexchange.com/questions/57497/… because there's a pattern of mentioning Alchemy in your answers, and it should be disclosed if you work for them. – eth Mar 6 at 18:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.