Skip to content

Commit a5ddd15

Browse files
technicallytyAlex | Interchain Labs
andauthored
feat: 4 node localnet infra (cosmos#301)
* localnet * remove that' * some fixes * expose json rpc from container * json rpc metrics --------- Co-authored-by: Alex | Interchain Labs <[email protected]>
1 parent 727407e commit a5ddd15

File tree

10 files changed

+975
-12
lines changed

10 files changed

+975
-12
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,5 @@ contracts/@openzeppelin/*
3333
evmd/build/
3434

3535
build/
36+
37+
.testnets

Makefile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,25 @@ contracts-compile:
349349
contracts-add:
350350
@echo "Adding a new smart contract to be compiled..."
351351
@python3 ./scripts/compile_smart_contracts/compile_smart_contracts.py --add $(CONTRACT)
352+
353+
###############################################################################
354+
### Localnet ###
355+
###############################################################################
356+
357+
localnet-build-env:
358+
$(MAKE) -C contrib/images evmd-env
359+
360+
localnet-build-nodes:
361+
$(DOCKER) run --rm -v $(CURDIR)/.testnets:/data cosmos/evmd \
362+
testnet init-files --v 4 -o /data --starting-ip-address 192.168.10.2 --keyring-backend=test --chain-id=local-4221 --use-docker=true
363+
docker compose up -d
364+
365+
localnet-stop:
366+
docker compose down
367+
368+
# localnet-start will run a 4-node testnet locally. The nodes are
369+
# based off the docker images in: ./contrib/images/simd-env
370+
localnet-start: localnet-stop localnet-build-env localnet-build-nodes
371+
372+
373+
.PHONY: localnet-start localnet-stop localnet-build-env localnet-build-nodes

contrib/images/Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
all: evmd-env
2+
3+
evmd-env: evmd-rmi
4+
docker build --tag cosmos/evmd -f evmd-env/Dockerfile \
5+
$(shell git rev-parse --show-toplevel)
6+
7+
evmd-rmi:
8+
docker rmi cosmos/evmd 2>/dev/null; true
9+
10+
.PHONY: all evmd-env evmd-rmi

contrib/images/evmd-env/Dockerfile

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Info on how to use this docker image can be found in DOCKER_README.md
2+
ARG IMG_TAG=latest
3+
4+
# Compile the evmd binary
5+
FROM golang:1.24-alpine AS evmd-builder
6+
WORKDIR /work
7+
ENV PACKAGES="curl build-base git bash file linux-headers eudev-dev"
8+
RUN apk add --no-cache $PACKAGES
9+
10+
COPY go.mod go.sum* ./
11+
RUN go mod download
12+
13+
COPY . .
14+
RUN LEDGER_ENABLED=false COSMOS_BUILD_OPTIONS="staticlink" BUILD_TAGS=muslc make build
15+
RUN echo "Checking binary linkage..." \
16+
&& file /work/build/evmd \
17+
&& (file /work/build/evmd | grep -q "statically linked" || echo "Warning: Binary may not be statically linked")
18+
19+
FROM alpine:$IMG_TAG AS run
20+
RUN apk add --no-cache build-base jq bash curl
21+
RUN addgroup -g 1025 nonroot
22+
RUN adduser -D nonroot -u 1025 -G nonroot
23+
24+
# Set up the runtime environment
25+
EXPOSE 26656 26657 1317 9090
26+
STOPSIGNAL SIGTERM
27+
VOLUME /evmd
28+
WORKDIR /evmd
29+
30+
# Copy the wrapper script and binary to expected locations
31+
COPY contrib/images/evmd-env/wrapper.sh /usr/bin/wrapper.sh
32+
COPY --from=evmd-builder /work/build/evmd /evmd/
33+
COPY --from=evmd-builder /work/build/evmd /usr/local/bin/
34+
35+
# Set proper ownership and permissions before switching to nonroot user
36+
RUN chown nonroot:nonroot /usr/bin/wrapper.sh && chmod +x /usr/bin/wrapper.sh
37+
RUN chown -R nonroot:nonroot /evmd
38+
39+
USER nonroot
40+
41+
ENTRYPOINT ["/usr/bin/wrapper.sh"]
42+
CMD ["start", "--log_format", "plain", "--minimum-gas-prices", "0.0001atest", "--json-rpc.api", "eth,txpool,personal,net,debug,web3", "--chain-id", "local-4221"]

contrib/images/evmd-env/wrapper.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env sh
2+
set -x
3+
4+
BINARY=/evmd/${BINARY:-evmd}
5+
ID=${ID:-0}
6+
LOG=${LOG:-evmd.log}
7+
8+
if ! [ -f "${BINARY}" ]; then
9+
echo "The binary $(basename "${BINARY}") cannot be found. Please add the binary to the shared folder. Please use the BINARY environment variable if the name of the binary is not 'evmd'"
10+
exit 1
11+
fi
12+
13+
export EVMDHOME="/data/node${ID}/evmd"
14+
15+
if [ -d "$(dirname "${EVMDHOME}"/"${LOG}")" ]; then
16+
"${BINARY}" --home "${EVMDHOME}" "$@" | tee "${EVMDHOME}/${LOG}"
17+
else
18+
"${BINARY}" --home "${EVMDHOME}" "$@"
19+
fi

docker-compose.yml

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
version: "3"
2+
3+
services:
4+
evmdnode0:
5+
container_name: evmdnode0
6+
image: "cosmos/evmd"
7+
environment:
8+
- DEBUG=0
9+
- ID=0
10+
- LOG=${LOG:-evmd.log}
11+
cap_add:
12+
- SYS_PTRACE
13+
security_opt:
14+
- seccomp:unconfined
15+
ports:
16+
- "26656-26657:26656-26657"
17+
- "1317:1317"
18+
- "9090:9090"
19+
- "2345:2345"
20+
- "6065:6065"
21+
- "8545-8546:8545-8546"
22+
volumes:
23+
- ./.testnets:/data:Z
24+
networks:
25+
localnet:
26+
ipv4_address: 192.168.10.2
27+
28+
evmdnode1:
29+
container_name: evmdnode1
30+
image: "cosmos/evmd"
31+
environment:
32+
- DEBUG=0
33+
- ID=1
34+
- LOG=${LOG:-evmd.log}
35+
cap_add:
36+
- SYS_PTRACE
37+
security_opt:
38+
- seccomp:unconfined
39+
ports:
40+
- "26666-26667:26656-26657"
41+
- "1318:1317"
42+
- "9091:9090"
43+
- "2346:2345"
44+
- "6075:6065"
45+
- "8555-8556:8545-8546"
46+
volumes:
47+
- ./.testnets:/data:Z
48+
networks:
49+
localnet:
50+
ipv4_address: 192.168.10.3
51+
52+
evmdnode2:
53+
container_name: evmdnode2
54+
image: "cosmos/evmd"
55+
environment:
56+
- DEBUG=0
57+
- ID=2
58+
- LOG=${LOG:-evmd.log}
59+
cap_add:
60+
- SYS_PTRACE
61+
security_opt:
62+
- seccomp:unconfined
63+
ports:
64+
- "26676-26677:26656-26657"
65+
- "1319:1317"
66+
- "9092:9090"
67+
- "2347:2345"
68+
- "6085:6065"
69+
- "8565-8566:8545-8546"
70+
volumes:
71+
- ./.testnets:/data:Z
72+
networks:
73+
localnet:
74+
ipv4_address: 192.168.10.4
75+
76+
evmdnode3:
77+
container_name: evmdnode3
78+
image: "cosmos/evmd"
79+
environment:
80+
- DEBUG=0
81+
- ID=3
82+
- LOG=${LOG:-evmd.log}
83+
cap_add:
84+
- SYS_PTRACE
85+
security_opt:
86+
- seccomp:unconfined
87+
ports:
88+
- "26686-26687:26656-26657"
89+
- "1320:1317"
90+
- "9093:9090"
91+
- "2348:2345"
92+
- "6095:6065"
93+
- "8575-8576:8545-8546"
94+
volumes:
95+
- ./.testnets:/data:Z
96+
networks:
97+
localnet:
98+
ipv4_address: 192.168.10.5
99+
100+
networks:
101+
localnet:
102+
driver: bridge
103+
ipam:
104+
driver: default
105+
config:
106+
- subnet: 192.168.10.0/25

evmd/cmd/evmd/cmd/creator.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"io"
6+
"path/filepath"
7+
8+
dbm "github.com/cosmos/cosmos-db"
9+
"github.com/cosmos/cosmos-sdk/baseapp"
10+
"github.com/cosmos/cosmos-sdk/client/flags"
11+
"github.com/cosmos/cosmos-sdk/server"
12+
servertypes "github.com/cosmos/cosmos-sdk/server/types"
13+
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
14+
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
15+
"github.com/cosmos/evm/evmd"
16+
evmdconfig "github.com/cosmos/evm/evmd/cmd/evmd/config"
17+
"github.com/spf13/cast"
18+
"github.com/spf13/viper"
19+
20+
"cosmossdk.io/log"
21+
"cosmossdk.io/store"
22+
"cosmossdk.io/store/snapshots"
23+
snapshottypes "cosmossdk.io/store/snapshots/types"
24+
storetypes "cosmossdk.io/store/types"
25+
)
26+
27+
type appCreator struct{}
28+
29+
func (a appCreator) newApp(
30+
logger log.Logger,
31+
db dbm.DB,
32+
traceStore io.Writer,
33+
appOpts servertypes.AppOptions,
34+
) servertypes.Application {
35+
var cache storetypes.MultiStorePersistentCache
36+
if cast.ToBool(appOpts.Get(server.FlagInterBlockCache)) {
37+
cache = store.NewCommitKVStoreCacheManager()
38+
}
39+
pruningOpts, err := server.GetPruningOptionsFromFlags(appOpts)
40+
if err != nil {
41+
panic(err)
42+
}
43+
44+
skipUpgradeHeights := make(map[int64]bool)
45+
for _, h := range cast.ToIntSlice(appOpts.Get(server.FlagUnsafeSkipUpgrades)) {
46+
skipUpgradeHeights[int64(h)] = true
47+
}
48+
49+
homeDir := cast.ToString(appOpts.Get(flags.FlagHome))
50+
chainID := cast.ToString(appOpts.Get(flags.FlagChainID))
51+
if chainID == "" {
52+
// fallback to genesis chain-id
53+
genDocFile := filepath.Join(homeDir, cast.ToString(appOpts.Get("genesis_file")))
54+
appGenesis, err := genutiltypes.AppGenesisFromFile(genDocFile)
55+
if err != nil {
56+
panic(err)
57+
}
58+
59+
chainID = appGenesis.ChainID
60+
}
61+
62+
snapshotDir := filepath.Join(homeDir, "data", "snapshots")
63+
snapshotDB, err := dbm.NewDB("metadata", server.GetAppDBBackend(appOpts), snapshotDir)
64+
if err != nil {
65+
panic(err)
66+
}
67+
snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir)
68+
if err != nil {
69+
panic(err)
70+
}
71+
72+
// BaseApp Opts
73+
snapshotOptions := snapshottypes.NewSnapshotOptions(
74+
cast.ToUint64(appOpts.Get(server.FlagStateSyncSnapshotInterval)),
75+
cast.ToUint32(appOpts.Get(server.FlagStateSyncSnapshotKeepRecent)),
76+
)
77+
baseappOptions := []func(*baseapp.BaseApp){
78+
baseapp.SetChainID(chainID),
79+
baseapp.SetPruning(pruningOpts),
80+
baseapp.SetMinGasPrices(cast.ToString(appOpts.Get(server.FlagMinGasPrices))),
81+
baseapp.SetHaltHeight(cast.ToUint64(appOpts.Get(server.FlagHaltHeight))),
82+
baseapp.SetHaltTime(cast.ToUint64(appOpts.Get(server.FlagHaltTime))),
83+
baseapp.SetMinRetainBlocks(cast.ToUint64(appOpts.Get(server.FlagMinRetainBlocks))),
84+
baseapp.SetInterBlockCache(cache),
85+
baseapp.SetTrace(cast.ToBool(appOpts.Get(server.FlagTrace))),
86+
baseapp.SetIndexEvents(cast.ToStringSlice(appOpts.Get(server.FlagIndexEvents))),
87+
baseapp.SetSnapshot(snapshotStore, snapshotOptions),
88+
baseapp.SetIAVLCacheSize(cast.ToInt(appOpts.Get(server.FlagIAVLCacheSize))),
89+
}
90+
91+
return evmd.NewExampleApp(
92+
logger,
93+
db,
94+
traceStore,
95+
true,
96+
simtestutil.EmptyAppOptions{},
97+
evmdconfig.EVMChainID,
98+
evmdconfig.EvmAppOptions,
99+
baseappOptions...,
100+
)
101+
}
102+
103+
func (a appCreator) appExport(
104+
logger log.Logger,
105+
db dbm.DB,
106+
traceStore io.Writer,
107+
height int64,
108+
forZeroHeight bool,
109+
jailAllowedAddrs []string,
110+
appOpts servertypes.AppOptions,
111+
modulesToExport []string,
112+
) (servertypes.ExportedApp, error) {
113+
var evmApp *evmd.EVMD
114+
115+
homePath, ok := appOpts.Get(flags.FlagHome).(string)
116+
if !ok || homePath == "" {
117+
return servertypes.ExportedApp{}, errors.New("application home is not set")
118+
}
119+
120+
// InvCheckPeriod
121+
viperAppOpts, ok := appOpts.(*viper.Viper)
122+
if !ok {
123+
return servertypes.ExportedApp{}, errors.New("appOpts is not viper.Viper")
124+
}
125+
// overwrite the FlagInvCheckPeriod
126+
viperAppOpts.Set(server.FlagInvCheckPeriod, 1)
127+
appOpts = viperAppOpts
128+
129+
var loadLatest bool
130+
if height == -1 {
131+
loadLatest = true
132+
}
133+
134+
evmApp = evmd.NewExampleApp(
135+
logger,
136+
db,
137+
traceStore,
138+
loadLatest,
139+
appOpts,
140+
evmdconfig.EVMChainID,
141+
evmdconfig.EvmAppOptions,
142+
)
143+
144+
if height != -1 {
145+
if err := evmApp.LoadHeight(height); err != nil {
146+
return servertypes.ExportedApp{}, err
147+
}
148+
}
149+
150+
return evmApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs, modulesToExport)
151+
}

0 commit comments

Comments
 (0)