[fix] update a lot and add test fixtures for transaction

dev
neeboo 6 years ago
parent 225500b64a
commit ce86e56a63
  1. 2
      package.json
  2. 4
      packages/harmony-account/src/account.ts
  3. 17
      packages/harmony-account/src/utils.ts
  4. 2
      packages/harmony-account/tsconfig.test.json
  5. 0
      packages/harmony-contract/test/abiCoder.test.ts
  6. 0
      packages/harmony-contract/test/fixtures/abiv2.ts
  7. 2
      packages/harmony-contract/tsconfig.test.json
  8. 0
      packages/harmony-core/test/blockchain.test.ts
  9. 2
      packages/harmony-core/tsconfig.test.json
  10. 0
      packages/harmony-crypto/test/base58.test.ts
  11. 0
      packages/harmony-crypto/test/base58fixture.ts
  12. 2
      packages/harmony-crypto/tsconfig.test.json
  13. 2
      packages/harmony-network/tsconfig.test.json
  14. 4
      packages/harmony-transaction/src/factory.ts
  15. 48
      packages/harmony-transaction/src/transaction.ts
  16. 1
      packages/harmony-transaction/src/types.ts
  17. 114
      packages/harmony-transaction/src/utils.ts
  18. 97
      packages/harmony-transaction/test/testSign.test.ts
  19. 15824
      packages/harmony-transaction/test/transactions.fixture.json
  20. 0
      packages/harmony-transaction/test/tsconfig.json
  21. 0
      packages/harmony-utils/test/fixture.ts
  22. 3
      packages/harmony-utils/test/tsconfig.json
  23. 0
      packages/harmony-utils/test/validators.test.ts
  24. 33
      scripts/jest/jest.src.config.js
  25. 3
      tsconfig.test.json

@ -13,6 +13,7 @@
"bootstrap": "lerna bootstrap",
"build": "yarn build:proto && yarn build:ts",
"build:ts": "tsc -b tsconfig.json",
"build:test":"tsc -b tsconfig.test.json",
"bundle": "ts-node -P scripts/tsconfig.json scripts/bundle.ts umd,esm",
"clean": "lerna clean --yes && lerna run clean && rimraf includes",
"schema": "ts-node -P scripts/tsconfig.json scripts/typings/schema.ts core",
@ -75,6 +76,7 @@
"dotenv": "^6.0.0",
"fancy-log": "^1.3.2",
"fbjs-scripts": "^0.8.3",
"ganache-cli": "^6.4.3",
"glob": "^7.1.3",
"glob-parent": "^3.1.0",
"gulp": "^4.0.0",

@ -11,10 +11,10 @@ import {
} from '@harmony-js/crypto';
import { isPrivateKey, add0xToString, hexToNumber } from '@harmony-js/utils';
import { Transaction } from '@harmony-js/transaction';
import { Transaction, RLPSign } from '@harmony-js/transaction';
import { Messenger, RPCMethod } from '@harmony-js/network';
import { Shards } from './types';
import { RLPSign, defaultMessenger } from './utils';
import { defaultMessenger } from './utils';
class Account {
/**

@ -1,23 +1,6 @@
import { HttpProvider, Messenger } from '@harmony-js/network';
import { Transaction, TxParams } from '@harmony-js/transaction';
import { sign, keccak256, Signature } from '@harmony-js/crypto';
import { ChainType } from '@harmony-js/utils';
export const RLPSign = (
transaction: Transaction,
prv: string,
): [Signature, string] => {
const [unsignedTxnHash, raw] = transaction.getRLPUnsigned();
const regroup: TxParams = {
...transaction.txParams,
unsignedTxnHash,
};
transaction.setParams(regroup);
const signature = sign(keccak256(unsignedTxnHash), prv);
const signed = transaction.getRLPSigned(raw, signature);
return [signature, signed];
};
export const defaultMessenger = new Messenger(
new HttpProvider('http://localhost:8545'),
ChainType.Harmony,

@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.test.json",
"include": ["src", "test", "../../typings/**/*.d.ts"],
"include": ["src", "__test__", "../../typings/**/*.d.ts"],
"references": []
}

@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.test.json",
"include": ["src", "test", "../../typings/**/*.d.ts"],
"include": ["src", "__test__", "../../typings/**/*.d.ts"],
"references": []
}

@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.test.json",
"include": ["src", "test", "../../typings/**/*.d.ts"],
"include": ["src", "__test__", "../../typings/**/*.d.ts"],
"references": []
}

@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.test.json",
"include": ["src", "test", "../../typings/**/*.d.ts"],
"include": ["src", "__test__", "../../typings/**/*.d.ts"],
"references": []
}

@ -1,5 +1,5 @@
{
"extends": "../../tsconfig.test.json",
"include": ["src", "test", "../../typings/**/*.d.ts"],
"include": ["src", "__test__", "../../typings/**/*.d.ts"],
"references": []
}

@ -3,7 +3,7 @@ import { Messenger } from '@harmony-js/network';
import { Transaction } from './transaction';
import { TxParams, TxStatus } from './types';
class TransactionFactory {
export class TransactionFactory {
static getContractAddress(tx: Transaction) {
const { from, nonce } = tx.txParams;
return getContractAddress(from, Number.parseInt(`${nonce}`, 10));
@ -39,5 +39,3 @@ class TransactionFactory {
return newTxn;
}
}
export { TransactionFactory };

@ -7,7 +7,7 @@ import {
Signature,
splitSignature,
} from '@harmony-js/crypto';
import { add0xToString, numberToHex } from '@harmony-js/utils';
import { add0xToString, numberToHex, ChainType } from '@harmony-js/utils';
import {
Messenger,
RPCMethod,
@ -24,6 +24,8 @@ import {
sleep,
TransactionEvents,
defaultMessenger,
transactionFieldsETH,
recoverETH,
} from './utils';
class Transaction {
@ -38,6 +40,7 @@ class Transaction {
private from: string;
private nonce: number | string;
private to: string;
private shardID: number | string;
private gasLimit: BN;
private gasPrice: BN;
private data: string;
@ -49,7 +52,7 @@ class Transaction {
// constructor
constructor(
params?: TxParams,
params?: TxParams | any,
messenger: Messenger = defaultMessenger,
txStatus = TxStatus.INTIALIZED,
) {
@ -63,6 +66,7 @@ class Transaction {
this.nonce = params ? params.nonce : 0;
this.gasPrice = params ? params.gasPrice : new BN(0);
this.gasLimit = params ? params.gasLimit : new BN(0);
this.shardID = params ? params.shardID : 0;
this.to = params ? params.to : '0x';
this.value = params ? params.value : new BN(0);
this.data = params ? params.data : '0x';
@ -88,7 +92,13 @@ class Transaction {
getRLPUnsigned(): [string, any[]] {
const raw: Array<string | Uint8Array> = [];
transactionFields.forEach((field: any) => {
// temp setting to be compatible with eth
const fields =
this.messenger.chainType === ChainType.Harmony
? transactionFields
: transactionFieldsETH;
fields.forEach((field: any) => {
let value = (<any>this.txParams)[field.name] || [];
value = arrayify(
hexlify(
@ -126,9 +136,11 @@ class Transaction {
}
getRLPSigned(raw: any[], signature: Signature): string {
// temp setting to be compatible with eth
const rawLength = this.messenger.chainType === ChainType.Harmony ? 10 : 9;
const sig = splitSignature(signature);
let v = 27 + (sig.recoveryParam || 0);
if (raw.length === 9) {
if (raw.length === rawLength) {
raw.pop();
raw.pop();
raw.pop();
@ -143,7 +155,12 @@ class Transaction {
}
recover(txnHash: string): Transaction {
this.setParams(recover(txnHash));
// temp setting to be compatible with eth
const recovered =
this.messenger.chainType === ChainType.Harmony
? recover(txnHash)
: recoverETH(txnHash);
this.setParams(recovered);
return this;
}
// use when using eth_sendTransaction
@ -151,6 +168,7 @@ class Transaction {
return {
from: this.txParams.from || '0x',
to: this.txParams.to || '0x',
shardID: this.txParams.shardID ? numberToHex(this.shardID) : '0x',
gas: this.txParams.gasLimit ? numberToHex(this.txParams.gasLimit) : '0x',
gasPrice: this.txParams.gasPrice
? numberToHex(this.txParams.gasPrice)
@ -168,6 +186,7 @@ class Transaction {
nonce: this.nonce || 0,
gasPrice: this.gasPrice || new BN(0),
gasLimit: this.gasLimit || new BN(0),
shardID: this.shardID || 0,
to: this.to || '0x',
value: this.value || new BN(0),
data: this.data || '0x',
@ -183,6 +202,7 @@ class Transaction {
this.nonce = params ? params.nonce : 0;
this.gasPrice = params ? params.gasPrice : new BN(0);
this.gasLimit = params ? params.gasLimit : new BN(0);
this.shardID = params ? params.shardID : 0;
this.to = params ? params.to : '0x';
this.value = params ? params.value : new BN(0);
this.data = params ? params.data : '0x';
@ -292,7 +312,7 @@ class Transaction {
} else {
this.txStatus = TxStatus.PENDING;
const currentBlock = await this.getBlockNumber();
this.blockNumbers.push(currentBlock);
this.blockNumbers.push('0x' + currentBlock.toString('hex'));
this.confirmationCheck += 1;
return false;
}
@ -315,17 +335,9 @@ class Transaction {
try {
const newBlock = await this.getBlockNumber();
// TODO: this is super ugly, must be a better way doing this
const nextBlock =
'0x' +
new BN(checkBlock.substring(2), 'hex')
.add(new BN(attempt === 0 ? attempt : 1))
.toString('hex');
const nextBlock = checkBlock.add(new BN(attempt === 0 ? attempt : 1));
if (
new BN(newBlock.substring(2), 'hex').gte(
new BN(nextBlock.substring(2), 'hex'),
)
) {
if (newBlock.gte(nextBlock)) {
checkBlock = newBlock;
if (await this.trackTx(txHash)) {
this.emitConfirm(this.txStatus);
@ -416,13 +428,13 @@ class Transaction {
this.emitter.emit(TransactionEvents.confirmation, data);
}
async getBlockNumber() {
async getBlockNumber(): Promise<BN> {
try {
const currentBlock = await this.messenger.send(RPCMethod.BlockNumber, []);
if (currentBlock.isError()) {
throw currentBlock.message;
}
return currentBlock.result;
return new BN(currentBlock.result.replace('0x', ''), 'hex');
} catch (error) {
throw error;
}

@ -6,6 +6,7 @@ export interface TxParams {
nonce: number | string;
gasLimit: BN;
gasPrice: BN;
shardID: number | string;
data: string;
value: BN;
chainId: number;

@ -13,11 +13,24 @@ import {
BN,
hexZeroPad,
recoverAddress,
Signature,
sign,
} from '@harmony-js/crypto';
import { HttpProvider, Messenger } from '@harmony-js/network';
import { TxParams } from './types';
import { Transaction } from './transaction';
export const transactionFields = [
{ name: 'nonce', length: 32, fix: false },
{ name: 'gasPrice', length: 32, fix: false, transform: 'hex' },
{ name: 'gasLimit', length: 32, fix: false, transform: 'hex' },
{ name: 'shardID', length: 32, fix: false },
{ name: 'to', length: 20, fix: true },
{ name: 'value', length: 32, fix: false, transform: 'hex' },
{ name: 'data', fix: false },
];
export const transactionFieldsETH = [
{ name: 'nonce', length: 32, fix: false },
{ name: 'gasPrice', length: 32, fix: false, transform: 'hex' },
{ name: 'gasLimit', length: 32, fix: false, transform: 'hex' },
@ -47,6 +60,91 @@ export const handleAddress = (value: string): string => {
};
export const recover = (rawTransaction: string) => {
const transaction = decode(rawTransaction);
if (transaction.length !== 10 && transaction.length !== 7) {
throw new Error('invalid rawTransaction');
}
const tx: TxParams = {
id: '0x',
from: '0x',
txnHash: '0x',
unsignedTxnHash: '0x',
nonce: new BN(strip0x(handleNumber(transaction[0]))).toNumber(),
gasPrice: new BN(strip0x(handleNumber(transaction[1]))),
gasLimit: new BN(strip0x(handleNumber(transaction[2]))),
shardID: new BN(strip0x(handleNumber(transaction[3]))).toNumber(),
to: handleAddress(transaction[4]),
value: new BN(strip0x(handleNumber(transaction[5]))),
data: transaction[6],
chainId: 0,
signature: {
r: '',
s: '',
recoveryParam: 0,
v: 0,
},
};
// Legacy unsigned transaction
if (transaction.length === 7) {
tx.unsignedTxnHash = rawTransaction;
return tx;
}
try {
tx.signature.v = new BN(strip0x(handleNumber(transaction[7]))).toNumber();
} catch (error) {
throw error;
}
tx.signature.r = hexZeroPad(transaction[8], 32);
tx.signature.s = hexZeroPad(transaction[9], 32);
if (
new BN(strip0x(handleNumber(tx.signature.r))).isZero() &&
new BN(strip0x(handleNumber(tx.signature.s))).isZero()
) {
// EIP-155 unsigned transaction
tx.chainId = tx.signature.v;
tx.signature.v = 0;
} else {
// Signed Tranasaction
tx.chainId = Math.floor((tx.signature.v - 35) / 2);
if (tx.chainId < 0) {
tx.chainId = 0;
}
let recoveryParam = tx.signature.v - 27;
const raw = transaction.slice(0, 7);
if (tx.chainId !== 0) {
raw.push(hexlify(tx.chainId));
raw.push('0x');
raw.push('0x');
recoveryParam -= tx.chainId * 2 + 8;
}
const digest = keccak256(encode(raw));
try {
tx.from = recoverAddress(digest, {
r: hexlify(tx.signature.r),
s: hexlify(tx.signature.s),
recoveryParam,
});
} catch (error) {
throw error;
}
tx.txnHash = keccak256(rawTransaction);
}
return tx;
};
export const recoverETH = (rawTransaction: string) => {
const transaction = decode(rawTransaction);
if (transaction.length !== 9 && transaction.length !== 6) {
throw new Error('invalid rawTransaction');
@ -60,6 +158,7 @@ export const recover = (rawTransaction: string) => {
nonce: new BN(strip0x(handleNumber(transaction[0]))).toNumber(),
gasPrice: new BN(strip0x(handleNumber(transaction[1]))),
gasLimit: new BN(strip0x(handleNumber(transaction[2]))),
shardID: 0,
to: handleAddress(transaction[3]),
value: new BN(strip0x(handleNumber(transaction[4]))),
data: transaction[5],
@ -146,3 +245,18 @@ export const defaultMessenger = new Messenger(
new HttpProvider('http://localhost:8545'),
ChainType.Harmony,
);
export const RLPSign = (
transaction: Transaction,
prv: string,
): [Signature, string] => {
const [unsignedTxnHash, raw] = transaction.getRLPUnsigned();
const regroup: TxParams = {
...transaction.txParams,
unsignedTxnHash,
};
transaction.setParams(regroup);
const signature = sign(keccak256(unsignedTxnHash), prv);
const signed = transaction.getRLPSigned(raw, signature);
return [signature, signed];
};

@ -0,0 +1,97 @@
import { Transaction } from '../src/transaction';
import { RLPSign } from '../src/utils';
import { TxStatus } from '../src/types';
import { HttpProvider, Messenger } from '@harmony-js/network';
import { isAddress, ChainType, hexToBN } from '@harmony-js/utils';
import { getAddressFromPrivateKey, BN } from '@harmony-js/crypto';
import txnVectors from './transactions.fixture.json';
const provider = new HttpProvider('http://localhost:9500');
describe('test sign tranction', () => {
it('should test sign transaction with ETH settings', () => {
const ethMessenger = new Messenger(provider, ChainType.Ethereum);
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < txnVectors.length; i += 1) {
const vector = txnVectors[i];
const address = getAddressFromPrivateKey(vector.privateKey);
expect(isAddress(address)).toEqual(true);
expect(address).toEqual(vector.accountAddress);
const transaction: Transaction = new Transaction(
{
gasLimit:
vector.gasLimit && vector.gasLimit !== '0x'
? hexToBN(vector.gasLimit)
: new BN(0),
gasPrice:
vector.gasPrice && vector.gasPrice !== '0x'
? hexToBN(vector.gasPrice)
: new BN(0),
to: vector.to || '0x',
value:
vector.value && vector.value !== '0x'
? hexToBN(vector.value)
: new BN(0),
data: vector.data || '0x',
nonce:
vector.nonce && vector.nonce !== '0x'
? hexToBN(vector.nonce).toNumber()
: 0,
},
ethMessenger,
TxStatus.INTIALIZED,
);
const unsigned = transaction.getRLPUnsigned();
expect(unsigned[0]).toEqual(vector.unsignedTransaction);
const signed = RLPSign(transaction, vector.privateKey);
expect(signed[0]).toEqual(vector.signedTransaction);
}
});
it('should test recover from ETHSignedtransaction', () => {
const ethMessenger = new Messenger(provider, ChainType.Ethereum);
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < txnVectors.length; i += 1) {
const vector = txnVectors[i];
const transaction: Transaction = new Transaction(
{},
ethMessenger,
TxStatus.INTIALIZED,
);
transaction.recover(vector.signedTransaction);
if (vector.gasLimit && vector.gasLimit !== '0x') {
expect(transaction.txParams.gasLimit.toString()).toEqual(
hexToBN(vector.gasLimit).toString(),
);
}
if (vector.gasPrice && vector.gasPrice !== '0x') {
expect(transaction.txParams.gasPrice.toString()).toEqual(
hexToBN(vector.gasPrice).toString(),
);
}
if (vector.nonce && vector.nonce !== '0x') {
expect(transaction.txParams.nonce).toEqual(
hexToBN(vector.nonce).toNumber(),
);
}
if (vector.data) {
expect(transaction.txParams.data).toEqual(vector.data);
}
if (vector.value && vector.value !== '0x') {
expect(transaction.txParams.value.toString()).toEqual(
hexToBN(vector.value).toString(),
);
}
if (vector.to && vector.to !== '0x') {
expect(transaction.txParams.to).toEqual(vector.to);
}
expect(transaction.txParams.from).toEqual(vector.accountAddress);
}
});
});

File diff suppressed because it is too large Load Diff

@ -0,0 +1,3 @@
{
"extends": "../tsconfig.test.json"
}

@ -10,22 +10,22 @@ const config = {
},
},
testMatch: [
// '<rootDir>/packages/**/__test__/?(*.)+(spec|test).js',
// '<rootDir>/packages/laksa-account/__test__/?(*.)+(spec|test).js',
'<rootDir>/packages/harmony-core/__test__/?(*.)+(spec|test).ts',
// '<rootDir>/packages/laksa-core/__test__/?(*.)+(spec|test).js'
// '<rootDir>/packages/laksa-core-contract/__test__/?(*.)+(spec|test).js'
'<rootDir>/packages/harmony-crypto/__test__/?(*.)+(spec|test).ts',
'<rootDir>/packages/harmony-contract/__test__/?(*.)+(spec|test).ts',
// '<rootDir>/packages/laksa-core-messenger/__test__/?(*.)+(spec|test).js'
// '<rootDir>/packages/laksa-core-provider/__test__/?(*.)+(spec|test).js',
// '<rootDir>/packages/laksa-core-transaction/__test__/?(*.)+(spec|test).js',
// '<rootDir>/packages/laksa-extend-keystore/__test__/?(*.)+(spec|test).js',
// '<rootDir>/packages/laksa-providers-http/__test__/?(*.)+(spec|test).js',
// '<rootDir>/packages/laksa-shared/__test__/?(*.)+(spec|test).js'
'<rootDir>/packages/harmony-utils/__test__/?(*.)+(spec|test).ts',
// '<rootDir>/packages/laksa-wallet/__test__/?(*.)+(spec|test).js'
// '<rootDir>/packages/laksa/__test__/?(*.)+(spec|test).js'
// '<rootDir>/packages/**/test/?(*.)+(spec|test).js',
// '<rootDir>/packages/laksa-account/test/?(*.)+(spec|test).js',
'<rootDir>/packages/harmony-core/test/?(*.)+(spec|test).ts',
// '<rootDir>/packages/laksa-core/test/?(*.)+(spec|test).js'
// '<rootDir>/packages/laksa-core-contract/test/?(*.)+(spec|test).js'
'<rootDir>/packages/harmony-crypto/test/?(*.)+(spec|test).ts',
'<rootDir>/packages/harmony-contract/test/?(*.)+(spec|test).ts',
// '<rootDir>/packages/laksa-core-messenger/test/?(*.)+(spec|test).js'
// '<rootDir>/packages/laksa-core-provider/test/?(*.)+(spec|test).js',
'<rootDir>/packages/harmony-transaction/test/?(*.)+(spec|test).ts',
// '<rootDir>/packages/laksa-extend-keystore/test/?(*.)+(spec|test).js',
// '<rootDir>/packages/laksa-providers-http/test/?(*.)+(spec|test).js',
// '<rootDir>/packages/laksa-shared/test/?(*.)+(spec|test).js'
'<rootDir>/packages/harmony-utils/test/?(*.)+(spec|test).ts',
// '<rootDir>/packages/laksa-wallet/test/?(*.)+(spec|test).js'
// '<rootDir>/packages/laksa/test/?(*.)+(spec|test).js'
],
moduleDirectories: ['src', 'node_modules'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
@ -48,6 +48,7 @@ const config = {
'packages/harmony-core/src/**/*.ts',
'packages/harmony-utils/src/**/*.ts',
'packages/harmony-crypto/src/**/*.ts',
'packages/harmony-transaction/src/**/*.ts',
],
timers: 'fake',
setupFiles: ['<rootDir>/scripts/jest/jest.setup.js'],

@ -8,6 +8,7 @@
{ "path": "packages/harmony-crypto" },
{ "path": "packages/harmony-utils" },
{ "path": "packages/harmony-network" },
{ "path": "packages/harmony-transaction" }
{ "path": "packages/harmony-transaction" },
{ "path": "packages/harmony-contract" }
]
}

Loading…
Cancel
Save