diff --git a/.gitignore b/.gitignore index fe3e9609..2800d83c 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,5 @@ typings/ # build / generate output **/dist -/bitcoin/signet \ No newline at end of file +/bitcoin/signet +*.sh \ No newline at end of file diff --git a/packages/cli/config.example.json b/packages/cli/config.example.json index 4f8b7d8b..8aa74d4c 100644 --- a/packages/cli/config.example.json +++ b/packages/cli/config.example.json @@ -8,4 +8,4 @@ "username": "bitcoin", "password": "opcatAwesome" } -} \ No newline at end of file +} diff --git a/packages/cli/src/commands/mint/mint.command.ts b/packages/cli/src/commands/mint/mint.command.ts index aaceed63..25ece007 100644 --- a/packages/cli/src/commands/mint/mint.command.ts +++ b/packages/cli/src/commands/mint/mint.command.ts @@ -27,6 +27,7 @@ import { import { broadcastMergeTokenTxs, mergeTokens } from '../send/merge'; import { calcTotalAmount, sendToken } from '../send/ft'; import { pickLargeFeeUtxo } from '../send/pick'; +import { MAX_INPUT } from '@cat-protocol/cat-smartcontracts'; interface MintCommandOptions extends BoardcastCommandOptions { id: string; new?: number; @@ -87,12 +88,13 @@ export class MintCommand extends BoardcastCommand { for (let index = 0; index < MAX_RETRY_COUNT; index++) { await this.merge(token, address); const feeRate = await this.getFeeRate(); - const feeUtxos = await this.getFeeUTXOs(address); + let feeUtxos = await this.getFeeUTXOs(address); if (feeUtxos.length === 0) { console.warn('Insufficient satoshis balance!'); return; } + feeUtxos = this.selectUtxos(feeUtxos); const count = await getTokenMinterCount( this.configService, token.tokenId, @@ -286,6 +288,12 @@ export class MintCommand extends BoardcastCommand { return val; } + selectUtxos(feeUtxos: any[]) { + const filteredUtxos = feeUtxos.filter(utxo => utxo.satoshis !== 546); + filteredUtxos.sort((a, b) => b.satoshis - a.satoshis); + return filteredUtxos.slice(0, MAX_INPUT); + } + async getFeeUTXOs(address: btc.Address) { let feeUtxos = await getUtxos( this.configService, diff --git a/packages/cli/src/commands/wallet/create.command.ts b/packages/cli/src/commands/wallet/create.command.ts index 22fdd3db..9c1dbb0d 100644 --- a/packages/cli/src/commands/wallet/create.command.ts +++ b/packages/cli/src/commands/wallet/create.command.ts @@ -8,6 +8,8 @@ import * as bip39 from 'bip39'; interface CreateCommandOptions extends BaseCommandOptions { name: string; + path_index: number; + mnemonic: string; } @SubCommand({ @@ -39,10 +41,18 @@ export class CreateCommand extends BaseCommand { ? options.name : `cat-${randomBytes(4).toString('hex')}`; + const path_index = options.path_index + ? options.path_index + : 0; + + const mnemonic = options.mnemonic + ? options.mnemonic + : bip39.generateMnemonic(); + const wallet: Wallet = { - accountPath: "m/86'/0'/0'/0/0", + accountPath: `m/86'/0'/0'/0/${path_index}`, name: name, - mnemonic: bip39.generateMnemonic(), + mnemonic: mnemonic, }; this.walletService.createWallet(wallet); @@ -72,4 +82,27 @@ export class CreateCommand extends BaseCommand { return val; } -} + @Option({ + flags: '-p,--path_index [path_index]', + description: 'path index', + }) + parsePathIndex(val: number): number { + if (!val || val < 0) { + logerror("path index can't be empty!", new Error('invalid path_index option')); + process.exit(0); + } + return val; + } + + @Option({ + flags: '-m,--mnemonic [mnemonic]', + description: 'mnemonic', + }) + parseMnemonic(val: string): string { + if (!val) { + logerror("mnemonic can't be empty!", new Error('invalid mnemonic option')); + process.exit(0); + } + return val; + } +} \ No newline at end of file diff --git a/packages/tracker/.env.example b/packages/tracker/.env.example index d5374187..ebd78322 100644 --- a/packages/tracker/.env.example +++ b/packages/tracker/.env.example @@ -1,3 +1,14 @@ +# FB Full Node image +FB_FULL_NODE_IMAGE=fractalbitcoin/fractal:v0.2.1 +POSTGRES_IMAGE=postgres:16 + +# TODO: DEVS need to update this to their own paths +USER_HOME=/home/ubuntu +DATABASE_VOLUME_PATH=$USER_HOME/data/pgdata +BITCOIND_DATA_DIR=$USER_HOME/data/bitcoin +DATABASE_DATA_DIR=/var/lib/postgresql/data/pgdata + +# POSTGRES DATABASE_TYPE=postgres DATABASE_HOST=127.0.0.1 DATABASE_PORT=5432 @@ -5,11 +16,32 @@ DATABASE_DB=postgres DATABASE_USERNAME=postgres DATABASE_PASSWORD=postgres -RPC_HOST=127.0.0.1 -RPC_PORT=8332 -RPC_USER=bitcoin -RPC_PASSWORD=opcatAwesome -NETWORK=mainnet -API_PORT=3000 -GENESIS_BLOCK_HEIGHT=0 # the height of block that CAT protocol launches +# TODO: DEVS needs to update this accord their machine resources +DATABASE_SHM_SIZE=1g + + +# TODO: DEVS +# BITCOIND default config in CAT protocol +BITCOIND_RPC_HOST=127.0.0.1 +BITCOIND_RPC_PORT=8332 +BITCOIND_RPC_USER=bitcoin +BITCOIND_RPC_PASSWORD=opcatAwesome + +BITCOIND_ZMQ_PUB_HASH_BLOCK_PORT=8330 +BITCOIND_ZMQ_PUB_RAW_TX_PORT=8331 +BITCOIND_P2P_PORT=8333 + +# TODO: DEVS needs to update this accord their machine resources +BITCOIND_RESOURCES_LIMITS_MEMORY=60G +BITCOIND_RESOURCES_LIMITS_CPU=4.0 +BITCOIND_RESOURCES_RESERVATIONS_MEMORY=40G +BITCOIND_RESOURCES_RESERVATIONS_CPU=2.0 + +BITCOIND_MEMSWAP_LIMIT=80G +BITCOIND_MEM_SWAPPINESS=100 + +# TODO: DEVS needs to update this accord their machine resources +CAT_PROTOCOL_NETWORK=mainnet +CAT_PROTOCOL_API_PORT=3000 +CAT_PROTOCOL_GENESIS_BLOCK_HEIGHT=0 diff --git a/packages/tracker/docker-compose.yml b/packages/tracker/docker-compose.yml index ca678122..d098e5df 100644 --- a/packages/tracker/docker-compose.yml +++ b/packages/tracker/docker-compose.yml @@ -1,40 +1,52 @@ +networks: + cat20_network: + external: true + services: postgres: - image: postgres:16 - shm_size: 1g + image: $POSTGRES_IMAGE + shm_size: $DATABASE_SHM_SIZE restart: always environment: POSTGRES_USER: $DATABASE_USERNAME POSTGRES_PASSWORD: $DATABASE_PASSWORD POSTGRES_DB: $DATABASE_DB - PGDATA: /var/lib/postgresql/data/pgdata + PGDATA: $DATABASE_DATA_DIR ports: - - "5432:5432" + - "${DATABASE_PORT}:${DATABASE_PORT}" volumes: - - ./docker/pgdata:/var/lib/postgresql/data + - $DATABASE_VOLUME_PATH:$DATABASE_DATA_DIR + networks: + - cat20_network bitcoind: - image: fractalbitcoin/fractal:v0.2.1 + image: $FB_FULL_NODE_IMAGE restart: always entrypoint: ["bitcoind", "-datadir=/data/", "-maxtipage=504576000"] command: "" healthcheck: test: ["CMD", "bitcoin-cli", "-datadir=/data/", "getblockchaininfo"] ports: - - "8330:8330" - - "8331:8331" - - "8332:8332" - - "8333:8333" + - "${BITCOIND_ZMQ_PUB_HASH_BLOCK_PORT}:${BITCOIND_ZMQ_PUB_HASH_BLOCK_PORT}" + - "${BITCOIND_ZMQ_PUB_RAW_TX_PORT}:${BITCOIND_ZMQ_PUB_RAW_TX_PORT}" + - "${BITCOIND_RPC_PORT}:${BITCOIND_RPC_PORT}" + - "${BITCOIND_P2P_PORT}:${BITCOIND_P2P_PORT}" deploy: resources: limits: - memory: 40G - memswap_limit: 60G - mem_swappiness: 100 + memory: $BITCOIND_RESOURCES_LIMITS_MEMORY + cpus: '${BITCOIND_RESOURCES_LIMITS_CPU}' + reservations: + memory: $BITCOIND_RESOURCES_RESERVATIONS_MEMORY + cpus: '${BITCOIND_RESOURCES_RESERVATIONS_CPU}' + memswap_limit: $BITCOIND_MEMSWAP_LIMIT + mem_swappiness: $BITCOIND_MEM_SWAPPINESS volumes: - - ./docker/data:/data + - $BITCOIND_DATA_DIR:/data logging: driver: "json-file" options: labels: "env,filebeat,name" max-size: "1g" max-file: "3" + networks: + - cat20_network \ No newline at end of file diff --git a/packages/tracker/init_script.sh b/packages/tracker/init_script.sh new file mode 100755 index 00000000..95ca1e30 --- /dev/null +++ b/packages/tracker/init_script.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +path=$(pwd) +ENV_FILE="$path/.env" + +if [ ! -f "$ENV_FILE" ]; then + echo ".env file not found: $ENV_FILE" + exit 1 +fi + +USER_HOME=$(grep -E '^USER_HOME=' "$ENV_FILE" | cut -d '=' -f 2) +DATABASE_VOLUME_PATH=$(grep -E '^DATABASE_VOLUME_PATH=' "$ENV_FILE" | cut -d '=' -f 2) +BITCOIND_DATA_DIR=$(grep -E '^BITCOIND_DATA_DIR=' "$ENV_FILE" | cut -d '=' -f 2) + +NESTED_DIRS=("$USER_HOME/data/pgdata" "$USER_HOME/data/bitcoin") +for DIR in "${NESTED_DIRS[@]}"; do + if [ -d "$DIR" ]; then + echo "Checking directory: $DIR" + if [ ! -w "$DIR" ]; then + echo "Changing permissions: $DIR" + sudo chmod -R 777 "$DIR" + else + echo "Permissions are correct: $DIR" + fi + else + echo "Directory does not exist: $DIR" + echo "Creating directory: $DIR" + sudo mkdir -p "$DIR" + sudo chmod -R 777 "$DIR" + fi +done + +echo "Check completed" + +sudo cp docker/data/bitcoin.conf "${NESTED_DIRS[1]}" +echo "Starting services" + +docker compose down + +# Check if the network exists +if ! docker network ls | grep -q cat20_network; then + echo "Creating network cat20_network" + docker network create cat20_network +else + echo "Network cat20_network already exists" +fi + +docker compose up -d + +cd ../../ +echo "Building tracker service" +docker build -t tracker:latest . +echo "Starting tracker service" + +if docker ps -a | grep -q tracker; then + echo "Tracker service already exists, removing..." + docker rm -f tracker +fi + +docker run -d \ + --name tracker \ + --network cat20_network \ + --add-host="host.docker.internal:host-gateway" \ + -e DATABASE_HOST="host.docker.internal" \ + -e BITCOIND_RPC_HOST="bitcoind" \ + -p 3000:3000 \ + tracker:latest + +echo "Tracker service started" \ No newline at end of file diff --git a/packages/tracker/src/app.module.ts b/packages/tracker/src/app.module.ts index 13bef678..15a08c21 100644 --- a/packages/tracker/src/app.module.ts +++ b/packages/tracker/src/app.module.ts @@ -42,10 +42,10 @@ require('dotenv').config(); // @ts-ignore type: process.env.DATABASE_TYPE, host: process.env.DATABASE_HOST, - port: parseInt(process.env.DATABASE_PORT), - username: process.env.DATABASE_USERNAME, - password: process.env.DATABASE_PASSWORD, - database: process.env.DATABASE_DB, + port: parseInt(process.env.DATABASE_PORT || '5432'), + username: process.env.DATABASE_USERNAME || 'postgres', + password: process.env.DATABASE_PASSWORD || 'postgres', + database: process.env.DATABASE_DB || 'postgres', entities: [ BlockEntity, TxEntity, diff --git a/packages/tracker/src/common/constants.ts b/packages/tracker/src/common/constants.ts index cdcf39c4..0bf6f200 100644 --- a/packages/tracker/src/common/constants.ts +++ b/packages/tracker/src/common/constants.ts @@ -49,7 +49,7 @@ export class Constants { static readonly QUERY_PAGING_MAX_LIMIT = 500; } -const _network = process.env.NETWORK || 'mainnet'; +const _network = process.env.CAT_PROTOCOL_NETWORK || 'mainnet'; export let network: Network; switch (_network) { diff --git a/packages/tracker/src/config/configuration.ts b/packages/tracker/src/config/configuration.ts index cb2c9f7f..f831125b 100644 --- a/packages/tracker/src/config/configuration.ts +++ b/packages/tracker/src/config/configuration.ts @@ -2,13 +2,13 @@ require('dotenv').config(); export default () => ({ - rpcHost: process.env.RPC_HOST, - rpcPort: process.env.RPC_PORT, - rpcUser: process.env.RPC_USER, - rpcPassword: process.env.RPC_PASSWORD, + rpcHost: process.env.BITCOIND_RPC_HOST, + rpcPort: process.env.BITCOIND_RPC_PORT, + rpcUser: process.env.BITCOIND_RPC_USER, + rpcPassword: process.env.BITCOIND_RPC_PASSWORD, genesisBlockHeight: Math.max( - parseInt(process.env.GENESIS_BLOCK_HEIGHT || '2'), + parseInt(process.env.CAT_PROTOCOL_GENESIS_BLOCK_HEIGHT || '2'), 2, ), }); diff --git a/packages/tracker/src/main.ts b/packages/tracker/src/main.ts index 9a72a3e0..fa42f00c 100644 --- a/packages/tracker/src/main.ts +++ b/packages/tracker/src/main.ts @@ -14,7 +14,9 @@ async function bootstrap() { .setLicense('MIT License', 'https://opensource.org/licenses/MIT') .setContact('CAT Protocol', 'https://catprotocol.org', '') .addServer(`https://tracker.catprotocol.org/api`) - .addServer(`http://127.0.0.1:${process.env.API_PORT || 3000}/api`) + .addServer( + `http://127.0.0.1:${process.env.CAT_PROTOCOL_API_PORT || 3000}/api`, + ) .build(); const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig); SwaggerModule.setup('/', app, swaggerDocument, { @@ -30,8 +32,10 @@ async function bootstrap() { app.setGlobalPrefix('api'); app.enableCors(); - await app.listen(process.env.API_PORT || 3000); - console.log(`Application is running on: ${await app.getUrl()}`); + await app.listen(process.env.CAT_PROTOCOL_API_PORT || 3000); + console.log( + `Application is running on: ${await app.getUrl()}, genesis block height: ${process.env.CAT_PROTOCOL_GENESIS_BLOCK_HEIGHT}`, + ); } bootstrap();