diff --git a/entrypoint.sh b/entrypoint.sh index c39e323..022617c 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,5 +1,16 @@ #!/bin/sh +if [ -z "$DATABASE_URL" ]; then + # DATABASE_URL is needed in + # 1 migrations + # 2 prisma orm config + # 3 k8s script passes this + # for docker I pass only user/pass/host/db + DATABASE_URL="postgres://${PG_USER}:${PG_PASS}@${PG_HOST}:5432/${DB_NAME}" + export DATABASE_URL +fi + + # Run Prisma migrations npx prisma migrate deploy diff --git a/src/modules/archive/archive-node.service.ts b/src/modules/archive/archive-node.service.ts index 9695858..d3cf589 100644 --- a/src/modules/archive/archive-node.service.ts +++ b/src/modules/archive/archive-node.service.ts @@ -13,6 +13,7 @@ import { InputJsonValue, InputJsonObject, } from '@prisma/client/runtime/library'; +import { Tuple } from '../../utilz/tuple'; type Transaction = { ts?: bigint | number; @@ -60,20 +61,18 @@ export class ArchiveNodeService implements Consumer { } } - public async handleBlock( - deserializedBlock: Block, - blockBytes: Uint8Array, - ): Promise { + public async handleBlock(blockObj: Block, blockBytes: Uint8Array): Promise> { try { - const block = deserializedBlock.toObject(); - if (!(await this.validateBlock(deserializedBlock))) { - throw new Error('Block validation failed'); - } - // Extract block hash from the block - const blockHash = this.getBlockHash(block); + // check by block hash + const blockHash = BlockUtil.hashBlockAsHex(blockBytes); if (await this.isBlockAlreadyStored(blockHash)) { console.log('Block already exists, skipping:', blockHash); - return true; + return [true, null]; + } + // check if valid + const block = blockObj.toObject(); + if (!(await this.validateBlock(blockObj))) { + throw new Error('Block validation failed'); } // Prepare the block data for insertion @@ -83,16 +82,15 @@ export class ArchiveNodeService implements Consumer { data: Buffer.from(blockBytes), // Store the binary data ts: block.ts, }; - // Prepare transaction data for insertion const transactionsData = await this.prepareTransactionsData( - deserializedBlock.getTxobjList(), + blockObj.getTxobjList(), blockHash, block.ts, ); if (transactionsData.length === 0) { console.log('All transactions already exist, skipping block insert.'); - return true; + return [true, null]; } // Insert block into the database await this.prisma.block.create({ data: blockData }); @@ -101,10 +99,10 @@ export class ArchiveNodeService implements Consumer { await this.prisma.transaction.createMany({ data: transactionsData }); console.log('Block and transactions inserted:', blockHash); - return true; + return [true, null]; } catch (error) { console.error('Failed to process block:', error); - return false; + return [false, error.message]; } } @@ -160,11 +158,7 @@ export class ArchiveNodeService implements Consumer { } // TODO: remove from or sender its redundant - private async prepareTransactionsData( - txObjList: Array, - blockHash: string, - blockTs: number, - ): Promise { + private async prepareTransactionsData(txObjList: Array, blockHash: string, blockTs: number,): Promise { const transactionsData = []; for (const txObj of txObjList) { diff --git a/src/modules/block/block.service.ts b/src/modules/block/block.service.ts index 58035a9..8c6964f 100644 --- a/src/modules/block/block.service.ts +++ b/src/modules/block/block.service.ts @@ -6,9 +6,14 @@ import { Block, Prisma, Transaction } from '@prisma/client'; import { ArchiveNodeService } from '../archive/archive-node.service'; import { BitUtil } from '../../utilz/bitUtil'; import { BlockUtil } from '../validator/blockUtil'; +import { Logger } from 'winston'; +import { WinstonUtil } from '../../utilz/winstonUtil'; +import { StrUtil } from '../../utilz/strUtil'; @Injectable() export class BlockService { + private log: Logger = WinstonUtil.newLog(BlockService); + constructor(private prisma: PrismaService, private archiveNodeService: ArchiveNodeService) {} @@ -148,8 +153,9 @@ export class BlockService { } // TODO: Add signature validation + // TODO: normal logging async push_putBlockHash(hashes: string[]) { - console.log('Input hashes:', hashes); + this.log.debug('push_putBlockHash: %s', StrUtil.fmt(hashes)); if (hashes.length === 0) { return []; } @@ -163,13 +169,13 @@ export class BlockService { const statusArr = results.map((result) => result.is_present === 1 ? 'DO_NOT_SEND' : 'SEND', ); - console.log('Returning response:', statusArr); // Debug final response + this.log.debug('Returning response: %s', statusArr); return statusArr; } // TODO: add signature validation async push_putBlock(blocks: string[]):Promise<{ status: string; reason?: string }[]> { - console.log('blocks:', blocks); + this.log.debug('push_putBlock: %s', StrUtil.fmt(blocks)); let statusArr: { status: string; reason?: string }[] = []; if (blocks.length === 0) { return statusArr; @@ -180,7 +186,11 @@ export class BlockService { let block = blocks[i]; const mb = BitUtil.base16ToBytes(block); const parsedBlock = BlockUtil.parseBlock(mb); - const res = await this.archiveNodeService.handleBlock(parsedBlock, mb,); + const [res, err] = await this.archiveNodeService.handleBlock(parsedBlock, mb); + if(err!=null) { + statusArr.push({ status: 'REJECTED', reason: err }); + continue; + } if (!res) { statusArr.push({ status: 'REJECTED', reason: 'duplicate' }); continue; @@ -190,6 +200,7 @@ export class BlockService { statusArr.push({ status: 'REJECTED', reason: error.message }); } } + this.log.debug('Returning response: %s', statusArr); return statusArr; } } diff --git a/src/modules/validator/validator-contract-state.service.ts b/src/modules/validator/validator-contract-state.service.ts index 2400700..8bd9e51 100644 --- a/src/modules/validator/validator-contract-state.service.ts +++ b/src/modules/validator/validator-contract-state.service.ts @@ -59,6 +59,10 @@ export class ValidatorContractState implements OnModuleInit { return this.contractCli.snodes } + public getArchivalNodesMap(): Map { + return this.contractCli.anodes + } + public getActiveValidatorsExceptSelf(): NodeInfo[] { const allNodes = Array.from(this.getAllNodesMap().values()) const onlyGoodValidators = allNodes.filter( @@ -138,10 +142,12 @@ class ContractClientFactory { private pushTokenAddr: string private validatorRpcEndpoint: string private validatorRpcNetwork: number + private abiDir: string private configDir: string nodeWallet: Wallet // private nodeWallet: Signer; - private validatorPrivateKeyFile: string + private validatorEthKeyPath: string; // new + private validatorPrivateKeyFileName: string; // old private validatorPrivateKeyPass: string private nodeAddress: string @@ -154,12 +160,13 @@ class ContractClientFactory { this.validatorRpcEndpoint, this.validatorRpcNetwork ) - this.configDir = EnvLoader.getPropertyOrFail('CONFIG_DIR') - this.abi = ContractClientFactory.loadValidatorContractAbi(this.configDir, 'ValidatorV1.json') + this.configDir = EnvLoader.getPropertyOrFail('CONFIG_DIR'); + this.abiDir = EnvLoader.getPropertyOrDefault('ABI_DIR', this.configDir + "/abi"); + this.abi = ContractClientFactory.loadValidatorContractAbi(this.abiDir, './ValidatorV1.json') } private static loadValidatorContractAbi(configDir: string, fileNameInConfigDir: string): string { - const fileAbsolute = path.resolve(configDir, `./${fileNameInConfigDir}`) + const fileAbsolute = path.resolve(configDir, `${fileNameInConfigDir}`) const file = fs.readFileSync(fileAbsolute, 'utf8') const json = JSON.parse(file) const abi = json.abi @@ -175,10 +182,12 @@ class ContractClientFactory { // creates a client, using an encrypted private key from disk, so that we could write to the blockchain public async buildRWClient(log: Logger): Promise { - this.validatorPrivateKeyFile = EnvLoader.getPropertyOrFail('VALIDATOR_PRIVATE_KEY_FILE') + this.validatorPrivateKeyFileName = EnvLoader.getPropertyOrFail('VALIDATOR_PRIVATE_KEY_FILE') this.validatorPrivateKeyPass = EnvLoader.getPropertyOrFail('VALIDATOR_PRIVATE_KEY_PASS') - const jsonFile = readFileSync(this.configDir + '/' + this.validatorPrivateKeyFile, 'utf-8') + // this is a new variable, which fallbacks to old + this.validatorEthKeyPath = EnvLoader.getPropertyOrDefault('ETH_KEY_PATH', this.configDir + '/' + this.validatorPrivateKeyFileName); + const jsonFile = readFileSync(this.validatorEthKeyPath, {encoding: 'utf8', flag: 'r'}) this.nodeWallet = await Wallet.fromEncryptedJson(jsonFile, this.validatorPrivateKeyPass) this.nodeAddress = await this.nodeWallet.getAddress()