Skip to content

Commit

Permalink
Merge pull request #1469 from kadena-community/feat/kadena-cli/devnet…
Browse files Browse the repository at this point in the history
…-simulation

[@kadena-clli] Add devnet simulate command (Feat)
  • Loading branch information
nil-amrutlal authored Feb 1, 2024
2 parents 6a4153b + b9da194 commit 0fc6010
Show file tree
Hide file tree
Showing 21 changed files with 991 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/strong-fireants-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@kadena/kadena-cli': patch
---

Added kadena devnet simulate command and auxiliary functionalities
12 changes: 12 additions & 0 deletions packages/tools/kadena-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,18 @@ brew install kda-cli
brew update kda-cli
```

## installation

To install the executable from this repo:

```sh
pnpm install
pnpm build --filter @kadena/kadena-cli

# make sure you're on the project's path
pnpm link -g
```

## list of commands

Each command can be made interactive by not filling in the flags. You can
Expand Down
2 changes: 2 additions & 0 deletions packages/tools/kadena-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"pact-lang-api": "^4.2.0",
"rimraf": "~5.0.1",
"sanitize-filename": "^1.6.3",
"seedrandom": "~3.0.5",
"yaml": "~2.1.1",
"zod": "~3.18.0"
},
Expand All @@ -82,6 +83,7 @@
"@types/mkdirp": "~1.0.2",
"@types/node": "^18.17.14",
"@types/rimraf": "~3.0.2",
"@types/seedrandom": "~3.0.8",
"eslint": "^8.45.0",
"prettier": "~3.0.3",
"typescript": "5.2.2",
Expand Down
33 changes: 33 additions & 0 deletions packages/tools/kadena-cli/src/constants/devnets.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import type { ChainId, IKeyPair } from '@kadena/types';
import type { IDevnetsCreateOptions } from '../devnet/utils/devnetHelpers.js';

export interface IDefaultDevnetOptions {
[key: string]: IDevnetsCreateOptions;
}

export interface IAccount {
account: string;
chainId?: ChainId;
keys: IKeyPair[];
}

export interface IAccountWithTokens extends IAccount {
tokens: { [key: string]: number };
}

/**
* @const devnetDefaults
* Provides the default devnet configurations.
Expand All @@ -18,6 +29,28 @@ export const devnetDefaults: IDefaultDevnetOptions = {
},
};

export const sender00: IAccount = {
keys: [
{
publicKey:
'368820f80c324bbc7c2b0610688a7da43e39f91d118732671cd9c7500ff43cca',
secretKey:
'251a920c403ae8c8f65f59142316af3c82b631fba46ddea92ee8c95035bd2898',
},
],
account: 'sender00',
};

export const defaultDevnetsPath: string = `${process.cwd()}/.kadena/devnets`;
export const standardDevnets: string[] = ['devnet'];
export const defaultDevnet: string = 'devnet';
export const defaultAccount = sender00;

/**
* @const simulationDefaults
* Provides the default simulation configurations.
*/
export const simulationDefaults = {
NETWORK_ID: 'fast-development',
CHAIN_COUNT: 20,
};
52 changes: 52 additions & 0 deletions packages/tools/kadena-cli/src/devnet/commands/devnetSimulation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import chalk from 'chalk';
import debug from 'debug';
import type { CreateCommandReturnType } from '../../utils/createCommand.js';
import { createCommand } from '../../utils/createCommand.js';
import { globalOptions } from '../../utils/globalOptions.js';
import { networkIsAlive } from '../utils/network.js';
import { simulateCoin } from '../utils/simulation/coin/simulate.js';
import { simulationOptions } from '../utils/simulation/simulationOptions.js';

export const simulateCommand: CreateCommandReturnType = createCommand(
'simulate',
'Simulate traffic on a devnet',
[
globalOptions.network(),
simulationOptions.simulationNumberOfAccounts({ isOptional: true }),
simulationOptions.simulationTransferInterval({ isOptional: true }),
globalOptions.logFolder({ isOptional: true }),
simulationOptions.simulationTokenPool({ isOptional: true }),
simulationOptions.simulationMaxTransferAmount({ isOptional: true }),
simulationOptions.simulationDefaultChainId({ isOptional: true }),
simulationOptions.simulationSeed({ isOptional: true }),
],
async (config) => {
try {
debug('devnet-simulate:action')({ config });

if (!(await networkIsAlive(config.networkConfig.networkHost))) {
console.log(
'Network is not reachable. Please check if the provided host is exposed.',
);
return;
}

await simulateCoin({
network: {
id: 'fast-development',
host: config.networkConfig.networkHost,
},
maxAmount: config.simulationMaxTransferAmount,
numberOfAccounts: config.simulationNumberOfAccounts,
transferInterval: config.simulationTransferInterval,
tokenPool: config.simulationTokenPool,
logFolder: config.logFolder,
defaultChain: config.simulationDefaultChainId,
seed: config.simulationSeed,
});
} catch (error) {
console.log(chalk.red(`\n${error.message}\n`));
process.exit(1);
}
},
);
2 changes: 2 additions & 0 deletions packages/tools/kadena-cli/src/devnet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { deleteDevnetCommand } from './commands/devnetDelete.js';
import { listDevnetsCommand } from './commands/devnetList.js';
import { manageDevnetsCommand } from './commands/devnetManage.js';
import { runDevnetCommand } from './commands/devnetRun.js';
import { simulateCommand } from './commands/devnetSimulation.js';
import { stopDevnetCommand } from './commands/devnetStop.js';
import { updateDevnetCommand } from './commands/devnetUpdate.js';

Expand All @@ -22,4 +23,5 @@ export function devnetCommandFactory(program: Command, version: string): void {
runDevnetCommand(devnetsProgram, version);
stopDevnetCommand(devnetsProgram, version);
updateDevnetCommand(devnetsProgram, version);
simulateCommand(devnetsProgram, version);
}
14 changes: 14 additions & 0 deletions packages/tools/kadena-cli/src/devnet/utils/network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import http from 'http';

export function networkIsAlive(networkHost: string): Promise<boolean> {
return new Promise((resolve) => {
http
.get(networkHost, (res) => {
resolve(res.statusCode === 200);
})
.on('error', (err) => {
console.error(`Error checking network: ${err.message}`);
resolve(false);
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { ChainId, ICommandResult } from '@kadena/client';
import { createSignWithKeypair } from '@kadena/client';
import { transferCrossChain } from '@kadena/client-utils/coin';
import type { IAccount } from '../../../../constants/devnets.js';
import { defaultAccount } from '../../../../constants/devnets.js';

export async function crossChainTransfer({
network,
sender,
receiver,
amount,
gasPayer,
}: {
network: { host: string; id: string };
sender: IAccount;
receiver: IAccount;
amount: number;
gasPayer: IAccount;
}): Promise<ICommandResult> {
// Gas Payer validations
if (gasPayer.chainId !== receiver.chainId && gasPayer !== defaultAccount) {
console.log(
`Gas payer ${gasPayer.account} does not for sure have an account on the receiver chain; using ${defaultAccount.account} as gas payer`,
);
gasPayer = defaultAccount;
}

if (!gasPayer.keys.map((key) => key.secretKey)) {
console.log(
`Gas payer ${gasPayer.account} does not have a secret key; using ${defaultAccount.account} as gas payer`,
);
gasPayer = defaultAccount;
}

console.log(
`Crosschain Transfer from ${sender.account}, chain ${sender.chainId}\nTo ${receiver.account}, chain ${receiver.chainId}\nAmount: ${amount}\nGas Payer: ${gasPayer.account}`,
);

const crossChainTransferRequest = transferCrossChain(
{
sender: {
account: sender.account,
publicKeys: sender.keys.map((key) => key.publicKey),
},
receiver: {
account: receiver.account,
keyset: {
keys: receiver.keys.map((key) => key.publicKey),
pred: 'keys-all',
},
},
targetChainGasPayer: {
account: gasPayer.account,
publicKeys: gasPayer.keys.map((key) => key.publicKey),
},
chainId: sender.chainId as ChainId,
targetChainId: receiver.chainId as ChainId,
amount: amount.toString(),
},
{
host: network.host,
defaults: {
networkId: network.id,
},
sign: createSignWithKeypair([...sender.keys, ...gasPayer.keys]),
},
);

return crossChainTransferRequest.executeTo('listen-continuation');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { ICommandResult } from '@kadena/client';
import { Pact, createSignWithKeypair } from '@kadena/client';
import { submitClient } from '@kadena/client-utils/core';
import { PactNumber } from '@kadena/pactjs';
import type { ChainId } from '@kadena/types';
import type { IAccount } from '../../../../constants/devnets.js';
import { stringifyProperty } from '../helper.js';

export async function safeTransfer({
network,
receiver,
chainId,
sender,
amount,
}: {
network: { host: string; id: string };
receiver: IAccount;
chainId: ChainId;
sender: IAccount;
amount: number;
}): Promise<ICommandResult> {
const extraAmount = new PactNumber('0.00000001').toPactDecimal();
const pactAmount = new PactNumber(amount)
.plus(extraAmount.decimal)
.toPactDecimal();

console.log(
`Safe Transfer from ${sender.account} to ${
receiver.account
}\nPublic Key: ${stringifyProperty(receiver.keys, 'publicKey')}\nAmount: ${
pactAmount.decimal
}`,
);

return submitClient({
host: network.host,
sign: createSignWithKeypair([...sender.keys, ...receiver.keys]),
defaults: {
networkId: network.id,
},
})(
Pact.builder
.execution(
Pact.modules.coin['transfer-create'](
sender.account,
receiver.account,
() => '(read-keyset "ks")',
pactAmount,
),
Pact.modules.coin.transfer(
receiver.account,
sender.account,
extraAmount,
),
)
.addData('ks', {
keys: receiver.keys.map((key) => key.publicKey),
pred: 'keys-all',
})
.addSigner(
sender.keys.map((key) => key.publicKey),
(withCap) => [
withCap('coin.GAS'),
withCap(
'coin.TRANSFER',
sender.account,
receiver.account,
pactAmount,
),
],
)
.addSigner(
receiver.keys.map((key) => key.publicKey),
(withCap) => [
withCap(
'coin.TRANSFER',
receiver.account,
sender.account,
extraAmount,
),
],
)
.setMeta({
gasLimit: 1500,
chainId,
senderAccount: sender.account,
ttl: 8 * 60 * 60, //8 hours
})
.setNetworkId(network.id)
.getCommand(),
).executeTo('listen');
}
Loading

0 comments on commit 0fc6010

Please sign in to comment.