Skip to content

Commit

Permalink
add TransactionService (#220)
Browse files Browse the repository at this point in the history
* add TransactionService
  • Loading branch information
volodymyr-basiuk authored Apr 17, 2024
1 parent fc21c48 commit ced6394
Show file tree
Hide file tree
Showing 18 changed files with 215 additions and 93 deletions.
1 change: 1 addition & 0 deletions src/blockchain/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './transaction-service';
121 changes: 121 additions & 0 deletions src/blockchain/transaction-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { Block, JsonRpcProvider, Signer, TransactionReceipt, TransactionRequest } from 'ethers';

/**
* Resend transaction options
* @type ResendTxnOptions
*/
export type ResendTxnOptions = {
increasedFeesPercentage?: number;
};

/**
* Interface for TransactionService
* @public
*/
export interface ITransactionService {
/**
* Returns transaction receipt and block by transaction hash
*
* @param {string} transactionHash - transaction hash.
* @returns `Promise<{receipt?: TransactionReceipt , block?: Block}>` - returns transaction receipt and block
* @public
*/
getTransactionReceiptAndBlock(
transactionHash: string
): Promise<{ receipt?: TransactionReceipt; block?: Block }>;

/**
* Send transaction.
*
* @param {Signer} signer - transaction signer.
* @param {TransactionRequest} request - transaction request.
* @returns `Promise<txnHash: string, txnReceipt: TransactionReceipt` - returns txn hash and txn receipt.
* @public
*/
sendTransactionRequest(
signer: Signer,
request: TransactionRequest
): Promise<{ txnHash: string; txnReceipt: TransactionReceipt }>;

/**
* Resend transaction with options. Useful when `transaction underpriced` error thrown on transaction.
*
* @param {Signer} signer - transaction signer.
* @param {TransactionRequest} request - transaction request.
* @param {ResendTxnOptions} opts - resend transaction options.
* @returns `Promise<{ txnHash: string; txnReceipt: TransactionReceipt }>` -returns txn hash and txn receipt.
* @public
*/
resendTransaction(
signer: Signer,
request: TransactionRequest,
opts?: ResendTxnOptions
): Promise<{ txnHash: string; txnReceipt: TransactionReceipt }>;
}

/**
* Transaction service to provide interaction with blockchain transactions.
* allows to: get tx receipt by tx id, send and resend transaction with new fees.
* @class TransactionService
* @public
* @implements ITransactionService interface
*/
export class TransactionService implements ITransactionService {
/**
* Creates an instance of TransactionService.
* @param {JsonRpcProvider} - RPC provider
*/
constructor(private readonly _provider: JsonRpcProvider) {}

/** {@inheritDoc ITransactionService.getTransactionReceiptAndBlock} */
async getTransactionReceiptAndBlock(
txnHash: string
): Promise<{ receipt?: TransactionReceipt; block?: Block }> {
const receipt = await this._provider.getTransactionReceipt(txnHash);
const block = await receipt?.getBlock();
return { receipt: receipt || undefined, block };
}

/** {@inheritDoc ITransactionService.sendTransactionRequest} */
async sendTransactionRequest(
signer: Signer,
request: TransactionRequest
): Promise<{ txnHash: string; txnReceipt: TransactionReceipt }> {
const tx = await signer.sendTransaction(request);
const txnReceipt = await tx.wait();
if (!txnReceipt) {
throw new Error(`transaction: ${tx.hash} failed to mined`);
}
const status: number | null = txnReceipt.status;
const txnHash: string = txnReceipt.hash;

if (!status) {
throw new Error(`transaction: ${txnHash} failed to mined`);
}

return { txnHash, txnReceipt };
}

/** {@inheritDoc ITransactionService.resendTransaction} */
async resendTransaction(
signer: Signer,
request: TransactionRequest,
opts?: ResendTxnOptions
): Promise<{ txnHash: string; txnReceipt: TransactionReceipt }> {
const feeData = await this._provider.getFeeData();
let { maxFeePerGas, maxPriorityFeePerGas, gasPrice } = feeData;

if (opts?.increasedFeesPercentage) {
const multiplyVal = BigInt((opts.increasedFeesPercentage + 100) / 100);
maxFeePerGas = maxFeePerGas ? maxFeePerGas * multiplyVal : null;
maxPriorityFeePerGas = maxPriorityFeePerGas ? maxPriorityFeePerGas * multiplyVal : null;
gasPrice = gasPrice ? gasPrice * multiplyVal : null;
}

request.maxFeePerGas = maxFeePerGas;
request.maxPriorityFeePerGas = maxPriorityFeePerGas;
request.gasPrice = gasPrice;

return this.sendTransactionRequest(signer, request);
}
}
9 changes: 6 additions & 3 deletions src/identity/identity-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import {
Iden3SmtRhsCredentialStatusPublisher
} from '../credentials/status/credential-status-publisher';
import { InputGenerator, IZKProver } from '../proof';
import { ITransactionService, TransactionService } from '../blockchain';

/**
* DID creation options
Expand Down Expand Up @@ -472,6 +473,7 @@ export interface IIdentityWallet {
export class IdentityWallet implements IIdentityWallet {
private readonly _credentialStatusPublisherRegistry: CredentialStatusPublisherRegistry;
private readonly _inputsGenerator: InputGenerator;
private readonly _transactionService: ITransactionService;

/**
* Constructs a new instance of the `IdentityWallet` class
Expand All @@ -491,6 +493,7 @@ export class IdentityWallet implements IIdentityWallet {
) {
this._credentialStatusPublisherRegistry = this.getCredentialStatusPublisherRegistry(_opts);
this._inputsGenerator = new InputGenerator(this, _credentialWallet, _storage.states);
this._transactionService = new TransactionService(_storage.states.getRpcProvider());
}

private getCredentialStatusPublisherRegistry(
Expand Down Expand Up @@ -1432,13 +1435,13 @@ export class IdentityWallet implements IIdentityWallet {
);

const txId = await this.transitState(did, oldTreeState, isOldStateGenesis, ethSigner, prover);
// TODO: update to get blockNumber and blockTimestamp from function instead of passing 0s
const { receipt, block } = await this._transactionService.getTransactionReceiptAndBlock(txId);
const credsWithIden3MTPProof = await this.generateIden3SparseMerkleTreeProof(
did,
[credential],
txId,
0,
0,
receipt?.blockNumber,
block?.timestamp,
undefined,
{
revNonce: Number(authClaim.getRevocationNonce()),
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './iden3comm';
export * from './circuits';
export * from './iden3comm';
export * from './utils';
export * from './blockchain';
import * as core from '@iden3/js-iden3-core';
import * as jsonLDMerklizer from '@iden3/js-jsonld-merklization';
export { core };
Expand Down
22 changes: 8 additions & 14 deletions src/storage/blockchain/onchain-revocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Contract, JsonRpcProvider, Signer, TransactionReceipt, TransactionReque
import { Proof, NodeAuxJSON, Hash } from '@iden3/js-merkletree';
import { EthConnectionConfig } from './state';
import abi from '../blockchain/abi/CredentialStatusResolver.json';
import { ITransactionService, TransactionService } from '../../blockchain';

/**
* OnChainRevocationStore is a class that allows to interact with the onchain contract
Expand All @@ -14,6 +15,7 @@ import abi from '../blockchain/abi/CredentialStatusResolver.json';
export class OnChainRevocationStorage {
private readonly _contract: Contract;
private readonly _provider: JsonRpcProvider;
private readonly _transactionService: ITransactionService;

/**
*
Expand All @@ -35,6 +37,7 @@ export class OnChainRevocationStorage {
contract = contract.connect(this._signer) as Contract;
}
this._contract = contract;
this._transactionService = new TransactionService(this._provider);
}

/**
Expand Down Expand Up @@ -99,20 +102,11 @@ export class OnChainRevocationStorage {
maxPriorityFeePerGas
};

const tx = await this._signer.sendTransaction(request);
return tx.wait().then((txReceipt) => {
if (!txReceipt) {
throw new Error(`transaction: ${tx.hash} failed to mine`);
}
const status: number | null = txReceipt.status;
const txnHash: string = txReceipt.hash;

if (!status) {
throw new Error(`transaction: ${txnHash} failed to mine`);
}

return txReceipt;
});
const { txnReceipt } = await this._transactionService.sendTransactionRequest(
this._signer,
request
);
return txnReceipt;
}

private static convertIssuerInfo(issuer: bigint[]): Issuer {
Expand Down
13 changes: 3 additions & 10 deletions src/storage/blockchain/onchain-zkp-verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { EthConnectionConfig } from './state';
import { IOnChainZKPVerifier } from '../interfaces/onchain-zkp-verifier';
import { ContractInvokeTransactionData, ZeroKnowledgeProofResponse } from '../../iden3comm';
import abi from './abi/ZkpVerifier.json';
import { TransactionService } from '../../blockchain';

/**
* OnChainZKPVerifier is a class that allows to interact with the OnChainZKPVerifier contract
Expand Down Expand Up @@ -88,17 +89,9 @@ export class OnChainZKPVerifier implements IOnChainZKPVerifier {
maxFeePerGas,
maxPriorityFeePerGas
};
const tx = await ethSigner.sendTransaction(request);
const txnReceipt = await tx.wait();
if (!txnReceipt) {
throw new Error(`transaction: ${tx.hash} failed to mined`);
}
const status: number | null = txnReceipt.status;
const txnHash: string = txnReceipt.hash;

if (!status) {
throw new Error(`transaction: ${txnHash} failed to mined`);
}
const transactionService = new TransactionService(provider);
const { txnHash } = await transactionService.sendTransactionRequest(ethSigner, request);
response.set(txnHash, zkProof);
}

Expand Down
33 changes: 11 additions & 22 deletions src/storage/blockchain/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { StateTransitionPubSignals } from '../../circuits';
import { byteEncoder } from '../../utils';
import abi from './abi/State.json';
import { DID, getChainId, Id } from '@iden3/js-iden3-core';
import { ITransactionService, TransactionService } from '../../blockchain';

/**
* Configuration of ethereum based blockchain connection
Expand Down Expand Up @@ -55,7 +56,8 @@ const defaultEthConnectionConfig: EthConnectionConfig = {
*/
export class EthStateStorage implements IStateStorage {
public readonly stateContract: Contract;
public readonly provider: JsonRpcProvider;
private readonly provider: JsonRpcProvider;
private readonly _transactionService: ITransactionService;

/**
* Creates an instance of EthStateStorage.
Expand All @@ -65,6 +67,7 @@ export class EthStateStorage implements IStateStorage {
const config = Array.isArray(ethConfig) ? ethConfig[0] : ethConfig;
this.provider = new JsonRpcProvider(config.url);
this.stateContract = new Contract(config.contractAddress, abi, this.provider);
this._transactionService = new TransactionService(this.getRpcProvider());
}

/** {@inheritdoc IStateStorage.getLatestStateById} */
Expand Down Expand Up @@ -145,7 +148,7 @@ export class EthStateStorage implements IStateStorage {
maxPriorityFeePerGas
};

const txnHash: string = await this.sendTransactionRequest(signer, request);
const { txnHash } = await this._transactionService.sendTransactionRequest(signer, request);

return txnHash;
}
Expand Down Expand Up @@ -187,7 +190,7 @@ export class EthStateStorage implements IStateStorage {
maxPriorityFeePerGas
};

const txnHash: string = await this.sendTransactionRequest(signer, request);
const { txnHash } = await this._transactionService.sendTransactionRequest(signer, request);

return txnHash;
}
Expand Down Expand Up @@ -227,6 +230,11 @@ export class EthStateStorage implements IStateStorage {
};
}

