From 4f319e5010f758726898e5e518d8fc1451e5cb15 Mon Sep 17 00:00:00 2001 From: SetarehGhorshi Date: Fri, 12 Jan 2024 08:11:20 -0500 Subject: [PATCH] new server --- docs/static/openapi.yml | 47 + i.py | 1235 +++++++++++++++++ proto/fairyring/pricefeed/googleapis | 1 + proto/fairyring/pricefeed/query.proto | 62 +- .../conditionalenc-tesnet/encrypter/encrypter | Bin 2602683 -> 2602683 bytes .../conditionalenc-tesnet/encrypter/main.go | 2 +- .../mnemonic-osmosis.txt | 2 +- testutil/conditionalenc-tesnet/readme.md | 271 ---- testutil/conditionalenc-tesnet/relayer.sh | 13 +- testutil/conditionalenc-tesnet/send-tx.sh | 50 +- testutil/conditionalenc-tesnet/start-fairy.sh | 4 +- testutil/conditionalenc/readme.md | 298 ---- testutil/osmosis-testnet/test-swap.sh | 4 +- testutil/swap-test/readme.md | 111 -- .../keeper/msg_server_submit_encrypted_tx.go | 23 +- x/conditionalenc/module.go | 4 +- x/pricefeed/keeper/grpc_query.go | 4 +- x/pricefeed/keeper/keeper.go | 40 +- x/pricefeed/types/query.pb.go | 10 +- x/pricefeed/types/query.pb.gw.go | 139 +- 20 files changed, 1490 insertions(+), 830 deletions(-) create mode 100644 i.py create mode 160000 proto/fairyring/pricefeed/googleapis delete mode 100644 testutil/conditionalenc-tesnet/readme.md delete mode 100644 testutil/conditionalenc/readme.md delete mode 100644 testutil/swap-test/readme.md diff --git a/docs/static/openapi.yml b/docs/static/openapi.yml index 0c93ae1a..02cd0b06 100644 --- a/docs/static/openapi.yml +++ b/docs/static/openapi.yml @@ -36525,6 +36525,53 @@ paths: type: string tags: - Query + /pricefeed/current_nonce/{denom}/{price}: + get: + summary: |- + get current nonce for a denom and price pair + operationId: PricefeedCurrentNonce + responses: + '200': + description: A successful response. + schema: + type: object + properties: + nonce: + description: |- + current nonce + type: string + description: >- + QueryCurrentNonceResponse is response type for the Query/CurrentNonce RPC + method. + default: + description: An unexpected error response. + schema: + type: object + properties: + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + '@type': + type: string + additionalProperties: {} + parameters: + - name: denom + in: path + required: true + type: string + - name: price + in: path + required: true + type: string + tags: + - Query /pricefeed/symbol_requests: get: summary: SymbolRequests queries all symbol requests. diff --git a/i.py b/i.py new file mode 100644 index 00000000..ee6d10be --- /dev/null +++ b/i.py @@ -0,0 +1,1235 @@ +import os +import sys +import argparse +import subprocess +import platform +import random +import textwrap +import urllib.request as urlrq +import ssl +import json +import tempfile +from enum import Enum + +DEFAULT_OSMOSIS_HOME = os.path.expanduser("~/.osmosisd") +DEFAULT_MONIKER = "osmosis" + +NETWORK_CHOICES = ['osmosis-1', 'osmo-test-5'] +INSTALL_CHOICES = ['node', 'client', 'localosmosis'] +PRUNING_CHOICES = ['default', 'nothing', 'everything'] + +# CLI arguments +parser = argparse.ArgumentParser(description="Osmosis Installer") + +parser.add_argument( + "--home", + type=str, + help=f"Osmosis installation location", +) + +parser.add_argument( + '-m', + "--moniker", + type=str, + help="Moniker name for the node (Default: 'osmosis')", +) + +parser.add_argument( + '-v', + '--verbose', + action='store_true', + help="Enable verbose output", + dest="verbose" +) + +parser.add_argument( + '-o', + '--overwrite', + action='store_true', + help="Overwrite existing Osmosis home without prompt", + dest="overwrite" +) + +parser.add_argument( + '-n', + '--network', + type=str, + choices=NETWORK_CHOICES, + help=f"Network to join: {NETWORK_CHOICES})", +) + +parser.add_argument( + '-p', + '--pruning', + type=str, + choices=PRUNING_CHOICES, + help=f"Pruning settings: {PRUNING_CHOICES})", +) + +parser.add_argument( + '-i', + '--install', + type=str, + choices=INSTALL_CHOICES, + help=f"Which installation to do: {INSTALL_CHOICES})", +) + +parser.add_argument( + "--binary_path", + type=str, + help=f"Path where to download the binary", + default="/usr/local/bin" +) + +parser.add_argument( + '-c', + '--cosmovisor', + action='store_true', + help="Install cosmovisor" +) + +parser.add_argument( + '-s', + '--service', + action='store_true', + help="Setup systemd service (Linux only)" +) + +args = parser.parse_args() + +# Choices +class InstallChoice(str, Enum): + NODE = "1" + CLIENT = "2" + LOCALOSMOSIS = "3" + +class NetworkChoice(str, Enum): + MAINNET = "1" + TESTNET = "2" + +class PruningChoice(str, Enum): + DEFAULT = "1" + NOTHING = "2" + EVERYTHING = "3" + +class Answer(str, Enum): + YES = "1" + NO = "2" + +# Network configurations +class Network: + def __init__(self, chain_id, version, genesis_url, binary_url, peers, rpc_node, addrbook_url, snapshot_url): + self.chain_id = chain_id + self.version = version + self.genesis_url = genesis_url + self.binary_url = binary_url + self.peers = peers + self.rpc_node = rpc_node + self.addrbook_url = addrbook_url + self.snapshot_url = snapshot_url + +TESTNET = Network( + chain_id = "osmo-test-5", + version = "v20.1.0-testnet", + genesis_url = "https://osmosis.fra1.digitaloceanspaces.com/osmo-test-5/genesis.json", + binary_url = { + "linux": { + "amd64": "https://osmosis.fra1.digitaloceanspaces.com/binaries/v20.1.0-testnet/osmosisd-20.1.0-testnet-linux-amd64", + "arm64": "https://osmosis.fra1.digitaloceanspaces.com/binaries/v20.1.0-testnet/osmosisd-20.1.0-testnet-linux-arm64" + }, + "darwin": { + "amd64": "https://osmosis.fra1.digitaloceanspaces.com/binaries/v20.1.0-testnet/osmosisd-20.1.0-testnet-darwin-amd64", + "arm64": "https://osmosis.fra1.digitaloceanspaces.com/binaries/v20.1.0-testnet/osmosisd-20.1.0-testnet-darwin-arm64" + }, + }, + peers = [ + "a5f81c035ff4f985d5e7c940c7c3b846389b7374@167.235.115.14:26656", + "05c41cc1fc7c8cb379e54d784bcd3b3907a1568e@157.245.26.231:26656", + "7c2b9e76be5c2142c76b429d9c29e902599ceb44@157.245.21.183:26656", + "f440c4980357d8b56db87ddd50f06bd551f1319a@5.78.98.19:26656", + "ade4d8bc,8cbe014af6ebdf3cb7b1e9ad36f412c0@testnet-seeds.polkachu.com:12556", + ], + rpc_node = "https://rpc.testnet.osmosis.zone:443", + addrbook_url = "https://rpc.testnet.osmosis.zone/addrbook", + snapshot_url = "https://snapshots.testnet.osmosis.zone/latest" +) + +MAINNET = Network( + chain_id = "osmosis-1", + version = "v20.2.1", + genesis_url = "https://osmosis.fra1.digitaloceanspaces.com/osmosis-1/genesis.json", + binary_url = { + "linux": { + "amd64": "https://osmosis.fra1.digitaloceanspaces.com/binaries/v20.2.1/osmosisd-20.2.1-linux-amd64", + "arm64": "https://osmosis.fra1.digitaloceanspaces.com/binaries/v20.2.1/osmosisd-20.2.1-linux-arm64" + }, + "darwin": { + "amd64": "https://osmosis.fra1.digitaloceanspaces.com/binaries/v20.2.1/osmosisd-20.2.1-darwin-amd64", + "arm64": "https://osmosis.fra1.digitaloceanspaces.com/binaries/v20.2.1/osmosisd-20.2.1-darwin-arm64" + }, + }, + peers = None, + rpc_node = "https://rpc.osmosis.zone:443", + addrbook_url = "https://rpc.osmosis.zone/addrbook", + snapshot_url = "https://snapshots.osmosis.zone/latest" +) + +COSMOVISOR_URL = { + "darwin": { + "amd64": "https://osmosis.fra1.digitaloceanspaces.com/binaries/cosmovisor/cosmovisor-v1.2.0-darwin-amd64", + "arm64": "https://osmosis.fra1.digitaloceanspaces.com/binaries/cosmovisor/cosmovisor-v1.2.0-darwin-arm64" + }, + "linux": { + "amd64": "https://osmosis.fra1.digitaloceanspaces.com/binaries/cosmovisor/cosmovisor-v1.2.0-linux-amd64", + "arm64": "https://osmosis.fra1.digitaloceanspaces.com/binaries/cosmovisor/cosmovisor-v1.2.0-linux-arm64" + } +} +# Terminal utils + +class bcolors: + OKGREEN = '\033[92m' + RED = '\033[91m' + ENDC = '\033[0m' + PURPLE = '\033[95m' + +def clear_screen(): + os.system('clear') + +# Messages + +def welcome_message(): + print(bcolors.OKGREEN + """ + ██████╗ ███████╗███╗ ███╗ ██████╗ ███████╗██╗███████╗ +██╔═══██╗██╔════╝████╗ ████║██╔═══██╗██╔════╝██║██╔════╝ +██║ ██║███████╗██╔████╔██║██║ ██║███████╗██║███████╗ +██║ ██║╚════██║██║╚██╔╝██║██║ ██║╚════██║██║╚════██║ +╚██████╔╝███████║██║ ╚═╝ ██║╚██████╔╝███████║██║███████║ +╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝╚══════╝ + +Welcome to the Osmosis node installer! + + +For more information, please visit https://docs.osmosis.zone + +If you have an old Osmosis installation, +- backup any important data before proceeding +- ensure that no osmosis services are running in the background +""" + bcolors.ENDC) + + +def client_complete_message(osmosis_home): + print(bcolors.OKGREEN + """ +✨ Congratulations! You have successfully completed setting up an Osmosis client! ✨ +""" + bcolors.ENDC) + + print("🧪 Try running: " + bcolors.OKGREEN + f"osmosisd status --home {osmosis_home}" + bcolors.ENDC) + print() + + +def node_complete_message(using_cosmovisor, using_service, osmosis_home): + print(bcolors.OKGREEN + """ +✨ Congratulations! You have successfully completed setting up an Osmosis node! ✨ +""" + bcolors.ENDC) + + if using_service: + + if using_cosmovisor: + print("🧪 To start the cosmovisor service run: ") + print(bcolors.OKGREEN + f"sudo systemctl start cosmovisor" + bcolors.ENDC) + else: + print("🧪 To start the osmosisd service run: ") + print(bcolors.OKGREEN + f"sudo systemctl start osmosisd" + bcolors.ENDC) + + else: + if using_cosmovisor: + print("🧪 To start cosmovisor run: ") + print(bcolors.OKGREEN + f"DAEMON_NAME=osmosisd DAEMON_HOME={osmosis_home} cosmovisor run start" + bcolors.ENDC) + else: + print("🧪 To start osmosisd run: ") + print(bcolors.OKGREEN + f"osmosisd start --home {osmosis_home}" + bcolors.ENDC) + + + + print() + +# Options + +def select_install(): + + # Check if setup is specified in args + if args.install: + if args.install == "node": + choice = InstallChoice.NODE + elif args.install == "client": + choice = InstallChoice.CLIENT + elif args.install == "localosmosis": + choice = InstallChoice.LOCALOSMOSIS + else: + print(bcolors.RED + f"Invalid setup {args.install}. Please choose a valid setup.\n" + bcolors.ENDC) + sys.exit(1) + + else: + + print(bcolors.OKGREEN + """ +Please choose the desired installation: + + 1) node - run an osmosis node and join mainnet or testnet + 2) client - setup osmosisd to query a public node + 3) localosmosis - setup a local osmosis development node + +💡 You can select the installation using the --install flag. + """ + bcolors.ENDC) + + while True: + choice = input("Enter your choice, or 'exit' to quit: ").strip() + + if choice.lower() == "exit": + print("Exiting the program...") + sys.exit(0) + + if choice not in [InstallChoice.NODE, InstallChoice.CLIENT, InstallChoice.LOCALOSMOSIS]: + print("Invalid input. Please choose a valid option.") + else: + break + + if args.verbose: + clear_screen() + print(f"Chosen install: {INSTALL_CHOICES[int(choice) - 1]}") + + clear_screen() + return choice + + +def select_network(): + """ + Selects a network based on user input or command-line arguments. + + Returns: + chosen_network (NetworkChoice): The chosen network, either MAINNET or TESTNET. + + Raises: + SystemExit: If an invalid network is specified or the user chooses to exit the program. + """ + + # Check if network is specified in args + if args.network: + if args.network == MAINNET.chain_id: + choice = NetworkChoice.MAINNET + elif args.network == TESTNET.chain_id: + choice = NetworkChoice.TESTNET + else: + print(bcolors.RED + f"Invalid network {args.network}. Please choose a valid network." + bcolors.ENDC) + sys.exit(1) + + # If not, ask the user to choose a network + else: + print(bcolors.OKGREEN + f""" +Please choose the desired network: + + 1) Mainnet ({MAINNET.chain_id}) + 2) Testnet ({TESTNET.chain_id}) + +💡 You can select the network using the --network flag. +""" + bcolors.ENDC) + + while True: + choice = input("Enter your choice, or 'exit' to quit: ").strip() + + if choice.lower() == "exit": + print("Exiting the program...") + sys.exit(0) + + if choice not in [NetworkChoice.MAINNET, NetworkChoice.TESTNET]: + print(bcolors.RED + "Invalid input. Please choose a valid option. Accepted values: [ 1 , 2 ] \n" + bcolors.ENDC) + else: + break + + if args.verbose: + clear_screen() + print(f"Chosen network: {NETWORK_CHOICES[int(choice) - 1]}") + + clear_screen() + return choice + + +def select_osmosis_home(): + """ + Selects the path for running the 'osmosisd init --home ' command. + + Returns: + osmosis_home (str): The selected path. + + """ + if args.home: + osmosis_home = args.home + else: + default_home = os.path.expanduser("~/.osmosisd") + print(bcolors.OKGREEN + f""" +Do you want to install Osmosis in the default location?: + + 1) Yes, use default location {DEFAULT_OSMOSIS_HOME} (recommended) + 2) No, specify custom location + +💡 You can specify the home using the --home flag. +""" + bcolors.ENDC) + + while True: + choice = input("Enter your choice, or 'exit' to quit: ").strip() + + if choice.lower() == "exit": + print("Exiting the program...") + sys.exit(0) + + if choice == Answer.YES: + osmosis_home = default_home + break + + elif choice == Answer.NO: + while True: + custom_home = input("Enter the path for Osmosis home: ").strip() + if custom_home != "": + osmosis_home = custom_home + break + else: + print("Invalid path. Please enter a valid directory.") + break + else: + print("Invalid choice. Please enter 1 or 2.") + + clear_screen() + return osmosis_home + + +def select_moniker(): + """ + Selects the moniker for the Osmosis node. + + Returns: + moniker (str): The selected moniker. + + """ + if args.moniker: + moniker = args.moniker + else: + print(bcolors.OKGREEN + f""" +Do you want to use the default moniker? + + 1) Yes, use default moniker ({DEFAULT_MONIKER}) + 2) No, specify custom moniker + +💡 You can specify the moniker using the --moniker flag. +""" + bcolors.ENDC) + + while True: + choice = input("Enter your choice, or 'exit' to quit: ").strip() + + if choice.lower() == "exit": + print("Exiting the program...") + sys.exit(0) + + if choice == Answer.YES: + moniker = DEFAULT_MONIKER + break + elif choice == Answer.NO: + while True: + custom_moniker = input("Enter the custom moniker: ") + if custom_moniker.strip() != "": + moniker = custom_moniker + break + else: + print("Invalid moniker. Please enter a valid moniker.") + break + else: + print("Invalid choice. Please enter 1 or 2.") + + clear_screen() + return moniker + + +def initialize_osmosis_home(osmosis_home, moniker): + """ + Initializes the Osmosis home directory with the specified moniker. + + Args: + osmosis_home (str): The chosen home directory. + moniker (str): The moniker for the Osmosis node. + + """ + if not args.overwrite: + + while True: + print(bcolors.OKGREEN + f""" +Do you want to initialize the Osmosis home directory at '{osmosis_home}'? + """ + bcolors.ENDC, end="") + + print(bcolors.RED + f""" +⚠️ All contents of the directory will be deleted. + """ + bcolors.ENDC, end="") + + print(bcolors.OKGREEN + f""" + 1) Yes, proceed with initialization + 2) No, quit + +💡 You can overwrite the osmosis home using --overwrite flag. + """ + bcolors.ENDC) + + choice = input("Enter your choice, or 'exit' to quit: ").strip() + + if choice.lower() == "exit": + print("Exiting the program...") + sys.exit(0) + + if choice == Answer.YES: + break + + elif choice == Answer.NO: + sys.exit(0) + + else: + print("Invalid choice. Please enter 1 or 2.") + + print(f"Initializing Osmosis home directory at '{osmosis_home}'...") + try: + subprocess.run( + ["rm", "-rf", osmosis_home], + stderr=subprocess.DEVNULL, check=True) + + subprocess.run( + ["osmosisd", "init", moniker, "-o", "--home", osmosis_home], + stderr=subprocess.DEVNULL, check=True) + + print("Initialization completed successfully.") + + except subprocess.CalledProcessError as e: + print("Initialization failed.") + print("Please check if the home directory is valid and has write permissions.") + print(e) + sys.exit(1) + + clear_screen() + + +def select_pruning(osmosis_home): + """ + Allows the user to choose pruning settings and performs actions based on the selected option. + + """ + + # Check if pruning settings are specified in args + if args.pruning: + if args.pruning == "default": + choice = PruningChoice.DEFAULT + elif args.pruning == "nothing": + choice = PruningChoice.NOTHING + elif args.pruning == "everything": + choice = PruningChoice.EVERYTHING + else: + print(bcolors.RED + f"Invalid pruning setting {args.pruning}. Please choose a valid setting.\n" + bcolors.ENDC) + sys.exit(1) + + else: + + print(bcolors.OKGREEN + """ +Please choose your desired pruning settings: + + 1) Default: (keep last 100,000 states to query the last week worth of data and prune at 100 block intervals) + 2) Nothing: (keep everything, select this if running an archive node) + 3) Everything: (keep last 10,000 states and prune at a random prime block interval) + +💡 You can select the pruning settings using the --pruning flag. + """ + bcolors.ENDC) + + while True: + choice = input("Enter your choice, or 'exit' to quit: ").strip() + + if choice.lower() == "exit": + print("Exiting the program...") + sys.exit(0) + + if choice not in [PruningChoice.DEFAULT, PruningChoice.NOTHING, PruningChoice.EVERYTHING]: + print("Invalid input. Please choose a valid option.") + else: + break + + if args.verbose: + clear_screen() + print(f"Chosen setting: {PRUNING_CHOICES[int(choice) - 1]}") + + app_toml = os.path.join(osmosis_home, "config", "app.toml") + + if choice == PruningChoice.DEFAULT: + # Nothing to do + pass + + elif choice == PruningChoice.NOTHING: + subprocess.run(["sed -i -E 's/pruning = \"default\"/pruning = \"nothing\"/g' " + app_toml], shell=True) + + elif choice == PruningChoice.EVERYTHING: + primeNum = random.choice([x for x in range(11, 97) if not [t for t in range(2, x) if not x % t]]) + subprocess.run(["sed -i -E 's/pruning = \"default\"/pruning = \"custom\"/g' " + app_toml], shell=True) + subprocess.run(["sed -i -E 's/pruning-keep-recent = \"0\"/pruning-keep-recent = \"10000\"/g' " + app_toml], shell=True) + subprocess.run(["sed -i -E 's/pruning-interval = \"0\"/pruning-interval = \"" + str(primeNum) + "\"/g' " + app_toml], shell=True) + + else: + print(bcolors.RED + f"Invalid pruning setting {choice}. Please choose a valid setting.\n" + bcolors.ENDC) + sys.exit(1) + + clear_screen() + + +def customize_config(home, network): + """ + Customizes the TOML configurations based on the network. + + Args: + home (str): The home directory. + network (str): The network identifier. + + """ + + # osmo-test-5 configuration + if network == NetworkChoice.TESTNET: + + # patch client.toml + client_toml = os.path.join(home, "config", "client.toml") + + with open(client_toml, "r") as config_file: + lines = config_file.readlines() + + for i, line in enumerate(lines): + if line.startswith("chain-id"): + lines[i] = f'chain-id = "{TESTNET.chain_id}"\n' + elif line.startswith("node"): + lines[i] = f'node = "{TESTNET.rpc_node}"\n' + + with open(client_toml, "w") as config_file: + config_file.writelines(lines) + + # patch config.toml + config_toml = os.path.join(home, "config", "config.toml") + + peers = ','.join(TESTNET.peers) + subprocess.run(["sed -i -E 's/persistent_peers = \"\"/persistent_peers = \"" + peers + "\"/g' " + config_toml], shell=True) + + # osmosis-1 configuration + elif network == NetworkChoice.MAINNET: + client_toml = os.path.join(home, "config", "client.toml") + + with open(client_toml, "r") as config_file: + lines = config_file.readlines() + + for i, line in enumerate(lines): + if line.startswith("chain-id"): + lines[i] = f'chain-id = "{MAINNET.chain_id}"\n' + elif line.startswith("node"): + lines[i] = f'node = "{MAINNET.rpc_node}"\n' + + with open(client_toml, "w") as config_file: + config_file.writelines(lines) + + else: + print(bcolors.RED + f"Invalid network {network}. Please choose a valid setting.\n" + bcolors.ENDC) + sys.exit(1) + + clear_screen() + + +def download_binary(network): + """ + Downloads the binary for the specified network based on the operating system and architecture. + + Args: + network (NetworkChoice): The network type, either MAINNET or TESTNET. + + Raises: + SystemExit: If the binary download URL is not available for the current operating system and architecture. + + """ + operating_system = platform.system().lower() + architecture = platform.machine() + + if architecture == "x86_64": + architecture = "amd64" + elif architecture == "aarch64": + architecture = "arm64" + + if architecture not in ["arm64", "amd64"]: + print(f"Unsupported architecture {architecture}.") + sys.exit(1) + + if network == NetworkChoice.TESTNET: + binary_urls = TESTNET.binary_url + else: + binary_urls = MAINNET.binary_url + + if operating_system in binary_urls and architecture in binary_urls[operating_system]: + binary_url = binary_urls[operating_system][architecture] + else: + print(f"Binary download URL not available for {operating_system}/{architecture}") + sys.exit(0) + + try: + binary_path = os.path.join(args.binary_path, "osmosisd") + + print("Downloading " + bcolors.PURPLE+ "osmosisd" + bcolors.ENDC, end="\n\n") + print("from " + bcolors.OKGREEN + f"{binary_url}" + bcolors.ENDC, end=" ") + print("to " + bcolors.OKGREEN + f"{binary_path}" + bcolors.ENDC) + print() + print(bcolors.OKGREEN + "💡 You can change the path using --binary_path" + bcolors.ENDC) + + subprocess.run(["wget", binary_url,"-q", "-O", "/tmp/osmosisd"], check=True) + os.chmod("/tmp/osmosisd", 0o755) + + if platform.system() == "Linux": + subprocess.run(["sudo", "mv", "/tmp/osmosisd", binary_path], check=True) + subprocess.run(["sudo", "chown", f"{os.environ['USER']}:{os.environ['USER']}", binary_path], check=True) + subprocess.run(["sudo", "chmod", "+x", binary_path], check=True) + else: + subprocess.run(["mv", "/tmp/osmosisd", binary_path], check=True) + + # Test binary + subprocess.run(["osmosisd", "version"], check=True) + + print("Binary downloaded successfully.") + + except subprocess.CalledProcessError as e: + print(e) + print("Failed to download the binary.") + sys.exit(1) + + clear_screen() + + +def download_genesis(network, osmosis_home): + """ + Downloads the genesis file for the specified network. + + Args: + network (NetworkChoice): The network type, either MAINNET or TESTNET. + osmosis_home (str): The path to the Osmosis home directory. + + Raises: + SystemExit: If the genesis download URL is not available for the current network. + + """ + if network == NetworkChoice.TESTNET: + genesis_url = TESTNET.genesis_url + else: + genesis_url = MAINNET.genesis_url + + if genesis_url: + try: + print("Downloading " + bcolors.PURPLE + "genesis.json" + bcolors.ENDC + f" from {genesis_url}") + genesis_path = os.path.join(osmosis_home, "config", "genesis.json") + + subprocess.run(["wget", genesis_url, "-q", "-O", genesis_path], check=True) + print("Genesis downloaded successfully.\n") + + except subprocess.CalledProcessError: + print("Failed to download the genesis.") + sys.exit(1) + + +def download_addrbook(network, osmosis_home): + """ + Downloads the addrbook for the specified network. + + Args: + network (NetworkChoice): The network type, either MAINNET or TESTNET. + osmosis_home (str): The path to the Osmosis home directory. + + Raises: + SystemExit: If the genesis download URL is not available for the current network. + + """ + if network == NetworkChoice.TESTNET: + addrbook_url = TESTNET.addrbook_url + else: + addrbook_url = MAINNET.addrbook_url + + if addrbook_url: + try: + print("Downloading " + bcolors.PURPLE + "addrbook.json" + bcolors.ENDC + f" from {addrbook_url}") + addrbook_path = os.path.join(osmosis_home, "config", "addrbook.json") + + subprocess.run(["wget", addrbook_url, "-q", "-O", addrbook_path], check=True) + print("Addrbook downloaded successfully.") + + except subprocess.CalledProcessError: + print("Failed to download the addrbook.") + sys.exit(1) + + clear_screen() + + +def download_snapshot(network, osmosis_home): + """ + Downloads the snapshot for the specified network. + + Args: + network (NetworkChoice): The network type, either MAINNET or TESTNET. + osmosis_home (str): The path to the Osmosis home directory. + + Raises: + SystemExit: If the genesis download URL is not available for the current network. + + """ + + def install_snapshot_prerequisites(): + """ + Installs the prerequisites: Homebrew (brew) package manager and lz4 compression library. + + Args: + osmosis_home (str): The path of the Osmosis home directory. + + """ + while True: + print(bcolors.OKGREEN + f""" +To download the snapshot, we need the lz4 compression library. +Do you want me to install it? + + 1) Yes, install lz4 + 2) No, continue without installing lz4 + """ + bcolors.ENDC) + + choice = input("Enter your choice, or 'exit' to quit: ").strip() + + if choice.lower() == "exit": + print("Exiting the program...") + sys.exit(0) + + if choice == Answer.YES: + break + + elif choice == Answer.NO: + clear_screen() + return + + else: + print("Invalid choice. Please enter 1 or 2.") + + operating_system = platform.system().lower() + if operating_system == "linux": + print("Installing lz4...") + subprocess.run(["sudo apt-get install wget liblz4-tool aria2 -y"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) + else: + print("Installing Homebrew...") + subprocess.run(['bash', '-c', '$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)']) + + print("Installing lz4...") + subprocess.run(['brew', 'install', 'lz4']) + + print("Installation completed successfully.") + clear_screen() + + + def parse_snapshot_info(network): + """ + Creates a dictionary containing the snapshot information for the specified network. + It merges the snapshot information from the osmosis official snapshot JSON and + quicksync from chianlayer https://dl2.quicksync.io/json/osmosis.json + + Returns: + dict: Dictionary containing the parsed snapshot information. + + """ + snapshot_info = [] + + if network == NetworkChoice.TESTNET: + snapshot_url = TESTNET.snapshot_url + chain_id = TESTNET.chain_id + quicksync_prefix = "osmotestnet-5" + elif network == NetworkChoice.MAINNET: + snapshot_url = MAINNET.snapshot_url + chain_id = MAINNET.chain_id + quicksync_prefix = "osmosis-1" + else: + print(f"Invalid network choice - {network}") + sys.exit(1) + + # Set SSL context + context = ssl.create_default_context() + context.check_hostname = False + context.verify_mode = ssl.CERT_NONE + + req = urlrq.Request(snapshot_url, headers={'User-Agent': 'Mozilla/5.0'}) + resp = urlrq.urlopen(req, context=context) + latest_snapshot_url = resp.read().decode() + + snapshot_info.append({ + "network": chain_id, + "mirror": "Germany", + "url": latest_snapshot_url.rstrip('\n'), + "type": "pruned", + "provider": "osmosis" + }) + + # Parse quicksync snapshot json + try: + url = "https://dl2.quicksync.io/json/osmosis.json" + resp = urlrq.urlopen(url, context=context) + data = resp.read().decode() + + snapshots = json.loads(data) + + for snapshot in snapshots: + + if not snapshot["file"].startswith(quicksync_prefix): + continue + + snapshot_info.append({ + "network": chain_id, + "mirror": snapshot["mirror"], + "url": snapshot["url"], + "type": snapshot["network"], + "provider": "chainlayer" + }) + + except (urlrq.URLError, json.JSONDecodeError) as e: + print(f"Error: Failed to fetch or parse snapshot JSON - {e}") + + return snapshot_info + + + def print_snapshot_download_info(snapshot_info): + """ + Prints the information about the snapshot download. + """ + + print(bcolors.OKGREEN + f""" +Choose one of the following snapshots: + """ + bcolors.ENDC) + + # Prepare table headers + column_widths = [1, 12, 12, 12] + headers = ["#", "Provider", "Location", "Type"] + header_row = " | ".join(f"{header:{width}}" for header, width in zip(headers, column_widths)) + + # Print table header + print(header_row) + print("-" * len(header_row)) + + # Print table content + for idx, snapshot in enumerate(snapshot_info): + + row_data = [str(idx + 1), snapshot["provider"], snapshot["mirror"], snapshot["type"]] + wrapped_data = [textwrap.fill(data, width=width) for data, width in zip(row_data, column_widths)] + formatted_row = " | ".join(f"{data:{width}}" for data, width in zip(wrapped_data, column_widths)) + print(formatted_row) + + print() + + install_snapshot_prerequisites() + snapshots = parse_snapshot_info(network) + + while True: + + print_snapshot_download_info(snapshots) + choice = input("Enter your choice, or 'exit' to quit: ").strip() + + if choice.lower() == "exit": + print("Exiting the program...") + sys.exit(0) + + if int(choice) < 0 or int(choice) > len(snapshots): + clear_screen() + print(bcolors.RED + "Invalid input. Please choose a valid option." + bcolors.ENDC) + else: + break + + snapshot_url = snapshots[int(choice) - 1]['url'] + + try: + print(f"\n🔽 Downloading snapshots from {snapshot_url}") + download_process = subprocess.Popen(["wget", "-q", "-O", "-", snapshot_url], stdout=subprocess.PIPE) + lz4_process = subprocess.Popen(["lz4", "-d"], stdin=download_process.stdout, stdout=subprocess.PIPE) + tar_process = subprocess.Popen(["tar", "-C", osmosis_home, "-xf", "-"], stdin=lz4_process.stdout, stdout=subprocess.PIPE) + + tar_process.wait() + print("Snapshot download and extraction completed successfully.") + + except subprocess.CalledProcessError as e: + print("Failed to download the snapshot.") + print(f"Error: {e}") + sys.exit(1) + + clear_screen() + + +def download_cosmovisor(osmosis_home): + """ + Downloads and installs cosmovisor. + + Returns: + use_cosmovisor(bool): Whether to use cosmovisor or not. + + """ + if not args.cosmovisor: + print(bcolors.OKGREEN + f""" +Do you want to install cosmovisor? + + 1) Yes, download and install cosmovisor (default) + 2) No + +💡 You can specify the cosmovisor setup using the --cosmovisor flag. +""" + bcolors.ENDC) + + while True: + choice = input("Enter your choice, or 'exit' to quit: ").strip() + + if choice.lower() == "exit": + print("Exiting the program...") + sys.exit(0) + + if choice == Answer.YES: + break + elif choice == Answer.NO: + print("Skipping cosmovisor installation.") + clear_screen() + return False + else: + print("Invalid choice. Please enter 1 or 2.") + + # Download and install cosmovisor + operating_system = platform.system().lower() + architecture = platform.machine() + + if architecture == "x86_64": + architecture = "amd64" + elif architecture == "aarch64": + architecture = "arm64" + + if architecture not in ["arm64", "amd64"]: + print(f"Unsupported architecture {architecture}.") + sys.exit(1) + + if operating_system in COSMOVISOR_URL and architecture in COSMOVISOR_URL[operating_system]: + binary_url = COSMOVISOR_URL[operating_system][architecture] + else: + print(f"Binary download URL not available for {os}/{architecture}") + sys.exit(0) + + try: + binary_path = os.path.join(args.binary_path, "cosmovisor") + + print("Downloading " + bcolors.PURPLE+ "cosmovisor" + bcolors.ENDC, end="\n\n") + print("from " + bcolors.OKGREEN + f"{binary_url}" + bcolors.ENDC, end=" ") + print("to " + bcolors.OKGREEN + f"{binary_path}" + bcolors.ENDC) + print() + print(bcolors.OKGREEN + "💡 You can change the path using --binary_path" + bcolors.ENDC) + + clear_screen() + temp_dir = tempfile.mkdtemp() + temp_binary_path = os.path.join(temp_dir, "cosmovisor") + + subprocess.run(["wget", binary_url,"-q", "-O", temp_binary_path], check=True) + os.chmod(temp_binary_path, 0o755) + + if platform.system() == "Linux": + subprocess.run(["sudo", "mv", temp_binary_path, binary_path], check=True) + subprocess.run(["sudo", "chown", f"{os.environ['USER']}:{os.environ['USER']}", binary_path], check=True) + subprocess.run(["sudo", "chmod", "+x", binary_path], check=True) + else: + subprocess.run(["mv", temp_binary_path, binary_path], check=True) + + # Test binary + subprocess.run(["cosmovisor", "help"], check=True) + + print("Binary downloaded successfully.") + + except subprocess.CalledProcessError: + print("Failed to download the binary.") + sys.exit(1) + + clear_screen() + + # Initialize cosmovisor + print("Setting up cosmovisor directory...") + + # Set environment variables + env = { + "DAEMON_NAME": "osmosisd", + "DAEMON_HOME": osmosis_home + } + + try: + subprocess.run(["/usr/local/bin/cosmovisor", "init", "/usr/local/bin/osmosisd"], check=True, env=env) + except subprocess.CalledProcessError: + print("Failed to initialize cosmovisor.") + sys.exit(1) + + clear_screen() + return True + + +def setup_cosmovisor_service(osmosis_home): + """ + Setup cosmovisor service on Linux. + """ + + operating_system = platform.system() + + if operating_system != "Linux": + return False + + if not args.service: + print(bcolors.OKGREEN + f""" +Do you want to setup cosmovisor as a background service? + + 1) Yes, setup cosmovisor as a service + 2) No + +💡 You can specify the service setup using the --service flag. +""" + bcolors.ENDC) + + while True: + choice = input("Enter your choice, or 'exit' to quit: ").strip() + + if choice.lower() == "exit": + print("Exiting the program...") + sys.exit(0) + + if choice == Answer.YES: + break + elif choice == Answer.NO: + return + + user = os.environ.get("USER") + + unit_file_contents = f"""[Unit] +Description=Cosmovisor daemon +After=network-online.target + +[Service] +Environment="DAEMON_NAME=osmosisd" +Environment="DAEMON_HOME={osmosis_home}" +Environment="DAEMON_RESTART_AFTER_UPGRADE=true" +Environment="DAEMON_ALLOW_DOWNLOAD_BINARIES=false" +Environment="DAEMON_LOG_BUFFER_SIZE=512" +Environment="UNSAFE_SKIP_BACKUP=true" +User={user} +ExecStart=/usr/local/bin/cosmovisor start --home {osmosis_home} +Restart=always +RestartSec=3 +LimitNOFILE=infinity +LimitNPROC=infinity + +[Install] +WantedBy=multi-user.target +""" + + unit_file_path = "/lib/systemd/system/cosmovisor.service" + + with open("cosmovisor.service", "w") as f: + f.write(unit_file_contents) + + subprocess.run(["sudo", "mv", "cosmovisor.service", unit_file_path]) + subprocess.run(["sudo", "systemctl", "daemon-reload"]) + subprocess.run(["systemctl", "restart", "systemd-journald"]) + + clear_screen() + return True + + +def setup_osmosisd_service(osmosis_home): + """ + Setup osmosisd service on Linux. + """ + + operating_system = platform.system() + + if operating_system != "Linux": + return False + + if not args.service: + print(bcolors.OKGREEN + """ +Do you want to set up osmosisd as a background service? + + 1) Yes, set up osmosisd as a service + 2) No + +💡 You can specify the service setup using the --service flag. +""" + bcolors.ENDC) + + while True: + choice = input("Enter your choice, or 'exit' to quit: ").strip() + + if choice.lower() == "exit": + print("Exiting the program...") + sys.exit(0) + + if choice == Answer.YES: + break + elif choice == Answer.NO: + return + + user = os.environ.get("USER") + + unit_file_contents = f"""[Unit] +Description=Osmosis Daemon +After=network-online.target + +[Service] +User={user} +ExecStart=/usr/local/bin/osmosisd start --home {osmosis_home} +Restart=always +RestartSec=3 +LimitNOFILE=infinity +LimitNPROC=infinity + +[Install] +WantedBy=multi-user.target +""" + + unit_file_path = "/lib/systemd/system/osmosisd.service" + + with open("osmosisd.service", "w") as f: + f.write(unit_file_contents) + + subprocess.run(["sudo", "mv", "osmosisd.service", unit_file_path]) + subprocess.run(["sudo", "systemctl", "daemon-reload"]) + subprocess.run(["systemctl", "restart", "systemd-journald"]) + + clear_screen() + return True + + +def main(): + + welcome_message() + + # Start the installation + chosen_install = select_install() + + if chosen_install == InstallChoice.NODE: + network = select_network() + download_binary(network) + osmosis_home = select_osmosis_home() + moniker = select_moniker() + initialize_osmosis_home(osmosis_home, moniker) + using_cosmovisor = download_cosmovisor(osmosis_home) + download_genesis(network, osmosis_home) + download_addrbook(network, osmosis_home) + select_pruning(osmosis_home) + download_snapshot(network, osmosis_home) + if using_cosmovisor: + using_service = setup_cosmovisor_service(osmosis_home) + else: + using_service = setup_osmosisd_service(osmosis_home) + node_complete_message(using_cosmovisor, using_service, osmosis_home) + + elif chosen_install == InstallChoice.CLIENT: + network = select_network() + download_binary(network) + osmosis_home = select_osmosis_home() + moniker = select_moniker() + initialize_osmosis_home(osmosis_home, moniker) + customize_config(osmosis_home, network) + client_complete_message(osmosis_home) + + elif chosen_install == InstallChoice.LOCALOSMOSIS: + print("Setting up a LocalOsmosis node not yet supported.") + sys.exit(1) + +main() diff --git a/proto/fairyring/pricefeed/googleapis b/proto/fairyring/pricefeed/googleapis new file mode 160000 index 00000000..c512b1a0 --- /dev/null +++ b/proto/fairyring/pricefeed/googleapis @@ -0,0 +1 @@ +Subproject commit c512b1a0af9cee0d281aae504334dc93b8dae5d2 diff --git a/proto/fairyring/pricefeed/query.proto b/proto/fairyring/pricefeed/query.proto index eea73aa4..d8798791 100644 --- a/proto/fairyring/pricefeed/query.proto +++ b/proto/fairyring/pricefeed/query.proto @@ -3,72 +3,32 @@ package pricefeed; import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; -import "fairyring/pricefeed/params.proto"; -import "fairyring/pricefeed/pricefeed.proto"; + + option go_package = "fairyring/x/pricefeed/types"; // Query defines the gRPC querier service. service Query { - // Parameters queries the parameters of the module. - rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { - option (google.api.http).get = "/pricefeed/params"; - } - // SymbolRequest queries a specific symbol request by symbol name. - // Returns the symbol request data for the given symbol. - // This method is used to query the status of a specific symbol request. - rpc SymbolRequest(QuerySymbolRequest) returns (QuerySymbolRequestResponse) { - option (google.api.http).get = "/pricefeed/symbol_requests/{symbol}"; - } - // SymbolRequests queries all symbol requests. - rpc SymbolRequests(QuerySymbolRequests) - returns (QuerySymbolRequestsResponse) { - option (google.api.http).get = "/pricefeed/symbol_requests"; - } - // Price queries the current price data for a specific symbol. - // Returns the current price for the given symbol. - rpc Price(QueryPrice) returns (QueryPriceResponse) { - option (google.api.http).get = "/pricefeed/prices/{symbol}"; - } -} -// QueryParamsRequest is request type for the Query/Params RPC method. -message QueryParamsRequest {} + rpc CurrentNonce(QueryCurrentNonceRequest) returns (QueryCurrentNonceResponse) { + option (google.api.http).get = "/pricefeed/current_nonce/{denom}/{price}"; + } -// QueryParamsResponse is response type for the Query/Params RPC method. -message QueryParamsResponse { - // params holds all the parameters of this module. - Params params = 1 [ (gogoproto.nullable) = false ]; } -// QuerySymbolRequest is request type for the Query/SymbolRequest RPC method. -message QuerySymbolRequest { string symbol = 1; } -// QuerySymbolRequestResponse is response type for the Query/SymbolRequest RPC -// method. -message QuerySymbolRequestResponse { - // symbol_request holds the symbol request data for the given symbol. - // The symbol request data for the given symbol name. - SymbolRequest symbol_request = 1; -} -// QuerySymbolRequests is request type for the Query/SymbolRequests RPC method. -message QuerySymbolRequests {} +message QueryCurrentNonceRequest { string denom = 1; +string price = 2; + } -// QuerySymbolRequestsResponse is response type for the Query/SymbolRequests -message QuerySymbolRequestsResponse { - // symbol_requests holds a list of all symbol requests. - // A list of all symbol requests in the state. - repeated SymbolRequest symbol_requests = 1 [ (gogoproto.nullable) = false ]; -} -// QueryPrice is request type for the Query/Price RPC method. -message QueryPrice { string symbol = 1; } -// QueryPriceResponse is response type for the Query/Price RPC method. -message QueryPriceResponse { +message QueryCurrentNonceResponse { // price holds the current price for the given symbol. // The current price data for the given symbol. - Price price = 1; + string nonce = 1; } + diff --git a/testutil/conditionalenc-tesnet/encrypter/encrypter b/testutil/conditionalenc-tesnet/encrypter/encrypter index c7104774420a77cdc51f629de1e6e871a7cf82df..133727097100a794dfe5a2cd2067adca67018b42 100755 GIT binary patch delta 322 zcmWN=xlV%s00!Vdy{q7TU&UiGp;2qGW6@X}#U5I1LnIXtqoH^}Q&=(K0Xnex1P*Qt zeFxhI(ACWcunYcQ^0j{7h!%dl(?vd4a!!s+y^__=4sz+Fq8JvdHO}jY8CA<)@?|?) zf7!S1ldSZ3CD`eN<5UZB@s5)-!t)cmZQsaN!#L&&w*qgS+PhN8bVJ$;X#z=%U=Q|U zANJz_4&qP{($PPcBx_!`8O3273A#<{Iz=S>@+b8j+lg}`vn4X7@u1cu!!jjBtr@z= zq(sK5nuhLQw5it!w-@)n+Em5kIuz14ieosA6F7-eIE^zni*q=S3%H0&xQr{fiZNtx N4cBoa;5wV`&p+-4fieI9 delta 320 zcmWN=J5GZD00!XD`mTcSw^s2nX{b=3b~BNtRn&nrSVL@332IXamTJRV4F~AJ<_R3! z7`k}?+Y9LE<^k-2|CfC2-&dlI?{AbMn-83FYuc5DYMzPrW#LK`n8vQBy9I;I^Cyoy zm)?_ds>2*8?s>-5pN|2ooxkbhkTCnDK7Z#kQcttC6U5$oWMz(!fBkrS)9XpT);(K!ev~+RZQX i.py && python3 i.py -``` -Next, create a new key: -```bash -osmosisd keys add wallet -``` -Copy the mnemonic into the `testutil/osmosis-testnet/mnemonic-osmosis.txt`. -Using the osmosis official faucet, deposit osmos to the generated address. - -3. Start the IBC relayer using the provided script in `fairyring/testutil/osmosis-testnet/relayer.sh`. - -4. In order to use one of the current pools on osmosis tesnet, transfer some osmos to the fairyring: -```bash -osmosisd tx ibc-transfer transfer transfer ${channel-id} fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c 10000uosmo --from wallet --fees 416uosmo --gas auto --gas-adjustment 1.5 -b block -``` - -5. Replace the `channel` parameter in the MEMO in `testutil/osmosis-testnet/test-swap.sh` script with the current channel id on the osmosis side, and the ibc bridged token sent in the transfer command with the denom of the briged osmo on fairyring. -Then, run the script to perform the swap. The result of the swap can be checked through noticing the new ibc bridged token on the user's balance on fairyring. - -## Osmosis Conditional Encryption - -In order to test the swap functionality through the conditional encryption using local osmosis chain, follow the below steps: - -1. Setup and run the Fairyring chain using the provided script in `fairyring/testutil/conditionalenc/start-fairy.sh`. -When running this script, it asks for a number `i` to set the chain id as `fairytest-i`. - -2. Setup and run a local version of Osmosis chain using the provided script in `fairyring/testutil/conditionalenc/start-osmo.sh`. - -3. Start the IBC relayers using the provided script in `fairyring/testutil/conditionalenc/relayer.sh`. -Input the same value for `i` as in step 2. - -4. Perform the initial transfer, create the pool and the contract by running the script in `fairyring/testutil/conditionalenc/setup-pool.sh`. - -5. Send the encrypted tx and submit pk and shares using the script in `fairyring/testutil/conditionalenc/send-tx.sh`. The message for the encrypted tx is hardcoded in `fairyring/testutil/conditionalenc/encrypter/main.go`. Also, it requires the `DistributedIBE` to be present in the same directory as fairyring. When running, it asks for the id you want to use for the encryption. The chain by default only checks for the ETH prices. So you can wait for a specific price to be reached and it will be shown in the logs like this ` =======================> {[1ETH1887399056900] } `. The `1ETH1887399056900` can be used as the id for encryption. -The hardcoded message is the following message converted to []byte: -``` -coin := am.keeper.MinGasPrice(ctx) -coin.Amount = sdk.NewIntFromUint64(500) - -cosmWasmPacketData := transfertypes.MsgTransfer{ - SourcePort: "transfer", - SourceChannel: "channel-1", - Token: coin, - Sender: "fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c", - Receiver: "osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9", - TimeoutTimestamp: uint64(ctx.BlockTime().UnixNano()+int64(180000*time.Minute)), - TimeoutHeight: types1.NewHeight(10000000000,100000000000), - Memo: `{"wasm":{"contract":"osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9", "msg":{"swap_with_action":{"swap_msg":{"token_out_min_amount":"10","path":[{"pool_id":"1","token_out_denom":"uosmo"}]},"after_swap_action":{"ibc_transfer":{"receiver":"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c","channel":"channel-0"}},"local_fallback_address":"osmo12smx2wdlyttvyzvzg54y2vnqwq2qjateuf7thj"}}}}`, - } -``` - -After the following steps, as it can be seen in the logs, the tx will be decrypted and sent to osmosis. The provided example performs a swap and the result tokens will be sent to fairyring. The new token can be seen throguh running: - -```bash -../../fairyringd query bank balances fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c --node tcp://127.0.0.1:26659 -``` - -### Message Format and Inputs - -The encrypted transactions are defined as follows: - -```go -type MsgSubmitEncryptedTx struct { - Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` - Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` - Condition string `protobuf:"bytes,3,opt,name=condition,proto3" json:"condition,omitempty"` -} -``` -The `Condition` field is a string created by the concatenation of a nonce, a token name and a price. For instance, if the user wants to submit a limit loss order which triggers when the price of `ETH` reaches `1887399056900` and the queried nonce from the chain for this specific price and token is `1`, the condition will be set to `1ETH1887399056900` and the transaction will be decrypted once the price of `ETH` reaches the mentioned amount. - -Next is the `Data` field which inlcudes the encrypted value of the tx. The message that is encrypted is structured as follows: - -```go -type MsgTransfer struct { - // the port on which the packet will be sent - SourcePort string `protobuf:"bytes,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty" yaml:"source_port"` - // the channel by which the packet will be sent - SourceChannel string `protobuf:"bytes,2,opt,name=source_channel,json=sourceChannel,proto3" json:"source_channel,omitempty" yaml:"source_channel"` - // the tokens to be transferred - Token types.Coin `protobuf:"bytes,3,opt,name=token,proto3" json:"token"` - // the sender address - Sender string `protobuf:"bytes,4,opt,name=sender,proto3" json:"sender,omitempty"` - // the recipient address on the destination chain - Receiver string `protobuf:"bytes,5,opt,name=receiver,proto3" json:"receiver,omitempty"` - // Timeout height relative to the current block height. - // The timeout is disabled when set to 0. - TimeoutHeight types1.Height `protobuf:"bytes,6,opt,name=timeout_height,json=timeoutHeight,proto3" json:"timeout_height" yaml:"timeout_height"` - // Timeout timestamp in absolute nanoseconds since unix epoch. - // The timeout is disabled when set to 0. - TimeoutTimestamp uint64 `protobuf:"varint,7,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty" yaml:"timeout_timestamp"` - // optional memo - Memo string `protobuf:"bytes,8,opt,name=memo,proto3" json:"memo,omitempty"` -} -``` -For our specific use case, the `SourcePort` should always be set to `transfer` and the value for the `SourceChannel` should be set to `channel-1`. -The Token is the token type and amount that the user chooses and is going to be transferred for the swap input. The Token can be defined like this: - -```go -import sdk "github.com/cosmos/cosmos-sdk/types" -. -. -. -coin := sdk.Coin{Amount: sdk.NewIntFromUint64(500), Denom: "frt"} -``` -The `Sender` is the user address on our chain. -The `Receiver` is the address of the smart contract on osmosis chain which can for the test purpose be set to `osmo1zl9ztmwe2wcdvv9std8xn06mdaqaqm789rutmazfh3z869zcax4sv0ctqw`. -The `TimeoutHeight` can be defined as below for now: -```go -import types1 "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" -. -. -. -TimeoutHeight := types1.NewHeight(100000000000,1000000000000) -``` -The `TimeoutTimestamp` can be defined as below for now: -```go -import "time" -. -. -. -TimeoutTimestamp := uint64(time.Now().UnixNano()+int64(280000*time.Minute)) -``` -An important field is the `Memo` which defines the details for the swap. The `Memo` should be a json formatted string like the below example: - -```json -{ - "wasm": { - "contract": "osmo1zl9ztmwe2wcdvv9std8xn06mdaqaqm789rutmazfh3z869zcax4sv0ctqw", - "msg": { - "swap_with_action": { - "swap_msg": { - "token_out_min_amount": "10", - "path": [ - { - "pool_id": "74", - "token_out_denom": "uion" - } - ] - }, - "after_swap_action": { - "ibc_transfer": { - "receiver": "fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c", - "channel": "channel-4293" - } - }, - "local_fallback_address": "osmo1pw5aj2u5thkgumkpdms0x78y97e6ppfl6vmjpd" - } - } - } -} -``` -- The `contract` value should be the same value as the `Receiver` filed in the `MsgTransfer`. -- The `token_out_min_amount` can be choosen by the user, but for testing, set it to `1`or `10`. -- The `pool_id` depends on the input and output tokens but for the test purpose, it can be set to any value like `47`. -- The `token_out_denom` is the swap output token defined by the user. -- The `receiver` is the address for the output token to be sent to. For instance it can be the same as the sender address in `MsgTransfer`. -- The `channel` is the channel id on the osmosis chain which is connected to fairyring. For the test, it can be set to `channel-4293`. -- The `local_fallback_address` is the address on osmosis chain which in case the ibc transfer did not work, the output tokens will be sent to. The value for the test can be set to `osmo1pw5aj2u5thkgumkpdms0x78y97e6ppfl6vmjpd` or the contract address. - -Values such as `pool_id` or `channel` should be hardcoded in the frontend. For instance, the frontend needs to know which pool id to use for each set of input and output token defined by the user. Later, we might add the option so that the user can choose these values on their own. - -After setting all fields in the `MsgTransfer` it should be marshalized to `[]byte` can be encrypted. The encryption output will go in the `Data` field of the transaction. - -The remaining field of the transaction is the `Creator` which will be set as any other transaction. - -#### Nonce Query -In order to get the nonce for a token and a specific price, you can use the following query: - -```bash -fairyringd query pricefeed current-nonce {denom} {price} -``` - -The nonce for a denom and a price increases each time the price reaches that price. The nonce ensures that the old txs encrypted for a specific price cannot be sent again when the price reaches the same value later on. \ No newline at end of file diff --git a/testutil/conditionalenc-tesnet/relayer.sh b/testutil/conditionalenc-tesnet/relayer.sh index 2dac077b..fb5a528c 100755 --- a/testutil/conditionalenc-tesnet/relayer.sh +++ b/testutil/conditionalenc-tesnet/relayer.sh @@ -22,7 +22,7 @@ log_level = "info" [mode.clients] enabled = true refresh = true -misbehaviour = true +misbehaviour = false [mode.connections] enabled = false @@ -69,7 +69,7 @@ account_prefix = 'band' key_name = 'testkey' store_prefix = 'ibc' default_gas = 1000000 -max_gas = 100000000 +max_gas = 50000000 gas_price = { price = 0.0025, denom = 'uband' } gas_multiplier = 3 max_msg_num = 30 @@ -82,9 +82,9 @@ address_type = { derivation = 'cosmos' } [[chains]] id = 'fairytest-${i}' -rpc_addr = 'http://0.0.0.0:26659' -grpc_addr = 'http://0.0.0.0:9092' -event_source = { url = "ws://0.0.0.0:26659/websocket", batch_delay = '50ms' , mode = "push" } +rpc_addr = 'http://localhost:26659' +grpc_addr = 'http://localhost:9092' +event_source = { url = "ws://localhost:26659/websocket", batch_delay = '50ms' , mode = "push" } rpc_timeout = '50s' account_prefix = 'fairy' key_name = 'requester' @@ -113,7 +113,7 @@ store_prefix = 'ibc' default_gas = 1000000 max_gas = 100000000 gas_price = { price = 0.0025, denom = 'uosmo' } -gas_multiplier = 3 +gas_multiplier = 3.2 max_msg_num = 30 max_tx_size = 2097152 clock_drift = '5s' @@ -149,6 +149,5 @@ hermes keys add --chain "fairytest-${i}" --mnemonic-file "./mnemonic-fairy.txt" hermes create channel --a-chain band-laozi-testnet6 --b-chain "fairytest-${i}" --a-port oracle --b-port pricefeed --order unordered --channel-version bandchain-1 --new-client-connection hermes create channel --a-chain osmo-test-5 --b-chain "fairytest-${i}" --a-port transfer --b-port transfer --order unordered --new-client-connection -#hermes create channel --a-chain localosmosis --b-chain "fairytest-${i}" --a-port transfer --b-port transfer --order unordered --new-client-connection hermes start \ No newline at end of file diff --git a/testutil/conditionalenc-tesnet/send-tx.sh b/testutil/conditionalenc-tesnet/send-tx.sh index 6fd23bf3..cc33e5cb 100755 --- a/testutil/conditionalenc-tesnet/send-tx.sh +++ b/testutil/conditionalenc-tesnet/send-tx.sh @@ -7,31 +7,31 @@ read -p "Enter the value for id: " i # cd .. -enc=$(./encrypter/encrypter ${i} a6798cf7364ba5e8337584394bf9c6d4970a174603aab18f06ed80ade5f65194e51522170814d2c38b68c2703bfb7ca0 "test") -# # # '{ -# # # "source_port": "transfer", -# # # "source_channel": "channel-1", -# # # "token":{ -# # # "denom": "frt", -# # # "amount": "500" -# # # }, -# # # "sender": "fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c", -# # # "receiver": "osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9", -# # # "timeout_height":{ -# # # "revision_number": "10000000000", -# # # "revision_height": "100000000000" -# # # }, -# # # "timeout_timestamp": "1699052860444761679", -# # # "memo": "{\"wasm\": {\"contract\": \"osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9\", \"msg\": {\"swap_with_action\":{\"swap_msg\":{\"token_out_min_amount\":\"10\",\"path\":[{\"pool_id\":\"1\",\"token_out_denom\":\"uosmo\"}]},\"after_swap_action\":{\"ibc_transfer\":{\"receiver\":\"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c\",\"channel\":\"channel-0\"}},\"local_fallback_address\":\"osmo12smx2wdlyttvyzvzg54y2vnqwq2qjateuf7thj\"}}}}" -# # # }') -# # #'{"source_port":"transfer","source_channel":"channel-1","token":{"denom":"frt","amount":"500"},"sender":"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c","receiver":"osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9","timeout_height":{"revision_number":10000000000,"revision_height":100000000000},"timeout_timestamp":1699052860444761679,"memo":"{\"wasm\": {\"contract\": \"osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9\", \"msg\": {\"swap_with_action\":{\"swap_msg\":{\"token_out_min_amount\":\"10\",\"path\":[{\"pool_id\":\"1\",\"token_out_denom\":\"uosmo\"}]},\"after_swap_action\":{\"ibc_transfer\":{\"receiver\":\"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c\",\"channel\":\"channel-0\"}},\"local_fallback_address\":\"osmo12smx2wdlyttvyzvzg54y2vnqwq2qjateuf7thj\"}}}}"}}') -# # #'{"denom":"frt","amount":"130","sender":"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c","receiver":"osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9","memo":"{\"wasm\": {\"contract\": \"osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9\", \"msg\": {\"swap_with_action\":{\"swap_msg\":{\"token_out_min_amount\":\"10\",\"path\":[{\"pool_id\":\"1\",\"token_out_denom\":\"uosmo\"}]},\"after_swap_action\":{\"ibc_transfer\":{\"receiver\":\"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c\",\"channel\":\"channel-0\"}},\"local_fallback_address\":\"osmo12smx2wdlyttvyzvzg54y2vnqwq2qjateuf7thj\"}}}}"}') - - -echo "${enc}" - - ../../fairyringd tx conditionalenc submit-encrypted-tx ${enc} ${i} --node tcp://127.0.0.1:26659 -b sync -y --from star -sleep 5 +# enc=$(./encrypter/encrypter ${i} a6798cf7364ba5e8337584394bf9c6d4970a174603aab18f06ed80ade5f65194e51522170814d2c38b68c2703bfb7ca0 "test") +# # # # '{ +# # # # "source_port": "transfer", +# # # # "source_channel": "channel-1", +# # # # "token":{ +# # # # "denom": "frt", +# # # # "amount": "500" +# # # # }, +# # # # "sender": "fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c", +# # # # "receiver": "osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9", +# # # # "timeout_height":{ +# # # # "revision_number": "10000000000", +# # # # "revision_height": "100000000000" +# # # # }, +# # # # "timeout_timestamp": "1699052860444761679", +# # # # "memo": "{\"wasm\": {\"contract\": \"osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9\", \"msg\": {\"swap_with_action\":{\"swap_msg\":{\"token_out_min_amount\":\"10\",\"path\":[{\"pool_id\":\"1\",\"token_out_denom\":\"uosmo\"}]},\"after_swap_action\":{\"ibc_transfer\":{\"receiver\":\"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c\",\"channel\":\"channel-0\"}},\"local_fallback_address\":\"osmo12smx2wdlyttvyzvzg54y2vnqwq2qjateuf7thj\"}}}}" +# # # # }') +# # # #'{"source_port":"transfer","source_channel":"channel-1","token":{"denom":"frt","amount":"500"},"sender":"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c","receiver":"osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9","timeout_height":{"revision_number":10000000000,"revision_height":100000000000},"timeout_timestamp":1699052860444761679,"memo":"{\"wasm\": {\"contract\": \"osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9\", \"msg\": {\"swap_with_action\":{\"swap_msg\":{\"token_out_min_amount\":\"10\",\"path\":[{\"pool_id\":\"1\",\"token_out_denom\":\"uosmo\"}]},\"after_swap_action\":{\"ibc_transfer\":{\"receiver\":\"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c\",\"channel\":\"channel-0\"}},\"local_fallback_address\":\"osmo12smx2wdlyttvyzvzg54y2vnqwq2qjateuf7thj\"}}}}"}}') +# # # #'{"denom":"frt","amount":"130","sender":"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c","receiver":"osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9","memo":"{\"wasm\": {\"contract\": \"osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9\", \"msg\": {\"swap_with_action\":{\"swap_msg\":{\"token_out_min_amount\":\"10\",\"path\":[{\"pool_id\":\"1\",\"token_out_denom\":\"uosmo\"}]},\"after_swap_action\":{\"ibc_transfer\":{\"receiver\":\"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c\",\"channel\":\"channel-0\"}},\"local_fallback_address\":\"osmo12smx2wdlyttvyzvzg54y2vnqwq2qjateuf7thj\"}}}}"}') + + +# echo "${enc}" + +# ../../fairyringd tx conditionalenc submit-encrypted-tx ${enc} ${i} --node tcp://127.0.0.1:26659 -b sync -y --from star +# sleep 5 ../../fairyringd tx keyshare create-latest-pub-key a6798cf7364ba5e8337584394bf9c6d4970a174603aab18f06ed80ade5f65194e51522170814d2c38b68c2703bfb7ca0 a6798cf7364ba5e8337584394bf9c6d4970a174603aab18f06ed80ade5f65194e51522170814d2c38b68c2703bfb7ca0 --node tcp://127.0.0.1:26659 -b sync -y --from star output=$($HOME/ShareGenerator/ShareGenerator derive 2ecab484b7a96eb1a4d9113b03cf09459c79c178ce3098baa2f8eb2bdcbec920 0 ${i} | jq '.') diff --git a/testutil/conditionalenc-tesnet/start-fairy.sh b/testutil/conditionalenc-tesnet/start-fairy.sh index 6be0ccbe..438dfe6c 100755 --- a/testutil/conditionalenc-tesnet/start-fairy.sh +++ b/testutil/conditionalenc-tesnet/start-fairy.sh @@ -24,7 +24,7 @@ go build ./cmd/fairyringd echo shrug make inmate anchor acid clock morning stage fiction build chef copy often inherit wonder pen boss join joke flock push morning chapter fever | ./fairyringd keys add star --recover # Add a genesis account -./fairyringd add-genesis-account fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c 5000000000000frt,5000000000000stake +./fairyringd add-genesis-account fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c 5000000000000frt,5000000000000stake,80000ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B # Generate a new gentx ./fairyringd gentx star 1000000000stake @@ -38,7 +38,7 @@ echo shrug make inmate anchor acid clock morning stage fiction build chef copy o GENESIS_FILE_PATH="$HOME/.fairyring/config/genesis.json" # Use jq to update "symbol_requests" in the genesis.json file -jq '.app_state.pricefeed.symbol_requests = [{"symbol": "OSMO", "oracle_script_id": 396, "block_interval": 2, "price_step": 1000000}]' $GENESIS_FILE_PATH > temp.json +jq '.app_state.pricefeed.symbol_requests = [{"symbol": "OSMO", "oracle_script_id": 396, "block_interval": 10, "price_step": 1000000}]' $GENESIS_FILE_PATH > temp.json jq '.app_state.keyshare.params.trusted_addresses = ["fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c"]' temp.json > temp2.json jq '.app_state.keyshare.validatorSetList = [{"validator":"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c", "index": "fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c", "isActive":true}]' temp2.json > temp3.json diff --git a/testutil/conditionalenc/readme.md b/testutil/conditionalenc/readme.md deleted file mode 100644 index 4ae6cd45..00000000 --- a/testutil/conditionalenc/readme.md +++ /dev/null @@ -1,298 +0,0 @@ -# Threshold Encrypted Stop Loss Orders - -This document describes how to enable threshold encrypted stop loss orders on Osmosis using Fairblock. - -## Motivation - -Existing protocols like 1inch or 0x that support limit orders/stop orders are all in the clear. -Generally, this is not advisable as searchers/adversaries are able to see key levels of a particular trading pair and possibly manipulate the market to trigger your order. -By encrypting the contents of a limit order, this market manipulation is prevented. - -## Design - -We give a simple example for what the desired user flow should look like. - -User has 500OSMO. The current price (TWAP oracle) is 0.5USDC/OSMO. -The user places a limit sell for 500OSMO with a trigger price of 0.4USDC/OSMO. -When the current price (TWAP oracle) `<= 0.4`, the user's stop loss is triggered, and ends with ~200USDC (definitely less due to fees, slippage, etc). - -Using Fairblock's `conditionalenc` module, the functionality to arbitrarily encrypt any Osmosis message/transaction using any ID is possible without requiring the module to be installed on Osmosis. - -For stop loss orders, we encrypt using a combination of the TWAP (at fix-width ticks) and a global nonce. - -We use the global nonce as a way to keep track of how many times a particular price tick has been hit. -This is because once a particular nonce has been used, the decryption key will already be released and future orders would already be able to be decrypted. - -### Encryption of User Orders - -We use an encrypted version of swap functions available in the 0xsquid contract to faciliate the stop loss. -Using the above example, the current TWAP is 0.5USDC/OSMO. -We encrypt the swap message using the ID `(0.4USDC/OSMO, 0)` -assuming the current global nonce for all ticks is 0, tick size of 0.05, and a reasonable `TokenOutMinAmount` value. -Then we submit the encrypted payload in a `MsgSubmitEncryptedTx` message. - -Suppose the following block, the TWAP becomes 0.35USDC/OSMO. Then, the following execution flow occurs: - -1. Decryption key request is sent for `(trigger, nonce) = (0.5USDC/OSMO, 0), (0.45USDC/OSMO, 0), (0.4USDC/OSMO, 0), (0.35USDC/OSMO, 0)`. -2. Increment global nonce for the affected ticks: `(trigger, new_nonce) = (0.5USDC/OSMO, 1), (0.45USDC/OSMO, 1), ...`. -3. For every encrypted limit order in store: - - attempt to decrypt the transaction using all released keys - - relay the transaction to the 0xsquid contract on Osmosis chain - -### Execution Details - -We provide some additional details on how execution might occur for these types of orders. -In order to execute the swap messages on Osmosis chain, we require an ibc relayer between Fairyring and Osmosis chains. The validity of the decrypted swap messages is not verified on Fairyring so it is important to ensure the correctness of the swap message in advance to prevent fund losses. - -To address the issue of spam on the network, one can perhaps make this service available to only OSMO stakers. -Another alternative is to have a fixed fee for submitting an order for the network. -Finally, one can add an expiry mechanism so that old orders are flushed and removed from state (maybe the fixed fee is a function of time in force). - -## Architecture Diagram - -![](./assets/osmosis-swap.png) - ---- - -## Other Notes (Don't publish) - -There are a lot of uncertainty around execution. -For example, if there are many stop loss orders at a particular tick, users may incur a high slippage. -We want to guarantee execution, however this would also depend on the slippage limits a user sets. -Setting `TokenOutMinAmount` as the minimum possible value would potentially drain a pool. -Perhaps batching the stop loss orders at each tick, executing the swap, and then running `protorev` might result in better price execution. - -Another detail is on the quantity of these orders. An attacker may be able to flood the chain with dummy orders and slow down decryption. -Perhaps an expiry mechanism can be added, so that really old orders are flushed and removed from the state. -Alternatively, some sort of collateral/fee can be used to address spam issue. - -## Osmosis Swap Test (Local Osmosis) - -In order to test the swap functionality using the squid contract on Osmosis chain, follow the below steps: - -1. Clone the osmosis chain repository in the same directory where the fairyring direcotry is located. - -```bash -git clone git@github.com:osmosis-labs/osmosis.git -``` - -2. Setup and run the Fairyring chain using the provided script in `fairyring/testutil/swap-test/start-fairy.sh`. -When running this script, it asks for a number `i` to set the chain id as `fairytest-i`. - - -3. Setup and run a local version of Osmosis chain using the provided script in `fairyring/testutil/swap-test/start-osmo.sh`. - -4. Start the IBC relayer using the provided script in `fairyring/testutil/swap-test/relayer.sh`. -Input the same value for `i` as in step 2. - -5. Perform a swap from `frt` to `uosmo` using the provided script in `fairyring/testutil/swap-test/test-swap.sh`. -This script will send some `frt` to the Osmosis chain to be able to create a pool. Then deploys the squid contract and creates the pool. -Finally, it will send a swap packet to the contract and after receiving the output `uosmo` in the format of an ibc transferred token, it will query and show the balance of the user on Fairyring chain which now includes the new swapped token. - -## Osmosis Swap Test (Osmosis Testnet) -In order to perform the swap test through the squid contract on Osmosis testnet, follow the below instructions: - -1. Setup and run the Fairyring chain as previously explained - -2. Create a client node through running the bellow command: -```bash -curl -sL https://get.osmosis.zone/install > i.py && python3 i.py -``` -Next, create a new key: -```bash -osmosisd keys add wallet -``` -Copy the mnemonic into the `testutil/osmosis-testnet/mnemonic-osmosis.txt`. -Using the osmosis official faucet, deposit osmos to the generated address. - -3. Start the IBC relayer using the provided script in `fairyring/testutil/osmosis-testnet/relayer.sh`. - -4. In order to use one of the current pools on osmosis tesnet, transfer some osmos to the fairyring: -```bash -osmosisd tx ibc-transfer transfer transfer ${channel-id} fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c 10000uosmo --from wallet --fees 416uosmo --gas auto --gas-adjustment 1.5 -b block -``` - -5. Replace the `channel` parameter in the MEMO in `testutil/osmosis-testnet/test-swap.sh` script with the current channel id on the osmosis side, and the ibc bridged token sent in the transfer command with the denom of the briged osmo on fairyring. -Then, run the script to perform the swap. The result of the swap can be checked through noticing the new ibc bridged token on the user's balance on fairyring. - -## Osmosis Swap Test (Osmosis Testnet and conditionalenc) -In order to perform the swap test through the squid contract on Osmosis testnet, follow the below instructions: - -1. Setup and run the Fairyring chain as previously explained - -2. Create a client node through running the bellow command: -```bash -curl -sL https://get.osmosis.zone/install > i.py && python3 i.py -``` -Next, create a new key: -```bash -osmosisd keys add wallet -``` -Copy the mnemonic into the `testutil/conditionalenc-testnet/mnemonic-osmosis.txt`. -Using the osmosis official faucet, deposit osmos to the generated address. - -3. Start the IBC relayer using the provided script in `fairyring/testutil/conditionalenc-testnet/relayer.sh`. - -4. In order to use one of the current pools on osmosis tesnet, transfer some osmos to the fairyring: -```bash -osmosisd tx ibc-transfer transfer transfer ${channel-id} fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c 10000uosmo --from wallet --fees 416uosmo --gas auto --gas-adjustment 1.5 -b block -``` -The `channel-id` is the id of the created channel on the osmosis side and can be determined through the relayer logs. - -5. Replace the `channel` parameter in the MEMO in `testutil/conditionalenc-tesnet/encrypter/main.go` with the current channel id on the osmosis side, and the ibc bridged token sent in the transfer command with the denom of the briged osmo on fairyring. The ibc token name can be determined through running the `../../fairyringd query bank balances fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c --node tcp://127.0.0.1:26659` and finding the token starting with `ibc/`. -Then, run the `send-tx.sh` script to perform the swap. The result of the swap can be checked through noticing the new ibc bridged token on the user's balance on fairyring. - -## Osmosis Conditional Encryption - -In order to test the swap functionality through the conditional encryption using local osmosis chain, follow the below steps: - -1. Setup and run the Fairyring chain using the provided script in `fairyring/testutil/conditionalenc/start-fairy.sh`. -When running this script, it asks for a number `i` to set the chain id as `fairytest-i`. - -2. Setup and run a local version of Osmosis chain using the provided script in `fairyring/testutil/conditionalenc/start-osmo.sh`. - -3. Start the IBC relayers using the provided script in `fairyring/testutil/conditionalenc/relayer.sh`. -Input the same value for `i` as in step 2. - -4. Perform the initial transfer, create the pool and the contract by running the script in `fairyring/testutil/conditionalenc/setup-pool.sh`. - -5. Send the encrypted tx and submit pk and shares using the script in `fairyring/testutil/conditionalenc/send-tx.sh`. The message for the encrypted tx is hardcoded in `fairyring/testutil/conditionalenc/encrypter/main.go`. Also, it requires the `DistributedIBE` to be present in the same directory as fairyring. When running, it asks for the id you want to use for the encryption. The chain by default only checks for the ETH prices. So you can wait for a specific price to be reached and it will be shown in the logs like this ` =======================> {[1ETH1887399056900] } `. The `1ETH1887399056900` can be used as the id for encryption. -The hardcoded message is the following message converted to []byte: -``` -coin := am.keeper.MinGasPrice(ctx) -coin.Amount = sdk.NewIntFromUint64(500) - -cosmWasmPacketData := transfertypes.MsgTransfer{ - SourcePort: "transfer", - SourceChannel: "channel-1", - Token: coin, - Sender: "fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c", - Receiver: "osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9", - TimeoutTimestamp: uint64(ctx.BlockTime().UnixNano()+int64(180000*time.Minute)), - TimeoutHeight: types1.NewHeight(10000000000,100000000000), - Memo: `{"wasm":{"contract":"osmo14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sq2r9g9", "msg":{"swap_with_action":{"swap_msg":{"token_out_min_amount":"10","path":[{"pool_id":"1","token_out_denom":"uosmo"}]},"after_swap_action":{"ibc_transfer":{"receiver":"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c","channel":"channel-0"}},"local_fallback_address":"osmo12smx2wdlyttvyzvzg54y2vnqwq2qjateuf7thj"}}}}`, - } -``` - -After the following steps, as it can be seen in the logs, the tx will be decrypted and sent to osmosis. The provided example performs a swap and the result tokens will be sent to fairyring. The new token can be seen throguh running: - -```bash -../../fairyringd query bank balances fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c --node tcp://127.0.0.1:26659 -``` - -### Message Format and Inputs - -The encrypted transactions are defined as follows: - -```go -type MsgSubmitEncryptedTx struct { - Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"` - Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` - Condition string `protobuf:"bytes,3,opt,name=condition,proto3" json:"condition,omitempty"` -} -``` -The `Condition` field is a string created by the concatenation of a nonce, a token name and a price. For instance, if the user wants to submit a limit loss order which triggers when the price of `ETH` reaches `1887399056900` and the queried nonce from the chain for this specific price and token is `1`, the condition will be set to `1ETH1887399056900` and the transaction will be decrypted once the price of `ETH` reaches the mentioned amount. - -Next is the `Data` field which inlcudes the encrypted value of the tx. The message that is encrypted is structured as follows: - -```go -type MsgTransfer struct { - // the port on which the packet will be sent - SourcePort string `protobuf:"bytes,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty" yaml:"source_port"` - // the channel by which the packet will be sent - SourceChannel string `protobuf:"bytes,2,opt,name=source_channel,json=sourceChannel,proto3" json:"source_channel,omitempty" yaml:"source_channel"` - // the tokens to be transferred - Token types.Coin `protobuf:"bytes,3,opt,name=token,proto3" json:"token"` - // the sender address - Sender string `protobuf:"bytes,4,opt,name=sender,proto3" json:"sender,omitempty"` - // the recipient address on the destination chain - Receiver string `protobuf:"bytes,5,opt,name=receiver,proto3" json:"receiver,omitempty"` - // Timeout height relative to the current block height. - // The timeout is disabled when set to 0. - TimeoutHeight types1.Height `protobuf:"bytes,6,opt,name=timeout_height,json=timeoutHeight,proto3" json:"timeout_height" yaml:"timeout_height"` - // Timeout timestamp in absolute nanoseconds since unix epoch. - // The timeout is disabled when set to 0. - TimeoutTimestamp uint64 `protobuf:"varint,7,opt,name=timeout_timestamp,json=timeoutTimestamp,proto3" json:"timeout_timestamp,omitempty" yaml:"timeout_timestamp"` - // optional memo - Memo string `protobuf:"bytes,8,opt,name=memo,proto3" json:"memo,omitempty"` -} -``` -For our specific use case, the `SourcePort` should always be set to `transfer` and the value for the `SourceChannel` should be set to `channel-1`. -The Token is the token type and amount that the user chooses and is going to be transferred for the swap input. The Token can be defined like this: - -```go -import sdk "github.com/cosmos/cosmos-sdk/types" -. -. -. -coin := sdk.Coin{Amount: sdk.NewIntFromUint64(500), Denom: "frt"} -``` -The `Sender` is the user address on our chain. -The `Receiver` is the address of the smart contract on osmosis chain which can for the test purpose be set to `osmo1zl9ztmwe2wcdvv9std8xn06mdaqaqm789rutmazfh3z869zcax4sv0ctqw`. -The `TimeoutHeight` can be defined as below for now: -```go -import types1 "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" -. -. -. -TimeoutHeight := types1.NewHeight(100000000000,1000000000000) -``` -The `TimeoutTimestamp` can be defined as below for now: -```go -import "time" -. -. -. -TimeoutTimestamp := uint64(time.Now().UnixNano()+int64(280000*time.Minute)) -``` -An important field is the `Memo` which defines the details for the swap. The `Memo` should be a json formatted string like the below example: - -```json -{ - "wasm": { - "contract": "osmo1zl9ztmwe2wcdvv9std8xn06mdaqaqm789rutmazfh3z869zcax4sv0ctqw", - "msg": { - "swap_with_action": { - "swap_msg": { - "token_out_min_amount": "10", - "path": [ - { - "pool_id": "74", - "token_out_denom": "uion" - } - ] - }, - "after_swap_action": { - "ibc_transfer": { - "receiver": "fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c", - "channel": "channel-4293" - } - }, - "local_fallback_address": "osmo1pw5aj2u5thkgumkpdms0x78y97e6ppfl6vmjpd" - } - } - } -} -``` -- The `contract` value should be the same value as the `Receiver` filed in the `MsgTransfer`. -- The `token_out_min_amount` can be choosen by the user, but for testing, set it to `1`or `10`. -- The `pool_id` depends on the input and output tokens but for the test purpose, it can be set to any value like `47`. -- The `token_out_denom` is the swap output token defined by the user. -- The `receiver` is the address for the output token to be sent to. For instance it can be the same as the sender address in `MsgTransfer`. -- The `channel` is the channel id on the osmosis chain which is connected to fairyring. For the test, it can be set to `channel-4293`. -- The `local_fallback_address` is the address on osmosis chain which in case the ibc transfer did not work, the output tokens will be sent to. The value for the test can be set to `osmo1pw5aj2u5thkgumkpdms0x78y97e6ppfl6vmjpd` or the contract address. - -Values such as `pool_id` or `channel` should be hardcoded in the frontend. For instance, the frontend needs to know which pool id to use for each set of input and output token defined by the user. Later, we might add the option so that the user can choose these values on their own. - -After setting all fields in the `MsgTransfer` it should be marshalized to `[]byte` can be encrypted. The encryption output will go in the `Data` field of the transaction. - -The remaining field of the transaction is the `Creator` which will be set as any other transaction. - -#### Nonce Query -In order to get the nonce for a token and a specific price, you can use the following query: - -```bash -fairyringd query pricefeed current-nonce {denom} {price} -``` - -The nonce for a denom and a price increases each time the price reaches that price. The nonce ensures that the old txs encrypted for a specific price cannot be sent again when the price reaches the same value later on. \ No newline at end of file diff --git a/testutil/osmosis-testnet/test-swap.sh b/testutil/osmosis-testnet/test-swap.sh index 5c9d8d3d..9f7b39a3 100755 --- a/testutil/osmosis-testnet/test-swap.sh +++ b/testutil/osmosis-testnet/test-swap.sh @@ -1,6 +1,6 @@ -MEMO=$(../../jenv/jenv.sh -c '{"wasm":{"contract":"osmo1zl9ztmwe2wcdvv9std8xn06mdaqaqm789rutmazfh3z869zcax4sv0ctqw","msg":{"swap_with_action":{"swap_msg":{"token_out_min_amount":"10","path":[{"pool_id":"74","token_out_denom":"uion"}]},"after_swap_action":{"ibc_transfer":{"receiver":"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c","channel":"channel-4293"}},"local_fallback_address":"osmo1pw5aj2u5thkgumkpdms0x78y97e6ppfl6vmjpd"}}}}') +MEMO=$(../../jenv/jenv.sh -c '{"wasm":{"contract":"osmo1zl9ztmwe2wcdvv9std8xn06mdaqaqm789rutmazfh3z869zcax4sv0ctqw","msg":{"swap_with_action":{"swap_msg":{"token_out_min_amount":"10","path":[{"pool_id":"74","token_out_denom":"uion"}]},"after_swap_action":{"ibc_transfer":{"receiver":"fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c","channel":"channel-4860"}},"local_fallback_address":"osmo1pw5aj2u5thkgumkpdms0x78y97e6ppfl6vmjpd"}}}}') -../../fairyringd tx ibc-transfer transfer transfer channel-0 osmo1zl9ztmwe2wcdvv9std8xn06mdaqaqm789rutmazfh3z869zcax4sv0ctqw 20000ibc/ED07A3391A112B175915CD8FAF43A2DA8E4790EDE12566649D0C2F97716B8518 --keyring-backend test --node tcp://127.0.0.1:26659 --from star -y --memo "$MEMO" -b async +../../fairyringd tx ibc-transfer transfer transfer channel-1 osmo1zl9ztmwe2wcdvv9std8xn06mdaqaqm789rutmazfh3z869zcax4sv0ctqw 20000ibc/0471F1C4E7AFD3F07702BEF6DC365268D64570F7C1FDC98EA6098DD6DE59817B --node tcp://127.0.0.1:26659 --from fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c -y --memo "$MEMO" -b async sleep 20 diff --git a/testutil/swap-test/readme.md b/testutil/swap-test/readme.md deleted file mode 100644 index 2119ef99..00000000 --- a/testutil/swap-test/readme.md +++ /dev/null @@ -1,111 +0,0 @@ -# Threshold Encrypted Stop Loss Orders - -This document describes how to enable threshold encrypted stop loss orders on Osmosis using Fairblock. - -## Motivation - -Existing protocols like 1inch or 0x that support limit orders/stop orders are all in the clear. -Generally, this is not advisable as searchers/adversaries are able to see key levels of a particular trading pair and possibly manipulate the market to trigger your order. -By encrypting the contents of a limit order, this market manipulation is prevented. - -## Design - -We give a simple example for what the desired user flow should look like. - -User has 500OSMO. The current price (TWAP oracle) is 0.5USDC/OSMO. -The user places a limit sell for 500OSMO with a trigger price of 0.4USDC/OSMO. -When the current price (TWAP oracle) `<= 0.4`, the user's stop loss is triggered, and ends with ~200USDC (definitely less due to fees, slippage, etc). - -Using Fairblock's `conditionalenc` module, the functionality to arbitrarily encrypt any Osmosis message/transaction using any ID is possible without requiring the module to be installed on Osmosis. - -For stop loss orders, we encrypt using a combination of the TWAP (at fix-width ticks) and a global nonce. - -We use the global nonce as a way to keep track of how many times a particular price tick has been hit. -This is because once a particular nonce has been used, the decryption key will already be released and future orders would already be able to be decrypted. - -### Encryption of User Orders - -We use an encrypted version of swap functions available in the 0xsquid contract to faciliate the stop loss. -Using the above example, the current TWAP is 0.5USDC/OSMO. -We encrypt the swap message using the ID `(0.4USDC/OSMO, 0)` -assuming the current global nonce for all ticks is 0, tick size of 0.05, and a reasonable `TokenOutMinAmount` value. -Then we submit the encrypted payload in a `MsgSubmitEncryptedTx` message. - -Suppose the following block, the TWAP becomes 0.35USDC/OSMO. Then, the following execution flow occurs: - -1. Decryption key request is sent for `(trigger, nonce) = (0.5USDC/OSMO, 0), (0.45USDC/OSMO, 0), (0.4USDC/OSMO, 0), (0.35USDC/OSMO, 0)`. -2. Increment global nonce for the affected ticks: `(trigger, new_nonce) = (0.5USDC/OSMO, 1), (0.45USDC/OSMO, 1), ...`. -3. For every encrypted limit order in store: - - attempt to decrypt the transaction using all released keys - - relay the transaction to the 0xsquid contract on Osmosis chain - -### Execution Details - -We provide some additional details on how execution might occur for these types of orders. -In order to execute the swap messages on Osmosis chain, we require an ibc relayer between Fairyring and Osmosis chains. The validity of the decrypted swap messages is not verified on Fairyring so it is important to ensure the correctness of the swap message in advance to prevent fund losses. - -To address the issue of spam on the network, one can perhaps make this service available to only OSMO stakers. -Another alternative is to have a fixed fee for submitting an order for the network. -Finally, one can add an expiry mechanism so that old orders are flushed and removed from state (maybe the fixed fee is a function of time in force). - -## Architecture Diagram - -![](./assets/osmosis-swap.png) - ---- - -## Other Notes (Don't publish) - -There are a lot of uncertainty around execution. -For example, if there are many stop loss orders at a particular tick, users may incur a high slippage. -We want to guarantee execution, however this would also depend on the slippage limits a user sets. -Setting `TokenOutMinAmount` as the minimum possible value would potentially drain a pool. -Perhaps batching the stop loss orders at each tick, executing the swap, and then running `protorev` might result in better price execution. - -Another detail is on the quantity of these orders. An attacker may be able to flood the chain with dummy orders and slow down decryption. -Perhaps an expiry mechanism can be added, so that really old orders are flushed and removed from the state. -Alternatively, some sort of collateral/fee can be used to address spam issue. - -## Osmosis Swap Test - -In order to test the swap functionality using the squid contract on Osmosis chain, follow the below steps: - -1. Clone the osmosis chain repository in the same directory where the fairyring direcotry is located. - -```bash -git clone git@github.com:osmosis-labs/osmosis.git -``` - -2. Setup and run the Fairyring chain using the provided script in `fairyring/testutil/swap-test/start-fairy.sh`. -When running this script, it asks for a number `i` to set the chain id as `fairytest-i`. - -3. Setup and run a local version of Osmosis chain using the provided script in `fairyring/testutil/swap-test/start-osmo.sh`. - -4. Start the IBC relayer using the provided script in `fairyring/testutil/swap-test/relayer.sh`. -Input the same value for `i` as in step 2. - -5. Perform a swap from `frt` to `uosmo` using the provided script in `fairyring/testutil/swap-test/test-swap.sh`. -This script will send some `frt` to the Osmosis chain to be able to create a pool. Then deploys the squid contract and creates the pool. -Finally, it will send a swap packet to the contract and after receiving the output `uosmo` in the format of an ibc transferred token, it will query and show the balance of the user on Fairyring chain which now includes the new swapped token. - -## Osmosis Conditional Encryption - -In order to test the swap functionality through the conditional encryption using local osmosis chain, follow the below steps: - -1. Setup and run the Fairyring chain using the provided script in `fairyring/testutil/conditionalenc/start-fairy.sh`. -When running this script, it asks for a number `i` to set the chain id as `fairytest-i`. - -2. Setup and run a local version of Osmosis chain using the provided script in `fairyring/testutil/conditionalenc/start-osmo.sh`. - -3. Start the IBC relayers using the provided script in `fairyring/testutil/conditionalenc/relayer.sh`. -Input the same value for `i` as in step 2. - -4. Perform the initial transfer, create the pool and the contract by running the script in `fairyring/testutil/conditionalenc/setup-pool.sh`. - -5. Send the encrypted tx and submit pk and shares using the script in `fairyring/testutil/conditionalenc/send-tx.sh`. - -After the following steps, as it can be seen in the logs, the tx will be decrypted and sent to osmosis. The provided example performs a swap and the result tokens will be sent to fairyring. The new token can be seen throguh running: - -```bash -../../fairyringd query bank balances fairy1p6ca57cu5u89qzf58krxgxaezp4wm9vu7lur3c --node tcp://127.0.0.1:26659 -``` \ No newline at end of file diff --git a/x/conditionalenc/keeper/msg_server_submit_encrypted_tx.go b/x/conditionalenc/keeper/msg_server_submit_encrypted_tx.go index bf0959bc..72f551ce 100644 --- a/x/conditionalenc/keeper/msg_server_submit_encrypted_tx.go +++ b/x/conditionalenc/keeper/msg_server_submit_encrypted_tx.go @@ -16,7 +16,8 @@ import ( func (k msgServer) SubmitEncryptedTx(goCtx context.Context, msg *types.MsgSubmitEncryptedTx) (*types.MsgSubmitEncryptedTxResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - + _, new_c := modifyCondition(msg.Condition) + msg.Condition = new_c // err := verifyNonce(msg.Condition, k, ctx) // if err != nil { // return nil, err @@ -84,4 +85,24 @@ func verifyNonce(condition string, k msgServer, ctx sdk.Context) error { } else { return fmt.Errorf("no match") } +} + +func modifyCondition(condition string) (error,string){ + re, err := regexp.Compile(`(\d+)([a-zA-Z]+)(\d+)`) + if err != nil { + fmt.Println("Error compiling regex:", err) + return err,"" + } + var price = 0 + // Find matches + matches := re.FindStringSubmatch(condition) + if matches != nil && len(matches) > 3 { + price, err = strconv.Atoi(matches[3]) + if err != nil { + return err,"" + } + price = price/1000000 + price = price * 1000000 + } + return nil,matches[1]+matches[2]+fmt.Sprint(price) } \ No newline at end of file diff --git a/x/conditionalenc/module.go b/x/conditionalenc/module.go index d9534c49..8f0a16ee 100644 --- a/x/conditionalenc/module.go +++ b/x/conditionalenc/module.go @@ -367,7 +367,7 @@ func (am AppModule) BeginBlock(ctx sdk.Context, b abci.RequestBeginBlock) { waitingList := am.pricefeedKeeper.GetList(ctx) - logrus.Info("Latest met condition: ---------------------> ", waitingList.LatestMetCondition) + logrus.Info("Latest met condition: ---------------------> ", waitingList.List) allAggKey := am.keeper.GetAllAggregatedConditionalKeyShare(ctx) am.keeper.Logger(ctx).Info(fmt.Sprintf("[Conditionalenc][AGGKEY] %v", allAggKey)) @@ -488,7 +488,7 @@ func (am AppModule) BeginBlock(ctx sdk.Context, b abci.RequestBeginBlock) { } } - + am.keeper.RemoveAllEncryptedTxFromCondition(ctx, item) am.pricefeedKeeper.RemoveFromList(ctx, item) } diff --git a/x/pricefeed/keeper/grpc_query.go b/x/pricefeed/keeper/grpc_query.go index a6e7c66e..f1c5f44f 100644 --- a/x/pricefeed/keeper/grpc_query.go +++ b/x/pricefeed/keeper/grpc_query.go @@ -6,7 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/sirupsen/logrus" + //"github.com/sirupsen/logrus" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -67,7 +67,7 @@ func (k Querier) Price(c context.Context, req *types.QueryPrice) (*types.QueryPr } func (k Querier) CurrentNonce(goCtx context.Context, req *types.QueryCurrentNonceRequest) (*types.QueryCurrentNonceResponse, error) { - logrus.Info("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@") + if req == nil { return nil, status.Error(codes.InvalidArgument, "invalid request") } diff --git a/x/pricefeed/keeper/keeper.go b/x/pricefeed/keeper/keeper.go index a32e7eb8..b114c555 100644 --- a/x/pricefeed/keeper/keeper.go +++ b/x/pricefeed/keeper/keeper.go @@ -23,7 +23,9 @@ import ( clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" host "github.com/cosmos/ibc-go/v7/modules/core/24-host" -// "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" + + // "github.com/sirupsen/logrus" //"github.com/sirupsen/logrus" @@ -173,6 +175,10 @@ func (k Keeper) GetRepeatedPrice(ctx sdk.Context, price types.Price) uint64 { } func (k Keeper) UpdatePrice(ctx sdk.Context, price types.Price) (bool, int64, int64) { + p := price.Price + p = p/1000000 + p = p*1000000 + price.Price = p old, found := k.GetPrice(ctx, price.Symbol) if !found || old.ResolveTime < price.ResolveTime { @@ -415,7 +421,7 @@ func (k Keeper) GetLatestCondition(ctx sdk.Context, symbol string) string { } // RemoveFromList removes an item from the waiting list func (k Keeper) RemoveFromList(ctx sdk.Context, item string) { - symbol := extractPrefix(item) + symbol := ExtractNonDigits(item) store := ctx.KVStore(k.storeKey) // Load current list var waitingList WaitingList @@ -423,9 +429,12 @@ func (k Keeper) RemoveFromList(ctx sdk.Context, item string) { if bz != nil { MustUnmarshalJSON(bz, &waitingList) } + logrus.Info("-----------------------------------------: ", symbol, item, waitingList.List) // Remove for i, v := range waitingList.List { + logrus.Info("----------------------------------------- v: ", v) if v == item { + logrus.Info("-----------------------------------------",waitingList.List[:i],waitingList.List[i+1:], item) waitingList.List = append(waitingList.List[:i], waitingList.List[i+1:]...) break } @@ -433,19 +442,14 @@ func (k Keeper) RemoveFromList(ctx sdk.Context, item string) { // Save store.Set([]byte(symbol+"waitingList"), MustMarshalJSON(waitingList)) } -func extractPrefix(s string) string { - // Get slice of runes from the string - runes := []rune(s) - - // Iterate through runes until we hit a digit - for i, r := range runes { - if unicode.IsDigit(r) { - return string(runes[:i]) - } - } - - // If there are no digits, return the entire string - return s +func ExtractNonDigits(s string) string { + var result strings.Builder + for _, char := range s { + if !unicode.IsDigit(char) { + result.WriteRune(char) + } + } + return result.String() } func extractNumber(s, symbol string) (int, error) { parts := strings.Split(s, symbol) @@ -457,6 +461,12 @@ func extractNumber(s, symbol string) (int, error) { } // StoreOracleResponsePacket is a function that receives an OracleResponsePacketData from BandChain. func (k Keeper) StoreOracleResponsePacket(ctx sdk.Context, res bandtypes.OracleResponsePacketData) error { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + "price_fetched", + + ), + ) // Decode the result from the response packet. k.mu.Lock() diff --git a/x/pricefeed/types/query.pb.go b/x/pricefeed/types/query.pb.go index 79f4ec65..380813b2 100644 --- a/x/pricefeed/types/query.pb.go +++ b/x/pricefeed/types/query.pb.go @@ -547,6 +547,7 @@ type QueryClient interface { // Price queries the current price data for a specific symbol. // Returns the current price for the given symbol. Price(ctx context.Context, in *QueryPrice, opts ...grpc.CallOption) (*QueryPriceResponse, error) + // Returns the current nonce for the given denom. CurrentNonce(ctx context.Context, in *QueryCurrentNonceRequest, opts ...grpc.CallOption) (*QueryCurrentNonceResponse, error) } func (c *queryClient) CurrentNonce(ctx context.Context, in *QueryCurrentNonceRequest, opts ...grpc.CallOption) (*QueryCurrentNonceResponse, error) { @@ -615,6 +616,7 @@ type QueryServer interface { // Price queries the current price data for a specific symbol. // Returns the current price for the given symbol. Price(context.Context, *QueryPrice) (*QueryPriceResponse, error) + // Returns the current nonce for the given denom. CurrentNonce(context.Context, *QueryCurrentNonceRequest) (*QueryCurrentNonceResponse, error) } @@ -622,9 +624,7 @@ type QueryServer interface { // UnimplementedQueryServer can be embedded to have forward compatible implementations. type UnimplementedQueryServer struct { } -func (*UnimplementedQueryServer) CurrentNonce(ctx context.Context, req *QueryCurrentNonceRequest) (*QueryCurrentNonceResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method CurrentNonce not implemented") -} + func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") } @@ -637,7 +637,9 @@ func (*UnimplementedQueryServer) SymbolRequests(ctx context.Context, req *QueryS func (*UnimplementedQueryServer) Price(ctx context.Context, req *QueryPrice) (*QueryPriceResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Price not implemented") } - +func (UnimplementedQueryServer) CurrentNonce(context.Context, *QueryCurrentNonceRequest) (*QueryCurrentNonceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CurrentNonce not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) } diff --git a/x/pricefeed/types/query.pb.gw.go b/x/pricefeed/types/query.pb.gw.go index afddf7c1..dd4cadfa 100644 --- a/x/pricefeed/types/query.pb.gw.go +++ b/x/pricefeed/types/query.pb.gw.go @@ -17,7 +17,7 @@ import ( "github.com/golang/protobuf/proto" "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/grpc-ecosystem/grpc-gateway/utilities" - "github.com/sirupsen/logrus" + //"github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/grpclog" @@ -79,6 +79,8 @@ func request_Query_SymbolRequest_0(ctx context.Context, marshaler runtime.Marsha } + + func local_request_Query_SymbolRequest_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq QuerySymbolRequest var metadata runtime.ServerMetadata @@ -106,37 +108,7 @@ func local_request_Query_SymbolRequest_0(ctx context.Context, marshaler runtime. } -func local_request_Query_CurrentNonce_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryCurrentNonceRequest - var metadata runtime.ServerMetadata -logrus.Info("&&&&&&&&&&&&&&&&&&&^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^") - var ( - val string - val2 string - ok bool - err error - _ = err - ) - - val, ok = pathParams["denom"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom") - } - protoReq.Denom, err = runtime.String(val) - val2, ok = pathParams["price"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom") - } - protoReq.Price, err = runtime.String(val2) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "symbol", err) - } - - msg, err := server.CurrentNonce(ctx, &protoReq) - return msg, metadata, err -} func request_Query_SymbolRequests_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq QuerySymbolRequests @@ -208,6 +180,77 @@ func local_request_Query_Price_0(ctx context.Context, marshaler runtime.Marshale msg, err := server.Price(ctx, &protoReq) return msg, metadata, err +} +func request_Query_CurrentNonce_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryCurrentNonceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["denom"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom") + } + + protoReq.Denom, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err) + } + + val, ok = pathParams["price"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "price") + } + + protoReq.Price, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "price", err) + } + + msg, err := client.CurrentNonce(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_CurrentNonce_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryCurrentNonceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["denom"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom") + } + + protoReq.Denom, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err) + } + + val, ok = pathParams["price"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "price") + } + + protoReq.Price, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "price", err) + } + + msg, err := server.CurrentNonce(ctx, &protoReq) + return msg, metadata, err + } // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". @@ -267,20 +310,22 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv var stream runtime.ServerTransportStream ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) - rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req) if err != nil { runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) return } - resp, md, err := local_request_Query_CurrentNonce_0(rctx, inboundMarshaler, server, req, pathParams) + resp, md, err := local_request_Query_CurrentNonce_0(annotatedContext, inboundMarshaler, server, req, pathParams) md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) - ctx = runtime.NewServerMetadataContext(ctx, md) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) if err != nil { - runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) return } - forward_Query_CurrentNonce_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + forward_Query_CurrentNonce_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) @@ -410,7 +455,27 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie forward_Query_SymbolRequest_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle("GET", pattern_Query_CurrentNonce_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_CurrentNonce_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_CurrentNonce_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) mux.Handle("GET", pattern_Query_SymbolRequests_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -455,7 +520,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie } var ( - pattern_Query_CurrentNonce_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"pricefeed", "current_nonce","denom","price"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_Query_CurrentNonce_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 1, 0, 4, 1, 5, 3}, []string{"pricefeed", "current_nonce","denom","price"}, "", runtime.AssumeColonVerbOpt(true))) pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"pricefeed", "params"}, "", runtime.AssumeColonVerbOpt(true)))