diff --git a/Dockerfile.parameterized b/Dockerfile.parameterized index 6031c1c..3448770 100644 --- a/Dockerfile.parameterized +++ b/Dockerfile.parameterized @@ -1,7 +1,18 @@ -FROM ghcr.io/anoma/namada:v0.46.0 AS namada +FROM ghcr.io/anoma/namada:v0.45.1 AS namada-0.45.1 +FROM ghcr.io/anoma/namada:v0.46.0 AS namada-0.46.0 -# Install system dependencies USER root +# Install both versions of Namada from before and after hard fork +COPY --from=namada-0.45.1 /usr/local/bin/namada /usr/local/bin/namada-0.45.1 +COPY --from=namada-0.45.1 /usr/local/bin/namadac /usr/local/bin/namadac-0.45.1 +COPY --from=namada-0.45.1 /usr/local/bin/namadan /usr/local/bin/namadan-0.45.1 +COPY --from=namada-0.45.1 /usr/local/bin/namadaw /usr/local/bin/namadaw-0.45.1 +RUN mv /usr/local/bin/namada /usr/local/bin/namada-0.46.0 \ + && mv /usr/local/bin/namadac /usr/local/bin/namadac-0.46.0 \ + && mv /usr/local/bin/namadan /usr/local/bin/namadan-0.46.0 \ + && mv /usr/local/bin/namadaw /usr/local/bin/namadaw-0.46.0 + +# Install system dependencies RUN apt update \ && apt install -y vim curl wget unzip procps simpleproxy jq less iputils-ping iproute2 tcpdump strace netcat-traditional RUN cd /tmp \ @@ -17,7 +28,7 @@ ENV CHAIN_ID="namada-dryrun.abaaeaf7b78cb3ac" ENV DATA_DIR="/home/namada/.local/share/namada/$CHAIN_ID" ENV WASM_DIR="$DATA_DIR/wasm" ENV CONFIG_DIR="$DATA_DIR/config.toml" -RUN namadac utils join-network --chain-id $CHAIN_ID --wasm-dir $WASM_DIR +RUN namadac-0.45.1 utils join-network --chain-id $CHAIN_ID --wasm-dir $WASM_DIR RUN sed -i.bak "s#^log_level *=.*#log_level = \"debug\"#" $CONFIG_DIR RUN sed -i.bak "s#^laddr = \"tcp://127.0.0.1:26657\"#laddr = \"tcp://0.0.0.0:26657\"#" $CONFIG_DIR RUN sed -i.bak "s#^persistent_peers_max_dial_period *=.*#persistent_peers_max_dial_period = \"5000ms\"#" $CONFIG_DIR @@ -37,4 +48,4 @@ ADD deno.json deno.lock deps.js / RUN deno cache --import-map=/deno.json --lock=/deno.lock deps.js ADD lib.js services.js status.js rpc_proxy.js control_node.js sync_proxy.js peers.json / ENTRYPOINT [ "/bin/bash" ] -CMD [ "-c", "/control.js" ] +CMD [ "-c", "/control_node.js" ] diff --git a/control_node.js b/control_node.js index 99eee8e..24df6ff 100755 --- a/control_node.js +++ b/control_node.js @@ -1,4 +1,4 @@ -#!/usr/bin/env -S deno run --allow-net --allow-run=namadan,pkill,pgrep --allow-env=HOST,PORT,NAMADA,CHAIN_ID,NODE_OUT --allow-read=/home/namada/.local/share/namada --allow-write=/home/namada/.local/share/namada +#!/usr/bin/env -S deno run --allow-net --allow-run=namadan-0.45.1,namadan-0.46.0,pkill,pgrep --allow-env=HOST,PORT,NAMADA,CHAIN_ID,NODE_OUT --allow-read=/home/namada/.local/share/namada --allow-write=/home/namada/.local/share/namada // This service runs the node. In order for the indexer to have time to fetch all data // before epoched data is pruned, this service parses the log output of the node, and // when the epoch has incremented it tells the outgoing proxy to cut off outgoing @@ -11,15 +11,48 @@ import { Service } from './services.js' if (import.meta.main) setTimeout(main, 0) function main () { + // Initialize and configure initialize() const { HOST, PORT, NAMADA, CHAIN_ID, NODE_OUT } = environment({ HOST: "0.0.0.0", PORT: "25551", - NAMADA: "namadan", + NAMADA: "0=namadan-0.45.1,182000=namadan-0.46.0", CHAIN_ID: "namada-dryrun.abaaeaf7b78cb3ac", NODE_OUT: "http://sync-proxy:25552" }) + + // Namada node service manager const service = new NamadaService(NAMADA, CHAIN_ID) + + // When the node log contains block height and epoch, do the magic + service.events.addEventListener('synced', async ({detail: {block, epoch}}) => { + // Switch to next version of Namada node if hardfork has occurred + block = BigInt(block) + let namada = service.namadas[0n] + // Find the next version to run + for (const hardfork of Object.keys(service.namadas)) { + if (block > hardfork) { + namada = service.namadas[hardfork] + break + } + } + // If the next version is different to the current one, launch it + if (namada != service.namada) { + await service.pause() + service.namada = namada + await service.start() + } + // Pause if epoch has incremented + epoch = BigInt(epoch) + if (epoch > service.epoch) { + service.epoch = epoch + console.log('🟠 Epoch has increased to', epoch) + service.events.dispatchEvent(new RequestPauseEvent()) + } + }) + + // When pause is requested, tell the sync-proxy to disconnect. + // The undexer will tell it to reenable connections when ready to continue. service.events.addEventListener('request-pause', async () => { let canConnect = true while (canConnect) { @@ -31,13 +64,18 @@ function main () { await new Promise(resolve=>setTimeout(resolve, 100)) } }) + + // Run HTTP+WS API server api('Node', HOST, PORT, service.routes(), { + // Notify undexer of sync progress onOpen: ({ send }) => { - service.events.addEventListener('synced', send) + service.events.addEventListener('synced', event => send(event)) }, + // Stop trying to notify undexer of sync progress on disconnect onClose: ({ send }) => { - service.events.removeEventListener('synced', send) + service.events.removeEventListener('synced', event => send(event)) }, + // Respond to resync command from undexer onMessage: async ({ event }) => { const data = JSON.parse(event.data) if (data.restart) { @@ -48,46 +86,75 @@ function main () { } } }) + + // And away we go! + service.start() } export class NamadaService extends Service { - constructor (namada = 'namadan', chainId) { + + constructor (namadas = "0=namadan-0.45.1,182000=namadan-0.46.0", chainId) { + // Multiple versions of Namada to support hard forks + namadas = Object.fromEntries(namadas + .split(',') + .map(x=>x.split('=')) + .map(([block, bin])=>[BigInt(block), bin]) + ) + const namada = namadas[0n] + if (!namada) { + throw new Error('NAMADA format: 0=...[,HardForkHeight=...]') + } + // Start with 1st version of Namada node super('Namada', namada, 'ledger', 'run') + // Which version to run starting from which block + this.namadas = namadas + // Currently selected version + this.namada = namada + // Used to find config file this.chainId = chainId + // Match block increment in log output this.regex = new RegExp('Block height: (\\d+).+epoch: (\\d+)') + // Brokers events asynchronously this.events = new EventTarget() + // Current epoch (FIXME: need to persist this!) this.epoch = 0n - this.start() } + + // Print config before launching node async start () { + await this.printConfig() + return super.start() + } + + // Print config + async printConfig () { const configPath = `/home/namada/.local/share/namada/${this.chainId}/config.toml` const config = (await Deno.readTextFile(configPath)).split('\n') for (const line of config.filter(line=>line.includes('persistent_peers'))) { console.log('ℹī¸ Config:', line) } - return super.start() } + + // Output from service is parsed line-by-line and passed to callback pipe (stream, _kind) { stream .pipeThrough(new TextDecoderStream()) .pipeThrough(new TextLineStream()) - .pipeTo(new WritableStream({ write: (chunk, _) => { - //if (!this.muted) console.log(`:: ${this.name} :: ${kind} :: ${chunk}`) - //this.muted || console.log(`:: ${this.name} :: ${kind} :: ${chunk}`) - if (!this.muted) console.log(chunk) - const match = chunk.match(this.regex) - if (match) { - let [block, epoch] = match.slice(1) - console.log(`đŸŸĸ Sync: block ${block} of epoch ${epoch}`) - this.events.dispatchEvent(new SyncEvent({ block, epoch })) - epoch = BigInt(epoch) - if (epoch > this.epoch) { - this.epoch = epoch - console.log('🟠 Epoch has increased to', epoch) - this.events.dispatchEvent(new RequestPauseEvent()) - } - } - } })) + .pipeTo(new WritableStream({ write: (chunk, _) => this.onChunk(chunk) })) + } + + // Handle block and epoch increments + onChunk (chunk) { + if (!this.muted) { + console.log(chunk) + } + const match = chunk.match(this.regex) + if (match) { + // Report block and epoch progress + const [block, epoch] = match.slice(1) + console.log(`đŸŸĸ Sync: block ${block} of epoch ${epoch}`) + this.events.dispatchEvent(new SyncEvent({ block, epoch })) + } } /** Delete node state, allowing the sync to start from scratch. * This is invoked by the indexer when it finds that it is more diff --git a/services.js b/services.js index 5b37741..9cdede8 100644 --- a/services.js +++ b/services.js @@ -115,7 +115,7 @@ export class Service extends LogPipe { return false } const { pid } = this.process - await new Deno.Command('pkill', { args: ['-9', 'simpleproxy'] }).spawn().status + await new Deno.Command('pkill', { args: ['-9', this.command] }).spawn().status console.log('🟠 Stopped:', this.name, 'at PID:', pid) return await this.state() }