Skip to content

Commit

Permalink
Merge branch 'release/0.6.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
xeroc committed Mar 12, 2018
2 parents f9201a8 + 488cd3d commit e0e6449
Show file tree
Hide file tree
Showing 17 changed files with 246 additions and 56 deletions.
11 changes: 8 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
.PHONY: clean-pyc clean-build docs

#
clean: clean-build clean-pyc

clean-build:
rm -fr build/ dist/ *.egg-info .eggs/ .tox/ __pycache__/ .cache/ .coverage htmlcov src
rm -fr build/
rm -fr dist/
rm -fr *.egg-info
rm -fr __pycache__/ .eggs/ .cache/ .tox/

clean-pyc:
find . -name '*.pyc' -exec rm -f {} +
Expand Down Expand Up @@ -35,7 +37,10 @@ check:

dist:
python3 setup.py sdist upload -r pypi
python3 setup.py bdist --format=zip upload
python3 setup.py bdist_wheel upload

docs:
sphinx-apidoc -d 6 -e -f -o docs . *.py tests
make -C docs clean html

release: clean check dist git
3 changes: 2 additions & 1 deletion grapheneapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
'graphenews',
'grapheneapi',
'grapheneclient',
'graphenewsrpc'
'graphenewsrpc',
'graphenehttprpc'
]
136 changes: 136 additions & 0 deletions grapheneapi/graphenehttprpc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import json
import time
import logging
from itertools import cycle
import requests
log = logging.getLogger(__name__)


class RPCError(Exception):
pass


class RPCRequestError(Exception):
pass


class NumRetriesReached(Exception):
pass


class GrapheneHTTPRPC(object):
""" This class allows to call API methods synchronously, without
callbacks.
:param str urls: Either a single REST endpoint URL, or a list of URLs
:param int num_retries: Try x times to num_retries to a node on
disconnect, -1 for indefinitely
Usage:
.. code-block:: python
ws = GrapheneHTTPRPC("https://api.node.com")
print(ws.get_account_count())
"""
def __init__(self, urls, **kwargs):
self.api_id = {}
self._request_id = 0
if isinstance(urls, list):
self.urls = cycle(urls)
else:
self.urls = cycle([urls])
self.num_retries = kwargs.get("num_retries", -1)

def get_request_id(self):
self._request_id += 1
return self._request_id

""" RPC Calls
"""
def rpcexec(self, payload):
""" Execute a call by sending the payload
:param json payload: Payload data
:raises ValueError: if the server does not respond in proper JSON
format
:raises RPCError: if the server returns an error
"""
log.debug(json.dumps(payload))
cnt = 0
while True:
cnt += 1

url = next(self.urls)

try:
query = requests.post(
url,
json=payload
)
if query.status_code != 200:
raise
break
except KeyboardInterrupt:
raise
except:
# Try next server
url = next(self.urls)

if (self.num_retries > -1 and
cnt > self.num_retries):
raise NumRetriesReached()
sleeptime = (cnt - 1) * 2 if cnt < 10 else 10
if sleeptime:
log.warning(
"Lost connection to node during rpcexec(): %s (%d/%d) "
% (self.url, cnt, self.num_retries) +
"Retrying in %d seconds" % sleeptime
)
time.sleep(sleeptime)

ret = {}
try:
ret = query.json()
except ValueError:
raise ValueError("Client returned invalid format. Expected JSON!")

log.debug(json.dumps(query.text))

if 'error' in ret:
if 'detail' in ret['error']:
raise RPCError(ret['error']['detail'])
else:
raise RPCError(ret['error']['message'])
else:
return ret["result"]

def __getattr__(self, name):
""" Map all methods to RPC calls and pass through the arguments
"""
def method(*args, **kwargs):

# Sepcify the api to talk to
if "api_id" not in kwargs:
if ("api" in kwargs):
if (kwargs["api"] in self.api_id and
self.api_id[kwargs["api"]]):
api_id = self.api_id[kwargs["api"]]
else:
api_id = kwargs["api"]
else:
api_id = 0
else:
api_id = kwargs["api_id"]

# let's be able to define the num_retries per query
self.num_retries = kwargs.get("num_retries", self.num_retries)

query = {"method": "call",
"params": [api_id, name, list(args)],
"jsonrpc": "2.0",
"id": self.get_request_id()}
r = self.rpcexec(query)
return r
return method
31 changes: 21 additions & 10 deletions grapheneapi/graphenewsrpc.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import sys
import threading
import websocket
import ssl
import json
import time
from itertools import cycle
import warnings
import logging
from itertools import cycle
log = logging.getLogger(__name__)


Expand All @@ -28,8 +25,9 @@ class GrapheneWebsocketRPC(object):
:param str urls: Either a single Websocket URL, or a list of URLs
:param str user: Username for Authentication
:param str password: Password for Authentication
:param Array apis: List of APIs to register to (default: ["database", "network_broadcast"])
:param int num_retries: Try x times to num_retries to a node on disconnect, -1 for indefinitely
:param Array apis: List of APIs to register to
:param int num_retries: Try x times to num_retries to a node on
disconnect, -1 for indefinitely
Available APIs
Expand Down Expand Up @@ -91,25 +89,31 @@ def wsconnect(self):
sleeptime = (cnt - 1) * 2 if cnt < 10 else 10
if sleeptime:
log.warning(
"Lost connection to node during wsconnect(): %s (%d/%d) "
% (self.url, cnt, self.num_retries) +
"Lost connection to node during wsconnect(): "
"%s (%d/%d) " % (self.url, cnt, self.num_retries) +
"Retrying in %d seconds" % sleeptime
)
time.sleep(sleeptime)
self.login(self.user, self.password, api_id=1)

