Skip to content

Commit

Permalink
Merge branch 'release/0.4.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
xeroc committed Jun 15, 2016
2 parents 3c5118f + ae7bea3 commit 6657133
Show file tree
Hide file tree
Showing 20 changed files with 1,050 additions and 339 deletions.
2 changes: 1 addition & 1 deletion docs/howto-exchanges-detailed.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ still support exchanges that require more confirmations for deposits.

We provide a so called *delayed* full node which accepts two additional
parameters for the configuration besides those already available with the
standard daemon (read :doc:`full`).
standard daemon.

* `trusted-node` RPC endpoint of a trusted validating node (required)
* `delay-block-count` Number of blocks to delay before advancing chain state (required)
Expand Down
31 changes: 31 additions & 0 deletions docs/howto-monitor-operations.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
***************************************************
Howto Monitor the blockchain for certain operations
***************************************************

Operations in blocks can be monitored relatively easy by using the
`block_stream` (for entire blocks) for `stream` (for specific
operations) generators.

The following example will only show ``transfer`` operations on the
blockchain:

.. code-block:: python
from grapheneapi.grapheneclient import GrapheneClient
from pprint import pprint
class Config():
witness_url = "ws://testnet.bitshares.eu/ws"
if __name__ == '__main__':
client = GrapheneClient(Config)
for b in client.ws.stream("transfer"):
pprint(b)
Note that you can define a starting block and instead of waiting for
sufficient confirmations (irreversible blocks), you can also consider
the real *head* block with:

.. code-block:: python
for b in client.ws.stream("transfer", start=199924, mode="head"):
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Tutorials
.. toctree::
:maxdepth: 1

howto-monitor-operations
howto-exchanges
howto-exchanges-detailed

Expand Down
46 changes: 46 additions & 0 deletions examples/approve_proposal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from grapheneapi.grapheneclient import GrapheneClient
from pprint import pprint


class config():
witness_url = "ws://testnet.bitshares.eu/ws"
wallet_host = "localhost"
wallet_port = 8092


if __name__ == '__main__':
client = GrapheneClient(config)
graphene = client.rpc

# Get current fees
core_asset = graphene.get_asset("1.3.0")
committee_account = graphene.get_account("committee-account")
proposals = client.ws.get_proposed_transactions(committee_account["id"])

for proposal in proposals:
print("Proposal: %s" % proposal["id"])

prop_op = proposal["proposed_transaction"]["operations"]

if len(prop_op) > 1:
print(" - [Warning] This proposal has more than 1 operation")

if graphene._confirm("Approve?"):
tx = graphene.approve_proposal(
"xeroc",
proposal["id"],
{"active_approvals_to_add":
["committee-member-1",
"committee-member-2",
"committee-member-3",
"committee-member-4",
"committee-member-5",
"committee-member-6",
"committee-member-7",
"init0",
"init1",
"init2",
"init3",
]},
True)
pprint(tx)
56 changes: 56 additions & 0 deletions examples/change_fee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from grapheneapi.grapheneclient import GrapheneClient
from graphenebase.transactions import getOperationNameForId
from pprint import pprint
from deepdiff import DeepDiff

proposer = "xeroc"
expiration = "2016-05-17T09:00:00"
price_per_kbyte = 0
everythin_flat_fee = 0.001
broadcast = True


class Wallet():
wallet_host = "localhost"
wallet_port = 8092

if __name__ == '__main__':
graphene = GrapheneClient(Wallet)
obj = graphene.getObject("2.0.0")
current_fees = obj["parameters"]["current_fees"]["parameters"]
old_fees = obj["parameters"]["current_fees"]
scale = obj["parameters"]["current_fees"]["scale"] / 1e4

# General change of parameter
changes = {}
for f in current_fees:
if ("price_per_kbyte" in f[1] and f[1]["price_per_kbyte"] != 0):
print("Changing operation %s[%d]" % (getOperationNameForId(
f[0]), f[0]))
changes[getOperationNameForId(f[0])] = f[1].copy()
changes[getOperationNameForId(f[0])]["price_per_kbyte"] = int(
price_per_kbyte / scale * 1e5)
if ("fee" in f[1] and f[1]["fee"] != 0):
print("Changing operation %s[%d]" % (getOperationNameForId(
f[0]), f[0]))
changes[getOperationNameForId(f[0])] = f[1].copy()
changes[getOperationNameForId(f[0])]["fee"] = int(
everythin_flat_fee / scale * 1e5)

