Skip to content

Commit

Permalink
feat(devnet): generate devnet in go
Browse files Browse the repository at this point in the history
Create a devnet docker image for local development.
It uses rollups-contracts own deployment scripts instead of foundry-rs
and replaces the shell script previousaly developed for the same purpose.
  • Loading branch information
marcelstanley committed Feb 6, 2024
1 parent 5bd4be4 commit d1ccc10
Show file tree
Hide file tree
Showing 15 changed files with 475 additions and 653 deletions.
69 changes: 51 additions & 18 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ARG ROOTFS_VERSION
ARG LINUX_VERSION
ARG LINUX_KERNEL_VERSION
ARG ROM_VERSION
ARG ANVIL_IP_ADDR

# Build directories.
ARG SNAPSHOT_BUILD_PATH=/build/snapshot
Expand Down Expand Up @@ -121,30 +122,27 @@ CMD /bin/bash
# STAGE: devnet-base
#
# This stage installs Foundry.
FROM ${BASE_IMAGE} as devnet-base
FROM golang:${GO_VERSION}-bookworm as devnet-base

# Install system dependencies.
ARG DEBIAN_FRONTEND=noninteractive
RUN <<EOF
set -e
apt-get update
apt-get install -y --no-install-recommends \
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates \
curl \
git \
jq \
wget \
xxd
git
EOF

# Install Foundry.
# Install Foundry
RUN curl -L https://foundry.paradigm.xyz | bash
ENV PATH="~/.foundry/bin:${PATH}"
ARG FOUNDRY_COMMIT_VERSION
RUN <<EOF
set -e
bash -c foundryup -C ${FOUNDRY_COMMIT_VERSION}
ln -s ~/.foundry/bin/anvil /usr/bin/anvil
ln -s ~/.foundry/bin/cast /usr/bin/cast
EOF

# STAGE: devnet-builder
Expand All @@ -154,13 +152,42 @@ FROM devnet-base as devnet-builder
ARG DEVNET_BUILD_PATH
WORKDIR ${DEVNET_BUILD_PATH}

# Install dependencies needed to install yarn
RUN <<EOF
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
gnupg
EOF

# Install yarn
RUN <<EOF
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
nodejs \
yarn
EOF

# Copy scripts
COPY . .

# Prepare environment
ARG ANVIL_IP_ADDR
ENV ANVIL_IP_ADDR="${ANVIL_IP_ADDR}"
ARG ANVIL_STATE_FILE="anvil_state.json"
ENV ANVIL_STATE_FILE="${ANVIL_STATE_FILE}"

# Copy machine snapshot hash.
ARG SNAPSHOT_BUILD_PATH
COPY --from=snapshot-builder ${SNAPSHOT_BUILD_PATH}/hash hash.bin

# Generate Anvil state.
COPY scripts scripts
RUN ./scripts/devnet/gen-devnet.sh -v -t hash.bin
# Config contract location
ENV ROLLUPS_CONTRACTS_PATH=./rollups-contracts

# Generate anvil state
RUN go run ./cmd/gen-devnet --silent --template-hash-file hash.bin > deployment.json


# STAGE: rollups-node-devnet
#
Expand All @@ -169,15 +196,21 @@ FROM devnet-base as rollups-node-devnet

# Copy anvil state file
ARG DEVNET_BUILD_PATH
ENV ANVIL_STATE_PATH=/usr/share/devnet/anvil_state.json
COPY --from=devnet-builder ${DEVNET_BUILD_PATH}/anvil_state.json ${ANVIL_STATE_PATH}

# Copy healthcheck script.
COPY scripts/devnet/lib/anvil_net_listening.sh /usr/bin/anvil_net_listening.sh
HEALTHCHECK --interval=1s --timeout=1s --retries=5 CMD anvil_net_listening.sh
ENV DEVNET_SHARE_PATH=/usr/share/devnet
COPY --from=devnet-builder ${DEVNET_BUILD_PATH}/anvil_state.json ${DEVNET_SHARE_PATH}/anvil_state.json
COPY --from=devnet-builder ${DEVNET_BUILD_PATH}/deployment.json ${DEVNET_SHARE_PATH}/deployment.json

# Make Anvil bind to the external network interface.
ENV ANVIL_IP_ADDR="0.0.0.0"
ARG ANVIL_IP_ADDR
ENV ANVIL_IP_ADDR="${ANVIL_IP_ADDR}"
ENV RPC_URL="http://${ANVIL_IP_ADDR}:8545"
HEALTHCHECK --interval=1s --timeout=1s --retries=5 CMD curl \
-X \
POST \
-s \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":"1","method":"net_listening","params":[]}' \
${RPC_URL}

# Start Anvil.
CMD anvil --block-time 1 --load-state $ANVIL_STATE_PATH
Expand Down
1 change: 1 addition & 0 deletions build/docker-bake.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ target "common" {
LINUX_VERSION = "0.17.0"
LINUX_KERNEL_VERSION = "5.15.63-ctsi-2-v0.17.0"
ROM_VERSION = "0.17.0"
ANVIL_IP_ADDR = "0.0.0.0"
}
}

Expand Down
30 changes: 30 additions & 0 deletions cmd/gen-devnet/anvil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

