Commit d0f22755 authored by felipe's avatar felipe Committed by GitHub

feat(multisig-mon): integrate with 1pass (#8457)

* feat(multisig-mon): integrate with 1pass

* use logger instead of console.log

* lint
parent 65d58130
......@@ -70,6 +70,8 @@ RUN --mount=type=cache,target=/root/.cache/go-build cd op-proposer && make op-pr
FROM alpine:3.18
COPY --from=1password/op:2 /usr/local/bin/op /usr/local/bin/op
COPY --from=builder /app/cannon/bin/cannon /usr/local/bin/
COPY --from=builder /app/op-program/bin/op-program /usr/local/bin/
......
import { exec } from 'child_process'
import {
BaseServiceV2,
StandardOptions,
......@@ -14,15 +16,17 @@ import { version } from '../../package.json'
type MultisigMonOptions = {
rpc: Provider
accounts: string
onePassServiceToken: string
}
type MultisigMonMetrics = {
safeNonce: Gauge
latestPreSignedPauseNonce: Gauge
unexpectedRpcErrors: Counter
}
type MultisigMonState = {
accounts: Array<{ address: string; nickname: string }>
accounts: Array<{ address: string; nickname: string; vault: string }>
}
export class MultisigMonService extends BaseServiceV2<
......@@ -46,9 +50,13 @@ export class MultisigMonService extends BaseServiceV2<
},
accounts: {
validator: validators.str,
desc: 'JSON array of [{ address, nickname, safe }] to monitor balances and nonces of',
desc: 'JSON array of [{ address, nickname, vault }] to monitor balances and nonces of',
public: true,
},
onePassServiceToken: {
validator: validators.str,
desc: '1Password Service Token',
},
},
metricsSpec: {
safeNonce: {
......@@ -56,6 +64,11 @@ export class MultisigMonService extends BaseServiceV2<
desc: 'Safe nonce',
labels: ['address', 'nickname'],
},
latestPreSignedPauseNonce: {
type: Gauge,
desc: 'Latest pre-signed pause nonce',
labels: ['address', 'nickname'],
},
unexpectedRpcErrors: {
type: Counter,
desc: 'Number of unexpected RPC errors',
......@@ -71,34 +84,105 @@ export class MultisigMonService extends BaseServiceV2<
protected async main(): Promise<void> {
for (const account of this.state.accounts) {
try {
const safeContract = new ethers.Contract(
account.address,
Safe.abi,
this.options.rpc
)
const safeNonce = await safeContract.nonce()
this.logger.info(`got nonce`, {
address: account.address,
nickname: account.nickname,
nonce: safeNonce.toString(),
})
this.metrics.safeNonce.set(
{ address: account.address, nickname: account.nickname },
parseInt(safeNonce.toString(), 10)
)
} catch (err) {
this.logger.error(`got unexpected RPC error`, {
section: 'safeNonce',
name: 'getSafeNonce',
err,
})
this.metrics.unexpectedRpcErrors.inc({
section: 'safeNonce',
name: 'getSafeNonce',
})
// get the nonce 1pass
if (this.options.onePassServiceToken) {
await this.getOnePassNonce(account)
}
// get the nonce from deployed safe
await this.getSafeNonce(account)
}
}
private async getSafeNonce(account: {
address: string
nickname: string
vault: string
}) {
try {
const safeContract = new ethers.Contract(
account.address,
Safe.abi,
this.options.rpc
)
const safeNonce = await safeContract.nonce()
this.logger.info(`got nonce`, {
address: account.address,
nickname: account.nickname,
nonce: safeNonce.toString(),
})
this.metrics.safeNonce.set(
{ address: account.address, nickname: account.nickname },
parseInt(safeNonce.toString(), 10)
)
} catch (err) {
this.logger.error(`got unexpected RPC error`, {
section: 'safeNonce',
name: 'getSafeNonce',
err,
})
this.metrics.unexpectedRpcErrors.inc({
section: 'safeNonce',
name: 'getSafeNonce',
})
}
}
private async getOnePassNonce(account: {
address: string
nickname: string
vault: string
}) {
try {
exec(
`OP_SERVICE_ACCOUNT_TOKEN=${this.options.onePassServiceToken} op item list --format json --vault="${account.vault}"`,
(error, stdout, stderr) => {
if (error) {
this.logger.error(`got unexpected error from onepass: ${error}`, {
section: 'onePassNonce',
name: 'getOnePassNonce',
})
return
}
if (stderr) {
this.logger.error(`got unexpected error from onepass`, {
section: 'onePassNonce',
name: 'getOnePassNonce',
stderr,
})
return
}
const items = JSON.parse(stdout)
let latestNonce = -1
this.logger.debug(`items in vault '${account.vault}':`)
for (const item of items) {
const title = item['title']
this.logger.debug(`- ${title}`)
if (title.startsWith('ready-') && title.endsWith('.json')) {
const nonce = parseInt(title.substring(6, title.length - 5), 10)
if (nonce > latestNonce) {
latestNonce = nonce
}
}
}
this.metrics.latestPreSignedPauseNonce.set(
{ address: account.address, nickname: account.nickname },
latestNonce
)
this.logger.debug(`latestNonce: ${latestNonce}`)
}
)
} catch (err) {
this.logger.error(`got unexpected error from onepass`, {
section: 'onePassNonce',
name: 'getOnePassNonce',
err,
})
this.metrics.unexpectedRpcErrors.inc({
section: 'onePassNonce',
name: 'getOnePassNonce',
})
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment