Skip to content

Commit

Permalink
Added Docker configuration for executor (sphinx-labs#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
RPate97 authored Nov 13, 2022
1 parent f04ea12 commit a43e0e3
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 43 deletions.
6 changes: 6 additions & 0 deletions .changeset/spicy-geckos-greet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@chugsplash/executor': patch
'@chugsplash/plugins': patch
---

Add Docker configuration for executor
3 changes: 3 additions & 0 deletions packages/executor/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
dist
.env
8 changes: 8 additions & 0 deletions packages/executor/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CHUGSPLASH_EXECUTOR__PRIVATE_KEY=<private key to deploy with>
CHUGSPLASH_EXECUTOR__NETWORK=<network to monitor, http://host.docker.internal:8545 for local node>
CHUGSPLASH_EXECUTOR__AMPLITUDE_KEY=<amplitude api key for analytics (optional)>
IPFS_PROJECT_ID=<ipfs project id to retrieve config file>
IPFS_API_KEY_SECRET=<ipfs api key to retrieve config file>
OPT_ETHERSCAN_API_KEY=<Optimism Etherscan API key>
ETH_ETHERSCAN_API_KEY=<Ethereum Etherscan API key>
INFURA_API_KEY=<Infura Project API Key>
9 changes: 9 additions & 0 deletions packages/executor/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
FROM node:16.16.0

COPY package.json package.json

RUN npm install

COPY . .

CMD [ "yarn", "start" ]
8 changes: 7 additions & 1 deletion packages/executor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
"lint:check": "yarn lint:ts:check",
"lint:ts:fix": "yarn lint:ts:check --fix",
"lint:ts:check": "eslint . --max-warnings=0",
"pre-commit": "lint-staged"
"pre-commit": "lint-staged",
"build-container": "docker build --tag chugsplash-executor .",
"run-container": "docker run --env-file ./.env chugsplash-executor"
},
"homepage": "https://github.com/smartcontracts/chugsplash/tree/develop/packages/executor#readme",
"license": "MIT",
Expand All @@ -40,5 +42,9 @@
"hardhat": "^2.10.0",
"ipfs-http-client": "56.0.3",
"undici": "^5.12.0"
},
"dependencies": {
"@amplitude/node": "^1.10.2",
"ts-node": "^10.9.1"
}
}
130 changes: 92 additions & 38 deletions packages/executor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,33 @@ import {
} from '@chugsplash/contracts'
import { ChugSplashBundleState } from '@chugsplash/core'
import { getChainId } from '@eth-optimism/core-utils'
import * as Amplitude from '@amplitude/node'

import { compileRemoteBundle, verifyChugSplashConfig } from './utils'

type Options = {
network: string
privateKey: string
amplitudeKey: string
}

type Metrics = {}

type State = {
registry: ethers.Contract
wallet: ethers.Wallet
ess: string[]
eps: string[]
lastBlockNumber: number
amplitudeClient: Amplitude.NodeClient
}

// TODO:
// Add logging agent for docker container and connect to a managed sink such as logz.io
// Refactor chugsplash commands to decide whether to use the executor based on the target network

export class ChugSplashExecutor extends BaseServiceV2<Options, Metrics, State> {
constructor(options?: Partial<Options>) {
super({
name: 'executor',
name: 'chugsplash-executor',
// eslint-disable-next-line @typescript-eslint/no-var-requires
version: require('../package.json').version,
loop: true,
Expand All @@ -37,89 +44,136 @@ export class ChugSplashExecutor extends BaseServiceV2<Options, Metrics, State> {
network: {
desc: 'network for the chain to run the executor on',
validator: validators.str,
default: 'http://localhost:8545',
},
// key: {
// desc: 'private key to use for signing transactions',
// validator: validators.str,
// },
privateKey: {
desc: 'private key used for deployments',
validator: validators.str,
},
amplitudeKey: {
desc: 'API key to send data to Amplitude',
validator: validators.str,
default: 'disabled',
},
},
metricsSpec: {},
})
}

async init() {
if (this.options.amplitudeKey !== 'disabled') {
this.state.amplitudeClient = Amplitude.init(this.options.amplitudeKey)
}

const reg = CHUGSPLASH_REGISTRY_PROXY_ADDRESS
const provider = ethers.getDefaultProvider(this.options.network)
this.state.registry = new ethers.Contract(
reg,
ChugSplashRegistryABI,
provider
)

this.state.wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider)
this.state.lastBlockNumber = -1
this.state.wallet = new ethers.Wallet(this.options.privateKey, provider)
}

