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

Commit 332412d

Browse files
authored
Merge pull request #12 from iotaledger/develop
1.0.0b5
2 parents 9828d48 + 0d274ce commit 332412d

File tree

8 files changed

+606
-103
lines changed

8 files changed

+606
-103
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
name = 'PyOTA',
2929
description = 'IOTA API library for Python',
3030
url = 'https://github.com/iotaledger/iota.lib.py',
31-
version = '1.0.0b4',
31+
version = '1.0.0b5',
3232

3333
packages = find_packages('src'),
3434
include_package_data = True,

src/iota/api.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -461,22 +461,23 @@ def get_latest_inclusion(self, hashes):
461461
"""
462462
return self.getLatestInclusion(hashes=hashes)
463463

464-
def get_new_addresses(self, index=None, count=1):
465-
# type: (Optional[int], Optional[int]) -> List[Address]
464+
def get_new_addresses(self, index=0, count=1):
465+
# type: (int, Optional[int]) -> List[Address]
466466
"""
467467
Generates one or more new addresses from the seed.
468468
469469
:param index:
470470
Specify the index of the new address (must be >= 1).
471471
472-
If not provided, the address will be generated deterministically.
473-
474472
:param count:
475473
Number of addresses to generate (must be >= 1).
476474
477475
Note: This is more efficient than calling ``get_new_address``
478476
inside a loop.
479477
478+
If ``None``, this method will scan the Tangle to find the next
479+
available unused address and return that.
480+
480481
:return:
481482
List of generated addresses.
482483

src/iota/commands/extended/get_new_addresses.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def _execute(self, request):
3535
index = request.get('index')
3636

3737
# Required parameters.
38-
seed = request['seed']
38+
seed = request['seed']
3939

4040
generator = AddressGenerator(seed)
4141

src/iota/crypto/signing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
from iota import TRITS_PER_TRYTE, TryteString, TrytesCompatible, Hash
1010
from iota.crypto import Curl, FRAGMENT_LENGTH, HASH_LENGTH
11-
from iota.crypto.types import PrivateKey
11+
from iota.crypto.types import PrivateKey, Seed
1212
from iota.exceptions import with_context
1313

