diff --git a/backend/src/blockchain.ts b/backend/src/blockchain.ts index 85b9465..475d389 100644 --- a/backend/src/blockchain.ts +++ b/backend/src/blockchain.ts @@ -15,6 +15,8 @@ const paymentReciver: `NQ${number} ${string}` = const period = 1000 * 60 * 60 * 24 * 30; // 30 day const periodCost = 10; // $10 const tolerance = 0.2; // 20% tolerance due to price fluctuations( especially with the coingecko api) +export const STAKING_CONTRACT_ADDRESS = + 'NQ77 0000 0000 0000 0000 0000 0000 0000 0001'; export enum ValidatorStatus { ACTIVE = 'ACTIVE', @@ -164,3 +166,26 @@ export async function getTotalRewards(validatorAddress: Address) { } return total; } + +/** + * Gets the transaction with the given hash + * @param transactionHash + * @returns + */ +export async function getTransaction(transactionHash: string) { + const client = getClient(); + const tx: CallResult = + await client.blockchain.getTransactionByHash(transactionHash); + return tx.data; +} + +/** + * Get the current block height + * @returns block height + */ +export async function getBlockHeight() { + const client = getClient(); + const blockHeight: CallResult = + await client.blockchain.getBlockNumber(); + return blockHeight.data; +} diff --git a/backend/src/index.ts b/backend/src/index.ts index e24572f..deb4e51 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -8,9 +8,12 @@ import { workerData, } from 'node:worker_threads'; import { + STAKING_CONTRACT_ADDRESS, convertAddressForRPC, + getBlockHeight, getPaymentStatus, getTotalRewards, + getTransaction, } from './blockchain.js'; import { createOrUpdateValidator, @@ -20,6 +23,8 @@ import { upgradeNode, } from './nodecontroller.js'; +import type { Transaction } from 'nimiq-rpc-client-ts'; + const testMode = process.env.NODE_ENV === 'test'; const jsonParser = bodyParser.json(); @@ -49,6 +54,7 @@ async function newValidator(argobj: any) { const validatorWalletAddress = argobj.validator_address; const signingSecret = argobj.signingSecret; const votingSecret = argobj.votingSecret; + const transactionHash = argobj.transactionHash; // Check payment const paymentStatus = await getPaymentStatus(validatorWalletAddress); @@ -59,6 +65,14 @@ async function newValidator(argobj: any) { // return; //curently not enforcing payment as the HUB Api seems to be broken again } + if (!(await checkTransaction(validatorWalletAddress, transactionHash))) { + console.log( + `Transaction ${transactionHash} not valid. Supposed to be for ${validatorWalletAddress}`, + ); + return; + } + console.log(`Creating validator ${validatorWalletAddress}`); + await createOrUpdateValidator( validatorWalletAddress, signingSecret, @@ -73,6 +87,16 @@ async function newValidator(argobj: any) { async function deleteValidator(argobj: any) { // Args const validatorWalletAddress = argobj.validator_address; + + const transactionHash = argobj.transactionHash; + + if (!(await checkTransaction(validatorWalletAddress, transactionHash))) { + console.log( + `Transaction ${transactionHash} not valid. Supposed to be for ${validatorWalletAddress}`, + ); + return; + } + console.log(`Deleting validator ${validatorWalletAddress}`); await removeValidator(validatorWalletAddress); } @@ -124,6 +148,46 @@ async function upgradeValidator(argobj: any) { return await upgradeNode(validatorWalletAddress); } +async function checkTransaction( + validatorWalletAddress: string, + transactionHash: string, +) { + const transaction: Transaction = await getTransaction(transactionHash); + if (!transaction) { + console.log( + `Transaction ${transactionHash} not found. Supposed to be for ${validatorWalletAddress}`, + ); + return false; + } else { + const blockHeight = await getBlockHeight(); + if (blockHeight - transaction.blockNumber > 180) { + console.log( + `Transaction ${transactionHash} too old. Supposed to be for ${validatorWalletAddress}`, + ); + return false; + } + if ( + convertAddressForRPC(transaction.from) !== + convertAddressForRPC(validatorWalletAddress) + ) { + console.log( + `Transaction ${transactionHash} not from ${validatorWalletAddress}.`, + ); + return false; + } + if ( + convertAddressForRPC(transaction.to) !== + convertAddressForRPC(STAKING_CONTRACT_ADDRESS) + ) { + console.log( + `Transaction ${transactionHash} not to ${STAKING_CONTRACT_ADDRESS}.`, + ); + return false; + } + return true; + } +} + interface PostResponse { code: number; message: string; diff --git a/backend/src/nodecontroller.ts b/backend/src/nodecontroller.ts index 3a53672..49cbef7 100644 --- a/backend/src/nodecontroller.ts +++ b/backend/src/nodecontroller.ts @@ -121,13 +121,11 @@ export async function removeValidator(address: string) { for (const spec of specs) { try { // delete if exists - await client.read(spec); - const response = await client.delete(spec); console.log(response); } catch (e) { - console.log('Already deleted'); + console.log(`${address} already deleted`); } } } diff --git a/components/delete/DeleteValidator.vue b/components/delete/DeleteValidator.vue index 3a79e16..3bdd0d2 100644 --- a/components/delete/DeleteValidator.vue +++ b/components/delete/DeleteValidator.vue @@ -30,7 +30,9 @@ console.log('Delete tx ', tx!.transactionHash); await useFetch( - `/api/validator/delete/${validatorAddress.toUserFriendlyAddress()}`, + `/api/validator/delete/${validatorAddress.toUserFriendlyAddress()}?transaction_hash=${ + tx.transactionHash + }`, ).catch(() => { console.log('Backend did not delete validator'); }); diff --git a/pages/sign-up/configure.vue b/pages/sign-up/configure.vue index 3f4fa25..20f61c7 100644 --- a/pages/sign-up/configure.vue +++ b/pages/sign-up/configure.vue @@ -35,7 +35,7 @@ throw new Error('reward wallet or keys not set'); const nimiqClient = useNuxtApp().$nimiqClient as Client; - const sender = store.validator.address; + const sender = store.validator.address!; if (!requireConsensus) { // throw error cannot wait for consensus @@ -43,8 +43,10 @@ // additionally await this before proceeding to sign await requireConsensus(); } + let tx; try { signing.value = true; + if (!(await isRegisteredValidator(store.validator.address!))) { if ( !(await hasEnoughFundsForValidatorDeposit( @@ -60,7 +62,7 @@ } } - const tx = await registerValidator( + tx = await registerValidator( store.validator.address!, store.validator.rewardAddress!, store.validator.warmKey!, @@ -86,7 +88,9 @@ console.log('spinning up validator'); // spin up validator await useFetch( - `/api/validator/create/${sender.toUserFriendlyAddress()}?signing_key=${store.validator.warmKey!.toHex()}&voting_key=${store.validator.hotKey!.toHex()}`, + `/api/validator/create/${sender.toUserFriendlyAddress()}?signing_key=${store.validator.warmKey!.toHex()}&voting_key=${store.validator.hotKey!.toHex()}&transaction_hash=${ + tx.transactionHash + }`, ); console.log('checking validator up'); diff --git a/server/api/validator/create/[address].ts b/server/api/validator/create/[address].ts index b7dfb03..f0be7ab 100644 --- a/server/api/validator/create/[address].ts +++ b/server/api/validator/create/[address].ts @@ -3,7 +3,12 @@ export default defineEventHandler((event) => { const query = getQuery(event); console.log(query); console.log(`Backend starting node for ${address}`); - createNode(address, query.signing_key, query.voting_key) + createNode( + address, + query.signing_key, + query.voting_key, + query.transaction_hash, + ) .then((data) => { console.log(data); }) @@ -26,6 +31,7 @@ async function createNode( address: string, signing_key: string, votingKey: string, + transactionHash: string, ) { // post address to backend const response = await fetch(BACKEND_BASE_URL, { @@ -39,6 +45,7 @@ async function createNode( validator_address: address, signingSecret: signing_key, votingSecret: votingKey, + transactionHash, }, }), }); diff --git a/server/api/validator/delete/[address].ts b/server/api/validator/delete/[address].ts index 32c7df5..70437b9 100644 --- a/server/api/validator/delete/[address].ts +++ b/server/api/validator/delete/[address].ts @@ -1,7 +1,8 @@ export default defineEventHandler((event) => { const address = decodeURIComponent(event.context.params.address); + const query = getQuery(event); console.log(`Backend deleting node for ${address}`); - deleteNode(address) + deleteNode(address, query.transaction_hash) .then((data) => { console.log(data); }) @@ -18,7 +19,7 @@ export default defineEventHandler((event) => { * @returns */ -async function deleteNode(address: string) { +async function deleteNode(address: string, transactionHash: string) { // post address to backend const response = await fetch(BACKEND_BASE_URL, { method: 'POST', @@ -29,6 +30,7 @@ async function deleteNode(address: string) { method: 'deleteValidator', argobj: { validator_address: address, + transactionHash, }, }), }); diff --git a/tsconfig.json b/tsconfig.json index a746f2a..a994a26 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,4 +1,4 @@ { // https://nuxt.com/docs/guide/concepts/typescript - "extends": "./.nuxt/tsconfig.json" + "extends": "./.nuxt/tsconfig.json", } diff --git a/utils/nimiq-network.ts b/utils/nimiq-network.ts index 0f598fd..fe716bc 100644 --- a/utils/nimiq-network.ts +++ b/utils/nimiq-network.ts @@ -31,7 +31,7 @@ export async function sendDepositToAddress(address: Address) { }; const signedTx = await hubApi.checkout(options); await nimiqClient.waitForConsensusEstablished(); - await nimiqClient.sendTransaction(signedTx.serializedTx); + return await nimiqClient.sendTransaction(signedTx.serializedTx); } export async function payForValidator( @@ -182,7 +182,7 @@ export async function updateValidatorPayoutAddress( appName: 'Staqe', transaction: update_validator_transaction.serialize(), }); - await nimiqClient.sendTransaction( + return await nimiqClient.sendTransaction( signed_update_validator_transaction.serializedTx, ); }