Skip to content

Commit

Permalink
Sprint 43 (#1126)
Browse files Browse the repository at this point in the history
* feat: scheduler backend created

Signed-off-by: Alberto Molina <[email protected]>

* fix: backend scheduler

Signed-off-by: Alberto Molina <[email protected]>

* feat: backend scheduler expired added

Signed-off-by: Alberto Molina <[email protected]>

* fix: backend expire bug fixed

Signed-off-by: Alberto Molina <[email protected]>

* feat: web status colors added

Signed-off-by: Alberto Molina <[email protected]>

* feat: cli expired added

Signed-off-by: Alberto Molina <[email protected]>

* fix: receipt check in scheduler

Signed-off-by: Alberto Molina <[email protected]>

* fix: scheduler singleton

Signed-off-by: Alberto Molina <[email protected]>

* feat: error status added to backend

Signed-off-by: Alberto Molina <[email protected]>

* feat: web orange color and red

Signed-off-by: Alberto Molina <[email protected]>

* feat: error added to cli

Signed-off-by: Alberto Molina <[email protected]>

* fix: web tests snapshots updated

Signed-off-by: Alberto Molina <[email protected]>

* fix: web submit button removed if start date not reached

Signed-off-by: Alberto Molina <[email protected]>

* feat: startdate in red if not yet reached

Signed-off-by: Alberto Molina <[email protected]>

* feat: start date tooltip

Signed-off-by: Mario Francia <[email protected]>

* feat: removed red color bg

Signed-off-by: Mario Francia <[email protected]>

* fix: icon start date position

Signed-off-by: Mario Francia <[email protected]>

* fix: version number updated  to 1.21.0

Signed-off-by: Alberto Molina <[email protected]>

* docs: scheduler added to backend documentation

Signed-off-by: Alberto Molina <[email protected]>

* feat: cli if startdate not reached submit is disabled

Signed-off-by: Alberto Molina <[email protected]>

---------

Signed-off-by: Alberto Molina <[email protected]>
Signed-off-by: Mario Francia <[email protected]>
Co-authored-by: Mario Francia <[email protected]>
  • Loading branch information
AlbertoMolinaIoBuilders and M-Francia authored May 21, 2024
1 parent 55d4a24 commit 7c9cf66
Show file tree
Hide file tree
Showing 31 changed files with 424 additions and 228 deletions.
1 change: 1 addition & 0 deletions FACTORY_VERSION.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
| Version | Contract name | Address | Network |
|---------| -------------- | ----------- | ------- |
| 1.21.0 | FactoryAddress | 0.0.2167166 | Testnet |
| 1.20.1 | FactoryAddress | 0.0.2167166 | Testnet |
| 1.20.0 | FactoryAddress | 0.0.2167166 | Testnet |
| 1.19.0 | FactoryAddress | 0.0.2167166 | Testnet |
Expand Down
12 changes: 11 additions & 1 deletion backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ Whenever users need to submit to the DLT network a transaction (Cash In, Freeze,

- They will first add the "raw transaction" (unsigned string of bytes) to the backend's database using the backend's api.
- After the transaction is created, key owners will have the possibility to asyncronously sign the transaction.
- Once all the required keys have signed the transaction, anyone can retrieve the transaction and its signatures, concatenate them and submit it to the Hedera network.
- Once all the required keys have signed the transaction, anyone can retrieve the transaction and its signatures, concatenate them and submit it to the Hedera network. (*)

> (*) If the transaction is not submited by anyone yet it has been properly signed, the __scheduled job__ will pick it up and submit it automatically.
# Architecture

Expand All @@ -56,10 +58,18 @@ The backend is made of two components:
- _signatures_: List of signatures already added to the transaction.
- _keyList_: List of public keys associated to the account the transaction belongs to. These are the keys that have the right to sign the transaction.
- _signedKeys_: List of public keys that have already signed the transaction (their signatures have been added to _signatures_).
- _startDate_: The date and time at which the transaction can eb submitted to the DLT.
- _status_: Current status of the transaction. There are two options:
- PENDING: transaction signature is still in progress.
- SIGNED: all required keys have already signed the transaction, we can submit it to the Hedera DLT.
- EXPIRED : all transactions that cannot be submitted to the DLT anymore. Transactions can be submitted within a 3-minute window from their startDate. After that, the transaction is considered "expired" and can no longer be submitted (it must be recreated).
- ERROR : a transaction that is no longer valid. Some transactions are valid when created but become invalid due to subsequent transactions submitted immediately after their creation. In such cases, the transaction status is set to "error" by the "scheduled job" when trying to submit it.
- _threshold_: minimum number of keys that must sign the transaction before we can submit to the network (update its status to _SIGNED_).
- **Scheduled Job**: the scheduled job is a job that runs every 30 seconds and scans the DB looking for two types of transactions.
- Transactions ready to submit : transactions that have been signed by all the necessary keys and we are currently within its 3-minute validity time windows (3 minutes from its start date). The scheduled job will pick these transactions and submit them to the DLT.
- If the transaction succeeds, it is immediatly removed frm the DB.
- If the transaction fails, its status is set to "ERROR"
- Expired Transactions : These are "pending" or "signed" transactions whose start date is more than 3 minutes ago. The scheduled job will change the status of these transactions to "expired."

# Overview

Expand Down
40 changes: 38 additions & 2 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hashgraph/stablecoin-npm-backend",
"version": "1.20.1",
"version": "1.21.0",
"description": "",
"author": "",
"license": "Apache-2.0",
Expand Down Expand Up @@ -28,6 +28,7 @@
"@nestjs/config": "^3.2.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/schedule": "^4.0.2",
"@nestjs/swagger": "^7.3.0",
"@nestjs/typeorm": "^10.0.2",
"@types/elliptic": "^6.4.18",
Expand Down Expand Up @@ -87,4 +88,4 @@
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
}
7 changes: 6 additions & 1 deletion backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import { ConfigModule, ConfigService } from '@nestjs/config';
import Transaction from './transaction/transaction.entity';
import { LoggerService } from './logger/logger.service';
import { RequestIDMiddleware } from './middleware/requestId.middleware';
import { ScheduleModule } from '@nestjs/schedule';
import TransactionService from './transaction/transaction.service';
import { JobsModule } from './jobs/jobs.module.js';

@Module({
imports: [
Expand All @@ -46,8 +49,10 @@ import { RequestIDMiddleware } from './middleware/requestId.middleware';
}),
}),
TransactionModule,
ScheduleModule.forRoot(),
JobsModule,
],
providers: [LoggerService],
providers: [LoggerService, TransactionService],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
Expand Down
200 changes: 200 additions & 0 deletions backend/src/jobs/autoSubmit.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
*
* Hedera Stablecoin SDK
*
* Copyright (C) 2023 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import TransactionService from '../transaction/transaction.service';
import { TransactionStatus } from '../transaction/status.enum';
import {
Transaction,
Client,
PublicKey,
TransactionResponse,
TransactionReceipt,
Status,
} from '@hashgraph/sdk';
import { GetTransactionsResponseDto } from '../transaction/dto/get-transactions-response.dto';
import { hexToUint8Array } from '../utils/utils';
import { LoggerService } from '../logger/logger.service.js';
import LogMessageDTO from '../logger/dto/log-message.dto.js';

@Injectable()
export default class AutoSubmitService {
constructor(
private readonly transactionService: TransactionService,
private readonly loggerService: LoggerService,
) {}

@Cron(CronExpression.EVERY_30_SECONDS)
async run() {
let done = false;
let page = 1;
const limit = 100;
const transactionsToSubmit = [];
const transactionsToExpire = [];

this.loggerService.log(
new LogMessageDTO('', 'Running auto submit job', null),
);

do {
const allTransactions = await this.transactionService.getAll(
null,
null,
null,
null,
{
page,
limit,
},
);

const currentDate: Date = new Date();
const currentUTCDate_Minus_3_Minutes = new Date(currentDate.getTime());
currentUTCDate_Minus_3_Minutes.setSeconds(
currentUTCDate_Minus_3_Minutes.getSeconds() - 180,
);

const submit = allTransactions.items.filter(
(tx) =>
tx.status == TransactionStatus.SIGNED &&
new Date(tx.start_date) <= currentDate &&
new Date(tx.start_date) > currentUTCDate_Minus_3_Minutes,
);

const expire = allTransactions.items.filter(
(tx) =>
tx.status != TransactionStatus.EXPIRED &&
tx.status != TransactionStatus.ERROR &&
new Date(tx.start_date) <= currentUTCDate_Minus_3_Minutes,
);

transactionsToSubmit.push(...submit);
transactionsToExpire.push(...expire);

page++;

if (page >= (await allTransactions.meta.totalPages)) done = true;
} while (done == false);

this.loggerService.log(
new LogMessageDTO(
'',
'transactionsToExpire : ' + transactionsToExpire.length,
null,
),
);

this.loggerService.log(
new LogMessageDTO(
'',
'transactionsToSubmit : ' + transactionsToSubmit.length,
null,
),
);

// EXPIRE TRANSACTIONS
await transactionsToExpire.forEach(async (t) => {
await this.transactionService.updateStatus(
t.id,
TransactionStatus.EXPIRED,
);
});

// SUBMIT SIGNED TRANSACTIONS TO HEDERA
await transactionsToSubmit.forEach(async (t) => {
const success = await this.submit(t);
if (success) {
const txId = t.id;
await this.transactionService.delete(t.id);
this.loggerService.log(
new LogMessageDTO('', `Removed transaction Id : ${txId}`, null),
);
} else {
await this.transactionService.updateStatus(
t.id,
TransactionStatus.ERROR,
);
}
});
}

async submit(transaction: GetTransactionsResponseDto): Promise<boolean> {
try {
const client: Client = Client.forName(transaction.network);

let deserializedTransaction = Transaction.fromBytes(
hexToUint8Array(transaction.transaction_message),
);

for (let i = 0; i < transaction.signatures.length; i++) {
const publicKey_i = PublicKey.fromString(transaction.signed_keys[i]);
deserializedTransaction = deserializedTransaction.addSignature(
publicKey_i,
hexToUint8Array(transaction.signatures[i]),
);
}

const submitTx = await deserializedTransaction.execute(client);
this.loggerService.log(
new LogMessageDTO(
'',
`Submitted transaction Id : ${submitTx.transactionId}`,
null,
),
);

await this.checkReceipt(submitTx, client);

this.loggerService.log(
new LogMessageDTO(
'',
`transaction Id : ${submitTx.transactionId} SUCCESS`,
null,
),
);

return true;
} catch (error) {
this.loggerService.error(
new LogMessageDTO(
'',
`Error submitting the transaction : ${(error as Error).message}`,
null,
),
);

return false;
}
}

private async checkReceipt(
transactionResponse: TransactionResponse,
client: Client,
): Promise<void> {
const transactionReceipt: TransactionReceipt | undefined =
await transactionResponse.getReceipt(client);
if (transactionReceipt.status !== Status.Success) {
throw new Error(
`transactions did not succeed. Status : ${transactionReceipt.status}`,
);
}
}
}
9 changes: 9 additions & 0 deletions backend/src/jobs/jobs.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import AutoSubmitService from './autoSubmit.service';
import { TransactionModule } from '../transaction/transaction.module';

@Module({
imports: [TransactionModule],
providers: [AutoSubmitService],
})
export class JobsModule {}
6 changes: 4 additions & 2 deletions backend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,13 @@ async function bootstrap() {
origin: function (origin, callback) {
if (!origin) return callback(null, true);
if (allowedOrigins.indexOf(origin) === -1) {
const msg = 'The CORS policy for this site does not allow access from the specified Origin.';
const msg =
'The CORS policy for this site does not allow access from the specified Origin.';
return callback(new Error(msg), false);
}
return callback(null, true);
}, allowedHeaders:
},
allowedHeaders:
'Origin, X-Requested-With, Content-Type, Accept, Authorization',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
});
Expand Down
Loading

0 comments on commit 7c9cf66

Please sign in to comment.