Add a command to create and publish a app deployment auction
This commit is contained in:
parent
03dd265e5f
commit
f17874b80f
129
stack_orchestrator/deploy/webapp/publish_deployment_auction.py
Normal file
129
stack_orchestrator/deploy/webapp/publish_deployment_auction.py
Normal file
@ -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 <http:#www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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)
|
@ -24,9 +24,6 @@ import tempfile
|
|||||||
import uuid
|
import uuid
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
AUCTION_KIND_PROVIDER = "provider"
|
|
||||||
TOKEN_DENOM = "alnt"
|
|
||||||
|
|
||||||
class AttrDict(dict):
|
class AttrDict(dict):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(AttrDict, self).__init__(*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):
|
def is_id(name_or_id: str):
|
||||||
return not is_lrn(name_or_id)
|
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:
|
class LaconicRegistryClient:
|
||||||
def __init__(self, config_file, log_file=None):
|
def __init__(self, config_file, log_file=None):
|
||||||
self.config_file = config_file
|
self.config_file = config_file
|
||||||
@ -784,16 +712,69 @@ def skip_by_tag(r, include_tags, exclude_tags):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def create_provider_auction(laconic: LaconicRegistryClient, params):
|
def confirm_payment(laconic: LaconicRegistryClient, record, payment_address, min_amount, logger):
|
||||||
auction_params = {
|
req_owner = laconic.get_owner(record)
|
||||||
"kind": AUCTION_KIND_PROVIDER,
|
if req_owner == payment_address:
|
||||||
"commits_duration": params["commits_duration"],
|
# No need to confirm payment if the sender and recipient are the same account.
|
||||||
"reveals_duration": params["reveals_duration"],
|
return True
|
||||||
"denom": TOKEN_DENOM,
|
|
||||||
"commit_fee": params["commit_fee"],
|
|
||||||
"reveal_fee": params["reveal_fee"],
|
|
||||||
"max_price": params["max_price"],
|
|
||||||
"num_providers": params["num_providers"],
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -26,6 +26,7 @@ from stack_orchestrator.deploy.webapp import (run_webapp,
|
|||||||
deploy_webapp_from_registry,
|
deploy_webapp_from_registry,
|
||||||
undeploy_webapp_from_registry,
|
undeploy_webapp_from_registry,
|
||||||
publish_webapp_deployer,
|
publish_webapp_deployer,
|
||||||
|
publish_deployment_auction,
|
||||||
request_webapp_deployment)
|
request_webapp_deployment)
|
||||||
from stack_orchestrator.deploy import deploy
|
from stack_orchestrator.deploy import deploy
|
||||||
from stack_orchestrator import version
|
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(deploy_webapp_from_registry.command, "deploy-webapp-from-registry")
|
||||||
cli.add_command(undeploy_webapp_from_registry.command, "undeploy-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_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(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") # deploy is an alias for deploy-system
|
||||||
cli.add_command(deploy.command, "deploy-system")
|
cli.add_command(deploy.command, "deploy-system")
|
||||||
|
Loading…
Reference in New Issue
Block a user