package main

import (
"os"
"strconv"

"github.com/cartesi/rollups-node/internal/services"
)

const ANVIL_STATE_INTERVAL int = 1

func newAnvilService(depConfig DeploymentConfig) services.CommandService {
var s services.CommandService
s.Name = "anvil"
s.HealthcheckPort = 8545
s.Path = "anvil"

s.Args = append(s.Args, "--host", depConfig.AnvilIpAddr)
s.Args = append(s.Args, "--dump-state", depConfig.AnvilStateFilePath)
s.Args = append(s.Args, "--state-interval", strconv.Itoa(ANVIL_STATE_INTERVAL))
s.Args = append(s.Args, "--silent")

s.Env = append(s.Env, "ANVIL_IP_ADDR=0.0.0.0")
s.Env = append(s.Env, os.Environ()...)

return s
}
130 changes: 130 additions & 0 deletions cmd/gen-devnet/deployer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

package main

import (
"context"
"encoding/json"
"fmt"
"os/exec"
"strings"

"github.com/cartesi/rollups-node/internal/config"
)

func deploy(ctx context.Context,
depConfig DeploymentConfig) (DeploymentInfo, error) {
var depInfo DeploymentInfo

config.InfoLogger.Printf("deployer: deploying %s", depConfig.RollupsContractsPath)
err := deployRollupsContracts(ctx, depConfig)
if err != nil {
return depInfo, fmt.Errorf("could not deploy rollups-contracts: %v", err)
}

config.InfoLogger.Println("deployer: creating application...")
depInfo, err = createApplication(ctx, depConfig)
if err != nil {
return depInfo, fmt.Errorf("could not create Application: %v", err)
}

config.InfoLogger.Println("deployer: gathering application info...")
return depInfo, nil
}

// Create a Rollups Application by calling the necessary factories
func createApplication(ctx context.Context, depConfig DeploymentConfig) (DeploymentInfo, error) {
var depInfo DeploymentInfo
if depConfig.Hash == "" {
return DeploymentInfo{}, fmt.Errorf("machine hash is missing")
}

// Create the Authority/History pair
addresses, _, err := execContract(ctx,
depConfig,
depConfig.AuthorityHistoryFactoryAddress,
"newAuthorityHistoryPair(address,bytes32)(address,address)",
depConfig.SignerAddress,
depConfig.Salt)
if err != nil {
return DeploymentInfo{}, fmt.Errorf("could not create authority/history pair: %v", err)
}

depInfo.AuthorityAddress = addresses[0]
depInfo.HistoryAddress = addresses[1]

// Create the Application, passing the address of the newly created Authority
addresses, blockNumber, err := execContract(ctx,
depConfig,
depConfig.ApplicationFactoryAddress,
"newApplication(address,address,bytes32,bytes32)(address)",
depInfo.AuthorityAddress,
depConfig.SignerAddress,
depConfig.Hash,
depConfig.Salt)
if err != nil {
return DeploymentInfo{}, fmt.Errorf("could not create application: %v", err)
}

depInfo.ApplicationAddress = addresses[0]
depInfo.BlockNumber = blockNumber

return depInfo, nil
}

// Call a contract factory, passing a factory function to be executed.
// Returns the resulting contract address(es) and the corresponding
// block number.
//
// Warning: a second call to a contract with the same arguments will fail.
func execContract(ctx context.Context,
depConfig DeploymentConfig,
args ...string) ([]string, string, error) {
rpcUrl := "http://" + depConfig.AnvilIpAddr + ":" + depConfig.AnvilPort
commonArgs := []string{"--rpc-url", rpcUrl}
commonArgs = append(commonArgs, args...)

var addresses []string
// Calculate the resulting deterministic address(es)
castCall := exec.CommandContext(ctx,
"cast",
"call")
castCall.Args = append(castCall.Args, commonArgs...)
var outStrBuilder strings.Builder
castCall.Stdout = &outStrBuilder
err := castCall.Run()
if err != nil {
return addresses, "", fmt.Errorf("command failed %v: %v", castCall.Args, err)
}
addresses = strings.Fields(outStrBuilder.String())

// Perform actual transaction on the contract
castSend := exec.CommandContext(ctx,
"cast",
"send",
"--json",
"--mnemonic",
depConfig.Mnemonic)
castSend.Args = append(castSend.Args, commonArgs...)
outStrBuilder.Reset()
castSend.Stdout = &outStrBuilder
err = castSend.Run()
if err != nil {
return addresses, "", fmt.Errorf("command failed %v: %v", castSend.Args, err)
}
if !depConfig.IsSilent {
config.InfoLogger.Printf("deployer: executed command %s", castSend.Args)
config.InfoLogger.Printf("deployer: output: %s", outStrBuilder.String())
}

// Extract blockNumber from JSON output
jsonMap := make(map[string](any))
err = json.Unmarshal([]byte([]byte(outStrBuilder.String())), &jsonMap)
if err != nil {
return addresses, "", fmt.Errorf("failed to extract block number, %s", err.Error())
}
blockNumber := jsonMap["blockNumber"].(string)

return addresses, blockNumber, nil
}
Loading

0 comments on commit d1ccc10

Please sign in to comment.