async main() {
// Find all active upgrades that have not yet been started
// Find all active upgrades that have not yet been executed in blocks after the stored hash
const approvalAnnouncementEvents = await this.state.registry.queryFilter(
this.state.registry.filters.EventAnnounced('ChugSplashBundleApproved')
this.state.registry.filters.EventAnnounced('ChugSplashBundleApproved'),
this.state.lastBlockNumber + 1
)

// If none found, return
if (approvalAnnouncementEvents.length === 0) {
this.logger.info('no events found')
return
}

this.logger.info(`${approvalAnnouncementEvents.length} events found`)

// store last block number
this.state.lastBlockNumber = approvalAnnouncementEvents.at(-1).blockNumber

// execute all approved bundles
for (const approvalAnnouncementEvent of approvalAnnouncementEvents) {
// fetch manager for relevant project
const signer = this.state.wallet
const manager = new ethers.Contract(
approvalAnnouncementEvent.args.manager,
ChugSplashManagerABI,
signer
)

// get active bundle id for this project
const activeBundleId = await manager.activeBundleId()
if (activeBundleId === ethers.constants.HashZero) {
console.log('no active bundle')
this.logger.error(`Error: No active bundle id found in manager`)
continue
}

// fetch bundle state
const bundleState: ChugSplashBundleState = await manager.bundles(
activeBundleId
)

// TODO: Add this to the ChugSplashManager contract
const selectedExecutor = await manager.getSelectedExecutor(activeBundleId)
if (selectedExecutor !== ethers.constants.AddressZero) {
// Someone else has been selected to execute the upgrade, so we can skip it.
continue
}

// get proposal event and compile
const proposalEvents = await manager.queryFilter(
manager.filters.ChugSplashBundleProposed(activeBundleId)
)

if (proposalEvents.length !== 1) {
// TODO: throw an error here or skip
}

const proposalEvent = proposalEvents[0]
const { bundle, canonicalConfig } = await compileRemoteBundle(
hre,
proposalEvent.args.configUri
)

// ensure compiled bundle matches proposed bundle
if (bundle.root !== proposalEvent.args.bundleRoot) {
// TODO: throw an error here or skip
// log error and continue
this.logger.error(
'Error: Compiled bundle root does not match proposal event bundle root',
canonicalConfig.options
)
continue
}

// execute bundle
try {
await hre.run('chugsplash-execute', {
chugSplashManager: manager,
bundleState,
bundle,
parsedConfig: canonicalConfig,
deployer: signer,
hide: false,
})
this.logger.info('Successfully executed')
} catch (e) {
// log error and continue
this.logger.error('Error: execution error', e, canonicalConfig.options)
continue
}

// verify on etherscan
try {
if ((await getChainId(this.state.wallet.provider)) !== 31337) {
await verifyChugSplashConfig(hre, proposalEvent.args.configUri)
this.logger.info('Successfully verified')
}
} catch (e) {
this.logger.error(
'Error: verification error',
e,
canonicalConfig.options
)
}

// todo call chugsplash-execute if deploying locally
await hre.run('chugsplash-execute', {
chugSplashManager: manager,
bundleState,
bundle,
parsedConfig: canonicalConfig,
deployer: signer,
hide: false,
})

if ((await getChainId(this.state.wallet.provider)) !== 31337) {
await verifyChugSplashConfig(hre, proposalEvent.args.configUri)
if (this.options.amplitudeKey !== 'disabled') {
this.state.amplitudeClient.logEvent({
event_type: 'ChugSplash Executed',
user_id: canonicalConfig.options.projectOwner,
event_properties: {
projectName: canonicalConfig.options.projectName,
},
})
}
}
}
Expand Down
22 changes: 19 additions & 3 deletions packages/executor/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
{
"extends": "../../tsconfig.json",

"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"sourceMap": true,
"esModuleInterop": true,
"composite": true,
"resolveJsonModule": true,
"declaration": true,
"noImplicitAny": false,
"removeComments": true,
"noLib": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"typeRoots": [
"node_modules/@types"
],
"rootDir": "./src",
"outDir": "./dist"
},

"exclude": [
"node_modules",
"dist"
],
"include": [
"src/**/*"
]
Expand Down
3 changes: 2 additions & 1 deletion packages/plugins/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ dist/
.deployed
artifacts
cache
deployments
deployments/
deployed/

0 comments on commit a43e0e3

Please sign in to comment.