Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: process tx after push #813

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
94e96e8
feat: process tx after push
r4mmer Feb 4, 2025
8b978c4
chore: add comments explaining steps
r4mmer Feb 5, 2025
8d9b598
chore: linter changes
r4mmer Feb 5, 2025
b55524f
feat: add token being created on storage
r4mmer Feb 6, 2025
7882a17
feat: create token txs should use the uid as output.token
r4mmer Feb 7, 2025
457dc12
Merge remote-tracking branch 'origin/master' into feat/push-tx-and-se…
r4mmer Feb 7, 2025
32f21f6
feat: fetch tx from api when it is not in storage
r4mmer Feb 10, 2025
cfe92eb
chore: linter changes
r4mmer Feb 10, 2025
d153f63
chore: remove typing on api
r4mmer Feb 10, 2025
6a23eb6
chore: adding array bounds to error message
r4mmer Feb 11, 2025
53b0eb1
feat: change index test
r4mmer Feb 11, 2025
a72b608
feat: check we already have the token first
r4mmer Feb 11, 2025
f48b51f
Merge remote-tracking branch 'origin/master' into feat/push-tx-and-se…
r4mmer Feb 21, 2025
e893e7c
chore: review changes
r4mmer Feb 21, 2025
9abd019
chore: linter changes
r4mmer Feb 24, 2025
e5cef34
tests: transaction util test
r4mmer Feb 24, 2025
3371335
Merge branch 'master' into feat/push-tx-and-send-to-storage
r4mmer Feb 24, 2025
d8f50a0
Merge remote-tracking branch 'origin/master' into feat/push-tx-and-se…
r4mmer Feb 27, 2025
aabb513
chore: fix type name on import
r4mmer Feb 27, 2025
2073342
chore: refactor to use hydrate util
r4mmer Feb 28, 2025
2723011
tests: fix expected error message
r4mmer Feb 28, 2025
d461387
chore: prefer storage method
r4mmer Feb 28, 2025
dddbedf
tests: fix mockup calls
r4mmer Feb 28, 2025
c16860f
chore: linter changed
r4mmer Feb 28, 2025
9dda85d
feat: create token array should use the created token
r4mmer Feb 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions __tests__/integration/helpers/wallet.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export async function stopAllWallets() {
*
* @return {Promise<CreateNewTokenResponse>}
*/
export async function createTokenHelper(hWallet, name, symbol, amount, options) {
export async function createTokenHelper(hWallet, name, symbol, amount, options = {}) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to line
All changes in this file are related to typing, some jsdoc types that were not converted into ts types.
This removes some typing alerts from the tests.
Obs: We don't have alerts on the CI because we exclude tests from tsc

const newTokenResponse = await hWallet.createNewToken(name, symbol, amount, options);
const tokenUid = newTokenResponse.hash;
await waitForTxReceived(hWallet, tokenUid);
Expand Down Expand Up @@ -271,10 +271,9 @@ export function waitForWalletReady(hWallet) {
* Waits for the wallet to receive the transaction from a websocket message
* and process the history
*
* @param {HathorWallet} hWallet
* @param {string} txId
* @param {number} [timeout] Timeout in milisseconds. Default value defined on test-constants.
* @returns {Promise<IHistoryTx>}
* @param hWallet
* @param txId
* @param [timeout] Timeout in milisseconds. Default value defined on test-constants.
*/
export async function waitForTxReceived(
hWallet: HathorWallet,
Expand All @@ -289,7 +288,7 @@ export async function waitForTxReceived(
timeoutReached = true;
}, timeoutPeriod);

let storageTx = await hWallet.getTx(txId);
let storageTx = (await hWallet.getTx(txId)) as IHistoryTx;

// We consider that the tx was received after it's in the storage
// and the history processing is finished
Expand All @@ -300,7 +299,7 @@ export async function waitForTxReceived(

// Tx not found, wait 1s before trying again
await delay(1000);
storageTx = await hWallet.getTx(txId);
storageTx = (await hWallet.getTx(txId)) as IHistoryTx;
}

// Clean timeout handler
Expand Down
10 changes: 3 additions & 7 deletions __tests__/integration/utils/core.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@

/**
* Simple way to wait asynchronously before continuing the funcion. Does not block the JS thread.
* @param {number} ms Amount of milliseconds to delay
* @returns {Promise<unknown>}
* @param ms Amount of milliseconds to delay
*/
export async function delay(ms) {
export async function delay(ms: number): Promise<unknown> {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
Expand All @@ -19,11 +18,8 @@ export async function delay(ms) {
/**
* Generates a random positive integer between the maximum and minimum values,
* with the default minimum equals zero
* @param {number} max
* @param {number} [min=0]
* @returns {number} Random number
*/
export function getRandomInt(max, min = 0) {
export function getRandomInt(max: number, min: number = 0): number {
const _min = Math.ceil(min);
const _max = Math.floor(max);
return Math.floor(Math.random() * (_max - _min + 1)) + _min;
Expand Down
5 changes: 2 additions & 3 deletions src/api/txApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/

import { AxiosResponse } from 'axios';
import { createRequestInstance } from './axiosInstance';
import { transformJsonBigIntResponse } from '../utils/bigint';
import { TransactionSchema, transactionSchema } from './schemas/txApi';
import { transactionSchema } from './schemas/txApi';

/**
* Api calls for transaction
Expand Down Expand Up @@ -78,7 +77,7 @@ const txApi = {
* @memberof ApiTransaction
* @inner
*/
getTransaction(id, resolve): Promise<void | AxiosResponse<TransactionSchema>> {
getTransaction(id, resolve): Promise<void> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getTransaction method does not return a TransactionSchema, it passes the TransactionSchema instance to the resolve argument.
We could type resolve: (a: TransactionSchema) => void which is correct, but some other parts of the lib break due to being reliant on the implicit typing.

const data = { id };
return this.getTransactionBase(data, resolve, transactionSchema);
},
Expand Down
12 changes: 12 additions & 0 deletions src/models/p2pkh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { intToBytes } from '../utils/buffer';
import helpers from '../utils/helpers';
import Address from './address';
import { IHistoryOutputDecoded } from '../types';

type optionsType = {
timelock?: number | null | undefined;
Expand Down Expand Up @@ -82,6 +83,17 @@ class P2PKH {
return util.buffer.concat(arr);
}

/**
* Build the original decoded script
*/
toData(): IHistoryOutputDecoded {
return {
type: this.getType(),
address: this.address.base58,
timelock: this.timelock,
};
}

/**
* Identify a script as P2PKH or not.
*
Expand Down
12 changes: 12 additions & 0 deletions src/models/p2sh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { OP_GREATERTHAN_TIMESTAMP, OP_HASH160, OP_EQUAL } from '../opcodes';
import helpers from '../utils/helpers';
import { intToBytes } from '../utils/buffer';
import Address from './address';
import { IHistoryOutputDecoded } from '../types';

type optionsType = {
timelock?: number | null | undefined;
Expand Down Expand Up @@ -49,6 +50,17 @@ class P2SH {
return 'p2sh';
}

/**
* Build the original decoded script
*/
toData(): IHistoryOutputDecoded {
return {
type: this.getType(),
address: this.address.base58,
timelock: this.timelock,
};
}

/**
* Create a P2SH script
*
Expand Down
11 changes: 11 additions & 0 deletions src/models/script_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { util } from 'bitcore-lib';
import buffer from 'buffer';
import { OP_CHECKSIG } from '../opcodes';
import helpers from '../utils/helpers';
import { IHistoryOutputDecoded } from '../types';

class ScriptData {
// String of data to store on the script
Expand All @@ -34,6 +35,16 @@ class ScriptData {
return 'data';
}

/**
* Build the original decoded script
*/
toData(): IHistoryOutputDecoded {
return {
type: this.getType(),
data: this.data,
};
}

/**
* Create an output script from data
*
Expand Down
19 changes: 19 additions & 0 deletions src/new/sendTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
} from '../types';
import Transaction from '../models/transaction';
import { bestUtxoSelection } from '../utils/utxo';
import { addCreatedTokenFromTx } from '../utils/storage';
import CreateTokenTransaction from '../models/create_token_transaction';

export interface ISendInput {
txId: string;
Expand Down Expand Up @@ -422,6 +424,23 @@ export default class SendTransaction extends EventEmitter {
throw new WalletError(ErrorMessages.TRANSACTION_IS_NULL);
}
this.transaction.updateHash();
if (this.storage) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the core of the PR.

When we push a transaction we need to convert it from a Transaction instance to a IHistoryTx (which is what is received via websocket) and add it to the storage with normal processing.

If we are creating a token we should also add it to the tokens here because the fullnode may not know that this token exists when we ask for the name and symbol for this UID. It also saves us from making an extraneous API call.

// Add transaction to storage and process storage
(async (storage: IStorage, transaction: Transaction) => {
// Get the transaction as a history object
const historyTx = await transactionUtils.convertTransactionToHistoryTx(
transaction,
storage
);
// Add transaction to storage
await storage.addTx(historyTx);
// Add token from a create token transaction to the storage
// This just returns if the transaction is not a CREATE_TOKEN_TX
await addCreatedTokenFromTx(transaction as CreateTokenTransaction, storage);
// Process new transaction
await storage.processNewTx(historyTx);
})(this.storage, this.transaction);
}
this.emit('send-tx-success', this.transaction);
resolve(this.transaction);
} else {
Expand Down
8 changes: 6 additions & 2 deletions src/storage/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,12 @@ export class Storage implements IStorage {
* Set the native token config on the store
*/
async saveNativeToken(): Promise<void> {
if ((await this.store.getToken(NATIVE_TOKEN_UID)) === null) {
await this.store.saveToken(this.getNativeTokenData());
await this.addToken(this.getNativeTokenData());
}

async addToken(data: ITokenData): Promise<void> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing docstring

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added docstring

if ((await this.store.getToken(data.uid)) === null) {
await this.store.saveToken(data);
}
}

Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ export interface IStorage {
processNewTx(tx: IHistoryTx): Promise<void>;

// Tokens
addToken(data: ITokenData): Promise<void>;
isTokenRegistered(tokenUid: string): Promise<boolean>;
registerToken(token: ITokenData): Promise<void>;
unregisterToken(tokenUid: string): Promise<void>;
Expand Down
32 changes: 30 additions & 2 deletions src/utils/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
HistorySyncFunction,
WalletType,
IUtxo,
ITokenData,
} from '../types';
import walletApi from '../api/wallet';
import helpers from './helpers';
Expand All @@ -35,8 +36,10 @@
NANO_CONTRACTS_VERSION,
LOAD_WALLET_MAX_RETRY,
LOAD_WALLET_RETRY_SLEEP,
CREATE_TOKEN_TX_VERSION,
} from '../constants';
import { AddressHistorySchema, GeneralTokenInfoSchema } from '../api/schemas/wallet';
import CreateTokenTransaction from '../models/create_token_transaction';

/**
* Get history sync method for a given mode
Expand Down Expand Up @@ -515,8 +518,12 @@

const { name, symbol } = response;
const tokenData = { uid, name, symbol };
// saveToken will ignore the meta and save only the token config
await store.saveToken(tokenData);

const storeToken = await store.getToken(uid);
if (storeToken === null) {
// saveToken will ignore the meta and save only the token config
await store.saveToken(tokenData);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this the same as the new storage.addToken method created?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, changed to use the storage method

}
}
}
Expand Down Expand Up @@ -890,3 +897,24 @@
// Remove utxo from locked utxos so that it is not processed again
await store.unlockUtxo(lockedUtxo);
}

export async function addCreatedTokenFromTx(
tx: CreateTokenTransaction,
storage: IStorage
): Promise<void> {
if (tx.version !== CREATE_TOKEN_TX_VERSION) {
return;
}

if (!tx.hash) {
throw new Error('Cannot infer UID from transaction without hash');

Check warning on line 910 in src/utils/storage.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/storage.ts#L910

Added line #L910 was not covered by tests
}

const tokenInfo: ITokenData = {
uid: tx.hash,
name: tx.name,
symbol: tx.symbol,
};

await storage.addToken(tokenInfo);
}
Loading
Loading