iota-ruby 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.editorconfig +8 -0
- data/.gitignore +12 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +88 -0
- data/Rakefile +10 -0
- data/bin/iota-console +15 -0
- data/examples/multisig.rb +57 -0
- data/iota-ruby.gemspec +25 -0
- data/lib/iota/api/api.rb +194 -0
- data/lib/iota/api/commands.rb +99 -0
- data/lib/iota/api/wrappers.rb +402 -0
- data/lib/iota/crypto/bundle.rb +163 -0
- data/lib/iota/crypto/converter.rb +241 -0
- data/lib/iota/crypto/curl.rb +74 -0
- data/lib/iota/crypto/hmac.rb +27 -0
- data/lib/iota/crypto/kerl.rb +78 -0
- data/lib/iota/crypto/private_key.rb +80 -0
- data/lib/iota/crypto/signing.rb +97 -0
- data/lib/iota/models/account.rb +470 -0
- data/lib/iota/models/base.rb +13 -0
- data/lib/iota/models/bundle.rb +87 -0
- data/lib/iota/models/input.rb +38 -0
- data/lib/iota/models/seed.rb +33 -0
- data/lib/iota/models/transaction.rb +52 -0
- data/lib/iota/models/transfer.rb +44 -0
- data/lib/iota/multisig/address.rb +41 -0
- data/lib/iota/multisig/multisig.rb +244 -0
- data/lib/iota/utils/ascii.rb +50 -0
- data/lib/iota/utils/broker.rb +117 -0
- data/lib/iota/utils/input_validator.rb +149 -0
- data/lib/iota/utils/object_validator.rb +34 -0
- data/lib/iota/utils/utils.rb +326 -0
- data/lib/iota/version.rb +3 -0
- data/lib/iota.rb +73 -0
- data/test/ascii_test.rb +114 -0
- data/test/kerl_test.rb +64 -0
- data/test/test_helper.rb +4 -0
- data/test/utils_test.rb +173 -0
- metadata +144 -0
@@ -0,0 +1,402 @@
|
|
1
|
+
module IOTA
|
2
|
+
module API
|
3
|
+
module Wrappers
|
4
|
+
def getTransactionsObjects(hashes, &callback)
|
5
|
+
# If not array of hashes, return error
|
6
|
+
if !@validator.isArrayOfHashes(hashes)
|
7
|
+
return sendData(false, "Invalid inputs provided", &callback)
|
8
|
+
end
|
9
|
+
|
10
|
+
ret_status = false
|
11
|
+
ret_data = nil
|
12
|
+
# get the trytes of the transaction hashes
|
13
|
+
getTrytes(hashes) do |status, trytes|
|
14
|
+
ret_status = status
|
15
|
+
if status
|
16
|
+
transactionObjects = []
|
17
|
+
trytes.each do |tryte|
|
18
|
+
if !tryte
|
19
|
+
transactionObjects << nil
|
20
|
+
else
|
21
|
+
transactionObjects << @utils.transactionObject(tryte)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
ret_data = transactionObjects
|
25
|
+
else
|
26
|
+
ret_data = trytes
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
sendData(ret_status, ret_data, &callback)
|
31
|
+
end
|
32
|
+
|
33
|
+
def findTransactionObjects(input, &callback)
|
34
|
+
findTransactions(input) do |status, transactions|
|
35
|
+
if !status
|
36
|
+
return sendData(status, transactions, &callback)
|
37
|
+
else
|
38
|
+
return getTransactionsObjects(transactions, &callback)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def getLatestInclusion(hashes, &callback)
|
44
|
+
ret_status = false
|
45
|
+
ret_data = nil
|
46
|
+
getNodeInfo do |status, data|
|
47
|
+
if status
|
48
|
+
ret_status = true
|
49
|
+
ret_data = data['latestSolidSubtangleMilestone']
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if ret_status
|
54
|
+
return getInclusionStates(hashes, [ret_data], &callback)
|
55
|
+
else
|
56
|
+
return sendData(ret_status, ret_data, &callback)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def storeAndBroadcast(trytes, &callback)
|
61
|
+
storeTransactions(trytes) do |status, data|
|
62
|
+
if !status
|
63
|
+
return sendData(status, data, &callback)
|
64
|
+
else
|
65
|
+
return broadcastTransactions(trytes, &callback)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def sendTrytes(trytes, depth, minWeightMagnitude, &callback)
|
71
|
+
# Check if correct depth and minWeightMagnitude
|
72
|
+
if !@validator.isValue(depth) || !@validator.isValue(minWeightMagnitude)
|
73
|
+
return sendData(false, "Invalid inputs provided", &callback)
|
74
|
+
end
|
75
|
+
|
76
|
+
getTransactionsToApprove(depth) do |status, approval_data|
|
77
|
+
if !status
|
78
|
+
return sendData(false, approval_data, &callback)
|
79
|
+
end
|
80
|
+
|
81
|
+
attachToTangle(approval_data['trunkTransaction'], approval_data['branchTransaction'], minWeightMagnitude, trytes) do |status1, attached_data|
|
82
|
+
if !status1
|
83
|
+
return sendData(false, attached_data, &callback)
|
84
|
+
end
|
85
|
+
|
86
|
+
# If the user is connected to the sandbox, we have to monitor the POW queue
|
87
|
+
# to check if the POW job was completed
|
88
|
+
if @sandbox
|
89
|
+
# Implement sandbox processing
|
90
|
+
jobUri = @sandbox + '/jobs/' + attached_data['id']
|
91
|
+
|
92
|
+
# Do the Sandbox send function
|
93
|
+
@broker.sandboxSend(jobUri) do |status2, sandbox_data|
|
94
|
+
if !status2
|
95
|
+
return sendData(false, sandbox_data, &callback)
|
96
|
+
end
|
97
|
+
|
98
|
+
storeAndBroadcast(sandbox_data) do |status3, data|
|
99
|
+
if status3
|
100
|
+
return sendData(false, data, &callback)
|
101
|
+
end
|
102
|
+
|
103
|
+
finalTxs = []
|
104
|
+
|
105
|
+
attachedTrytes.each do |trytes1|
|
106
|
+
finalTxs << @utils.transactionObject(trytes1)
|
107
|
+
end
|
108
|
+
|
109
|
+
return sendData(true, finalTxs, &callback)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
# Broadcast and store tx
|
114
|
+
storeAndBroadcast(attached_data) do |status2, data|
|
115
|
+
if !status2
|
116
|
+
return sendData(false, data, &callback)
|
117
|
+
end
|
118
|
+
|
119
|
+
transactions = attached_data.map { |tryte| @utils.transactionObject(tryte) }
|
120
|
+
|
121
|
+
sendData(true, transactions, &callback)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def bundlesFromAddresses(addresses, inclusionStates, &callback)
|
129
|
+
# call wrapper function to get txs associated with addresses
|
130
|
+
findTransactionObjects(addresses: addresses) do |status, transactionObjects|
|
131
|
+
if !status
|
132
|
+
return sendData(false, transactionObjects, &callback)
|
133
|
+
end
|
134
|
+
|
135
|
+
# set of tail transactions
|
136
|
+
tailTransactions = []
|
137
|
+
nonTailBundleHashes = []
|
138
|
+
|
139
|
+
transactionObjects.each do |trx|
|
140
|
+
# Sort tail and nonTails
|
141
|
+
if trx.currentIndex == 0
|
142
|
+
tailTransactions << trx.hash
|
143
|
+
else
|
144
|
+
nonTailBundleHashes << trx.bundle
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Get tail transactions for each nonTail via the bundle hash
|
149
|
+
findTransactionObjects(bundles: nonTailBundleHashes.uniq) do |st1, trxObjects|
|
150
|
+
if !st1
|
151
|
+
return sendData(false, trxObjects, &callback)
|
152
|
+
end
|
153
|
+
|
154
|
+
trxObjects.each do |trx|
|
155
|
+
tailTransactions << trx.hash if trx.currentIndex == 0
|
156
|
+
end
|
157
|
+
|
158
|
+
finalBundles = []
|
159
|
+
tailTransactions = tailTransactions.uniq
|
160
|
+
tailTxStates = []
|
161
|
+
|
162
|
+
# If inclusionStates, get the confirmation status of the tail transactions, and thus the bundles
|
163
|
+
if inclusionStates && tailTransactions.length > 0
|
164
|
+
getLatestInclusion(tailTransactions) do |st2, states|
|
165
|
+
# If error, return it to original caller
|
166
|
+
if !status
|
167
|
+
return sendData(false, states, &callback)
|
168
|
+
end
|
169
|
+
|
170
|
+
tailTxStates = states
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
tailTransactions.each do |tailTx|
|
175
|
+
getBundle(tailTx) do |st2, bundleTransactions|
|
176
|
+
if st2
|
177
|
+
if inclusionStates
|
178
|
+
thisInclusion = tailTxStates[tailTransactions.index(tailTx)]
|
179
|
+
|
180
|
+
bundleTransactions.each do |bundleTx|
|
181
|
+
bundleTx.persistence = thisInclusion
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
finalBundles << IOTA::Models::Bundle.new(bundleTransactions)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
# Sort bundles by attachmentTimestamp
|
190
|
+
finalBundles = finalBundles.sort{|a, b| a.attachmentTimestamp <=> b.attachmentTimestamp}
|
191
|
+
return sendData(true, finalBundles, &callback)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def getBundle(transaction, &callback)
|
197
|
+
# Check if correct hash
|
198
|
+
if !@validator.isHash(transaction)
|
199
|
+
return sendData(false, "Invalid transaction input provided", &callback)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Initiate traverseBundle
|
203
|
+
traverseBundle(transaction, nil, []) do |status, bundle|
|
204
|
+
if !status
|
205
|
+
return sendData(false, bundle, &callback)
|
206
|
+
end
|
207
|
+
|
208
|
+
if !@utils.isBundle(bundle)
|
209
|
+
return sendData(false, "Invalid Bundle provided", &callback)
|
210
|
+
end
|
211
|
+
|
212
|
+
return sendData(true, bundle, &callback)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def replayBundle(tail, depth, minWeightMagnitude, &callback)
|
217
|
+
# Check if correct tail hash
|
218
|
+
if !@validator.isHash(tail)
|
219
|
+
return sendData(false, "Invalid trytes provided", &callback)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Check if correct depth and minWeightMagnitude
|
223
|
+
if !@validator.isValue(depth) || !@validator.isValue(minWeightMagnitude)
|
224
|
+
return sendData(false, "Invalid inputs provided", &callback)
|
225
|
+
end
|
226
|
+
|
227
|
+
getBundle(tail) do |status, transactions|
|
228
|
+
if !status
|
229
|
+
return sendData(false, transactions, &callback)
|
230
|
+
end
|
231
|
+
|
232
|
+
bundleTrytes = []
|
233
|
+
transactions.each do |trx|
|
234
|
+
bundleTrytes << @utils.transactionTrytes(trx);
|
235
|
+
end
|
236
|
+
|
237
|
+
return sendTrytes(bundleTrytes.reverse, depth, minWeightMagnitude, &callback)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def broadcastBundle(tail, &callback)
|
242
|
+
# Check if correct tail hash
|
243
|
+
if !@validator.isHash(tail)
|
244
|
+
return sendData(false, "Invalid trytes provided", &callback)
|
245
|
+
end
|
246
|
+
|
247
|
+
getBundle(tail) do |status, transactions|
|
248
|
+
if !status
|
249
|
+
return sendData(false, transactions, &callback)
|
250
|
+
end
|
251
|
+
|
252
|
+
bundleTrytes = []
|
253
|
+
transactions.each do |trx|
|
254
|
+
bundleTrytes << @utils.transactionTrytes(trx);
|
255
|
+
end
|
256
|
+
|
257
|
+
return broadcastTransactions(bundleTrytes.reverse, &callback)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def traverseBundle(trunkTx, bundleHash, bundle, &callback)
|
262
|
+
# Get trytes of transaction hash
|
263
|
+
getTrytes([trunkTx]) do |status, trytesList|
|
264
|
+
if !status
|
265
|
+
return sendData(false, trytesList, &callback)
|
266
|
+
end
|
267
|
+
|
268
|
+
trytes = trytesList[0]
|
269
|
+
|
270
|
+
if !trytes
|
271
|
+
return sendData(false, "Bundle transactions not visible", &callback)
|
272
|
+
end
|
273
|
+
|
274
|
+
# get the transaction object
|
275
|
+
txObject = @utils.transactionObject(trytes)
|
276
|
+
|
277
|
+
if !trytes
|
278
|
+
return sendData(false, "Invalid trytes, could not create object", &callback)
|
279
|
+
end
|
280
|
+
|
281
|
+
# If first transaction to search is not a tail, return error
|
282
|
+
if !bundleHash && txObject.currentIndex != 0
|
283
|
+
return sendData(false, "Invalid tail transaction supplied", &callback)
|
284
|
+
end
|
285
|
+
|
286
|
+
# If no bundle hash, define it
|
287
|
+
if !bundleHash
|
288
|
+
bundleHash = txObject.bundle
|
289
|
+
end
|
290
|
+
|
291
|
+
# If different bundle hash, return with bundle
|
292
|
+
if bundleHash != txObject.bundle
|
293
|
+
return sendData(true, bundle, &callback)
|
294
|
+
end
|
295
|
+
|
296
|
+
# If only one bundle element, return
|
297
|
+
if txObject.lastIndex == 0 && txObject.currentIndex == 0
|
298
|
+
return sendData(true, [txObject], &callback)
|
299
|
+
end
|
300
|
+
|
301
|
+
# Define new trunkTransaction for search
|
302
|
+
trunkTx = txObject.trunkTransaction
|
303
|
+
|
304
|
+
# Add transaction object to bundle
|
305
|
+
bundle << txObject
|
306
|
+
|
307
|
+
# Continue traversing with new trunkTx
|
308
|
+
return traverseBundle(trunkTx, bundleHash, bundle, &callback)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def isReattachable(inputAddresses, &callback)
|
313
|
+
# if string provided, make array
|
314
|
+
inputAddresses = [inputAddresses] if @validator.isString(inputAddresses)
|
315
|
+
|
316
|
+
# Categorized value transactions
|
317
|
+
# hash -> txarray map
|
318
|
+
addressTxsMap = {}
|
319
|
+
addresses = []
|
320
|
+
|
321
|
+
inputAddresses.each do |address|
|
322
|
+
if !@validator.isAddress(address)
|
323
|
+
return sendData(false, "Invalid inputs provided", &callback)
|
324
|
+
end
|
325
|
+
|
326
|
+
address = @utils.noChecksum(address)
|
327
|
+
|
328
|
+
addressTxsMap[address] = []
|
329
|
+
addresses << address
|
330
|
+
end
|
331
|
+
|
332
|
+
findTransactionObjects(addresses: addresses) do |status, transactions|
|
333
|
+
if !status
|
334
|
+
return sendData(false, transactions, &callback)
|
335
|
+
end
|
336
|
+
|
337
|
+
valueTransactions = []
|
338
|
+
|
339
|
+
transactions.each do |trx|
|
340
|
+
if trx.value < 0
|
341
|
+
txAddress = trx.address
|
342
|
+
txHash = trx.hash
|
343
|
+
|
344
|
+
addressTxsMap[txAddress] << txHash
|
345
|
+
valueTransactions << txHash
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
if valueTransactions.length > 0
|
350
|
+
# get the includion states of all the transactions
|
351
|
+
getLatestInclusion(valueTransactions) do |st1, inclusionStates|
|
352
|
+
if !st1
|
353
|
+
return sendData(false, inclusionStates, &callback)
|
354
|
+
end
|
355
|
+
|
356
|
+
# bool array
|
357
|
+
results = addresses.map do |address|
|
358
|
+
txs = addressTxsMap[address]
|
359
|
+
numTxs = txs.length
|
360
|
+
|
361
|
+
if numTxs == 0
|
362
|
+
true
|
363
|
+
else
|
364
|
+
shouldReattach = true
|
365
|
+
|
366
|
+
(0...numTxs).step(1) do |i|
|
367
|
+
tx = txs[i]
|
368
|
+
txIndex = valueTransactions.index(tx)
|
369
|
+
isConfirmed = inclusionStates[txIndex]
|
370
|
+
shouldReattach = isConfirmed ? false : true
|
371
|
+
break if isConfirmed
|
372
|
+
end
|
373
|
+
|
374
|
+
shouldReattach
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
# If only one entry, return first
|
379
|
+
results = results.first if results.length == 1
|
380
|
+
|
381
|
+
return sendData(true, results, &callback)
|
382
|
+
end
|
383
|
+
else
|
384
|
+
results = [];
|
385
|
+
numAddresses = addresses.length;
|
386
|
+
|
387
|
+
# prepare results array if multiple addresses
|
388
|
+
if numAddresses > 1
|
389
|
+
numAddresses.each do |i|
|
390
|
+
results << true
|
391
|
+
end
|
392
|
+
else
|
393
|
+
results = true
|
394
|
+
end
|
395
|
+
|
396
|
+
return sendData(true, results, &callback)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module IOTA
|
2
|
+
module Crypto
|
3
|
+
class Bundle
|
4
|
+
attr_reader :bundle
|
5
|
+
|
6
|
+
def initialize(bundle = [])
|
7
|
+
bundle = bundle.map do |b|
|
8
|
+
if b.class != IOTA::Models::Transaction
|
9
|
+
IOTA::Models::Transaction.new(b)
|
10
|
+
else
|
11
|
+
b
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
@bundle = bundle
|
16
|
+
end
|
17
|
+
|
18
|
+
def addEntry(signatureMessageLength, address, value, tag, timestamp)
|
19
|
+
(0...signatureMessageLength).step(1) do |i|
|
20
|
+
transactionObject = IOTA::Models::Transaction.new(
|
21
|
+
address: address,
|
22
|
+
value: i==0 ? value : 0,
|
23
|
+
obsoleteTag: tag,
|
24
|
+
tag: tag,
|
25
|
+
timestamp: timestamp
|
26
|
+
)
|
27
|
+
|
28
|
+
@bundle << transactionObject
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def addTrytes(signatureFragments)
|
33
|
+
emptySignatureFragment = ''
|
34
|
+
emptyHash = '9' * 81
|
35
|
+
emptyTag = '9' * 27
|
36
|
+
emptyTimestamp = '9' * 9
|
37
|
+
|
38
|
+
while emptySignatureFragment.length < 2187
|
39
|
+
emptySignatureFragment += '9'
|
40
|
+
end
|
41
|
+
|
42
|
+
(0...@bundle.length).step(1) do |i|
|
43
|
+
# Fill empty signatureMessageFragment
|
44
|
+
@bundle[i].signatureMessageFragment = signatureFragments[i] ? signatureFragments[i] : emptySignatureFragment
|
45
|
+
|
46
|
+
# Fill empty trunkTransaction
|
47
|
+
@bundle[i].trunkTransaction = emptyHash
|
48
|
+
|
49
|
+
# Fill empty branchTransaction
|
50
|
+
@bundle[i].branchTransaction = emptyHash
|
51
|
+
|
52
|
+
@bundle[i].attachmentTimestamp = emptyTimestamp
|
53
|
+
@bundle[i].attachmentTimestampLowerBound = emptyTimestamp
|
54
|
+
@bundle[i].attachmentTimestampUpperBound = emptyTimestamp
|
55
|
+
|
56
|
+
# Fill empty nonce
|
57
|
+
@bundle[i].nonce = emptyTag
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def finalize
|
62
|
+
validBundle = false
|
63
|
+
|
64
|
+
while !validBundle
|
65
|
+
|
66
|
+
kerl = Kerl.new
|
67
|
+
|
68
|
+
(0...@bundle.length).step(1) do |i|
|
69
|
+
valueTrits = Converter.trits(@bundle[i].value)
|
70
|
+
while valueTrits.length < 81
|
71
|
+
valueTrits << 0
|
72
|
+
end
|
73
|
+
|
74
|
+
timestampTrits = Converter.trits(@bundle[i].timestamp)
|
75
|
+
while timestampTrits.length < 27
|
76
|
+
timestampTrits << 0
|
77
|
+
end
|
78
|
+
|
79
|
+
@bundle[i].currentIndex = i
|
80
|
+
currentIndexTrits = Converter.trits(@bundle[i].currentIndex)
|
81
|
+
while currentIndexTrits.length < 27
|
82
|
+
currentIndexTrits << 0
|
83
|
+
end
|
84
|
+
|
85
|
+
@bundle[i].lastIndex = @bundle.length - 1
|
86
|
+
lastIndexTrits = Converter.trits(@bundle[i].lastIndex)
|
87
|
+
while lastIndexTrits.length < 27
|
88
|
+
lastIndexTrits << 0
|
89
|
+
end
|
90
|
+
|
91
|
+
bundleEssence = Converter.trits(@bundle[i].address + Converter.trytes(valueTrits) + @bundle[i].obsoleteTag + Converter.trytes(timestampTrits) + Converter.trytes(currentIndexTrits) + Converter.trytes(lastIndexTrits))
|
92
|
+
kerl.absorb(bundleEssence, 0, bundleEssence.length)
|
93
|
+
end
|
94
|
+
|
95
|
+
hash = []
|
96
|
+
kerl.squeeze(hash, 0, Kerl::HASH_LENGTH)
|
97
|
+
hash = Converter.trytes(hash)
|
98
|
+
|
99
|
+
(0...@bundle.length).step(1) do |i|
|
100
|
+
@bundle[i].bundle = hash
|
101
|
+
end
|
102
|
+
|
103
|
+
normalizedHash = normalizedBundle(hash)
|
104
|
+
if !normalizedHash.index(13).nil?
|
105
|
+
# Insecure bundle. Increment Tag and recompute bundle hash.
|
106
|
+
increasedTagTrits = Converter.trits(@bundle[0].obsoleteTag)
|
107
|
+
|
108
|
+
# Adder implementation with 1 round
|
109
|
+
(0...increasedTagTrits.length).step(1) do |j|
|
110
|
+
increasedTagTrits[j] += 1
|
111
|
+
|
112
|
+
if increasedTagTrits[j] > 1
|
113
|
+
increasedTagTrits[j] = -1
|
114
|
+
else
|
115
|
+
break
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
@bundle[0].obsoleteTag = Converter.trytes(increasedTagTrits)
|
120
|
+
else
|
121
|
+
validBundle = true
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def normalizedBundle(bundleHash)
|
127
|
+
normalizedBundleArr = []
|
128
|
+
|
129
|
+
(0...3).step(1) do |i|
|
130
|
+
sum = 0
|
131
|
+
(0...27).step(1) do |j|
|
132
|
+
normalizedBundleArr[i * 27 + j] = Converter.value(Converter.trits(bundleHash[i * 27 + j]))
|
133
|
+
sum += normalizedBundleArr[i * 27 + j]
|
134
|
+
end
|
135
|
+
|
136
|
+
if sum >= 0
|
137
|
+
while sum > 0
|
138
|
+
sum -= 1
|
139
|
+
(0...27).step(1) do |j|
|
140
|
+
if normalizedBundleArr[i * 27 + j] > -13
|
141
|
+
normalizedBundleArr[i * 27 + j] -= 1
|
142
|
+
break
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
else
|
147
|
+
while sum < 0
|
148
|
+
sum += 1
|
149
|
+
(0...27).step(1) do |j|
|
150
|
+
if normalizedBundleArr[i * 27 + j] < 13
|
151
|
+
normalizedBundleArr[i * 27 + j] += 1
|
152
|
+
break
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
normalizedBundleArr
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|