/** {@inheritdoc IStateStorage.getRpcProvider} */
getRpcProvider(): JsonRpcProvider {
return this.provider;
}

private getStateContractAndProviderForId(id: bigint): {
stateContract: Contract;
provider: JsonRpcProvider;
Expand Down Expand Up @@ -259,23 +267,4 @@ export class EthStateStorage implements IStateStorage {

return this.ethConfig as EthConnectionConfig;
}

private async sendTransactionRequest(
signer: Signer,
request: TransactionRequest
): Promise<string> {
const tx = await signer.sendTransaction(request);
const txnReceipt = await tx.wait();
if (!txnReceipt) {
throw new Error(`transaction: ${tx.hash} failed to mined`);
}
const status: number | null = txnReceipt.status;
const txnHash: string = txnReceipt.hash;

if (!status) {
throw new Error(`transaction: ${txnHash} failed to mined`);
}

return txnHash;
}
}
9 changes: 8 additions & 1 deletion src/storage/interfaces/state.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ZKProof } from '@iden3/js-jwz';
import { Signer } from 'ethers';
import { JsonRpcProvider, Signer } from 'ethers';
import { RootInfo, StateInfo, StateProof } from '../entities/state';
import { Id } from '@iden3/js-iden3-core';
import { Hash } from '@iden3/js-merkletree';
Expand Down Expand Up @@ -71,4 +71,11 @@ export interface IStateStorage {
* @returns `Promise<RootInfo>`
*/
getGISTRootInfo(root: bigint, userId: bigint): Promise<RootInfo>;

/**
* gets RPC provider
*
* @returns `Promise<JsonRpcProvider>`
*/
getRpcProvider(): JsonRpcProvider;
}
12 changes: 11 additions & 1 deletion tests/credentials/credential-statuses/rhs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import { Blockchain, DidMethod, NetworkId } from '@iden3/js-iden3-core';
import { expect } from 'chai';
import { RHSResolver } from '../../../src/credentials';
import { CredentialStatusResolverRegistry } from '../../../src/credentials';
import { RHS_URL, SEED_USER, createIdentity } from '../../helpers';
import { RHS_URL, SEED_USER, createIdentity, RPC_URL } from '../../helpers';
import { JsonRpcProvider } from 'ethers';

describe('rhs', () => {
let idWallet: IdentityWallet;
Expand Down Expand Up @@ -61,6 +62,9 @@ describe('rhs', () => {
createdAtBlock: 0n,
replacedAtBlock: 0n
});
},
getRpcProvider() {
return new JsonRpcProvider(RPC_URL);
}
};

Expand Down Expand Up @@ -106,6 +110,9 @@ describe('rhs', () => {
createdAtBlock: 0n,
replacedAtBlock: 0n
});
},
getRpcProvider() {
return new JsonRpcProvider(RPC_URL);
}
};
const mockStateStorageForSecondState: IStateStorage = {
Expand Down Expand Up @@ -150,6 +157,9 @@ describe('rhs', () => {
createdAtBlock: 0n,
replacedAtBlock: 0n
});
},
getRpcProvider() {
return new JsonRpcProvider(RPC_URL);
}
};

Expand Down
Loading

0 comments on commit ced6394

Please sign in to comment.