Skip to content
This repository was archived by the owner on Jan 13, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all 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
35 changes: 35 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,41 @@ This method returns a ``dict`` with the following items:
- ``bundles: List[Bundle]``: Matching bundles, sorted by tail
transaction timestamp.

``is_promotable``
-------------------

This extended API function helps you to determine whether a tail transaction
(bundle) is promotable.
Example usage could be to determine if a transaction can be promoted or you
should reattach (``replay_bundle``).

The method takes a list of tail transaction hashes, calls ``check_consistency``
to verify consistency. If successful, fetches the transaction trytes from the
Tangle and checks if ``attachment_timestamp`` is within reasonable limits.

Parameters
~~~~~~~~~~

- ``tails: List[TransactionHash]``: Tail transaction hashes to check.

Return
~~~~~~

This method returns a ``dict`` with the following items:

- ``promotable: bool``: ``True``, if:

- Tails are consistent.
See `API Reference <https://docs.iota.org/docs/node-software/0.1/iri/references/api-reference#checkconsistency>`_.
- ``attachment_timestamp`` for all transactions are less than current time
and attachement happened no earlier than ``depth`` milestones.
By default, ``depth`` = 6.

parameter is ``False`` otherwise.

- ``info: Optional(List[String])``: If ``promotable`` is ``False``, contains information
about the error.

``is_reattachable``
-------------------

Expand Down
37 changes: 37 additions & 0 deletions iota/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,43 @@ def get_transfers(self, start=0, stop=None, inclusion_states=False):
inclusionStates=inclusion_states,
)

def is_promotable(
self,
tails, # type: Iterable[TransactionHash]
):
# type: (Iterable(TransactionHash)] -> dict
"""
Checks if tail transaction(s) is promotable by calling
:py:meth:`check_consistency` and verifying that `attachmentTimestamp`
is above a lower bound.
Lower bound is calculated based on number of milestones issued
since transaction attachment.

:param tails:
List of tail transaction hashes.

:return:
The return type mimics that of :py:meth:`check_consistency`.
Dict with the following structure::

{
'promotable': bool,
If true, all tails are promotable. If false, see `info`
field.

'info': Optional(List[String])
If `promotable` is false, this contains info about what
went wrong.

}

References:
- https://github.com/iotaledger/iota.js/blob/next/api_reference.md#module_core.isPromotable
"""
return extended.IsPromotableCommand(self.adapter)(
tails=tails,
)

def prepare_transfer(
self,
transfers, # type: Iterable[ProposedTransaction]
Expand Down
1 change: 1 addition & 0 deletions iota/commands/extended/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from .get_new_addresses import *
from .get_transaction_objects import *
from .get_transfers import *
from .is_promotable import *
from .is_reattachable import *
from .prepare_transfer import *
from .promote_transaction import *
Expand Down
116 changes: 116 additions & 0 deletions iota/commands/extended/is_promotable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# coding=utf-8
from __future__ import absolute_import, division, print_function, \
unicode_literals

from iota.commands import FilterCommand, RequestFilter
from iota.commands.core import CheckConsistencyCommand, GetTrytesCommand
from iota.transaction import Transaction
from iota import TransactionHash
import filters as f
from iota.filters import Trytes
import time

__all__ = [
'IsPromotableCommand',
]

MILESTONE_INTERVAL = 2 * 60 * 1000
"""
Approximate interval in which a milestone is issued.
Unit is in milliseconds, so it is roughly 2 minutes.
"""

ONE_WAY_DELAY = 1 * 60 * 1000
"""
Propagation delay of the network (ms). (really-really worst case scenario)
The time needed for the message to propegate from client to edges of
majority network.
"""

DEPTH = 6
"""
The number of milestones issued since `attachmentTimestamp`.
"""

get_current_ms = lambda : int(round(time.time() * 1000))
"""
Calculate current time in milliseconds.
"""

class IsPromotableCommand(FilterCommand):
"""
Determines if a tail transaction is promotable.

See :py:meth:`iota.api.Iota.is_promotable` for more info.
"""
command = 'isPromotable'

def get_request_filter(self):
return IsPromotableRequestFilter()

def get_response_filter(self):
pass

def _execute(self, request):
tails = request['tails']

# First, check consistency
# A transaction is consistent, if:
# - The node isn't missing the transaction's branch or trunk transactions
# - The transaction's bundle is valid
# - The transaction's branch and trunk transactions are valid
cc_response = CheckConsistencyCommand(self.adapter)(
tails=tails,
)

if not cc_response['state']:
# One or more transactions are inconsistent
return {
'promotable' : False,
'info' : cc_response['info'],
}

transactions = [
Transaction.from_tryte_string(x) for x in
GetTrytesCommand(self.adapter)(hashes=tails)['trytes']
]

response = {
'promotable' : True,
'info' : [],
}

# Check timestamps
now = get_current_ms()
for tx in transactions:
is_within = is_within_depth(tx.attachment_timestamp, now)
if not is_within:
# Inform the user about what went wrong.
response['info'].append('Transaction {tx_hash} is above max depth.'.format(
tx_hash=tx.hash
))
# If one tx fails, response is false
response['promotable'] = response['promotable'] and is_within

# If there are no problems, we don't need 'info' field
if response['promotable']:
response['info'] = None

return response

class IsPromotableRequestFilter(RequestFilter):
def __init__(self):
super(IsPromotableRequestFilter, self).__init__({
'tails':
f.Required |
f.Array |
f.FilterRepeater(f.Required | Trytes(TransactionHash)),
})

def is_within_depth(attachment_timestamp, now, depth=DEPTH):
# type (int, int, Optiona(int)) -> bool
"""
Checks if `attachment_timestamp` is within limits of `depth`.
"""
return attachment_timestamp < now and \
now - attachment_timestamp < depth * MILESTONE_INTERVAL - ONE_WAY_DELAY
Loading