Skip to content
This repository was archived by the owner on Jan 13, 2023. It is now read-only.

Commit 27f74d6

Browse files
authored
Merge pull request #127 from scottbelden/promote_transaction
add promote_transaction_api
2 parents 31fa921 + ab729af commit 27f74d6

File tree

10 files changed

+610
-7
lines changed

10 files changed

+610
-7
lines changed

iota/api.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,33 @@ def prepare_transfer(self, transfers, inputs=None, change_address=None):
775775
changeAddress = change_address,
776776
)
777777

778+
def promote_transaction(
779+
self,
780+
transaction,
781+
depth,
782+
min_weight_magnitude = None,
783+
):
784+
# type: (TransactionHash, int, Optional[int]) -> dict
785+
"""
786+
Promotes a transaction by adding spam on top of it.
787+
788+
:return:
789+
Dict containing the following values::
790+
791+
{
792+
'bundle': Bundle,
793+
The newly-published bundle.
794+
}
795+
"""
796+
if min_weight_magnitude is None:
797+
min_weight_magnitude = self.default_min_weight_magnitude
798+
799+
return extended.PromoteTransactionCommand(self.adapter)(
800+
transaction = transaction,
801+
depth = depth,
802+
minWeightMagnitude = min_weight_magnitude,
803+
)
804+
778805
def replay_bundle(
779806
self,
780807
transaction,

iota/commands/core/get_transactions_to_approve.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,26 @@ class GetTransactionsToApproveRequestFilter(RequestFilter):
3232
def __init__(self):
3333
super(GetTransactionsToApproveRequestFilter, self).__init__({
3434
'depth': f.Required | f.Type(int) | f.Min(1),
35+
36+
'reference': Trytes(result_type=TransactionHash),
37+
},
38+
39+
allow_missing_keys = {
40+
'reference',
3541
})
3642

43+
def _apply(self, value):
44+
value = super(GetTransactionsToApproveRequestFilter, self)._apply(value) # type: dict
45+
46+
if self._has_errors:
47+
return value
48+
49+
# Remove reference if null.
50+
if value['reference'] is None:
51+
del value['reference']
52+
53+
return value
54+
3755

3856
class GetTransactionsToApproveResponseFilter(ResponseFilter):
3957
def __init__(self):

iota/commands/extended/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from .get_new_addresses import *
2020
from .get_transfers import *
2121
from .prepare_transfer import *
22+
from .promote_transaction import *
2223
from .replay_bundle import *
2324
from .send_transfer import *
2425
from .send_trytes import *
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# coding=utf-8
2+
from __future__ import absolute_import, division, print_function, \
3+
unicode_literals
4+
5+
import filters as f
6+
from iota import (
7+
Bundle, TransactionHash, Address, ProposedTransaction, BadApiResponse,
8+
)
9+
from iota.commands import FilterCommand, RequestFilter
10+
from iota.commands.core.check_consistency import CheckConsistencyCommand
11+
from iota.commands.extended.send_transfer import SendTransferCommand
12+
from iota.filters import Trytes
13+
14+
__all__ = [
15+
'PromoteTransactionCommand',
16+
]
17+
18+
19+
class PromoteTransactionCommand(FilterCommand):
20+
"""
21+
Executes ``promoteTransaction`` extended API command.
22+
23+
See :py:meth:`iota.api.Iota.promote_transaction` for more information.
24+
"""
25+
command = 'promoteTransaction'
26+
27+
def get_request_filter(self):
28+
return PromoteTransactionRequestFilter()
29+
30+
def get_response_filter(self):
31+
pass
32+
33+
def _execute(self, request):
34+
depth = request['depth'] # type: int
35+
min_weight_magnitude = request['minWeightMagnitude'] # type: int
36+
transaction = request['transaction'] # type: TransactionHash
37+
38+
cc_response = CheckConsistencyCommand(self.adapter)(tails=[transaction])
39+
if cc_response['state'] is False:
40+
raise BadApiResponse(
41+
'Transaction {transaction} is not promotable. '
42+
'You should reattach first.'.format(transaction=transaction)
43+
)
44+
45+
spam_transfer = ProposedTransaction(
46+
address=Address(b''),
47+
value=0,
48+
)
49+
50+
return SendTransferCommand(self.adapter)(
51+
seed=spam_transfer.address,
52+
depth=depth,
53+
transfers=[spam_transfer],
54+
minWeightMagnitude=min_weight_magnitude,
55+
reference=transaction,
56+
)
57+
58+
59+
class PromoteTransactionRequestFilter(RequestFilter):
60+
def __init__(self):
61+
super(PromoteTransactionRequestFilter, self).__init__({
62+
'depth': f.Required | f.Type(int) | f.Min(1),
63+
'transaction': f.Required | Trytes(result_type=TransactionHash),
64+
65+
# Loosely-validated; testnet nodes require a different value than
66+
# mainnet.
67+
'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1),
68+
})

iota/commands/extended/send_transfer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from typing import List, Optional
66

77
import filters as f
8-
from iota import Address, Bundle, ProposedTransaction
8+
from iota import Address, Bundle, ProposedTransaction, TransactionHash
99
from iota.commands import FilterCommand, RequestFilter
1010
from iota.commands.extended.prepare_transfer import PrepareTransferCommand
1111
from iota.commands.extended.send_trytes import SendTrytesCommand
@@ -38,6 +38,7 @@ def _execute(self, request):
3838
min_weight_magnitude = request['minWeightMagnitude'] # type: int
3939
seed = request['seed'] # type: Seed
4040
transfers = request['transfers'] # type: List[ProposedTransaction]
41+
reference = request['reference'] # type: Optional[TransactionHash]
4142

4243
pt_response = PrepareTransferCommand(self.adapter)(
4344
changeAddress = change_address,
@@ -50,6 +51,7 @@ def _execute(self, request):
5051
depth = depth,
5152
minWeightMagnitude = min_weight_magnitude,
5253
trytes = pt_response['trytes'],
54+
reference = reference,
5355
)
5456

5557
return {
@@ -82,10 +84,13 @@ def __init__(self):
8284
# Note that ``inputs`` is allowed to be an empty array.
8385
'inputs':
8486
f.Array | f.FilterRepeater(f.Required | Trytes(result_type=Address)),
87+
88+
'reference': Trytes(result_type=TransactionHash),
8589
},
8690

8791
allow_missing_keys = {
8892
'changeAddress',
8993
'inputs',
94+
'reference',
9095
},
9196
)