def register_apis(self):
"""
# We no longer register to those apis separately because we can instead
# name them when doing a call
self.api_id["database"] = self.database(api_id=1)
self.api_id["history"] = self.history(api_id=1)
self.api_id["network_broadcast"] = self.network_broadcast(api_id=1)
"""
pass

""" RPC Calls
"""
def rpcexec(self, payload):
""" Execute a call by sending the payload
:param json payload: Payload data
:raises ValueError: if the server does not respond in proper JSON format
:raises ValueError: if the server does not respond in proper JSON
format
:raises RPCError: if the server returns an error
"""
log.debug(json.dumps(payload))
Expand All @@ -118,7 +122,9 @@ def rpcexec(self, payload):
cnt += 1

try:
self.ws.send(json.dumps(payload, ensure_ascii=False).encode('utf8'))
self.ws.send(
json.dumps(payload, ensure_ascii=False).encode('utf8')
)
reply = self.ws.recv()
break
except KeyboardInterrupt:
Expand Down Expand Up @@ -175,11 +181,16 @@ def method(*args, **kwargs):
self.api_id[kwargs["api"]]):
api_id = self.api_id[kwargs["api"]]
else:
# Try the query by providing the argument
# right away
api_id = kwargs["api"]
"""
raise ValueError(
"Unknown API! "
"Verify that you have registered to %s"
% kwargs["api"]
)
"""
else:
api_id = 0
else:
Expand Down
29 changes: 18 additions & 11 deletions graphenebase/account.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import ecdsa
import hashlib
from binascii import hexlify, unhexlify
import sys
import re
import os

from binascii import hexlify, unhexlify
from .base58 import ripemd160, Base58
from .dictionary import words as BrainKeyDictionary

""" This class and the methods require python3 """
assert sys.version_info[0] == 3, "graphenelib requires python3"
import ecdsa


class PasswordKey(object):
Expand All @@ -28,9 +26,10 @@ def get_private(self):
""" Derive private key from the brain key and the current sequence
number
"""
a = bytes(self.account +
self.role +
self.password, 'utf8')
if sys.version > '3':
a = bytes(self.account + self.role + self.password, 'utf8')
else:
a = bytes(self.account + self.role + self.password).encode('utf8')
s = hashlib.sha256(a).digest()
return PrivateKey(hexlify(s).decode('ascii'))

Expand Down Expand Up @@ -95,7 +94,10 @@ def get_private(self):
number
"""
encoded = "%s %d" % (self.brainkey, self.sequence)
a = bytes(encoded, 'ascii')
if sys.version > '3':
a = bytes(encoded, 'ascii')
else:
a = bytes(encoded).encode('ascii')
s = hashlib.sha256(hashlib.sha512(a).digest()).digest()
return PrivateKey(hexlify(s).decode('ascii'))

Expand Down Expand Up @@ -319,8 +321,10 @@ def compressedpubkey(self):
p = ecdsa.SigningKey.from_string(secret, curve=ecdsa.SECP256k1).verifying_key.pubkey.point
x_str = ecdsa.util.number_to_string(p.x(), order)
y_str = ecdsa.util.number_to_string(p.y(), order)
compressed = hexlify(bytes(chr(2 + (p.y() & 1)), 'ascii') + x_str).decode('ascii')
uncompressed = hexlify(bytes(chr(4), 'ascii') + x_str + y_str).decode('ascii')
compressed = hexlify(
chr(2 + (p.y() & 1)).encode('ascii') + x_str).decode('ascii')
uncompressed = hexlify(
chr(4).encode('ascii') + x_str + y_str).decode('ascii')
return([compressed, uncompressed])

def __format__(self, _format):
Expand All @@ -341,4 +345,7 @@ def __str__(self):

def __bytes__(self):
""" Returns the raw private key """
return bytes(self._wif)
if sys.version > '3':
return bytes(self._wif)
else:
return self._wif.__bytes__()
13 changes: 8 additions & 5 deletions graphenebase/base58.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
import logging
log = logging.getLogger(__name__)

""" This class and the methods require python3 """
assert sys.version_info[0] == 3, "graphenelib requires python3"

""" Default Prefix """
PREFIX = "GPH"

Expand Down Expand Up @@ -110,7 +107,10 @@ def __bytes__(self):


def base58decode(base58_str):
base58_text = bytes(base58_str, "ascii")
if sys.version > '3':
base58_text = bytes(base58_str, "ascii")
else:
base58_text = base58_str.encode("ascii")
n = 0
leading_zeroes_count = 0
for b in base58_text:
Expand All @@ -128,7 +128,10 @@ def base58decode(base58_str):


def base58encode(hexstring):
byteseq = bytes(unhexlify(bytes(hexstring, 'ascii')))
if sys.version > '3':
byteseq = bytes(unhexlify(bytes(hexstring, 'ascii')))
else:
byteseq = bytearray(unhexlify(hexstring.decode("ascii")))
n = 0
leading_zeroes_count = 0
for c in byteseq:
Expand Down
Loading

0 comments on commit e0e6449

Please sign in to comment.