From b981626c7879cbe9fe960c9c238fb0d33eed478f Mon Sep 17 00:00:00 2001 From: Starsquid <108214377+starsquidnodes@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:26:40 +0100 Subject: [PATCH 1/7] Add vote extension support --- build.py | 54 ++++++--------- feeder/Dockerfile | 10 ++- feeder/entrypoint.sh | 10 +++ feeder/start.sh | 12 ---- kujira/Dockerfile | 3 +- prepare/prepare.py | 24 ++++--- prepare/templates/app.toml.j2 | 110 +++++++++++++------------------ prepare/templates/client.toml.j2 | 4 +- prepare/templates/config.toml.j2 | 60 +++++++---------- prepare/templates/feeder.toml.j2 | 29 +------- versions.yml | 19 ++++++ 11 files changed, 140 insertions(+), 195 deletions(-) create mode 100644 feeder/entrypoint.sh delete mode 100644 feeder/start.sh create mode 100644 versions.yml diff --git a/build.py b/build.py index 5933083..000b832 100755 --- a/build.py +++ b/build.py @@ -4,42 +4,18 @@ import platform import subprocess import sys - -GO_VERSIONS = { - "kujira": { - "v0.9.0": "1.20.2", - "v0.9.1": "1.20.2", - "v0.9.2": "1.20.2", - "v0.9.3-1": "1.20.2", - "sdk-50": "1.21.3", - }, - "feeder": { - "v0.8.3": "1.20.2", - "v0.11.0": "1.20.2", - }, - "relayer": { - "v2.5.0": "1.21.7", - } -} - -VERSIONS = { - "v0.1.0": { - "kujira": "v0.9.3-1", - "feeder": "v0.11.0", - "relayer": "v2.5.0" - } -} +import yaml def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("-n", "--namespace", default="teamkujira") parser.add_argument("-a", "--app") - parser.add_argument("-t", "--tag", default="v0.1.0") + parser.add_argument("-t", "--tag", required=True) parser.add_argument("--push", action="store_true") parser.add_argument("--podman", action="store_true") parser.add_argument("--manifest", action="store_true") - parser.add_argument("--archs", default=platform.machine()) + parser.add_argument("--archs") return parser.parse_args() @@ -47,7 +23,7 @@ def build_arg(s): return ["--build-arg", s] -def build(command, namespace, app, tag, push=False): +def build(command, namespace, app, tag, versions, push=False): arch = platform.machine() if arch == "x86_64": @@ -58,7 +34,7 @@ def build(command, namespace, app, tag, push=False): cmd = [command, "build", "--tag", fulltag] if app in ["feeder", "kujira", "relayer"]: - go_version = GO_VERSIONS.get(app, {}).get(tag) + go_version = versions["go"].get(app, {}).get(tag) if not go_version: print(f"no go version defined for {app}:{tag}") sys.exit(1) @@ -67,9 +43,9 @@ def build(command, namespace, app, tag, push=False): cmd += build_arg(f"go_version={go_version}") if app == "prepare": - kujira_version = VERSIONS[tag]["kujira"] - feeder_version = VERSIONS[tag]["feeder"] - relayer_version = VERSIONS[tag]["relayer"] + kujira_version = versions["pond"][tag]["kujira"] + feeder_version = versions["pond"][tag]["feeder"] + relayer_version = versions["pond"][tag]["relayer"] cmd += build_arg(f"kujira_version={kujira_version}") cmd += build_arg(f"feeder_version={feeder_version}") @@ -115,6 +91,8 @@ def manifest(command, namespace, app, tag, archs, push=False): def main(): args = parse_args() + versions = yaml.safe_load(open("versions.yml", "r")) + command = "docker" if args.podman: command = "podman" @@ -124,7 +102,7 @@ def main(): apps = [args.app] for app in apps: - tag = VERSIONS[args.tag].get(app) + tag = versions["pond"][args.tag].get(app) if app == "prepare": tag = args.tag @@ -132,12 +110,18 @@ def main(): print(f"no tag defined for {app} ({args.tag})") sys.exit(1) - build(command, args.namespace, app, tag, args.push) + build(command, args.namespace, app, tag, versions, args.push) if not args.manifest: return - manifest(command, args.namespace, app, tag, args.archs, args.push) + if not args.archs: + archs = platform.machine() + + if archs == "x86_64": + archs = "amd64" + + manifest(command, args.namespace, app, tag, archs, args.push) if __name__ == "__main__": diff --git a/feeder/Dockerfile b/feeder/Dockerfile index bbe1803..92b304d 100644 --- a/feeder/Dockerfile +++ b/feeder/Dockerfile @@ -9,8 +9,7 @@ RUN git clone --depth 1 --branch ${feeder_version} https://github.com/Team-Kujir make install WORKDIR /dist -RUN mkdir bin lib && \ - mv $(ldd /go/bin/price-feeder | egrep "libwasmvm.[^\.]+.so" | awk '{print $3}') lib/ && \ +RUN mkdir bin && \ mv /go/bin/price-feeder bin/ FROM ubuntu:latest @@ -18,12 +17,11 @@ FROM ubuntu:latest RUN apt-get update && apt-get install --yes ca-certificates COPY --from=build /dist/bin/* /usr/local/bin/ -COPY --from=build /dist/lib/* /usr/lib/ -COPY start.sh /usr/local/bin/ +COPY entrypoint.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/start.sh +RUN chmod +x /usr/local/bin/entrypoint.sh WORKDIR /feeder -ENTRYPOINT ["start.sh"] +ENTRYPOINT ["entrypoint.sh"] diff --git a/feeder/entrypoint.sh b/feeder/entrypoint.sh new file mode 100644 index 0000000..21cb894 --- /dev/null +++ b/feeder/entrypoint.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +if [ -n "$USER" ]; then + useradd -u $USER -md /feeder feeder + chown -R feeder /feeder + + exec runuser -u feeder -- "$@" +else + $@ +fi diff --git a/feeder/start.sh b/feeder/start.sh deleted file mode 100644 index b8c9ffd..0000000 --- a/feeder/start.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -if [ -n "$USER" ]; then - useradd -u $USER -m feeder - chown -R feeder /feeder - - cd /feeder - exec runuser -u feeder -- echo "" | price-feeder config.toml -else - cd /feeder - echo "" | price-feeder config.toml -fi diff --git a/kujira/Dockerfile b/kujira/Dockerfile index 63909ec..a92b638 100644 --- a/kujira/Dockerfile +++ b/kujira/Dockerfile @@ -4,8 +4,9 @@ FROM golang:${go_version} AS build ARG kujira_version WORKDIR /build -RUN git clone --depth 1 --branch ${kujira_version} https://github.com/Team-Kujira/core.git && \ +RUN git clone https://github.com/Team-Kujira/core.git && \ cd core && \ + git checkout ${kujira_version} && \ make install WORKDIR /dist diff --git a/prepare/prepare.py b/prepare/prepare.py index 32d9b2b..705106f 100755 --- a/prepare/prepare.py +++ b/prepare/prepare.py @@ -71,7 +71,7 @@ def create_gentx(self, amount): return valoper - def collectd_gentxs(self): + def collect_gentxs(self): subprocess.run([ self.binary, "--home", self.home, "genesis", "collect-gentxs" ], capture_output=False, text=True) @@ -143,7 +143,8 @@ def init_chain(name, chain_id, binary, denom, nodes, port_prefix, mnemonics, pod "mnemonic": mnemonic, "api_port": f"{port_prefix}{i+1:02}17", "app_port": f"{port_prefix}{i+1:02}56", - "rpc_port": f"{port_prefix}{i+1:02}57" + "rpc_port": f"{port_prefix}{i+1:02}57", + "feeder_url": f"http://localhost:10{i+1}71/api/v1/prices" }) if i == 0: @@ -163,8 +164,8 @@ def init_chain(name, chain_id, binary, denom, nodes, port_prefix, mnemonics, pod amount = 10_000_000_000_000 total += amount - address = node.add_key(mnemonic, True) - node.add_genesis_account(address, amount) + address = main.add_key(mnemonic, True) + main.add_genesis_account(address, amount) info["accounts"].append({ "address": address, @@ -178,17 +179,17 @@ def init_chain(name, chain_id, binary, denom, nodes, port_prefix, mnemonics, pod amount = 1_000_000_000_000 total += amount - address = node.add_key(mnemonic, True) - node.add_genesis_account(address, amount) + address = main.add_key(mnemonic, True) + main.add_genesis_account(address, amount) info["ibc"] = { "address": address, "mnemonic": mnemonic } - node.collectd_gentxs() + main.collect_gentxs() - genesis = json.load(open(f"{node.home}/config/genesis.json")) + genesis = json.load(open(f"{main.home}/config/genesis.json")) genesis["app_state"]["crisis"]["constant_fee"]["denom"] = denom genesis["app_state"]["denom"]["params"]["creation_fee"][0]["denom"] = denom @@ -199,6 +200,7 @@ def init_chain(name, chain_id, binary, denom, nodes, port_prefix, mnemonics, pod genesis["app_state"]["mint"]["params"]["mint_denom"] = denom genesis["app_state"]["staking"]["params"]["unbonding_time"] = "1209600s" genesis["app_state"]["staking"]["params"]["bond_denom"] = denom + genesis["consensus"]["params"]["abci"]["vote_extensions_enable_height"] = "1" if chain_id == "pond-1": genesis["app_state"]["oracle"]["params"]["whitelist"] = [ @@ -247,6 +249,7 @@ def init_chain(name, chain_id, binary, denom, nodes, port_prefix, mnemonics, pod "address": validator["address"], "valoper": validator["valoper"], "chain_id": chain_id, + "node_num": i+1, "podman": podman } @@ -280,11 +283,14 @@ def init_chain(name, chain_id, binary, denom, nodes, port_prefix, mnemonics, pod for i in range(nodes): validator = info["validators"][i] - validator["rpc_url"] = f"http://localhost:{validator['rpc_port']}" validator["api_url"] = f"http://localhost:{validator['api_port']}" + validator["app_url"] = f"tcp://localhost:{validator['app_port']}" + validator["rpc_url"] = f"http://localhost:{validator['rpc_port']}" validator.pop("app_port") validator.pop("api_port") validator.pop("rpc_port") + if chain_id != "pond-1": + validator.pop("feeder_url") info["validators"][i] = validator diff --git a/prepare/templates/app.toml.j2 b/prepare/templates/app.toml.j2 index a489791..4a8b621 100644 --- a/prepare/templates/app.toml.j2 +++ b/prepare/templates/app.toml.j2 @@ -10,6 +10,10 @@ # specified in this config (e.g. 0.25token1;0.0001token2). minimum-gas-prices = "0ukuji" +# The maximum gas a query coming over rest/grpc may consume. +# If this is set to zero, the query can consume an unbounded amount of gas. +query-gas-limit = "0" + # default: the last 362880 states are kept, pruning at 10 block intervals # nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node) # everything: 2 latest states will be kept; pruning at 10 block intervals. @@ -35,15 +39,15 @@ halt-time = 0 # MinRetainBlocks defines the minimum block height offset from the current # block being committed, such that all blocks past this offset are pruned -# from Tendermint. It is used as part of the process of determining the +# from CometBFT. It is used as part of the process of determining the # ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates # that no blocks should be pruned. # -# This configuration value is only responsible for pruning Tendermint blocks. +# This configuration value is only responsible for pruning CometBFT blocks. # It has no bearing on application state pruning which is determined by the # "pruning-*" configurations. # -# Note: Tendermint block pruning is dependant on this parameter in conunction +# Note: CometBFT block pruning is dependant on this parameter in conjunction # with the unbonding (safety threshold) period, state pruning and state sync # snapshot parameters to determine the correct minimum value of # ResponseCommit.RetainHeight. @@ -53,7 +57,7 @@ min-retain-blocks = 0 inter-block-cache = true # IndexEvents defines the set of events in the form {eventType}.{attributeKey}, -# which informs Tendermint what to index. If empty, all events will be indexed. +# which informs CometBFT what to index. If empty, all events will be indexed. # # Example: # ["message.sender", "message.recipient"] @@ -72,7 +76,7 @@ iavl-lazy-loading = false # AppDBBackend defines the database backend type to use for the application and snapshots DBs. # An empty string indicates that a fallback will be used. -# The fallback is the db_backend value set in Tendermint's config.toml. +# The fallback is the db_backend value set in CometBFT's config.toml. app-db-backend = "" ############################################################################### @@ -126,54 +130,18 @@ address = "tcp://0.0.0.0:{{ api_port }}" # MaxOpenConnections defines the number of maximum open connections. max-open-connections = 1000 -# RPCReadTimeout defines the Tendermint RPC read timeout (in seconds). +# RPCReadTimeout defines the CometBFT RPC read timeout (in seconds). rpc-read-timeout = 10 -# RPCWriteTimeout defines the Tendermint RPC write timeout (in seconds). +# RPCWriteTimeout defines the CometBFT RPC write timeout (in seconds). rpc-write-timeout = 0 -# RPCMaxBodyBytes defines the Tendermint maximum request body (in bytes). +# RPCMaxBodyBytes defines the CometBFT maximum request body (in bytes). rpc-max-body-bytes = 1000000 # EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk). enabled-unsafe-cors = false -############################################################################### -### Rosetta Configuration ### -############################################################################### - -[rosetta] - -# Enable defines if the Rosetta API server should be enabled. -enable = false - -# Address defines the Rosetta API server to listen on. -address = ":8080" - -# Network defines the name of the blockchain that will be returned by Rosetta. -blockchain = "app" - -# Network defines the name of the network that will be returned by Rosetta. -network = "network" - -# Retries defines the number of retries when connecting to the node before failing. -retries = 3 - -# Offline defines if Rosetta server should run in offline mode. -offline = false - -# EnableDefaultSuggestedFee defines if the server should suggest fee by default. -# If 'construction/medata' is called without gas limit and gas price, -# suggested fee based on gas-to-suggest and denom-to-suggest will be given. -enable-fee-suggestion = false - -# GasToSuggest defines gas limit when calculating the fee -gas-to-suggest = 200000 - -# DenomToSuggest defines the defult denom for fee suggestion. -# Price must be in minimum-gas-prices. -denom-to-suggest = "uatom" - ############################################################################### ### gRPC Configuration ### ############################################################################### @@ -202,14 +170,9 @@ max-send-msg-size = "2147483647" # GRPCWebEnable defines if the gRPC-web should be enabled. # NOTE: gRPC must also be enabled, otherwise, this configuration is a no-op. +# NOTE: gRPC-Web uses the same address as the API server. enable = false -# Address defines the gRPC-web server address to bind to. -address = "127.0.0.1:9091" - -# EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk). -enable-unsafe-cors = false - ############################################################################### ### State Sync Configuration ### ############################################################################### @@ -226,27 +189,30 @@ snapshot-interval = 0 snapshot-keep-recent = 2 ############################################################################### -### Store / State Streaming ### +### State Streaming ### ############################################################################### -[store] -streamers = [] +# Streaming allows nodes to stream state to external systems. +[streaming] -[streamers] -[streamers.file] -keys = ["*"] -write_dir = "" -prefix = "" +# streaming.abci specifies the configuration for the ABCI Listener streaming service. +[streaming.abci] -# output-metadata specifies if output the metadata file which includes the abci request/responses -# during processing the block. -output-metadata = "true" +# List of kv store keys to stream out via gRPC. +# The store key names MUST match the module's StoreKey name. +# +# Example: +# ["acc", "bank", "gov", "staking", "mint"[,...]] +# ["*"] to expose all keys. +keys = [] -# stop-node-on-error specifies if propagate the file streamer errors to consensus state machine. -stop-node-on-error = "true" +# The plugin name used for streaming via gRPC. +# Streaming is only enabled if this is set. +# Supported plugins: abci +plugin = "" -# fsync specifies if call fsync after writing the files. -fsync = "false" +# stop-node-on-err specifies whether to stop the node on message delivery error. +stop-node-on-err = true ############################################################################### ### Mempool ### @@ -259,7 +225,7 @@ fsync = "false" # # Note, this configuration only applies to SDK built-in app-side mempool # implementations. -max-txs = "5000" +max-txs = 5000 [wasm] # This is the maximum sdk gas (wasm and storage) that we allow for any x/wasm "smart" queries @@ -267,3 +233,15 @@ query_gas_limit = 30000000 # This is the number of wasm vm instances we keep cached in memory for speed-up # Warning: this is currently unstable and may lead to crashes, best to keep for 0 unless testing locally lru_size = 0 + +[oracle] +# Endpoint to query oracle prices for vote extension +{% if chain_id == "pond-1" %} +{% if podman %} +endpoint = "http://127.0.0.1:{{ feeder_port }}/api/v1/prices" +{% else %} +endpoint = "http://feeder1-{{ node_num }}:{{ feeder_port }}/api/v1/prices" +{% endif %} +{% else %} +endpoint = "" +{% endif %} diff --git a/prepare/templates/client.toml.j2 b/prepare/templates/client.toml.j2 index 0485d17..d4ba1b4 100644 --- a/prepare/templates/client.toml.j2 +++ b/prepare/templates/client.toml.j2 @@ -11,7 +11,7 @@ chain-id = "{{ chain_id }}" keyring-backend = "os" # CLI output format (text|json) output = "text" -# : to Tendermint RPC interface for this chain +# : to CometBFT RPC interface for this chain node = "tcp://localhost:{{ rpc_port }}" -# Transaction broadcasting mode (sync|async|block) +# Transaction broadcasting mode (sync|async) broadcast-mode = "sync" diff --git a/prepare/templates/config.toml.j2 b/prepare/templates/config.toml.j2 index 7fc94c1..1fbff34 100644 --- a/prepare/templates/config.toml.j2 +++ b/prepare/templates/config.toml.j2 @@ -6,6 +6,10 @@ # "$HOME/.cometbft" by default, but could be changed via $CMTHOME env variable # or --home cmd flag. +# The version of the CometBFT binary that created or +# last modified the config file. Do not modify this. +version = "0.38.0" + ####################################################################### ### Main Base Config Options ### ####################################################################### @@ -17,14 +21,6 @@ proxy_app = "tcp://127.0.0.1:{{ abci_port }}" # A custom human readable name for this node moniker = "{{ moniker }}" -# If this node is many blocks behind the tip of the chain, BlockSync -# allows them to catchup quickly by downloading blocks in parallel -# and verifying their commits -# -# Deprecated: this key will be removed and BlockSync will be enabled -# unconditionally in the next major release. -block_sync = true - # Database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb # * goleveldb (github.com/syndtr/goleveldb - most popular implementation) # - pure go @@ -204,11 +200,9 @@ pprof_laddr = "127.0.0.1:{{ pprof_port }}" # Address to listen for incoming connections laddr = "tcp://0.0.0.0:{{ app_port }}" -# Address to advertise to peers for them to dial -# If empty, will use the same port as the laddr, -# and will introspect on the listener or use UPnP -# to figure out the address. ip and port are required -# example: 159.89.10.97:26656 +# Address to advertise to peers for them to dial. If empty, will use the same +# port as the laddr, and will introspect on the listener to figure out the +# address. IP and port are required. Example: 159.89.10.97:26656 external_address = "" # Comma separated list of seed nodes to connect to @@ -217,9 +211,6 @@ seeds = "" # Comma separated list of nodes to keep persistent connections to persistent_peers = "{{ persistent_peers }}" -# UPNP port forwarding -upnp = false - # Path to address book addr_book_file = "config/addrbook.json" @@ -275,13 +266,24 @@ dial_timeout = "3s" ####################################################### [mempool] -# Mempool version to use: -# 1) "v0" - (default) FIFO mempool. -# 2) "v1" - prioritized mempool (deprecated; will be removed in the next release). -version = "v0" - +# Recheck (default: true) defines whether CometBFT should recheck the +# validity for all remaining transaction in the mempool after a block. +# Since a block affects the application state, some transactions in the +# mempool may become invalid. If this does not apply to your application, +# you can disable rechecking. recheck = true + +# Broadcast (default: true) defines whether the mempool should relay +# transactions to other peers. Setting this to false will stop the mempool +# from relaying transactions to other peers until they are included in a +# block. In other words, if Broadcast is disabled, only the peer you send +# the tx to will see it until it is included in a block. broadcast = true + +# WalPath (default: "") configures the location of the Write Ahead Log +# (WAL) for the mempool. The WAL is disabled by default. To enable, set +# WalPath to where you want the WAL to be written (e.g. +# "data/mempool.wal"). wal_dir = "" # Maximum number of transactions in the mempool @@ -309,22 +311,6 @@ max_tx_bytes = 1048576 # XXX: Unused due to https://github.com/tendermint/tendermint/issues/5796 max_batch_bytes = 0 -# ttl-duration, if non-zero, defines the maximum amount of time a transaction -# can exist for in the mempool. -# -# Note, if ttl-num-blocks is also defined, a transaction will be removed if it -# has existed in the mempool at least ttl-num-blocks number of blocks or if it's -# insertion time into the mempool is beyond ttl-duration. -ttl-duration = "0s" - -# ttl-num-blocks, if non-zero, defines the maximum number of blocks a transaction -# can exist for in the mempool. -# -# Note, if ttl-duration is also defined, a transaction will be removed if it -# has existed in the mempool at least ttl-num-blocks number of blocks or if -# it's insertion time into the mempool is beyond ttl-duration. -ttl-num-blocks = 0 - ####################################################### ### State Sync Configuration Options ### ####################################################### diff --git a/prepare/templates/feeder.toml.j2 b/prepare/templates/feeder.toml.j2 index 4cde77e..4e5e181 100644 --- a/prepare/templates/feeder.toml.j2 +++ b/prepare/templates/feeder.toml.j2 @@ -1,36 +1,11 @@ -gas_adjustment = 1.7 -gas_prices = "0.00125ukuji" -enable_server = true -enable_voter = true - -history_db = "/root/feeder.db" +history_db = "/feeder/feeder.db" [server] -listen_addr = "localhost:{{ feeder_port }}" +listen_addr = "0.0.0.0:{{ feeder_port }}" read_timeout = "20s" verbose_cors = true write_timeout = "20s" -[rpc] -rpc_timeout = "100ms" -{% if podman %} -grpc_endpoint = "127.0.0.1:{{ grpc_port }}" -tmrpc_endpoint = "http://127.0.0.1:{{ rpc_port }}" -{% else %} -grpc_endpoint = "{{ moniker | lower }}:{{ grpc_port }}" -tmrpc_endpoint = "http://{{ moniker | lower }}:{{ rpc_port }}" -{% endif %} - -[account] -address = "{{ address }}" -chain_id = "{{ chain_id }}" -validator = "{{ valoper }}" -prefix = "kujira" - -[keyring] -backend = "memory" -dir = "/root" - [[deviation_thresholds]] base = "USDT" threshold = "2" diff --git a/versions.yml b/versions.yml new file mode 100644 index 0000000..b4f2b27 --- /dev/null +++ b/versions.yml @@ -0,0 +1,19 @@ +pond: + v0.1.0: + kujira: v0.9.3-1 + feeder: v0.11.0 + relayer: v2.5.0 + oracle: + kujira: 0fa65a9 + feeder: api-only + relayer: v2.5.0 + +go: + kujira: + v0.9.3-1: 1.20.2 + 0fa65a9: 1.21.3 + feeder: + v0.11.0: 1.20.2 + api-only: 1.21.3 + relayer: + v2.5.0: 1.21.7 \ No newline at end of file From eb5a535e44869307b5dd1095053af3096aca6248 Mon Sep 17 00:00:00 2001 From: Starsquid <108214377+starsquidnodes@users.noreply.github.com> Date: Fri, 16 Feb 2024 14:26:06 +0100 Subject: [PATCH 2/7] Update versions --- build.py | 3 ++- versions.yml | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/build.py b/build.py index 000b832..dfce1ca 100755 --- a/build.py +++ b/build.py @@ -115,7 +115,8 @@ def main(): if not args.manifest: return - if not args.archs: + archs = args.archs + if not archs: archs = platform.machine() if archs == "x86_64": diff --git a/versions.yml b/versions.yml index b4f2b27..58a643a 100644 --- a/versions.yml +++ b/versions.yml @@ -4,16 +4,16 @@ pond: feeder: v0.11.0 relayer: v2.5.0 oracle: - kujira: 0fa65a9 + kujira: b5777ec feeder: api-only - relayer: v2.5.0 + relayer: v2.5.1 go: kujira: v0.9.3-1: 1.20.2 - 0fa65a9: 1.21.3 + b5777ec: 1.21.3 feeder: v0.11.0: 1.20.2 api-only: 1.21.3 relayer: - v2.5.0: 1.21.7 \ No newline at end of file + v2.5.1: 1.21.7 \ No newline at end of file From 962840fdb34d85a1ecd0f40e964ca4d30bd7b8eb Mon Sep 17 00:00:00 2001 From: Starsquid <108214377+starsquidnodes@users.noreply.github.com> Date: Wed, 21 Feb 2024 23:26:18 +0100 Subject: [PATCH 3/7] Add deployer account, pre-import keys --- prepare/mnemonics.json | 3 ++- prepare/prepare.py | 52 +++++++++++++++++++++--------------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/prepare/mnemonics.json b/prepare/mnemonics.json index f793ed4..b8056ca 100644 --- a/prepare/mnemonics.json +++ b/prepare/mnemonics.json @@ -22,5 +22,6 @@ "index light average senior silent limit usual local involve delay update rack cause inmate wall render magnet common feature laundry exact casual resource hundred", "prefer forget visit mistake mixture feel eyebrow autumn shop pair address airport diesel street pass vague innocent poem method awful require hurry unhappy shoulder" ], - "relayer": "two detail rare vivid evil essence almost similar tattoo thing rabbit thought alert brick snap tumble dust sail eternal vapor aim finger paddle silver" + "relayer": "two detail rare vivid evil essence almost similar tattoo thing rabbit thought alert brick snap tumble dust sail eternal vapor aim finger paddle silver", + "deployer": "box tail afraid victory scout bottom later indoor wave exhaust hybrid try artist narrow room pudding idle human again pupil method can tomorrow taxi" } \ No newline at end of file diff --git a/prepare/prepare.py b/prepare/prepare.py index 705106f..ae90f8c 100755 --- a/prepare/prepare.py +++ b/prepare/prepare.py @@ -35,14 +35,14 @@ def init(self): return output["node_id"] - def add_key(self, mnemonic, dryrun=False): + def add_key(self, name, mnemonic, dryrun=False): extra = [] if dryrun: extra = ["--dry-run"] result = subprocess.run([ self.binary, "--output", "json", - "--home", self.home, "keys", "add", "validator", + "--home", self.home, "keys", "add", name, "--keyring-backend", "test", "--recover" ] + extra, capture_output=True, text=True, input=mnemonic) @@ -106,18 +106,18 @@ def get_persistent_peers(validators, node_id, podman): return persistent_peers -def init_chain(name, chain_id, binary, denom, nodes, port_prefix, mnemonics, podman): +def init_chain(prefix, chain_id, binary, denom, nodes, port_prefix, mnemonics, podman): info = { "validators": [], - "accounts": [] + "accounts": {} } total = 0 - main = Node(f"{name}1", id, binary, denom, port_prefix) + main = Node(f"{prefix}1", id, binary, denom, port_prefix) for i in range(nodes): - moniker = f"{name}{i+1}" + moniker = f"{prefix}{i+1}" mnemonic = mnemonics["validators"][i] amount = 300_000_000_000 @@ -127,7 +127,7 @@ def init_chain(name, chain_id, binary, denom, nodes, port_prefix, mnemonics, pod node = Node(moniker, chain_id, binary, denom, port_prefix) node_id = node.init() - address = node.add_key(mnemonic) + address = node.add_key("validator", mnemonic) node.add_genesis_account(address, amount) valoper = node.create_gentx(staked) @@ -155,37 +155,38 @@ def init_chain(name, chain_id, binary, denom, nodes, port_prefix, mnemonics, pod shutil.copyfile( f"{gentx_path}/{gentx_file}", - f"{WORKDIR}/{name}1/config/gentx/{gentx_file}" + f"{WORKDIR}/{prefix}1/config/gentx/{gentx_file}" ) for i in range(0, len(mnemonics.get("accounts", []))): + name = f"test{i}" mnemonic = mnemonics["accounts"][i] amount = 10_000_000_000_000 total += amount - address = main.add_key(mnemonic, True) + address = main.add_key(name, mnemonic) main.add_genesis_account(address, amount) - info["accounts"].append({ + info["accounts"][name] = { "address": address, "mnemonic": mnemonic - }) - - # add ibc account + } - mnemonic = mnemonics["relayer"] + # add special accounts + for name in ["relayer", "deployer"]: + mnemonic = mnemonics[name] - amount = 1_000_000_000_000 - total += amount + amount = 1_000_000_000_000 + total += amount - address = main.add_key(mnemonic, True) - main.add_genesis_account(address, amount) + address = main.add_key(name, mnemonic) + main.add_genesis_account(address, amount) - info["ibc"] = { - "address": address, - "mnemonic": mnemonic - } + info["accounts"][name] = { + "address": address, + "mnemonic": mnemonic + } main.collect_gentxs() @@ -209,7 +210,7 @@ def init_chain(name, chain_id, binary, denom, nodes, port_prefix, mnemonics, pod ] for i in range(nodes): - filename = f"{WORKDIR}/{name}{i+1}/config/genesis.json" + filename = f"{WORKDIR}/{prefix}{i+1}/config/genesis.json" json.dump(genesis, open(filename, "w")) app_toml = jinja2.Template( @@ -228,7 +229,7 @@ def init_chain(name, chain_id, binary, denom, nodes, port_prefix, mnemonics, pod for i, validator in enumerate(info["validators"]): # validator nodes - home = f"{WORKDIR}/{name}{i+1}" + home = f"{WORKDIR}/{prefix}{i+1}" node_id = validator["node_id"] @@ -336,8 +337,7 @@ def main(): "pond-2": info2["validators"] } - config["wallets"] = info1["accounts"] - config["ibc"] = info1["ibc"] + config["accounts"] = info1["accounts"] # init relayer From b1c280a7d8ce2ea4a6dd40047e3087f7aae261c8 Mon Sep 17 00:00:00 2001 From: Starsquid <108214377+starsquidnodes@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:38:53 +0100 Subject: [PATCH 4/7] Add automatic dev build version --- build.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/build.py b/build.py index dfce1ca..6d7e119 100755 --- a/build.py +++ b/build.py @@ -5,13 +5,14 @@ import subprocess import sys import yaml +import datetime as dt def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("-n", "--namespace", default="teamkujira") parser.add_argument("-a", "--app") - parser.add_argument("-t", "--tag", required=True) + parser.add_argument("-t", "--tag") parser.add_argument("--push", action="store_true") parser.add_argument("--podman", action="store_true") parser.add_argument("--manifest", action="store_true") @@ -23,6 +24,18 @@ def build_arg(s): return ["--build-arg", s] +def get_version(versions, name, tag): + versions = versions["pond"] + version = versions.get(tag, {}).get(name) + if not version: + version = versions["latest"].get(name) + + if not version: + raise f"no version found for {name}" + + return version + + def build(command, namespace, app, tag, versions, push=False): arch = platform.machine() @@ -43,13 +56,14 @@ def build(command, namespace, app, tag, versions, push=False): cmd += build_arg(f"go_version={go_version}") if app == "prepare": - kujira_version = versions["pond"][tag]["kujira"] - feeder_version = versions["pond"][tag]["feeder"] - relayer_version = versions["pond"][tag]["relayer"] + kujira_version = get_version(versions, "kujira", tag) + feeder_version = get_version(versions, "feeder", tag) + relayer_version = get_version(versions, "relayer", tag) cmd += build_arg(f"kujira_version={kujira_version}") cmd += build_arg(f"feeder_version={feeder_version}") cmd += build_arg(f"relayer_version={relayer_version}") + cmd += build_arg(f"prepare_version={tag}") cmd += build_arg(f"namespace={namespace}") cmd += build_arg(f"arch={arch}") @@ -93,6 +107,11 @@ def main(): versions = yaml.safe_load(open("versions.yml", "r")) + pond_version = args.tag + if not pond_version: + pond_version = dt.datetime.utcnow().strftime("%Y%m%d%H%M%S") + versions["pond"][pond_version] = versions["pond"]["latest"] + command = "docker" if args.podman: command = "podman" @@ -102,12 +121,12 @@ def main(): apps = [args.app] for app in apps: - tag = versions["pond"][args.tag].get(app) + tag = versions["pond"][pond_version].get(app) if app == "prepare": - tag = args.tag + tag = pond_version if not tag: - print(f"no tag defined for {app} ({args.tag})") + print(f"no tag defined for {app} ({pond_version})") sys.exit(1) build(command, args.namespace, app, tag, versions, args.push) From 7664e4adf756dbb961567dc2b1ee9216c9d6ff20 Mon Sep 17 00:00:00 2001 From: Starsquid <108214377+starsquidnodes@users.noreply.github.com> Date: Wed, 6 Mar 2024 14:40:07 +0100 Subject: [PATCH 5/7] Add contract deployment --- contracts/Dockerfile | 16 - contracts/contracts.py | 266 ------------- contracts/params/add_orca_queue.json | 12 - contracts/params/add_usk_collateral.json | 15 - prepare/Dockerfile | 27 +- prepare/contracts/README.md | 69 ++++ prepare/contracts/deploy.py | 469 +++++++++++++++++++++++ prepare/contracts/plans/kujira.yml | 128 +++++++ prepare/contracts/registry.yml | 45 +++ prepare/prepare.py | 19 +- prepare/templates/client.toml.j2 | 6 +- prepare/templates/feeder.toml.j2 | 5 + relayer/Dockerfile | 3 +- versions.yml | 15 +- 14 files changed, 762 insertions(+), 333 deletions(-) delete mode 100644 contracts/Dockerfile delete mode 100755 contracts/contracts.py delete mode 100644 contracts/params/add_orca_queue.json delete mode 100644 contracts/params/add_usk_collateral.json create mode 100644 prepare/contracts/README.md create mode 100755 prepare/contracts/deploy.py create mode 100644 prepare/contracts/plans/kujira.yml create mode 100644 prepare/contracts/registry.yml diff --git a/contracts/Dockerfile b/contracts/Dockerfile deleted file mode 100644 index 7175d80..0000000 --- a/contracts/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -ARG kujira_version -FROM teamkujira/kujirad:${kujira_version} - -RUN apt-get update && \ - apt-get install -y --no-install-recommends python3 && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - -COPY contracts.py /usr/local/bin/ - -RUN chmod +x /usr/local/bin/* - -WORKDIR /tmp - -ENTRYPOINT ["contracts.py"] - diff --git a/contracts/contracts.py b/contracts/contracts.py deleted file mode 100755 index 2fb7268..0000000 --- a/contracts/contracts.py +++ /dev/null @@ -1,266 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import requests -import base64 -import subprocess -import json -import hashlib -import time -import logging -import sys - - -class Chain: - def __init__(self, binary, chain_id): - self.binary = binary - self.chain_id = chain_id - - self.accounts = {} - self.denoms = {} - self.contracts = {} - - # get address - result = subprocess.run([ - binary, "keys", "show", "validator", "-a", "--keyring-backend", "test", "--home", "/Users/christian/.pond/kujira01" - ], text=True, capture_output=True) - - self.address = result.stdout.strip() - - result = self.q("auth accounts") - for account in result["accounts"]: - name = account.get("name") - if not name: - continue - - self.accounts[name] = account["base_account"]["address"] - - def q(self, args): - cmd = [self.binary, "--home", "/Users/christian/.pond/kujira01", "q"] + args.split() + [ - "--node", "http://127.0.0.1:10157", - "--chain-id", self.chain_id, "--output", "json" - ] - result = subprocess.run(cmd, capture_output=True, text=True) - - if result.returncode == 0: - return json.loads(result.stdout) - else: - print(result.stderr) - return None - - def tx(self, args): - time.sleep(8) - cmd = [self.binary, "--home", "/Users/christian/.pond/kujira01", "tx"] + args.split() + [ - "--node", "http://127.0.0.1:10157", - "--chain-id", self.chain_id, "--output", "json", - "--from", "validator", "--keyring-backend", "test", - "--gas", "auto", "--gas-adjustment", "2", "--yes", - "-b", "async" - ] - result = subprocess.run(cmd, capture_output=True, text=True) - - if result.returncode != 0: - print(result.stderr) - print(result.stdout) - return None - - return json.loads(result.stdout) - - def wait_for(self, tx, timeout=10): - print(f"wait for tx: {tx}") - result = None - while result == None and timeout > 0: - print(timeout) - time.sleep(1) - timeout -= 1 - result = self.q(f"tx {tx}") - - # if result.returncode != 0: - # print(result) - - def create_denom(self, denom): - logging.info(f"create {denom}") - - path = f"factory/{self.address}/u{denom}".lower() - self.denoms[path] = denom - - return { - "@type": "/kujira.denom.MsgCreateDenom", - "sender": self.address, - "nonce": f"u{denom}".lower() - } - - def instantiate_fin_contract(self, denom1, denom2, precision=4, delta=0): - logging.info(f"Instantiate Fin pair {denom1}/{denom2}") - - path1 = "" - path2 = "" - for path, denom in self.denoms.items(): - if denom1 == denom: - path1 = path - if denom2 == denom: - path2 = path - - code_id = 2 - label = f"FIN pair {denom1}/{denom2}" - - return { - "@type": "/cosmwasm.wasm.v1.MsgInstantiateContract", - "sender": self.address, - "admin": self.address, - "code_id": f"{code_id}", - "label": label, - "msg": { - "owner": self.address, - "denoms": [ - {"native": path1}, - {"native": path2} - ], - "price_precision": { - "decimal_places": 4 - }, - "decimal_delta": 0, - "fee_taker": "0.0015", - "fee_maker": "0.00075" - }, - "funds": [] - } - - def instantiate_orca_queue(self, source, collateral, denom): - params = { - # "bid_denom": f"factory/{self.address}/uusk", - "bid_denom": denom, - "bid_threshold": "10000000000", - # "collateral_denom": f"factory/{self.address}/usquid", - "collateral_denom": collateral, - "fee_address": self.accounts["fee_collector"], - "liquidation_fee": "0.01", - "max_slot": 30, - "owner": self.address, - "premium_rate_per_slot": "0.01", - "waiting_period": 600, - "withdrawal_fee": "0.005" - } - - def send(self, messages): - tx = { - "body": { - "messages": messages, - "memo": "", - "timeout_height": "0", - "extension_options": [], - "non_critical_extension_options": [] - }, - "auth_info": { - "signer_infos": [], - "fee": { - "amount": [], - "gas_limit": "1000000000", - "payer": "", - "granter": "" - }, - "tip": None - }, - "signatures": [] - } - - tx_file = "/tmp/tx.json" - - open(tx_file, "w").write(json.dumps(tx)) - - result = self.tx(f"sign {tx_file}") - - if not result: - return - - open(f"{tx_file}.signed", "w").write(json.dumps(result)) - - result = self.tx(f"broadcast {tx_file}.signed") - - self.wait_for(result["txhash"]) - - def get_contracts(self): - contracts = {} - result = self.q("wasm list-codes") - for index in range(len(result["code_infos"])): - id = index + 1 - contracts[id] = {} - result = self.q(f"wasm list-contract-by-code {id}") - for contract in result["contracts"]: - result = self.q(f"wasm contract {contract}") - contracts[id][contract] = result["contract_info"]["label"] - - print(json.dumps(contracts, indent=2)) - - -def parse_args(): - parser = argparse.ArgumentParser() - parser.add_argument( - "--rpc", default="https://api-kujira.starsquid.io") - # "--rpc", default="https://rest.cosmos.directory/kujira") - return parser.parse_args() - - -def main(): - args = parse_args() - - kujira = Chain( - "kujirad-v0.8.8-43-gc5a82f3", "pond-1") - - contracts = [{ - "name": "usk_market", - "id": 73 - }, { - "name": "fin", - "id": 134 - }] - - kujira.get_contracts() - - result = kujira.q("wasm list-codes") - if result["code_infos"]: - # contracts already deployed, exit - sys.exit(0) - - messages = [] - - for contract in contracts: - id = contract["id"] - url = f"{args.rpc}/cosmwasm/wasm/v1/code/{id}" - response = requests.get(url) - - print(url) - - if response.status_code != 200: - print(f"response status: {response.status_code}") - sys.exit(1) - - data = response.json().get("data") - if not data: - raise "data is None" - - bz = base64.b64decode(data) - - messages.append({ - "@type": "/cosmwasm.wasm.v1.MsgStoreCode", - "sender": kujira.address, - "wasm_byte_code": data, - "instantiate_permission": None - }) - - for denom in ["USK", "SQUID"]: - messages.append(kujira.create_denom(denom)) - - print("Deploy contracts") - # kujira.send(messages) - - print(kujira.denoms) - - messages = [kujira.instantiate_fin_contract("SQUID", "USK")] - - print("Instantiate contracts") - kujira.send(messages) - - -if __name__ == "__main__": - main() diff --git a/contracts/params/add_orca_queue.json b/contracts/params/add_orca_queue.json deleted file mode 100644 index 42ba92d..0000000 --- a/contracts/params/add_orca_queue.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "bid_denom": "factory/kujira1qk00h5atutpsv900x202pxx42npjr9thg58dnqpa72f2p7m2luase444a7/uusk", - "bid_threshold": "10000000000", - "collateral_denom": "ibc/4F393C3FCA4190C0A6756CE7F6D897D5D1BE57D6CCB80D0BC87393566A7B6602", - "fee_address": "kujira17xpfvakm2amg962yls6f84z3kell8c5lp3pcxh", - "liquidation_fee": "0.01", - "max_slot": 30, - "owner": "kujira1tsekaqv9vmem0zwskmf90gpf0twl6k57e8vdnq", - "premium_rate_per_slot": "0.01", - "waiting_period": 600, - "withdrawal_fee": "0.005" -} \ No newline at end of file diff --git a/contracts/params/add_usk_collateral.json b/contracts/params/add_usk_collateral.json deleted file mode 100644 index 2659904..0000000 --- a/contracts/params/add_usk_collateral.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "owner": "kujira1knlrgd8l99sz7lyzsp4lkp5gaduak50rmhnp7s", - "stable_denom": "factory/kujira1knlrgd8l99sz7lyzsp4lkp5gaduak50rmhnp7s/uusk", - "stable_denom_admin": "kujira1qk00h5atutpsv900x202pxx42npjr9thg58dnqpa72f2p7m2luase444a7", - "collateral_denom": "ibc/A7A5C44AA67317F1B3FFB27BAFC89C9CC04F61306F6F834F89A74B8F82D252A1", - "collateral_denom_decimal_delta": 0, - "oracle_denom": "SOMM", - "max_ratio": "0.5", - "mint_fee": "0.001", - "interest_rate": "0.01", - "orca_address": "kujira1tsekaqv9vmem0zwskmf90gpf0twl6k57e8vdnq", - "max_debt": "250000000000", - "liquidation_threshold": "1000000000", - "liquidation_ratio": "0.1" -} \ No newline at end of file diff --git a/prepare/Dockerfile b/prepare/Dockerfile index b6931ff..6cb4878 100644 --- a/prepare/Dockerfile +++ b/prepare/Dockerfile @@ -3,15 +3,16 @@ ARG kujira_version ARG arch FROM ${namespace}/kujira:${kujira_version}-${arch} -ARG kujira_version -ARG feeder_version -ARG relayer_version -ENV KUJIRA_VERSION=${kujira_version} -ENV FEEDER_VERSION=${feeder_version} -ENV RELAYER_VERSION=${relayer_version} - +WORKDIR /tmp RUN apt-get update && \ - apt-get install -y --no-install-recommends python3 python3-jinja2 && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates \ + python3 \ + python3-jinja2 \ + python3-mako \ + python3-requests \ + python3-yaml \ + && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -19,9 +20,19 @@ COPY prepare.py entrypoint.sh /usr/local/bin/ COPY templates /tmp/templates COPY keys /tmp/keys COPY mnemonics.json /tmp/mnemonics.json +COPY contracts /tmp/contracts RUN chmod +x /usr/local/bin/* +ARG kujira_version +ARG feeder_version +ARG relayer_version +ARG prepare_version +ENV KUJIRA_VERSION=${kujira_version} +ENV FEEDER_VERSION=${feeder_version} +ENV RELAYER_VERSION=${relayer_version} +ENV PREPARE_VERSION=${prepare_version} + WORKDIR /tmp ENTRYPOINT ["entrypoint.sh"] diff --git a/prepare/contracts/README.md b/prepare/contracts/README.md new file mode 100644 index 0000000..ea858e6 --- /dev/null +++ b/prepare/contracts/README.md @@ -0,0 +1,69 @@ +# contract deployer + +## Requirements + +- python3-mako +- python3-requests +- python3-yaml + +## Usage + +The deployment is a wrapper around and therefore needs a working kujirad binary as well as a funded wallet for the required transactions. It is designed for pond, but should also work on testnet (see `--help` for parameters) + +```sh +./deploy.py --node http://localhost:10157 plans/kujira.yml +``` + +### Registry + +The `registry.yml` provides a reference to the compiled code, as well as the expected hash. Currently only mainnet is available as source, more options will be added in the future. + +Example: + +```yaml +kujira_fin: + checksum: 8A6FA03E62DA9CB75F1CB9A4EFEA6AAFA920AD5FCA40A7B335560727BD42C198 + source: kaiyo-1://243 +``` + +### Plan files + +Plan files define the params and order of the contracts that should be deployed and denoms that need to be created. After each contract deployment, the deployer queries its address and `{"config":{}}` output which can then be referenced in later steps by the its deployment `name`. Denoms are handled at first and can be referenced as well. + +Already existing code, denoms and contracts won't be deployed or instantiated again. + +Example: + +```yml +denoms: + - name: KUJI + path: ukuji + - name: USK + nonce: uusk + +contracts: + - name: kujira_fin_kuji_usk + code: kujira_fin + label: FIN KUJI-USK + msg: + denoms: + - native: ${denoms.KUJI.path} + - native: ${denoms.USK.path} + price_precision: + decimal_places: 4 + decimal_delta: 0 + fee_taker: "0.0015" + fee_maker: "0.00075" + + - name: kujira_bow_kuji_usk + code: kujira_bow_xyk + label: BOW KUJI-USK + funds: 10000000ukuji + msg: + fin_contract: ${contracts.kujira_fin_kuji_usk.address} + intervals: + - "0.01" + - "0.05" + fee: "0.1" + amp: "1" +``` diff --git a/prepare/contracts/deploy.py b/prepare/contracts/deploy.py new file mode 100755 index 0000000..3904a78 --- /dev/null +++ b/prepare/contracts/deploy.py @@ -0,0 +1,469 @@ +#!/usr/bin/env python3 + +import argparse +import subprocess +import os +import hashlib +import json +import yaml +import logging +import sys +import requests +from mako.template import Template +import base64 +import time +from urllib.parse import urlparse +import glob + + +class Class: + def __init__(self, d): + for k, v in d.items(): + if isinstance(v, dict): + v = Class(v) + setattr(self, k, v) + + +class Contract(): + code = None + label = None + msg = None + funds = None + + def __init__(self, definition): + for k, v in definition.items(): + setattr(self, k, v) + + +class Code: + def __init__(self, source, hash=None): + self.hash = hash + self.code = None + self.source = source + + +class Deployer: + def __init__(self, binary, chain_id, wallet, home, api_host, node): + debug("init deployer", binary=binary, chain_id=chain_id) + + self.binary = binary + self.chain_id = chain_id + self.wallet = wallet + + self.home = home + self.node = node + self.tmpdir = "/tmp" + + api_host = api_host.replace("https://", "") + self.api_url = f"https://{api_host}" + + self.codes = {} + self.set_codes() + + # map of all available code ids deployed in $chain_id + # {data_hash: code_id} + self.code_ids = {} + self.update_code_ids() + + self._set_wallet_address() + + self.denoms = {} + self.contracts = {} + + def _set_wallet_address(self): + command = [ + self.binary, "--home", self.home, "--keyring-backend", "test", + "keys", "show", self.wallet, "-a" + ] + + debug(" ".join(command)) + + result = subprocess.run(command, capture_output=True, text=True) + + address = result.stdout + if not address: + error("couldn't get address", wallet=self.wallet) + + self.address = address + + def set_codes(self): + dirname = os.path.dirname(__file__) + filename = f"{dirname}/registry.yml" + + registry = yaml.safe_load(open(filename, "r").read()) + for name, values, in registry.items(): + source = values.get("source") + if not source: + continue + + hash = values.get("checksum") + + self.codes[name] = Code(source, hash) + + def handle_code(self, name): + debug("handle code", name=name) + code = self.codes.get(name) + if not code: + error("code not registered", name=name) + + if not code.code: + debug("code missing") + + parsed = urlparse(code.source) + + if parsed.scheme == "kaiyo-1": + code_id = parsed.netloc + if not code_id: + error("code id missing", source=code.source) + url = f"{self.api_url}/cosmwasm/wasm/v1/code/{code_id}" + + info("download code from mainnet", id=code_id, name=name) + debug(url=url) + + response = requests.get(url) + response.raise_for_status() + data = response.json() + + code_hash = data["code_info"]["data_hash"] + + filename = f"{self.tmpdir}/{name}.wasm" + open(filename, "wb").write( + base64.b64decode(data["data"]) + ) + self.codes[name].hash = code_hash + self.codes[name].source = filename + + code_id = self.code_ids.get(code_hash) + if code_id: + message = f"code already deployed on {self.chain_id}" + info(message, id=code_id) + self.codes[name].id = code_id + return + + self.deploy_code(name) + + return self.codes[name].hash + + def get_deployed_codes(self): + codes = {} + for name, code in self.codes.items(): + if not code.hash: + continue + id = self.code_ids.get(code.hash) + if not id: + continue + codes[name] = { + "hash": code.hash, + "id": id + } + + return codes + + def deploy_code(self, name): + info("deploy code", name=name) + code = self.codes[name] + + self.tx(f"wasm store {code.source}") + self.code_ids[code.hash] = str(len(self.code_ids) + 1) + + def update_code_ids(self): + code_infos = [] + next_key = "dummy" + + while next_key: + result = self.q("wasm list-code") + code_infos += result["code_infos"] + next_key = result["pagination"]["next_key"] + + for info in code_infos: + self.code_ids[info["data_hash"]] = info["code_id"] + + def q(self, args, extra=True, parse_json=True, ignore_errors=False): + command = [ + self.binary, "--home", self.home, "query" + ] + args.split() + + if extra: + command += [ + "--chain-id", self.chain_id, "--output", "json", + ] + + if self.node: + command += ["--node", self.node] + + debug(" ".join(command)) + + result = subprocess.run(command, capture_output=True, text=True) + + if result.returncode == 0: + if not parse_json: + return result.stdout.strip() + + return json.loads(result.stdout) + else: + if ignore_errors: + return None + error(result.stderr) + + def tx(self, args, ignore_errors=False): + if isinstance(args, str): + args = args.split() + + command = [ + self.binary, "--home", self.home, "tx" + ] + args + [ + "--chain-id", self.chain_id, "--output", "json", + "--from", self.wallet, "--keyring-backend", "test", + "--gas", "auto", "--gas-adjustment", "2", "--yes", + ] + + if self.node: + command += ["--node", self.node] + + debug("send transaction", command=" ".join(command)) + + result = subprocess.run(command, capture_output=True, text=True) + + if result.returncode != 0: + if ignore_errors: + return None + else: + error(result.stderr) + + result = json.loads(result.stdout) + debug(result=result) + txhash = result.get("txhash") + if txhash: + self.wait_for(txhash) + + return result + + def wait_for(self, txhash): + debug("wait for tx", hash=txhash) + interval = 0.5 + + for _ in range(20): + time.sleep(interval) + result = self.q(f"tx {txhash}", ignore_errors=True) + if result: + return + + def compute_salt(self, contract): + code = self.codes[contract.code] + code_id = self.code_ids[code.hash] + string = f"{self.address} {code_id} {contract.label}" + salt = hashlib.sha256(string.encode("utf-8")).hexdigest() + return salt + + def build_address(self, contract: Contract): + self.handle_code(contract.code) + + salt = self.compute_salt(contract) + + code = self.codes.get(contract.code) + if not code: + error(f"code not found", name=contract.code) + + code_hash = code.hash + + query = f"wasm build-address {code_hash} {self.address} {salt}" + return self.q(query, False, False) + + def get_contract_config(self, address): + debug("get contract config", address=address) + query = '{"config":{}}' + response = self.q( + f"wasm contract-state smart {address} {query}", ignore_errors=True + ) + if response: + config = response.get("data") + config["address"] = address + return config + + return None + + def instantiate_contract(self, contract): + if not contract.msg.get("owner"): + contract.msg["owner"] = self.address + + info("instantiate contract", name=contract.code, label=contract.label) + debug(msg=contract.msg) + code = self.codes[contract.code] + code_id = self.code_ids[code.hash] + salt = self.compute_salt(contract) + args = json.dumps(contract.msg) + + command = [ + "wasm", "instantiate2", code_id, args, salt, + "--label", contract.label, "--admin", self.address + ] + + if contract.funds: + command += ["--amount", contract.funds] + + self.tx(command) + + def create_denom(self, nonce): + info("create denom", nonce=nonce) + self.tx(f"denom create-denom {nonce}", ignore_errors=True) + return f"factory/{self.address}/{nonce}" + + def handle_denom(self, params): + debug("handle denom", name=params["name"]) + name = params.get("name") + if not name: + error("name not found") + + path = params.get("path") + if not path: + nonce = params.get("nonce") + if not nonce: + error(f"no path or nonce found for {name}") + + path = self.create_denom(nonce) + + self.denoms[name] = {"name": name, "path": path} + + def handle_contract(self, definition): + code = definition["code"] + name = definition["name"] + debug("handle contract", code=code) + template = Template(json.dumps(definition)) + params = { + "denoms": Class(self.denoms), + "contracts": Class(self.contracts), + } + + rendered = template.render(**params) + contract = Contract(json.loads(rendered)) + + # generate address + address = self.build_address(contract) + + # query address + config = self.get_contract_config(address) + + if config: + info("contract already exists", code=code, address=address) + debug(config=config) + self.add_contract(name, config, address) + return + + debug("contract not found", address=address) + + self.instantiate_contract(contract) + config = self.get_contract_config(address) + self.add_contract(name, config, address) + + def add_contract(self, name, config, address): + config["address"] = address + self.contracts[name] = config + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("planfile", nargs="*") + parser.add_argument("-d", "--debug", action="store_true") + parser.add_argument("-b", "--binary", default="kujirad") + parser.add_argument("--chain-id", default="pond-1") + parser.add_argument("--wallet", default="deployer") + parser.add_argument("--home", default="~/.pond/kujira1-1") + parser.add_argument("--node") + parser.add_argument("--api-host", + default="rest.cosmos.directory/kujira") + parser.add_argument("--pond-json", default="~/.pond/pond.json") + return parser.parse_args() + + +def error(message=None, **kwargs): + log(message, logging.ERROR, kwargs) + sys.exit(1) + + +def warning(message=None, **kwargs): + log(message, logging.WARNING, kwargs) + + +def debug(message=None, **kwargs): + log(message, logging.DEBUG, kwargs) + + +def info(message=None, **kwargs): + log(message, logging.INFO, kwargs) + + +def log(message, level, kwargs): + tags = [] + for k, v in kwargs.items(): + tags.append(f"{k}={v}") + + tags = ", ".join(tags) + + if message: + message = f"{message} {tags}" + else: + message = tags + + logging.log(level, message) + + +def main(): + args = parse_args() + + log_level = logging.INFO + if args.debug: + log_level = logging.DEBUG + + logging.basicConfig( + level=log_level, + format="%(levelname)s %(message)s" + ) + + logging.addLevelName(logging.DEBUG, "DBG") + logging.addLevelName(logging.INFO, "INF") + logging.addLevelName(logging.WARNING, "WRN") + logging.addLevelName(logging.ERROR, "ERR") + + home = os.path.expanduser(args.home) + + deployer = Deployer( + args.binary, args.chain_id, args.wallet, home, args.api_host, args.node + ) + + planfiles = [] + + for string in args.planfile: + if os.path.isdir(string): + planfiles += glob.glob(f"{string}/*.yml") + elif os.path.isfile(string): + planfiles.append(string) + + for planfile in planfiles: + plan = yaml.safe_load(open(planfile, "r")) + + # reset contract config + deployer.contracts = {} + + for denom in plan.get("denoms"): + deployer.handle_denom(denom) + + for contract in plan.get("contracts"): + deployer.handle_contract(contract) + + if args.pond_json: + filename = os.path.expanduser(args.pond_json) + debug("update pond info", file=filename) + + if not os.path.isfile(filename): + warning("file not found", file=filename) + return + + codes = deployer.get_deployed_codes() + if codes: + data = json.load(open(filename, "r")) + data["codes"] = codes + json.dump(data, open(filename, "w")) + + +if __name__ == "__main__": + main() diff --git a/prepare/contracts/plans/kujira.yml b/prepare/contracts/plans/kujira.yml new file mode 100644 index 0000000..1bd0ac2 --- /dev/null +++ b/prepare/contracts/plans/kujira.yml @@ -0,0 +1,128 @@ +denoms: + - name: KUJI + path: ukuji + - name: USK + nonce: uusk + # Token for stable pairing + - name: USDC + nonce: uusdc + # Token with 18dp for testing decimal deltas + - name: ETH + nonce: aeth + # Token for LSD pairing + - name: stETH + nonce: asteth + +contracts: + - name: kujira_fin_kuji_usk + code: kujira_fin + label: FIN KUJI-USK + msg: + denoms: + - native: ${denoms.KUJI.path} + - native: ${denoms.USK.path} + price_precision: + decimal_places: 4 + decimal_delta: 0 + fee_taker: "0.0015" + fee_maker: "0.00075" + + - name: kujira_bow_kuji_usk + code: kujira_bow_xyk + label: BOW KUJI-USK + funds: 10000000ukuji + msg: + fin_contract: ${contracts.kujira_fin_kuji_usk.address} + intervals: + - "0.01" + - "0.05" + fee: "0.1" + amp: "1" + + - name: kujira_fin_steth_eth + code: kujira_fin + label: FIN stETH-ETH + msg: + denoms: + - native: ${denoms.stETH.path} + - native: ${denoms.ETH.path} + price_precision: + decimal_places: 4 + decimal_delta: 0 + fee_taker: "0.0015" + fee_maker: "0.00075" + + - name: kujira_bow_steth_eth + code: kujira_bow_lsd + label: BOW stETH-ETH + funds: 10000000ukuji + msg: + fin_contract: ${contracts.kujira_fin_steth_eth.address} + adapter: + oracle: + - denom: STETH + decimals: 6 + - denom: ETH + decimals: 6 + strategy: + bid_fee: "0.005" + bid_factor: "0.001" + bid_utilization: "0.8" + bid_count: 20 + ask_fee: "0.001" + ask_utilization: "1" + + - name: kujira_fin_eth_usk + code: kujira_fin + label: FIN ETH-USK + msg: + denoms: + - native: ${denoms.ETH.path} + - native: ${denoms.USK.path} + price_precision: + decimal_places: 4 + decimal_delta: 12 + fee_taker: "0.0015" + fee_maker: "0.00075" + + - name: kujira_bow_eth_usk + code: kujira_bow_xyk + label: BOW ETH-USK + funds: 10000000ukuji + msg: + fin_contract: ${contracts.kujira_fin_eth_usk.address} + intervals: + - "0.01" + - "0.05" + fee: "0.1" + amp: "1" + + - name: kujira_fin_usdc_usk + code: kujira_fin + label: FIN USDC-USK + msg: + denoms: + - native: ${denoms.USDC.path} + - native: ${denoms.USK.path} + price_precision: + decimal_places: 6 + decimal_delta: 0 + fee_taker: "0.0015" + fee_maker: "0.00075" + + - name: kujira_bow_usdc_usk + code: kujira_bow_stable + label: BOW USDC-USK + funds: 10000000ukuji + msg: + fin_contract: ${contracts.kujira_fin_usdc_usk.address} + strategy: + target_price: "1" + ask_fee: "0.0005" + ask_factor: "0.001" + ask_utilization: "0.8" + ask_count: 15 + bid_fee: "0.0005" + bid_factor: "0.001" + bid_utilization: "0.8" + bid_count: 15 \ No newline at end of file diff --git a/prepare/contracts/registry.yml b/prepare/contracts/registry.yml new file mode 100644 index 0000000..4308378 --- /dev/null +++ b/prepare/contracts/registry.yml @@ -0,0 +1,45 @@ +calc: + checksum: DD9761B3E49D34BE18076CFA2BE7AC4F8B3AF65EB13CB8B24E38F9388130C2D2 + source: kaiyo-1://157 +kujira_bow_lsd: + checksum: 715934C48A41A7AEC050A6DEC9A1C957ABF96FFC84F7B8AD3260309749E9A7E9 + source: kaiyo-1://167 +kujira_bow_stable: + checksum: F3B812304F6AECEBD4CE388DA0F5161FD5BE1589C9008C5036258EC89ABCC502 + source: kaiyo-1://166 +kujira_bow_xyk: + checksum: 418CF9A2E005B6B44352DAFE1E8C5998F834F3BACF47497E95E69A3EB2DFAA22 + source: kaiyo-1://126 +kujira_bow_margin: + checksum: 5EEEAEC66D81449EEE7A687C47CBE53AD1571AA9F235CD019BF354F65F3C4610 + source: kaiyo-1://188 +kujira_bow_staking: + checksum: B081C828D3FC8FA658CA9906423BB90744336CCBA5363881A1CDB7449F716AC6 + source: kaiyo-1://244 +kujira_fin: + checksum: 8A6FA03E62DA9CB75F1CB9A4EFEA6AAFA920AD5FCA40A7B335560727BD42C198 + source: kaiyo-1://243 +kujira_ghost_market: + checksum: 575FCBB5F87A8ECBBB1A136D6082880B22F203AA39C51DE501F92CBCA3D86B43 + source: kaiyo-1://186 +kujira_ghost_vault: + checksum: 9FBCDAEFC596C285542DFA443CC33212A544CE4E191D1C5F1EF2F3AC4E2BB3C9 + source: kaiyo-1://140 +kujira_orca: + checksum: 64ECE3AB0E8CD640023FF81E6C7F5CF1C0C19D4CAFA419BD241976B6A714B2A7 + source: kaiyo-1://234 +kujira_pilot: + checksum: A178E33C489142FF5DE8A89261BB3CA86EEFC2FB382DCD57CBB6FB1531D55F46 + source: kaiyo-1://95 +# kujira_stable_margin_limit: +# checksum: abc... +# source: kaiyo-1:// +kujira_stable_margin_swap: + checksum: 2395063CC30A098DB87A7F275C7A8EBFF889E9D40DB18E09CED6921370351DC4 + source: kaiyo-1://87 +kujira_stable_market: + checksum: E9EC73285A3D9E1CED23924D3BA6EA9267B039DA596686EA7AEB1E474415DA13 + source: kaiyo-1://73 +kujira_stable_mint: + checksum: 98CC2EDAA8A5D1AD8CD15EADB6CA8A52267F29142177F105E3C65D0ECFF50C5F + source: kaiyo-1://11 \ No newline at end of file diff --git a/prepare/prepare.py b/prepare/prepare.py index ae90f8c..acd01d2 100755 --- a/prepare/prepare.py +++ b/prepare/prepare.py @@ -26,12 +26,12 @@ def __init__(self, name, chain_id, binary, denom, port_prefix): self.denom = denom def init(self): - result = subprocess.run([ + result = subprocess.check_output([ self.binary, "--home", self.home, "init", self.name, "--chain-id", self.chain_id, "--default-denom", self.denom - ], capture_output=True, text=True) + ], stderr=subprocess.STDOUT) - output = json.loads(result.stderr) + output = json.loads(result) return output["node_id"] @@ -58,11 +58,11 @@ def add_genesis_account(self, address, amount): ]) def create_gentx(self, amount): - subprocess.run([ + subprocess.check_call([ self.binary, "--output", "json", "--home", self.home, "genesis", "gentx", "validator", f"{amount}{self.denom}", "--chain-id", self.chain_id, "--keyring-backend", "test" - ], capture_output=True, text=True) + ], stderr=subprocess.DEVNULL) gentx_path = f"{self.home}/config/gentx" gentx_file = os.listdir(gentx_path)[0] @@ -72,9 +72,9 @@ def create_gentx(self, amount): return valoper def collect_gentxs(self): - subprocess.run([ + subprocess.check_output([ self.binary, "--home", self.home, "genesis", "collect-gentxs" - ], capture_output=False, text=True) + ], stderr=subprocess.DEVNULL) def parse_args(): @@ -114,7 +114,7 @@ def init_chain(prefix, chain_id, binary, denom, nodes, port_prefix, mnemonics, p total = 0 - main = Node(f"{prefix}1", id, binary, denom, port_prefix) + main = Node(f"{prefix}1", chain_id, binary, denom, port_prefix) for i in range(nodes): moniker = f"{prefix}{i+1}" @@ -314,7 +314,8 @@ def main(): "version": { "kujira": os.environ.get("KUJIRA_VERSION"), "feeder": os.environ.get("FEEDER_VERSION"), - "relayer": os.environ.get("RELAYER_VERSION") + "relayer": os.environ.get("RELAYER_VERSION"), + "prepare": os.environ.get("PREPARE_VERSION") } } diff --git a/prepare/templates/client.toml.j2 b/prepare/templates/client.toml.j2 index d4ba1b4..d09de64 100644 --- a/prepare/templates/client.toml.j2 +++ b/prepare/templates/client.toml.j2 @@ -12,6 +12,10 @@ keyring-backend = "os" # CLI output format (text|json) output = "text" # : to CometBFT RPC interface for this chain -node = "tcp://localhost:{{ rpc_port }}" +{% if podman %} +node = "tcp://127.0.0.1:{{ rpc_port }}" +{% else %} +node = "tcp://{{ moniker }}:{{ rpc_port }}" +{% endif %} # Transaction broadcasting mode (sync|async) broadcast-mode = "sync" diff --git a/prepare/templates/feeder.toml.j2 b/prepare/templates/feeder.toml.j2 index 4e5e181..739e744 100644 --- a/prepare/templates/feeder.toml.j2 +++ b/prepare/templates/feeder.toml.j2 @@ -20,6 +20,11 @@ base = "ETH" quote = "USDT" providers = ["binance", "bitget", "lbank", "mexc", "okx", "xt"] +[[currency_pairs]] +base = "STETH" +quote = "USDT" +providers = ["bybit", "okx", "bitget", "gate"] + [[currency_pairs]] base = "USDT" quote = "USD" diff --git a/relayer/Dockerfile b/relayer/Dockerfile index 56ae577..d52ae42 100644 --- a/relayer/Dockerfile +++ b/relayer/Dockerfile @@ -4,8 +4,9 @@ FROM golang:${go_version} AS build ARG relayer_version WORKDIR /build -RUN git clone --depth 1 --branch ${relayer_version} https://github.com/cosmos/relayer.git && \ +RUN git clone https://github.com/cosmos/relayer.git && \ cd relayer && \ + git checkout ${relayer_version} && \ make install WORKDIR /dist diff --git a/versions.yml b/versions.yml index 58a643a..ac551f7 100644 --- a/versions.yml +++ b/versions.yml @@ -1,19 +1,24 @@ pond: + latest: + kujira: 03985a2 + feeder: api-only + relayer: v2.5.1 v0.1.0: kujira: v0.9.3-1 feeder: v0.11.0 relayer: v2.5.0 - oracle: - kujira: b5777ec + v0.2.0: + kujira: 03985a2 feeder: api-only - relayer: v2.5.1 + relayer: v2.5.2 go: kujira: v0.9.3-1: 1.20.2 - b5777ec: 1.21.3 + 03985a2: 1.21.3 feeder: v0.11.0: 1.20.2 api-only: 1.21.3 relayer: - v2.5.1: 1.21.7 \ No newline at end of file + v2.5.1: 1.21.7 + v2.5.2: 1.21.7 \ No newline at end of file From 81560daa6a28e30275896604de631b8afbe3db19 Mon Sep 17 00:00:00 2001 From: Starsquid <108214377+starsquidnodes@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:45:08 +0100 Subject: [PATCH 6/7] Fix build script --- build.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build.py b/build.py index 6d7e119..6b8d32b 100755 --- a/build.py +++ b/build.py @@ -88,9 +88,6 @@ def manifest(command, namespace, app, tag, archs, push=False): fulltag = f"{namespace}/{app}:{tag}" commands = [] - print("------------------") - print(fulltag) - tags = [f"{fulltag}-{x}" for x in archs.split(",")] commands.append([command, "manifest", "create", f"{fulltag}"] + tags) @@ -132,7 +129,7 @@ def main(): build(command, args.namespace, app, tag, versions, args.push) if not args.manifest: - return + continue archs = args.archs if not archs: From fa5a091fc6054c507550996197a82e2ec37cc3c9 Mon Sep 17 00:00:00 2001 From: Starsquid <108214377+starsquidnodes@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:45:22 +0100 Subject: [PATCH 7/7] Fix address string --- prepare/contracts/deploy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prepare/contracts/deploy.py b/prepare/contracts/deploy.py index 3904a78..d8a75d3 100755 --- a/prepare/contracts/deploy.py +++ b/prepare/contracts/deploy.py @@ -80,7 +80,7 @@ def _set_wallet_address(self): result = subprocess.run(command, capture_output=True, text=True) - address = result.stdout + address = result.stdout.strip() if not address: error("couldn't get address", wallet=self.wallet) @@ -335,6 +335,7 @@ def handle_contract(self, definition): } rendered = template.render(**params) + contract = Contract(json.loads(rendered)) # generate address