diff --git a/agent/sn_agent/accounting/__init__.py b/agent/sn_agent/accounting/__init__.py index 5f74db6..cdc1888 100644 --- a/agent/sn_agent/accounting/__init__.py +++ b/agent/sn_agent/accounting/__init__.py @@ -8,12 +8,22 @@ from abc import ABC from sn_agent.accounting.settings import AccountingSettings +from sn_agent.api import internal_perform_job from sn_agent.job.job_descriptor import JobDescriptor +from sn_agent.network.sn import MarketJob + + +class PriceTooLowException(Exception): + pass + +class IncorrectContractState(Exception): + pass class Accounting(ABC): def __init__(self, app): self.app = app self.settings = AccountingSettings() + self.network = app['network'] def job_is_contracted(self, job: JobDescriptor): if not job is None: @@ -21,5 +31,26 @@ def job_is_contracted(self, job: JobDescriptor): else: return False + def incoming_offer(self, service_id, price): + + if price < 0: + raise PriceTooLowException() + + market_job = self.network.create_market_job(service_id, price) + + return market_job.address + + def perform_job(self, market_job_address, service_id, job_params): + + market_job = self.network.get_market_job(market_job_address) + + if market_job.state != MarketJob.PENDING: + raise IncorrectContractState() + + result = internal_perform_job(self.app, service_id, job_params) + + market_job.set_state = MarketJob.COMPLETED + return result + def setup_accounting(app): app['accounting'] = Accounting(app) diff --git a/agent/sn_agent/api/__init__.py b/agent/sn_agent/api/__init__.py index 32c9d26..f3fd6cd 100644 --- a/agent/sn_agent/api/__init__.py +++ b/agent/sn_agent/api/__init__.py @@ -5,10 +5,7 @@ from aiohttp.web_response import Response from jsonrpcserver.aio import methods -from sn_agent import ontology -from sn_agent.api.job import can_perform_service, perform_job -from sn_agent.job.job_descriptor import JobDescriptor -from sn_agent.ontology.service_descriptor import ServiceDescriptor +from sn_agent.api.job import internal_perform_job, internal_offer, internal_can_perform logger = logging.getLogger(__name__) @@ -17,22 +14,26 @@ @methods.add async def can_perform(service_node_id=None, context=None): - # figure out what we are being asked to perform and answer - service = ServiceDescriptor(service_node_id) - app = context - return await can_perform_service(app, service) + logging.debug('Starting can perform for %s with params of %s', service_node_id) + result = await internal_can_perform(context, service_node_id) + logging.debug('Result of perform was %s', result) + return result @methods.add async def perform(service_node_id=None, job_params=None, context=None): logging.debug('Starting perform for %s with params of %s', service_node_id, job_params) - service_descriptor = ServiceDescriptor(service_node_id) + result = await internal_perform_job(context, service_node_id, job_params) + logging.debug('Result of perform was %s', result) + return result - job = JobDescriptor(service_descriptor, job_params) - app = context - result = await perform_job(app, job) - logging.debug('Result of perform was %s', result) +@methods.add +async def offer(service_node_id=None, job_params=None, context=None): + price = job_params + logging.debug('Starting offer for %s with price of %s', service_node_id, price) + result = await internal_offer(context, service_node_id, price) + logging.debug('Result of offer was %s', result) return result diff --git a/agent/sn_agent/api/job.py b/agent/sn_agent/api/job.py index 7a0c8a2..40fa29b 100644 --- a/agent/sn_agent/api/job.py +++ b/agent/sn_agent/api/job.py @@ -6,6 +6,25 @@ logger = logging.getLogger(__name__) +async def internal_can_perform(app, service_node_id): + # figure out what we are being asked to perform and answer + service = ServiceDescriptor(service_node_id) + return await can_perform_service(app, service) + + +async def internal_offer(app, service_node_id, price): + service_descriptor = ServiceDescriptor(service_node_id) + result = app['accounting'].incoming_offer(service_descriptor, price) + return result + + +async def internal_perform_job(app, service_node_id, job_params): + service_descriptor = ServiceDescriptor(service_node_id) + job = JobDescriptor(service_descriptor, job_params) + result = await perform_job(app, job) + return result + + async def can_perform_service(app, service_descriptor: ServiceDescriptor): logger.debug("get_can_perform: %s", service_descriptor) @@ -40,4 +59,3 @@ async def perform_job(app, job_descriptor: JobDescriptor): }] return results - diff --git a/agent/sn_agent/network/settings.py b/agent/sn_agent/network/settings.py index f95cf70..f5dcc26 100644 --- a/agent/sn_agent/network/settings.py +++ b/agent/sn_agent/network/settings.py @@ -3,7 +3,7 @@ from urllib3.util import Url -from sn_agent import SettingsBase +from sn_agent import SettingsBase, Required THIS_DIR = Path(__file__).parent @@ -17,6 +17,7 @@ def __init__(self, **custom_settings): self.GATEWAY = '0.0.0.0' self.CLIENT_URL = 'http://testrpc:8545' + self.ACCOUNT_PASSWORD = Required(str) self.CLASS = 'sn_agent.network.sn.SNNetwork' diff --git a/agent/sn_agent/network/sn.py b/agent/sn_agent/network/sn.py index e9001c5..996f87c 100644 --- a/agent/sn_agent/network/sn.py +++ b/agent/sn_agent/network/sn.py @@ -14,9 +14,21 @@ logger = logging.getLogger(__name__) +class MarketJob(object): + UNKNOWN = None + PENDING = 'pending' + COMPLETED = 'completed' + + def __init__(self): + self.state = self.UNKNOWN + + class UnresolvedAgentException(Exception): pass +class AccountNotUnlockedException(Exception): + pass + class FileResolver(ResolverABC): def __init__(self, lookup_file): @@ -31,10 +43,6 @@ def resolve(self, agent_id): return agent_urls.get(agent_id) -class DHTResolver(ResolverABC): - def resolve(self, agent_id): - return None - class SNNetwork(NetworkABC): def __init__(self, app): @@ -47,7 +55,6 @@ def __init__(self, app): self.resolvers = [] self.resolvers.append(FileResolver(self.settings.AGENT_URL_LOOKUP_FILE)) - self.resolvers.append(DHTResolver()) async def startup(self): logger.debug('Starting up the network') @@ -135,7 +142,10 @@ def getAgentsById(self, id): contract = self.get_agent_registry_contract() return contract.call(self.payload).getAgent(id) - def createMarketJob(self, agents, amounts, payer, firstService, lastService): + def create_market_job(self, agents, amounts, payer, firstService, lastService): + + self.ensure_unlocked() + contract = self.get_market_job_contract() return contract.deploy( transaction={ @@ -150,12 +160,19 @@ def createMarketJob(self, agents, amounts, payer, firstService, lastService): ) ) - def setJobCompleted(self): + def set_market_job_completed(self): contract = self.get_market_job_contract() + + self.ensure_unlocked() + return contract.call(self.payload).setJobCompleted() def payAgent(self, agentAccounts): + contract = self.get_market_job_contract() + + self.ensure_unlocked() + return contract.call({'from': agentAccounts[0]}).withdraw() # Utility Functions @@ -188,3 +205,9 @@ def get_contract(self, type_name): address = self.getAddress(type_name) contract = self.client_connection.eth.contract(abi=abi, address=address) return contract + + def ensure_unlocked(self): + unlock_state = self.client_connection.personal.unlockAccount(self.account, self.settings.ACCOUNT_PASSWORD, duration=30) + + if not unlock_state: + raise AccountNotUnlockedException() diff --git a/agent/sn_agent/ui/static/jsoneditor.js b/agent/sn_agent/ui/static/jsoneditor.js new file mode 100644 index 0000000..e69de29 diff --git a/agent/sn_agent/ui/templates/service-default.jinja2 b/agent/sn_agent/ui/templates/service-default.jinja2 index 741ca05..0990f02 100644 --- a/agent/sn_agent/ui/templates/service-default.jinja2 +++ b/agent/sn_agent/ui/templates/service-default.jinja2 @@ -2,13 +2,27 @@ {% block content %} + + + + + + +
+