Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:
DEFAULT_SERVER: https://node1.mainnet.hathor.network/v1a/
VOIDED_TX_OFFSET: 5
WS_DOMAIN: ws.wallet-service.hathor.network
AUTH_SECRET: ""
AUTH_SECRET: "foobar"
WALLET_SERVICE_LAMBDA_ENDPOINT: ""
FIREBASE_PROJECT_ID: ""
FIREBASE_PRIVATE_KEY_ID: ""
Expand All @@ -114,6 +114,9 @@ jobs:
ALERT_MANAGER_REGION: us-east-1
ALERT_MANAGER_TOPIC: alert-topic
PUSH_ALLOWED_PROVIDERS: ""
REDIS_URL: redis://127.0.0.1:6379
REDIS_PASSWORD: ""
IS_OFFLINE: 'true'

- name: Run integration tests on the daemon
run: |
Expand Down
83 changes: 83 additions & 0 deletions db/migrations/20250306170811-node_version_proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
// Dropping the table and recreating it is easier than deleting the fields then addind the new
// one, there is also no danger of losing any data since this is fetched from the fullnode
await queryInterface.dropTable('version_data');
await queryInterface.createTable('version_data', {
id: {
type: Sequelize.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true,
defaultValue: 1,
},
timestamp: {
type: Sequelize.BIGINT.UNSIGNED,
allowNull: false,
},
data: {
type: Sequelize.TEXT,
allowNull: false,
},
});
},

