From 8051a3fc6b571b38449901f7bcb0a478b3b42049 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Thu, 26 Sep 2024 19:01:41 +0530 Subject: [PATCH] Add a command to create and publish a app deployment auction --- .../webapp/publish_deployment_auction.py | 129 +++++++++++++++ stack_orchestrator/deploy/webapp/util.py | 149 ++++++++---------- stack_orchestrator/main.py | 2 + 3 files changed, 196 insertions(+), 84 deletions(-) create mode 100644 stack_orchestrator/deploy/webapp/publish_deployment_auction.py diff --git a/stack_orchestrator/deploy/webapp/publish_deployment_auction.py b/stack_orchestrator/deploy/webapp/publish_deployment_auction.py new file mode 100644 index 00000000..df96cb0f --- /dev/null +++ b/stack_orchestrator/deploy/webapp/publish_deployment_auction.py @@ -0,0 +1,129 @@ +# Copyright ©2023 Vulcanize +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import shutil +import sys +import tempfile + +import click +import yaml + +from stack_orchestrator.deploy.webapp.util import ( + LaconicRegistryClient, +) + +AUCTION_KIND_PROVIDER = "provider" +TOKEN_DENOM = "alnt" + +def fatal(msg: str): + print(msg, file=sys.stderr) + sys.exit(1) + +# TODO: Add defaults for auction params +@click.command() +@click.option( + "--laconic-config", help="Provide a config file for laconicd", required=True +) +@click.option( + "--app", + help="The LRN of the application to deploy.", + required=True, +) +@click.option( + "--commits-duration", + help="Auction commits duration (in seconds).", + required=True, +) +@click.option( + "--reveals-duration", + help="Auction reveals duration (in seconds).", + required=True, +) +@click.option( + "--commit-fee", + help="Auction bid commit fee (in alnt).", + required=True, +) +@click.option( + "--reveal-fee", + help="Auction bid reveal fee (in alnt).", + required=True, +) +@click.option( + "--max-price", + help="Max acceptable bid price (in alnt).", + required=True, +) +@click.option( + "--num-providers", + help="Max acceptable bid price (in alnt).", + required=True, +) +@click.option( + "--dry-run", + help="Don't publish anything, just report what would be done.", + is_flag=True, +) +@click.pass_context +def command( + ctx, + laconic_config, + app, + commits_duration, + reveals_duration, + commit_fee, + reveal_fee, + max_price, + num_providers, + dry_run, +): # noqa: C901 + tempdir = tempfile.mkdtemp() + try: + laconic = LaconicRegistryClient(laconic_config) + + app_record = laconic.get_record(app) + if not app_record: + fatal(f"Unable to locate app: {app}") + + provider_auction_params = { + "kind": AUCTION_KIND_PROVIDER, + "commits_duration": commits_duration, + "reveals_duration": reveals_duration, + "denom": TOKEN_DENOM, + "commit_fee": commit_fee, + "reveal_fee": reveal_fee, + "max_price": max_price, + "num_providers": num_providers, + } + auction_id = laconic.create_auction(provider_auction_params) + + if not auction_id: + fatal("Unable to create a provider auction") + + deployment_auction = { + "record": { + "type": "ApplicationDeploymentAuction", + "application": app, + "auction": auction_id, + } + } + + if dry_run: + print(yaml.dump(deployment_auction)) + return + + # Publish the deployment auction record + laconic.publish(deployment_auction) + finally: + shutil.rmtree(tempdir, ignore_errors=True) diff --git a/stack_orchestrator/deploy/webapp/util.py b/stack_orchestrator/deploy/webapp/util.py index e8a74fa5..5f0a903f 100644 --- a/stack_orchestrator/deploy/webapp/util.py +++ b/stack_orchestrator/deploy/webapp/util.py @@ -24,9 +24,6 @@ import tempfile import uuid import yaml -AUCTION_KIND_PROVIDER = "provider" -TOKEN_DENOM = "alnt" - class AttrDict(dict): def __init__(self, *args, **kwargs): super(AttrDict, self).__init__(*args, **kwargs) @@ -93,75 +90,6 @@ def is_lrn(name_or_id: str): def is_id(name_or_id: str): return not is_lrn(name_or_id) - -def confirm_payment(laconic, record, payment_address, min_amount, logger): - req_owner = laconic.get_owner(record) - if req_owner == payment_address: - # No need to confirm payment if the sender and recipient are the same account. - return True - - if not record.attributes.payment: - logger.log(f"{record.id}: no payment tx info") - return False - - tx = laconic.get_tx(record.attributes.payment) - if not tx: - logger.log(f"{record.id}: cannot locate payment tx") - return False - - if tx.code != 0: - logger.log( - f"{record.id}: payment tx {tx.hash} was not successful - code: {tx.code}, log: {tx.log}" - ) - return False - - if tx.sender != req_owner: - logger.log( - f"{record.id}: payment sender {tx.sender} in tx {tx.hash} does not match deployment " - f"request owner {req_owner}" - ) - return False - - if tx.recipient != payment_address: - logger.log( - f"{record.id}: payment recipient {tx.recipient} in tx {tx.hash} does not match {payment_address}" - ) - return False - - pay_denom = "".join([i for i in tx.amount if not i.isdigit()]) - if pay_denom != "alnt": - logger.log( - f"{record.id}: {pay_denom} in tx {tx.hash} is not an expected payment denomination" - ) - return False - - pay_amount = int("".join([i for i in tx.amount if i.isdigit()])) - if pay_amount < min_amount: - logger.log( - f"{record.id}: payment amount {tx.amount} is less than minimum {min_amount}" - ) - return False - - # Check if the payment was already used on a - used = laconic.app_deployments( - {"deployer": payment_address, "payment": tx.hash}, all=True - ) - if len(used): - logger.log(f"{record.id}: payment {tx.hash} already used on deployment {used}") - return False - - used = laconic.app_deployment_removals( - {"deployer": payment_address, "payment": tx.hash}, all=True - ) - if len(used): - logger.log( - f"{record.id}: payment {tx.hash} already used on deployment removal {used}" - ) - return False - - return True - - class LaconicRegistryClient: def __init__(self, config_file, log_file=None): self.config_file = config_file @@ -784,16 +712,69 @@ def skip_by_tag(r, include_tags, exclude_tags): return False -def create_provider_auction(laconic: LaconicRegistryClient, params): - auction_params = { - "kind": AUCTION_KIND_PROVIDER, - "commits_duration": params["commits_duration"], - "reveals_duration": params["reveals_duration"], - "denom": TOKEN_DENOM, - "commit_fee": params["commit_fee"], - "reveal_fee": params["reveal_fee"], - "max_price": params["max_price"], - "num_providers": params["num_providers"], - } +def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min_amount, logger): + req_owner = laconic.get_owner(record) + if req_owner == payment_address: + # No need to confirm payment if the sender and recipient are the same account. + return True - return laconic.create_auction(auction_params) + if not record.attributes.payment: + logger.log(f"{record.id}: no payment tx info") + return False + + tx = laconic.get_tx(record.attributes.payment) + if not tx: + logger.log(f"{record.id}: cannot locate payment tx") + return False + + if tx.code != 0: + logger.log( + f"{record.id}: payment tx {tx.hash} was not successful - code: {tx.code}, log: {tx.log}" + ) + return False + + if tx.sender != req_owner: + logger.log( + f"{record.id}: payment sender {tx.sender} in tx {tx.hash} does not match deployment " + f"request owner {req_owner}" + ) + return False + + if tx.recipient != payment_address: + logger.log( + f"{record.id}: payment recipient {tx.recipient} in tx {tx.hash} does not match {payment_address}" + ) + return False + + pay_denom = "".join([i for i in tx.amount if not i.isdigit()]) + if pay_denom != "alnt": + logger.log( + f"{record.id}: {pay_denom} in tx {tx.hash} is not an expected payment denomination" + ) + return False + + pay_amount = int("".join([i for i in tx.amount if i.isdigit()])) + if pay_amount < min_amount: + logger.log( + f"{record.id}: payment amount {tx.amount} is less than minimum {min_amount}" + ) + return False + + # Check if the payment was already used on a + used = laconic.app_deployments( + {"deployer": payment_address, "payment": tx.hash}, all=True + ) + if len(used): + logger.log(f"{record.id}: payment {tx.hash} already used on deployment {used}") + return False + + used = laconic.app_deployment_removals( + {"deployer": payment_address, "payment": tx.hash}, all=True + ) + if len(used): + logger.log( + f"{record.id}: payment {tx.hash} already used on deployment removal {used}" + ) + return False + + return True diff --git a/stack_orchestrator/main.py b/stack_orchestrator/main.py index 5ae10468..be61f68a 100644 --- a/stack_orchestrator/main.py +++ b/stack_orchestrator/main.py @@ -26,6 +26,7 @@ from stack_orchestrator.deploy.webapp import (run_webapp, deploy_webapp_from_registry, undeploy_webapp_from_registry, publish_webapp_deployer, + publish_deployment_auction, request_webapp_deployment) from stack_orchestrator.deploy import deploy from stack_orchestrator import version @@ -64,6 +65,7 @@ cli.add_command(deploy_webapp.command, "deploy-webapp") cli.add_command(deploy_webapp_from_registry.command, "deploy-webapp-from-registry") cli.add_command(undeploy_webapp_from_registry.command, "undeploy-webapp-from-registry") cli.add_command(publish_webapp_deployer.command, "publish-deployer-to-registry") +cli.add_command(publish_deployment_auction.command, "publish-deployment-auction") cli.add_command(request_webapp_deployment.command, "request-webapp-deployment") cli.add_command(deploy.command, "deploy") # deploy is an alias for deploy-system cli.add_command(deploy.command, "deploy-system")