# overwrite / set specific fees
changes["transfer"]["price_per_kbyte"] = int(0)
# changes["account_update"]["price_per_kbyte"] = int( 5 / scale * 1e5)

print("=" * 80)
tx = graphene.rpc.propose_fee_change(proposer,
expiration,
changes,
broadcast)
proposed_ops = tx["operations"][0][1]["proposed_ops"][0]
new_fees = proposed_ops["op"][1]["new_parameters"]["current_fees"]

pprint(DeepDiff(old_fees, new_fees))

if not broadcast:
print("=" * 80)
print("Set broadcast to 'True' if the transaction shall be broadcast!")
59 changes: 59 additions & 0 deletions examples/transfer_back_to_issuer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from grapheneapi import GrapheneClient
from grapheneexchange import GrapheneExchange
from graphenebase import transactions
from pprint import pprint

issuer = "xeroc"
from_account = "maker"
to_account = "xeroc"
asset = "LIVE"
amount = 100.0
wifs = ["<active-wif-key-of-issuer>"]
witness_url = "ws://testnet.bitshares.eu/ws"


def constructSignedTransaction(ops):
ops = transactions.addRequiredFees(client.ws, ops, "1.3.0")
ref_block_num, ref_block_prefix = transactions.getBlockParams(client.ws)
expiration = transactions.formatTimeFromNow(30)
tx = transactions.Signed_Transaction(
ref_block_num=ref_block_num,
ref_block_prefix=ref_block_prefix,
expiration=expiration,
operations=ops
)
w = tx.sign(wifs, chain=client.getChainInfo())
return w


#: Connetion Settings
class Config():
witness_url = witness_url


if __name__ == '__main__':
config = Config
client = GrapheneClient(config)

issuer = client.ws.get_account(issuer)
from_account = client.ws.get_account(from_account)
to_account = client.ws.get_account(to_account)
asset = client.ws.get_asset(asset)
amount = int(amount * 10 ** asset["precision"])

ops = []
op = transactions.Override_transfer(**{
"fee": {"amount": 0,
"asset_id": "1.3.0"},
"issuer": issuer["id"],
"from": from_account["id"],
"to": to_account["id"],
"amount": {"amount": amount,
"asset_id": asset["id"]},
"extensions": []
})
ops.append(transactions.Operation(op))

tx = constructSignedTransaction(ops)
pprint(transactions.JsonObj(tx))
print(client.ws.broadcast_transaction(transactions.JsonObj(tx), api="network_broadcast"))
5 changes: 3 additions & 2 deletions grapheneapi/grapheneapi.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import sys
import json
import logging
log = logging.getLogger(__name__)

try:
import requests
Expand Down Expand Up @@ -113,8 +115,7 @@ def rpcexec(self, payload):
rtype: json
raises RPCConnection: if no connction can be made
raises UnauthorizedError: if the user is not authorized
raise ValueError: if the API returns a non-JSON formated
answer
raise ValueError: if the API returns a non-JSON formated answer
It is not recommended to use this method directly, unless
you know what you are doing. All calls available to the API
Expand Down
116 changes: 68 additions & 48 deletions grapheneapi/grapheneclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,8 @@
from .graphenews import GrapheneWebsocket
from collections import OrderedDict

import logging as log

#: max number of objects to chache
max_cache_objects = 50


class LimitedSizeDict(OrderedDict):
""" This class limits the size of the objectMap
"""

def __init__(self, *args, **kwds):
self.size_limit = kwds.pop("size_limit", max_cache_objects)
OrderedDict.__init__(self, *args, **kwds)
self._check_size_limit()

def __setitem__(self, key, value):
OrderedDict.__setitem__(self, key, value)
self._check_size_limit()

def _check_size_limit(self):
if self.size_limit is not None:
while len(self) > self.size_limit:
self.popitem(last=False)

def __getitem__(self, key):
""" keep the element longer in the memory by moving it to the end
"""
self.move_to_end(key)
return OrderedDict.__getitem__(self, key)
import logging
log = logging.getLogger(__name__)


