From 6bfd25d06b3539edf98ae08fc23e650bc0bd04f1 Mon Sep 17 00:00:00 2001 From: trevorjtclarke Date: Thu, 3 Feb 2022 11:29:47 -0800 Subject: [PATCH] refactor debug, comb for configs, add more status info --- bin/croncat.js | 12 +- src/actions-bk.js | 405 ---------------------------------------------- src/agent.js | 29 ++-- src/index.js | 3 + src/rpc.js | 6 +- src/slack.js | 6 +- src/tasks.js | 85 ++-------- src/triggers.js | 16 +- src/util.js | 14 +- 9 files changed, 61 insertions(+), 515 deletions(-) delete mode 100644 src/actions-bk.js diff --git a/bin/croncat.js b/bin/croncat.js index f9277d3..ba48fed 100644 --- a/bin/croncat.js +++ b/bin/croncat.js @@ -1,13 +1,13 @@ require('dotenv').config() -const chalk = require('chalk') -const yargs = require('yargs') +import chalk from 'chalk' +import yargs from 'yargs' import { createDaemonFile } from '../src/createSystemctl' import * as config from '../src/configuration' import * as entry from '../src/index' import * as agent from '../src/agent' import * as rpc from '../src/rpc' -// import * as tasks from '../src/tasks' import * as triggers from '../src/triggers' +import * as util from '../src/util' const AGENT_ACCOUNT_ID = config.AGENT_ACCOUNT_ID @@ -98,7 +98,11 @@ const tasksCmd = { desc: 'Check how many tasks are currently available', builder: (yargs) => yargs, handler: async options => { - await rpc.call('get_slot_tasks', options, true) + // Deprecated in favor of web UI + // await rpc.call('get_slot_tasks', options, true) + const manager = await util.getCronManager() + const { networkId } = manager.account.connection + console.log(`${chalk.gray('View ' + networkId.toLowerCase() + ' Tasks:')} ${chalk.blueBright('https://cron.cat/tasks?network=' + networkId.toLowerCase())}`) } } diff --git a/src/actions-bk.js b/src/actions-bk.js deleted file mode 100644 index c64eb8b..0000000 --- a/src/actions-bk.js +++ /dev/null @@ -1,405 +0,0 @@ -require('dotenv').config() -const contractAbi = require('../src/contract_abi.json') -import { utils } from 'near-api-js' -import axios from 'axios' -import Big from 'big.js' -import NearProvider from './near' -import chalk from 'chalk' -import slack from './slack' - -const log = console.log -export const env = process.env.NODE_ENV || 'development' -export const near_env = process.env.NEAR_ENV || 'testnet' -export const LOG_LEVEL = process.env.LOG_LEVEL || 'info' -export const WAIT_INTERVAL_MS = process.env.WAIT_INTERVAL_MS ? parseInt(`${process.env.WAIT_INTERVAL_MS}`) : 30000 -export const AGENT_ACCOUNT_ID = process.env.AGENT_ACCOUNT_ID || 'croncat-agent' -export const AGENT_MIN_TASK_BALANCE = utils.format.parseNearAmount(`${process.env.AGENT_MIN_TASK_BALANCE || '1'}`) // Default: 1_000_000_000_000_000_000_000_000 (1 NEAR) -export const AGENT_AUTO_REFILL = process.env.AGENT_AUTO_REFILL === 'true' ? true : false -export const AGENT_AUTO_RE_REGISTER = process.env.AGENT_AUTO_RE_REGISTER === 'true' ? true : false -export const BASE_GAS_FEE = 300000000000000 -export const BASE_ATTACHED_PAYMENT = 0 -export const BASE_REGISTER_AGENT_FEE = '4840000000000000000000' -let agentSettings = {} -let croncatSettings = {} - -const slackToken = process.env.SLACK_TOKEN || null -const slackProvider = new slack({ slackToken }) -const notifySlack = text => { - try { - if (slackToken) return slackProvider.send({ - slackChannel: process.env.SLACK_CHANNEL, - text - }) - } catch (e) { - // - } -} - -const pingHeartbeat = async () => { - if (process.env.HEARTBEAT === 'true') { - try { - await axios.get(process.env.HEARTBEAT_URL) - } catch (e) { - // nopes - } - } - return Promise.resolve() -} - -function removeUnneededArgs(obj) { - const allowed = ['agent_account_id', 'payable_account_id', 'account', 'offset', 'accountId', 'account_id', 'payableAccountId'] - const fin = {} - - Object.keys(obj).forEach(k => { - if (allowed.includes(k)) fin[k] = obj[k] - }) - - return fin -} - -export const Near = new NearProvider({ - networkId: env === 'production' ? 'mainnet' : 'testnet', - accountId: AGENT_ACCOUNT_ID, -}) -let cronManager = null -let agentAccount = null - -export async function connect(options) { - try { - await Near.getNearConnection(options) - } catch (e) { - log(`${chalk.red('NEAR Connection Failed')}`) - process.exit(1) - } -} - -export async function getAgentBalance() { - try { - const balance = await Near.getAccountBalance() - return balance - } catch (e) { - log(`${chalk.red('NEAR RPC Failed')}`) - await notifySlack(`*Attention!* NEAR ${near_env} RPC Failed to retrieve balance!`) - process.exit(1) - } -} - -export async function getCronManager(accountId, options) { - if (cronManager) return cronManager - await connect(options) - const _n = Near - const abi = contractAbi.abis.manager - const contractId = contractAbi[env].manager - if (accountId) _n.accountId = accountId - cronManager = await _n.getContractInstance(contractId, abi) - return cronManager -} - -// NOTE: Optional "payable_account_id" here -export async function registerAgent(agentId, payable_account_id, options) { - const account = agentId || AGENT_ACCOUNT_ID - const manager = await getCronManager(account, options) - - try { - const res = await manager.register_agent({ - args: { payable_account_id }, - gas: BASE_GAS_FEE, - amount: BASE_REGISTER_AGENT_FEE, - }) - log(`Registered Agent: ${chalk.blue(account)}`) - } catch (e) { - if(e.type === 'KeyNotFound') { - log(`${chalk.red('Agent Registration Failed:')} ${chalk.bold.red(`Please login to your account '${account}' and try again.`)}`) - } else { - log(`${chalk.red('Agent Registration Failed:')} ${chalk.bold.red('Please remove your credentials and try again.')}`) - } - process.exit(1) - } -} - -export async function getAgent(agentId, options) { - const manager = await getCronManager(null, options) - try { - const res = await manager.get_agent({ account_id: agentId || agentAccount }) - return res - } catch (ge) { - if (LOG_LEVEL === 'debug') console.log(ge); - } -} - -export async function getCroncatInfo(options) { - const manager = await getCronManager(null, options) - try { - const res = await manager.get_info() - - return { - paused: res[0], - owner_id: res[1], - agent_active_queue: res[2], - agent_pending_queue: res[3], - agent_task_ratio: res[4], - agents_eject_threshold: res[5], - slots: res[6], - tasks: res[7], - available_balance: res[8], - staked_balance: res[9], - agent_fee: res[10], - gas_price: res[11], - proxy_callback_gas: res[12], - slot_granularity: res[13], - agent_storage_usage: res[14], - } - } catch (ge) { - if (LOG_LEVEL === 'debug') console.log(ge); - } -} - -export async function checkAgentBalance(agentId) { - const balance = await getAgentBalance() - const formattedBalance = utils.format.formatNearAmount(balance) - const hasEnough = Big(balance).gt(BASE_GAS_FEE) - log(` - Agent Account: ${chalk.white(agentId || AGENT_ACCOUNT_ID)} - Agent Balance: ${!hasEnough ? chalk.red(formattedBalance) : chalk.green(formattedBalance)} - `) - if (!hasEnough) { - log(` - ${chalk.red('Your agent account does not have enough to pay for signing transactions.')} - Use the following steps: - ${chalk.bold.white('1. Copy your account id: ')}${chalk.underline.white(agentId || AGENT_ACCOUNT_ID)} - ${chalk.bold.white('2. Use the web wallet to send funds: ')}${chalk.underline.blue(Near.config.walletUrl + '/send-money')} - ${chalk.bold.white('3. Use NEAR CLI to send funds: ')} "near send OTHER_ACCOUNT ${AGENT_ACCOUNT_ID} ${(Big(BASE_GAS_FEE).mul(4))}" - `) - process.exit(0) - } -} - -export async function checkAgentTaskBalance(options) { - const balance = await getAgentBalance() - const notEnough = Big(balance).lt(AGENT_MIN_TASK_BALANCE) - if (notEnough) { - log(` - ${chalk.red('Agent is running low on funds, attempting to refill from rewards...')} - `) - await refillAgentTaskBalance(options) - } -} - -export async function refillAgentTaskBalance(options) { - try { - const manager = await getCronManager(null, options) - const res = await manager.withdraw_task_balance({ - args: {}, - gas: BASE_GAS_FEE, - }) - const balance = await getAgentBalance() - const notEnough = Big(balance).lt(AGENT_MIN_TASK_BALANCE) - if (notEnough) { - log(`${chalk.red('Balance too low.')}`) - await notifySlack(`*Attention!* Not enough balance to execute tasks, refill please.`) - process.exit(1) - } else { - log(`Agent Refilled, Balance: ${chalk.blue(utils.format.formatNearAmount(balance))}`) - await notifySlack(`Agent Refilled, Balance: *${utils.format.formatNearAmount(balance)}*`) - } - } catch (e) { - log(`${chalk.red('No balance to withdraw.')}`) - await notifySlack(`*Attention!* No balance to withdraw.`) - process.exit(1) - } -} - -let agentBalanceCheckIdx = 0 -export async function runAgentTick(options = {}) { - const manager = await getCronManager(null, options) - const agentId = options.accountId || options.account_id - let skipThisIteration = false - let totalTasks = 0 - let previousAgentSettings = {...agentSettings} - - // Logic will trigger on initial run, then every 5th txn - // NOTE: This is really only useful if the payout account is the same as the agent - if (AGENT_AUTO_REFILL && agentBalanceCheckIdx === 0) { - await checkAgentTaskBalance(options) - - // Always ping heartbeat here, checks prefs above - await pingHeartbeat() - } - agentBalanceCheckIdx++ - if (agentBalanceCheckIdx > 5) agentBalanceCheckIdx = 0 - - // 1. Check for tasks - let taskRes - try { - // Only get task hashes my agent can execute - taskRes = await manager.get_agent_tasks({ account_id: agentId }) - } catch (e) { - log(`${chalk.red('Connection interrupted, trying again soon...')}`) - // Wait, then try loop again. - setTimeout(() => { runAgentTick(options) }, WAIT_INTERVAL_MS) - return; - } - totalTasks = parseInt(taskRes[0]) - if (taskRes[1] === '0') log(`${chalk.gray(new Date().toISOString())} Available Tasks: ${chalk.red(totalTasks)}, Current Slot: ${chalk.red('Paused')}`) - else log(`${chalk.gray(new Date().toISOString())} ${chalk.gray('[' + options.networkId.toUpperCase() + ']')} Available Tasks: ${chalk.blueBright(totalTasks)}, Current Slot: ${chalk.yellow(taskRes[1])}`) - - if (LOG_LEVEL === 'debug') console.log('taskRes', taskRes) - if (totalTasks <= 0) skipThisIteration = true - - try { - agentSettings = await getAgent(agentId) - } catch (ae) { - agentSettings = {} - // if no status, trigger a delayed retry - setTimeout(() => { runAgentTick(options) }, WAIT_INTERVAL_MS) - return; - } - // Check agent is active & able to run tasks - if (!agentSettings || !agentSettings.status || agentSettings.status !== 'Active') { - log(`Agent Status: ${chalk.white(agentSettings.status)}`) - skipThisIteration = true - } - - // Alert if agent changes status: - if (previousAgentSettings.status !== agentSettings.status) { - log(`Agent Status: ${chalk.white(agentSettings.status)}`) - await notifySlack(`*Agent Status Update:*\nYour agent is now a status of *${agentSettings.status}*`) - - // TODO: At this point we could check if we need to re-register the agent if enough remaining balance, and status went from active to pending or none. - // NOTE: For now, stopping the process if no agent settings. - if (!agentSettings.status) process.exit(1) - } - - // Use agentSettings to check if the maximum missed slots have happened, stop and notify! - let last_missed_slot = agentSettings.last_missed_slot; - if (last_missed_slot !== 0) { - if (last_missed_slot > (parseInt(taskRes[1]) + (croncatSettings.agents_eject_threshold * croncatSettings.slot_granularity))) { - log(`${chalk.red('Agent has been ejected! Too many slots missed!')}`) - await notifySlack(`*Agent has been ejected! Too many slots missed!*`) - process.exit(1); - } - } - - // 2. Sign task and submit to chain - if (!skipThisIteration) { - try { - const res = await manager.proxy_call({ - args: {}, - gas: BASE_GAS_FEE, - amount: BASE_ATTACHED_PAYMENT, - }) - if (LOG_LEVEL === 'debug') console.log(res) - // log(`${chalk.yellowBright('TX:' + res.transaction_outcome.id)}`) - } catch (e) { - if (LOG_LEVEL === 'debug') console.log(e) - // Check if the agent should slow down to wait for next slot opportunity - if (e.type && e.type === 'FunctionCallError') { - // Check if we need to skip iteration based on max calls in this slot, so we dont waste more fees. - if (e.kind.ExecutionError.search('Agent has exceeded execution for this slot') > -1) { - skipThisIteration = true - } - } - } - } - - // Wait, then loop again. - // Run immediately if executed tasks remain for this slot, then sleep until next slot. - const nextAttemptInterval = skipThisIteration ? WAIT_INTERVAL_MS : 100 - setTimeout(() => { runAgentTick(options) }, nextAttemptInterval) -} - -export async function agentFunction(method, args, isView, gas = BASE_GAS_FEE, amount = BASE_ATTACHED_PAYMENT) { - const account = args.account || args.account_id || args.agent_account_id || AGENT_ACCOUNT_ID - const manager = await getCronManager(account, args) - const params = method === 'unregister' ? {} : removeUnneededArgs(args) - let res - if (LOG_LEVEL === 'debug') console.log(account, isView, manager[method], params, gas, amount); - - try { - res = isView - ? await manager[method](params) - : await manager[method]({ - args: params, - gas, - amount: utils.format.parseNearAmount(`${amount}`), - }) - } catch (e) { - if (e && e.panic_msg) { - log('\n') - log('\t' + chalk.red(e.panic_msg.split(',')[0].replace('panicked at ', '').replace(/\'/g, ''))) - log('\n') - } - } - - if (res && !isView) return res - if (!res && !isView) log('\n\t' + chalk.green(`${method} Success!`) + '\n') - if (!res && isView) log(chalk.green(`No response data`)) - - if (isView && res) { - try { - const payload = typeof res === 'object' ? res : JSON.parse(res) - - if (method === 'get_agent') { - const balance = await getAgentBalance() - const formattedBalance = utils.format.formatNearAmount(balance) - payload.wallet_balance = formattedBalance - } - - if (payload.balance) { - payload.reward_balance = utils.format.formatNearAmount(payload.balance) - delete payload.balance - } - - log('\n') - Object.keys(payload).forEach(k => { - log(`${chalk.bold.white(k.replace(/\_/g, ' '))}: ${chalk.white(payload[k])}`) - }) - log('\n') - } catch (ee) { - log(`${chalk.bold.white(method.replace(/\_/g, ' '))}: ${chalk.white(res)}`) - } - } - - if (method === 'get_agent') { - // Check User Balance - const balance = await getAgentBalance() - - // ALERT USER is their balance is lower than they should be - if (!balance || balance < 3e24) { - log(`${chalk.bold.red('Attention!')}: ${chalk.redBright('Please add more funds to your account to continue sending transactions')}`) - log(`${chalk.bold.red('Current Account Balance:')}: ${chalk.redBright(utils.format.formatNearAmount(balance))}\n`) - - await notifySlack(`*Attention!* Please add more funds to your account to continue sending transactions.\nCurrent Account Balance: *${utils.format.formatNearAmount(balance)}*`) - } - } -} - -export async function bootstrapAgent(agentId, options) { - await connect(options) - - // 1. Check for local signing keys, if none - generate new and halt until funded - agentAccount = `${await Near.getAccountCredentials(agentId || AGENT_ACCOUNT_ID)}` - - // 2. Check for balance, if enough to execute txns, start main tasks - await checkAgentBalance(agentId) - - // 3. Check if agent is registered, if not register immediately before proceeding - try { - agentSettings = await getAgent(agentId) - if (!agentSettings) { - log(`No Agent: ${chalk.red('Please register')}`) - process.exit(0); - } - log(`Registered Agent: ${chalk.white(agentId || AGENT_ACCOUNT_ID)}`) - croncatSettings = await getCroncatInfo(options) - if (!croncatSettings) { - log(`No Croncat Deployed At this Network`) - process.exit(0); - } - } catch (e) { - if (AGENT_AUTO_RE_REGISTER) { - log(`No Agent: ${chalk.gray('Attempting to register...')}`) - await registerAgent(agentId) - } else log(`No Agent: ${chalk.gray('Please register')}`) - } -} \ No newline at end of file diff --git a/src/agent.js b/src/agent.js index d792cbd..4455b61 100644 --- a/src/agent.js +++ b/src/agent.js @@ -31,9 +31,9 @@ export async function registerAgent(agentId, payable_account_id) { amount: config.BASE_REGISTER_AGENT_FEE, }) console.log(`Registered Agent: ${chalk.blue(account)}`) - if (config.LOG_LEVEL === 'debug') console.log('REGISTER ARGS', res); + util.dbug('REGISTER ARGS', res); } catch (e) { - if (config.LOG_LEVEL === 'debug') console.log(e); + util.dbug(e); if(e.type === 'KeyNotFound') { console.log(`${chalk.red('Agent Registration Failed:')} ${chalk.bold.red(`Please login to your account '${account}' and try again.`)}`) } else { @@ -49,7 +49,7 @@ export async function getAgent(agentId = config.AGENT_ACCOUNT_ID) { const res = await manager.get_agent({ account_id: agentId }) return res } catch (ge) { - if (config.LOG_LEVEL === 'debug') console.log(ge); + util.dbug(ge); } } @@ -57,17 +57,15 @@ export async function checkAgentBalance() { const balance = await getAgentBalance() const formattedBalance = utils.format.formatNearAmount(balance) const hasEnough = Big(balance).gt(config.BASE_GAS_FEE) - console.log(` - Agent Account: ${chalk.white(config.AGENT_ACCOUNT_ID)} - Agent Balance: ${!hasEnough ? chalk.red(formattedBalance) : chalk.green(formattedBalance)} - `) + console.log(`Agent Account: ${chalk.white(config.AGENT_ACCOUNT_ID)} +Agent Balance: ${!hasEnough ? chalk.red(formattedBalance) : chalk.green(formattedBalance)}`) if (!hasEnough) { console.log(` ${chalk.red('Your agent account does not have enough to pay for signing transactions.')} Use the following steps: ${chalk.bold.white('1. Copy your account id: ')}${chalk.underline.white(config.AGENT_ACCOUNT_ID)} ${chalk.bold.white('2. Use the web wallet to send funds: ')}${chalk.underline.blue(util.Near.config.walletUrl + '/send-money')} - ${chalk.bold.white('3. Use NEAR CLI to send funds: ')} "near send OTHER_ACCOUNT ${config.AGENT_ACCOUNT_ID} ${(Big(BASE_GAS_FEE).mul(4))}" + ${chalk.bold.white('3. Use NEAR CLI to send funds: ')} "near send OTHER_ACCOUNT ${config.AGENT_ACCOUNT_ID} ${(Big(config.BASE_GAS_FEE).mul(4))}" `) process.exit(0) } @@ -87,7 +85,7 @@ export async function checkAgentTaskBalance() { export async function refillAgentTaskBalance() { try { const manager = await util.getCronManager() - await manager.withdraw_task_balance({ args: {}, gas: BASE_GAS_FEE }) + await manager.withdraw_task_balance({ args: {}, gas: config.BASE_GAS_FEE }) const balance = await getAgentBalance() const notEnough = Big(balance).lt(config.AGENT_MIN_TASK_BALANCE) if (notEnough) { @@ -125,6 +123,10 @@ export const reRegisterAgent = async () => { await registerAgent() } +export const currentStatus = () => { + return agentSettings.status || 'Pending' +} + // returns if agent is active or not export const checkStatus = async () => { let isActive = false @@ -197,7 +199,7 @@ export async function bootstrap() { process.exit(0); } } else { - console.log(`Registered Agent: ${chalk.white(agentId)}`) + console.log(`${chalk.gray('Registered Agent: ')}${chalk.white(agentId)}`) } croncatSettings = await util.getCroncatInfo() if (!croncatSettings) { @@ -205,7 +207,7 @@ export async function bootstrap() { process.exit(1); } } catch (e) { - if (config.LOG_LEVEL === 'debug') console.log(e); + util.dbug(e); if (config.AGENT_AUTO_RE_REGISTER) requiresRegister = true else console.log(`No Agent: ${chalk.red('Please register')}`) } @@ -215,5 +217,8 @@ export async function bootstrap() { await registerAgent(agentId) } - return agentSettings ? true : false + console.log(`${chalk.gray('Agent Status: ')}${chalk.white(agentSettings.status)}`) + if (agentSettings.status === 'Pending') console.log(`${chalk.yellow('Agent waiting until croncat manager changes agent status to Active...')}\n${chalk.gray('Do not stop this process unless you are done being a croncat agent, see https://cron.cat/tasks for more info')}`) + + return agentSettings && agentSettings.status === 'Active' ? true : false } \ No newline at end of file diff --git a/src/index.js b/src/index.js index ea7667a..45f286d 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ import * as config from './configuration' import * as agent from './agent' import * as tasks from './tasks' import * as triggers from './triggers' +import * as util from './util' // Start agent sub-loops export const runSubLoops = async () => { @@ -11,6 +12,7 @@ export const runSubLoops = async () => { // do the things tasks.run() // do the moar thinsg + // TODO: Remove once feature is fully launched in mainnet if (config.BETA_FEATURES) triggers.run() } @@ -23,6 +25,7 @@ export const runMainLoop = async () => { // loop and check agent status until its available async function checkAgent() { const active = await agent.checkStatus() + util.dbug('checkAgent is active', active) if (active) runSubLoops() else setTimeout(checkAgent, config.WAIT_INTERVAL_MS || 60 * 1000) } diff --git a/src/rpc.js b/src/rpc.js index 30d8ab7..6806a12 100644 --- a/src/rpc.js +++ b/src/rpc.js @@ -9,7 +9,7 @@ export async function call(method, args, isView, gas = config.BASE_GAS_FEE, amou const manager = await util.getCronManager(account, args) const params = method === 'unregister' ? {} : util.removeUnneededArgs(args) let res - if (config.LOG_LEVEL === 'debug') console.log(account, isView, manager[method], params, gas, amount); + util.dbug(account, isView, manager[method], params, gas, amount); try { res = isView @@ -25,7 +25,7 @@ export async function call(method, args, isView, gas = config.BASE_GAS_FEE, amou console.log('\t' + chalk.red(e.panic_msg.split(',')[0].replace('panicked at ', '').replace(/\'/g, ''))) console.log('\n') } - if (config.LOG_LEVEL === 'debug') console.log('rpcFunction', e); + util.dbug('rpc.call', e); } if (res && !isView) return res @@ -54,7 +54,7 @@ export async function call(method, args, isView, gas = config.BASE_GAS_FEE, amou console.log('\n') } catch (ee) { console.log(`${chalk.bold.white(method.replace(/\_/g, ' '))}: ${chalk.white(res)}`) - if (config.LOG_LEVEL === 'debug') console.log('rpcFunction view:', ee); + util.dbug('rpc.call view:', ee); } } diff --git a/src/slack.js b/src/slack.js index 0e74502..748932d 100644 --- a/src/slack.js +++ b/src/slack.js @@ -1,5 +1,5 @@ -require('dotenv').config() import axios from 'axios' +import * as config from './configuration' class Slack { constructor(options) { @@ -15,8 +15,8 @@ class Slack { send(options = {}) { const url = this.getHookUrl(options) - const env_name = process.env.NEAR_ENV || 'testnet' - const account = process.env.AGENT_ACCOUNT_ID || null + const env_name = config.NEAR_ENV + const account = config.AGENT_ACCOUNT_ID if (!url) return const data = { channel: options.slackChannel ? `#${options.slackChannel}` : '#general', diff --git a/src/tasks.js b/src/tasks.js index ed38e13..8dd6f65 100644 --- a/src/tasks.js +++ b/src/tasks.js @@ -1,80 +1,13 @@ import * as config from './configuration' +import * as agent from './agent' import * as util from './util' -import { utils } from 'near-api-js' import chalk from 'chalk' -export async function rpcFunction(method, args, isView, gas = BASE_GAS_FEE, amount = BASE_ATTACHED_PAYMENT) { - const account = args.account || args.account_id || args.agent_account_id || AGENT_ACCOUNT_ID - const manager = await getCronManager(account, args) - const params = method === 'unregister' ? {} : util.removeUnneededArgs(args) - let res - if (LOG_LEVEL === 'debug') console.log(account, isView, manager[method], params, gas, amount); - - try { - res = isView - ? await manager[method](params) - : await manager[method]({ - args: params, - gas, - amount: utils.format.parseNearAmount(`${amount}`), - }) - } catch (e) { - if (e && e.panic_msg) { - log('\n') - log('\t' + chalk.red(e.panic_msg.split(',')[0].replace('panicked at ', '').replace(/\'/g, ''))) - log('\n') - } - if (LOG_LEVEL === 'debug') console.log('rpcFunction', e); - } - - if (res && !isView) return res - if (!res && !isView) log('\n\t' + chalk.green(`${method} Success!`) + '\n') - if (!res && isView) log(chalk.green(`No response data`)) - - if (isView && res) { - try { - const payload = typeof res === 'object' ? res : JSON.parse(res) - - if (method === 'get_agent') { - const balance = await getAgentBalance() - const formattedBalance = utils.format.formatNearAmount(balance) - payload.wallet_balance = formattedBalance - } - - if (payload.balance) { - payload.reward_balance = utils.format.formatNearAmount(payload.balance) - delete payload.balance - } - - log('\n') - Object.keys(payload).forEach(k => { - log(`${chalk.bold.white(k.replace(/\_/g, ' '))}: ${chalk.white(payload[k])}`) - }) - log('\n') - } catch (ee) { - log(`${chalk.bold.white(method.replace(/\_/g, ' '))}: ${chalk.white(res)}`) - if (LOG_LEVEL === 'debug') console.log('rpcFunction view:', ee); - } - } - - if (method === 'get_agent') { - // Check User Balance - const balance = await getAgentBalance() - - // ALERT USER is their balance is lower than they should be - if (!balance || balance < 3e24) { - log(`${chalk.bold.red('Attention!')}: ${chalk.redBright('Please add more funds to your account to continue sending transactions')}`) - log(`${chalk.bold.red('Current Account Balance:')}: ${chalk.redBright(utils.format.formatNearAmount(balance))}\n`) - - await notifySlack(`*Attention!* Please add more funds to your account to continue sending transactions.\nCurrent Account Balance: *${utils.format.formatNearAmount(balance)}*`) - } - } -} - // returns if agent should skip next call or not export const getTasks = async () => { const manager = await util.getCronManager() const agentId = config.AGENT_ACCOUNT_ID + const agentStatus = agent.currentStatus() let skipThisIteration = false let totalTasks = 0 let taskRes @@ -84,16 +17,16 @@ export const getTasks = async () => { taskRes = await manager.get_agent_tasks({ account_id: agentId }) } catch (e) { console.log(`${chalk.red('Connection interrupted, trying again soon...')}`) - if (config.LOG_LEVEL === 'debug') console.log('getTasks', e); + util.dbug('getTasks', e); // Wait, then try loop again. skipThisIteration = true return; } totalTasks = parseInt(taskRes[0]) - if (taskRes[1] === '0') console.log(`${chalk.gray(new Date().toISOString())} Available Tasks: ${chalk.red(totalTasks)}, Current Slot: ${chalk.red('Paused')}`) - else console.log(`${chalk.gray(new Date().toISOString())} ${chalk.gray('[' + manager.account.connection.networkId.toUpperCase() + ']')} Available Tasks: ${chalk.blueBright(totalTasks)}, Current Slot: ${chalk.yellow(taskRes[1])}`) + if (taskRes[1] === '0') console.log(`${chalk.gray(new Date().toISOString())} ${chalk.gray('[' + manager.account.connection.networkId.toUpperCase() + ' ' + agentStatus + ']')} Tasks: ${chalk.red(totalTasks)}, Current Slot: ${chalk.red('Paused')}`) + else console.log(`${chalk.gray(new Date().toISOString())} ${chalk.gray('[' + manager.account.connection.networkId.toUpperCase() + ' ' + agentStatus + ']')} Tasks: ${chalk.blueBright(totalTasks)}, Current Slot: ${chalk.yellow(taskRes[1])}`) - if (config.LOG_LEVEL === 'debug') console.log('taskRes', taskRes) + util.dbug('taskRes', taskRes) if (totalTasks <= 0) skipThisIteration = true return skipThisIteration @@ -110,10 +43,10 @@ export const proxyCall = async () => { gas: config.BASE_GAS_FEE, amount: config.BASE_ATTACHED_PAYMENT, }) - if (config.LOG_LEVEL === 'debug') console.log(res) - if (config.LOG_LEVEL === 'debug') console.log(`${chalk.yellowBright('TX:' + res.transaction_outcome.id)}`) + util.dbug(res) + util.dbug(`${chalk.yellowBright('TX:' + res.transaction_outcome.id)}`) } catch (e) { - if (config.LOG_LEVEL === 'debug') console.log('proxy_call issue', e) + util.dbug('proxy_call issue', e) // Check if the agent should slow down to wait for next slot opportunity if (e.type && e.type === 'FunctionCallError') { // Check if we need to skip iteration based on max calls in this slot, so we dont waste more fees. diff --git a/src/triggers.js b/src/triggers.js index 889e739..93d4e6a 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -8,6 +8,7 @@ const CACHE_DELAY = 60 * 1000 const TIME_GRANULARITY = 100 * 10000 let lastCacheCheckTs = +new Date() - CACHE_DELAY let triggersProcessed = 0 +let triggersExecuted = 0 // Get trigger list export const getTriggers = async (from_index = 0, limit = 100) => { @@ -18,7 +19,7 @@ export const getTriggers = async (from_index = 0, limit = 100) => { // Only get task hashes my agent can execute triggers = await manager.get_triggers({ from_index: `${from_index}`, limit: `${limit}` }) } catch (e) { - if (config.LOG_LEVEL === 'debug') console.log('getTriggers', e) + util.dbug('getTriggers', e) } return triggers @@ -26,7 +27,6 @@ export const getTriggers = async (from_index = 0, limit = 100) => { // Get trigger cache // cache for current triggers list & configs -// TODO: Add logging & stats logging if desired export const getAllTriggers = async () => { if (lastCacheCheckTs + CACHE_DELAY > +new Date()) return cache cache = [] @@ -46,9 +46,10 @@ export const getAllTriggers = async () => { // stats logging const manager = await util.getCronManager() - console.log(`${chalk.gray(new Date().toISOString())} ${chalk.gray('[' + manager.account.connection.networkId.toUpperCase() + ']')} Available Triggers: ${chalk.blueBright(cache.length)}, Processed: ${chalk.yellow(triggersProcessed)}`) + console.log(`${chalk.gray(new Date().toISOString())} ${chalk.gray('[' + manager.account.connection.networkId.toUpperCase() + ']')} Triggers: ${chalk.blueBright(cache.length)}, Processed: ${chalk.yellow(triggersProcessed)}, Executed: ${chalk.yellow(triggersExecuted)}`) // reset after log triggersProcessed = 0 + triggersExecuted = 0 return cache } @@ -65,13 +66,13 @@ export const viewTrigger = async trigger_hash => { try { // Check if the trigger evaluates to true or false const res = await util.queryRpc(`${trigger.contract_id}`, `${trigger.function_id}`, null, null, trigger.arguments) - if (config.LOG_LEVEL === 'debug') console.log('callTrigger res', res) + util.dbug('callTrigger res', res) if (!res) outcome = false // res should return a standard payload: (bool, Base64VecU8) if (typeof res === 'boolean') outcome = res if (typeof res === 'object' && typeof res[0] === 'boolean') outcome = res[0] } catch (e) { - if (config.LOG_LEVEL === 'debug') console.log('callTrigger', e) + util.dbug('callTrigger', e) } triggersProcessed += 1 @@ -90,10 +91,11 @@ export const callTrigger = async trigger_hash => { gas: config.BASE_GAS_FEE, amount: config.BASE_ATTACHED_PAYMENT, }) - if (config.LOG_LEVEL === 'debug') console.log('callTrigger res', res) + util.dbug('callTrigger res', res) } catch (e) { - if (config.LOG_LEVEL === 'debug') console.log('callTrigger', e) + util.dbug('callTrigger', e) } + triggersExecuted += 1 } // NOTE: This is built to be SPEED optimized, rather than safe on account balance. Meaning failed TXNs can happen for lack of balance. diff --git a/src/util.js b/src/util.js index 3cb965c..095882d 100644 --- a/src/util.js +++ b/src/util.js @@ -5,6 +5,10 @@ import NearProvider from './near' import chalk from 'chalk' import slack from './slack' +export function dbug() { + if (config.LOG_LEVEL === 'debug') console.log(...arguments) +} + const slackToken = config.SLACK_TOKEN || null const slackChannel = config.SLACK_CHANNEL || 'general' const slackProvider = new slack({ slackToken }) @@ -15,7 +19,7 @@ export const notifySlack = text => { text }) } catch (e) { - if (config.LOG_LEVEL === 'debug') console.log('notifySlack', e); + util.dbug('notifySlack', e); } } @@ -24,7 +28,7 @@ export const pingHeartbeat = async () => { try { await axios.get(config.HEARTBEAT_URL) } catch (e) { - if (config.LOG_LEVEL === 'debug') console.log('pingHeartbeat', e); + util.dbug('pingHeartbeat', e); } } return Promise.resolve() @@ -78,7 +82,7 @@ export const queryRpc = async (account_id, method_name, args, options = {}, args args_base64: args_base64 || btoa(JSON.stringify(args || {})) }) } catch (e) { - if (config.LOG_LEVEL === 'debug') console.log('queryRpc', e) + util.dbug('queryRpc', e) } return options && typeof options.request_type !== 'undefined' ? res : parseResponse(res.result) @@ -93,7 +97,7 @@ export async function connect(options) { await Near.getNearConnection(options) } catch (e) { log(`${chalk.red('NEAR Connection Failed')}`) - if (config.LOG_LEVEL === 'debug') console.log('near connect', e); + util.dbug('near connect', e); // TODO: Retry with diff Provider before hard exit process.exit(1) } @@ -133,6 +137,6 @@ export async function getCroncatInfo() { agent_storage_usage: res[14], } } catch (e) { - if (config.LOG_LEVEL === 'debug') console.log('getCroncatInfo', e); + util.dbug('getCroncatInfo', e); } }