async down (queryInterface, Sequelize) {
await queryInterface.dropTable('version_data');
// The "old" table structure was copied from ./20210706175820-create-version-data.js
await queryInterface.createTable('version_data', {
id: {
type: Sequelize.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true,
defaultValue: 1,
},
timestamp: {
type: Sequelize.BIGINT.UNSIGNED,
allowNull: false,
},
version: {
type: Sequelize.STRING(11),
allowNull: false,
},
network: {
type: Sequelize.STRING(8),
allowNull: false,
},
min_weight: {
type: Sequelize.FLOAT.UNSIGNED,
allowNull: false,
},
min_tx_weight: {
type: Sequelize.FLOAT.UNSIGNED,
allowNull: false,
},
min_tx_weight_coefficient: {
type: Sequelize.FLOAT.UNSIGNED,
allowNull: false,
},
min_tx_weight_k: {
type: Sequelize.FLOAT.UNSIGNED,
allowNull: false,
},
token_deposit_percentage: {
type: Sequelize.FLOAT.UNSIGNED,
allowNull: false,
},
reward_spend_min_blocks: {
type: Sequelize.INTEGER.UNSIGNED,
allowNull: false,
},
max_number_inputs: {
type: Sequelize.INTEGER.UNSIGNED,
allowNull: false,
},
max_number_outputs: {
type: Sequelize.INTEGER.UNSIGNED,
allowNull: false,
},
});
}
};
2 changes: 1 addition & 1 deletion packages/wallet-service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ DB_ENDPOINT=localhost
DB_NAME=wallet_service
DB_USER=my_user
DB_PASS=password123
REDIS_HOST=localhost
REDIS_URL=localhost
REDIS_PORT=6379
AUTH_SECRET=foobar
EXPLORER_SERVICE_LAMBDA_ENDPOINT=http://localhost:3001
Expand Down
1 change: 1 addition & 0 deletions packages/wallet-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"devDependencies": {
"@types/aws-lambda": "8.10.95",
"@types/jest": "^29.5.13",
"@types/joi": "^17.2.3",
"@types/node": "18.0.4",
"@typescript-eslint/eslint-plugin": "6.7.4",
"@typescript-eslint/parser": "3.3.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/wallet-service/src/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import middy from '@middy/core';
import cors from '@middy/http-cors';
import createDefaultLogger from '@src/logger';
import { Logger } from 'winston';
import config from '@src/config';

const EXPIRATION_TIME_IN_SECONDS = 1800;

Expand Down Expand Up @@ -141,7 +142,7 @@ export const tokenHandler: APIGatewayProxyHandler = middy(async (event) => {
addr: address.toString(),
wid: walletId,
},
process.env.AUTH_SECRET,
config.authSecret,
{
expiresIn: EXPIRATION_TIME_IN_SECONDS,
jwtid: uuid4(),
Expand Down Expand Up @@ -200,7 +201,7 @@ export const bearerAuthorizer: APIGatewayTokenAuthorizerHandler = middy(async (e
try {
data = jwt.verify(
sanitizedToken,
process.env.AUTH_SECRET,
config.authSecret,
);
} catch (e) {
// XXX: find a way to return specific error to frontend or make all errors Unauthorized?
Expand Down
9 changes: 4 additions & 5 deletions packages/wallet-service/src/api/healthcheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,10 @@ import fullnode from '@src/fullnode';
import { closeDbConnection, getDbConnection } from '@src/utils';
import { APIGatewayProxyHandler } from 'aws-lambda';
import { getRedisClient, ping } from '@src/redis';
import config from '@src/config';

const mysql = getDbConnection();

const HEALTHCHECK_MAXIMUM_HEIGHT_DIFFERENCE = Number(process.env.HEALTHCHECK_MAXIMUM_HEIGHT_DIFFERENCE ?? 5)

const checkDatabaseHeight: HealthcheckCallbackResponse = async () => {
try {
const [currentHeight, fullnodeStatus] = await Promise.all([
Expand All @@ -26,10 +25,10 @@ const checkDatabaseHeight: HealthcheckCallbackResponse = async () => {

const currentFullnodeHeight = fullnodeStatus['dag']['best_block']['height'];

if (currentFullnodeHeight - currentHeight < HEALTHCHECK_MAXIMUM_HEIGHT_DIFFERENCE) {
if (currentFullnodeHeight - currentHeight < config.healthCheckMaximumHeightDifference) {
return new HealthcheckCallbackResponse({
status: HealthcheckStatus.PASS,
output: `Database and fullnode heights are within ${HEALTHCHECK_MAXIMUM_HEIGHT_DIFFERENCE} blocks difference`,
output: `Database and fullnode heights are within ${config.healthCheckMaximumHeightDifference} blocks difference`,
});
} else {
return new HealthcheckCallbackResponse({
Expand Down Expand Up @@ -142,4 +141,4 @@ export const getHealthcheck: APIGatewayProxyHandler = middy(async (event) => {
statusCode: response.getHttpStatusCode(),
body: response.toJson(),
};
});
});
13 changes: 7 additions & 6 deletions packages/wallet-service/src/api/txProposalCreate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { v4 as uuidv4 } from 'uuid';
import Joi from 'joi';

import { ApiError } from '@src/api/errors';
import { maybeRefreshWalletConstants, walletIdProxyHandler } from '@src/commons';
import { walletIdProxyHandler } from '@src/commons';
import {
createTxProposal,
getUtxos,
Expand All @@ -29,6 +29,8 @@ import { closeDbConnection, getDbConnection, getUnixTimestamp } from '@src/utils
import middy from '@middy/core';
import cors from '@middy/http-cors';
import { constants, Network, Transaction, helpersUtils } from '@hathor/wallet-lib';
import { getFullnodeData } from '@src/nodeConfig';
import config from '@src/config';

const mysql = getDbConnection();

Expand All @@ -42,7 +44,7 @@ const bodySchema = Joi.object({
* This lambda is called by API Gateway on POST /txproposals
*/
export const create = middy(walletIdProxyHandler(async (walletId, event) => {
await maybeRefreshWalletConstants(mysql);
const versionData = await getFullnodeData(mysql);

const eventBody = (function parseBody(body) {
try {
Expand All @@ -67,10 +69,9 @@ export const create = middy(walletIdProxyHandler(async (walletId, event) => {
}

const body = value;
const tx: Transaction = helpersUtils.createTxFromHex(body.txHex, new Network(process.env.NETWORK));
const tx: Transaction = helpersUtils.createTxFromHex(body.txHex, new Network(config.network));

// XXX: DEC-0001
if (tx.outputs.length > constants.MAX_OUTPUTS) {
if (tx.outputs.length > versionData.maxNumberOutputs) {
return closeDbAndGetError(mysql, ApiError.TOO_MANY_OUTPUTS, { outputs: tx.outputs.length });
}

Expand Down Expand Up @@ -111,7 +112,7 @@ export const create = middy(walletIdProxyHandler(async (walletId, event) => {
return closeDbAndGetError(mysql, ApiError.INPUTS_ALREADY_USED);
}

if (inputUtxos.length > constants.MAX_OUTPUTS) {
if (inputUtxos.length > versionData.maxNumberOutputs) {
return closeDbAndGetError(mysql, ApiError.TOO_MANY_INPUTS, { inputs: inputUtxos.length });
}

Expand Down
3 changes: 2 additions & 1 deletion packages/wallet-service/src/api/txProposalSend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
import { closeDbAndGetError } from '@src/api/utils';
import middy from '@middy/core';
import cors from '@middy/http-cors';
import config from '@src/config';

const mysql = getDbConnection();

Expand Down Expand Up @@ -91,7 +92,7 @@ export const send: APIGatewayProxyHandler = middy(walletIdProxyHandler(async (wa

const now = getUnixTimestamp();
const txProposalInputs = await getTxProposalInputs(mysql, txProposalId);
const tx = hathorLib.helpersUtils.createTxFromHex(txHex, new hathorLib.Network(process.env.NETWORK));
const tx = hathorLib.helpersUtils.createTxFromHex(txHex, new hathorLib.Network(config.network));

if (tx.inputs.length !== txProposalInputs.length) {
return closeDbAndGetError(mysql, ApiError.TX_PROPOSAL_NO_MATCH);
Expand Down
3 changes: 2 additions & 1 deletion packages/wallet-service/src/api/txhistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import { walletIdProxyHandler } from '@src/commons';
import middy from '@middy/core';
import cors from '@middy/http-cors';
import Joi from 'joi';
import config from '@src/config';

const MAX_COUNT = parseInt(process.env.TX_HISTORY_MAX_COUNT || '50', 10);
const MAX_COUNT = config.txHistoryMaxCount;
const htrToken = hathorLib.constants.NATIVE_TOKEN_UID;

const paramsSchema = Joi.object({
Expand Down
12 changes: 2 additions & 10 deletions packages/wallet-service/src/api/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,12 @@
import { APIGatewayProxyHandler } from 'aws-lambda';
import 'source-map-support/register';

import {
getVersionData,
} from '@src/db';
import {
FullNodeVersionData,
} from '@src/types';
import {
closeDbConnection,
getDbConnection,
} from '@src/utils';
import { warmupMiddleware } from '@src/api/utils';
import { maybeRefreshWalletConstants } from '@src/commons';
import { getRawFullnodeData } from '@src/nodeConfig'
import middy from '@middy/core';
import cors from '@middy/http-cors';

Expand All @@ -31,9 +25,7 @@ const mysql = getDbConnection();
* This lambda is called by API Gateway on GET /version
*/
export const get: APIGatewayProxyHandler = middy(async () => {
await maybeRefreshWalletConstants(mysql);

const versionData: FullNodeVersionData = await getVersionData(mysql);
const versionData = await getRawFullnodeData(mysql);

await closeDbConnection(mysql);

Expand Down
16 changes: 9 additions & 7 deletions packages/wallet-service/src/api/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ import Joi from 'joi';
import createDefaultLogger from '@src/logger';
import { Severity } from '@wallet-service/common/src/types';
import { addAlert } from '@wallet-service/common/src/utils/alerting.utils';
import config from '@src/config';

const mysql = getDbConnection();

const MAX_LOAD_WALLET_RETRIES: number = parseInt(process.env.MAX_LOAD_WALLET_RETRIES || '5', 10);
const MAX_LOAD_WALLET_RETRIES: number = config.maxLoadWalletRetries;

/*
* Get the status of a wallet
Expand All @@ -67,7 +68,7 @@ export const get: APIGatewayProxyHandler = middy(walletIdProxyHandler(async (wal

// If the env requires to validate the first address
// then we must set the firstAddress field as required
const shouldConfirmFirstAddress = process.env.CONFIRM_FIRST_ADDRESS === 'true';
const shouldConfirmFirstAddress = config.confirmFirstAddress;
const firstAddressJoi = shouldConfirmFirstAddress ? Joi.string().required() : Joi.string();

const loadBodySchema = Joi.object({
Expand All @@ -92,14 +93,14 @@ const loadBodySchema = Joi.object({
/* istanbul ignore next */
export const invokeLoadWalletAsync = async (xpubkey: string, maxGap: number): Promise<void> => {
const client = new LambdaClient({
endpoint: process.env.STAGE === 'dev'
endpoint: config.stage === 'dev'
? 'http://localhost:3002'
: `https://lambda.${process.env.AWS_REGION}.amazonaws.com`,
region: process.env.AWS_REGION,
: `https://lambda.${config.awsRegion}.amazonaws.com`,
region: config.awsRegion,
});
const command = new InvokeCommand({
// FunctionName is composed of: service name - stage - function name
FunctionName: `${process.env.SERVICE_NAME}-${process.env.STAGE}-loadWalletAsync`,
FunctionName: `${config.serviceName}-${config.stage}-loadWalletAsync`,
InvocationType: 'Event',
Payload: JSON.stringify({ xpubkey, maxGap }),
});
Expand Down Expand Up @@ -276,7 +277,7 @@ export const load: APIGatewayProxyHandler = middy(async (event) => {

const xpubkeyStr = value.xpubkey;
const authXpubkeyStr = value.authXpubkey;
const maxGap = parseInt(process.env.MAX_ADDRESS_GAP, 10);
const maxGap = config.maxAddressGap;

const timestamp = value.timestamp;
const xpubkeySignature = value.xpubkeySignature;
Expand Down Expand Up @@ -442,6 +443,7 @@ export const loadWalletFailed: Handler<SNSEvent> = async (event) => {
);
}
} catch (e) {
logger.error('Error during loadWalletFailed', e);
await addAlert(
'Failed to handle loadWalletFailed event',
`Failed to process the loadWalletFailed event. This indicates that wallets failed to load and we weren't able to recover, please check the logs as soon as possible.`,
Expand Down
Loading