Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RequestCoreService #9

Merged
merged 8 commits into from Jan 28, 2018
Merged
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
214 changes: 199 additions & 15 deletions servicesCore/requestCore_service.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,219 @@
import asyncio

from artifacts import *
from config import config
from servicesContracts.requestEthereum_service import RequestEthereumService
import servicesExtensions
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you need to import all of servicesExtensions? Same for servicesContracts below.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, should be an explicit import

import servicesContracts
from servicesExtensions.requestSyncrhoneExtensionEscrow_service import RequestSynchroneExtensionEscrowService
from servicesExternal.ipfs_service import Ipfs
from servicesExternal.web3_single import Web3Single

EMPTY_BYTES_32 = '0x0000000000000000000000000000000000000000'

class RequestCoreService:
"""
The RequestCoreService class is the interface for the Request Core contract
"""

def __init__(self):
pass
self._web3Single = Web3Single.getInstance()
self._ipfs = Ipfs.getInstance()
# TODO: load requestCoreArtifact using JSONLoader
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still a TODO? It should be loaded automatically by the artifacts module.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no longer at TODO

self._abiRequestCore = requestCoreArtifact['abi']
if not requestCoreArtifact['networks'][self._web3Single.networkName]:
raise ValueError('RequestCore Artifact does not have configuration for network: "' + self._web3Single.networkName + '"')
self._addressRequestCore = requestCoreArtifact['networks'][self._web3Single.networkName].address
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the .address here work?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, should be ['address']

self._instanceRequestCore = self._web3Single.web3.eth.Contract(self._abiRequestCore, self._addressRequestCore)

def getCurrentNumRequest(self):
pass
async def getCurrentNumRequest(self):
"""
Get the number of the last request (N.B. != id)
"""
try:
return self._instanceRequestCore.call().numRequests()
except Exception as e:
raise e

def getVersion(self):
pass
async def getVersion(self):
"""
Get the version of the contract
"""
try:
return self._instanceRequestCore.call().VERSION()
except Exception as e:
raise e

def getCollectEstimation(self, expectedAmount: any, currencyContract: str, extension: str):
pass
async def getCollectEstimation(self, expectedAmount: any, currencyContract: str, extension: str):
"""
Get the estimation of ether (in wei) needed to create a request
:param expectedAmount: amount expected of the request
:param currencyContract: address of the currency contract of the request
:param extension: address of the extension contract of the request
"""
if not self._web3Single.isAddressNoChecksum(currencyContract):
raise ValueError('currencyContract must be a valid eth address')

def getRequest(self, requestId: str):
pass
if extension and extension != '' and not self._web3Single.isAddressNoChecksum(extension):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An empty string already evaluates to False so no need for extension != ''.

raise ValueError('extension must be a valid eth address')

try:
data = self._instanceRequestCore.call().getCollectEstimation(expectedAmount, currencyContract, extension)
return data
except Exception as e:
raise e

async def getRequest(self, requestId: str):
"""
Get a request by its requestId
:param requestId: requestId of the request
"""
if not self._web3Single.isHexStrictBytes32(requestId):
raise ValueError('requestId must be a 32 bytes hex string')
try:
data = self._instanceRequestCore.call().requests(requestId)
if data.creator == EMPTY_BYTES_32:
raise ValueError('request not found')

# excluding BN
dataResult = {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is data here a data structure or a dictionary?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear. The web3 documentation isn't super explicit (I think v4 changed the Contract api).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we'll assume that it's fine and if it errors out later it's an easy fix.

'balance': data.balance,
'creator': data.creator,
'currencyContract': data.currencyContract,
'data': data.data,
'expectedAmount': data.expectedAmount,
'extension': data.extension if data.extension != EMPTY_BYTES_32 else None,
'payee': data.payee,
'payer': data.payer,
'requestId': requestId,
'state': int(data.state)
}

# get information from the currency contract
if servicesExtensions.getServiceFromAddress(data.currencyContract):
ccyContractDetails = await getServiceFromAddress(data.currencyContract).getRequestCurrencyContractInfo(requestId)
dataResult['currencyContract'] = ccyContractDetails

# get information from the extension contract
if data.extension and data.extension != '' and servicesExtensions.getServiceFromAddress(data.extension):
extensionDetails = await servicesExtensions.getServiceFromAddress(data.extension).getRequestExtensionInfo(requestId)
dataResult['extension'] = extensionDetails

