feat(@embark/accounts-manager): Get alternative coinbase address

In dev mode, accounts are funded per the blockchain accounts config. In specific situations, there may not be enough funds on the account returned by `eth_coinbase`. In that case, and in the case when `eth_coinbase` returns `0x0` (or equivalent), loop through all accounts and find the one that has the most funds and use that as the coinbase account.
This commit is contained in:
emizzle 2020-03-02 21:21:57 +11:00 committed by Pascal Precht
parent edf43470e4
commit 72e609a7e9
1 changed files with 53 additions and 3 deletions

View File

@ -26,6 +26,7 @@ export default class AccountsManager {
// reset accounts backing variable as the accounts in config may have changed and // reset accounts backing variable as the accounts in config may have changed and
// web.eth.getAccounts may return a different value now // web.eth.getAccounts may return a different value now
this._accounts = null; this._accounts = null;
this._web3 = null;
// as the accounts may have changed, we need to fund the accounts again // as the accounts may have changed, we need to fund the accounts again
await this.parseAndFundAccounts(); await this.parseAndFundAccounts();
@ -54,14 +55,13 @@ export default class AccountsManager {
} }
private async parseAndFundAccounts() { private async parseAndFundAccounts() {
const web3 = await this.web3;
const accounts = await this.accounts; const accounts = await this.accounts;
if (!accounts.length || !this.embark.config.blockchainConfig.isDev) { if (!accounts.length || !this.embark.config.blockchainConfig.isDev) {
return; return;
} }
try { try {
const coinbase = await web3.eth.getCoinbase(); const web3 = await this.web3;
const coinbase = await this.getCoinbaseAddress();
const acctsFromConfig = AccountParser.parseAccountsConfig(this.embark.config.blockchainConfig.accounts, web3, dappPath(), this.logger, accounts); const acctsFromConfig = AccountParser.parseAccountsConfig(this.embark.config.blockchainConfig.accounts, web3, dappPath(), this.logger, accounts);
const accountsWithBalance = accounts.map((address) => { const accountsWithBalance = accounts.map((address) => {
const acctFromConfig = acctsFromConfig.find((acctCfg) => acctCfg.address === address); const acctFromConfig = acctsFromConfig.find((acctCfg) => acctCfg.address === address);
@ -82,4 +82,54 @@ export default class AccountsManager {
this.logger.error(__("Error funding accounts"), err.message || err); this.logger.error(__("Error funding accounts"), err.message || err);
} }
} }
async findAccountWithMostFunds() {
const web3 = await this.web3;
const accounts = await web3.eth.getAccounts();
let highestBalance = {
balance: web3.utils.toBN(0),
account: ""
};
for (const account of accounts) {
// eslint-disable-next-line no-await-in-loop
const balance = web3.utils.toBN(await web3.eth.getBalance(account));
if (balance.gt(highestBalance.balance)) {
highestBalance = { balance, account };
}
}
return highestBalance.account;
}
async findAlternativeCoinbase() {
try {
return this.findAccountWithMostFunds();
} catch (err) {
throw new Error(`Error getting coinbase address: ${err.message || err}`);
}
}
async getCoinbaseAddress() {
const web3 = await this.web3;
try {
const coinbaseAddress = await web3.eth.getCoinbase();
// if the blockchain returns a zeroed address, we can find the account
// with the most funds and use that as the "from" account to txfer
// funds.
if (!coinbaseAddress ||
web3.utils.hexToNumberString(coinbaseAddress) === "0" || // matches 0x0 and 0x00000000000000000000000000000000000000
(await web3.eth.getBalance(coinbaseAddress)) === "0"
) {
return this.findAlternativeCoinbase();
}
return coinbaseAddress;
} catch (err) {
// if the blockchain doesn't support 'eth_coinbase' RPC commands,
// we can find the account with the most funds and use that as the
// "from" account to txfer funds.
if (err.message.includes("The method eth_coinbase does not exist/is not available")) {
return this.findAlternativeCoinbase();
}
throw new Error(`Error finding coinbase address: ${err.message || err}`);
}
}
} }