XRPLF / xrpl.js

A JavaScript/TypeScript API for interacting with the XRP Ledger in Node.js and the browser
https://xrpl.org/
1.2k stars 511 forks source link

Try .getBalance() for list of addresses #891

Closed fsanano closed 6 years ago

fsanano commented 6 years ago

Hi, in my app I try .getBalances() in loop, for list of addresses, but get this error:

LedgerVersionError: ledgerVersion null is greater than server's most recent validated ledger: 8964053

my code :

const RippleAPI = require('ripple-lib').RippleAPI;

const api = new RippleAPI({
  server: 'wss://s.altnet.rippletest.net:51233',
});

api.connect();

export default {
  methods: {
    /**
     * Fetch ripple wallet balance
     * @param  {String}  address [Wallet address]
     */
    async rippleFetchWalletBalance(address) {
      try {
        const balances = await api.getBalances(address);
        // console.info('balances', balances);
        balances.forEach((item) => {
          const payload = {
            address,
            key: 'value',
            value: item.value / 1000,
          };
          this.$store.dispatch('updateWallet', payload);
        });
      } catch (e) {
        console.error('e', e);// eslint-disable-line
      }
    },
  },
};

but when I get SINGLE address balance it work good.

fsanano commented 6 years ago

Also I try change my method like this:

async rippleFetchWalletBalance(address) {
  try {
    await api.connect();
    const balances = await api.getBalances(address);
    // console.info('balances', balances);
    balances.forEach((item) => {
      const payload = {
        address,
        key: 'value',
        value: item.value / 1000,
      };
      this.$store.dispatch('updateWallet', payload);
    });
    await api.disconnect();
  } catch (e) {
    console.error('e', e);// eslint-disable-line
  }
}
tuloski commented 6 years ago

I don't see the loop you are mentioning in your code.

fsanano commented 6 years ago

That method iterate, ok show all code.

async rippleFetchWalletBalance(address) {
  try {
    await api.connect();
    const balances = await api.getBalances(address);
    // console.info('balances', balances);
    balances.forEach((item) => {
      const payload = {
        address,
        key: 'value',
        value: item.value / 1000,
      };
      this.$store.dispatch('updateWallet', payload);
    });
    await api.disconnect();
  } catch (e) {
    console.error('e', e);// eslint-disable-line
  }
}

fetchWalletsData(list) {
  const addresses = Object.keys(list);
  if (!isEmpty(addresses)) {
    addresses.forEach(async (addr) => {
      const { address } = this.wallets[addr];
      await this.rippleFetchWalletBalance( address);
    });
  }
},
tuloski commented 6 years ago

Are you sure you are passing to rippleFetchWalletBalance only a string (the address) each iteration?

sharafian commented 6 years ago

Can you try awaiting api.connect before you make any calls to rippleFetchWalletBalance? My theory is that you're accidentally getting some undefined values due to ripple lib's connection logic.

The error you're seeing is thrown here: https://github.com/ripple/ripple-lib/blob/develop/src/api.ts#L96

So somewhere in your request it's requesting a ledger_index of null. Probably being triggered when you request a balance from here: https://github.com/ripple/ripple-lib/blob/187154a2b08b2c24307d2a8503d5010f236f3fa6/src/ledger/utils.ts#L26

The code that supplies that ledger_index is here: https://github.com/ripple/ripple-lib/blob/develop/src/common/connection.ts#L379

It awaits connection and then returns the ledger index. But it's actually evaluated immediately, even if the connection hasn't connected yet. So the ledger index may be undefined or null, because that's the default value it starts at (https://github.com/ripple/ripple-lib/blob/develop/src/common/connection.ts#L43)

The reason this would happen on multiple calls only is that when you call connect() it triggers this branch (https://github.com/ripple/ripple-lib/blob/develop/src/common/connection.ts#L307) which connects and then runs the full connection logic once the websocket is open. But it's already in the connection process, it resolves immediately when the connection has opened but before other connection logic is run (https://github.com/ripple/ripple-lib/blob/develop/src/common/connection.ts#L305). So your second call to connect may actually resolve before the first one.

fsanano commented 6 years ago

@sharafian Thank you for request. Yesterday I did as you advised, but there was another mistake. After the api.connect() was immediately closed, because the loop was not awaiting. But is fixed with adding promises. This code work!

async rippleFetchWalletsData() {
  await api.connect();
  const wallets = this.filterWalletsByCoin('ripple');
  const promises = wallets.map(async (wallet) => {
    const { address } = wallet;
    await this.rippleFetchWalletBalance(address);
    await this.rippleFetchWalletTransactions(address);
  });
  await Promise.all(promises);
  api.disconnect();
},

async rippleFetchWalletBalance(address) {
  try {
    const balances = await api.getBalances(address);
    console.info('balances', balances);
  } catch (e) {
    console.error('e', e);// eslint-disable-line
  }
},

Close issue!

sharafian commented 6 years ago

Great! I think we should keep this open until we put in a fix, though, because the behavior of api.connect wasn't as expected. @intelliot if you have a chance, could you look into a fix for the bug that I outlined in my last comment?

intelliot commented 6 years ago

@sharafian I've looked into this for a bit, and I don't quite understand what's going on. I ran some code similar to what @fsanano originally posted, but I didn't get the same error.

  1. What's the most straightforward way to trigger this bug?
  2. What would be one way to fix it?
intelliot commented 6 years ago

I'm closing this issue, but feel free to open an issue or PR with an improvement to api.connect.