Skip to content

Commit

Permalink
feat: fail if the tx is dropped (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicosantangelo authored and menduz committed May 9, 2018
1 parent b333fbc commit 5f1065f
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 95 deletions.
69 changes: 9 additions & 60 deletions specs/Contracts.spec.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
import { expect } from 'chai'
import { NodeConnectionFactory } from './NodeConnectionFactory'
import { deployContract } from './deployContract'
import { eth, txUtils } from '../dist'
import { MANAToken } from '../dist/contracts'
const ganache = require('ganache-cli')

describe('ETH tests', () => {
const nodeConnectionFactory = new NodeConnectionFactory()

it('should return no instantiated contracts', () => {
expect(() => eth.getContract('')).to.throw()
})

describe('ETH using provider', function() {
const provider: object = ganache.provider({
accounts: [
{
balance: 100012300001 /* gas */,
secretKey: '0x8485898485bbe08a0a9b97fdf695aec8e9f1d196c589d4b6ff50a0232518b682'
}
],
network_id: 3,
logger: {
log(...args) {
console.log(...args)
}
},
vmErrorsOnRPCResponse: true
})
const provider = nodeConnectionFactory.createProvider()

it('should call .connect({}) and it works', async () => {
const r = await eth.connect({ provider })
Expand All @@ -34,21 +23,7 @@ describe('ETH tests', () => {
})

describe('ETH using http RPC', function() {
const provider = ganache.server({
accounts: [
{
balance: 100012300001 /* gas */,
secretKey: '0x8485898485bbe08a0a9b97fdf695aec8e9f1d196c589d4b6ff50a0232518b682'
}
],
network_id: 3,
logger: {
log(...args) {
console.log(...args)
}
},
vmErrorsOnRPCResponse: true
})
const provider = nodeConnectionFactory.createServer()

it('should start the server', done => {
provider.listen(7654, function(err) {
Expand Down Expand Up @@ -104,13 +79,16 @@ function doTest() {
this.timeout(100000)
const contract = await deployContract(eth.wallet, 'MANA', require('./fixtures/MANAToken.json'))
console.log(`> Tx: ${contract.transactionHash}`)

const txRecipt = await eth.wallet.getTransactionReceipt(contract.transactionHash)
expect(typeof txRecipt.contractAddress).to.eq('string')
expect(txRecipt.contractAddress.length).to.be.greaterThan(0)

const x = await txUtils.getTransaction(contract.transactionHash)
expect(typeof x).eq('object')
expect(x.hash).eq(contract.transactionHash)
expect(typeof x.recepeit).eq('object')

manaAddress = txRecipt.contractAddress
})

Expand Down Expand Up @@ -179,32 +157,3 @@ function doTest() {
}
})
}

async function deployContract(wallet, name: string, contract: any) {
const account = await wallet.getAccount()

const newContract = await wallet.getContract(contract.abi)
const gasEstimate = await wallet.estimateGas({ data: contract.bytecode })

console.log(`> Will deploy contract ${name} with gas: ${gasEstimate}`)

const options = { from: account, data: contract.bytecode, gas: gasEstimate }

let resolver = null
let rejecter = null

const prom = new Promise<any>((x, y) => {
resolver = x
rejecter = y
})

newContract.new(options, function(err, myContract) {
if (err) {
rejecter(err)
} else {
resolver(myContract)
}
})

return prom
}
54 changes: 54 additions & 0 deletions specs/NodeConnectionFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const ganache = require('ganache-cli')

export type ConnectionOptions = {
accounts?: Array<object>
debug?: boolean
logger?: object | Function
mnemonic?: string
port?: number
seed?: string
total_accounts?: number
fork?: string
network_id?: number
time?: Date
locked?: boolean
unlocked_accounts?: Array<string>
db_path?: string
account_keys_path?: string
vmErrorsOnRPCResponse?: boolean
}

export class NodeConnectionFactory {
connectionOptions: ConnectionOptions

constructor(options?: ConnectionOptions) {
this.assignConnectionOptions()
}

assignConnectionOptions(options?: ConnectionOptions) {
this.connectionOptions = {
accounts: [
{
balance: 100012300001 /* gas */,
secretKey: '0x8485898485bbe08a0a9b97fdf695aec8e9f1d196c589d4b6ff50a0232518b682'
}
],
network_id: 3,
logger: {
log(...args) {
console.log(...args)
}
},
vmErrorsOnRPCResponse: true,
...options
}
}

createProvider() {
return ganache.provider(this.connectionOptions)
}

createServer() {
return ganache.server(this.connectionOptions)
}
}
33 changes: 33 additions & 0 deletions specs/deployContract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export type Artifact = {
abi: object
bytecode: string
}

export async function deployContract(wallet, name: string, contract: Artifact) {
const account = await wallet.getAccount()

const newContract = await wallet.getContract(contract.abi)
const gasEstimate = await wallet.estimateGas({ data: contract.bytecode })

console.log(`> Will deploy contract ${name} with gas: ${gasEstimate}`)

const options = { from: account, data: contract.bytecode, gas: gasEstimate }

let resolver = null
let rejecter = null

const prom = new Promise<any>((x, y) => {
resolver = x
rejecter = y
})

newContract.new(options, function(err, myContract) {
if (err) {
rejecter(err)
} else {
resolver(myContract)
}
})

return prom
}
Empty file added specs/ethProviderFactory.tx
Empty file.
116 changes: 116 additions & 0 deletions specs/txUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { expect } from 'chai'
import { NodeConnectionFactory } from './NodeConnectionFactory'
import { deployContract } from './deployContract'
import { eth, txUtils } from '../dist'

describe('txUtils tests', () => {
const DEFAULT_FETCH_DELAY = txUtils.TRANSACTION_FETCH_DELAY
const nodeConnectionFactory = new NodeConnectionFactory()
let provider

before(() => {
provider = nodeConnectionFactory.createProvider()
return eth.connect({ provider })
})

describe('.getTransaction', function() {
it('should return the transaction status and its recepeit', async function() {
this.timeout(100000)

const contract = await deployContract(eth.wallet, 'MANA', require('./fixtures/MANAToken.json'))
const { recepeit, ...tx } = await txUtils.getTransaction(contract.transactionHash)

expect(Object.keys(tx)).to.be.deep.equal([
'hash',
'nonce',
'blockHash',
'blockNumber',
'transactionIndex',
'from',
'to',
'value',
'gas',
'gasPrice',
'input'
])
expect(tx.hash).to.be.equal('0x505d58d5b6a38304deaad305ff2d773354cc939afc456562ba6bddbbf201e27f')

expect(Object.keys(recepeit)).to.be.deep.equal([
'transactionHash',
'transactionIndex',
'blockHash',
'blockNumber',
'gasUsed',
'cumulativeGasUsed',
'contractAddress',
'logs',
'status',
'logsBloom'
])
expect(recepeit.transactionHash).to.be.equal('0x505d58d5b6a38304deaad305ff2d773354cc939afc456562ba6bddbbf201e27f')
})

it('should return null if the tx hash is invalid or dropped', async () => {
const invalidTx = await txUtils.getTransaction(
'0xc15c7dda554711eac29d4a983e53aa161dd1bdc6e1d013bb29da1f607916de1'
)
expect(invalidTx).to.be.equal(null)

const droppedTx = await txUtils.getTransaction(
'0x24615f57f5754f2479d6657f7ac9a56006d8d6f634c6955310a5af1c79f4969'
)
expect(droppedTx).to.be.equal(null)
})
})

describe('.isTxDropped', function() {
it('should wait TRANSACTION_FETCH_DELAY for each retry attempts', async function() {
txUtils.TRANSACTION_FETCH_DELAY = 50
const retryAttemps = 5
const totalTime = 5 * 50

const begining = Date.now()
await txUtils.isTxDropped('0x24615f57f5754f2479d6657f7ac9a56006d8d6f634c6955310a5af1c79f4969', retryAttemps)
const end = Date.now()
const delay = end - begining

expect(delay).to.be.within(totalTime, totalTime + 100) // give it 100ms of leway
})

afterEach(() => {
txUtils.TRANSACTION_FETCH_DELAY = DEFAULT_FETCH_DELAY
})
})

describe('.waitForCompletion', function() {
it('should return a failed tx for a dropped hash', async function() {
txUtils.TRANSACTION_FETCH_DELAY = 10

const droppedTx = await txUtils.waitForCompletion(
'0x24615f57f5754f2479d6657f7ac9a56006d8d6f634c6955310a5af1c79f4969'
)

expect(droppedTx).to.be.deep.equal({
hash: '0x24615f57f5754f2479d6657f7ac9a56006d8d6f634c6955310a5af1c79f4969',
status: txUtils.TRANSACTION_STATUS.failed
})
})

it('should return the full transaction after it finishes', async function() {
txUtils.TRANSACTION_FETCH_DELAY = 10

// compiled solidity source code
const code =
'603d80600c6000396000f3007c01000000000000000000000000000000000000000000000000000000006000350463c6888fa18114602d57005b6007600435028060005260206000f3'
const txHash = await eth.wallet.sendTransaction({ data: code })
const tx = await txUtils.waitForCompletion(txHash)

expect(tx.hash).to.be.equal(txHash)
expect(tx.recepeit).not.to.be.equal(undefined)
})

afterEach(() => {
txUtils.TRANSACTION_FETCH_DELAY = DEFAULT_FETCH_DELAY
})
})
})
25 changes: 0 additions & 25 deletions src/ethereum/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ethUtils } from './ethUtils'
import { promisify } from '../utils/index'
import { Contract } from './Contract'
import { Wallet } from './wallets/Wallet'
import { Abi } from './abi'

export type ConnectOptions = {
/** An array of objects defining contracts or Contract subclasses to use. Check {@link eth#setContracts} */
Expand Down Expand Up @@ -151,30 +150,6 @@ export namespace eth {
return contracts[name]
}

/**
* Interface for the web3 `getTransaction` method
* @param {string} txId - Transaction id/hash
* @return {object} - An object describing the transaction (if it exists)
*/
export function fetchTxStatus(txId: string): Promise<object> {
return promisify(wallet.getWeb3().eth.getTransaction)(txId)
}

/**
* Interface for the web3 `getTransactionReceipt` method. It adds the decoded logs to the result (if any)
* @param {string} txId - Transaction id/hash
* @return {object} - An object describing the transaction receipt (if it exists) with it's logs
*/
export async function fetchTxReceipt(txId: string): Promise<object> {
const receipt = await promisify(wallet.getWeb3().eth.getTransactionReceipt)(txId)

if (receipt) {
receipt.logs = Abi.decodeLogs(receipt.logs)
}

return receipt
}

export async function sign(payload) {
const message = ethUtils.toHex(payload)
const signature = await wallet.sign(message)
Expand Down
Loading

0 comments on commit 5f1065f

Please sign in to comment.