Skip to content

Commit

Permalink
Merge pull request #13 from mondata-dev/security-checks
Browse files Browse the repository at this point in the history
Transaction verification
  • Loading branch information
dpoetzsch authored Feb 7, 2024
2 parents 1fae7bc + 41066b2 commit 8c51ee2
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 13 deletions.
25 changes: 25 additions & 0 deletions backend/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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<Transaction> =
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<number> =
await client.blockchain.getBlockNumber();
return blockHeight.data;
}
64 changes: 64 additions & 0 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import {
workerData,
} from 'node:worker_threads';
import {
STAKING_CONTRACT_ADDRESS,
convertAddressForRPC,
getBlockHeight,
getPaymentStatus,
getTotalRewards,
getTransaction,
} from './blockchain.js';
import {
createOrUpdateValidator,
Expand All @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand All @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down
4 changes: 1 addition & 3 deletions backend/src/nodecontroller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion components/delete/DeleteValidator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
Expand Down
10 changes: 7 additions & 3 deletions pages/sign-up/configure.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,18 @@
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
} else {
// additionally await this before proceeding to sign
await requireConsensus();
}
let tx;
try {
signing.value = true;
if (!(await isRegisteredValidator(store.validator.address!))) {
if (
!(await hasEnoughFundsForValidatorDeposit(
Expand All @@ -60,7 +62,7 @@
}
}
const tx = await registerValidator(
tx = await registerValidator(
store.validator.address!,
store.validator.rewardAddress!,
store.validator.warmKey!,
Expand All @@ -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');
Expand Down
9 changes: 8 additions & 1 deletion server/api/validator/create/[address].ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
})
Expand All @@ -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, {
Expand All @@ -39,6 +45,7 @@ async function createNode(
validator_address: address,
signingSecret: signing_key,
votingSecret: votingKey,
transactionHash,
},
}),
});
Expand Down
6 changes: 4 additions & 2 deletions server/api/validator/delete/[address].ts
Original file line number Diff line number Diff line change
@@ -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);
})
Expand All @@ -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',
Expand All @@ -29,6 +30,7 @@ async function deleteNode(address: string) {
method: 'deleteValidator',
argobj: {
validator_address: address,
transactionHash,
},
}),
});
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
"extends": "./.nuxt/tsconfig.json",
}
4 changes: 2 additions & 2 deletions utils/nimiq-network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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,
);
}
Expand Down

0 comments on commit 8c51ee2

Please sign in to comment.