From ec7a217f6f0a61df1959d079102b1a968835c730 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Thu, 5 Sep 2024 18:50:30 +0300 Subject: [PATCH 01/17] Dockerize TON localnet --- Makefile | 5 + contrib/localnet/docker-compose.yml | 14 ++ contrib/localnet/ton/.gitignore | 1 + contrib/localnet/ton/Dockerfile | 34 +++++ contrib/localnet/ton/download-jar.sh | 17 +++ contrib/localnet/ton/entrypoint.sh | 8 ++ contrib/localnet/ton/readme.md | 61 +++++++++ contrib/localnet/ton/sidecar.go | 124 ++++++++++++++++++ go.mod | 6 + go.sum | 6 + .../chains/ton/observer/observer_test.go | 63 +++++++++ zetaclient/chains/ton/utils.go | 24 ++++ 12 files changed, 363 insertions(+) create mode 100644 contrib/localnet/ton/.gitignore create mode 100644 contrib/localnet/ton/Dockerfile create mode 100755 contrib/localnet/ton/download-jar.sh create mode 100644 contrib/localnet/ton/entrypoint.sh create mode 100644 contrib/localnet/ton/readme.md create mode 100644 contrib/localnet/ton/sidecar.go create mode 100644 zetaclient/chains/ton/observer/observer_test.go create mode 100644 zetaclient/chains/ton/utils.go diff --git a/Makefile b/Makefile index 814b5969e9..a300d11485 100644 --- a/Makefile +++ b/Makefile @@ -245,6 +245,11 @@ solana: @echo "Building solana docker image" $(DOCKER) build -t solana-local -f contrib/localnet/solana/Dockerfile contrib/localnet/solana/ +ton: + @echo "Building ton docker image" + contrib/localnet/ton/download-jar.sh + $(DOCKER) buildx build --platform linux/amd64 -t ton-local -f contrib/localnet/ton/Dockerfile contrib/localnet/ton/ + start-e2e-test: zetanode @echo "--> Starting e2e test" cd contrib/localnet/ && $(DOCKER_COMPOSE) up -d diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml index 606b4db4be..fca0e42a72 100644 --- a/contrib/localnet/docker-compose.yml +++ b/contrib/localnet/docker-compose.yml @@ -241,6 +241,20 @@ services: ipv4_address: 172.20.0.103 entrypoint: [ "/usr/bin/start-solana.sh" ] + ton: + image: ton-local:latest + container_name: ton + hostname: ton + profiles: + - ton + - all + ports: + - "8111:8000" # sidecar + - "4443:4443" # lite client + networks: + mynetwork: + ipv4_address: 172.20.0.104 + orchestrator: image: orchestrator:latest tty: true diff --git a/contrib/localnet/ton/.gitignore b/contrib/localnet/ton/.gitignore new file mode 100644 index 0000000000..d392f0e82c --- /dev/null +++ b/contrib/localnet/ton/.gitignore @@ -0,0 +1 @@ +*.jar diff --git a/contrib/localnet/ton/Dockerfile b/contrib/localnet/ton/Dockerfile new file mode 100644 index 0000000000..25a376ea3d --- /dev/null +++ b/contrib/localnet/ton/Dockerfile @@ -0,0 +1,34 @@ +# Let's build a sidecar +FROM golang:1.20 AS go-builder +WORKDIR /opt/sidecar +COPY ./sidecar.go . +RUN go build -o /opt/sidecar/sidecar sidecar.go + +FROM openjdk:24-slim AS ton-node + +ARG WORKDIR="/opt/my-local-ton" + +# Install dependencies && drop apt cache +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + curl jq vim lsb-release \ + && rm -rf /var/lib/apt/lists/* + +COPY ./my-local-ton.jar $WORKDIR/ +COPY ./entrypoint.sh $WORKDIR/ + +COPY --from=go-builder /opt/sidecar $WORKDIR/ + +WORKDIR $WORKDIR + +# Ensure whether the build works or not. +RUN chmod +x entrypoint.sh && chmod +x sidecar \ + && java -jar my-local-ton.jar debug nogui test-binaries; + +# Lite Client +EXPOSE 4443 + +# Sidecar +EXPOSE 8000 + +ENTRYPOINT ["/opt/my-local-ton/entrypoint.sh"] diff --git a/contrib/localnet/ton/download-jar.sh b/contrib/localnet/ton/download-jar.sh new file mode 100755 index 0000000000..762ff3fd06 --- /dev/null +++ b/contrib/localnet/ton/download-jar.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +script_dir=$(cd -- "$(dirname -- "$0")" &> /dev/null && pwd) + +# Downloads JAR outside of the Dockerfile to avoid re-downloading it every time during rebuilds. +jar_version="v120" +jar_url="https://github.com/neodix42/MyLocalTon/releases/download/${jar_version}/MyLocalTon-x86-64.jar" +jar_file="$script_dir/my-local-ton.jar" + +if [ -f "$jar_file" ]; then + echo "File $jar_file already exists. Skipping download." + exit 0 +fi + +echo "File not found. Downloading..." +echo "URL: $jar_url" +wget -q --show-progress -O "$jar_file" "$jar_url" diff --git a/contrib/localnet/ton/entrypoint.sh b/contrib/localnet/ton/entrypoint.sh new file mode 100644 index 0000000000..8a50339953 --- /dev/null +++ b/contrib/localnet/ton/entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +java -jar my-local-ton.jar with-validators-1 nogui debug & + +./sidecar & + +# Wait for both processes to finish +wait -n diff --git a/contrib/localnet/ton/readme.md b/contrib/localnet/ton/readme.md new file mode 100644 index 0000000000..f5eb0d78d7 --- /dev/null +++ b/contrib/localnet/ton/readme.md @@ -0,0 +1,61 @@ +# TON localnet + +This docker image represents a fully working TON node with 1 validator and a faucet wallet. + +## How it works + +- It uses [my-local-ton](https://github.com/neodix42/MyLocalTon) project without GUI. + Port `4443` is used for [lite-client connection](https://docs.ton.org/participate/run-nodes/enable-liteserver-node). +- It also has a convenient sidecar on port `8000` with some useful tools. +- Please note that it might take **several minutes** to bootstrap the network. + +## Sidecar + +### Getting faucet wallet + +```shell +curl -s http://ton:8000/faucet.json | jq +{ + "initialBalance": 1000001000000000, + "privateKey": "...", + "publicKey": "...", + "walletRawAddress": "...", + "mnemonic": "...", + "walletVersion": "V3R2", + "workChain": 0, + "subWalletId": 42, + "created": false +} +``` + +### Getting lite client config + +Please note that the config returns IP of localhost (`int 2130706433`). +You need to replace it with the actual IP of the docker container. + +```shell +curl -s http://ton:8000/lite-client.json | jq +{ + "@type": "config.global", + "dht": { ... }, + "liteservers": [ + { + "id": { "key": "...", "@type": "pub.ed25519" }, + "port": 4443, + "ip": 2130706433 + } + ], + "validator": { ... } +} +``` + +### Checking the node status + +It checks for config existence and the fact of faucet wallet deployment + +```shell +curl -s http://ton:8000/status | jq +{ + "status": "OK" +} +``` \ No newline at end of file diff --git a/contrib/localnet/ton/sidecar.go b/contrib/localnet/ton/sidecar.go new file mode 100644 index 0000000000..ec9860633e --- /dev/null +++ b/contrib/localnet/ton/sidecar.go @@ -0,0 +1,124 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "net/http" + "os" +) + +const ( + port = ":8000" + + basePath = "/opt/my-local-ton/myLocalTon" + liteClientConfigPath = basePath + "/genesis/db/my-ton-local.config.json" + settingsPath = basePath + "/settings.json" + + faucetJSONKey = "faucetWalletSettings" +) + +func main() { + http.HandleFunc("/faucet.json", errorWrapper(faucetHandler)) + http.HandleFunc("/lite-client.json", errorWrapper(liteClientHandler)) + http.HandleFunc("/status", errorWrapper(statusHandler)) + + //nolint:gosec + if err := http.ListenAndServe(port, nil); err != nil { + log.Fatal(err) + } +} + +func errorWrapper(handler func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := handler(w, r); err != nil { + errResponse(w, http.StatusInternalServerError, err) + } + } +} + +// Handler for the /faucet.json route +func faucetHandler(w http.ResponseWriter, _ *http.Request) error { + faucet, err := extractFaucetFromSettings(settingsPath) + if err != nil { + return err + } + + jsonResponse(w, http.StatusOK, faucet) + return nil +} + +func liteClientHandler(w http.ResponseWriter, _ *http.Request) error { + data, err := os.ReadFile(liteClientConfigPath) + if err != nil { + return fmt.Errorf("could not read lite client config: %w", err) + } + + jsonResponse(w, http.StatusOK, json.RawMessage(data)) + return nil +} + +// Handler for the /status route +func statusHandler(w http.ResponseWriter, _ *http.Request) error { + if _, err := os.Stat(liteClientConfigPath); err != nil { + return fmt.Errorf("lite client config %q not found: %w", liteClientConfigPath, err) + } + + faucet, err := extractFaucetFromSettings(settingsPath) + if err != nil { + return err + } + + type faucetShape struct { + Created bool `json:"created"` + } + + var fs faucetShape + if err = json.Unmarshal(faucet, &fs); err != nil { + return fmt.Errorf("failed to parse faucet settings: %w", err) + } + + if !fs.Created { + return errors.New("faucet is not created yet") + } + + jsonResponse(w, http.StatusOK, map[string]string{"status": "OK"}) + return nil +} + +func extractFaucetFromSettings(filePath string) (json.RawMessage, error) { + data, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("could not read faucet settings: %w", err) + } + + var keyValue map[string]json.RawMessage + if err := json.Unmarshal(data, &keyValue); err != nil { + return nil, fmt.Errorf("failed to parse faucet settings: %w", err) + } + + faucet, ok := keyValue[faucetJSONKey] + if !ok { + return nil, errors.New("faucet settings not found in JSON") + } + + return faucet, nil +} + +func errResponse(w http.ResponseWriter, status int, err error) { + jsonResponse(w, status, map[string]string{"error": err.Error()}) +} + +func jsonResponse(w http.ResponseWriter, status int, data any) { + bytes, err := json.Marshal(data) + if err != nil { + bytes = []byte("Failed to marshal JSON") + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + + //nolint:errcheck + w.Write(bytes) +} diff --git a/go.mod b/go.mod index 922c2cf4b1..57f3e3b7c1 100644 --- a/go.mod +++ b/go.mod @@ -335,6 +335,12 @@ require ( sigs.k8s.io/yaml v1.4.0 // indirect ) +require ( + github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect + github.com/snksoft/crc v1.1.0 // indirect + github.com/tonkeeper/tongo v1.9.3 // indirect +) + replace ( github.com/agl/ed25519 => github.com/binance-chain/edwards25519 v0.0.0-20200305024217-f36fc4b53d43 github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.22.3 diff --git a/go.sum b/go.sum index 89792efb1f..c6d2d28543 100644 --- a/go.sum +++ b/go.sum @@ -1243,6 +1243,8 @@ github.com/nishanths/predeclared v0.0.0-20200524104333-86fad755b4d3/go.mod h1:nt github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae h1:7smdlrfdcZic4VfsGKD2ulWL804a4GVphr4s7WZxGiY= +github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -1445,6 +1447,8 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4= +github.com/snksoft/crc v1.1.0 h1:HkLdI4taFlgGGG1KvsWMpz78PkOC9TkPVpTV/cuWn48= +github.com/snksoft/crc v1.1.0/go.mod h1:5/gUOsgAm7OmIhb6WJzw7w5g2zfJi4FrHYgGPdshE+A= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -1563,6 +1567,8 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tonkeeper/tongo v1.9.3 h1:VNIZIuPeMw0+KZPvP57+EbgRwGZocN2v5CulRxba20A= +github.com/tonkeeper/tongo v1.9.3/go.mod h1:MjgIgAytFarjCoVjMLjYEtpZNN1f2G/pnZhKjr28cWs= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= diff --git a/zetaclient/chains/ton/observer/observer_test.go b/zetaclient/chains/ton/observer/observer_test.go new file mode 100644 index 0000000000..e978a589b9 --- /dev/null +++ b/zetaclient/chains/ton/observer/observer_test.go @@ -0,0 +1,63 @@ +package observer + +import ( + "context" + "encoding/json" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "github.com/tonkeeper/tongo/config" + "github.com/tonkeeper/tongo/liteapi" +) + +// todo tmp (will be resolved automatically) +// taken from ton:8000/lite-client.json +const configRaw = `{"@type":"config.global","dht":{"@type":"dht.config.global","k":3,"a":3,"static_nodes": +{"@type":"dht.nodes","nodes":[]}},"liteservers":[{"id":{"key":"+DjLFqH/N5jO1ZO8PYVYU6a6e7EnnsF0GWFsteE+qy8=","@type": +"pub.ed25519"},"port":4443,"ip":2130706433}],"validator":{"@type":"validator.config.global","zero_state": +{"workchain":-1,"shard":-9223372036854775808,"seqno":0,"root_hash":"rR8EFZNlyj3rfYlMyQC8gT0A6ghDrbKe4aMmodiNw6I=", +"file_hash":"fT2hXGv1OF7XDhraoAELrYz6wX3ue16QpSoWTiPrUAE="},"init_block":{"workchain":-1,"shard":-9223372036854775808, +"seqno":0,"root_hash":"rR8EFZNlyj3rfYlMyQC8gT0A6ghDrbKe4aMmodiNw6I=", +"file_hash":"fT2hXGv1OF7XDhraoAELrYz6wX3ue16QpSoWTiPrUAE="}}}` + +func TestObserver(t *testing.T) { + t.Skip("skip test") + + ctx := context.Background() + + cfg, err := config.ParseConfig(strings.NewReader(configRaw)) + require.NoError(t, err) + + client, err := liteapi.NewClient(liteapi.WithConfigurationFile(*cfg)) + require.NoError(t, err) + + res, err := client.GetMasterchainInfo(ctx) + require.NoError(t, err) + + // Outputs: + // { + // "Last": { + // "Workchain": 4294967295, + // "Shard": 9223372036854775808, + // "Seqno": 915, + // "RootHash": "2e9e312c5bd3b7b96d23ce1342ac76e5486012c9aac44781c2c25dbc55f5c8ad", + // "FileHash": "d3745319bfaeebb168d9db6bb5b4752b6b28ab9041735c81d4a02fc820040851" + // }, + // "StateRootHash": "02538fb9dc802004012285a90a7af9ba279706e2deea9ca635decd80e94a7045", + // "Init": { + // "Workchain": 4294967295, + // "RootHash": "ad1f04159365ca3deb7d894cc900bc813d00ea0843adb29ee1a326a1d88dc3a2", + // "FileHash": "7d3da15c6bf5385ed70e1adaa0010bad8cfac17dee7b5e90a52a164e23eb5001" + // } + // } + t.Logf("Masterchain info") + logJSON(t, res) +} + +func logJSON(t *testing.T, v any) { + b, err := json.MarshalIndent(v, "", " ") + require.NoError(t, err) + + t.Log(string(b)) +} diff --git a/zetaclient/chains/ton/utils.go b/zetaclient/chains/ton/utils.go new file mode 100644 index 0000000000..905d58517f --- /dev/null +++ b/zetaclient/chains/ton/utils.go @@ -0,0 +1,24 @@ +package ton + +import ( + "fmt" + "net/http" + + "github.com/tonkeeper/tongo/config" +) + +// ConfigFromURL downloads & parses config. +// +//nolint:gosec +func ConfigFromURL(url string) (*config.GlobalConfigurationFile, error) { + res, err := http.Get(url) + if err != nil { + return nil, err + } + + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("failed to download config file: %s", res.Status) + } + + return config.ParseConfig(res.Body) +} From dab8c3766d270c49149a3ac051e742d061afe9be Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Mon, 9 Sep 2024 21:41:10 +0300 Subject: [PATCH 02/17] Use docker ip for the sidecar --- contrib/localnet/docker-compose.yml | 2 ++ contrib/localnet/ton/sidecar.go | 54 +++++++++++++++++++++++++--- contrib/localnet/ton/sidecar_test.go | 36 +++++++++++++++++++ 3 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 contrib/localnet/ton/sidecar_test.go diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml index fca0e42a72..705d7672a2 100644 --- a/contrib/localnet/docker-compose.yml +++ b/contrib/localnet/docker-compose.yml @@ -251,6 +251,8 @@ services: ports: - "8111:8000" # sidecar - "4443:4443" # lite client + environment: + DOCKER_IP: 172.20.0.104 networks: mynetwork: ipv4_address: 172.20.0.104 diff --git a/contrib/localnet/ton/sidecar.go b/contrib/localnet/ton/sidecar.go index ec9860633e..46f934d782 100644 --- a/contrib/localnet/ton/sidecar.go +++ b/contrib/localnet/ton/sidecar.go @@ -1,16 +1,20 @@ package main import ( + "bytes" + "encoding/binary" "encoding/json" "errors" "fmt" "log" + "net" "net/http" "os" ) const ( - port = ":8000" + port = ":8000" + dockerPort = ":8111" basePath = "/opt/my-local-ton/myLocalTon" liteClientConfigPath = basePath + "/genesis/db/my-ton-local.config.json" @@ -49,12 +53,26 @@ func faucetHandler(w http.ResponseWriter, _ *http.Request) error { return nil } +// liteClientHandler returns lite json client config +// and alters localhost to docker IP if needed. func liteClientHandler(w http.ResponseWriter, _ *http.Request) error { data, err := os.ReadFile(liteClientConfigPath) if err != nil { return fmt.Errorf("could not read lite client config: %w", err) } + dockerIP := os.Getenv("DOCKER_IP") + + if dockerIP != "" { + altered, err := alterConfigIP(data, dockerIP) + if err != nil { + errResponse(w, http.StatusInternalServerError, err) + return nil + } + + data = altered + } + jsonResponse(w, http.StatusOK, json.RawMessage(data)) return nil } @@ -111,14 +129,42 @@ func errResponse(w http.ResponseWriter, status int, err error) { } func jsonResponse(w http.ResponseWriter, status int, data any) { - bytes, err := json.Marshal(data) + b, err := json.Marshal(data) if err != nil { - bytes = []byte("Failed to marshal JSON") + b = []byte("Failed to marshal JSON") } w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) //nolint:errcheck - w.Write(bytes) + w.Write(b) +} + +// TON's lite client config contains the IP of the node. +// And it's localhost, we need to change it to the docker IP. +func alterConfigIP(config []byte, ipString string) ([]byte, error) { + const localhost = uint32(2130706433) + + ip := net.ParseIP(ipString) + if ip == nil { + return nil, fmt.Errorf("failed to parse IP: %q", ipString) + } + + return bytes.ReplaceAll( + config, + uint32ToBytes(localhost), + uint32ToBytes(ip2int(ip)), + ), nil +} + +func ip2int(ip net.IP) uint32 { + if len(ip) == 16 { + return binary.BigEndian.Uint32(ip[12:16]) + } + return binary.BigEndian.Uint32(ip) +} + +func uint32ToBytes(n uint32) []byte { + return []byte(fmt.Sprintf("%d", n)) } diff --git a/contrib/localnet/ton/sidecar_test.go b/contrib/localnet/ton/sidecar_test.go new file mode 100644 index 0000000000..0a5e28be53 --- /dev/null +++ b/contrib/localnet/ton/sidecar_test.go @@ -0,0 +1,36 @@ +package main + +import ( + "strings" + "testing" +) + +func TestIPAlteration(t *testing.T) { + // ARRANGE + const data = `{ + "@type": "config.global", + "dht": { ... }, + "liteservers":[ {"id":{ ... }, "port": 4443, "ip": 2130706433} ], + "validator": { ... } + }` + + // https://www.browserling.com/tools/ip-to-dec + const ( + ip = "142.250.186.78" + expected = "2398796366" + ) + + // ACT + out, err := alterConfigIP([]byte(data), ip) + + // ASSERT + if err != nil { + t.Errorf("Failed: %s", err) + } + + t.Logf("Output: %s", out) + + if !strings.Contains(string(out), expected) { + t.Errorf("Unexpected outcome") + } +} From f597b10793258e36043ca6accffffe4664e0756b Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Mon, 9 Sep 2024 21:54:01 +0300 Subject: [PATCH 03/17] Wire TON into localnet. Add sidecar client --- Makefile | 5 ++ cmd/zetae2e/config/clients.go | 65 +++++++++++++++------ cmd/zetae2e/config/local.yml | 84 +++++++++++++++++++++++++++- cmd/zetae2e/config/localnet.yml | 1 + cmd/zetae2e/init.go | 2 + cmd/zetae2e/local/local.go | 21 +++++++ cmd/zetae2e/local/ton.go | 55 ++++++++++++++++++ contrib/localnet/ton/sidecar.go | 3 +- e2e/config/config.go | 13 +++-- e2e/e2etests/e2etests.go | 20 ++++++- e2e/e2etests/test_ton_deposit.go | 7 +++ e2e/runner/clients.go | 12 ++-- e2e/runner/logger.go | 18 ++++-- e2e/runner/ton.go | 33 +++++++++++ e2e/runner/ton/sidecarclient.go | 96 ++++++++++++++++++++++++++++++++ zetaclient/chains/ton/utils.go | 18 +++++- 16 files changed, 410 insertions(+), 43 deletions(-) create mode 100644 cmd/zetae2e/local/ton.go create mode 100644 e2e/e2etests/test_ton_deposit.go create mode 100644 e2e/runner/ton.go create mode 100644 e2e/runner/ton/sidecarclient.go diff --git a/Makefile b/Makefile index a300d11485..86ef7aa1b9 100644 --- a/Makefile +++ b/Makefile @@ -285,6 +285,11 @@ start-solana-test: zetanode solana export E2E_ARGS="--skip-regular --test-solana" && \ cd contrib/localnet/ && $(DOCKER_COMPOSE) --profile solana -f docker-compose.yml up -d +start-ton-test: zetanode ton + @echo "--> Starting TON test" + export E2E_ARGS="--skip-regular --test-ton" && \ + cd contrib/localnet/ && $(DOCKER_COMPOSE) --profile ton -f docker-compose.yml up -d + start-v2-test: zetanode @echo "--> Starting e2e smart contracts v2 test" export E2E_ARGS="--skip-regular --test-v2" && \ diff --git a/cmd/zetae2e/config/clients.go b/cmd/zetae2e/config/clients.go index 0a2534cd09..467be6b858 100644 --- a/cmd/zetae2e/config/clients.go +++ b/cmd/zetae2e/config/clients.go @@ -8,50 +8,72 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/ethclient" "github.com/gagliardetto/solana-go/rpc" + ton "github.com/tonkeeper/tongo/liteapi" + tonrunner "github.com/zeta-chain/node/e2e/runner/ton" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "github.com/zeta-chain/node/e2e/config" "github.com/zeta-chain/node/e2e/runner" zetacore_rpc "github.com/zeta-chain/node/pkg/rpc" + tonutil "github.com/zeta-chain/node/zetaclient/chains/ton" ) // getClientsFromConfig get clients from config -func getClientsFromConfig(ctx context.Context, conf config.Config, account config.Account) ( - runner.Clients, - error, -) { - var solanaClient *rpc.Client - if conf.RPCs.Solana != "" { - if solanaClient = rpc.New(conf.RPCs.Solana); solanaClient == nil { - return runner.Clients{}, fmt.Errorf("failed to get solana client") - } - } +func getClientsFromConfig(ctx context.Context, conf config.Config, account config.Account) (runner.Clients, error) { btcRPCClient, err := getBtcClient(conf.RPCs.Bitcoin) if err != nil { return runner.Clients{}, fmt.Errorf("failed to get btc client: %w", err) } + evmClient, evmAuth, err := getEVMClient(ctx, conf.RPCs.EVM, account) if err != nil { return runner.Clients{}, fmt.Errorf("failed to get evm client: %w", err) } + + var solanaClient *rpc.Client + if conf.RPCs.Solana != "" { + if solanaClient = rpc.New(conf.RPCs.Solana); solanaClient == nil { + return runner.Clients{}, fmt.Errorf("failed to get solana client") + } + } + + var ( + tonClient *ton.Client + tonSidecarClient *tonrunner.SidecarClient + ) + + if conf.RPCs.TONSidecarURL != "" { + tonSidecarClient = tonrunner.NewSidecarClient(conf.RPCs.TONSidecarURL) + + c, err := getTONClient(ctx, tonSidecarClient.LiteServerURL()) + if err != nil { + return runner.Clients{}, fmt.Errorf("failed to get ton client: %w", err) + } + + tonClient = c + } + zetaCoreClients, err := GetZetacoreClient(conf) if err != nil { return runner.Clients{}, fmt.Errorf("failed to get zetacore client: %w", err) } + zevmClient, zevmAuth, err := getEVMClient(ctx, conf.RPCs.Zevm, account) if err != nil { return runner.Clients{}, fmt.Errorf("failed to get zevm client: %w", err) } return runner.Clients{ - Zetacore: zetaCoreClients, - BtcRPC: btcRPCClient, - Solana: solanaClient, - Evm: evmClient, - EvmAuth: evmAuth, - Zevm: zevmClient, - ZevmAuth: zevmAuth, + Zetacore: zetaCoreClients, + BtcRPC: btcRPCClient, + Solana: solanaClient, + TON: tonClient, + TONSidecar: tonSidecarClient, + Evm: evmClient, + EvmAuth: evmAuth, + Zevm: zevmClient, + ZevmAuth: zevmAuth, }, nil } @@ -106,6 +128,15 @@ func getEVMClient( return evmClient, evmAuth, nil } +func getTONClient(ctx context.Context, configURL string) (*ton.Client, error) { + cfg, err := tonutil.ConfigFromURL(ctx, configURL) + if err != nil { + return nil, fmt.Errorf("failed to get ton config: %w", err) + } + + return ton.NewClient(ton.WithConfigurationFile(*cfg)) +} + func GetZetacoreClient(conf config.Config) (zetacore_rpc.Clients, error) { if conf.RPCs.ZetaCoreGRPC != "" { return zetacore_rpc.NewGRPCClients( diff --git a/cmd/zetae2e/config/local.yml b/cmd/zetae2e/config/local.yml index a0205ee000..2481ab31fa 100644 --- a/cmd/zetae2e/config/local.yml +++ b/cmd/zetae2e/config/local.yml @@ -1,8 +1,85 @@ -zeta_chain_id: "athens_7001-1" +zeta_chain_id: "athens_101-1" default_account: - bech32_address: zeta1uhznv7uzyjq84s3q056suc8pkme85lkvhrz3dd + bech32_address: "zeta1uhznv7uzyjq84s3q056suc8pkme85lkvhrz3dd" evm_address: "0xE5C5367B8224807Ac2207d350E60e1b6F27a7ecC" private_key: "d87baf7bf6dc560a252596678c12e41f7d1682837f05b29d411bc3f78ae2c263" +additional_accounts: + user_erc20: + bech32_address: "zeta1datate7xmwm4uk032f9rmcu0cwy7ch7kg6y6zv" + evm_address: "0x6F57D5E7c6DBb75e59F1524a3dE38Fc389ec5Fd6" + private_key: "fda3be1b1517bdf48615bdadacc1e6463d2865868dc8077d2cdcfa4709a16894" + user_zeta_test: + bech32_address: "zeta1tnp0hvsq4y5mxuhrq9h3jfwulxywpq0ads0rer" + evm_address: "0x5cC2fBb200A929B372e3016F1925DcF988E081fd" + private_key: "729a6cdc5c925242e7df92fdeeb94dadbf2d0b9950d4db8f034ab27a3b114ba7" + user_zevm_mp_test: + bech32_address: "zeta13t3zjxvwec7g38q8mdjga37rpes9zkfvv7tkn2" + evm_address: "0x8Ae229198eCE3c889C07DB648Ec7C30E6051592c" + private_key: "105460aebf71b10bfdb710ef5aa6d2932ee6ff6fc317ac9c24e0979903b10a5d" + user_bitcoin: + bech32_address: "zeta19q7czqysah6qg0n4y3l2a08gfzqxydla492v80" + evm_address: "0x283d810090EdF4043E75247eAeBcE848806237fD" + private_key: "7bb523963ee2c78570fb6113d886a4184d42565e8847f1cb639f5f5e2ef5b37a" + user_solana: + bech32_address: "zeta1zqlajgj0qr8rqylf2c572t0ux8vqt45d4zngpm" + evm_address: "0x103FD9224F00ce3013e95629e52DFc31D805D68d" + private_key: "dd53f191113d18e57bd4a5494a64a020ba7919c815d0a6d34a42ebb2839e9d95" + solana_private_key: "4yqSQxDeTBvn86BuxcN5jmZb2gaobFXrBqu8kiE9rZxNkVMe3LfXmFigRsU4sRp7vk4vVP1ZCFiejDKiXBNWvs2C" + user_ether: + bech32_address: "zeta134rakuus43xn63yucgxhn88ywj8ewcv6ezn2ga" + evm_address: "0x8D47Db7390AC4D3D449Cc20D799ce4748F97619A" + private_key: "098e74a1c2261fa3c1b8cfca8ef2b4ff96c73ce36710d208d1f6535aef42545d" + user_misc: + bech32_address: "zeta1jqfx6qhyrj0t9ggvl3p64vaax3s9y0xldt020w" + evm_address: "0x90126d02E41c9eB2a10cfc43aAb3BD3460523Cdf" + private_key: "853c0945b8035a501b1161df65a17a0a20fc848bda8975a8b4e9222cc6f84cd4" + user_admin: + bech32_address: "zeta17w0adeg64ky0daxwd2ugyuneellmjgnx4e483s" + evm_address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + private_key: "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + user_migration: + bech32_address: "zeta1pvtxa708yvdmszn687nne6nl8qn704daf420xz" + evm_address: "0x0B166ef9e7231Bb80A7A3FA73CEA7F3827E7D5BD" + private_key: "0bcc2fa28b526f90e1d54648d612db901e860bf68248555593f91ea801c6b482" + user_precompile: + bech32_address: "zeta1k4f0l2e9qqjccxnstwj0uaarxvn44lj990she9" + evm_address: "0xb552FFAb2500258C1A705Ba4Fe77A333275AFE45" + private_key: "bd6b74387f11b31d21e87c2ae7a23ec269aee08a355dad6c508a6fceb79d1f48" + user_v2_ether: + bech32_address: "zeta1erlqlpl5da7a9r3emzw60kax9fxc3h0r3z7c5e" + evm_address: "0xC8fe0F87f46F7Dd28e39D89Da7Dba62A4D88dde3" + private_key: "11c25af71c82602a681ce622bf76f4f0fbc3b7f23ce935db6249d1517322f436" + user_v2_erc20: + bech32_address: "zeta12wp6syndml6jd32m7f9mn2wscsxz6cff8nczl4" + evm_address: "0x5383A8126ddff526C55bF24Bb9a9D0c40c2d6129" + private_key: "77b0e4dcc29c5c47b6999dabd42abcfdf7750ccc86d6659c1373ec1ea3b4af6c" + user_v2_ether_revert: + bech32_address: "zeta1m7m5xd79x9qmlyfpqxcwuac04r3dewfpdcfw5e" + evm_address: "0xdFb74337c53141bf912101b0Ee770FA8e2DCB921" + private_key: "be7098604cc40f95d68298a3b4ae13972ac8a3df271ba19ddf169070d30e8ba8" + user_v2_erc20_revert: + bech32_address: "zeta1nry9yeg6njhjrp2ctppa8558vqxal9fxk69zxg" + evm_address: "0x98c852651A9CAF2185585843d3D287600Ddf9526" + private_key: "bf9456c679bb5a952a9a137fcfc920e0413efdb97c36de1e57455763084230cb" +policy_accounts: + emergency_policy_account: + bech32_address: "zeta16m2cnrdwtgweq4njc6t470vl325gw4kp6s7tap" + evm_address: "0xd6d5898dAE5A1D905672c6975F3d9f8aA88756C1" + private_key: "88BE93D11624B794F4BCC77BEA7385AF7EAD0B183B913485C74F0A803ABBC3F0" + operational_policy_account: + bech32_address: "zeta1pgx85vzx4fzh5zzyjqgs6a6cmaujd0xs8efrkc" + evm_address: "0x0A0c7a3046AA457A084490110d7758Df7926bcd0" + private_key: "59D1B982BD446545A1740ABD01F1ED9C162B72ACC1522B9B71B6DB5A9C37FA7D" + admin_policy_account: + bech32_address: "zeta142ds9x7raljv2qz9euys93e64gjmgdfnc47dwq" + evm_address: "0xAa9b029BC3EFe4c50045Cf0902c73aAa25b43533" + private_key: "0595CB0CD9BF5264A85A603EC8E43C30ADBB5FD2D9E2EF84C374EA4A65BB616C" +observer_relayer_accounts: + relayer_accounts: + - solana_address: "2qBVcNBZCubcnSR3NyCnFjCfkCVUB3G7ECPoaW5rxVjx" + solana_private_key: "3EMjCcCJg53fMEGVj13UPQpo6py9AKKyLE2qroR4yL1SvAN2tUznBvDKRYjntw7m6Jof1R2CSqjTddL27rEb6sFQ" + - solana_address: "4kkCV8H38xirwQTkE5kL6FHNtYGHnMQQ7SkCjAxibHFK" + solana_private_key: "5SSv7jWzamtjWNKGiKf3gvCPHcq9mE5x6LhYgzJCKNSxoQ83gFpmMgmg2JS2zdKcBEdwy7y9bvWgX4LBiUpvnrPf" rpcs: zevm: "http://localhost:9545" evm: "http://localhost:8545" @@ -15,7 +92,8 @@ rpcs: params: regnet zetacore_grpc: "localhost:9090" zetacore_rpc: "http://localhost:26657" - solana: "http://solana:8899" + solana: "http://localhost:8899" + ton_sidecar_url: "http://localhost:8111" contracts: zevm: system_contract: "0x91d18e54DAf4F677cB28167158d6dd21F6aB3921" diff --git a/cmd/zetae2e/config/localnet.yml b/cmd/zetae2e/config/localnet.yml index 48791750e0..5bd207a020 100644 --- a/cmd/zetae2e/config/localnet.yml +++ b/cmd/zetae2e/config/localnet.yml @@ -91,6 +91,7 @@ rpcs: disable_tls: true params: regnet solana: "http://solana:8899" + ton_sidecar_url: "http://ton:8000" zetacore_grpc: "zetacore0:9090" zetacore_rpc: "http://zetacore0:26657" # contracts will be populated on first run diff --git a/cmd/zetae2e/init.go b/cmd/zetae2e/init.go index df77991e88..0a58db71a9 100644 --- a/cmd/zetae2e/init.go +++ b/cmd/zetae2e/init.go @@ -28,6 +28,8 @@ func NewInitCmd() *cobra.Command { InitCmd.Flags().StringVar(&initConf.RPCs.Bitcoin.Host, "btcURL", "bitcoin:18443", "--grpcURL bitcoin:18443") InitCmd.Flags(). StringVar(&initConf.RPCs.Solana, "solanaURL", "http://solana:8899", "--solanaURL http://solana:8899") + InitCmd.Flags(). + StringVar(&initConf.RPCs.TONSidecarURL, "tonSidecarURL", "http://ton:8111", "--tonSidecarURL http://ton:8111") InitCmd.Flags().StringVar(&initConf.ZetaChainID, "chainID", "athens_101-1", "--chainID athens_101-1") InitCmd.Flags().StringVar(&configFile, local.FlagConfigFile, "e2e.config", "--cfg ./e2e.config") diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 40900872f4..9d154e5e78 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -33,6 +33,7 @@ const ( flagTestPerformance = "test-performance" flagTestCustom = "test-custom" flagTestSolana = "test-solana" + flagTestTON = "test-ton" flagSkipRegular = "skip-regular" flagLight = "light" flagSetupOnly = "setup-only" @@ -68,6 +69,7 @@ func NewLocalCmd() *cobra.Command { cmd.Flags().Bool(flagTestPerformance, false, "set to true to run performance tests") cmd.Flags().Bool(flagTestCustom, false, "set to true to run custom tests") cmd.Flags().Bool(flagTestSolana, false, "set to true to run solana tests") + cmd.Flags().Bool(flagTestTON, false, "set to true to run TON tests") cmd.Flags().Bool(flagSkipRegular, false, "set to true to skip regular tests") cmd.Flags().Bool(flagLight, false, "run the most basic regular tests, useful for quick checks") cmd.Flags().Bool(flagSetupOnly, false, "set to true to only setup the networks") @@ -97,6 +99,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { testPerformance = must(cmd.Flags().GetBool(flagTestPerformance)) testCustom = must(cmd.Flags().GetBool(flagTestCustom)) testSolana = must(cmd.Flags().GetBool(flagTestSolana)) + testTON = must(cmd.Flags().GetBool(flagTestTON)) skipRegular = must(cmd.Flags().GetBool(flagSkipRegular)) light = must(cmd.Flags().GetBool(flagLight)) setupOnly = must(cmd.Flags().GetBool(flagSetupOnly)) @@ -374,6 +377,24 @@ func localE2ETest(cmd *cobra.Command, _ []string) { eg.Go(solanaTestRoutine(conf, deployerRunner, verbose, solanaTests...)) } + if testTON { + if deployerRunner.Clients.TON == nil { + logger.Print("❌ TON client is nil, maybe TON lite-server config is not set") + os.Exit(1) + } + + if deployerRunner.Clients.TONSidecar == nil { + logger.Print("❌ TON sidecar is nil") + os.Exit(1) + } + + tonTests := []string{ + e2etests.TestTONDepositName, + } + + eg.Go(tonTestRoutine(conf, deployerRunner, verbose, tonTests...)) + } + if testV2 { // update the ERC20 custody contract for v2 tests // note: not run in testV2Migration because it is already run in the migration process diff --git a/cmd/zetae2e/local/ton.go b/cmd/zetae2e/local/ton.go new file mode 100644 index 0000000000..cb7886696a --- /dev/null +++ b/cmd/zetae2e/local/ton.go @@ -0,0 +1,55 @@ +package local + +import ( + "time" + + "github.com/fatih/color" + "github.com/pkg/errors" + "github.com/zeta-chain/node/e2e/config" + "github.com/zeta-chain/node/e2e/e2etests" + "github.com/zeta-chain/node/e2e/runner" +) + +// tonTestRoutine runs TON related e2e tests +func tonTestRoutine( + conf config.Config, + deployerRunner *runner.E2ERunner, + verbose bool, + testNames ...string, +) func() error { + return func() (err error) { + tonRunner, err := initTestRunner( + "ton", + conf, + deployerRunner, + conf.AdditionalAccounts.UserSolana, + runner.NewLogger(verbose, color.FgCyan, "ton"), + ) + if err != nil { + return errors.Wrap(err, "unable to init ton test runner") + } + + tonRunner.Logger.Print("πŸƒ starting TON tests") + startTime := time.Now() + + tests, err := tonRunner.GetE2ETestsToRunByName(e2etests.AllE2ETests, testNames...) + if err != nil { + return errors.Wrap(err, "unable to get ton tests to run") + } + + if err := tonRunner.RunE2ETests(tests); err != nil { + return errors.Wrap(err, "ton tests failed") + } + + if err := tonRunner.EnsureTONBootstrapped(5 * time.Minute); err != nil { + return errors.Wrap(err, "unable to bootstrap TON") + } + + // todo setup deployer wallet + // todo deploy gateway + + tonRunner.Logger.Print("🍾 ton tests completed in %s", time.Since(startTime).String()) + + return nil + } +} diff --git a/contrib/localnet/ton/sidecar.go b/contrib/localnet/ton/sidecar.go index 46f934d782..eafe44d301 100644 --- a/contrib/localnet/ton/sidecar.go +++ b/contrib/localnet/ton/sidecar.go @@ -13,8 +13,7 @@ import ( ) const ( - port = ":8000" - dockerPort = ":8111" + port = ":8000" basePath = "/opt/my-local-ton/myLocalTon" liteClientConfigPath = basePath + "/genesis/db/my-ton-local.config.json" diff --git a/e2e/config/config.go b/e2e/config/config.go index 15a7d57306..089ea39b70 100644 --- a/e2e/config/config.go +++ b/e2e/config/config.go @@ -91,12 +91,13 @@ type ObserverRelayerAccounts struct { // RPCs contains the configuration for the RPC endpoints type RPCs struct { - Zevm string `yaml:"zevm"` - EVM string `yaml:"evm"` - Bitcoin BitcoinRPC `yaml:"bitcoin"` - Solana string `yaml:"solana"` - ZetaCoreGRPC string `yaml:"zetacore_grpc"` - ZetaCoreRPC string `yaml:"zetacore_rpc"` + Zevm string `yaml:"zevm"` + EVM string `yaml:"evm"` + Bitcoin BitcoinRPC `yaml:"bitcoin"` + Solana string `yaml:"solana"` + TONSidecarURL string `yaml:"ton_sidecar_url"` + ZetaCoreGRPC string `yaml:"zetacore_grpc"` + ZetaCoreRPC string `yaml:"zetacore_rpc"` } // BitcoinRPC contains the configuration for the Bitcoin RPC endpoint diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 988c4fc5b8..24bf17217a 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -52,13 +52,18 @@ const ( TestERC20DepositAndCallRefundName = "erc20_deposit_and_call_refund" /* - Solana tests - */ + * Solana tests + */ TestSolanaDepositName = "solana_deposit" TestSolanaWithdrawName = "solana_withdraw" TestSolanaDepositAndCallName = "solana_deposit_and_call" TestSolanaDepositAndCallRefundName = "solana_deposit_and_call_refund" + /** + * TON tests + */ + TestTONDepositName = "ton_deposit" + /* Bitcoin tests Test transfer of Bitcoin asset across chains @@ -400,6 +405,17 @@ var AllE2ETests = []runner.E2ETest{ }, TestSolanaDepositAndCallRefund, ), + /* + TON tests + */ + runner.NewE2ETest( + TestTONDepositName, + "deposit TON into ZEVM", + []runner.ArgDefinition{ + {Description: "amount in nano tons", DefaultValue: "900000000"}, // 0.9 TON + }, + TestTONDeposit, + ), /* Bitcoin tests */ diff --git a/e2e/e2etests/test_ton_deposit.go b/e2e/e2etests/test_ton_deposit.go new file mode 100644 index 0000000000..c2aa0dcbf8 --- /dev/null +++ b/e2e/e2etests/test_ton_deposit.go @@ -0,0 +1,7 @@ +package e2etests + +import "github.com/zeta-chain/node/e2e/runner" + +func TestTONDeposit(r *runner.E2ERunner, args []string) { + r.Logger.Info("Hello from TestTONDeposit! Args: %v", args) +} diff --git a/e2e/runner/clients.go b/e2e/runner/clients.go index e0a4ab1c97..4fbed015ab 100644 --- a/e2e/runner/clients.go +++ b/e2e/runner/clients.go @@ -5,6 +5,8 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/ethclient" "github.com/gagliardetto/solana-go/rpc" + ton "github.com/tonkeeper/tongo/liteapi" + tonrunner "github.com/zeta-chain/node/e2e/runner/ton" zetacore_rpc "github.com/zeta-chain/node/pkg/rpc" ) @@ -14,10 +16,12 @@ type Clients struct { Zetacore zetacore_rpc.Clients // the RPC clients for external chains in the localnet - BtcRPC *rpcclient.Client - Solana *rpc.Client - Evm *ethclient.Client - EvmAuth *bind.TransactOpts + BtcRPC *rpcclient.Client + Solana *rpc.Client + Evm *ethclient.Client + EvmAuth *bind.TransactOpts + TON *ton.Client + TONSidecar *tonrunner.SidecarClient // the RPC clients for ZetaChain Zevm *ethclient.Client diff --git a/e2e/runner/logger.go b/e2e/runner/logger.go index d52a625249..16b34836f4 100644 --- a/e2e/runner/logger.go +++ b/e2e/runner/logger.go @@ -71,15 +71,21 @@ func (l *Logger) PrintNoPrefix(message string, args ...interface{}) { } // Info prints a message to the logger if verbose is true -func (l *Logger) Info(message string, args ...interface{}) { +func (l *Logger) Info(message string, args ...any) { + if !l.verbose { + return + } + l.mu.Lock() defer l.mu.Unlock() - if l.verbose { - text := fmt.Sprintf(message, args...) - // #nosec G104 - we are not using user input - _, _ = l.logger.Print(l.getPrefixWithPadding() + loggerSeparator + "[INFO]" + text + "\n") - } + var ( + content = fmt.Sprintf(message, args...) + line = fmt.Sprintf("%s%s[INFO] %s \n", l.getPrefixWithPadding(), loggerSeparator, content) + ) + + // #nosec G104 - we are not using user input + _, _ = l.logger.Print(line) } // InfoLoud prints a message to the logger if verbose is true diff --git a/e2e/runner/ton.go b/e2e/runner/ton.go new file mode 100644 index 0000000000..9bd6edd8e5 --- /dev/null +++ b/e2e/runner/ton.go @@ -0,0 +1,33 @@ +package runner + +import ( + "context" + "time" + + "github.com/pkg/errors" + "github.com/zeta-chain/node/e2e/runner/ton" +) + +// EnsureTONBootstrapped waits unless TON node is bootstrapped. +// - Node should be operational +// - Lite server config exists +// - Faucet is deployed +func (r *E2ERunner) EnsureTONBootstrapped(timeout time.Duration) error { + ctx, cancel := context.WithTimeout(r.Ctx, timeout) + defer cancel() + + for { + err := r.Clients.TONSidecar.Status(ctx) + switch { + case errors.Is(err, context.DeadlineExceeded): + return errors.Wrap(err, "timeout waiting for TON to bootstrap") + case errors.Is(err, ton.ErrNotHealthy): + // okay, continue + case err == nil: + return nil + } + + r.Logger.Info("Waiting for TON to bootstrap...") + time.Sleep(2 * time.Second) + } +} diff --git a/e2e/runner/ton/sidecarclient.go b/e2e/runner/ton/sidecarclient.go new file mode 100644 index 0000000000..9a00510a75 --- /dev/null +++ b/e2e/runner/ton/sidecarclient.go @@ -0,0 +1,96 @@ +package ton + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "github.com/pkg/errors" +) + +type SidecarClient struct { + baseURL string + c *http.Client +} + +type Faucet struct { + InitialBalance int64 `json:"initialBalance"` + PrivateKey string `json:"privateKey"` + PublicKey string `json:"publicKey"` + WalletRawAddress string `json:"walletRawAddress"` + Mnemonic string `json:"mnemonic"` + WalletVersion string `json:"walletVersion"` + WorkChain int `json:"workChain"` + SubWalletId int `json:"subWalletId"` + Created bool `json:"created"` +} + +var ErrNotHealthy = fmt.Errorf("TON node is not healthy yet") + +func NewSidecarClient(baseURL string) *SidecarClient { + c := &http.Client{Timeout: 3 * time.Second} + return &SidecarClient{baseURL, c} +} + +// LiteServerURL returns the URL to the lite server config +func (c *SidecarClient) LiteServerURL() string { + return fmt.Sprintf("%s/lite-client.json", c.baseURL) +} + +// GetFaucet returns the faucet information. +func (c *SidecarClient) GetFaucet(ctx context.Context) (Faucet, error) { + resp, err := c.get(ctx, "faucet.json") + if err != nil { + return Faucet{}, err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return Faucet{}, fmt.Errorf("unexpected response status: %d", resp.StatusCode) + } + + var faucet Faucet + if err := json.NewDecoder(resp.Body).Decode(&faucet); err != nil { + return Faucet{}, err + } + + return faucet, nil +} + +// Status checks the health of the TON node. Returns ErrNotHealthy or nil. +func (c *SidecarClient) Status(ctx context.Context) error { + resp, err := c.get(ctx, "status") + if err != nil { + return err + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body: %w", err) + } + + if err := resp.Body.Close(); err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return errors.Wrapf(ErrNotHealthy, "status %d. %s", resp.StatusCode, string(body)) + } + + return nil +} + +func (c *SidecarClient) get(ctx context.Context, path string) (*http.Response, error) { + url := fmt.Sprintf("%s/%s", c.baseURL, path) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + return c.c.Do(req) +} diff --git a/zetaclient/chains/ton/utils.go b/zetaclient/chains/ton/utils.go index 905d58517f..11c3e2050f 100644 --- a/zetaclient/chains/ton/utils.go +++ b/zetaclient/chains/ton/utils.go @@ -1,17 +1,29 @@ package ton import ( + "context" "fmt" "net/http" + "time" "github.com/tonkeeper/tongo/config" ) -// ConfigFromURL downloads & parses config. +// ConfigFromURL downloads & parses lite server config. // //nolint:gosec -func ConfigFromURL(url string) (*config.GlobalConfigurationFile, error) { - res, err := http.Get(url) +func ConfigFromURL(ctx context.Context, url string) (*config.GlobalConfigurationFile, error) { + const timeout = 3 * time.Second + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + res, err := http.DefaultClient.Do(req) if err != nil { return nil, err } From 16a2a9fa9719f920c9048d8f9dc0a46212ce3c1e Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Mon, 9 Sep 2024 21:56:52 +0300 Subject: [PATCH 04/17] Rename utils to config --- cmd/zetae2e/config/clients.go | 4 ++-- zetaclient/chains/ton/{utils.go => config.go} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename zetaclient/chains/ton/{utils.go => config.go} (100%) diff --git a/cmd/zetae2e/config/clients.go b/cmd/zetae2e/config/clients.go index 467be6b858..3d1e571287 100644 --- a/cmd/zetae2e/config/clients.go +++ b/cmd/zetae2e/config/clients.go @@ -16,7 +16,7 @@ import ( "github.com/zeta-chain/node/e2e/config" "github.com/zeta-chain/node/e2e/runner" zetacore_rpc "github.com/zeta-chain/node/pkg/rpc" - tonutil "github.com/zeta-chain/node/zetaclient/chains/ton" + tonconfig "github.com/zeta-chain/node/zetaclient/chains/ton" ) // getClientsFromConfig get clients from config @@ -129,7 +129,7 @@ func getEVMClient( } func getTONClient(ctx context.Context, configURL string) (*ton.Client, error) { - cfg, err := tonutil.ConfigFromURL(ctx, configURL) + cfg, err := tonconfig.ConfigFromURL(ctx, configURL) if err != nil { return nil, fmt.Errorf("failed to get ton config: %w", err) } diff --git a/zetaclient/chains/ton/utils.go b/zetaclient/chains/ton/config.go similarity index 100% rename from zetaclient/chains/ton/utils.go rename to zetaclient/chains/ton/config.go From cb4d140886124058eddcbd91b82e847d331e5824 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Mon, 9 Sep 2024 21:59:33 +0300 Subject: [PATCH 05/17] Please linter --- cmd/zetae2e/config/clients.go | 2 +- cmd/zetae2e/local/ton.go | 1 + e2e/runner/clients.go | 2 +- e2e/runner/ton.go | 1 + e2e/runner/ton/sidecarclient.go | 3 +++ zetaclient/chains/ton/config.go | 4 +++- 6 files changed, 10 insertions(+), 3 deletions(-) diff --git a/cmd/zetae2e/config/clients.go b/cmd/zetae2e/config/clients.go index 3d1e571287..b6f746c814 100644 --- a/cmd/zetae2e/config/clients.go +++ b/cmd/zetae2e/config/clients.go @@ -9,12 +9,12 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/gagliardetto/solana-go/rpc" ton "github.com/tonkeeper/tongo/liteapi" - tonrunner "github.com/zeta-chain/node/e2e/runner/ton" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "github.com/zeta-chain/node/e2e/config" "github.com/zeta-chain/node/e2e/runner" + tonrunner "github.com/zeta-chain/node/e2e/runner/ton" zetacore_rpc "github.com/zeta-chain/node/pkg/rpc" tonconfig "github.com/zeta-chain/node/zetaclient/chains/ton" ) diff --git a/cmd/zetae2e/local/ton.go b/cmd/zetae2e/local/ton.go index cb7886696a..ae79eb9275 100644 --- a/cmd/zetae2e/local/ton.go +++ b/cmd/zetae2e/local/ton.go @@ -5,6 +5,7 @@ import ( "github.com/fatih/color" "github.com/pkg/errors" + "github.com/zeta-chain/node/e2e/config" "github.com/zeta-chain/node/e2e/e2etests" "github.com/zeta-chain/node/e2e/runner" diff --git a/e2e/runner/clients.go b/e2e/runner/clients.go index 4fbed015ab..9e80eb1c07 100644 --- a/e2e/runner/clients.go +++ b/e2e/runner/clients.go @@ -6,8 +6,8 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/gagliardetto/solana-go/rpc" ton "github.com/tonkeeper/tongo/liteapi" - tonrunner "github.com/zeta-chain/node/e2e/runner/ton" + tonrunner "github.com/zeta-chain/node/e2e/runner/ton" zetacore_rpc "github.com/zeta-chain/node/pkg/rpc" ) diff --git a/e2e/runner/ton.go b/e2e/runner/ton.go index 9bd6edd8e5..dc850cac53 100644 --- a/e2e/runner/ton.go +++ b/e2e/runner/ton.go @@ -5,6 +5,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/zeta-chain/node/e2e/runner/ton" ) diff --git a/e2e/runner/ton/sidecarclient.go b/e2e/runner/ton/sidecarclient.go index 9a00510a75..dafd26317f 100644 --- a/e2e/runner/ton/sidecarclient.go +++ b/e2e/runner/ton/sidecarclient.go @@ -16,6 +16,9 @@ type SidecarClient struct { c *http.Client } +// Faucet represents the faucet information. +// +//nolint:revive,stylecheck // comes from my-local-ton type Faucet struct { InitialBalance int64 `json:"initialBalance"` PrivateKey string `json:"privateKey"` diff --git a/zetaclient/chains/ton/config.go b/zetaclient/chains/ton/config.go index 11c3e2050f..7533c9a56d 100644 --- a/zetaclient/chains/ton/config.go +++ b/zetaclient/chains/ton/config.go @@ -18,7 +18,7 @@ func ConfigFromURL(ctx context.Context, url string) (*config.GlobalConfiguration ctx, cancel := context.WithTimeout(ctx, timeout) defer cancel() - req, err := http.NewRequest(http.MethodGet, url, nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err } @@ -28,6 +28,8 @@ func ConfigFromURL(ctx context.Context, url string) (*config.GlobalConfiguration return nil, err } + defer res.Body.Close() + if res.StatusCode != http.StatusOK { return nil, fmt.Errorf("failed to download config file: %s", res.Status) } From d793a25a40cb8fef2554c21d62e4d9608216bdfc Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Tue, 10 Sep 2024 16:48:52 +0300 Subject: [PATCH 06/17] Add faucet; build gateway data --- cmd/zetae2e/local/ton.go | 15 +++-- e2e/e2etests/test_ton_deposit.go | 25 +++++++- e2e/runner/runner.go | 2 + e2e/runner/setup_ton.go | 43 ++++++++++++++ e2e/runner/ton/deployer.go | 51 ++++++++++++++++ e2e/runner/ton/gateway.compiled.json | 5 ++ e2e/runner/ton/gateway.go | 89 ++++++++++++++++++++++++++++ e2e/runner/ton/gateway_test.go | 52 ++++++++++++++++ 8 files changed, 273 insertions(+), 9 deletions(-) create mode 100644 e2e/runner/setup_ton.go create mode 100644 e2e/runner/ton/deployer.go create mode 100644 e2e/runner/ton/gateway.compiled.json create mode 100644 e2e/runner/ton/gateway.go create mode 100644 e2e/runner/ton/gateway_test.go diff --git a/cmd/zetae2e/local/ton.go b/cmd/zetae2e/local/ton.go index ae79eb9275..14721e3b7b 100644 --- a/cmd/zetae2e/local/ton.go +++ b/cmd/zetae2e/local/ton.go @@ -38,16 +38,19 @@ func tonTestRoutine( return errors.Wrap(err, "unable to get ton tests to run") } - if err := tonRunner.RunE2ETests(tests); err != nil { - return errors.Wrap(err, "ton tests failed") - } + const bootstrapTimeout = 5 * time.Minute - if err := tonRunner.EnsureTONBootstrapped(5 * time.Minute); err != nil { + if err := tonRunner.EnsureTONBootstrapped(bootstrapTimeout); err != nil { return errors.Wrap(err, "unable to bootstrap TON") } - // todo setup deployer wallet - // todo deploy gateway + if err := tonRunner.SetupTON(); err != nil { + return errors.Wrap(err, "unable to setup TON account") + } + + if err := tonRunner.RunE2ETests(tests); err != nil { + return errors.Wrap(err, "ton tests failed") + } tonRunner.Logger.Print("🍾 ton tests completed in %s", time.Since(startTime).String()) diff --git a/e2e/e2etests/test_ton_deposit.go b/e2e/e2etests/test_ton_deposit.go index c2aa0dcbf8..46967c8d88 100644 --- a/e2e/e2etests/test_ton_deposit.go +++ b/e2e/e2etests/test_ton_deposit.go @@ -1,7 +1,26 @@ package e2etests -import "github.com/zeta-chain/node/e2e/runner" +import ( + "cosmossdk.io/math" + "github.com/stretchr/testify/require" + "github.com/tonkeeper/tongo/utils" -func TestTONDeposit(r *runner.E2ERunner, args []string) { - r.Logger.Info("Hello from TestTONDeposit! Args: %v", args) + "github.com/zeta-chain/node/e2e/runner" +) + +func TestTONDeposit(r *runner.E2ERunner, _ []string) { + ctx := r.Ctx + + deployerBalance, err := r.TONDeployer.GetBalance(ctx) + require.NoError(r, err, "failed to get deployer balance") + + r.Logger.Print("TON deployer address %s", r.TONDeployer.Wallet().GetAddress().ToHuman(false, true)) + + require.False(r, deployerBalance.IsZero(), "deployer balance is zero") + + r.Logger.Print("TON deployer balance: %s", prettyPrintTON(deployerBalance)) +} + +func prettyPrintTON(v math.Uint) string { + return utils.HumanFriendlyCoinsRepr(int64(v.Uint64())) } diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index 38106a76b4..98cb2d3066 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -34,6 +34,7 @@ import ( "github.com/zeta-chain/node/e2e/contracts/contextapp" "github.com/zeta-chain/node/e2e/contracts/erc20" "github.com/zeta-chain/node/e2e/contracts/zevmswap" + tonrunner "github.com/zeta-chain/node/e2e/runner/ton" "github.com/zeta-chain/node/e2e/txserver" "github.com/zeta-chain/node/e2e/utils" "github.com/zeta-chain/node/pkg/contracts/testdappv2" @@ -69,6 +70,7 @@ type E2ERunner struct { BTCTSSAddress btcutil.Address BTCDeployerAddress *btcutil.AddressWitnessPubKeyHash SolanaDeployerAddress solana.PublicKey + TONDeployer *tonrunner.Deployer // all clients. // a reference to this type is required to enable creating a new E2ERunner. diff --git a/e2e/runner/setup_ton.go b/e2e/runner/setup_ton.go new file mode 100644 index 0000000000..99cccf0c8d --- /dev/null +++ b/e2e/runner/setup_ton.go @@ -0,0 +1,43 @@ +package runner + +import ( + "fmt" + + "github.com/pkg/errors" + + "github.com/zeta-chain/node/e2e/runner/ton" +) + +// SetupTON setups TON deployer and deploys Gateway contract +func (r *E2ERunner) SetupTON() error { + if r.Clients.TON == nil || r.Clients.TONSidecar == nil { + return fmt.Errorf("TON clients are not initialized") + } + + // 1. Setup Deployer (acts as a faucet as well) + faucetConfig, err := r.Clients.TONSidecar.GetFaucet(r.Ctx) + if err != nil { + return errors.Wrap(err, "unable to get faucet config") + } + + deployer, err := ton.NewDeployer(r.Clients.TON, faucetConfig) + if err != nil { + return errors.Wrap(err, "unable to create TON deployer") + } + + r.TONDeployer = deployer + + gwCode, gwState, err := ton.GetGatewayCodeAndState(r.TSSAddress) + if err != nil { + return errors.Wrap(err, "unable to get TON Gateway code and state") + } + + gw, err := r.TONDeployer.Deploy(r.Ctx, gwCode, gwState) + if err != nil { + return errors.Wrap(err, "unable to deploy TON Gateway") + } + + fmt.Println("TON Gateway deployed", gw) + + return nil +} diff --git a/e2e/runner/ton/deployer.go b/e2e/runner/ton/deployer.go new file mode 100644 index 0000000000..9da828ca4f --- /dev/null +++ b/e2e/runner/ton/deployer.go @@ -0,0 +1,51 @@ +package ton + +import ( + "context" + "fmt" + + "cosmossdk.io/math" + "github.com/pkg/errors" + "github.com/tonkeeper/tongo/boc" + "github.com/tonkeeper/tongo/liteapi" + "github.com/tonkeeper/tongo/wallet" +) + +type Deployer struct { + client *liteapi.Client + wallet *wallet.Wallet +} + +func NewDeployer(client *liteapi.Client, cfg Faucet) (*Deployer, error) { + version := wallet.V3R2 + if cfg.WalletVersion != "V3R2" { + return nil, fmt.Errorf("unsupported wallet version %q", cfg.WalletVersion) + } + + pk, err := wallet.SeedToPrivateKey(cfg.Mnemonic) + if err != nil { + return nil, errors.Wrap(err, "invalid mnemonic") + } + + w, err := wallet.New(pk, version, client, wallet.WithSubWalletID(uint32(cfg.SubWalletId))) + if err != nil { + return nil, errors.Wrap(err, "failed to create wallet") + } + + return &Deployer{client: client, wallet: &w}, nil +} + +func (d *Deployer) Wallet() *wallet.Wallet { + return d.wallet +} + +func (d *Deployer) GetBalance(ctx context.Context) (math.Uint, error) { + b, err := d.wallet.GetBalance(ctx) + + return math.NewUint(b), err +} + +func (d *Deployer) Deploy(ctx context.Context, code, state *boc.Cell) (string, error) { + // todo + return "", nil +} diff --git a/e2e/runner/ton/gateway.compiled.json b/e2e/runner/ton/gateway.compiled.json new file mode 100644 index 0000000000..5177dd0d6c --- /dev/null +++ b/e2e/runner/ton/gateway.compiled.json @@ -0,0 +1,5 @@ +{ + "hash": "a675833643f352d5d40e08efc5f093b6a0e6c246531ff1ba44374a30d8366d18", + "hashBase64": "pnWDNkPzUtXUDgjvxfCTtqDmwkZTH/G6RDdKMNg2bRg=", + "hex": "b5ee9c724102130100039b000114ff00f4a413f4bcf2c80b01020120020b020148030802b8d033d0d303fa40300171b0f2d06420fa4430c000f2e06922d749c160f2d06502d31fd33f593020c064925f04e020c065e302c0668ea0218208989680b9f2d06a20d7498100a0b9f2d067d39f20c702f2d068d430db3ce05f03f2c066040503ec30218208989680b9f2d06a20d7498100a0b9f2d067d39f30db3cdb3c018208989680a1f84221a0f862f8438208989680a0f863db3c708065c8cb1fcb3f5003cf1658fa02cb9fc98d041cd95b9917db1bd9d7db595cdcd859d960fe14307170530073c8cb01f828cf16cb01cb5fcb00cb00ccc970fb000f061204f4db3cdb3c20830d8068218409a904a413f94130315301bc8e2f8d0858d95b1b081cda5e99481a5cc81d1bdbc8189a59ce8816d9dbdd0b081dd85b9d1760fe1430fe2030fe2030f2f0925f03e2028208989680a1f84221a0f862f8438208989680a0f863db3c708066c8cb1fcb3f5004cf165003fa0212cb9fccc90f061207000ef841c000f2d06e005e8d041cd95b9917db1bd9d7db595cdcd859d960fe14307170530073c8cb01f828cf16cb01cb5fcb00cb00ccc970fb00020120090a010dbe64bed9e7c2240f0115bfda36d9e7c20fc217c22c0f04f8f2d31f218100c8ba8ff031db3cdb3cd0fa40fa00d31f3002fa4401c000f2e06921c000f2d06af844a413bdf2d06df80071c8c922103402318d04dcd95b9917dcda5b5c1b1957db595cdcd859d960fe1430708018c8cb05542005745003cb02cb07cbff58fa0212cb6ac901fb00f84201a1f862f844a4f864db3ce0210f10120c04528100c9ba8f9d31db3cdb3cd0d300d31f30f844a4bdf2d06df800f861f844a4f864db3ce0218100caba0f10120d047a8fb531db3cdb3cd0d69fd31f30f844a4bdf2d06df80020f865f844a4f8648bf6e65775f7473735f616464726573738fe1430fe2030db3ce0018100cbba0f10120e033a8f96db3cdb3cd0d4d31f30f844a4bdf2d06df800fb04db3ce030f2c0660f10120038ed44d0d30001f861fa0001f862fa0001f863d31f01f864d69f30f8650170810208d718d3ffd43020f90022bdf2d06bf84513db3c8d0558da1958dad7d958d91cd857dcda59db985d1d5c9960fe1430fe20c3fff2d06c11008e01d3070120c21e92a6e19720c21a92a6e5e0e201d3ffd3ff301034f912c3ff935f0471e002c304935f0372e0c8cbffcbff71f90403c8cbffc9d08100a0d722c705c0009173e07f0030f844f841c8cb00f842fa02f843fa02cb1ff845cf16c9ed54375cf328" +} \ No newline at end of file diff --git a/e2e/runner/ton/gateway.go b/e2e/runner/ton/gateway.go new file mode 100644 index 0000000000..f2eb5019d2 --- /dev/null +++ b/e2e/runner/ton/gateway.go @@ -0,0 +1,89 @@ +package ton + +import ( + _ "embed" + "encoding/json" + + eth "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "github.com/tonkeeper/tongo/boc" +) + +// https://github.com/zeta-chain/protocol-contracts-ton +// `make compile` +// +//go:embed gateway.compiled.json +var tonGatewayCodeJSON []byte + +// GetGatewayCodeAndState returns TON Gateway code and initial state cells. +// Returns (code, state, error). +func GetGatewayCodeAndState(tss eth.Address) (*boc.Cell, *boc.Cell, error) { + code, err := getGatewayCode() + if err != nil { + return nil, nil, errors.Wrap(err, "unable to get TON Gateway code") + } + + state, err := buildGatewayState(tss) + if err != nil { + return nil, nil, errors.Wrap(err, "unable to build TON Gateway state") + } + + return code, state, nil +} + +func getGatewayCode() (*boc.Cell, error) { + var code struct { + Hex string `json:"hex"` + } + + if err := json.Unmarshal(tonGatewayCodeJSON, &code); err != nil { + return nil, errors.Wrap(err, "unable to unmarshal TON Gateway code") + } + + cells, err := boc.DeserializeBocHex(code.Hex) + if err != nil { + return nil, errors.Wrap(err, "unable to deserialize TON Gateway code") + } + + if len(cells) != 1 { + return nil, errors.New("invalid cells count") + } + + return cells[0], nil +} + +// buildGatewayState returns TON Gateway initial state cell +func buildGatewayState(tss eth.Address) (*boc.Cell, error) { + const evmAddressBits = 20 * 8 + + tssSlice := boc.NewBitString(evmAddressBits) + if err := tssSlice.WriteBytes(tss.Bytes()); err != nil { + return nil, errors.Wrap(err, "unable to convert TSS address to ton slice") + } + + cell := boc.NewCell() + + err := errCollect( + cell.WriteBit(true), // deposits_enabled + cell.WriteUint(0, 4), // total_locked varUint + cell.WriteUint(0, 4), // fees + cell.WriteUint(0, 32), // seqno + cell.WriteBitString(tssSlice), // tss_address + ) + + if err != nil { + return nil, errors.Wrap(err, "unable to write TON Gateway state cell") + } + + return cell, nil +} + +func errCollect(errs ...error) error { + for i, err := range errs { + if err != nil { + return errors.Wrapf(err, "error at index %d", i) + } + } + + return nil +} diff --git a/e2e/runner/ton/gateway_test.go b/e2e/runner/ton/gateway_test.go new file mode 100644 index 0000000000..b0cfc6fd5e --- /dev/null +++ b/e2e/runner/ton/gateway_test.go @@ -0,0 +1,52 @@ +package ton + +import ( + "crypto/ecdsa" + "encoding/hex" + "testing" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" +) + +func TestGateway(t *testing.T) { + // ARRANGE + // Given TSS address + const sampleTSSPrivateKey = "0xb984cd65727cfd03081fc7bf33bf5c208bca697ce16139b5ded275887e81395a" + + pkBytes, err := hex.DecodeString(sampleTSSPrivateKey[2:]) + require.NoError(t, err) + + privateKey, err := crypto.ToECDSA(pkBytes) + require.NoError(t, err) + + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + require.True(t, ok) + + tss := crypto.PubkeyToAddress(*publicKeyECDSA) + + t.Run("Build gateway deployment payload", func(t *testing.T) { + // ACT + codeCell, errCode := getGatewayCode() + stateCell, errState := buildGatewayState(tss) + + // ASSERT + require.NoError(t, errCode) + require.NoError(t, errState) + + codeString, err := codeCell.ToBocStringCustom(false, true, false, 0) + require.NoError(t, err) + + stateString, err := stateCell.ToBocStringCustom(false, true, false, 0) + require.NoError(t, err) + + t.Logf("Gateway code: %s", codeString) + t.Logf("Gateway state: %s", stateString) + + // Taken from jest tests in protocol-contracts-ton (using the same TSS address private key) + const expectedState = "b5ee9c7241010101001c0000338000000000124d38a790fdf1d9311fae87d4b21aeffd77bc26c0776433f3" + + require.Equal(t, expectedState, stateString) + }) +} From 50adc5a1618890b9049eb9b12b0a4bd85cffaa00 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Wed, 11 Sep 2024 17:53:36 +0300 Subject: [PATCH 07/17] Move TON localnet to a separate repository --- Makefile | 7 +- contrib/localnet/docker-compose.yml | 18 +-- contrib/localnet/ton/.gitignore | 1 - contrib/localnet/ton/Dockerfile | 34 ------ contrib/localnet/ton/download-jar.sh | 17 --- contrib/localnet/ton/entrypoint.sh | 8 -- contrib/localnet/ton/readme.md | 61 ---------- contrib/localnet/ton/sidecar.go | 169 --------------------------- contrib/localnet/ton/sidecar_test.go | 36 ------ 9 files changed, 10 insertions(+), 341 deletions(-) delete mode 100644 contrib/localnet/ton/.gitignore delete mode 100644 contrib/localnet/ton/Dockerfile delete mode 100755 contrib/localnet/ton/download-jar.sh delete mode 100644 contrib/localnet/ton/entrypoint.sh delete mode 100644 contrib/localnet/ton/readme.md delete mode 100644 contrib/localnet/ton/sidecar.go delete mode 100644 contrib/localnet/ton/sidecar_test.go diff --git a/Makefile b/Makefile index 86ef7aa1b9..1e14ddb95b 100644 --- a/Makefile +++ b/Makefile @@ -245,11 +245,6 @@ solana: @echo "Building solana docker image" $(DOCKER) build -t solana-local -f contrib/localnet/solana/Dockerfile contrib/localnet/solana/ -ton: - @echo "Building ton docker image" - contrib/localnet/ton/download-jar.sh - $(DOCKER) buildx build --platform linux/amd64 -t ton-local -f contrib/localnet/ton/Dockerfile contrib/localnet/ton/ - start-e2e-test: zetanode @echo "--> Starting e2e test" cd contrib/localnet/ && $(DOCKER_COMPOSE) up -d @@ -285,7 +280,7 @@ start-solana-test: zetanode solana export E2E_ARGS="--skip-regular --test-solana" && \ cd contrib/localnet/ && $(DOCKER_COMPOSE) --profile solana -f docker-compose.yml up -d -start-ton-test: zetanode ton +start-ton-test: zetanode @echo "--> Starting TON test" export E2E_ARGS="--skip-regular --test-ton" && \ cd contrib/localnet/ && $(DOCKER_COMPOSE) --profile ton -f docker-compose.yml up -d diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml index 705d7672a2..f6da6dec07 100644 --- a/contrib/localnet/docker-compose.yml +++ b/contrib/localnet/docker-compose.yml @@ -31,7 +31,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.200 - entrypoint: ["zetacored", "rosetta", "--tendermint", "zetacore0:26657", "--grpc", "zetacore0:9090", "--network", "athens_101-1", "--blockchain", "zetacore"] + entrypoint: [ "zetacored", "rosetta", "--tendermint", "zetacore0:26657", "--grpc", "zetacore0:9090", "--network", "athens_101-1", "--blockchain", "zetacore" ] volumes: - ssh:/root/.ssh @@ -48,7 +48,7 @@ services: - "9090:9090" healthcheck: # use the zevm endpoint for the healthcheck as it is the slowest to come up - test: ["CMD", "curl", "-f", "-X", "POST", "--data", '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":67}', "-H", "Content-Type: application/json", "http://localhost:8545"] + test: [ "CMD", "curl", "-f", "-X", "POST", "--data", '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":67}', "-H", "Content-Type: application/json", "http://localhost:8545" ] interval: 30s timeout: 10s retries: 3 @@ -57,7 +57,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.11 - entrypoint: ["/root/start-zetacored.sh"] + entrypoint: [ "/root/start-zetacored.sh" ] environment: - HOTKEY_BACKEND=file - HOTKEY_PASSWORD=password # test purposes only @@ -73,7 +73,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.12 - entrypoint: ["/root/start-zetacored.sh"] + entrypoint: [ "/root/start-zetacored.sh" ] environment: - HOTKEY_BACKEND=file - HOTKEY_PASSWORD=password # test purposes only @@ -193,7 +193,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.100 - entrypoint: ["geth", "--dev", "--http", "--http.addr", "172.20.0.100", "--http.vhosts", "*", "--http.api", "eth,web3,net", "--http.corsdomain", "https://remix.ethereum.org", "--dev.period", "2"] + entrypoint: [ "geth", "--dev", "--http", "--http.addr", "172.20.0.100", "--http.vhosts", "*", "--http.api", "eth,web3,net", "--http.corsdomain", "https://remix.ethereum.org", "--dev.period", "2" ] eth2: build: @@ -242,7 +242,7 @@ services: entrypoint: [ "/usr/bin/start-solana.sh" ] ton: - image: ton-local:latest + image: ghcr.io/zeta-chain/ton-docker:latest container_name: ton hostname: ton profiles: @@ -270,7 +270,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.2 - entrypoint: ["/work/start-zetae2e.sh", "local"] + entrypoint: [ "/work/start-zetae2e.sh", "local" ] environment: - LOCALNET_MODE=${LOCALNET_MODE-} - E2E_ARGS=${E2E_ARGS-} @@ -285,7 +285,7 @@ services: profiles: - upgrade - all - entrypoint: ["/root/start-upgrade-host.sh"] + entrypoint: [ "/root/start-upgrade-host.sh" ] networks: mynetwork: ipv4_address: 172.20.0.250 @@ -300,7 +300,7 @@ services: profiles: - upgrade - all - entrypoint: ["/root/start-upgrade-orchestrator.sh"] + entrypoint: [ "/root/start-upgrade-orchestrator.sh" ] networks: mynetwork: ipv4_address: 172.20.0.251 diff --git a/contrib/localnet/ton/.gitignore b/contrib/localnet/ton/.gitignore deleted file mode 100644 index d392f0e82c..0000000000 --- a/contrib/localnet/ton/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.jar diff --git a/contrib/localnet/ton/Dockerfile b/contrib/localnet/ton/Dockerfile deleted file mode 100644 index 25a376ea3d..0000000000 --- a/contrib/localnet/ton/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -# Let's build a sidecar -FROM golang:1.20 AS go-builder -WORKDIR /opt/sidecar -COPY ./sidecar.go . -RUN go build -o /opt/sidecar/sidecar sidecar.go - -FROM openjdk:24-slim AS ton-node - -ARG WORKDIR="/opt/my-local-ton" - -# Install dependencies && drop apt cache -RUN apt-get update \ - && apt-get install -y --no-install-recommends \ - curl jq vim lsb-release \ - && rm -rf /var/lib/apt/lists/* - -COPY ./my-local-ton.jar $WORKDIR/ -COPY ./entrypoint.sh $WORKDIR/ - -COPY --from=go-builder /opt/sidecar $WORKDIR/ - -WORKDIR $WORKDIR - -# Ensure whether the build works or not. -RUN chmod +x entrypoint.sh && chmod +x sidecar \ - && java -jar my-local-ton.jar debug nogui test-binaries; - -# Lite Client -EXPOSE 4443 - -# Sidecar -EXPOSE 8000 - -ENTRYPOINT ["/opt/my-local-ton/entrypoint.sh"] diff --git a/contrib/localnet/ton/download-jar.sh b/contrib/localnet/ton/download-jar.sh deleted file mode 100755 index 762ff3fd06..0000000000 --- a/contrib/localnet/ton/download-jar.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -script_dir=$(cd -- "$(dirname -- "$0")" &> /dev/null && pwd) - -# Downloads JAR outside of the Dockerfile to avoid re-downloading it every time during rebuilds. -jar_version="v120" -jar_url="https://github.com/neodix42/MyLocalTon/releases/download/${jar_version}/MyLocalTon-x86-64.jar" -jar_file="$script_dir/my-local-ton.jar" - -if [ -f "$jar_file" ]; then - echo "File $jar_file already exists. Skipping download." - exit 0 -fi - -echo "File not found. Downloading..." -echo "URL: $jar_url" -wget -q --show-progress -O "$jar_file" "$jar_url" diff --git a/contrib/localnet/ton/entrypoint.sh b/contrib/localnet/ton/entrypoint.sh deleted file mode 100644 index 8a50339953..0000000000 --- a/contrib/localnet/ton/entrypoint.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -java -jar my-local-ton.jar with-validators-1 nogui debug & - -./sidecar & - -# Wait for both processes to finish -wait -n diff --git a/contrib/localnet/ton/readme.md b/contrib/localnet/ton/readme.md deleted file mode 100644 index f5eb0d78d7..0000000000 --- a/contrib/localnet/ton/readme.md +++ /dev/null @@ -1,61 +0,0 @@ -# TON localnet - -This docker image represents a fully working TON node with 1 validator and a faucet wallet. - -## How it works - -- It uses [my-local-ton](https://github.com/neodix42/MyLocalTon) project without GUI. - Port `4443` is used for [lite-client connection](https://docs.ton.org/participate/run-nodes/enable-liteserver-node). -- It also has a convenient sidecar on port `8000` with some useful tools. -- Please note that it might take **several minutes** to bootstrap the network. - -## Sidecar - -### Getting faucet wallet - -```shell -curl -s http://ton:8000/faucet.json | jq -{ - "initialBalance": 1000001000000000, - "privateKey": "...", - "publicKey": "...", - "walletRawAddress": "...", - "mnemonic": "...", - "walletVersion": "V3R2", - "workChain": 0, - "subWalletId": 42, - "created": false -} -``` - -### Getting lite client config - -Please note that the config returns IP of localhost (`int 2130706433`). -You need to replace it with the actual IP of the docker container. - -```shell -curl -s http://ton:8000/lite-client.json | jq -{ - "@type": "config.global", - "dht": { ... }, - "liteservers": [ - { - "id": { "key": "...", "@type": "pub.ed25519" }, - "port": 4443, - "ip": 2130706433 - } - ], - "validator": { ... } -} -``` - -### Checking the node status - -It checks for config existence and the fact of faucet wallet deployment - -```shell -curl -s http://ton:8000/status | jq -{ - "status": "OK" -} -``` \ No newline at end of file diff --git a/contrib/localnet/ton/sidecar.go b/contrib/localnet/ton/sidecar.go deleted file mode 100644 index eafe44d301..0000000000 --- a/contrib/localnet/ton/sidecar.go +++ /dev/null @@ -1,169 +0,0 @@ -package main - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "errors" - "fmt" - "log" - "net" - "net/http" - "os" -) - -const ( - port = ":8000" - - basePath = "/opt/my-local-ton/myLocalTon" - liteClientConfigPath = basePath + "/genesis/db/my-ton-local.config.json" - settingsPath = basePath + "/settings.json" - - faucetJSONKey = "faucetWalletSettings" -) - -func main() { - http.HandleFunc("/faucet.json", errorWrapper(faucetHandler)) - http.HandleFunc("/lite-client.json", errorWrapper(liteClientHandler)) - http.HandleFunc("/status", errorWrapper(statusHandler)) - - //nolint:gosec - if err := http.ListenAndServe(port, nil); err != nil { - log.Fatal(err) - } -} - -func errorWrapper(handler func(w http.ResponseWriter, r *http.Request) error) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if err := handler(w, r); err != nil { - errResponse(w, http.StatusInternalServerError, err) - } - } -} - -// Handler for the /faucet.json route -func faucetHandler(w http.ResponseWriter, _ *http.Request) error { - faucet, err := extractFaucetFromSettings(settingsPath) - if err != nil { - return err - } - - jsonResponse(w, http.StatusOK, faucet) - return nil -} - -// liteClientHandler returns lite json client config -// and alters localhost to docker IP if needed. -func liteClientHandler(w http.ResponseWriter, _ *http.Request) error { - data, err := os.ReadFile(liteClientConfigPath) - if err != nil { - return fmt.Errorf("could not read lite client config: %w", err) - } - - dockerIP := os.Getenv("DOCKER_IP") - - if dockerIP != "" { - altered, err := alterConfigIP(data, dockerIP) - if err != nil { - errResponse(w, http.StatusInternalServerError, err) - return nil - } - - data = altered - } - - jsonResponse(w, http.StatusOK, json.RawMessage(data)) - return nil -} - -// Handler for the /status route -func statusHandler(w http.ResponseWriter, _ *http.Request) error { - if _, err := os.Stat(liteClientConfigPath); err != nil { - return fmt.Errorf("lite client config %q not found: %w", liteClientConfigPath, err) - } - - faucet, err := extractFaucetFromSettings(settingsPath) - if err != nil { - return err - } - - type faucetShape struct { - Created bool `json:"created"` - } - - var fs faucetShape - if err = json.Unmarshal(faucet, &fs); err != nil { - return fmt.Errorf("failed to parse faucet settings: %w", err) - } - - if !fs.Created { - return errors.New("faucet is not created yet") - } - - jsonResponse(w, http.StatusOK, map[string]string{"status": "OK"}) - return nil -} - -func extractFaucetFromSettings(filePath string) (json.RawMessage, error) { - data, err := os.ReadFile(filePath) - if err != nil { - return nil, fmt.Errorf("could not read faucet settings: %w", err) - } - - var keyValue map[string]json.RawMessage - if err := json.Unmarshal(data, &keyValue); err != nil { - return nil, fmt.Errorf("failed to parse faucet settings: %w", err) - } - - faucet, ok := keyValue[faucetJSONKey] - if !ok { - return nil, errors.New("faucet settings not found in JSON") - } - - return faucet, nil -} - -func errResponse(w http.ResponseWriter, status int, err error) { - jsonResponse(w, status, map[string]string{"error": err.Error()}) -} - -func jsonResponse(w http.ResponseWriter, status int, data any) { - b, err := json.Marshal(data) - if err != nil { - b = []byte("Failed to marshal JSON") - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - - //nolint:errcheck - w.Write(b) -} - -// TON's lite client config contains the IP of the node. -// And it's localhost, we need to change it to the docker IP. -func alterConfigIP(config []byte, ipString string) ([]byte, error) { - const localhost = uint32(2130706433) - - ip := net.ParseIP(ipString) - if ip == nil { - return nil, fmt.Errorf("failed to parse IP: %q", ipString) - } - - return bytes.ReplaceAll( - config, - uint32ToBytes(localhost), - uint32ToBytes(ip2int(ip)), - ), nil -} - -func ip2int(ip net.IP) uint32 { - if len(ip) == 16 { - return binary.BigEndian.Uint32(ip[12:16]) - } - return binary.BigEndian.Uint32(ip) -} - -func uint32ToBytes(n uint32) []byte { - return []byte(fmt.Sprintf("%d", n)) -} diff --git a/contrib/localnet/ton/sidecar_test.go b/contrib/localnet/ton/sidecar_test.go deleted file mode 100644 index 0a5e28be53..0000000000 --- a/contrib/localnet/ton/sidecar_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "strings" - "testing" -) - -func TestIPAlteration(t *testing.T) { - // ARRANGE - const data = `{ - "@type": "config.global", - "dht": { ... }, - "liteservers":[ {"id":{ ... }, "port": 4443, "ip": 2130706433} ], - "validator": { ... } - }` - - // https://www.browserling.com/tools/ip-to-dec - const ( - ip = "142.250.186.78" - expected = "2398796366" - ) - - // ACT - out, err := alterConfigIP([]byte(data), ip) - - // ASSERT - if err != nil { - t.Errorf("Failed: %s", err) - } - - t.Logf("Output: %s", out) - - if !strings.Contains(string(out), expected) { - t.Errorf("Unexpected outcome") - } -} From a4827b325c2960d48c2fdf02b78d5186ef29a4b6 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:16:28 +0300 Subject: [PATCH 08/17] E2E demo --- cmd/zetae2e/config/clients.go | 38 +-- cmd/zetae2e/local/local.go | 5 - cmd/zetae2e/local/ton.go | 6 - .../orchestrator/Dockerfile.fastbuild | 1 + .../localnet/orchestrator/start-zetae2e.sh | 5 + contrib/localnet/scripts/wait-for-ton.sh | 36 +++ e2e/e2etests/test_ton_deposit.go | 34 ++- e2e/runner/clients.go | 12 +- e2e/runner/runner.go | 2 + e2e/runner/setup_ton.go | 34 ++- e2e/runner/ton.go | 34 --- e2e/runner/ton/accounts.go | 220 ++++++++++++++++++ .../ton/{gateway_test.go => accounts_test.go} | 16 +- .../ton/{sidecarclient.go => clients.go} | 49 +++- e2e/runner/ton/coin.go | 26 +++ e2e/runner/ton/deployer.go | 165 +++++++++++-- e2e/runner/ton/gateway.go | 89 ------- zetaclient/chains/ton/config.go | 4 +- 18 files changed, 575 insertions(+), 201 deletions(-) create mode 100755 contrib/localnet/scripts/wait-for-ton.sh delete mode 100644 e2e/runner/ton.go create mode 100644 e2e/runner/ton/accounts.go rename e2e/runner/ton/{gateway_test.go => accounts_test.go} (80%) rename e2e/runner/ton/{sidecarclient.go => clients.go} (80%) create mode 100644 e2e/runner/ton/coin.go delete mode 100644 e2e/runner/ton/gateway.go diff --git a/cmd/zetae2e/config/clients.go b/cmd/zetae2e/config/clients.go index b6f746c814..2eb51a86e0 100644 --- a/cmd/zetae2e/config/clients.go +++ b/cmd/zetae2e/config/clients.go @@ -15,6 +15,7 @@ import ( "github.com/zeta-chain/node/e2e/config" "github.com/zeta-chain/node/e2e/runner" tonrunner "github.com/zeta-chain/node/e2e/runner/ton" + "github.com/zeta-chain/node/pkg/retry" zetacore_rpc "github.com/zeta-chain/node/pkg/rpc" tonconfig "github.com/zeta-chain/node/zetaclient/chains/ton" ) @@ -38,20 +39,16 @@ func getClientsFromConfig(ctx context.Context, conf config.Config, account confi } } - var ( - tonClient *ton.Client - tonSidecarClient *tonrunner.SidecarClient - ) - + var tonClient *tonrunner.Client if conf.RPCs.TONSidecarURL != "" { - tonSidecarClient = tonrunner.NewSidecarClient(conf.RPCs.TONSidecarURL) + sidecar := tonrunner.NewSidecarClient(conf.RPCs.TONSidecarURL) - c, err := getTONClient(ctx, tonSidecarClient.LiteServerURL()) + client, err := getTONClient(ctx, sidecar.LiteServerURL()) if err != nil { return runner.Clients{}, fmt.Errorf("failed to get ton client: %w", err) } - tonClient = c + tonClient = &tonrunner.Client{Client: client, SidecarClient: sidecar} } zetaCoreClients, err := GetZetacoreClient(conf) @@ -65,15 +62,14 @@ func getClientsFromConfig(ctx context.Context, conf config.Config, account confi } return runner.Clients{ - Zetacore: zetaCoreClients, - BtcRPC: btcRPCClient, - Solana: solanaClient, - TON: tonClient, - TONSidecar: tonSidecarClient, - Evm: evmClient, - EvmAuth: evmAuth, - Zevm: zevmClient, - ZevmAuth: zevmAuth, + Zetacore: zetaCoreClients, + BtcRPC: btcRPCClient, + Solana: solanaClient, + TON: tonClient, + Evm: evmClient, + EvmAuth: evmAuth, + Zevm: zevmClient, + ZevmAuth: zevmAuth, }, nil } @@ -129,7 +125,13 @@ func getEVMClient( } func getTONClient(ctx context.Context, configURL string) (*ton.Client, error) { - cfg, err := tonconfig.ConfigFromURL(ctx, configURL) + // It might take some time to bootstrap the sidecar + cfg, err := retry.DoTypedWithRetry( + func() (*tonconfig.GlobalConfigurationFile, error) { + return tonconfig.ConfigFromURL(ctx, configURL) + }, + ) + if err != nil { return nil, fmt.Errorf("failed to get ton config: %w", err) } diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 9d154e5e78..1893ae55ac 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -383,11 +383,6 @@ func localE2ETest(cmd *cobra.Command, _ []string) { os.Exit(1) } - if deployerRunner.Clients.TONSidecar == nil { - logger.Print("❌ TON sidecar is nil") - os.Exit(1) - } - tonTests := []string{ e2etests.TestTONDepositName, } diff --git a/cmd/zetae2e/local/ton.go b/cmd/zetae2e/local/ton.go index 14721e3b7b..f7491fcaca 100644 --- a/cmd/zetae2e/local/ton.go +++ b/cmd/zetae2e/local/ton.go @@ -38,12 +38,6 @@ func tonTestRoutine( return errors.Wrap(err, "unable to get ton tests to run") } - const bootstrapTimeout = 5 * time.Minute - - if err := tonRunner.EnsureTONBootstrapped(bootstrapTimeout); err != nil { - return errors.Wrap(err, "unable to bootstrap TON") - } - if err := tonRunner.SetupTON(); err != nil { return errors.Wrap(err, "unable to setup TON account") } diff --git a/contrib/localnet/orchestrator/Dockerfile.fastbuild b/contrib/localnet/orchestrator/Dockerfile.fastbuild index d260d4a1f0..c776d96c06 100644 --- a/contrib/localnet/orchestrator/Dockerfile.fastbuild +++ b/contrib/localnet/orchestrator/Dockerfile.fastbuild @@ -14,6 +14,7 @@ COPY --from=solana /usr/bin/solana /usr/local/bin/ COPY --from=zeta /usr/local/bin/zetacored /usr/local/bin/zetaclientd /usr/local/bin/zetae2e /usr/local/bin/ COPY contrib/localnet/orchestrator/start-zetae2e.sh /work/ +COPY contrib/localnet/scripts/wait-for-ton.sh /work/ COPY cmd/zetae2e/config/localnet.yml /work/config.yml RUN chmod +x /work/*.sh diff --git a/contrib/localnet/orchestrator/start-zetae2e.sh b/contrib/localnet/orchestrator/start-zetae2e.sh index 0e3eda7e54..4a8323c85d 100644 --- a/contrib/localnet/orchestrator/start-zetae2e.sh +++ b/contrib/localnet/orchestrator/start-zetae2e.sh @@ -142,6 +142,11 @@ if host solana > /dev/null; then solana airdrop 100 "$relayer" > /dev/null fi +# Wait for TON node to bootstrap +if host ton > /dev/null; then + ./wait-for-ton.sh +fi + ### Run zetae2e command depending on the option passed # Mode migrate is used to run the e2e tests before and after the TSS migration diff --git a/contrib/localnet/scripts/wait-for-ton.sh b/contrib/localnet/scripts/wait-for-ton.sh new file mode 100755 index 0000000000..411572e3df --- /dev/null +++ b/contrib/localnet/scripts/wait-for-ton.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +timeout_seconds=300 # 5 minutes +poll_interval=10 # Check every 10 seconds + +status_url="http://ton:8000/status" + +check_status() { + response=$(curl -s -w "\n%{http_code}" $status_url) + body=$(echo "$response" | head -n 1) + http_status=$(echo "$response" | tail -n 1) + + if [ "$http_status" == "200" ]; then + echo "Pass: $body" + return 0 + else + echo "Waiting: $body" + return 1 + fi +} + +echo "πŸ’Ž Checking TON status at $status_url (timeout: $timeout_seconds seconds)" + +elapsed=0 +while [ $elapsed -lt $timeout_seconds ]; do + if check_status; then + echo "πŸ’Ž TON node bootstrapped" + exit 0 + fi + + sleep $poll_interval + elapsed=$((elapsed + poll_interval)) +done + +echo "πŸ’Ž TON CHECK FAIL. Timeout reached ($timeout_seconds seconds)" +exit 1 \ No newline at end of file diff --git a/e2e/e2etests/test_ton_deposit.go b/e2e/e2etests/test_ton_deposit.go index 46967c8d88..a2e8df09d0 100644 --- a/e2e/e2etests/test_ton_deposit.go +++ b/e2e/e2etests/test_ton_deposit.go @@ -3,24 +3,40 @@ package e2etests import ( "cosmossdk.io/math" "github.com/stretchr/testify/require" - "github.com/tonkeeper/tongo/utils" "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/runner/ton" ) +// TestTONDeposit (!) This boilerplate is a demonstration of E2E capabilities for TON integration +// Actual Deposit test is not implemented yet. func TestTONDeposit(r *runner.E2ERunner, _ []string) { - ctx := r.Ctx + ctx, deployer := r.Ctx, r.TONDeployer - deployerBalance, err := r.TONDeployer.GetBalance(ctx) + // Given deployer + deployerBalance, err := deployer.GetBalance(ctx) require.NoError(r, err, "failed to get deployer balance") + require.NotZero(r, deployerBalance, "deployer balance is zero") - r.Logger.Print("TON deployer address %s", r.TONDeployer.Wallet().GetAddress().ToHuman(false, true)) + // Given sample wallet with a balance of 50 TON + sender, err := deployer.CreateWallet(ctx, ton.TONCoins(50)) + require.NoError(r, err) - require.False(r, deployerBalance.IsZero(), "deployer balance is zero") + // That was funded (again) but the faucet + _, err = deployer.Fund(ctx, sender.GetAddress(), ton.TONCoins(30)) + require.NoError(r, err) - r.Logger.Print("TON deployer balance: %s", prettyPrintTON(deployerBalance)) -} + // Check sender balance + sb, err := sender.GetBalance(ctx) + require.NoError(r, err) + + senderBalance := math.NewUint(sb) -func prettyPrintTON(v math.Uint) string { - return utils.HumanFriendlyCoinsRepr(int64(v.Uint64())) + // note that it's not exactly 80 TON, but 79.99... due to gas fees + // We'll tackle gas math later. + r.Logger.Print( + "Balance of sender (%s): %s", + sender.GetAddress().ToHuman(false, true), + ton.FormatCoins(senderBalance), + ) } diff --git a/e2e/runner/clients.go b/e2e/runner/clients.go index 9e80eb1c07..2a67d43ffa 100644 --- a/e2e/runner/clients.go +++ b/e2e/runner/clients.go @@ -5,7 +5,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/ethclient" "github.com/gagliardetto/solana-go/rpc" - ton "github.com/tonkeeper/tongo/liteapi" tonrunner "github.com/zeta-chain/node/e2e/runner/ton" zetacore_rpc "github.com/zeta-chain/node/pkg/rpc" @@ -16,12 +15,11 @@ type Clients struct { Zetacore zetacore_rpc.Clients // the RPC clients for external chains in the localnet - BtcRPC *rpcclient.Client - Solana *rpc.Client - Evm *ethclient.Client - EvmAuth *bind.TransactOpts - TON *ton.Client - TONSidecar *tonrunner.SidecarClient + BtcRPC *rpcclient.Client + Solana *rpc.Client + Evm *ethclient.Client + EvmAuth *bind.TransactOpts + TON *tonrunner.Client // the RPC clients for ZetaChain Zevm *ethclient.Client diff --git a/e2e/runner/runner.go b/e2e/runner/runner.go index 98cb2d3066..29a10c6a4f 100644 --- a/e2e/runner/runner.go +++ b/e2e/runner/runner.go @@ -17,6 +17,7 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/gagliardetto/solana-go" "github.com/gagliardetto/solana-go/rpc" + "github.com/tonkeeper/tongo/ton" "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/erc20custody.sol" zetaeth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zeta.eth.sol" zetaconnectoreth "github.com/zeta-chain/protocol-contracts/v1/pkg/contracts/evm/zetaconnector.eth.sol" @@ -71,6 +72,7 @@ type E2ERunner struct { BTCDeployerAddress *btcutil.AddressWitnessPubKeyHash SolanaDeployerAddress solana.PublicKey TONDeployer *tonrunner.Deployer + TONGateway ton.AccountID // all clients. // a reference to this type is required to enable creating a new E2ERunner. diff --git a/e2e/runner/setup_ton.go b/e2e/runner/setup_ton.go index 99cccf0c8d..9b744401ad 100644 --- a/e2e/runner/setup_ton.go +++ b/e2e/runner/setup_ton.go @@ -10,12 +10,14 @@ import ( // SetupTON setups TON deployer and deploys Gateway contract func (r *E2ERunner) SetupTON() error { - if r.Clients.TON == nil || r.Clients.TONSidecar == nil { + if r.Clients.TON == nil { return fmt.Errorf("TON clients are not initialized") } + ctx := r.Ctx + // 1. Setup Deployer (acts as a faucet as well) - faucetConfig, err := r.Clients.TONSidecar.GetFaucet(r.Ctx) + faucetConfig, err := r.Clients.TON.GetFaucet(ctx) if err != nil { return errors.Wrap(err, "unable to get faucet config") } @@ -25,19 +27,35 @@ func (r *E2ERunner) SetupTON() error { return errors.Wrap(err, "unable to create TON deployer") } - r.TONDeployer = deployer + depAddr := deployer.GetAddress() + r.Logger.Print("πŸ’ŽTON Deployer %s (%s)", depAddr.ToRaw(), depAddr.ToHuman(false, true)) - gwCode, gwState, err := ton.GetGatewayCodeAndState(r.TSSAddress) + gwAccount, err := ton.ConstructGatewayAccount(r.TSSAddress) if err != nil { - return errors.Wrap(err, "unable to get TON Gateway code and state") + return errors.Wrap(err, "unable to initialize TON gateway") + } + + // 2. Deploy Gateway + initStateAmount := ton.TONCoins(10) + + if err := deployer.Deploy(ctx, gwAccount, initStateAmount); err != nil { + return errors.Wrapf(err, "unable to deploy TON gateway") } - gw, err := r.TONDeployer.Deploy(r.Ctx, gwCode, gwState) + r.Logger.Print("πŸ’ŽTON Gateway deployed %s (%s)", gwAccount.ID.ToRaw(), gwAccount.ID.ToHuman(false, true)) + + // 3. Check that the gateway indeed was deployed and has desired TON balance. + gwBalance, err := deployer.GetBalanceOf(ctx, gwAccount.ID) if err != nil { - return errors.Wrap(err, "unable to deploy TON Gateway") + return errors.Wrap(err, "unable to get balance of TON gateway") + } + + if gwBalance.IsZero() { + return fmt.Errorf("TON gateway balance is zero") } - fmt.Println("TON Gateway deployed", gw) + r.TONDeployer = deployer + r.TONGateway = gwAccount.ID return nil } diff --git a/e2e/runner/ton.go b/e2e/runner/ton.go deleted file mode 100644 index dc850cac53..0000000000 --- a/e2e/runner/ton.go +++ /dev/null @@ -1,34 +0,0 @@ -package runner - -import ( - "context" - "time" - - "github.com/pkg/errors" - - "github.com/zeta-chain/node/e2e/runner/ton" -) - -// EnsureTONBootstrapped waits unless TON node is bootstrapped. -// - Node should be operational -// - Lite server config exists -// - Faucet is deployed -func (r *E2ERunner) EnsureTONBootstrapped(timeout time.Duration) error { - ctx, cancel := context.WithTimeout(r.Ctx, timeout) - defer cancel() - - for { - err := r.Clients.TONSidecar.Status(ctx) - switch { - case errors.Is(err, context.DeadlineExceeded): - return errors.Wrap(err, "timeout waiting for TON to bootstrap") - case errors.Is(err, ton.ErrNotHealthy): - // okay, continue - case err == nil: - return nil - } - - r.Logger.Info("Waiting for TON to bootstrap...") - time.Sleep(2 * time.Second) - } -} diff --git a/e2e/runner/ton/accounts.go b/e2e/runner/ton/accounts.go new file mode 100644 index 0000000000..4e62e39edc --- /dev/null +++ b/e2e/runner/ton/accounts.go @@ -0,0 +1,220 @@ +package ton + +import ( + _ "embed" + "encoding/json" + "fmt" + + eth "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" + "github.com/tonkeeper/tongo/boc" + "github.com/tonkeeper/tongo/tlb" + "github.com/tonkeeper/tongo/ton" + "github.com/tonkeeper/tongo/wallet" + "golang.org/x/crypto/ed25519" +) + +const workchainID = 0 + +// https://github.com/zeta-chain/protocol-contracts-ton +// `make compile` +// +//go:embed gateway.compiled.json +var tonGatewayCodeJSON []byte + +type AccountInit struct { + Code *boc.Cell + Data *boc.Cell + StateInit *tlb.StateInit + ID ton.AccountID +} + +// ConstructWalletFromSeed constructs wallet AccountInit from seed. +// Used for wallets deployment. +func ConstructWalletFromSeed(seed string, client blockchain) (*AccountInit, *wallet.Wallet, error) { + pk, err := wallet.SeedToPrivateKey(seed) + if err != nil { + return nil, nil, errors.Wrap(err, "invalid mnemonic") + } + + return ConstructWalletFromPrivateKey(pk, client) +} + +func ConstructWalletFromPrivateKey(pk ed25519.PrivateKey, client blockchain) (*AccountInit, *wallet.Wallet, error) { + const version = wallet.V5R1 + + w, err := wallet.New(pk, version, client) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to create wallet") + } + + stateInit, err := w.StateInit() + if err != nil { + return nil, nil, errors.Wrap(err, "failed to get state init for wallet") + } + + var ( + code = stateInit.Code.Value.Value + data = stateInit.Data.Value.Value + ) + + accInit, err := ConstructAccount(&code, &data) + if err != nil { + return nil, nil, errors.Wrap(err, "failed to construct account") + } + + // // todo addresses don't match! Why????? + // // todo probably state init doesn't match.... + // // ACC INIT ADDR 0:da797e6dcdfcbd3e05a35dd45a240287668890bb2981005ac0775b840eeb62af (EXISTS) + // // WALLET ADDR 0:e071859fa4a95e41249e721f87717278ca7e68dcce74aace483c695284eb7284 (DOESN'T EXIST) + // + // // todo + // fmt.Println("ACC INIT ADDR", accInit.ID.ToRaw()) + // fmt.Println("WALLET ADDR", w.GetAddress().ToRaw()) + // + // // Double-check the balance + // b, err := w.GetBalance(ctx) + // if err != nil { + // return nil, errors.Wrap(err, "failed to get balance") + // } + // + // balanceCoins := math.NewUint(b) + // if !amount.Equal(balanceCoins) { + // return nil, fmt.Errorf( + // "unexpected balance for %s. Got %s, want %s", + // w.GetAddress().ToRaw(), + // FormatCoins(balanceCoins), + // FormatCoins(amount)) + // } + + // If state init and internal tongo's wallet.stateInit are the same, + // then addresses should match. + if accInit.ID.String() != w.GetAddress().String() { + return nil, nil, errors.New("account init doesn't match to created wallet") + } + + return accInit, &w, nil +} + +func ConstructAccount(code, data *boc.Cell) (*AccountInit, error) { + stateInit := generateStateInit(code, data) + + id, err := generateAddress(workchainID, stateInit) + if err != nil { + return nil, errors.Wrap(err, "unable to generate address") + } + + return &AccountInit{ + Code: code, + Data: data, + StateInit: stateInit, + ID: id, + }, nil +} + +func ConstructGatewayAccount(tss eth.Address) (*AccountInit, error) { + code, err := getGatewayCode() + if err != nil { + return nil, errors.Wrap(err, "unable to get TON Gateway code") + } + + data, err := buildGatewayData(tss) + if err != nil { + return nil, errors.Wrap(err, "unable to build TON Gateway data") + } + + return ConstructAccount(code, data) +} + +func getGatewayCode() (*boc.Cell, error) { + var code struct { + Hex string `json:"hex"` + } + + if err := json.Unmarshal(tonGatewayCodeJSON, &code); err != nil { + return nil, errors.Wrap(err, "unable to unmarshal TON Gateway code") + } + + cells, err := boc.DeserializeBocHex(code.Hex) + if err != nil { + return nil, errors.Wrap(err, "unable to deserialize TON Gateway code") + } + + if len(cells) != 1 { + return nil, errors.New("invalid cells count") + } + + return cells[0], nil +} + +// buildGatewayState returns TON Gateway initial state data cell +func buildGatewayData(tss eth.Address) (*boc.Cell, error) { + const evmAddressBits = 20 * 8 + + tssSlice := boc.NewBitString(evmAddressBits) + if err := tssSlice.WriteBytes(tss.Bytes()); err != nil { + return nil, errors.Wrap(err, "unable to convert TSS address to ton slice") + } + + var ( + zeroCoins = tlb.Coins(0) + enc = &tlb.Encoder{} + cell = boc.NewCell() + ) + + err := errCollect( + cell.WriteBit(true), // deposits_enabled + zeroCoins.MarshalTLB(cell, enc), // total_locked + zeroCoins.MarshalTLB(cell, enc), // fees + cell.WriteUint(0, 32), // seqno + cell.WriteBitString(tssSlice), // tss_address + ) + + if err != nil { + return nil, errors.Wrap(err, "unable to write TON Gateway state cell") + } + + return cell, nil +} + +func errCollect(errs ...error) error { + for i, err := range errs { + if err != nil { + return errors.Wrapf(err, "error at index %d", i) + } + } + + return nil +} + +// copied from tongo wallets_common.go +func generateStateInit(code, data *boc.Cell) *tlb.StateInit { + return &tlb.StateInit{ + Code: tlb.Maybe[tlb.Ref[boc.Cell]]{ + Exists: true, + Value: tlb.Ref[boc.Cell]{Value: *code}, + }, + Data: tlb.Maybe[tlb.Ref[boc.Cell]]{ + Exists: true, + Value: tlb.Ref[boc.Cell]{Value: *data}, + }, + } +} + +// copied from tongo wallets_common.go +func generateAddress(workchain int32, stateInit *tlb.StateInit) (ton.AccountID, error) { + stateCell := boc.NewCell() + if err := tlb.Marshal(stateCell, stateInit); err != nil { + return ton.AccountID{}, fmt.Errorf("can not marshal wallet state: %v", err) + } + + h, err := stateCell.Hash() + if err != nil { + return ton.AccountID{}, err + } + + var hash tlb.Bits256 + copy(hash[:], h[:]) + + return ton.AccountID{Workchain: workchain, Address: hash}, nil +} diff --git a/e2e/runner/ton/gateway_test.go b/e2e/runner/ton/accounts_test.go similarity index 80% rename from e2e/runner/ton/gateway_test.go rename to e2e/runner/ton/accounts_test.go index b0cfc6fd5e..50ec07ee79 100644 --- a/e2e/runner/ton/gateway_test.go +++ b/e2e/runner/ton/accounts_test.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" + "github.com/tonkeeper/tongo/wallet" ) func TestGateway(t *testing.T) { @@ -29,7 +30,7 @@ func TestGateway(t *testing.T) { t.Run("Build gateway deployment payload", func(t *testing.T) { // ACT codeCell, errCode := getGatewayCode() - stateCell, errState := buildGatewayState(tss) + stateCell, errState := buildGatewayData(tss) // ASSERT require.NoError(t, errCode) @@ -50,3 +51,16 @@ func TestGateway(t *testing.T) { require.Equal(t, expectedState, stateString) }) } + +func TestWalletConstruction(t *testing.T) { + // ARRANGE + seed := wallet.RandomSeed() + + // ACT + accInit, w, err := ConstructWalletFromSeed(seed, nil) + + // ASSERT + require.NoError(t, err) + require.NotNil(t, accInit) + require.NotNil(t, w) +} diff --git a/e2e/runner/ton/sidecarclient.go b/e2e/runner/ton/clients.go similarity index 80% rename from e2e/runner/ton/sidecarclient.go rename to e2e/runner/ton/clients.go index dafd26317f..d24e0a492e 100644 --- a/e2e/runner/ton/sidecarclient.go +++ b/e2e/runner/ton/clients.go @@ -9,13 +9,53 @@ import ( "time" "github.com/pkg/errors" + ton "github.com/tonkeeper/tongo/liteapi" ) +type Client struct { + *ton.Client + *SidecarClient +} + +func (c *Client) WaitForBlocks(ctx context.Context) error { + const ( + blocksToWait = 3 + interval = 3 * time.Second + ) + + block, err := c.GetMasterchainInfo(ctx) + if err != nil { + return err + } + + waitFor := block.Last.Seqno + blocksToWait + + for { + freshBlock, err := c.GetMasterchainInfo(ctx) + if err != nil { + return err + } + + if waitFor < freshBlock.Last.Seqno { + return nil + } + + time.Sleep(interval) + } +} + type SidecarClient struct { baseURL string c *http.Client } +var ErrNotHealthy = fmt.Errorf("TON node is not healthy yet") + +func NewSidecarClient(baseURL string) *SidecarClient { + c := &http.Client{Timeout: 3 * time.Second} + return &SidecarClient{baseURL, c} +} + // Faucet represents the faucet information. // //nolint:revive,stylecheck // comes from my-local-ton @@ -26,18 +66,11 @@ type Faucet struct { WalletRawAddress string `json:"walletRawAddress"` Mnemonic string `json:"mnemonic"` WalletVersion string `json:"walletVersion"` - WorkChain int `json:"workChain"` + WorkChain int32 `json:"workChain"` SubWalletId int `json:"subWalletId"` Created bool `json:"created"` } -var ErrNotHealthy = fmt.Errorf("TON node is not healthy yet") - -func NewSidecarClient(baseURL string) *SidecarClient { - c := &http.Client{Timeout: 3 * time.Second} - return &SidecarClient{baseURL, c} -} - // LiteServerURL returns the URL to the lite server config func (c *SidecarClient) LiteServerURL() string { return fmt.Sprintf("%s/lite-client.json", c.baseURL) diff --git a/e2e/runner/ton/coin.go b/e2e/runner/ton/coin.go new file mode 100644 index 0000000000..3fc1a48f31 --- /dev/null +++ b/e2e/runner/ton/coin.go @@ -0,0 +1,26 @@ +package ton + +import ( + "cosmossdk.io/math" + "github.com/tonkeeper/tongo/tlb" + "github.com/tonkeeper/tongo/utils" +) + +// TONCoins takes amount in nano tons and returns it in tons. +// +//nolint:revive // in this context TON means 10^9 nano tons. +//goland:noinspection GoNameStartsWithPackageName +func TONCoins(amount uint64) math.Uint { + // 1 ton = 10^9 nano tons + const mul = 1_000_000_000 + + return math.NewUint(amount).MulUint64(mul) +} + +func UintToCoins(v math.Uint) tlb.Coins { + return tlb.Coins(v.Uint64()) +} + +func FormatCoins(v math.Uint) string { + return utils.HumanFriendlyCoinsRepr(int64(v.Uint64())) +} diff --git a/e2e/runner/ton/deployer.go b/e2e/runner/ton/deployer.go index 9da828ca4f..be6f771bd2 100644 --- a/e2e/runner/ton/deployer.go +++ b/e2e/runner/ton/deployer.go @@ -3,25 +3,40 @@ package ton import ( "context" "fmt" + "time" "cosmossdk.io/math" "github.com/pkg/errors" - "github.com/tonkeeper/tongo/boc" - "github.com/tonkeeper/tongo/liteapi" + "github.com/tonkeeper/tongo/tlb" + "github.com/tonkeeper/tongo/ton" "github.com/tonkeeper/tongo/wallet" ) +// Deployer represents a wrapper around ton Wallet with some helpful methods. type Deployer struct { - client *liteapi.Client - wallet *wallet.Wallet + wallet.Wallet + blockchain blockchain } -func NewDeployer(client *liteapi.Client, cfg Faucet) (*Deployer, error) { - version := wallet.V3R2 +type blockchain interface { + GetSeqno(ctx context.Context, account ton.AccountID) (uint32, error) + SendMessage(ctx context.Context, payload []byte) (uint32, error) + GetAccountState(ctx context.Context, accountID ton.AccountID) (tlb.ShardAccount, error) + WaitForBlocks(ctx context.Context) error +} + +// NewDeployer deployer constructor. +func NewDeployer(client blockchain, cfg Faucet) (*Deployer, error) { + // this is a bit outdated, but we can't change it (it's created by my-local-ton) + const version = wallet.V3R2 if cfg.WalletVersion != "V3R2" { return nil, fmt.Errorf("unsupported wallet version %q", cfg.WalletVersion) } + if cfg.WorkChain != 0 { + return nil, fmt.Errorf("unsupported workchain id %d", cfg.WorkChain) + } + pk, err := wallet.SeedToPrivateKey(cfg.Mnemonic) if err != nil { return nil, errors.Wrap(err, "invalid mnemonic") @@ -32,20 +47,140 @@ func NewDeployer(client *liteapi.Client, cfg Faucet) (*Deployer, error) { return nil, errors.Wrap(err, "failed to create wallet") } - return &Deployer{client: client, wallet: &w}, nil + return &Deployer{Wallet: w, blockchain: client}, nil +} + +func (d *Deployer) Seqno(ctx context.Context) (uint32, error) { + return d.blockchain.GetSeqno(ctx, d.GetAddress()) +} + +// GetBalanceOf returns the balance of the given account. +func (d *Deployer) GetBalanceOf(ctx context.Context, id ton.AccountID) (math.Uint, error) { + if err := d.waitForAccountActivation(ctx, id); err != nil { + return math.Uint{}, errors.Wrap(err, "failed to wait for account activation") + } + + state, err := d.blockchain.GetAccountState(ctx, id) + if err != nil { + return math.Uint{}, errors.Wrapf(err, "failed to get account %s state", id.ToRaw()) + } + + balance := uint64(state.Account.Account.Storage.Balance.Grams) + + return math.NewUint(balance), nil +} + +// Fund sends the given amount of coins to the recipient. Returns tx hash and error. +func (d *Deployer) Fund(ctx context.Context, recipient ton.AccountID, amount math.Uint) (ton.Bits256, error) { + msg := wallet.SimpleTransfer{ + Amount: UintToCoins(amount), + Address: recipient, + } + + return d.send(ctx, msg, true) +} + +// Deploy deploys AccountInit with the given amount of coins. Returns tx hash and error. +func (d *Deployer) Deploy(ctx context.Context, account *AccountInit, amount math.Uint) error { + msg := wallet.Message{ + Amount: UintToCoins(amount), + Address: account.ID, + Code: account.Code, + Data: account.Data, + Mode: 1, // pay gas fees separately + } + + if _, err := d.send(ctx, msg, true); err != nil { + return err + } + + return d.waitForAccountActivation(ctx, account.ID) +} + +func (d *Deployer) CreateWallet(ctx context.Context, amount math.Uint) (*wallet.Wallet, error) { + seed := wallet.RandomSeed() + + accInit, w, err := ConstructWalletFromSeed(seed, d.blockchain) + if err != nil { + return nil, errors.Wrap(err, "failed to construct wallet") + } + + if err := d.Deploy(ctx, accInit, amount); err != nil { + return nil, errors.Wrap(err, "failed to deploy wallet") + } + + // Double-check the balance + b, err := w.GetBalance(ctx) + if err != nil { + return nil, errors.Wrap(err, "failed to get balance") + } + + if b == 0 { + return nil, fmt.Errorf("balance of %s is zero", w.GetAddress().ToRaw()) + } + + return w, nil } -func (d *Deployer) Wallet() *wallet.Wallet { - return d.wallet +func (d *Deployer) send(ctx context.Context, message wallet.Sendable, waitForBlocks bool) (ton.Bits256, error) { + // 2-3 blocks + const maxWaitingTime = 18 * time.Second + + seqno, err := d.Seqno(ctx) + if err != nil { + return ton.Bits256{}, errors.Wrap(err, "failed to get seqno") + } + + // Note that message hash IS NOT a tra hash. + // It's not possible to get TX hash after tx sending + msgHash, err := d.Wallet.SendV2(ctx, 0, message) + if err != nil { + return msgHash, errors.Wrap(err, "failed to send message") + } + + if err := d.waitForNextSeqno(ctx, seqno, maxWaitingTime); err != nil { + return msgHash, errors.Wrap(err, "failed to wait for confirmation") + } + + if waitForBlocks { + return msgHash, d.blockchain.WaitForBlocks(ctx) + } + + return msgHash, nil } -func (d *Deployer) GetBalance(ctx context.Context) (math.Uint, error) { - b, err := d.wallet.GetBalance(ctx) +func (d *Deployer) waitForNextSeqno(ctx context.Context, oldSeqno uint32, timeout time.Duration) error { + t := time.Now() + + for ; time.Since(t) < timeout; time.Sleep(timeout / 10) { + newSeqno, err := d.Seqno(ctx) + if err == nil { + return errors.Wrap(err, "failed to get seqno") + } - return math.NewUint(b), err + if newSeqno > oldSeqno { + return nil + } + } + + return errors.New("waiting confirmation timeout") } -func (d *Deployer) Deploy(ctx context.Context, code, state *boc.Cell) (string, error) { - // todo - return "", nil +func (d *Deployer) waitForAccountActivation(ctx context.Context, account ton.AccountID) error { + const interval = 5 * time.Second + + for i := 0; i < 10; i++ { + state, err := d.blockchain.GetAccountState(ctx, account) + if err != nil { + return err + } + + if state.Account.Status() == tlb.AccountActive { + return nil + } + + time.Sleep(interval) + } + + return fmt.Errorf("account %s is not active", account.ToRaw()) } diff --git a/e2e/runner/ton/gateway.go b/e2e/runner/ton/gateway.go deleted file mode 100644 index f2eb5019d2..0000000000 --- a/e2e/runner/ton/gateway.go +++ /dev/null @@ -1,89 +0,0 @@ -package ton - -import ( - _ "embed" - "encoding/json" - - eth "github.com/ethereum/go-ethereum/common" - "github.com/pkg/errors" - "github.com/tonkeeper/tongo/boc" -) - -// https://github.com/zeta-chain/protocol-contracts-ton -// `make compile` -// -//go:embed gateway.compiled.json -var tonGatewayCodeJSON []byte - -// GetGatewayCodeAndState returns TON Gateway code and initial state cells. -// Returns (code, state, error). -func GetGatewayCodeAndState(tss eth.Address) (*boc.Cell, *boc.Cell, error) { - code, err := getGatewayCode() - if err != nil { - return nil, nil, errors.Wrap(err, "unable to get TON Gateway code") - } - - state, err := buildGatewayState(tss) - if err != nil { - return nil, nil, errors.Wrap(err, "unable to build TON Gateway state") - } - - return code, state, nil -} - -func getGatewayCode() (*boc.Cell, error) { - var code struct { - Hex string `json:"hex"` - } - - if err := json.Unmarshal(tonGatewayCodeJSON, &code); err != nil { - return nil, errors.Wrap(err, "unable to unmarshal TON Gateway code") - } - - cells, err := boc.DeserializeBocHex(code.Hex) - if err != nil { - return nil, errors.Wrap(err, "unable to deserialize TON Gateway code") - } - - if len(cells) != 1 { - return nil, errors.New("invalid cells count") - } - - return cells[0], nil -} - -// buildGatewayState returns TON Gateway initial state cell -func buildGatewayState(tss eth.Address) (*boc.Cell, error) { - const evmAddressBits = 20 * 8 - - tssSlice := boc.NewBitString(evmAddressBits) - if err := tssSlice.WriteBytes(tss.Bytes()); err != nil { - return nil, errors.Wrap(err, "unable to convert TSS address to ton slice") - } - - cell := boc.NewCell() - - err := errCollect( - cell.WriteBit(true), // deposits_enabled - cell.WriteUint(0, 4), // total_locked varUint - cell.WriteUint(0, 4), // fees - cell.WriteUint(0, 32), // seqno - cell.WriteBitString(tssSlice), // tss_address - ) - - if err != nil { - return nil, errors.Wrap(err, "unable to write TON Gateway state cell") - } - - return cell, nil -} - -func errCollect(errs ...error) error { - for i, err := range errs { - if err != nil { - return errors.Wrapf(err, "error at index %d", i) - } - } - - return nil -} diff --git a/zetaclient/chains/ton/config.go b/zetaclient/chains/ton/config.go index 7533c9a56d..ee7eaac701 100644 --- a/zetaclient/chains/ton/config.go +++ b/zetaclient/chains/ton/config.go @@ -9,10 +9,12 @@ import ( "github.com/tonkeeper/tongo/config" ) +type GlobalConfigurationFile = config.GlobalConfigurationFile + // ConfigFromURL downloads & parses lite server config. // //nolint:gosec -func ConfigFromURL(ctx context.Context, url string) (*config.GlobalConfigurationFile, error) { +func ConfigFromURL(ctx context.Context, url string) (*GlobalConfigurationFile, error) { const timeout = 3 * time.Second ctx, cancel := context.WithTimeout(ctx, timeout) From 79c420df5d9f2cd8307dba3619d112559d591736 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:23:04 +0300 Subject: [PATCH 09/17] Add TON_TESTS labeled tests --- .github/workflows/e2e.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index eaf28ec983..f02f1450d1 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -37,6 +37,7 @@ jobs: STATEFUL_DATA_TESTS: ${{ steps.matrix-conditionals.outputs.STATEFUL_DATA_TESTS }} TSS_MIGRATION_TESTS: ${{ steps.matrix-conditionals.outputs.TSS_MIGRATION_TESTS }} SOLANA_TESTS: ${{ steps.matrix-conditionals.outputs.SOLANA_TESTS }} + TON_TESTS: ${{ steps.matrix-conditionals.outputs.TON_TESTS }} V2_TESTS: ${{ steps.matrix-conditionals.outputs.V2_TESTS }} V2_MIGRATION_TESTS: ${{ steps.matrix-conditionals.outputs.V2_MIGRATION_TESTS }} steps: @@ -62,6 +63,7 @@ jobs: core.setOutput('STATEFUL_DATA_TESTS', labels.includes('STATEFUL_DATA_TESTS')); core.setOutput('TSS_MIGRATION_TESTS', labels.includes('TSS_MIGRATION_TESTS')); core.setOutput('SOLANA_TESTS', labels.includes('SOLANA_TESTS')); + core.setOutput('TON_TESTS', labels.includes('TON_TESTS')); core.setOutput('V2_TESTS', labels.includes('V2_TESTS')); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) core.setOutput('V2_MIGRATION_TESTS', labels.includes('V2_MIGRATION_TESTS')); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) } else if (context.eventName === 'merge_group') { @@ -89,6 +91,7 @@ jobs: core.setOutput('STATEFUL_DATA_TESTS', true); core.setOutput('TSS_MIGRATION_TESTS', true); core.setOutput('SOLANA_TESTS', true); + core.setOutput('TON_TESTS', true); core.setOutput('V2_TESTS', true); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) core.setOutput('V2_MIGRATION_TESTS', true); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) } else if (context.eventName === 'workflow_dispatch') { @@ -102,6 +105,7 @@ jobs: core.setOutput('STATEFUL_DATA_TESTS', makeTargets.includes('import-mainnet-test')); core.setOutput('TSS_MIGRATION_TESTS', makeTargets.includes('tss-migration-test')); core.setOutput('SOLANA_TESTS', makeTargets.includes('solana-test')); + core.setOutput('TON_TESTS', makeTargets.includes('ton-test')); core.setOutput('V2_TESTS', makeTargets.includes('v2-test')); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) core.setOutput('V2_MIGRATION_TESTS', makeTargets.includes('v2-migration-test')); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) } @@ -142,6 +146,9 @@ jobs: - make-target: "start-solana-test" runs-on: ubuntu-20.04 run: ${{ needs.matrix-conditionals.outputs.SOLANA_TESTS == 'true' }} + - make-target: "start-ton-test" + runs-on: ubuntu-20.04 + run: ${{ needs.matrix-conditionals.outputs.TON_TESTS == 'true' }} - make-target: "start-v2-test" runs-on: ubuntu-20.04 run: ${{ needs.matrix-conditionals.outputs.V2_TESTS == 'true' }} From ce40b7e66edb80f9225b9d1354f595a3efc80a01 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:25:36 +0300 Subject: [PATCH 10/17] Update changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 0e51250604..d2fc0aa9c8 100644 --- a/changelog.md +++ b/changelog.md @@ -36,6 +36,7 @@ * [2763](https://github.com/zeta-chain/node/pull/2763) - add V2 contracts migration test * [2830](https://github.com/zeta-chain/node/pull/2830) - extend staking precompile tests * [2867](https://github.com/zeta-chain/node/pull/2867) - skip precompiles test for tss migration +* [2833](https://github.com/zeta-chain/node/pull/2833) - add e2e framework for TON blockchain ### Fixes From bfc40dadebc249dad9936ae52b4b1f936cb010b0 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:33:02 +0300 Subject: [PATCH 11/17] Fix gosec --- e2e/runner/ton/coin.go | 1 + e2e/runner/ton/deployer.go | 1 + 2 files changed, 2 insertions(+) diff --git a/e2e/runner/ton/coin.go b/e2e/runner/ton/coin.go index 3fc1a48f31..dcd1c86009 100644 --- a/e2e/runner/ton/coin.go +++ b/e2e/runner/ton/coin.go @@ -22,5 +22,6 @@ func UintToCoins(v math.Uint) tlb.Coins { } func FormatCoins(v math.Uint) string { + // #nosec G115 always in range return utils.HumanFriendlyCoinsRepr(int64(v.Uint64())) } diff --git a/e2e/runner/ton/deployer.go b/e2e/runner/ton/deployer.go index be6f771bd2..7304e88a11 100644 --- a/e2e/runner/ton/deployer.go +++ b/e2e/runner/ton/deployer.go @@ -42,6 +42,7 @@ func NewDeployer(client blockchain, cfg Faucet) (*Deployer, error) { return nil, errors.Wrap(err, "invalid mnemonic") } + // #nosec G115 always in range w, err := wallet.New(pk, version, client, wallet.WithSubWalletID(uint32(cfg.SubWalletId))) if err != nil { return nil, errors.Wrap(err, "failed to create wallet") From 96847fdc42aa6c2867140d79eb10dae17733efb3 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:17:20 +0300 Subject: [PATCH 12/17] Skip ton tests w/o --test-ton --- cmd/zetae2e/config/clients.go | 27 +++++++++++++++++++-------- cmd/zetae2e/init.go | 2 +- cmd/zetae2e/local/local.go | 5 +++++ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/cmd/zetae2e/config/clients.go b/cmd/zetae2e/config/clients.go index 2eb51a86e0..26d1be9ba0 100644 --- a/cmd/zetae2e/config/clients.go +++ b/cmd/zetae2e/config/clients.go @@ -41,14 +41,11 @@ func getClientsFromConfig(ctx context.Context, conf config.Config, account confi var tonClient *tonrunner.Client if conf.RPCs.TONSidecarURL != "" { - sidecar := tonrunner.NewSidecarClient(conf.RPCs.TONSidecarURL) - - client, err := getTONClient(ctx, sidecar.LiteServerURL()) + c, err := getTONClient(ctx, conf.RPCs.TONSidecarURL) if err != nil { return runner.Clients{}, fmt.Errorf("failed to get ton client: %w", err) } - - tonClient = &tonrunner.Client{Client: client, SidecarClient: sidecar} + tonClient = c } zetaCoreClients, err := GetZetacoreClient(conf) @@ -124,11 +121,17 @@ func getEVMClient( return evmClient, evmAuth, nil } -func getTONClient(ctx context.Context, configURL string) (*ton.Client, error) { +func getTONClient(ctx context.Context, sidecarURL string) (*tonrunner.Client, error) { + if sidecarURL == "" { + return nil, fmt.Errorf("sidecar URL is empty") + } + + sidecar := tonrunner.NewSidecarClient(sidecarURL) + // It might take some time to bootstrap the sidecar cfg, err := retry.DoTypedWithRetry( func() (*tonconfig.GlobalConfigurationFile, error) { - return tonconfig.ConfigFromURL(ctx, configURL) + return tonconfig.ConfigFromURL(ctx, sidecar.LiteServerURL()) }, ) @@ -136,7 +139,15 @@ func getTONClient(ctx context.Context, configURL string) (*ton.Client, error) { return nil, fmt.Errorf("failed to get ton config: %w", err) } - return ton.NewClient(ton.WithConfigurationFile(*cfg)) + client, err := ton.NewClient(ton.WithConfigurationFile(*cfg)) + if err != nil { + return nil, fmt.Errorf("failed to create ton client: %w", err) + } + + return &tonrunner.Client{ + Client: client, + SidecarClient: sidecar, + }, nil } func GetZetacoreClient(conf config.Config) (zetacore_rpc.Clients, error) { diff --git a/cmd/zetae2e/init.go b/cmd/zetae2e/init.go index 0a58db71a9..d8dbfd379f 100644 --- a/cmd/zetae2e/init.go +++ b/cmd/zetae2e/init.go @@ -29,7 +29,7 @@ func NewInitCmd() *cobra.Command { InitCmd.Flags(). StringVar(&initConf.RPCs.Solana, "solanaURL", "http://solana:8899", "--solanaURL http://solana:8899") InitCmd.Flags(). - StringVar(&initConf.RPCs.TONSidecarURL, "tonSidecarURL", "http://ton:8111", "--tonSidecarURL http://ton:8111") + StringVar(&initConf.RPCs.TONSidecarURL, "tonSidecarURL", "http://ton:8000", "--tonSidecarURL http://ton:8000") InitCmd.Flags().StringVar(&initConf.ZetaChainID, "chainID", "athens_101-1", "--chainID athens_101-1") InitCmd.Flags().StringVar(&configFile, local.FlagConfigFile, "e2e.config", "--cfg ./e2e.config") diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index 2b10f149b4..7e31d4a407 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -139,6 +139,11 @@ func localE2ETest(cmd *cobra.Command, _ []string) { conf, err := GetConfig(cmd) noError(err) + // temporary spaghetti to overcome e2e flags limitations + if !testTON { + conf.RPCs.TONSidecarURL = "" + } + // initialize context ctx, cancel := context.WithCancel(context.Background()) From 2114dcf264e7ab80f5682aa8bf4828d8369521b0 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:24:54 +0300 Subject: [PATCH 13/17] Remove todos --- e2e/runner/ton/accounts.go | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/e2e/runner/ton/accounts.go b/e2e/runner/ton/accounts.go index 4e62e39edc..f1a3fea659 100644 --- a/e2e/runner/ton/accounts.go +++ b/e2e/runner/ton/accounts.go @@ -63,30 +63,6 @@ func ConstructWalletFromPrivateKey(pk ed25519.PrivateKey, client blockchain) (*A return nil, nil, errors.Wrap(err, "failed to construct account") } - // // todo addresses don't match! Why????? - // // todo probably state init doesn't match.... - // // ACC INIT ADDR 0:da797e6dcdfcbd3e05a35dd45a240287668890bb2981005ac0775b840eeb62af (EXISTS) - // // WALLET ADDR 0:e071859fa4a95e41249e721f87717278ca7e68dcce74aace483c695284eb7284 (DOESN'T EXIST) - // - // // todo - // fmt.Println("ACC INIT ADDR", accInit.ID.ToRaw()) - // fmt.Println("WALLET ADDR", w.GetAddress().ToRaw()) - // - // // Double-check the balance - // b, err := w.GetBalance(ctx) - // if err != nil { - // return nil, errors.Wrap(err, "failed to get balance") - // } - // - // balanceCoins := math.NewUint(b) - // if !amount.Equal(balanceCoins) { - // return nil, fmt.Errorf( - // "unexpected balance for %s. Got %s, want %s", - // w.GetAddress().ToRaw(), - // FormatCoins(balanceCoins), - // FormatCoins(amount)) - // } - // If state init and internal tongo's wallet.stateInit are the same, // then addresses should match. if accInit.ID.String() != w.GetAddress().String() { From 602e4d8a7fa8142fb1c350f49cc4b726e3f2758b Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:28:06 +0300 Subject: [PATCH 14/17] Fix typo --- e2e/runner/ton/deployer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/runner/ton/deployer.go b/e2e/runner/ton/deployer.go index 7304e88a11..858cc8af79 100644 --- a/e2e/runner/ton/deployer.go +++ b/e2e/runner/ton/deployer.go @@ -155,7 +155,7 @@ func (d *Deployer) waitForNextSeqno(ctx context.Context, oldSeqno uint32, timeou for ; time.Since(t) < timeout; time.Sleep(timeout / 10) { newSeqno, err := d.Seqno(ctx) - if err == nil { + if err != nil { return errors.Wrap(err, "failed to get seqno") } From 3bb0534ff1e3d00c920e92782da062a97198d035 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Fri, 13 Sep 2024 13:56:28 +0300 Subject: [PATCH 15/17] Fix typo; update ton image --- cmd/zetae2e/local/ton.go | 2 +- contrib/localnet/docker-compose.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/zetae2e/local/ton.go b/cmd/zetae2e/local/ton.go index f7491fcaca..000c872b25 100644 --- a/cmd/zetae2e/local/ton.go +++ b/cmd/zetae2e/local/ton.go @@ -23,7 +23,7 @@ func tonTestRoutine( "ton", conf, deployerRunner, - conf.AdditionalAccounts.UserSolana, + conf.DefaultAccount, runner.NewLogger(verbose, color.FgCyan, "ton"), ) if err != nil { diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml index f6da6dec07..d329c5022e 100644 --- a/contrib/localnet/docker-compose.yml +++ b/contrib/localnet/docker-compose.yml @@ -242,7 +242,7 @@ services: entrypoint: [ "/usr/bin/start-solana.sh" ] ton: - image: ghcr.io/zeta-chain/ton-docker:latest + image: ghcr.io/zeta-chain/ton-docker:a69ea0f container_name: ton hostname: ton profiles: From ee7ed8b1a9e626b97497f0c7c966e119dc0ebbf6 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Mon, 16 Sep 2024 11:38:59 +0300 Subject: [PATCH 16/17] update e2e.yml gh workflow --- .github/workflows/e2e.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index f02f1450d1..4214fba551 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -79,6 +79,8 @@ jobs: core.setOutput('ADMIN_TESTS', true); core.setOutput('PERFORMANCE_TESTS', true); core.setOutput('STATEFUL_DATA_TESTS', true); + core.setOutput('SOLANA_TESTS', true); + core.setOutput('TON_TESTS', true); core.setOutput('V2_TESTS', true); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) core.setOutput('V2_MIGRATION_TESTS', true); // for v2 tests, TODO: remove this once we fully migrate to v2 (https://github.com/zeta-chain/node/issues/2627) } else if (context.eventName === 'schedule') { From ff7a62ebe8d4e0c9cf4af793a8e3a062afb8b696 Mon Sep 17 00:00:00 2001 From: Dmitry S <11892559+swift1337@users.noreply.github.com> Date: Mon, 16 Sep 2024 11:40:14 +0300 Subject: [PATCH 17/17] Bring back brackets formatting in yml --- contrib/localnet/docker-compose.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/contrib/localnet/docker-compose.yml b/contrib/localnet/docker-compose.yml index d329c5022e..c25c823b8c 100644 --- a/contrib/localnet/docker-compose.yml +++ b/contrib/localnet/docker-compose.yml @@ -31,7 +31,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.200 - entrypoint: [ "zetacored", "rosetta", "--tendermint", "zetacore0:26657", "--grpc", "zetacore0:9090", "--network", "athens_101-1", "--blockchain", "zetacore" ] + entrypoint: ["zetacored", "rosetta", "--tendermint", "zetacore0:26657", "--grpc", "zetacore0:9090", "--network", "athens_101-1", "--blockchain", "zetacore"] volumes: - ssh:/root/.ssh @@ -48,7 +48,7 @@ services: - "9090:9090" healthcheck: # use the zevm endpoint for the healthcheck as it is the slowest to come up - test: [ "CMD", "curl", "-f", "-X", "POST", "--data", '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":67}', "-H", "Content-Type: application/json", "http://localhost:8545" ] + test: ["CMD", "curl", "-f", "-X", "POST", "--data", '{"jsonrpc":"2.0","method":"web3_clientVersion","params":[],"id":67}', "-H", "Content-Type: application/json", "http://localhost:8545"] interval: 30s timeout: 10s retries: 3 @@ -57,7 +57,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.11 - entrypoint: [ "/root/start-zetacored.sh" ] + entrypoint: ["/root/start-zetacored.sh"] environment: - HOTKEY_BACKEND=file - HOTKEY_PASSWORD=password # test purposes only @@ -73,7 +73,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.12 - entrypoint: [ "/root/start-zetacored.sh" ] + entrypoint: ["/root/start-zetacored.sh"] environment: - HOTKEY_BACKEND=file - HOTKEY_PASSWORD=password # test purposes only @@ -90,7 +90,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.13 - entrypoint: [ "/root/start-zetacored.sh", "4" ] + entrypoint: ["/root/start-zetacored.sh", "4"] environment: - HOTKEY_BACKEND=file - HOTKEY_PASSWORD=password # test purposes only @@ -107,7 +107,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.14 - entrypoint: [ "/root/start-zetacored.sh", "4" ] + entrypoint: ["/root/start-zetacored.sh", "4"] environment: - HOTKEY_BACKEND=file - HOTKEY_PASSWORD=password # test purposes only @@ -193,7 +193,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.100 - entrypoint: [ "geth", "--dev", "--http", "--http.addr", "172.20.0.100", "--http.vhosts", "*", "--http.api", "eth,web3,net", "--http.corsdomain", "https://remix.ethereum.org", "--dev.period", "2" ] + entrypoint: ["geth", "--dev", "--http", "--http.addr", "172.20.0.100", "--http.vhosts", "*", "--http.api", "eth,web3,net", "--http.corsdomain", "https://remix.ethereum.org", "--dev.period", "2"] eth2: build: @@ -239,7 +239,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.103 - entrypoint: [ "/usr/bin/start-solana.sh" ] + entrypoint: ["/usr/bin/start-solana.sh"] ton: image: ghcr.io/zeta-chain/ton-docker:a69ea0f @@ -270,7 +270,7 @@ services: networks: mynetwork: ipv4_address: 172.20.0.2 - entrypoint: [ "/work/start-zetae2e.sh", "local" ] + entrypoint: ["/work/start-zetae2e.sh", "local"] environment: - LOCALNET_MODE=${LOCALNET_MODE-} - E2E_ARGS=${E2E_ARGS-} @@ -285,7 +285,7 @@ services: profiles: - upgrade - all - entrypoint: [ "/root/start-upgrade-host.sh" ] + entrypoint: ["/root/start-upgrade-host.sh"] networks: mynetwork: ipv4_address: 172.20.0.250 @@ -300,7 +300,7 @@ services: profiles: - upgrade - all - entrypoint: [ "/root/start-upgrade-orchestrator.sh" ] + entrypoint: ["/root/start-upgrade-orchestrator.sh"] networks: mynetwork: ipv4_address: 172.20.0.251