@leofcoin/lib 0.4.12 → 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.
- package/dist/lib.js +76 -0
- package/package.json +20 -58
- package/rollup.config.js +11 -7
- package/src/addresses.json +6 -0
- package/src/bytecodes.json +6 -0
- package/src/lib.js +62 -0
- package/.travis.yml +0 -25
- package/README.md +0 -9
- package/deploy.js +0 -8
- package/index.js +0 -739
- package/src/block.js +0 -70
- package/src/chain.js +0 -350
- package/src/errors.js +0 -19
- package/src/hash.js +0 -60
- package/src/index.js +0 -3
- package/src/params.js +0 -18
- package/src/transaction.js +0 -205
- package/test/index.js +0 -106
package/index.js
DELETED
|
@@ -1,739 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var ipldLfcTx = require('ipld-lfc-tx');
|
|
4
|
-
var ipldLfc = require('ipld-lfc');
|
|
5
|
-
var CID = require('cids');
|
|
6
|
-
var crypto = require('crypto');
|
|
7
|
-
var MultiWallet = require('@leofcoin/multi-wallet');
|
|
8
|
-
|
|
9
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
10
|
-
|
|
11
|
-
function _interopNamespace(e) {
|
|
12
|
-
if (e && e.__esModule) return e;
|
|
13
|
-
var n = Object.create(null);
|
|
14
|
-
if (e) {
|
|
15
|
-
Object.keys(e).forEach(function (k) {
|
|
16
|
-
if (k !== 'default') {
|
|
17
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
18
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
19
|
-
enumerable: true,
|
|
20
|
-
get: function () {
|
|
21
|
-
return e[k];
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
n['default'] = e;
|
|
28
|
-
return Object.freeze(n);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
var ipldLfcTx__default = /*#__PURE__*/_interopDefaultLegacy(ipldLfcTx);
|
|
32
|
-
var ipldLfcTx__namespace = /*#__PURE__*/_interopNamespace(ipldLfcTx);
|
|
33
|
-
var ipldLfc__default = /*#__PURE__*/_interopDefaultLegacy(ipldLfc);
|
|
34
|
-
var CID__default = /*#__PURE__*/_interopDefaultLegacy(CID);
|
|
35
|
-
var MultiWallet__default = /*#__PURE__*/_interopDefaultLegacy(MultiWallet);
|
|
36
|
-
|
|
37
|
-
const reward = 150;
|
|
38
|
-
const consensusSubsidyInterval = 52500;
|
|
39
|
-
const genesisCID = 'zsNS6wZiHSc2QPHmjV8TMNn798b4Kp9jpjsBNeUkPhaJTza3GosWUgE72Jy3X9jKMrFCcDni7Pq4yXogQN4TcAfrPmTXFt';
|
|
40
|
-
const GENESISBLOCK = {
|
|
41
|
-
index: 0,
|
|
42
|
-
prevHash: Buffer.alloc(47).toString('hex'),
|
|
43
|
-
time: 1590240964,
|
|
44
|
-
transactions: [],
|
|
45
|
-
nonce: 1077701
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* network (hardcoded for now)
|
|
50
|
-
* @dev change to 'leofcoin:olivia' for testnet
|
|
51
|
-
*/
|
|
52
|
-
const network = 'leofcoin';
|
|
53
|
-
|
|
54
|
-
const invalid = (name, text) => new Error(`Invalid ${name}: ${text}`);
|
|
55
|
-
// TODO: show notification
|
|
56
|
-
/**
|
|
57
|
-
* @example
|
|
58
|
-
* const errors = new Errors()
|
|
59
|
-
*/
|
|
60
|
-
class Errors {
|
|
61
|
-
BlockError(text) {
|
|
62
|
-
return invalid('block', text)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
TransactionError(text) {
|
|
66
|
-
return invalid('transaction', text)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
MinerWarning(text) {
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const { LFCNode, util } = ipldLfc__default['default'];
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @extends {Validator}
|
|
77
|
-
* @example
|
|
78
|
-
* const hash = new Hash()
|
|
79
|
-
*/
|
|
80
|
-
class Hash extends Errors {
|
|
81
|
-
constructor() {
|
|
82
|
-
super();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
hashFromMultihash(multihash) {
|
|
86
|
-
const cid = new CID__default['default'](multihash.replace('/ipfs/', ''));
|
|
87
|
-
return cid.multihash.slice(cid.prefix.length / 2).toString('hex')
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
multihashFromHash(hash) {
|
|
91
|
-
const cid = new CID__default['default'](1, 'leofcoin-block', Buffer.from(`1d40${hash}`, 'hex'), 'base58btc');
|
|
92
|
-
return cid.toBaseEncodedString();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async blockHash(block) {
|
|
96
|
-
block = await new LFCNode({...block});
|
|
97
|
-
const cid = await util.cid(await util.serialize(block));
|
|
98
|
-
return cid.toBaseEncodedString();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Generate transaction hash
|
|
103
|
-
*
|
|
104
|
-
* @param {object} transaction {id, type, inputs, outputs}
|
|
105
|
-
*/
|
|
106
|
-
async transactionHash(transaction) {
|
|
107
|
-
const tx = await new ipldLfcTx__default['default'].LFCTx(transaction);
|
|
108
|
-
const cid = await ipldLfcTx__default['default'].util.cid(await tx.serialize());
|
|
109
|
-
return cid.toBaseEncodedString()
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Generate transaction input hash
|
|
114
|
-
*
|
|
115
|
-
* @param {object} transactionInput {transaction, index, amount, address}
|
|
116
|
-
*/
|
|
117
|
-
transactionInputHash(transactionInput) {
|
|
118
|
-
const {tx, index, amount, address} = transactionInput;
|
|
119
|
-
return _SHA256({tx, index, amount, address});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const { LFCTx, util: util$1 } = ipldLfcTx__namespace;
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* @extends {Hash}
|
|
128
|
-
* @example
|
|
129
|
-
* const transaction = new Transaction()
|
|
130
|
-
*/
|
|
131
|
-
class Transaction extends Hash {
|
|
132
|
-
constructor() {
|
|
133
|
-
super();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Create transaction
|
|
139
|
-
*
|
|
140
|
-
* @param inputs
|
|
141
|
-
* @param outputs
|
|
142
|
-
* @param reward
|
|
143
|
-
* @return {{id: string, reward: boolean, inputs: *, outputs: *, hash: string}}
|
|
144
|
-
*/
|
|
145
|
-
async newTransaction(inputs, outputs, reward = null) {
|
|
146
|
-
try {
|
|
147
|
-
const tx = new LFCTx({
|
|
148
|
-
id: crypto.randomBytes(32).toString('hex'),
|
|
149
|
-
time: Math.floor(new Date().getTime() / 1000),
|
|
150
|
-
reward,
|
|
151
|
-
outputs,
|
|
152
|
-
inputs
|
|
153
|
-
});
|
|
154
|
-
// const cid = await util.cid(tx.serialize())
|
|
155
|
-
// await global.ipfs.dag.put(tx, {format: util.codec, hashAlg: util.defaultHashAlg, version: 1, baseFormat: 'base58btc'})
|
|
156
|
-
return tx
|
|
157
|
-
} catch (e) {
|
|
158
|
-
throw e
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Create reward transaction for block mining
|
|
164
|
-
*
|
|
165
|
-
* @param {string} address
|
|
166
|
-
* @param {number} height
|
|
167
|
-
* @return {id: string, reward: boolean, inputs: *, outputs: *, hash: string}
|
|
168
|
-
*/
|
|
169
|
-
async createRewardTransaction(address, amount) {
|
|
170
|
-
return this.newTransaction([], [{index: 0, amount, address}], 'mined');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* validate transaction
|
|
175
|
-
*
|
|
176
|
-
* @param multihash
|
|
177
|
-
* @param transaction
|
|
178
|
-
* @param unspent
|
|
179
|
-
*/
|
|
180
|
-
async validateTransaction(multihash, transaction, unspent) {
|
|
181
|
-
if (!transaction.reward) delete transaction.reward;
|
|
182
|
-
const outputs = transaction.outputs.map(o => {
|
|
183
|
-
// TODO: fix script
|
|
184
|
-
if (!o.script) delete o.script;
|
|
185
|
-
return o
|
|
186
|
-
});
|
|
187
|
-
console.log(transaction);
|
|
188
|
-
transaction.outputs = outputs;
|
|
189
|
-
if (!transaction.script) delete transaction.script;
|
|
190
|
-
if (!ipldLfcTx.util.isValid(transaction)) return await ipldLfcTx.util.validate(transaction)
|
|
191
|
-
if (multihash !== await this.transactionHash(transaction)) throw this.TransactionError('Invalid transaction hash');
|
|
192
|
-
// TODO: versions should be handled here...
|
|
193
|
-
// Verify each input signature
|
|
194
|
-
|
|
195
|
-
if (transaction.inputs) {
|
|
196
|
-
transaction.inputs.forEach(input => {
|
|
197
|
-
const { signature, address } = input;
|
|
198
|
-
const hash = this.transactionInputHash(input);
|
|
199
|
-
|
|
200
|
-
let wallet = new MultiWallet__default['default'](network);
|
|
201
|
-
wallet.fromAddress(address, null, network);
|
|
202
|
-
|
|
203
|
-
if (!wallet.verify(Buffer.from(signature, 'hex'), Buffer.from(hash, 'hex')))
|
|
204
|
-
throw this.TransactionError('Invalid input signature');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// Check if inputs are in unspent list
|
|
208
|
-
transaction.inputs.forEach((input) => {
|
|
209
|
-
if (!unspent.find(out => out.tx === input.tx && out.index === input.index)) { throw this.TransactionError('Input has been already spent: ' + input.tx); }
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (transaction.reward === 'mined') {
|
|
214
|
-
// For reward transaction: check if reward output is correct
|
|
215
|
-
if (transaction.outputs.length !== 1) throw this.TransactionError('Reward transaction must have exactly one output');
|
|
216
|
-
if (transaction.outputs[0].amount !== reward) throw this.TransactionError(`Mining reward must be exactly: ${reward}`);
|
|
217
|
-
} else if (transaction.inputs) {
|
|
218
|
-
// For normal transaction: check if total output amount equals input amount
|
|
219
|
-
if (transaction.inputs.reduce((acc, input) => acc + input.amount, 0) !==
|
|
220
|
-
transaction.outputs.reduce((acc, output) => acc + output.amount, 0)) { throw this.TransactionError('Input and output amounts do not match'); }
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return true;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* validate transactions list for current block
|
|
228
|
-
*
|
|
229
|
-
* @param {array} transactions
|
|
230
|
-
* @param unspent
|
|
231
|
-
*/
|
|
232
|
-
async validateTransactions(transactions, unspent) {
|
|
233
|
-
const _transactions = [];
|
|
234
|
-
for (let tx of transactions) {
|
|
235
|
-
let multihash = tx.multihash;
|
|
236
|
-
|
|
237
|
-
if (multihash) tx = await leofcoin.api.transaction.get(multihash);
|
|
238
|
-
else {
|
|
239
|
-
tx = await new LFCTx({...tx});
|
|
240
|
-
multihash = await util$1.cid(await tx.serialize());
|
|
241
|
-
multihash = multihash.toBaseEncodedString();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
_transactions.push({multihash, value: tx.toJSON()});
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
for (const {value, multihash} of _transactions) {
|
|
249
|
-
// TODO: fix value.scrip
|
|
250
|
-
await this.validateTransaction(multihash, value, unspent);
|
|
251
|
-
}
|
|
252
|
-
} catch (e) {
|
|
253
|
-
throw e
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (_transactions.filter(({value}) => value.reward === 'mined').length !== 1)
|
|
257
|
-
throw this.TransactionError('Transactions cannot have more than one reward');
|
|
258
|
-
|
|
259
|
-
return true
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Verify signature
|
|
264
|
-
*
|
|
265
|
-
* @param {string} address - signer address
|
|
266
|
-
* @param {string} signature - signature to verify
|
|
267
|
-
* @param {string} hash - transaction hash
|
|
268
|
-
*/
|
|
269
|
-
verifySignature(address, signature, hash) {
|
|
270
|
-
const wallet = new MultiWallet__default['default'](network);
|
|
271
|
-
return wallet.verify(signature, hash, address);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Create and sign input
|
|
276
|
-
*
|
|
277
|
-
* @param transaction Based on transaction id
|
|
278
|
-
* @param index Based on transaction output index
|
|
279
|
-
* @param amount
|
|
280
|
-
* @param wallet
|
|
281
|
-
* @return {Object} {transaction, index, amount, address}
|
|
282
|
-
*/
|
|
283
|
-
createInput(transaction, index, amount, wallet) {
|
|
284
|
-
const input = {
|
|
285
|
-
transaction,
|
|
286
|
-
index,
|
|
287
|
-
amount,
|
|
288
|
-
address: wallet.address,
|
|
289
|
-
};
|
|
290
|
-
input.signature = wallet.sign(Buffer.from(this.transactionInputHash(input), 'hex')).toString('hex');
|
|
291
|
-
return input;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Create a transaction
|
|
296
|
-
*
|
|
297
|
-
* @param wallet
|
|
298
|
-
* @param toAddress
|
|
299
|
-
* @param amount
|
|
300
|
-
* @return {Object} {id, reward, inputs, outputs, hash,}
|
|
301
|
-
*/
|
|
302
|
-
async buildTransaction(wallet, toAddress, amount, unspent) {
|
|
303
|
-
let inputsAmount = 0;
|
|
304
|
-
// const unspent = await this.getUnspentForAddress(wallet.address);
|
|
305
|
-
const inputsRaw = unspent.filter(i => {
|
|
306
|
-
const more = inputsAmount < amount;
|
|
307
|
-
if (more) inputsAmount += i.amount;
|
|
308
|
-
return more;
|
|
309
|
-
});
|
|
310
|
-
if (inputsAmount < amount) throw this.TransactionError('Not enough funds');
|
|
311
|
-
// TODO: Add multiSigning
|
|
312
|
-
const inputs = inputsRaw.map(i => this.createInput(i.tx, i.index, i.amount, wallet));
|
|
313
|
-
// Send amount to destination address
|
|
314
|
-
const outputs = [{index: 0, amount, address: toAddress}];
|
|
315
|
-
// Send back change to my wallet
|
|
316
|
-
if (inputsAmount - amount > 0) {
|
|
317
|
-
outputs.push({index: 1, amount: inputsAmount - amount, address: wallet.address});
|
|
318
|
-
}
|
|
319
|
-
return this.newTransaction(inputs, outputs);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const { LFCNode: LFCNode$1, util: util$2 } = ipldLfc__default['default'];
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* @extends {Transaction}
|
|
328
|
-
* @example
|
|
329
|
-
* const block = new Block()
|
|
330
|
-
*/
|
|
331
|
-
class Block extends Transaction {
|
|
332
|
-
constructor() {
|
|
333
|
-
super();
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
getDifficulty(hash) {
|
|
337
|
-
return parseInt(hash.substring(0, 8), 16);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
goodBlock(block, difficulty){
|
|
341
|
-
return new Promise(async (resolve, reject) => {
|
|
342
|
-
block.hash = await this.blockHash(block);
|
|
343
|
-
if (parseInt(block.hash.substring(0, 8), 16) >= difficulty) {
|
|
344
|
-
block.nonce++;
|
|
345
|
-
block = await this.goodBlock(block, difficulty);
|
|
346
|
-
}
|
|
347
|
-
resolve(block);
|
|
348
|
-
})
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
async validateBlock(previousBlock, block, difficulty, unspent) {
|
|
352
|
-
const valid = await ipldLfc__default['default'].util.isValid(block);
|
|
353
|
-
if (!valid) return await ipldLfc__default['default'].util.validate(block)
|
|
354
|
-
// console.log(block, previousBlock);
|
|
355
|
-
if (previousBlock.index + 1 !== block.index) return this.BlockError('index');
|
|
356
|
-
if (!previousBlock.hash) {
|
|
357
|
-
previousBlock = await new LFCNode$1(previousBlock);
|
|
358
|
-
previousBlock = previousBlock.toJSON();
|
|
359
|
-
previousBlock.hash = await util$2.cid(util$2.serialize(previousBlock));
|
|
360
|
-
previousBlock.hash = previousBlock.hash.toBaseEncodedString();
|
|
361
|
-
}
|
|
362
|
-
console.log(await this.blockHash(block), block.hash);
|
|
363
|
-
if (previousBlock.hash !== block.prevHash) throw this.BlockError('prevhash');
|
|
364
|
-
if (await this.blockHash(block) !== block.hash) return this.BlockError('hash');
|
|
365
|
-
if (this.getDifficulty(block.hash) > difficulty) return this.BlockError('difficulty');
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
return this.validateTransactions(block.transactions, unspent);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
372
|
-
* Create a new genesis block
|
|
373
|
-
*/
|
|
374
|
-
async newGenesisDAGNode(difficulty = 1, address = Buffer.alloc(32).toString('hex')) {
|
|
375
|
-
let block = {
|
|
376
|
-
index: 0,
|
|
377
|
-
prevHash: Buffer.alloc(47).toString('hex'),
|
|
378
|
-
time: Math.floor(new Date().getTime() / 1000),
|
|
379
|
-
transactions: [
|
|
380
|
-
// ms.unspent(network, [], wallet.account()).create(index: 0, amount: consensusSubsidy(0), address)
|
|
381
|
-
],
|
|
382
|
-
nonce: 0
|
|
383
|
-
};
|
|
384
|
-
block.hash = await blockHash(block);
|
|
385
|
-
block = await this.goodBlock(block, difficulty);
|
|
386
|
-
const node = new LFCNode$1(block);
|
|
387
|
-
return node;
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
const invalidTransactions = {};
|
|
392
|
-
|
|
393
|
-
globalThis.chain = globalThis.chain || [
|
|
394
|
-
GENESISBLOCK
|
|
395
|
-
];
|
|
396
|
-
|
|
397
|
-
globalThis.mempool = globalThis.mempool || [];
|
|
398
|
-
globalThis.blockHashSet = globalThis.blockHashSet || [];
|
|
399
|
-
|
|
400
|
-
const filterPeers = (peers, localPeer) => {
|
|
401
|
-
const set = [];
|
|
402
|
-
return peers.reduce((p, c) => {
|
|
403
|
-
if (set.indexOf(c.peer) === -1 && c.peer !== localPeer) {
|
|
404
|
-
set.push(c.peer);
|
|
405
|
-
p.push(c);
|
|
406
|
-
}
|
|
407
|
-
return p
|
|
408
|
-
}, [])
|
|
409
|
-
};
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* @extends {Block}
|
|
413
|
-
* @example
|
|
414
|
-
* const chain = new Chain()
|
|
415
|
-
*/
|
|
416
|
-
class Chain extends Block {
|
|
417
|
-
constructor() {
|
|
418
|
-
super();
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
get chain() { return globalThis.chain }
|
|
422
|
-
|
|
423
|
-
get mempool() { return globalThis.mempool }
|
|
424
|
-
|
|
425
|
-
get blockHashSet() { return globalThis.blockHashSet }
|
|
426
|
-
|
|
427
|
-
// TODO: needs 3 nodes running
|
|
428
|
-
invalidTransaction(data) {
|
|
429
|
-
// console.log(data.data.toString());
|
|
430
|
-
data = JSON.parse(data.data.toString());
|
|
431
|
-
if (!invalidTransactions[data.tx]) invalidTransactions[data.tx] = 0;
|
|
432
|
-
++invalidTransactions[data.tx];
|
|
433
|
-
const count = invalidTransactions[data.tx];
|
|
434
|
-
if (count === 3) {
|
|
435
|
-
const memIndex = mempool.indexOf(data);
|
|
436
|
-
mempool.splice(memIndex, 1);
|
|
437
|
-
delete invalidTransactions[data.tx];
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* @param {number} height
|
|
443
|
-
*/
|
|
444
|
-
consensusSubsidy(height) {
|
|
445
|
-
// console.log(height);
|
|
446
|
-
const quarterlings = height / consensusSubsidyInterval;
|
|
447
|
-
if (quarterlings >= 256) {
|
|
448
|
-
return 0;
|
|
449
|
-
}
|
|
450
|
-
//subsidy is lowered by 12.5 %, approx every year
|
|
451
|
-
const minus = quarterlings >= 1 ? (quarterlings * (reward / 256)) : 0;
|
|
452
|
-
return reward - minus;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
async getTransactions(withMempool = true, index = 0) {
|
|
456
|
-
const _chain = [...chain];
|
|
457
|
-
if (index === chain.length - 1) return []
|
|
458
|
-
_chain.slice(index + 1, chain.length - 1);
|
|
459
|
-
let transactions = _chain.reduce((transactions, block) => [ ...transactions, ...block.transactions], []);
|
|
460
|
-
if (withMempool) transactions = transactions.concat(mempool);
|
|
461
|
-
let _transactions = [];
|
|
462
|
-
// TODO: deprecated, get/put should be handled in core
|
|
463
|
-
for (const tx of transactions) {
|
|
464
|
-
const {multihash} = tx;
|
|
465
|
-
if (multihash) {
|
|
466
|
-
let value;
|
|
467
|
-
if (leofcoin.api.transaction) value = await leofcoin.api.transaction.get(multihash);
|
|
468
|
-
_transactions.push(value);
|
|
469
|
-
} else {
|
|
470
|
-
_transactions.push(tx);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
return _transactions
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
async getTransactionsForAddress(address, index = 0) {
|
|
478
|
-
const transactions = await this.getTransactions(false, index);
|
|
479
|
-
return transactions.filter(tx => tx.inputs.find(i => i.address === address) ||
|
|
480
|
-
tx.outputs.find(o => o.address === address));
|
|
481
|
-
};
|
|
482
|
-
|
|
483
|
-
/**
|
|
484
|
-
*
|
|
485
|
-
* @param {string} withMempool - with or without mempool inclusion
|
|
486
|
-
* @param {number} index - block height to start from
|
|
487
|
-
*/
|
|
488
|
-
async getUnspent(withMempool = false, index = 0) {
|
|
489
|
-
const transactions = await this.getTransactions(withMempool, index);
|
|
490
|
-
// Find all inputs with their tx ids
|
|
491
|
-
const inputs = transactions.reduce((inputs, tx) => inputs.concat(tx.inputs), []);
|
|
492
|
-
|
|
493
|
-
// Find all outputs with their tx ids
|
|
494
|
-
const outputs = transactions.reduce((outputs, tx) =>
|
|
495
|
-
outputs.concat(tx.outputs.map(output => Object.assign({}, output, {tx: tx.id}))), []);
|
|
496
|
-
|
|
497
|
-
// Figure out which outputs are unspent
|
|
498
|
-
const unspent = outputs.filter(output =>
|
|
499
|
-
typeof inputs.find(input => input.tx === output.tx && input.index === output.index && input.amount === output.amount) === 'undefined');
|
|
500
|
-
return unspent;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* @param {String} address - wallet address
|
|
505
|
-
* @param {number} index - block height to start from
|
|
506
|
-
*/
|
|
507
|
-
async getUnspentForAddress(address = null, index = 0) {
|
|
508
|
-
const unspent = await this.getUnspent(true, index);
|
|
509
|
-
return unspent.filter(u => u.address === address);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* @param {String} address - wallet address
|
|
514
|
-
* @param {Number} index - Block index to get balance after
|
|
515
|
-
*/
|
|
516
|
-
async getBalanceForAddress(address = null, index) {
|
|
517
|
-
// debug(`Getting balance for ${address}`)
|
|
518
|
-
const unspent = await this.getUnspentForAddress(address, index);
|
|
519
|
-
const amount = unspent.reduce((acc, u) => acc + u.amount , 0);
|
|
520
|
-
// debug(`Got ${amount} for ${address}`)
|
|
521
|
-
return amount
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* @deprecated Use getTransactionsForAddress(addr, index) instead
|
|
526
|
-
*
|
|
527
|
-
* @param {string} address - wallet address
|
|
528
|
-
* @param {number} index - block height to start from
|
|
529
|
-
*
|
|
530
|
-
* @return {number} balance
|
|
531
|
-
*/
|
|
532
|
-
async getBalanceForAddressAfter(address = null, index = 0) {
|
|
533
|
-
// debug(`Getting balance for ${address} @${index}`)
|
|
534
|
-
const unspent = await this.getUnspentForAddress(address, index);
|
|
535
|
-
const amount = unspent.reduce((acc, u) => acc + u.amount , 0);
|
|
536
|
-
// debug(`Got ${amount} for ${address} @${index}`)
|
|
537
|
-
return amount
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
median(array) {
|
|
541
|
-
array.sort((a,b) => a - b);
|
|
542
|
-
|
|
543
|
-
var half = Math.floor(array.length / 2);
|
|
544
|
-
|
|
545
|
-
if(array.length % 2)
|
|
546
|
-
return array[half];
|
|
547
|
-
else
|
|
548
|
-
return (array[half - 1] + array[half]) / 2.0;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
difficulty() {
|
|
552
|
-
// TODO: lower difficulty when transactionpool contain more then 500 tx ?
|
|
553
|
-
// TODO: raise difficulty when pool is empty
|
|
554
|
-
|
|
555
|
-
// or
|
|
556
|
-
|
|
557
|
-
// TODO: implement iTX (instant transaction)
|
|
558
|
-
// iTX is handled by multiple peers, itx is chained together by their hashes
|
|
559
|
-
// by handlng a tx as itx the block well be converted into a iRootBlock
|
|
560
|
-
// this results into smaller chains (tangles, tails) which should improve
|
|
561
|
-
// resolving transactions, wallet amounts etc ...
|
|
562
|
-
const start = chain.length >= 128 ? (chain.length - 128) : 0;
|
|
563
|
-
const blocks = chain.slice(start, (chain.length - 1)).reverse();
|
|
564
|
-
const stamps = [];
|
|
565
|
-
for (var i = 0; i < blocks.length; i++) {
|
|
566
|
-
if (blocks[i + 1]) {
|
|
567
|
-
stamps.push(blocks[i].time - blocks[i + 1].time);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
if (stamps.length === 0) {
|
|
571
|
-
stamps.push(10);
|
|
572
|
-
}
|
|
573
|
-
let blocksMedian = this.median(stamps) || 10;
|
|
574
|
-
const offset = blocksMedian / 10;
|
|
575
|
-
// offset for quick recovery
|
|
576
|
-
if (blocksMedian < 10) {
|
|
577
|
-
blocksMedian -= (offset / 2);
|
|
578
|
-
} else if (blocksMedian > 10) {
|
|
579
|
-
blocksMedian += (offset * 2);
|
|
580
|
-
}
|
|
581
|
-
if (blocksMedian < 0) blocksMedian = -blocksMedian;
|
|
582
|
-
console.log(`Average Block Time: ${blocksMedian}`);
|
|
583
|
-
console.log(`Difficulty: ${10 / blocksMedian}`);
|
|
584
|
-
return (1000 / (10 / blocksMedian));
|
|
585
|
-
};//10000
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
/**
|
|
589
|
-
* Get the transactions for the next Block
|
|
590
|
-
*
|
|
591
|
-
* @return {object} transactions
|
|
592
|
-
*/
|
|
593
|
-
async nextBlockTransactions() {
|
|
594
|
-
|
|
595
|
-
try {
|
|
596
|
-
const unspent = await this.getUnspent(false);
|
|
597
|
-
return mempool.filter(async transaction => {
|
|
598
|
-
const multihash = transaction.multihash;
|
|
599
|
-
const value = await leofcoin.api.transaction.get(multihash);
|
|
600
|
-
try {
|
|
601
|
-
await this.validateTransaction(multihash, value.toJSON(), unspent);
|
|
602
|
-
return transaction
|
|
603
|
-
} catch (e) {
|
|
604
|
-
globalThis.pubsub.publish('invalid-transaction', Buffer.from(JSON.stringify(transaction)));
|
|
605
|
-
console.error(e);
|
|
606
|
-
}
|
|
607
|
-
});
|
|
608
|
-
} catch (e) {
|
|
609
|
-
throw new Error(e)
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
longestChain() {
|
|
614
|
-
return new Promise(async (resolve, reject) => {
|
|
615
|
-
try {
|
|
616
|
-
// TODO: peernet.id => peer.id
|
|
617
|
-
let peers = await filterPeers([...peernet.peers], peernet.id);
|
|
618
|
-
let set = [];
|
|
619
|
-
const request = new globalThis.peernet.protos['peernet-request']({
|
|
620
|
-
request: 'lastBlock'
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
for (const peer of peers) {
|
|
624
|
-
const to = peernet._getPeerId(peer.id);
|
|
625
|
-
if (to) {
|
|
626
|
-
const node = await peernet.prepareMessage(to, request.encoded);
|
|
627
|
-
let response = await peer.request(node.encoded);
|
|
628
|
-
const proto = new globalThis.peernet.protos['peernet-message'](Buffer.from(response.data));
|
|
629
|
-
response = new globalThis.peernet.protos['peernet-response'](Buffer.from(proto.decoded.data));
|
|
630
|
-
const block = JSON.parse(response.decoded.response);
|
|
631
|
-
set.push({peer, block});
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
let localIndex;
|
|
635
|
-
let localHash;
|
|
636
|
-
try {
|
|
637
|
-
const index = await chainStore.get('localIndex');
|
|
638
|
-
const hash = await chainStore.get('localBlock');
|
|
639
|
-
localIndex = Number(index.toString());
|
|
640
|
-
localHash = hash.toString();
|
|
641
|
-
} catch (e) {
|
|
642
|
-
localIndex = 0;
|
|
643
|
-
localHash = genesisCID;
|
|
644
|
-
await chainStore.put('localIndex', 0);
|
|
645
|
-
await chainStore.put('localBlock', genesisCID);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
set = set.reduce((p, c) => {
|
|
649
|
-
if (Number(c.height) > Number(p.height)) {
|
|
650
|
-
c.seen = 1;
|
|
651
|
-
return c
|
|
652
|
-
} else if (Number(c.height) === Number(p.height)) {
|
|
653
|
-
p.seen += 1;
|
|
654
|
-
}
|
|
655
|
-
return p
|
|
656
|
-
}, { height: localIndex, hash: localHash, seen: 0});
|
|
657
|
-
|
|
658
|
-
// temp
|
|
659
|
-
// if (_blocks.seen < 2) {
|
|
660
|
-
// _blocks = history[_blocks.block.index - 1]
|
|
661
|
-
//
|
|
662
|
-
// }
|
|
663
|
-
// const localIndex = await chainStore.get('localIndex')
|
|
664
|
-
// const localHash = await chainStore.get('localBlock')
|
|
665
|
-
return resolve({index: set.height, hash: set.hash})
|
|
666
|
-
|
|
667
|
-
} catch (e) {
|
|
668
|
-
console.warn(e);
|
|
669
|
-
// debug(e)
|
|
670
|
-
reject(e);
|
|
671
|
-
}
|
|
672
|
-
})
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
/**
|
|
676
|
-
* Resolve latest block from the longest chain.
|
|
677
|
-
* @return Promise(resolve(block))
|
|
678
|
-
*/
|
|
679
|
-
lastBlock() {
|
|
680
|
-
return new Promise(async (resolve, reject) => {
|
|
681
|
-
const result = await this.longestChain();
|
|
682
|
-
|
|
683
|
-
resolve(result); // retrieve links
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
/**
|
|
688
|
-
* @param {String} address
|
|
689
|
-
* @return {Function} newBlock
|
|
690
|
-
*/
|
|
691
|
-
async nextBlock(address) {
|
|
692
|
-
let transactions;
|
|
693
|
-
let previousBlock;
|
|
694
|
-
try {
|
|
695
|
-
previousBlock = await this.lastBlock();
|
|
696
|
-
|
|
697
|
-
if (previousBlock.index > chain.length - 1) {
|
|
698
|
-
await leofcoin.api.chain.sync();
|
|
699
|
-
previousBlock = await this.lastBlock();
|
|
700
|
-
}
|
|
701
|
-
if (!previousBlock.index) previousBlock = chain[chain.length - 1];
|
|
702
|
-
transactions = await this.nextBlockTransactions();
|
|
703
|
-
} catch (e) {
|
|
704
|
-
previousBlock = GENESISBLOCK;
|
|
705
|
-
previousBlock.hash = genesisCID;
|
|
706
|
-
transactions = await this.nextBlockTransactions();
|
|
707
|
-
} finally {
|
|
708
|
-
// console.log(transactions, previousBlock, address);
|
|
709
|
-
return await this.newBlock({transactions, previousBlock, address});
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
/**
|
|
714
|
-
* Create new block
|
|
715
|
-
*
|
|
716
|
-
* @param {Array} transactions - transactions
|
|
717
|
-
* @param {Object} previousBlock - previousBlock
|
|
718
|
-
* @param {String} address - address
|
|
719
|
-
* @return {Object} index, prevHash, time, transactions, nonce
|
|
720
|
-
*/
|
|
721
|
-
async newBlock({transactions = [], previousBlock, address}) {
|
|
722
|
-
const index = previousBlock.index + 1;
|
|
723
|
-
const minedTx = await this.createRewardTransaction(address, this.consensusSubsidy(index));
|
|
724
|
-
transactions.push(minedTx.toJSON());
|
|
725
|
-
this.data = {
|
|
726
|
-
index,
|
|
727
|
-
prevHash: previousBlock.hash,
|
|
728
|
-
time: Math.floor(new Date().getTime() / 1000),
|
|
729
|
-
transactions,
|
|
730
|
-
nonce: 0
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
this.data.hash = await this.blockHash(this.data);
|
|
734
|
-
|
|
735
|
-
return this.data;
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
module.exports = Chain;
|