Skip to content

Commit

Permalink
Even better block watching + various code refactorings (#173)
Browse files Browse the repository at this point in the history
* Continue watching if one bucket stop/start failed.

* More reliable in progress check. Some minor refactorings.

* Fix liniting
  • Loading branch information
kosecki123 committed Aug 7, 2018
1 parent 21a1a40 commit ae70e6b
Show file tree
Hide file tree
Showing 9 changed files with 200 additions and 203 deletions.
241 changes: 106 additions & 135 deletions src/Actions/Actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { isExecuted, isTransactionStatusSuccessful } from './Helpers';
import hasPending from './Pending';
import { IWalletReceipt } from '../Wallet';
import { ExecuteStatus, ClaimStatus } from '../Enum';
import { TxSendErrors } from '../Enum/TxSendErrors';

export function shortenAddress(address: string) {
return `${address.slice(0, 6)}...${address.slice(address.length - 5, address.length)}`;
Expand All @@ -22,169 +23,73 @@ export default class Actions {
return ClaimStatus.NOT_ENABLED;
}

const requiredDeposit = txRequest.requiredDeposit;
// TODO make this a constant
const claimData = txRequest.claimData;

// Gas needed ~ 89k, this provides a buffer... just in case
const gasEstimate = 120000;

const opts = {
to: txRequest.address,
value: requiredDeposit,
gas: gasEstimate,
gasPrice: await this.config.util.networkGasPrice(),
data: claimData
};
const opts = await this.getClaimingOpts(txRequest);

if (await hasPending(this.config, txRequest, { type: 'claim' })) {
return ClaimStatus.PENDING;
}

if (this.config.wallet.isNextAccountFree()) {
try {
// this.config.logger.debug(`[${txRequest.address}] Sending claim transactions with opts: ${JSON.stringify(opts)}`);
const { receipt, from, error } = await this.config.wallet.sendFromNext(opts);
// this.config.logger.debug(`[${txRequest.address}] Received receipt: ${JSON.stringify(receipt)}\n And from: ${from}`);
let claimingError;

if (error) {
this.config.logger.debug(
`Actions::claim(${shortenAddress(txRequest.address)})::sendFromNext error: ${error}`
);
return ClaimStatus.FAILED;
}

if (isTransactionStatusSuccessful(receipt.status)) {
await txRequest.refreshData();
const cost = new BigNumber(receipt.gasUsed).mul(
new BigNumber(txRequest.data.txData.gasPrice)
);
try {
const { receipt, from, error } = await this.config.wallet.sendFromNext(opts);

this.config.cache.get(txRequest.address).claimedBy = from;
if (!error && isTransactionStatusSuccessful(receipt.status)) {
await txRequest.refreshData();

this.config.statsDb.updateClaimed(from, cost);
const gasUsed = new BigNumber(receipt.gasUsed);
const gasPrice = new BigNumber(txRequest.data.txData.gasPrice);
const cost = gasUsed.mul(gasPrice);

if (txRequest.isClaimed) {
return ClaimStatus.SUCCESS;
}
}
this.config.cache.get(txRequest.address).claimedBy = from;
this.config.statsDb.updateClaimed(from, cost);

if (this.config.cache.has(txRequest.address)) {
this.config.cache.get(txRequest.address).claimingFailed = true;
} else {
this.config.cache.set(txRequest.address, {
claimedBy: null,
claimingFailed: true,
wasCalled: false,
windowStart: null
});
}
return ClaimStatus.SUCCESS;
} else if (error === TxSendErrors.SENDING_IN_PROGRESS) {
return ClaimStatus.PENDING;
}

return ClaimStatus.FAILED;
} catch (err) {
this.config.logger.debug(
`Actions::claim(${shortenAddress(txRequest.address)})::sendFromIndex error: ${err}`
);
claimingError = error;
} catch (err) {
claimingError = err;
}

return ClaimStatus.FAILED;
}
if (this.config.cache.has(txRequest.address)) {
this.config.cache.get(txRequest.address).claimingFailed = true;
} else {
this.config.logger.debug(
`Actions::claim(${shortenAddress(
txRequest.address
)})::Wallet with index 0 is not able to send tx.`
);
return ClaimStatus.FAILED;
this.config.cache.set(txRequest.address, {
claimedBy: null,
claimingFailed: true,
wasCalled: false,
windowStart: null
});
}

//TODO get transaction object from txHash
this.config.logger.error(`[${txRequest.address}] error: ${claimingError}`);

return ClaimStatus.FAILED;
}

public async execute(txRequest: any): Promise<any> {
const gasToExecute = txRequest.callGas
.add(180000)
.div(64)
.times(65)
.round();
// TODO Check that the gasToExecue < gasLimit of latest block w/ some margin

// TODO make this a constant
const executeData = txRequest.executeData;

const claimIndex = this.config.wallet.getAddresses().indexOf(txRequest.claimedBy);
this.config.logger.debug(`Claim Index ${claimIndex}`);

const opts = {
to: txRequest.address,
value: 0,
gas: gasToExecute,
gasPrice: txRequest.gasPrice,
data: executeData
};

if (
await hasPending(this.config, txRequest, {
type: 'execute',
exactPrice: opts.gasPrice
exactPrice: txRequest.gasPrice
})
) {
return ExecuteStatus.PENDING;
}

const handleTransactionReturn = async (
walletReceipt: IWalletReceipt
): Promise<ExecuteStatus> => {
const { receipt, from, error } = walletReceipt;

if (error) {
this.config.logger.debug(`Actions.execute: ${ExecuteStatus.FAILED}`);
return ExecuteStatus.FAILED;
}

if (isTransactionStatusSuccessful(receipt.status)) {
let bounty = new BigNumber(0);
let cost = new BigNumber(0);

if (isExecuted(receipt)) {
await txRequest.refreshData();

const data = receipt.logs[0].data;
bounty = this.config.web3.toDecimal(data.slice(0, 66));

const cached = this.config.cache.get(txRequest.address);

if (cached) {
cached.wasCalled = true;
}
} else {
// If not executed, must add the gas cost into cost. Otherwise, TimeNode was
// reimbursed for gas.
cost = new BigNumber(receipt.gasUsed).mul(new BigNumber(txRequest.data.txData.gasPrice));
}

this.config.statsDb.updateExecuted(from, bounty, cost);

if (txRequest.wasSuccessful) {
return ExecuteStatus.SUCCESS;
}
}

return ExecuteStatus.FAILED;
};

if (claimIndex !== -1) {
const walletReceipt = await this.config.wallet.sendFromIndex(claimIndex, opts);

return await handleTransactionReturn(walletReceipt);
}
const opts = await this.getExecutionOpts(txRequest);
const claimIndex = this.config.wallet.getAddresses().indexOf(txRequest.claimedBy);
this.config.logger.debug(`Actions:execute: claiming account index=${claimIndex}`);

if (this.config.wallet.isNextAccountFree()) {
const walletReceipt = await this.config.wallet.sendFromNext(opts);
const receipt =
claimIndex !== -1
? await this.config.wallet.sendFromIndex(claimIndex, opts)
: await this.config.wallet.sendFromNext(opts);

return await handleTransactionReturn(walletReceipt);
} else {
this.config.logger.debug('Actions.execute : No available wallet to send a transaction.');
}
return await this.handleTransactionReceipt(txRequest, receipt);
}

public async cleanup(txRequest: any): Promise<boolean> {
Expand Down Expand Up @@ -241,4 +146,70 @@ export default class Actions {
//TODO get tx Obj from hash
}
}

private async handleTransactionReceipt(
txRequest: any,
walletReceipt: IWalletReceipt
): Promise<ExecuteStatus> {
const { receipt, from, error } = walletReceipt;

if (error) {
this.config.logger.debug(`Actions.execute: ${ExecuteStatus.FAILED}`);
return ExecuteStatus.FAILED;
}

if (isTransactionStatusSuccessful(receipt.status)) {
let bounty = new BigNumber(0);
let cost = new BigNumber(0);

if (isExecuted(receipt)) {
await txRequest.refreshData();

const data = receipt.logs[0].data;
bounty = this.config.web3.toDecimal(data.slice(0, 66));

const cached = this.config.cache.get(txRequest.address);

if (cached) {
cached.wasCalled = true;
}
} else {
// If not executed, must add the gas cost into cost. Otherwise, TimeNode was
// reimbursed for gas.
cost = new BigNumber(receipt.gasUsed).mul(new BigNumber(txRequest.data.txData.gasPrice));
}

this.config.statsDb.updateExecuted(from, bounty, cost);

return ExecuteStatus.SUCCESS;
}

return ExecuteStatus.FAILED;
}

private async getClaimingOpts(txRequest: any): Promise<any> {
return {
to: txRequest.address,
value: txRequest.requiredDeposit,
gas: 120000,
gasPrice: await this.config.util.networkGasPrice(),
data: txRequest.claimData
};
}

private async getExecutionOpts(txRequest: any): Promise<any> {
const gas = txRequest.callGas
.add(180000)
.div(64)
.times(65)
.round();

return {
to: txRequest.address,
value: 0,
gas,
gasPrice: txRequest.gasPrice,
data: txRequest.executeData
};
}
}
47 changes: 28 additions & 19 deletions src/Actions/Pending.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ const hasPendingParity = (
conf: any,
txRequest: any,
opts: { type?: string; checkGasPrice?: boolean; exactPrice?: any }
) => {
): Promise<boolean> => {
opts.checkGasPrice = opts.checkGasPrice === undefined ? true : opts.checkGasPrice;
const provider = conf.web3.currentProvider;

return new Promise( async (resolve, reject) => {
try{
return new Promise(async (resolve, reject) => {
try {
await provider.sendAsync(
{
jsonrpc: '2.0',
Expand All @@ -28,8 +28,9 @@ const hasPendingParity = (
},
async (err: Error, res: any) => {
if (err || res.error || !res.result) {
const errMsg = (err && err.message) || err || (res.error && res.error.message) || res.error;
conf.logger.error(errMsg)
const errMsg =
(err && err.message) || err || (res.error && res.error.message) || res.error;
conf.logger.error(errMsg);
return;
}

Expand All @@ -39,7 +40,11 @@ const hasPendingParity = (
res.result[count] &&
(!opts.checkGasPrice ||
(await hasValidGasPrice(conf, res.result[count], opts.exactPrice)));
if (res.result[count] && isOfType(res.result[count], opts.type) && withValidGasPrice) {
if (
res.result[count] &&
isOfType(res.result[count], opts.type) &&
withValidGasPrice
) {
resolve(true);
}
}
Expand Down Expand Up @@ -67,12 +72,12 @@ const hasPendingGeth = (
conf: any,
txRequest: any,
opts: { type?: string; checkGasPrice?: boolean; exactPrice?: any }
) => {
): Promise<boolean> => {
opts.checkGasPrice = opts.checkGasPrice === undefined ? true : opts.checkGasPrice;
const provider = conf.web3.currentProvider;

return new Promise((resolve, reject) => {
try{
try {
provider.sendAsync(
{
jsonrpc: '2.0',
Expand All @@ -82,8 +87,9 @@ const hasPendingGeth = (
},
async (err: Error, res: any) => {
if (err || res.error || !res.result) {
const errMsg = (err && err.message) || err || (res.error && res.error.message) || res.error;
conf.logger.error(errMsg)
const errMsg =
(err && err.message) || err || (res.error && res.error.message) || res.error;
conf.logger.error(errMsg);
return;
}

Expand Down Expand Up @@ -135,7 +141,7 @@ const hasValidGasPrice = async (conf: any, transaction: any, exactPrice?: any) =
await new Promise((resolve, reject) => {
conf.web3.eth.getGasPrice((err: Error, res: any) => {
if (err) {
conf.logger.error(err)
conf.logger.error(err);
return;
}
currentGasPrice = res;
Expand Down Expand Up @@ -172,15 +178,18 @@ const hasPending = async (
conf: any,
txRequest: any,
opts: { type?: string; checkGasPrice?: boolean; exactPrice?: any }
) => {
): Promise<boolean> => {
let result = false;

if (conf.client === 'parity') {
return hasPendingParity(conf, txRequest, opts);
} else if (conf.client === 'geth') {
return hasPendingGeth(conf, txRequest, opts);
} else {
return;
}
if (conf.client === 'parity') {
result = await hasPendingParity(conf, txRequest, opts);
} else if (conf.client === 'geth') {
result = await hasPendingGeth(conf, txRequest, opts);
}

conf.logger.debug(` ${txRequest.address} hasPending=${result}`);

return result;
};

export default hasPending;
3 changes: 2 additions & 1 deletion src/Enum/ExecuteStatus.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export enum ExecuteStatus {
PENDING = 'Another execution is already pending.',
FAILED = 'Unable to send the execute action.',
SUCCESS = 'Transaction executed successfully.'
SUCCESS = 'Transaction executed successfully.',
NO_ACCOUNTS = 'All accounts in use'
}

0 comments on commit ae70e6b

Please sign in to comment.