iota/commands/extended/send_trytes.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from typing import List
66

77
import filters as f
8-
from iota import TransactionTrytes, TryteString
8+
from iota import TransactionTrytes, TryteString, TransactionHash
99
from iota.commands import FilterCommand, RequestFilter
1010
from iota.commands.core.attach_to_tangle import AttachToTangleCommand
1111
from iota.commands.core.get_transactions_to_approve import \
@@ -36,10 +36,14 @@ def _execute(self, request):
3636
depth = request['depth'] # type: int
3737
min_weight_magnitude = request['minWeightMagnitude'] # type: int
3838
trytes = request['trytes'] # type: List[TryteString]
39+
reference = request['reference'] # type: Optional[TransactionHash]
3940

4041
# Call ``getTransactionsToApprove`` to locate trunk and branch
4142
# transactions so that we can attach the bundle to the Tangle.
42-
gta_response = GetTransactionsToApproveCommand(self.adapter)(depth=depth)
43+
gta_response = GetTransactionsToApproveCommand(self.adapter)(
44+
depth=depth,
45+
reference=reference,
46+
)
4347

4448
att_response = AttachToTangleCommand(self.adapter)(
4549
branchTransaction = gta_response.get('branchTransaction'),
@@ -72,4 +76,10 @@ def __init__(self):
7276
# Loosely-validated; testnet nodes require a different value than
7377
# mainnet.
7478
'minWeightMagnitude': f.Required | f.Type(int) | f.Min(1),
79+
80+
'reference': Trytes(result_type=TransactionHash),
81+
},
82+
83+
allow_missing_keys = {
84+
'reference',
7585
})

test/commands/core/get_transactions_to_approve_test.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,43 @@
1010
from iota.adapter import MockAdapter
1111
from iota.commands.core.get_transactions_to_approve import \
1212
GetTransactionsToApproveCommand
13+
from iota.filters import Trytes
1314

1415

1516
class GetTransactionsToApproveRequestFilterTestCase(BaseFilterTestCase):
1617
filter_type =\
1718
GetTransactionsToApproveCommand(MockAdapter()).get_request_filter
1819
skip_value_check = True
1920

20-
def test_pass_happy_path(self):
21+
def setUp(self):
22+
super(GetTransactionsToApproveRequestFilterTestCase, self).setUp()
23+
24+
# Define some tryte sequences that we can reuse between tests.
25+
self.trytes1 = (
26+
b'TESTVALUEONE9DONTUSEINPRODUCTION99999JBW'
27+
b'GEC99GBXFFBCHAEJHLC9DX9EEPAI9ICVCKBX9FFII'
28+
)
29+
30+
def test_pass_happy_path_without_reference(self):
31+
"""
32+
Request is valid without reference.
33+
"""
34+
request = {
35+
'depth': 100,
36+
}
37+
38+
filter_ = self._filter(request)
39+
40+
self.assertFilterPasses(filter_)
41+
self.assertDictEqual(filter_.cleaned_data, request)
42+
43+
def test_pass_happy_path_with_reference(self):
2144
"""
22-
Request is valid.
45+
Request is valid with reference.
2346
"""
2447
request = {
2548
'depth': 100,
49+
'reference': TransactionHash(self.trytes1),
2650
}
2751

2852
filter_ = self._filter(request)
@@ -115,6 +139,38 @@ def test_fail_depth_too_small(self):
115139
},
116140
)
117141

142+
def test_fail_reference_wrong_type(self):
143+
"""
144+
``reference`` is not a TrytesCompatible value.
145+
"""
146+
self.assertFilterErrors(
147+
{
148+
'reference': 42,
149+
150+
'depth': 100,
151+
},
152+
153+
{
154+
'reference': [f.Type.CODE_WRONG_TYPE],
155+
},
156+
)
157+
158+
def test_fail_reference_not_trytes(self):
159+
"""
160+
``reference`` contains invalid characters.
161+
"""
162+
self.assertFilterErrors(
163+
{
164+
'reference': b'not valid; must contain only uppercase and "9"',
165+
166+
'depth': 100,
167+
},
168+
169+
{
170+
'reference': [Trytes.CODE_NOT_TRYTES],
171+
},
172+
)
173+
118174

119175
class GetTransactionsToApproveResponseFilterTestCase(BaseFilterTestCase):
120176
filter_type =\

0 commit comments

Comments
 (0)