1515 validate_signature_fragments
1616from iota .exceptions import with_context
1717from 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