class ExampleConfig() :
Expand Down Expand Up @@ -105,6 +78,10 @@ class Config(GrapheneWebsocketProtocol): ## Note the dependency
#: ``onAccountUpdate()`` to be called
watch_accounts = ["fabian", "nathan"]

#: Assets you want to watch. Changes will be used to call
#: ``onAssetUpdate()``.
watch_assets = ["USD"]

#: Markets to watch. Changes to these will result in the method
#: ``onMarketUpdate()`` to be called
watch_markets = ["USD:CORE"]
Expand Down Expand Up @@ -184,6 +161,20 @@ def onAccountUpdate(self, data):
"""
pass

def onAssetUpdate(self, data):
""" This method is called when any of the assets in watch_assets
changes. The changes of the following objects are monitored:
* Asset object (``1.3.x``)
* Dynamic Asset data (``2.3.x``)
* Bitasset data (``2.4.x``)
Hence, this method needs to distinguish these three
objects!
"""
pass

def onMarketUpdate(self, data):
""" This method will be called if a subscribed market sees an
event (registered to through ``watch_markets``).
Expand Down Expand Up @@ -414,6 +405,10 @@ def __init__(self, config):
except:
raise Exception("Couldn't load assets for market %s"
% market)
if not quote or not base:
raise Exception("Couldn't load assets for market %s"
% market)

if "id" in quote and "id" in base:
if "onMarketUpdate" in available_features:
self.markets.update({
Expand All @@ -432,6 +427,19 @@ def __init__(self, config):
log.warn("Market assets could not be found: %s"
% market)
self.setMarketCallBack(self.markets)

if ("watch_assets" in available_features):
assets = []
for asset in config.watch_assets:
a = self.ws.get_asset(asset)
if not a:
log.warning("The asset %s does not exist!" % a)

if ("onAssetUpdate" in available_features):
a["callback"] = config.onAssetUpdate
assets.append(a)
self.setAssetDispatcher(assets)

if "onRegisterHistory" in available_features:
self.setEventCallbacks(
{"registered-history": config.onRegisterHistory})
Expand Down Expand Up @@ -490,6 +498,30 @@ def getChainInfo(self):
"core_symbol" : core_asset["symbol"],
"chain_id" : chain_id}

def getObject(self, oid):
""" Get an Object either from Websocket store (if available) or
from RPC connection.
"""
if self.ws :
[_instance, _type, _id] = oid.split(".")
if (not (oid in self.ws.objectMap) or
_instance == "1" and _type == "7"): # force refresh orders
data = self.ws.get_object(oid)
self.ws.objectMap[oid] = data
else:
data = self.ws.objectMap[oid]
if len(data) == 1 :
return data[0]
else:
return data
else :
return self.rpc.get_object(oid)[0]

def get_object(self, oid):
""" Identical to ``getObject``
"""
return self.getObject(oid)

""" Forward these calls to Websocket API
"""
def setEventCallbacks(self, callbacks):
Expand All @@ -513,6 +545,13 @@ def setMarketCallBack(self, markets):
"""
self.ws.setMarketCallBack(markets)

def setAssetDispatcher(self, markets):
""" Internally used to register Market update callbacks
"""
self.ws.setAssetDispatcher(markets)

""" Connect to Websocket and run asynchronously
"""
def connect(self):
""" Only *connect* to the websocket server. Does **not** run the
subsystem.
Expand All @@ -529,22 +568,3 @@ def run(self):
""" Connect to Websocket server **and** run the subsystem """
self.connect()
self.run_forever()

def getObject(self, oid):
""" Get an Object either from Websocket store (if available) or
from RPC connection.
"""
if self.ws :
[_instance, _type, _id] = oid.split(".")
if (not (oid in self.ws.objectMap) or
_instance == "1" and _type == "7"): # force refresh orders
data = self.ws.get_object(oid)
self.ws.objectMap[oid] = data
else:
data = self.ws.objectMap[oid]
if len(data) == 1 :
return data[0]
else:
return data
else :
return self.rpc.get_object(oid)[0]
Loading

0 comments on commit 6657133

Please sign in to comment.