diff --git a/.codebuild/build.sh b/.codebuild/build.sh index c3fc85bc..a45243cd 100644 --- a/.codebuild/build.sh +++ b/.codebuild/build.sh @@ -66,7 +66,6 @@ deploy_hathor_network_account() { send_slack_message "New version deployed to mainnet-staging: ${GIT_REF_TO_DEPLOY}" elif expr "${GIT_REF_TO_DEPLOY}" : "v.*" >/dev/null; then echo $GIT_REF_TO_DEPLOY > /tmp/docker_image_tag - make build-daemon; # --- Testnet --- # Gets all env vars with `testnet_` prefix and re-exports them without the prefix @@ -75,29 +74,35 @@ deploy_hathor_network_account() { done make migrate; + make build-daemon; make deploy-lambdas-testnet; + # The idea here is that if the lambdas deploy fail, the built image won't be pushed: + make push-daemon; # Unsets all the testnet env vars so we make sure they don't leak to other deploys for var in "${!testnet_@}"; do unset ${var#testnet_} done + send_slack_message "New version deployed to testnet-production: ${GIT_REF_TO_DEPLOY}" + # --- Mainnet --- # Gets all env vars with `mainnet_` prefix and re-exports them without the prefix for var in "${!mainnet_@}"; do export ${var#mainnet_}="${!var}" done make migrate; + make build-daemon; make deploy-lambdas-mainnet; + # The idea here is that if the lambdas deploy fail, the built image won't be pushed: + make push-daemon; # Unsets all the mainnet env vars so we make sure they don't leak to other deploys for var in "${!mainnet_@}"; do unset ${var#mainnet_} done - # The idea here is that if the lambdas deploy fail, the built image won't be pushed: - make push-daemon; - send_slack_message "New version deployed to testnet-production and mainnet-production: ${GIT_REF_TO_DEPLOY}" + send_slack_message "New version deployed to mainnet-production: ${GIT_REF_TO_DEPLOY}" else # Gets all env vars with `dev_` prefix and re-exports them without the prefix for var in "${!dev_@}"; do diff --git a/packages/wallet-service/src/api/auth.ts b/packages/wallet-service/src/api/auth.ts index f97bdfc7..48a6946d 100644 --- a/packages/wallet-service/src/api/auth.ts +++ b/packages/wallet-service/src/api/auth.ts @@ -235,5 +235,4 @@ export const bearerAuthorizer: APIGatewayTokenAuthorizerHandler = middy(async (e } return _generatePolicy(walletId, 'Deny', event.methodArn, logger); -}).use(cors()) - .use(errorHandler()); +}).use(cors()); diff --git a/packages/wallet-service/src/api/txProposalCreate.ts b/packages/wallet-service/src/api/txProposalCreate.ts index b65c8dfe..253f71fd 100644 --- a/packages/wallet-service/src/api/txProposalCreate.ts +++ b/packages/wallet-service/src/api/txProposalCreate.ts @@ -119,7 +119,7 @@ export const create = middy(walletIdProxyHandler(async (walletId, event) => { // mark utxos with tx-proposal id const txProposalId = uuidv4(); - markUtxosWithProposalId(mysql, txProposalId, inputUtxos); + await markUtxosWithProposalId(mysql, txProposalId, inputUtxos); await createTxProposal(mysql, txProposalId, walletId, now); diff --git a/packages/wallet-service/src/api/version.ts b/packages/wallet-service/src/api/version.ts index 73eb66e0..729a3618 100644 --- a/packages/wallet-service/src/api/version.ts +++ b/packages/wallet-service/src/api/version.ts @@ -11,9 +11,10 @@ import 'source-map-support/register'; import { closeDbConnection, getDbConnection, + getUnixTimestamp, } from '@src/utils'; import { warmupMiddleware } from '@src/api/utils'; -import { getRawFullnodeData } from '@src/nodeConfig' +import { getFullnodeData } from '@src/nodeConfig' import errorHandler from '@src/api/middlewares/errorHandler'; import middy from '@middy/core'; import cors from '@middy/http-cors'; @@ -26,7 +27,7 @@ const mysql = getDbConnection(); * This lambda is called by API Gateway on GET /version */ export const get: APIGatewayProxyHandler = middy(async () => { - const versionData = await getRawFullnodeData(mysql); + const versionData = await getFullnodeData(mysql); await closeDbConnection(mysql); @@ -34,7 +35,10 @@ export const get: APIGatewayProxyHandler = middy(async () => { statusCode: 200, body: JSON.stringify({ success: true, - data: versionData, + data: { + ...versionData, + timestamp: getUnixTimestamp(), + }, }), }; }).use(cors()) diff --git a/packages/wallet-service/src/db/index.ts b/packages/wallet-service/src/db/index.ts index 5ba752ca..796d4b49 100644 --- a/packages/wallet-service/src/db/index.ts +++ b/packages/wallet-service/src/db/index.ts @@ -1875,19 +1875,13 @@ export const releaseTxProposalUtxos = async ( mysql: ServerlessMysql, txProposalIds: string[], ): Promise => { - const result: OkPacket = await mysql.query( + await mysql.query( `UPDATE \`tx_output\` SET \`tx_proposal\` = NULL, \`tx_proposal_index\` = NULL WHERE \`tx_proposal\` IN (?)`, [txProposalIds], ); - - assert.strictEqual( - result.affectedRows, - txProposalIds.length, - 'Not all utxos were correctly updated', - ); }; /** diff --git a/packages/wallet-service/src/nodeConfig.ts b/packages/wallet-service/src/nodeConfig.ts index 189506e4..4038dfba 100644 --- a/packages/wallet-service/src/nodeConfig.ts +++ b/packages/wallet-service/src/nodeConfig.ts @@ -10,6 +10,7 @@ import { ServerlessMysql } from 'serverless-mysql'; import { getVersionData, updateVersionData } from '@src/db'; import { FullNodeVersionData, FullNodeApiVersionResponse } from '@src/types'; import fullnode from '@src/fullnode'; +import { constants } from '@hathor/wallet-lib'; const VERSION_CHECK_MAX_DIFF = 60 * 60; // 1 hour @@ -48,9 +49,9 @@ export function convertApiVersionData(data: FullNodeApiVersionResponse): FullNod rewardSpendMinBlocks: data.reward_spend_min_blocks, maxNumberInputs: data.max_number_inputs, maxNumberOutputs: data.max_number_outputs, - decimalPlaces: data.decimal_places, - nativeTokenName: data.native_token.name, - nativeTokenSymbol: data.native_token.symbol, + decimalPlaces: data.decimal_places ?? constants.DECIMAL_PLACES, + nativeTokenName: data.native_token?.name ?? constants.DEFAULT_NATIVE_TOKEN_CONFIG.name, + nativeTokenSymbol: data.native_token?.symbol ?? constants.DEFAULT_NATIVE_TOKEN_CONFIG.symbol, }; } diff --git a/packages/wallet-service/src/schemas.ts b/packages/wallet-service/src/schemas.ts index 723cce3d..030de90f 100644 --- a/packages/wallet-service/src/schemas.ts +++ b/packages/wallet-service/src/schemas.ts @@ -8,6 +8,8 @@ import Joi from 'joi'; import { FullNodeApiVersionResponse, EnvironmentConfig } from '@src/types'; +export const Sha256Schema = Joi.string().hex().length(64); + export const FullnodeVersionSchema = Joi.object({ version: Joi.string().min(1).required(), network: Joi.string().min(1).required(), @@ -20,9 +22,9 @@ export const FullnodeVersionSchema = Joi.object({ max_number_inputs: Joi.number().integer().positive().required(), max_number_outputs: Joi.number().integer().positive().required(), decimal_places: Joi.number().integer().positive(), - genesis_block_hash: Joi.string().min(1).required(), - genesis_tx1_hash: Joi.string().hex().length(64).required(), - genesis_tx2_hash: Joi.string().hex().length(64).required(), + genesis_block_hash: Sha256Schema, + genesis_tx1_hash: Sha256Schema, + genesis_tx2_hash: Sha256Schema, native_token: Joi.object({ name: Joi.string().min(1).max(30).required(), symbol: Joi.string().min(1).max(5).required(), diff --git a/packages/wallet-service/src/types.ts b/packages/wallet-service/src/types.ts index e2c8d811..edd97b56 100644 --- a/packages/wallet-service/src/types.ts +++ b/packages/wallet-service/src/types.ts @@ -114,11 +114,11 @@ export interface FullNodeApiVersionResponse { reward_spend_min_blocks: number; max_number_inputs: number; max_number_outputs: number; - decimal_places: number; - genesis_block_hash: string, - genesis_tx1_hash: string, - genesis_tx2_hash: string, - native_token: { name: string, symbol: string}; + decimal_places?: number; + genesis_block_hash?: string, + genesis_tx1_hash?: string, + genesis_tx2_hash?: string, + native_token?: { name: string, symbol: string}; } export interface TxProposal { diff --git a/packages/wallet-service/tests/api.test.ts b/packages/wallet-service/tests/api.test.ts index 260457f7..ca17a30c 100644 --- a/packages/wallet-service/tests/api.test.ts +++ b/packages/wallet-service/tests/api.test.ts @@ -60,6 +60,7 @@ import { import fullnode from '@src/fullnode'; import { getHealthcheck } from '@src/api/healthcheck'; import { Severity } from '@wallet-service/common'; +import { convertApiVersionData } from '@src/nodeConfig'; // Monkey patch bitcore-lib @@ -1682,17 +1683,22 @@ test('GET /version', async () => { genesis_tx2_hash: 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc', native_token: { name: 'Hathor', symbol: 'HTR'}, }; + const returnData = convertApiVersionData(mockData); const ts = getUnixTimestamp() await updateVersionData(mysql, ts, mockData); + const event = makeGatewayEvent({}); const result = await getVersionDataGet(event, null, null) as APIGatewayProxyResult; const returnBody = JSON.parse(result.body as string); expect(result.statusCode).toBe(200); expect(returnBody.success).toBe(true); - expect(returnBody.data).toStrictEqual(mockData); + expect(returnBody.data).toEqual(expect.objectContaining({ + timestamp: expect.anything(), + ...returnData, + })); }); test('GET /wallet/proxy/transactions/{txId}', async () => { diff --git a/packages/wallet-service/tests/db.test.ts b/packages/wallet-service/tests/db.test.ts index 857e85e3..33759da5 100644 --- a/packages/wallet-service/tests/db.test.ts +++ b/packages/wallet-service/tests/db.test.ts @@ -84,6 +84,7 @@ import { cleanupVoidedTx, checkTxWasVoided, getWalletTxHistory, + getTxOutputs, } from '@src/db'; import * as Db from '@src/db'; import { cleanUnsentTxProposalsUtxos } from '@src/db/cronRoutines'; @@ -1580,7 +1581,31 @@ test('createTxProposal, updateTxProposal, getTxProposal, countUnsentTxProposals, // Release txProposalUtxos should properly release the utxos. This method will throw an error if the // updated count is different from the sent tx proposals count. await releaseTxProposalUtxos(mysql, [txProposalId1, txProposalId2, txProposalId3]); - await expect(releaseTxProposalUtxos(mysql, ['invalid-tx-proposal'])).rejects.toMatchInlineSnapshot('[AssertionError: Not all utxos were correctly updated]'); + + const txOutputs = await getTxOutputs(mysql, [{ + txId: 'tx1', + timestamp: 0, + version: 0, + voided: false, + weight: 0 + }, { + txId: 'tx2', + timestamp: 0, + version: 0, + voided: false, + weight: 0 + }, { + txId: 'tx3', + timestamp: 0, + version: 0, + voided: false, + weight: 0, + }]); + + // Check that txProposalId is null after releasing + txOutputs.forEach((txOutput) => { + expect(txOutput.txProposalId).toBeNull(); + }); }); test('updateVersionData', async () => { diff --git a/packages/wallet-service/tests/nodeConfig.test.ts b/packages/wallet-service/tests/nodeConfig.test.ts new file mode 100644 index 00000000..92352adb --- /dev/null +++ b/packages/wallet-service/tests/nodeConfig.test.ts @@ -0,0 +1,94 @@ +import { + closeDbConnection, + getDbConnection, + getUnixTimestamp, +} from '@src/utils'; +import { addToVersionDataTable, cleanDatabase } from '@tests/utils'; +import { FullNodeApiVersionResponse } from '@src/types'; +import { convertApiVersionData, getRawFullnodeData } from '@src/nodeConfig'; + +const mysql = getDbConnection(); + +const VERSION_DATA: FullNodeApiVersionResponse = { + version: '0.63.1', + network: 'mainnet', + min_weight: 14, + min_tx_weight: 14, + min_tx_weight_coefficient: 1.6, + min_tx_weight_k: 100, + token_deposit_percentage: 0.01, + reward_spend_min_blocks: 300, + max_number_inputs: 255, + max_number_outputs: 255, + decimal_places: 2, + genesis_block_hash: '000006cb93385b8b87a545a1cbb6197e6caff600c12cc12fc54250d39c8088fc', + genesis_tx1_hash: '0002d4d2a15def7604688e1878ab681142a7b155cbe52a6b4e031250ae96db0a', + genesis_tx2_hash: '0002ad8d1519daaddc8e1a37b14aac0b045129c01832281fb1c02d873c7abbf9', + native_token: { + name: 'Hathor-new', + symbol: 'nHTR' + } +}; + +const OLD_VERSION_DATA: FullNodeApiVersionResponse = { + version: '0.63.1', + network: 'mainnet', + min_weight: 14, + min_tx_weight: 14, + min_tx_weight_coefficient: 1.6, + min_tx_weight_k: 100, + token_deposit_percentage: 0.01, + reward_spend_min_blocks: 300, + max_number_inputs: 255, + max_number_outputs: 255, +}; + +beforeEach(async () => { + jest.resetModules(); + await cleanDatabase(mysql); +}); + +afterAll(async () => { + await closeDbConnection(mysql); +}); + +test('getRawFullnodeData', async () => { + const now = getUnixTimestamp(); + await addToVersionDataTable(mysql, now, VERSION_DATA); + + await expect(getRawFullnodeData(mysql)).resolves.toStrictEqual(VERSION_DATA); +}); + +test('convertApiVersionData', async () => { + expect(convertApiVersionData(OLD_VERSION_DATA)).toStrictEqual({ + version: OLD_VERSION_DATA.version, + network: OLD_VERSION_DATA.network, + minWeight: OLD_VERSION_DATA.min_weight, + minTxWeight: OLD_VERSION_DATA.min_tx_weight, + minTxWeightCoefficient: OLD_VERSION_DATA.min_tx_weight_coefficient, + minTxWeightK: OLD_VERSION_DATA.min_tx_weight_k, + tokenDepositPercentage: OLD_VERSION_DATA.token_deposit_percentage, + rewardSpendMinBlocks: OLD_VERSION_DATA.reward_spend_min_blocks, + maxNumberInputs: OLD_VERSION_DATA.max_number_inputs, + maxNumberOutputs: OLD_VERSION_DATA.max_number_outputs, + decimalPlaces: 2, + nativeTokenName: 'Hathor', + nativeTokenSymbol: 'HTR', + }); + + expect(convertApiVersionData(VERSION_DATA)).toStrictEqual({ + version: VERSION_DATA.version, + network: VERSION_DATA.network, + minWeight: VERSION_DATA.min_weight, + minTxWeight: VERSION_DATA.min_tx_weight, + minTxWeightCoefficient: VERSION_DATA.min_tx_weight_coefficient, + minTxWeightK: VERSION_DATA.min_tx_weight_k, + tokenDepositPercentage: VERSION_DATA.token_deposit_percentage, + rewardSpendMinBlocks: VERSION_DATA.reward_spend_min_blocks, + maxNumberInputs: VERSION_DATA.max_number_inputs, + maxNumberOutputs: VERSION_DATA.max_number_outputs, + decimalPlaces: VERSION_DATA.decimal_places, + nativeTokenName: VERSION_DATA.native_token.name, + nativeTokenSymbol: VERSION_DATA.native_token.symbol, + }); +}); diff --git a/scripts/push-daemon.sh b/scripts/push-daemon.sh index b186545b..2af968ca 100644 --- a/scripts/push-daemon.sh +++ b/scripts/push-daemon.sh @@ -1,6 +1,11 @@ set -e set -o pipefail +if [ -z "$ACCOUNT_ID" ]; then + echo "Please export a ACCOUNT_ID env var before running this"; + exit 1; +fi + DOCKER_IMAGE_TAG=$(cat /tmp/docker_image_tag) if [ -z "$DOCKER_IMAGE_TAG" ]; then