1414
__all__ = [
@@ -68,7 +68,7 @@ def __init__(self, seed):
6868
# type: (TrytesCompatible) -> None
6969
super(KeyGenerator, self).__init__()
7070

71-
self.seed = TryteString(seed)
71+
self.seed = Seed(seed)
7272

7373
def get_keys(self, start, count=1, step=1, iterations=1):
7474
# type: (int, int, int, int) -> List[PrivateKey]

src/iota/crypto/types.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,15 @@ def get_digest_trits(self):
8787
"""
8888
hashes_per_fragment = FRAGMENT_LENGTH // Hash.LEN
8989

90-
digest = [0] * HASH_LENGTH
90+
key_chunks = self.iter_chunks(FRAGMENT_LENGTH)
9191

92-
for (i, fragment) in enumerate(self.iter_chunks(FRAGMENT_LENGTH)): # type: Tuple[int, TryteString]
93-
fragment_start = i * FRAGMENT_LENGTH
94-
fragment_end = fragment_start + FRAGMENT_LENGTH
95-
fragment_trits = fragment[fragment_start:fragment_end].as_trits()
92+
# The digest will contain one hash per key fragment.
93+
digest = [0] * HASH_LENGTH * len(key_chunks)
94+
95+
for (i, fragment) in enumerate(key_chunks): # type: Tuple[int, TryteString]
96+
fragment_trits = fragment.as_trits()
9697

97-
key_fragment = [0] * len(fragment_trits)
98+
key_fragment = [0] * FRAGMENT_LENGTH
9899
hash_trits = []
99100

100101
for j in range(hashes_per_fragment):
@@ -113,6 +114,9 @@ def get_digest_trits(self):
113114
sponge.absorb(key_fragment)
114115
sponge.squeeze(hash_trits)
115116

117+
fragment_start = i * FRAGMENT_LENGTH
118+
fragment_end = fragment_start + FRAGMENT_LENGTH
119+
116120
digest[fragment_start:fragment_end] = hash_trits
117121

118122
return digest

src/iota/transaction.py

Lines changed: 71 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
validate_signature_fragments
1616
from iota.exceptions import with_context
1717
from iota.json import JsonSerializable
18+
from six import PY2
1819

1920
__all__ = [
2021
'Bundle',
@@ -127,51 +128,51 @@ def __init__(
127128
branch_transaction_hash,
128129
nonce,
129130
):
130-
# type: (Optional[TransactionHash], Optional[TryteString], Address, int, Tag, int, Optional[int], Optional[int], Optional[BundleHash], Optional[TransactionHash], Optional[TransactionHash], Optional[Hash]) -> None
131-
self.hash = hash_
131+
# type: (Optional[TransactionHash], Optional[Fragment], Address, int, Optional[Tag], int, Optional[int], Optional[int], Optional[BundleHash], Optional[TransactionHash], Optional[TransactionHash], Optional[Hash]) -> None
132+
self.hash = hash_ # type: Optional[TransactionHash]
132133
"""
133134
Transaction ID, generated by taking a hash of the transaction
134135
trits.
135136
"""
136137

137-
self.bundle_hash = bundle_hash
138+
self.bundle_hash = bundle_hash # type: Optional[BundleHash]
138139
"""
139140
Bundle hash, generated by taking a hash of metadata from all the
140141
transactions in the bundle.
141142
"""
142143

143-
self.address = address
144+
self.address = address # type: Address
144145
"""
145146
The address associated with this transaction.
146147
If ``value`` is != 0, the associated address' balance is adjusted
147148
as a result of this transaction.
148149
"""
149150

150-
self.value = value
151+
self.value = value # type: int
151152
"""
152153
Amount to adjust the balance of ``address``.
153154
Can be negative (i.e., for spending inputs).
154155
"""
155156

156-
self.tag = tag
157+
self.tag = tag # type: Optional[Tag]
157158
"""
158159
Optional classification tag applied to this transaction.
159160
"""
160161

161-
self.nonce = nonce
162+
self.nonce = nonce # type: Optional[Hash]
162163
"""
163164
Unique value used to increase security of the transaction hash.
164165
"""
165166

166-
self.timestamp = timestamp
167+
self.timestamp = timestamp # type: int
167168
"""
168169
Timestamp used to increase the security of the transaction hash.
169170
170171
IMPORTANT: This value is easy to forge!
171172
Do not rely on it when resolving conflicts!
172173
"""
173174

174-
self.current_index = current_index
175+
self.current_index = current_index # type: Optional[int]
175176
"""
176177
The position of the transaction inside the bundle.
177178
@@ -180,12 +181,12 @@ def __init__(
180181
last.
181182
"""
182183

183-
self.last_index = last_index
184+
self.last_index = last_index # type: Optional[int]
184185
"""
185186
The position of the final transaction inside the bundle.
186187
"""
187188

188-
self.trunk_transaction_hash = trunk_transaction_hash
189+
self.trunk_transaction_hash = trunk_transaction_hash # type: Optional[TransactionHash]
189190
"""
190191
In order to add a transaction to the Tangle, you must perform PoW
191192
to "approve" two existing transactions, called the "trunk" and
@@ -195,7 +196,7 @@ def __init__(
195196
a bundle.
196197
"""
197198

198-
self.branch_transaction_hash = branch_transaction_hash
199+
self.branch_transaction_hash = branch_transaction_hash # type: Optional[TransactionHash]
199200
"""
200201
In order to add a transaction to the Tangle, you must perform PoW
201202
to "approve" two existing transactions, called the "trunk" and
@@ -204,7 +205,7 @@ def __init__(
204205
The branch transaction generally has no significance.
205206
"""
206207

207-
self.signature_message_fragment = signature_message_fragment
208+
self.signature_message_fragment = signature_message_fragment # type: Optional[Fragment]
208209
"""
209210
"Signature/Message Fragment" (note the slash):
210211
@@ -676,19 +677,34 @@ class ProposedBundle(JsonSerializable, Sequence[ProposedTransaction]):
676677
A collection of proposed transactions, to be treated as an atomic
677678
unit when attached to the Tangle.
678679
"""
679-
def __init__(self, transactions=None):
680-
# type: (Optional[Iterable[ProposedTransaction]]) -> None
680+
def __init__(self, transactions=None, inputs=None, change_address=None):
681+
# type: (Optional[Iterable[ProposedTransaction]], Optional[Iterable[Address]], Optional[Address]) -> None
681682
super(ProposedBundle, self).__init__()
682683

683684
self.hash = None # type: Optional[Hash]
684-
self.tag = None # type: Optional[Tag]
685685

686686
self._transactions = [] # type: List[ProposedTransaction]
687687

688688
if transactions:
689689
for t in transactions:
690690
self.add_transaction(t)
691691

692+
if inputs:
693+
self.add_inputs(inputs)
694+
695+
self.change_address = change_address
696+
697+
def __bool__(self):
698+
# type: () -> bool
699+
"""
700+
Returns whether this bundle has any transactions.
701+
"""
702+
return bool(self._transactions)
703+
704+
# :bc: Magic methods have different names in Python 2.
705+
if PY2:
706+
__nonzero__ = __bool__
707+
692708
def __contains__(self, transaction):
693709
# type: (ProposedTransaction) -> bool
694710
return transaction in self._transactions
@@ -730,6 +746,19 @@ def balance(self):
730746
"""
731747
return sum(t.value for t in self._transactions)
732748

749+
@property
750+
def tag(self):
751+
# type: () -> Tag
752+
"""
753+
Determines the most relevant tag for the bundle.
754+
"""
755+
for txn in reversed(self): # type: ProposedTransaction
756+
if txn.tag:
757+
# noinspection PyTypeChecker
758+
return txn.tag
759+
760+
return Tag(b'')
761+
733762
def as_json_compatible(self):
734763
# type: () -> List[dict]
735764
"""
@@ -762,6 +791,9 @@ def add_transaction(self, transaction):
762791
if self.hash:
763792
raise RuntimeError('Bundle is already finalized.')
764793

794+
if transaction.value < 0:
795+
raise ValueError('Use ``add_inputs`` to add inputs to the bundle.')
796+
765797
self._transactions.append(ProposedTransaction(
766798
address = transaction.address,
767799
value = transaction.value,
@@ -770,9 +802,6 @@ def add_transaction(self, transaction):
770802
timestamp = transaction.timestamp,
771803
))
772804

773-
# Last-added transaction determines the bundle tag.
774-
self.tag = transaction.tag or self.tag
775-
776805
# If the message is too long to fit in a single transactions,
777806
# it must be split up into multiple transactions so that it will
778807
# fit.
@@ -835,7 +864,7 @@ def add_inputs(self, inputs):
835864
)
836865

837866
# Add the input as a transaction.
838-
self.add_transaction(ProposedTransaction(
867+
self._transactions.append(ProposedTransaction(
839868
address = addy,
840869
tag = self.tag,
841870

@@ -848,7 +877,7 @@ def add_inputs(self, inputs):
848877
# transaction length limit.
849878
# Subtract 1 to account for the transaction we just added.
850879
for _ in range(AddressGenerator.DIGEST_ITERATIONS - 1):
851-
self.add_transaction(ProposedTransaction(
880+
self._transactions.append(ProposedTransaction(
852881
address = addy,
853882
tag = self.tag,
854883

@@ -867,16 +896,7 @@ def send_unspent_inputs_to(self, address):
867896
if self.hash:
868897
raise RuntimeError('Bundle is already finalized.')
869898

870-
# Negative balance means that there are unspent inputs.
871-
# See :py:meth:`balance` for more info.
872-
unspent_inputs = -self.balance
873-
874-
if unspent_inputs > 0:
875-
self.add_transaction(ProposedTransaction(
876-
address = address,
877-
value = unspent_inputs,
878-
tag = self.tag,
879-
))
899+
self.change_address = address
880900

881901
def finalize(self):
882902
# type: () -> None
@@ -886,21 +906,34 @@ def finalize(self):
886906
if self.hash:
887907
raise RuntimeError('Bundle is already finalized.')
888908

909+
if not self:
910+
raise ValueError('Bundle has no transactions.')
911+
889912
# Quick validation.
890913
balance = self.balance
891-
if balance > 0:
914+
915+
if balance < 0:
916+
if self.change_address:
917+
self.add_transaction(ProposedTransaction(
918+
address = self.change_address,
919+
value = -balance,
920+
tag = self.tag,
921+
))
922+
else:
923+
raise ValueError(
924+
'Bundle has unspent inputs (balance: {balance}); '
925+
'use ``send_unspent_inputs_to`` to create '
926+
'change transaction.'.format(
927+
balance = balance,
928+
),
929+
)
930+
elif balance > 0:
892931
raise ValueError(
893932
'Inputs are insufficient to cover bundle spend '
894933
'(balance: {balance}).'.format(
895934
balance = balance,
896935
),
897936
)
898-
elif balance < 0:
899-
raise ValueError(
900-
'Bundle has unspent inputs (balance: {balance}).'.format(
901-
balance = balance,
902-
),
903-
)
904937

905938
# Generate bundle hash.
906939
sponge = Curl()

0 commit comments

Comments
 (0)