Skip to content
Snippets Groups Projects
Commit 46846d2a authored by adorda's avatar adorda
Browse files

Primer commit

parents
No related branches found
No related tags found
No related merge requests found
Showing
with 693 additions and 0 deletions
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/django-ethgateway.iml" filepath="$PROJECT_DIR$/.idea/django-ethgateway.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="17eca87e-b1e6-4119-93d9-1acdd5d74eb4" name="Default Changelist" comment="" />
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FileEditorManager">
<leaf>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/README.rst">
<provider selected="true" editor-type-id="restructured-text-editor" />
</entry>
</file>
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/setup.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="345">
<caret line="23" column="35" selection-start-line="23" selection-start-column="35" selection-end-line="23" selection-end-column="35" />
<folding>
<element signature="e#0#9#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/MANIFEST.in">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="30">
<caret line="2" column="24" selection-start-line="2" selection-start-column="24" selection-end-line="2" selection-end-column="24" />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Python Script" />
</list>
</option>
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/README.rst" />
<option value="$PROJECT_DIR$/MANIFEST.in" />
<option value="$PROJECT_DIR$/LICENSE" />
<option value="$PROJECT_DIR$/setup.py" />
</list>
</option>
</component>
<component name="ProjectFrameBounds" extendedState="6">
<option name="x" value="1786" />
<option name="y" value="27" />
<option name="width" value="1590" />
<option name="height" value="675" />
</component>
<component name="ProjectView">
<navigator proportions="" version="1">
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="Scope" />
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="django-ethgateway" type="b2602c69:ProjectViewProjectNode" />
<item name="django-ethgateway" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="django-ethgateway" type="b2602c69:ProjectViewProjectNode" />
<item name="django-ethgateway" type="462c0819:PsiDirectoryNode" />
<item name="gateway" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
</subPane>
</pane>
</panes>
</component>
<component name="PropertiesComponent">
<property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
<property name="node.js.detected.package.eslint" value="true" />
<property name="node.js.path.for.package.eslint" value="project" />
<property name="node.js.selected.package.eslint" value="(autodetect)" />
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="17eca87e-b1e6-4119-93d9-1acdd5d74eb4" name="Default Changelist" comment="" />
<created>1562781241880</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1562781241880</updated>
<workItem from="1562781243096" duration="2223000" />
</task>
<servers />
</component>
<component name="TimeTrackingManager">
<option name="totallyTimeSpent" value="2223000" />
</component>
<component name="ToolWindowManager">
<frame x="1667" y="27" width="1853" height="1053" extended-state="6" />
<editor active="true" />
<layout>
<window_info id="Favorites" side_tool="true" />
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.24958494" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info anchor="bottom" id="Docker" weight="0.3297062" />
<window_info anchor="bottom" id="Database Changes" />
<window_info anchor="bottom" id="Version Control" />
<window_info anchor="bottom" id="Python Console" />
<window_info anchor="bottom" id="Terminal" visible="true" weight="0.3297062" />
<window_info anchor="bottom" id="Event Log" side_tool="true" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Find" order="1" />
<window_info anchor="bottom" id="Run" order="2" />
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="right" id="SciView" />
<window_info anchor="right" id="Database" />
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
</layout>
</component>
<component name="TypeScriptGeneratedFilesManager">
<option name="version" value="1" />
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/README.rst">
<provider selected="true" editor-type-id="restructured-text-editor" />
</entry>
<entry file="file://$PROJECT_DIR$/MANIFEST.in">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="30">
<caret line="2" column="24" selection-start-line="2" selection-start-column="24" selection-end-line="2" selection-end-column="24" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/LICENSE">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="10095">
<caret line="673" column="49" selection-start-line="673" selection-start-column="49" selection-end-line="673" selection-end-column="49" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/gateway/fields.py">
<provider selected="true" editor-type-id="text-editor" />
</entry>
<entry file="file://$PROJECT_DIR$/setup.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="345">
<caret line="23" column="35" selection-start-line="23" selection-start-column="35" selection-end-line="23" selection-end-column="35" />
<folding>
<element signature="e#0#9#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</component>
</project>
\ No newline at end of file
include LICENSE
include README.rst
recursive-include docs *
\ No newline at end of file
================
Ethereum Gateway
================
App que hace de wrapper de web3 sencillo, con utilidades para deployar contratos o búsquedas rápidas de logs
Quick start
-----------
1. Agregar "gateway" al setting INSTALLED_APPS ::
INSTALLED_APPS = [
...
'gateway',
]
File added
File added
File added
File added
from django.apps import AppConfig
class GatewayConfig(AppConfig):
name = 'gateway'
import logging
import asyncio
import math
from eth_utils import to_text
from web3 import Web3
import aiohttp
from web3.utils.encoding import FriendlyJsonSerde
from gateway.utils import DictToHexBytes
logger = logging.getLogger('django-gateway.gateway')
def generate_http_gateway(url, is_poa):
provider_generator = HTTPProviderGenerator(url)
return Gateway(provider_generator, is_poa)
class HTTPProviderGenerator:
def __init__(self, url):
self.url = url
def generate_provider(self):
return Web3.HTTPProvider(self.url)
class InvalidTransactionException(Exception):
pass
class NoConnectionException(Exception):
pass
class Gateway:
def __init__(self, provider_generator, is_geth_poa_network=False):
self.w3 = Web3(provider_generator.generate_provider())
if is_geth_poa_network:
from web3.middleware import geth_poa_middleware
self.w3.middleware_stack.inject(geth_poa_middleware, layer=0)
if not self.w3.isConnected():
raise InvalidTransactionException("No connection to node")
def get_balance(self, address, measure='wei'):
return self.w3.fromWei(self.w3.eth.getBalance(self.to_checksum_address(address)), measure)
# precondición: la dirección es válida
def get_contract(self, abi, address):
return self.w3.eth.contract(abi=abi, address=address)
# precondición: la dirección es válida
def get_contract_functions(self, abi, address):
contrato = self.get_contract(abi, address)
return contrato.functions
# precondición: la dirección es válida
def get_functions(self, contract):
return contract.functions
# precondición: la dirección es válida
def get_contract_events(self, abi, address):
contrato = self.get_contract(abi=abi, address=address)
return contrato.events
def get_events(self, contract):
return contract.events
def is_valid_address(self, address):
return self.w3.isAddress(address)
def to_checksum_address(self, address):
return self.w3.toChecksumAddress(address)
def get_block(self, block_number):
return self.w3.eth.getBlock(block_number)
def get_tx_receipt(self, tx_hash):
return self.w3.eth.getTransactionReceipt(tx_hash)
def get_transaction(self, tx_hash):
return self.w3.eth.getTransaction(tx_hash)
def get_last_blocknumber(self):
return self.w3.eth.blockNumber
def get_node_accounts(self):
return self.w3.eth.accounts
def is_connected(self):
return self.w3.eth.isConnected()
def execute_transaction(self, function, transaction_data=None, exception_message=""):
logger.debug("Executing transaction for function {} / tx data {}".format(str(function), str(transaction_data)))
if transaction_data is None:
transaction_data = {}
try:
function.call(transaction_data)
except ValueError as ve:
raise InvalidTransactionException(ve.args[0]['message'])
tx_hash = function.transact(transaction_data)
logger.debug("Transaction hash: {}".format(tx_hash.hex()))
return tx_hash
def call_transaction(self, function, transaction_data=None, exception_message=""):
if transaction_data is None:
transaction_data = {}
res = function.call(transaction_data)
return res
def search_entries(self, event, params):
logger.debug("Searching entries for event {} / params : {}".format(str(event), str(params)))
if 'fromBlock' not in params:
params['fromBlock'] = 0
if 'toBlock' not in params:
params['toBlock'] = 'latest'
event_filter = event.createFilter(**params)
return event_filter.get_all_entries()
def get_undeployed_contract(self, abi, bytecode):
return self.w3.eth.contract(abi=abi, bytecode=bytecode)
def get_receipt(self, tx, wait=False):
rcpt = self.w3.eth.getTransactionReceipt(tx)
if wait:
rcpt = self.w3.eth.waitForTransactionReceipt(tx)
return rcpt
def unlock_account(self, account, passphrase):
logger.debug("Unlocking account for {}".format(str(account)))
return self.w3.personal.unlockAccount(account, passphrase)
def get_event_abi(self, event):
return
def get_tx_input(self, contract, tx):
return contract.decode_function_input(tx.input)
def transaction_is_canonical(self, transaction):
last_block_number = self.get_last_blocknumber()
tx_block_number = transaction.blockNumber
required_block_difference = math.ceil(self.get_signers_count() / 2) + 1
return (last_block_number - tx_block_number) > required_block_difference
def pending_transactions(self):
pending = self.w3.txpool.content['pending']
txs = []
for account, transactions in pending.items():
for nonce, tx in transactions.items():
txs.append(tx)
return txs
def get_signers_count(self):
return 10
def get_smart_contract_bytecode(self, smart_contract_address):
return self.w3.eth.getCode(smart_contract_address)
class ContractInteractor:
gateway = ... # type: Gateway
searcher = ... # type: HTTPFastLogSearcher
def __init__(self, gateway, abi, address, searcher=None):
self.searcher = searcher
self.abi = abi
self.contract_address = address
self.gateway = gateway
def execute_function(self, function, tx_params, error_message=''):
res = self.gateway.execute_transaction(function, tx_params, error_message)
return res.hex()
def search_logs(self, event_name, event_params):
logger.debug("Searching logs for {} with params {}".format(str(event_name), str(event_params)))
contract = self.gateway.get_contract(self.abi, self.contract_address)
events = self.gateway.get_events(contract)
event = getattr(events, event_name)
if self.searcher is not None:
entries = self.searcher.search_entries(event, event_params)
else:
entries = self.gateway.search_entries(event, event_params)
return entries
def get_function(self, function_name):
return getattr(self.gateway.get_functions(self.gateway.get_contract(self.abi, self.contract_address)),
function_name)
def call_function(self, function, tx_params={}, error_message=''):
res = self.gateway.call_transaction(function, tx_params, error_message)
return res
def decode_function_input(self, tx):
contract = self.gateway.get_contract(self.abi, self.contract_address)
return self.gateway.get_tx_input(contract, tx)
class ContractDeployer:
gateway = ... # type: Gateway
def __init__(self, gateway):
self.gateway = gateway
def deploy_contract(self, abi, bytecode, gas, from_account, wait=False):
logger.debug("Deploying contract from account: {}".format(str(from_account)))
contrato = self.gateway.get_undeployed_contract(abi, bytecode)
# Submit the transaction that deploys the contract
tx_hash = contrato.constructor().transact({'from': from_account, 'gas': gas, 'gasLimit': gas})
logger.debug("Transaction hash for contract deploy : {}".format(str(tx_hash.hex())))
# Wait for the transaction to be mined, and get the transaction receipt
tx_receipt = self.gateway.get_receipt(tx_hash, wait)
logger.debug("Receipt for contract deploy: {}".format(str(tx_receipt)))
return tx_receipt
def estimate_deploy(self, abi, bytecode):
contrato = self.gateway.get_undeployed_contract(abi, bytecode)
return contrato.constructor().estimateGas()
class HTTPFastLogSearcher:
coroutines = 10
gateway = ... # type: Gateway
url = ... # type: str
to_hex_dict = DictToHexBytes()
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
def encode_rpc_request(self, method, params, rpc_id):
rpc_dict = {
"jsonrpc": "2.0",
"method": method,
"params": params or [],
"id": rpc_id,
}
return rpc_dict
async def _async_search_entries(self, session, event, params, json_rpc_id, res_args):
logger.debug("Starting coroutine id : {}".format(json_rpc_id))
logger.debug("Creating filter with params : {}".format(str(params)))
event_filter = event.createFilter(**params)
logger.debug("Encoding json rpc request for filter id: {} / json_rpc_id : {}"
.format(str(event_filter.filter_id), str(json_rpc_id)))
rpc_params = self.encode_rpc_request('eth_getFilterLogs', [event_filter.filter_id], json_rpc_id)
logger.debug("Requesting POST to url: {}".format(str(self.url)))
async with session.post(self.url, json=rpc_params) as raw_response:
logger.debug("Awaiting for response of id : {}".format(str(json_rpc_id)))
data = await raw_response.read() # necesito los bytes, no uso el .json() a proposito
text_response = to_text(data) # hago esto porque asi puedo decodear usando los metodos de web3
decoded_data = FriendlyJsonSerde().json_decode(text_response)
logger.debug("Data assigned: {}".format(str(data)))
if 'result' in decoded_data and len(decoded_data['result']) > 0:
logger.debug("Decoding response of id : {}".format(str(json_rpc_id)))
res_args[json_rpc_id] = []
for r in decoded_data['result']:
logger.debug("Adding response for id : {}".format(str(json_rpc_id)))
self.to_hex_dict.hex_dict(r)
res_args[json_rpc_id].append(event_filter.format_entry(r)) # me desentiendo de la interpretacion
# con w3
async def _async_search(self, event, params, amount, from_block, resto, res):
logger.debug("Creating aiohttp session")
headers = {'Content-Type': 'application/json', }
async with aiohttp.ClientSession(headers=headers) as session:
tasks = []
logger.debug("Looping to {}".format(str(self.coroutines)))
for x in range(self.coroutines):
p = params.copy()
p['fromBlock'] = int(amount * x) + from_block
p['toBlock'] = int(amount * (x + 1)) + from_block
tasks.append(self._async_search_entries(session, event, p, x, res))
if resto != 0:
p = params.copy()
p['fromBlock'] = int(amount * self.coroutines) + from_block
p['toBlock'] = 'latest'
tasks.append(self._async_search_entries(session, event, p, self.coroutines + 1, res))
logger.debug("Gathering tasks")
await asyncio.gather(*tasks)
def search_entries(self, event, params):
logger.debug("Searching entries fast for event: {} / params : {}".format(str(event), str(params)))
from_block = params['fromBlock']
to_block = params['toBlock']
if 'fromBlock' not in params:
params['fromBlock'] = 0
from_block = 0
if 'toBlock' not in params:
params['toBlock'] = 'latest'
loop = asyncio.get_event_loop()
logger.debug("Search from block: {}".format(str(from_block)))
if params['toBlock'] == 'latest':
to_block = self.gateway.get_last_blocknumber()
logger.debug("Search until latest block : {}".format(str(to_block)))
amount = int((to_block - from_block) / self.coroutines)
resto = int((to_block - from_block) % self.coroutines)
logger.debug("Block amount per coroutine : {}".format(str(amount)))
if amount < self.coroutines:
logger.debug("Amount is less than coroutines number")
results = self.gateway.search_entries(event, params)
return results
else:
results = {}
logger.debug("Running loop until complete")
loop.run_until_complete(self._async_search(event, params, amount, from_block, resto, results))
res = []
for k, v in results.items():
for i in v:
res.append(i)
return res
import json
from django.core.management.base import BaseCommand
import logging
import getpass
from gateway.gateway import ContractDeployer, Gateway, HTTPProviderGenerator
logger = logging.getLogger('cmd-logger')
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('--node_url', help="Node URL", type=str)
parser.add_argument('--is_poa', help="Is POA network", action='store_false')
parser.add_argument('--account', help='Account a usar', action=str)
parser.add_argument('--bytecode', help='Path del bytecode', action=str)
parser.add_argument('--abi', help='Path del abi', action=str)
parser.add_argument('--gas', help='Gas a usar', action=int)
parser.set_defaults(node_url=None, account=None, bytecode=None, abi=None, gas=None, is_poa=False)
def handle(self, *args, **options):
try:
if options['node_url'] is None:
options['node_url'] = input("Ingrese la url del nodo")
provider = HTTPProviderGenerator(options['node_url'])
gateway = Gateway(provider, options['is_poa'])
self.stdout.write(str(gateway.get_node_accounts()))
if options['account'] is None:
options['account'] = input('Ingrese el owner account: ')
if options['account'] is None:
raise Exception("No fue ingresado owner account")
passphrase = getpass.getpass('Ingrese el passphrase: ')
unlocked = gateway.unlock_account(options['account'], passphrase) # TODO Pedir por prompt
if not unlocked:
raise Exception("Contraseña incorrecta")
bytecode_file = options['bytecode']
if bytecode_file is None:
bytecode_file = input('Ingrese el file path del bytecode: ')
if bytecode_file is None:
raise Exception("No fue ingresado file path del bytecode")
with open(bytecode_file, 'r') as myfile:
bytecode = myfile.read().replace('\n', '')
abi_file = options['bytecode']
if abi_file is None:
abi_file = input("Ingrese file path del abi_nuevo: ")
if abi_file is None:
raise Exception("No fue ingresado file path del abi_nuevo")
with open(abi_file) as json_file:
abi = json.dumps(json.load(json_file))
deployer = ContractDeployer(gateway)
gas = deployer.estimate_deploy(abi, bytecode)
logger.info("Gas estimado: {}".format(str(gas)))
if options['gas'] is not None:
gas_aux = options['gas']
else:
gas_aux = input("Ingrese un límite de gas opcional: ")
if gas_aux is not '':
gas = int(gas_aux)
tx_receipt = deployer.deploy_contract(abi, bytecode, gas, True)
logger.info("Contract address: {0}".format(tx_receipt['contractAddress']))
logger.info("tx_receipt: {}".format(str(tx_receipt)))
except Exception as e:
logger.exception(e)
import json
from django.core.management.base import BaseCommand
import logging
from gateway.gateway import Gateway, HTTPProviderGenerator
logger = logging.getLogger('cmd-logger')
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument('--node_url', help="Node URL", type=str)
parser.add_argument('--is_poa', help="Is POA network", action='store_false')
parser.set_defaults(node_url=None, is_poa=False)
def handle(self, *args, **options):
try:
if options['node_url'] is None:
options['node_url'] = input("Ingrese la url del nodo")
provider = HTTPProviderGenerator(options['node_url'])
gateway = Gateway(provider, options['is_poa'])
self.stdout.write(str(gateway.get_node_accounts()))
except Exception as e:
logger.error(e)
from hexbytes import HexBytes
from web3 import Web3
def is_hex(s):
try:
int(s, 16)
return True
except Exception:
return False
class DictToHexBytes:
def hex_dict(self, dictionary):
for k, v in dictionary.items():
if isinstance(v, dict):
self.hex_dict(dictionary[k])
elif isinstance(v, list):
self.hex_list(dictionary[k])
elif is_hex(v):
dictionary[k] = HexBytes(v)
def hex_list(self, sequence):
i = 0
while i < len(sequence):
sequence[i] = HexBytes(sequence[i])
i = i + 1
def string_to_hex32bytes(some_string):
return Web3.toHex(some_string).encode()
def hex32bytes_string(some_hex32bytes):
try:
return Web3.toText(some_hex32bytes.decode().strip('\\\u0000'))
except UnicodeDecodeError as ude:
return some_hex32bytes
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment