Skip to content
This repository has been archived by the owner on Jun 7, 2023. It is now read-only.

Commit

Permalink
Tests and polishing
Browse files Browse the repository at this point in the history
- Add tests
- Add length checks
- Prepeare setup.py for packaging
- Remove entangled interface
- Update README
  • Loading branch information
lzpap committed Oct 13, 2019
1 parent d441902 commit b62c07b
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 177 deletions.
94 changes: 0 additions & 94 deletions README.md

This file was deleted.

118 changes: 118 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
ccurl.interface.py
==================

A python interface to load `Ccurl
library <https://github.com/iotaledger/ccurl>`__ and perform proof of
work locally, without relying on a node. To be used together with
`PyOTA <https://github.com/iotaledger/iota.py>`__, the Python Client
Library for IOTA. For more info, `read the
docs <https://pyota.readthedocs.io/en/latest/>`__.

Motivation
----------

Currently, PyOTA doesn't support performing proof of work locally. The
``attach_to_tangle`` API command sends the prepared transaction trytes
to an IOTA node, that does the PoW by filling out
``attachment_timestamp*`` fields and calculating ``nonce`` with Curl
P-81. In case of bundles, transactions are chained together through
their ``transaction hash``.

Installation
------------

To use the module, follow the steps: - Clone the repo from GitHub:

``$ git clone https://github.com/lzpap/ccurl.interface.py.git``

- Make sure you have `cmake <https://cmake.org/>`__ availabke on your
system. This is a build dependecy for the ccurl library.
- Build ccurl according to `build
instructions <https://github.com/iotaledger/ccurl/blob/master/README.md>`__
and put the ``.so`` library into the ``src`` folder, or run the
``init`` script in the main folder to build and initialize:

``$ ./init.sh``

- Create a vitual environment / activate the one you use for PyOTA.
- Install th python package from source by running:

``$ pip install -e .``

How to use?
-----------

Once installed, you can use the module to replace ``attach_to_tangle``
core api call in PyOTA. Just import the ``ccurl_interface`` module from
the ``pow`` package and you are good to go.

An example code below illustrates how to do PoW for a bundle consisting
of two transactions.

Code Example
------------

::

import iota
from pprint import *
from pow import ccurl_interface

# Generate seed
myseed = iota.crypto.types.Seed.random()

#Generate two addresses
addres_generator = iota.crypto.addresses.AddressGenerator(myseed)
addys = addres_generator.get_addresses(1, count=2)

# preparing transactions
pt = iota.ProposedTransaction(address = iota.Address(addys[0]),
tag = iota.Tag(b'LOCALATTACHINTERFACE99999'),
value = 0)

pt2 = iota.ProposedTransaction(address = iota.Address(addys[1]),
tag = iota.Tag(b'LOCALATTACHINTERFACE99999'),
value = 0)

# preparing bundle that consists of both transactions prepared in the previous example
pb = iota.ProposedBundle(transactions=[pt2,pt])

# generate bundle hash
pb.finalize()

# declare a api instance
api = iota.Iota("https://nodes.thetangle.org:443") # selecting IOTA node

# get tips to be approved by your bundle
gta = api.get_transactions_to_approve(depth=3)

minimum_weight_magnitude = 14 # target is mainnet

# perform PoW locally
bundle_trytes =\
ccurl_interface.attach_to_tangle(
pb.as_tryte_strings(),
gta['trunkTransaction'],
gta['branchTransaction'],
mwm
)
# Broadcast transactions on the Tangle
broadcasted = api.broadcast_and_store(bundle_trytes)

bundle_broadcasted =iota.Bundle.from_tryte_strings(broadcasted['trytes'])

pprint('Local pow broadcasted transactions are:')
pprint(bundle_broadcasted.as_json_compatible())

Tests
-----

Run ``nosetests`` to test in current environment.
Run ``tox -v -p all`` to test in Python 2.7, 3.5, 3.6 and 3.8.

Contribute
----------

Raise issues:
https://github.com/lzpap/ccurl.interface.py/issues
3 changes: 2 additions & 1 deletion examples/with_ccurl.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@
message = iota.TryteString.from_unicode('Hey hey, trying to figure this thing out. This is tx2, now is %s' % (NowIs)),
tag = iota.Tag(b'LOCALATTACHINTERFACE99999'), # Up to 27 trytes
value = 0)

# besides the given attributes, library also adds a transaction timestamp

# preparing bundle that consists of both transactions prepared in the previous example
pb = iota.ProposedBundle(transactions=[pt2,pt]) # list of prepared transactions is needed at least
pb = iota.ProposedBundle(transactions=[pt,pt2]) # list of prepared transactions is needed at least

# generate bundle hash using sponge/absorb function + normalize bundle hash + copy bundle hash into each transaction / bundle is finalized
pb.finalize()
Expand Down
72 changes: 65 additions & 7 deletions pow/ccurl_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
import iota
import math
import time
from iota.exceptions import with_context

from pkg_resources import resource_filename
libccurl_path = resource_filename("pow","libccurl.so")

# Calculate time in milliseconds (for timestamp)
get_current_ms = lambda : int(round(time.time() * 1000))

# Load ccurl lib
_libccurl = CDLL(libccurl_path)

Expand All @@ -16,10 +20,33 @@
_libccurl.ccurl_digest_transaction.restype = POINTER(c_char)
_libccurl.ccurl_digest_transaction.argtypes = [c_char_p]

def check_tx_trytes_length(trytes):
"""
Checks if trytes are exactly one transaction in length.
"""
if len(trytes) != iota.TransactionTrytes.LEN:
raise with_context(
exc=ValueError('Trytes must be {len} trytes long.'.format(
len= iota.TransactionTrytes.LEN
)),

context={
'trytes': trytes,
},
)

# calling function in libccurl to calculate nonce
# based on transaction trytes
def get_powed_tx_trytes( trytes, mwm ):
# TODO: raise error if
"""
Calls `ccurl_pow` function from ccurl
library to calculate `nonce`. Returns transaction
trytes with calucalted nonce included, in unicode
string format.
"""
# Make sure we supply the right size of tx trytes to ccurl
check_tx_trytes_length(trytes)
# Call of external C function
powed_trytes = _libccurl.ccurl_pow(trytes.encode('utf-8'), mwm)
# Return value is a c_char pointer,pointing
# to the first character
Expand All @@ -33,32 +60,63 @@ def get_powed_tx_trytes( trytes, mwm ):
# calling function in libccurl to calculate tx hash
# based on transaction trytes (with nonce included)
def get_hash_trytes(trytes):
"""
Calls `ccurl_digest_transaction` function from ccurl
library to calculate `rtansaction hash`. Returns the
81 trytes long hash in unicode string format.
"""
# Make sure we supply the right size of tx trytes to ccurl
check_tx_trytes_length(trytes)
# Call of external C function
hash_pointer = _libccurl.ccurl_digest_transaction(trytes.encode('utf-8'))
# We know the length of the bytearray (c char array)
hash_bytes = hash_pointer[:81]
hash_unicode = hash_bytes.decode('utf-8')
return hash_unicode

# Takes a bundle object, calculates the pow, attaches tx hash
def attach_to_tangle(bundle_trytes, # Iterable[txtrytes]
trunk_transaction_hash,
branch_transaction_hash,
mwm):
def attach_to_tangle(bundle_trytes, # Iterable[TryteString]
trunk_transaction_hash, # TransactionHash
branch_transaction_hash, # TransactionHash
mwm=14): # Int
"""
Attaches the bundle to the Tangle by doing Proof-of-Work
locally. No connection to the Tangle is needed in this step.
:param bundle_trytes:
List of TryteString(s) that contain raw transaction trytes.
:param trunk_transaction_hash:
Trunk transaction hash obtained from an iota node.
Result of the tip selection process.
:param branch_transaction_hash:
Branch transaction hash obtained from an iota node.
Result of the tip selection process.
:param mwm:
Minimum Weight Magnitude to be used during the PoW.
Number of trailing zero trits in transaction hash.
:returns:
The bundle as a list of transaction trytes (TryteStrings).
Attachment timestamp and nonce included.
"""
previoustx = None

# Construct bundle object
bundle = iota.Bundle.from_tryte_strings(bundle_trytes)

for txn in reversed(bundle.transactions):
txn.attachment_timestamp = int(round(time.time() * 1000))
txn.attachment_timestamp = get_current_ms()
txn.attachment_timestamp_upper_bound = (math.pow(3,27) - 1) // 2

if (not previoustx): # this is the tail transaction
if txn.current_index == txn.last_index:
txn.branch_transaction_hash = branch_transaction_hash
txn.trunk_transaction_hash = trunk_transaction_hash
else:
raise ValueError('Something is not right, the last this should be the last tx in the bundle')
raise ValueError('Tail transaction is incosistent in bundle')

else: # It is not a tail transaction
txn.branch_transaction_hash = trunk_transaction_hash
Expand Down
Loading

0 comments on commit b62c07b

Please sign in to comment.