# get ipfs details if needed
if dataResult.data and dataResult.data != '':
# TODO: might need to do some json wrangling
dataResult['data'] = await self._ipfs.get_file(dataResult.data)
else:
dataResult['data'] = None

return dataResult
except Exception as e:
raise e

async def getRequestByTransactionHash(self, _hash:str):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop the _ in front of hash and add a space between : and str.

"""
Get a request and method called by the hash of a transaction
:param hash: hash of the ethereum transaction
"""
try:
errors = []
warnings = []
transaction = await self._web3Single.getTransaction(_hash)
if not transaction:
raise ValueError('transaction not found')

ccyContract = transaction.to

ccyContractservice = await servicesContracts.getServiceFromAddress(ccyContract)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ccyContractService instead of service

# get information from the currency contract
if not ccyContractservice:
raise ValueError('Contract is not supported by request')

method = ccyContractservice.decodeInputData(transaction.input)
if not method.name:
raise ValueError('transaction data not parsable')

request = None

txReceipt = await self._web3Single.getTransactionReceipt(_hash)
# if already mined
if txReceipt:
if txReceipt.status != '0x1' and txReceipt.status != 1:
errors.append('transaction has failed')
elif transaction.method and transaction.method.pararmeters and transaction.method.parameters._requestId:
# simple action
request = await self.getRequest(transaction.method.parameters._requestId)
elif transaction txReceipt.logs and txReceipt.logs[0] and self._web3Single.areSameAddressNoChecksum(txReceipt.logs[0].address, self._addressRequestCore)
# maybe a creation
event = self._web3Single.decodeTransactionLog(self._abiRequestCore, 'Created', txReceipt.logs[0])
if event:
request = self.getRequest(event.requestId)
else:
# if not mined
methodGenerated = ccyContractservice.generateWeb3Method(transaction.method.name, self._web3Single.resultToArray(transaction.method.parameters))
options = {
'from': transaction.from,
'gas': transaction.gas,
'value': transaction.value
}

try:
test = await self._web3Single.callMethod(methodGenerated, options)
except Exception as e:
warnings.append('transaction may failed: ' + e)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may have failed


if transaction.gasPrice < config.ethereum.gasPriceMinimumCriticalInWei:
warnings.append('transaction gasPrice is low')

errors = None if not errors else errors
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can do errors = errors or None and if errors == [] it will be None. Same for warnings below.

warnings = None if not warnings else warnings
return (request, transaction, errors, warnings)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we have to do any of the resolve stuff here.


except Exception as e:
raise e

def getRequestByTransactionHash(self, hash:str):
pass

def getRequestEvents(self, requestId: str, fromBlock: int = None, toBlock: int = None):
pass

def getRequestsByAddress(self, address: str, fromBlock: int = None, toBlock: int = None):
pass
async def getRequestsByAddress(self, address: str, fromBlock: int = None, toBlock: int = None):
"""
Get the list of requests connected to an address
:param address: address to get the requests
:param fromBlock: search requests from this block
:param toBlock: search requests until this block
"""
try:
networkName = self._web3Single.networkName

# get events Created with payee == address
eventsCorePayee = await self._instanceRequestCore.getPastEvents('Created', {
'filter': {'payee': address},
'fromBlock': fromBlock if fromBlock else requestCoreArtifact['networks'][networkName]['blockNumber'],
'toBlock': toBlock if toBlock else 'latest'
})

# get events Created with payer == address
eventsCorePayer = await this._instanceRequestCore.getPastEvents('Created', {
'filter': {'payer': address},
'fromBlock': fromBlock if fromBlock else requestCoreArtifact['network'][networkName]['blockNumber'],
'toBlock': toBlock if toBlock else 'latest'
})

# clean the data and get timestamp for request as payee
# TODO: implement this, I believe it is just adding
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this isn't done can you have this raise a NotImplemented error so that people don't call it accidentally. Also maybe open an issue for it so that people don't scroll by and see that it's not pass and assume that it's done.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

# a _meta dict to each event using a map

# clean the data and get timestamp for request as payer
# ditto here, but for eventsCorePayer

return {'asPayee': eventsCorePayee, 'asPayer': eventsCorePayer}
except Exception as e:
raise e

def getIpfsFile(self, hash: str):
pass
async def getIpfsFile(self, hash: str):
"""
Get the file content from ipfs
:param hash: hash of the file
"""
return self._ipfs.get_file(hash)