@bsv/sdk 1.1.29 → 1.1.32
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/cjs/package.json +1 -1
- package/dist/cjs/src/primitives/Schnorr.js +92 -0
- package/dist/cjs/src/primitives/Schnorr.js.map +1 -0
- package/dist/cjs/src/primitives/index.js +3 -1
- package/dist/cjs/src/primitives/index.js.map +1 -1
- package/dist/cjs/src/totp/totp.js +1 -1
- package/dist/cjs/src/totp/totp.js.map +1 -1
- package/dist/cjs/src/transaction/Beef.js +292 -155
- package/dist/cjs/src/transaction/Beef.js.map +1 -1
- package/dist/cjs/src/transaction/BeefParty.js +46 -26
- package/dist/cjs/src/transaction/BeefParty.js.map +1 -1
- package/dist/cjs/src/transaction/BeefTx.js +31 -16
- package/dist/cjs/src/transaction/BeefTx.js.map +1 -1
- package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +12 -6
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/primitives/Schnorr.js +87 -0
- package/dist/esm/src/primitives/Schnorr.js.map +1 -0
- package/dist/esm/src/primitives/index.js +1 -0
- package/dist/esm/src/primitives/index.js.map +1 -1
- package/dist/esm/src/totp/totp.js +1 -1
- package/dist/esm/src/totp/totp.js.map +1 -1
- package/dist/esm/src/transaction/Beef.js +294 -157
- package/dist/esm/src/transaction/Beef.js.map +1 -1
- package/dist/esm/src/transaction/BeefParty.js +47 -27
- package/dist/esm/src/transaction/BeefParty.js.map +1 -1
- package/dist/esm/src/transaction/BeefTx.js +35 -20
- package/dist/esm/src/transaction/BeefTx.js.map +1 -1
- package/dist/esm/src/transaction/MerklePath.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +12 -6
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/primitives/Schnorr.d.ts +65 -0
- package/dist/types/src/primitives/Schnorr.d.ts.map +1 -0
- package/dist/types/src/primitives/index.d.ts +1 -0
- package/dist/types/src/primitives/index.d.ts.map +1 -1
- package/dist/types/src/transaction/Beef.d.ts +131 -90
- package/dist/types/src/transaction/Beef.d.ts.map +1 -1
- package/dist/types/src/transaction/BeefParty.d.ts +34 -23
- package/dist/types/src/transaction/BeefParty.d.ts.map +1 -1
- package/dist/types/src/transaction/BeefTx.d.ts +11 -6
- package/dist/types/src/transaction/BeefTx.d.ts.map +1 -1
- package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts +8 -2
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/primitives.md +120 -9
- package/docs/transaction.md +141 -9
- package/package.json +1 -1
- package/src/primitives/Schnorr.ts +95 -0
- package/src/primitives/__tests/Schnorr.test.ts +272 -0
- package/src/primitives/index.ts +1 -0
- package/src/totp/totp.ts +1 -1
- package/src/transaction/Beef.ts +493 -378
- package/src/transaction/BeefParty.ts +71 -58
- package/src/transaction/BeefTx.ts +133 -143
- package/src/transaction/MerklePath.ts +11 -11
- package/src/transaction/Transaction.ts +42 -36
- package/src/transaction/__tests/Beef.test.ts +55 -10
package/src/transaction/Beef.ts
CHANGED
|
@@ -1,237 +1,314 @@
|
|
|
1
|
-
import MerklePath from
|
|
2
|
-
import Transaction from
|
|
3
|
-
import ChainTracker from
|
|
4
|
-
import BeefTx from
|
|
5
|
-
import { Reader, Writer, toHex, toArray } from
|
|
1
|
+
import MerklePath from './MerklePath.js'
|
|
2
|
+
import Transaction from './Transaction.js'
|
|
3
|
+
import ChainTracker from './ChainTracker.js'
|
|
4
|
+
import BeefTx from './BeefTx.js'
|
|
5
|
+
import { Reader, Writer, toHex, toArray } from '../primitives/utils.js'
|
|
6
6
|
|
|
7
|
-
export const BEEF_MAGIC = 4022206465
|
|
7
|
+
export const BEEF_MAGIC = 4022206465 // 0100BEEF in LE order
|
|
8
8
|
export const BEEF_MAGIC_V2 = 4022206466 // 0200BEEF in LE order
|
|
9
9
|
export const BEEF_MAGIC_TXID_ONLY_EXTENSION = 4022206465 // 0100BEEF in LE order
|
|
10
|
+
export const ATOMIC_BEEF = 0x01010101 // Atomic Beef serialization prefix
|
|
10
11
|
|
|
11
12
|
export type BeefVersion = undefined | 'V1' | 'V2'
|
|
12
13
|
|
|
13
14
|
/*
|
|
14
15
|
* BEEF standard: BRC-62: Background Evaluation Extended Format (BEEF) Transactions
|
|
15
16
|
* https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0062.md
|
|
16
|
-
*
|
|
17
|
+
*
|
|
17
18
|
* BUMP standard: BRC-74: BSV Unified Merkle Path (BUMP) Format
|
|
18
19
|
* https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0074.md
|
|
19
20
|
*
|
|
21
|
+
* BRC-95: Atomic BEEF Transactions
|
|
22
|
+
* https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0095.md
|
|
23
|
+
*
|
|
24
|
+
* The Atomic BEEF format is supported by the binary deserialization static method `fromBinary`.
|
|
25
|
+
*
|
|
26
|
+
* BRC-96: BEEF V2, Txid Only Extension
|
|
27
|
+
* https://github.com/bitcoin-sv/BRCs/blob/master/transactions/0096.md
|
|
28
|
+
*
|
|
20
29
|
* A valid serialized BEEF is the cornerstone of Simplified Payment Validation (SPV)
|
|
21
30
|
* where they are exchanged between two non-trusting parties to establish the
|
|
22
31
|
* validity of a newly constructed bitcoin transaction and its inputs from prior
|
|
23
32
|
* transactions.
|
|
24
|
-
*
|
|
33
|
+
*
|
|
25
34
|
* A `Beef` is fundamentally an list of `BUMP`s and a list of transactions.
|
|
26
|
-
*
|
|
35
|
+
*
|
|
27
36
|
* A `BUMP` is a partial merkle tree for a 'mined' bitcoin block.
|
|
28
37
|
* It can therefore be used to prove the validity of transaction data
|
|
29
38
|
* for each transaction txid whose merkle path is included in the tree.
|
|
30
|
-
*
|
|
39
|
+
*
|
|
31
40
|
* To be valid, the list of transactions must be sorted in dependency order:
|
|
32
41
|
* oldest transaction first;
|
|
33
42
|
* and each transaction must either
|
|
34
43
|
* have a merkle path in one of the BUMPs, or
|
|
35
44
|
* have all of its input transactions included in the list of transactions.
|
|
36
|
-
*
|
|
45
|
+
*
|
|
37
46
|
* The `Beef` class supports the construction of valid BEEFs by allowing BUMPs
|
|
38
47
|
* (merkle paths) and transactions to be merged sequentially.
|
|
39
|
-
*
|
|
48
|
+
*
|
|
40
49
|
* The `Beef` class also extends the standard by supporting 'known' transactions.
|
|
41
50
|
* A 'known' transaction is represented solely by its txid.
|
|
42
51
|
* To become valid, all the 'known' transactions in a `Beef` must be replaced by full
|
|
43
52
|
* transactions and merkle paths, if they are mined.
|
|
44
|
-
*
|
|
53
|
+
*
|
|
45
54
|
* The purpose of supporting 'known' transactions is that one or both parties
|
|
46
55
|
* generating and exchanging BEEFs often possess partial knowledge of valid transactions
|
|
47
56
|
* due to their history.
|
|
48
|
-
*
|
|
57
|
+
*
|
|
49
58
|
* A valid `Beef` is only required when sent to a party with no shared history,
|
|
50
59
|
* such as a transaction processor.
|
|
60
|
+
*
|
|
61
|
+
* IMPORTANT NOTE:
|
|
62
|
+
* It is fundamental to the BEEF value proposition that only valid transactions and valid
|
|
63
|
+
* merkle path (BUMP) data be added to it. Merging invalid data breaks the `verify` and `isValid`
|
|
64
|
+
* functions. There is no support for removing invalid data. A `Beef` that becomes invalid
|
|
65
|
+
* must be discarded.
|
|
51
66
|
*/
|
|
52
67
|
export class Beef {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
68
|
+
bumps: MerklePath[] = []
|
|
69
|
+
txs: BeefTx[] = []
|
|
70
|
+
version: BeefVersion = undefined
|
|
71
|
+
atomicTxid: string | undefined = undefined
|
|
56
72
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
constructor (version?: BeefVersion) {
|
|
74
|
+
this.version = version
|
|
75
|
+
}
|
|
60
76
|
|
|
61
|
-
|
|
77
|
+
/**
|
|
62
78
|
* BEEF_MAGIC is the original V1 version.
|
|
63
79
|
* BEEF_MAGIC_V2 includes support for txidOnly transactions in serialized beefs.
|
|
64
80
|
* @returns version magic value based on current contents and constructor version parameter.
|
|
65
81
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
return BEEF_MAGIC
|
|
82
|
+
get magic (): number {
|
|
83
|
+
if (this.version === 'V1') { return BEEF_MAGIC }
|
|
69
84
|
|
|
70
|
-
|
|
71
|
-
return BEEF_MAGIC_V2
|
|
85
|
+
if (this.version === 'V2') { return BEEF_MAGIC_V2 }
|
|
72
86
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return BEEF_MAGIC_V2
|
|
87
|
+
const hasTxidOnly = this.txs.findIndex(tx => tx.isTxidOnly) > -1
|
|
88
|
+
if (hasTxidOnly) { return BEEF_MAGIC_V2 }
|
|
76
89
|
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
return BEEF_MAGIC
|
|
91
|
+
}
|
|
79
92
|
|
|
80
|
-
|
|
93
|
+
/**
|
|
81
94
|
* @param txid of `beefTx` to find
|
|
82
95
|
* @returns `BeefTx` in `txs` with `txid`.
|
|
83
96
|
*/
|
|
84
|
-
|
|
85
|
-
|
|
97
|
+
findTxid (txid: string): BeefTx | undefined {
|
|
98
|
+
return this.txs.find(tx => tx.txid === txid)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @returns `MerklePath` with level zero hash equal to txid or undefined.
|
|
103
|
+
*/
|
|
104
|
+
findBump (txid: string): MerklePath | undefined {
|
|
105
|
+
return this.bumps.find(b => b.path[0].find(leaf => leaf.hash === txid))
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Finds a Transaction in this `Beef`
|
|
110
|
+
* and adds any missing input SourceTransactions from this `Beef`.
|
|
111
|
+
*
|
|
112
|
+
* The result is suitable for signing.
|
|
113
|
+
*
|
|
114
|
+
* @param txid The id of the target transaction.
|
|
115
|
+
* @returns Transaction with all available input `SourceTransaction`s from this Beef.
|
|
116
|
+
*/
|
|
117
|
+
findTransactionForSigning (txid: string): Transaction | undefined {
|
|
118
|
+
|
|
119
|
+
const beefTx = this.findTxid(txid)
|
|
120
|
+
if (!beefTx) return undefined
|
|
121
|
+
|
|
122
|
+
for (const i of beefTx.tx.inputs) {
|
|
123
|
+
if (!i.sourceTransaction) {
|
|
124
|
+
const itx = this.findTxid(i.sourceTXID)
|
|
125
|
+
if (itx) {
|
|
126
|
+
i.sourceTransaction = itx.tx
|
|
127
|
+
}
|
|
128
|
+
}
|
|
86
129
|
}
|
|
87
130
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
131
|
+
return beefTx.tx
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Builds the proof tree rooted at a specific `Transaction`.
|
|
135
|
+
*
|
|
136
|
+
* To succeed, the Beef must contain all the required transaction and merkle path data.
|
|
137
|
+
*
|
|
138
|
+
* @param txid The id of the target transaction.
|
|
139
|
+
* @returns Transaction with input `SourceTransaction` and `MerklePath` populated from this Beef.
|
|
140
|
+
*/
|
|
141
|
+
findAtomicTransaction (txid: string): Transaction | undefined {
|
|
142
|
+
|
|
143
|
+
const beefTx = this.findTxid(txid)
|
|
144
|
+
if (!beefTx) return undefined
|
|
145
|
+
|
|
146
|
+
const addInputProof = (beef: Beef, tx: Transaction) => {
|
|
147
|
+
const mp = beef.findBump(tx.id('hex'))
|
|
148
|
+
if (mp)
|
|
149
|
+
tx.merklePath = mp
|
|
150
|
+
else {
|
|
151
|
+
for (const i of tx.inputs) {
|
|
152
|
+
if (!i.sourceTransaction) {
|
|
153
|
+
const itx = beef.findTxid(i.sourceTXID)
|
|
154
|
+
if (itx) {
|
|
155
|
+
i.sourceTransaction = itx.tx
|
|
100
156
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// Definitely the same... combine them to save space
|
|
107
|
-
b.combine(bump)
|
|
108
|
-
bumpIndex = i
|
|
109
|
-
break
|
|
110
|
-
}
|
|
157
|
+
}
|
|
158
|
+
if (i.sourceTransaction) {
|
|
159
|
+
const mp = beef.findBump(i.sourceTransaction.id('hex'))
|
|
160
|
+
if (mp) {
|
|
161
|
+
i.sourceTransaction.merklePath = mp
|
|
111
162
|
}
|
|
163
|
+
else {
|
|
164
|
+
addInputProof(beef, i.sourceTransaction)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
112
167
|
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
113
170
|
|
|
114
|
-
|
|
115
|
-
if (bumpIndex === undefined) {
|
|
116
|
-
bumpIndex = this.bumps.length
|
|
117
|
-
this.bumps.push(bump)
|
|
118
|
-
}
|
|
171
|
+
addInputProof(this, beefTx.tx)
|
|
119
172
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
173
|
+
return beefTx.tx
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Merge a MerklePath that is assumed to be fully valid.
|
|
178
|
+
* @param bump
|
|
179
|
+
* @returns index of merged bump
|
|
180
|
+
*/
|
|
181
|
+
mergeBump (bump: MerklePath): number {
|
|
182
|
+
let bumpIndex: number | undefined
|
|
183
|
+
// If this proof is identical to another one previously added, we use that first. Otherwise, we try to merge it with proofs from the same block.
|
|
184
|
+
for (let i = 0; i < this.bumps.length; i++) {
|
|
185
|
+
const b = this.bumps[i]
|
|
186
|
+
if (b === bump) { // Literally the same
|
|
187
|
+
return i
|
|
188
|
+
}
|
|
189
|
+
if (b.blockHeight === bump.blockHeight) {
|
|
190
|
+
// Probably the same...
|
|
191
|
+
const rootA = b.computeRoot()
|
|
192
|
+
const rootB = bump.computeRoot()
|
|
193
|
+
if (rootA === rootB) {
|
|
194
|
+
// Definitely the same... combine them to save space
|
|
195
|
+
b.combine(bump)
|
|
196
|
+
bumpIndex = i
|
|
197
|
+
break
|
|
133
198
|
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
134
201
|
|
|
135
|
-
|
|
202
|
+
// if the proof is not yet added, add a new path.
|
|
203
|
+
if (bumpIndex === undefined) {
|
|
204
|
+
bumpIndex = this.bumps.length
|
|
205
|
+
this.bumps.push(bump)
|
|
136
206
|
}
|
|
137
207
|
|
|
138
|
-
|
|
208
|
+
// review if any transactions are proven by this bump
|
|
209
|
+
const b = this.bumps[bumpIndex]
|
|
210
|
+
for (const tx of this.txs) {
|
|
211
|
+
const txid = tx.txid
|
|
212
|
+
if (!tx.bumpIndex) {
|
|
213
|
+
for (const n of b.path[0]) {
|
|
214
|
+
if (n.hash === txid) {
|
|
215
|
+
tx.bumpIndex = bumpIndex
|
|
216
|
+
n.txid = true
|
|
217
|
+
break
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return bumpIndex
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
139
227
|
* Merge a serialized transaction.
|
|
140
|
-
*
|
|
228
|
+
*
|
|
141
229
|
* Checks that a transaction with the same txid hasn't already been merged.
|
|
142
|
-
*
|
|
230
|
+
*
|
|
143
231
|
* Replaces existing transaction with same txid.
|
|
144
|
-
*
|
|
145
|
-
* @param rawTx
|
|
232
|
+
*
|
|
233
|
+
* @param rawTx
|
|
146
234
|
* @param bumpIndex Optional. If a number, must be valid index into bumps array.
|
|
147
235
|
* @returns txid of rawTx
|
|
148
236
|
*/
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
237
|
+
mergeRawTx (rawTx: number[], bumpIndex?: number): BeefTx {
|
|
238
|
+
const newTx: BeefTx = new BeefTx(rawTx, bumpIndex)
|
|
239
|
+
this.removeExistingTxid(newTx.txid)
|
|
240
|
+
this.txs.push(newTx)
|
|
241
|
+
this.tryToValidateBumpIndex(newTx)
|
|
242
|
+
return newTx
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
158
246
|
* Merge a `Transaction` and any referenced `merklePath` and `sourceTransaction`, recursifely.
|
|
159
|
-
*
|
|
247
|
+
*
|
|
160
248
|
* Replaces existing transaction with same txid.
|
|
161
|
-
*
|
|
249
|
+
*
|
|
162
250
|
* Attempts to match an existing bump to the new transaction.
|
|
163
|
-
*
|
|
164
|
-
* @param tx
|
|
251
|
+
*
|
|
252
|
+
* @param tx
|
|
165
253
|
* @returns txid of tx
|
|
166
254
|
*/
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
this.mergeTransaction(input.sourceTransaction)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return newTx
|
|
255
|
+
mergeTransaction (tx: Transaction): BeefTx {
|
|
256
|
+
const txid = tx.id('hex')
|
|
257
|
+
this.removeExistingTxid(txid)
|
|
258
|
+
let bumpIndex: number | undefined
|
|
259
|
+
if (tx.merklePath) { bumpIndex = this.mergeBump(tx.merklePath) }
|
|
260
|
+
const newTx = new BeefTx(tx, bumpIndex)
|
|
261
|
+
this.txs.push(newTx)
|
|
262
|
+
this.tryToValidateBumpIndex(newTx)
|
|
263
|
+
bumpIndex = newTx.bumpIndex
|
|
264
|
+
if (bumpIndex === undefined) {
|
|
265
|
+
for (const input of tx.inputs) {
|
|
266
|
+
if (input.sourceTransaction) { this.mergeTransaction(input.sourceTransaction) }
|
|
267
|
+
}
|
|
184
268
|
}
|
|
269
|
+
return newTx
|
|
270
|
+
}
|
|
185
271
|
|
|
186
|
-
|
|
272
|
+
/**
|
|
187
273
|
* Removes an existing transaction from the BEEF, given its TXID
|
|
188
274
|
* @param txid TXID of the transaction to remove
|
|
189
275
|
*/
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
276
|
+
removeExistingTxid (txid: string) {
|
|
277
|
+
const existingTxIndex = this.txs.findIndex(t => t.txid === txid)
|
|
278
|
+
if (existingTxIndex >= 0) { this.txs.splice(existingTxIndex, 1) }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
mergeTxidOnly (txid: string): BeefTx {
|
|
282
|
+
if (this.version === 'V1') { throw new Error('BEEF V1 format does not support txid only transactions.') }
|
|
283
|
+
|
|
284
|
+
let tx = this.txs.find(t => t.txid === txid)
|
|
285
|
+
if (!tx) {
|
|
286
|
+
tx = new BeefTx(txid)
|
|
287
|
+
this.txs.push(tx)
|
|
288
|
+
this.tryToValidateBumpIndex(tx)
|
|
194
289
|
}
|
|
290
|
+
return tx
|
|
291
|
+
}
|
|
195
292
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
let tx = this.txs.find(t => t.txid === txid)
|
|
201
|
-
if (!tx) {
|
|
202
|
-
tx = new BeefTx(txid)
|
|
203
|
-
this.txs.push(tx)
|
|
204
|
-
this.tryToValidateBumpIndex(tx)
|
|
205
|
-
}
|
|
206
|
-
return tx
|
|
293
|
+
mergeBeefTx (btx: BeefTx): BeefTx {
|
|
294
|
+
let beefTx = this.findTxid(btx.txid)
|
|
295
|
+
if (!beefTx && btx.isTxidOnly) { beefTx = this.mergeTxidOnly(btx.txid) } else if (!beefTx || beefTx.isTxidOnly) {
|
|
296
|
+
if (btx._tx) { beefTx = this.mergeTransaction(btx._tx) } else { beefTx = this.mergeRawTx(btx._rawTx) }
|
|
207
297
|
}
|
|
298
|
+
return beefTx
|
|
299
|
+
}
|
|
208
300
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
if (!beefTx && btx.isTxidOnly)
|
|
212
|
-
beefTx = this.mergeTxidOnly(btx.txid)
|
|
213
|
-
else if (!beefTx || beefTx.isTxidOnly) {
|
|
214
|
-
if (btx._tx)
|
|
215
|
-
beefTx = this.mergeTransaction(btx._tx)
|
|
216
|
-
else
|
|
217
|
-
beefTx = this.mergeRawTx(btx._rawTx!)
|
|
218
|
-
}
|
|
219
|
-
return beefTx
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
mergeBeef(beef: number[] | Beef) {
|
|
223
|
-
const b: Beef = Array.isArray(beef) ? Beef.fromBinary(beef) : beef
|
|
301
|
+
mergeBeef (beef: number[] | Beef) {
|
|
302
|
+
const b: Beef = Array.isArray(beef) ? Beef.fromBinary(beef) : beef
|
|
224
303
|
|
|
225
|
-
|
|
226
|
-
this.mergeBump(bump)
|
|
304
|
+
for (const bump of b.bumps) { this.mergeBump(bump) }
|
|
227
305
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
306
|
+
for (const tx of b.txs) { this.mergeBeefTx(tx) }
|
|
307
|
+
}
|
|
231
308
|
|
|
232
|
-
|
|
309
|
+
/**
|
|
233
310
|
* Sorts `txs` and checks structural validity of beef.
|
|
234
|
-
*
|
|
311
|
+
*
|
|
235
312
|
* Does NOT verify merkle roots.
|
|
236
313
|
*
|
|
237
314
|
* Validity requirements:
|
|
@@ -239,14 +316,14 @@ export class Beef {
|
|
|
239
316
|
* 2. All transactions have bumps or their inputs chain back to bumps (or are known).
|
|
240
317
|
* 3. Order of transactions satisfies dependencies before dependents.
|
|
241
318
|
* 4. No transactions with duplicate txids.
|
|
242
|
-
*
|
|
319
|
+
*
|
|
243
320
|
* @param allowTxidOnly optional. If true, transaction txid only is assumed valid
|
|
244
321
|
*/
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
322
|
+
isValid (allowTxidOnly?: boolean): boolean {
|
|
323
|
+
return this.verifyValid(allowTxidOnly).valid
|
|
324
|
+
}
|
|
248
325
|
|
|
249
|
-
|
|
326
|
+
/**
|
|
250
327
|
* Sorts `txs` and confirms validity of transaction data contained in beef
|
|
251
328
|
* by validating structure of this beef and confirming computed merkle roots
|
|
252
329
|
* using `chainTracker`.
|
|
@@ -256,291 +333,329 @@ export class Beef {
|
|
|
256
333
|
* 2. All transactions have bumps or their inputs chain back to bumps (or are known).
|
|
257
334
|
* 3. Order of transactions satisfies dependencies before dependents.
|
|
258
335
|
* 4. No transactions with duplicate txids.
|
|
259
|
-
*
|
|
336
|
+
*
|
|
260
337
|
* @param chainTracker Used to verify computed merkle path roots for all bump txids.
|
|
261
338
|
* @param allowTxidOnly optional. If true, transaction txid is assumed valid
|
|
262
339
|
*/
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
for (const height of Object.keys(r.roots)) {
|
|
268
|
-
const isValid = await chainTracker.isValidRootForHeight(r.roots[height], Number(height))
|
|
269
|
-
if (!isValid)
|
|
270
|
-
return false
|
|
271
|
-
}
|
|
340
|
+
async verify (chainTracker: ChainTracker, allowTxidOnly?: boolean): Promise<boolean> {
|
|
341
|
+
const r = this.verifyValid(allowTxidOnly)
|
|
342
|
+
if (!r.valid) return false
|
|
272
343
|
|
|
273
|
-
|
|
344
|
+
for (const height of Object.keys(r.roots)) {
|
|
345
|
+
const isValid = await chainTracker.isValidRootForHeight(r.roots[height], Number(height))
|
|
346
|
+
if (!isValid) { return false }
|
|
274
347
|
}
|
|
275
348
|
|
|
276
|
-
|
|
277
|
-
|
|
349
|
+
return true
|
|
350
|
+
}
|
|
278
351
|
|
|
279
|
-
|
|
352
|
+
private verifyValid (allowTxidOnly?: boolean): { valid: boolean, roots: Record<number, string> } {
|
|
353
|
+
const r: { valid: boolean, roots: Record<number, string> } = { valid: false, roots: {} }
|
|
280
354
|
|
|
281
|
-
|
|
355
|
+
this.sortTxs()
|
|
282
356
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
for (const tx of this.txs) {
|
|
287
|
-
if (tx.isTxidOnly) {
|
|
288
|
-
if (!allowTxidOnly) return r
|
|
289
|
-
txids[tx.txid] = true
|
|
290
|
-
}
|
|
291
|
-
}
|
|
357
|
+
// valid txids: only txids if allowed, bump txids, then txids with input's in txids
|
|
358
|
+
const txids: Record<string, boolean> = {}
|
|
292
359
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (r.roots[b.blockHeight] !== root)
|
|
300
|
-
return false
|
|
301
|
-
return true
|
|
302
|
-
}
|
|
360
|
+
for (const tx of this.txs) {
|
|
361
|
+
if (tx.isTxidOnly) {
|
|
362
|
+
if (!allowTxidOnly) return r
|
|
363
|
+
txids[tx.txid] = true
|
|
364
|
+
}
|
|
365
|
+
}
|
|
303
366
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
367
|
+
const confirmComputedRoot = (b: MerklePath, txid: string): boolean => {
|
|
368
|
+
const root = b.computeRoot(txid)
|
|
369
|
+
if (!r.roots[b.blockHeight]) {
|
|
370
|
+
// accept the root as valid for this block and reuse for subsequent txids
|
|
371
|
+
r.roots[b.blockHeight] = root
|
|
372
|
+
}
|
|
373
|
+
if (r.roots[b.blockHeight] !== root) { return false }
|
|
374
|
+
return true
|
|
375
|
+
}
|
|
314
376
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
377
|
+
for (const b of this.bumps) {
|
|
378
|
+
for (const n of b.path[0]) {
|
|
379
|
+
if (n.txid && n.hash) {
|
|
380
|
+
txids[n.hash] = true
|
|
381
|
+
// all txid hashes in all bumps must have agree on computed merkle path roots
|
|
382
|
+
if (!confirmComputedRoot(b, n.hash)) { return r }
|
|
320
383
|
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
321
386
|
|
|
322
|
-
|
|
323
|
-
|
|
387
|
+
for (const t of this.txs) {
|
|
388
|
+
for (const i of t.inputTxids)
|
|
389
|
+
// all input txids must be included before they are referenced
|
|
390
|
+
{ if (!txids[i]) return r }
|
|
391
|
+
txids[t.txid] = true
|
|
324
392
|
}
|
|
325
393
|
|
|
326
|
-
|
|
394
|
+
r.valid = true
|
|
395
|
+
return r
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
327
399
|
* Returns a binary array representing the serialized BEEF
|
|
328
400
|
* @returns A binary array representing the BEEF
|
|
329
401
|
*/
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
writer.writeUInt32LE(this.magic)
|
|
402
|
+
toBinary (): number[] {
|
|
403
|
+
const writer = new Writer()
|
|
404
|
+
writer.writeUInt32LE(this.magic)
|
|
334
405
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
writer.writeVarIntNum(this.txs.length)
|
|
341
|
-
for (const tx of this.txs) {
|
|
342
|
-
tx.toWriter(writer, this.magic)
|
|
343
|
-
}
|
|
406
|
+
writer.writeVarIntNum(this.bumps.length)
|
|
407
|
+
for (const b of this.bumps) {
|
|
408
|
+
writer.write(b.toBinary())
|
|
409
|
+
}
|
|
344
410
|
|
|
345
|
-
|
|
411
|
+
writer.writeVarIntNum(this.txs.length)
|
|
412
|
+
for (const tx of this.txs) {
|
|
413
|
+
tx.toWriter(writer, this.magic)
|
|
346
414
|
}
|
|
347
415
|
|
|
348
|
-
|
|
416
|
+
return writer.toArray()
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
349
420
|
* Returns a hex string representing the serialized BEEF
|
|
350
421
|
* @returns A hex string representing the BEEF
|
|
351
422
|
*/
|
|
352
|
-
|
|
353
|
-
|
|
423
|
+
toHex (): string {
|
|
424
|
+
return toHex(this.toBinary())
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
static fromReader (br: Reader): Beef {
|
|
428
|
+
let version = br.readUInt32LE()
|
|
429
|
+
let atomicTxid: string | undefined = undefined
|
|
430
|
+
if (version === ATOMIC_BEEF) {
|
|
431
|
+
// Skip the txid and re-read the BEEF version
|
|
432
|
+
atomicTxid = toHex(br.readReverse(32))
|
|
433
|
+
version = br.readUInt32LE()
|
|
354
434
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const bumpsLength = br.readVarIntNum()
|
|
362
|
-
for (let i = 0; i < bumpsLength; i++) {
|
|
363
|
-
const bump = MerklePath.fromReader(br)
|
|
364
|
-
beef.bumps.push(bump)
|
|
365
|
-
}
|
|
366
|
-
const txsLength = br.readVarIntNum()
|
|
367
|
-
for (let i = 0; i < txsLength; i++) {
|
|
368
|
-
const beefTx = BeefTx.fromReader(br, version)
|
|
369
|
-
beef.txs.push(beefTx)
|
|
370
|
-
}
|
|
371
|
-
return beef
|
|
435
|
+
if (version !== BEEF_MAGIC && version !== BEEF_MAGIC_V2) { throw new Error(`Serialized BEEF must start with ${BEEF_MAGIC} or ${BEEF_MAGIC_V2} but starts with ${version}`) }
|
|
436
|
+
const beef = new Beef(version === BEEF_MAGIC_V2 ? 'V2' : undefined)
|
|
437
|
+
const bumpsLength = br.readVarIntNum()
|
|
438
|
+
for (let i = 0; i < bumpsLength; i++) {
|
|
439
|
+
const bump = MerklePath.fromReader(br)
|
|
440
|
+
beef.bumps.push(bump)
|
|
372
441
|
}
|
|
442
|
+
const txsLength = br.readVarIntNum()
|
|
443
|
+
for (let i = 0; i < txsLength; i++) {
|
|
444
|
+
const beefTx = BeefTx.fromReader(br, version)
|
|
445
|
+
beef.txs.push(beefTx)
|
|
446
|
+
}
|
|
447
|
+
beef.atomicTxid = atomicTxid
|
|
448
|
+
return beef
|
|
449
|
+
}
|
|
373
450
|
|
|
374
|
-
|
|
451
|
+
/**
|
|
375
452
|
* Constructs an instance of the Beef class based on the provided binary array
|
|
376
453
|
* @param bin The binary array from which to construct BEEF
|
|
377
454
|
* @returns An instance of the Beef class constructed from the binary data
|
|
378
455
|
*/
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
456
|
+
static fromBinary (bin: number[]): Beef {
|
|
457
|
+
const br = new Reader(bin)
|
|
458
|
+
return Beef.fromReader(br)
|
|
459
|
+
}
|
|
383
460
|
|
|
384
|
-
|
|
461
|
+
/**
|
|
385
462
|
* Constructs an instance of the Beef class based on the provided string
|
|
386
463
|
* @param s The string value from which to construct BEEF
|
|
387
464
|
* @param enc The encoding of the string value from which BEEF should be constructed
|
|
388
465
|
* @returns An instance of the Beef class constructed from the string
|
|
389
466
|
*/
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
467
|
+
static fromString (s: string, enc?: 'hex' | 'utf8' | 'base64'): Beef {
|
|
468
|
+
enc ||= 'hex'
|
|
469
|
+
const bin = toArray(s, enc)
|
|
470
|
+
const br = new Reader(bin)
|
|
471
|
+
return Beef.fromReader(br)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
398
475
|
* Try to validate newTx.bumpIndex by looking for an existing bump
|
|
399
476
|
* that proves newTx.txid
|
|
400
|
-
*
|
|
477
|
+
*
|
|
401
478
|
* @param newTx A new `BeefTx` that has been added to this.txs
|
|
402
479
|
* @returns true if a bump was found, false otherwise
|
|
403
480
|
*/
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
return false
|
|
481
|
+
private tryToValidateBumpIndex (newTx: BeefTx): boolean {
|
|
482
|
+
if (newTx.bumpIndex !== undefined) { return true }
|
|
483
|
+
const txid = newTx.txid
|
|
484
|
+
for (let i = 0; i < this.bumps.length; i++) {
|
|
485
|
+
const j = this.bumps[i].path[0].findIndex(b => b.hash === txid)
|
|
486
|
+
if (j >= 0) {
|
|
487
|
+
newTx.bumpIndex = i
|
|
488
|
+
this.bumps[i].path[0][j].txid = true
|
|
489
|
+
return true
|
|
490
|
+
}
|
|
417
491
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
492
|
+
return false
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Sort the `txs` by input txid dependency order:
|
|
497
|
+
* - Oldest Tx Anchored by Path
|
|
498
|
+
* - Newer Txs depending on Older parents
|
|
499
|
+
* - Newest Tx
|
|
500
|
+
*
|
|
501
|
+
* with proof (MerklePath) last, longest chain of dependencies first
|
|
502
|
+
*
|
|
503
|
+
* @returns `{ missingInputs, notValid, valid, withMissingInputs }`
|
|
422
504
|
*/
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
505
|
+
sortTxs ()
|
|
506
|
+
: {
|
|
507
|
+
missingInputs: string[],
|
|
508
|
+
notValid: string[],
|
|
509
|
+
valid: string[],
|
|
510
|
+
withMissingInputs: string[],
|
|
511
|
+
txidOnly: string[]
|
|
512
|
+
} {
|
|
513
|
+
// Hashtable of valid txids (with proof or all inputs chain to proof)
|
|
514
|
+
const validTxids: Record<string, boolean> = {}
|
|
515
|
+
|
|
516
|
+
// Hashtable of all transaction txids to transaction
|
|
517
|
+
const txidToTx: Record<string, BeefTx> = {}
|
|
518
|
+
|
|
519
|
+
// queue of unsorted transactions ...
|
|
520
|
+
let queue: BeefTx[] = []
|
|
521
|
+
|
|
522
|
+
// sorted transactions: hasProof to with longest dependency chain
|
|
523
|
+
const result: BeefTx[] = []
|
|
524
|
+
|
|
525
|
+
const txidOnly: BeefTx[] = []
|
|
526
|
+
|
|
527
|
+
for (const tx of this.txs) {
|
|
528
|
+
txidToTx[tx.txid] = tx
|
|
529
|
+
tx.isValid = tx.hasProof
|
|
530
|
+
if (tx.isValid) {
|
|
531
|
+
validTxids[tx.txid] = true
|
|
532
|
+
result.push(tx)
|
|
533
|
+
} else if (tx.isTxidOnly)
|
|
534
|
+
txidOnly.push(tx)
|
|
535
|
+
else
|
|
536
|
+
queue.push(tx)
|
|
537
|
+
}
|
|
427
538
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
539
|
+
// Hashtable of unknown input txids used to fund transactions without their own proof.
|
|
540
|
+
const missingInputs: Record<string, boolean> = {}
|
|
541
|
+
// transactions with one or more missing inputs
|
|
542
|
+
const txsMissingInputs: BeefTx[] = []
|
|
543
|
+
|
|
544
|
+
const possiblyMissingInputs = queue
|
|
545
|
+
queue = []
|
|
546
|
+
|
|
547
|
+
for (const tx of possiblyMissingInputs) {
|
|
548
|
+
let hasMissingInput = false
|
|
549
|
+
if (!tx.isValid) {
|
|
550
|
+
// For all the unproven transactions,
|
|
551
|
+
// link their inputs that exist in this beef,
|
|
552
|
+
// make a note of missing inputs.
|
|
553
|
+
for (const inputTxid of tx.inputTxids) {
|
|
554
|
+
if (!txidToTx[inputTxid]) {
|
|
555
|
+
missingInputs[inputTxid] = true
|
|
556
|
+
hasMissingInput = true
|
|
557
|
+
}
|
|
432
558
|
}
|
|
559
|
+
}
|
|
560
|
+
if (hasMissingInput)
|
|
561
|
+
txsMissingInputs.push(tx)
|
|
562
|
+
else
|
|
563
|
+
queue.push(tx)
|
|
564
|
+
}
|
|
433
565
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
566
|
+
// As long as we have unsorted transactions...
|
|
567
|
+
while (queue.length > 0) {
|
|
568
|
+
const oldQueue = queue
|
|
569
|
+
queue = []
|
|
570
|
+
for (const tx of oldQueue) {
|
|
571
|
+
if (tx.inputTxids.every(txid => validTxids[txid])) {
|
|
572
|
+
validTxids[tx.txid] = true
|
|
573
|
+
result.push(tx)
|
|
574
|
+
} else
|
|
575
|
+
queue.push(tx)
|
|
576
|
+
}
|
|
577
|
+
if (oldQueue.length === queue.length)
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
445
580
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
// sorted transactions
|
|
449
|
-
const result: BeefTx[] = []
|
|
450
|
-
|
|
451
|
-
// Increment each txid's degree for every input reference to it by another txid
|
|
452
|
-
for (const tx of this.txs) {
|
|
453
|
-
for (const inputTxid of tx.inputTxids) {
|
|
454
|
-
const tx = txidToTx[inputTxid]
|
|
455
|
-
if (tx)
|
|
456
|
-
tx.degree++
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
// Since circular dependencies aren't possible, start with the txids no one depends on.
|
|
460
|
-
// These are the transactions that should be sent last...
|
|
461
|
-
for (const tx of this.txs) {
|
|
462
|
-
if (tx.degree === 0) {
|
|
463
|
-
queue.push(tx)
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
// As long as we have transactions to send...
|
|
467
|
-
while (queue.length) {
|
|
468
|
-
let tx = queue.shift()!
|
|
469
|
-
// Add it as new first to send
|
|
470
|
-
result.unshift(tx)
|
|
471
|
-
// And remove its impact on degree
|
|
472
|
-
// noting that any tx achieving a
|
|
473
|
-
// value of zero can be sent...
|
|
474
|
-
for (const inputTxid of tx.inputTxids) {
|
|
475
|
-
const inputTx = txidToTx[inputTxid]
|
|
476
|
-
if (inputTx) {
|
|
477
|
-
inputTx.degree--
|
|
478
|
-
if (inputTx.degree === 0) {
|
|
479
|
-
queue.push(inputTx)
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
this.txs = result
|
|
581
|
+
// transactions that don't have proofs and don't chain to proofs
|
|
582
|
+
const txsNotValid = queue
|
|
485
583
|
|
|
486
|
-
|
|
584
|
+
// New order of txs is sorted, unsortable, txidOnly (no raw transaction)
|
|
585
|
+
this.txs = result.concat(queue).concat(txidOnly).concat(txsMissingInputs)
|
|
586
|
+
|
|
587
|
+
return {
|
|
588
|
+
missingInputs: Object.keys(missingInputs),
|
|
589
|
+
notValid: txsNotValid.map(tx => tx.txid),
|
|
590
|
+
valid: Object.keys(validTxids),
|
|
591
|
+
withMissingInputs: txsMissingInputs.map(tx => tx.txid),
|
|
592
|
+
txidOnly: txidOnly.map(tx => tx.txid)
|
|
487
593
|
}
|
|
594
|
+
}
|
|
488
595
|
|
|
489
|
-
|
|
596
|
+
/**
|
|
490
597
|
* @returns a shallow copy of this beef
|
|
491
598
|
*/
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
599
|
+
clone (): Beef {
|
|
600
|
+
const c = new Beef()
|
|
601
|
+
c.bumps = Array.from(this.bumps)
|
|
602
|
+
c.txs = Array.from(this.txs)
|
|
603
|
+
return c
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
500
607
|
* Ensure that all the txids in `knownTxids` are txidOnly
|
|
501
|
-
* @param knownTxids
|
|
608
|
+
* @param knownTxids
|
|
502
609
|
*/
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}
|
|
512
|
-
// TODO: bumps could be trimmed to eliminate unreferenced proofs.
|
|
610
|
+
trimKnownTxids (knownTxids: string[]) {
|
|
611
|
+
for (let i = 0; i < this.txs.length;) {
|
|
612
|
+
const tx = this.txs[i]
|
|
613
|
+
if (tx.isTxidOnly && knownTxids.includes(tx.txid)) {
|
|
614
|
+
this.txs.splice(i, 1)
|
|
615
|
+
} else {
|
|
616
|
+
i++
|
|
617
|
+
}
|
|
513
618
|
}
|
|
514
|
-
|
|
515
|
-
|
|
619
|
+
// TODO: bumps could be trimmed to eliminate unreferenced proofs.
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* @returns array of transaction txids that either have a proof or whose inputs chain back to a proven transaction.
|
|
624
|
+
*/
|
|
625
|
+
getValidTxids() : string[] {
|
|
626
|
+
const r = this.sortTxs()
|
|
627
|
+
return r.valid
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/**
|
|
516
631
|
* @returns Summary of `Beef` contents as multi-line string.
|
|
517
632
|
*/
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
542
|
-
return log
|
|
633
|
+
toLogString (): string {
|
|
634
|
+
let log = ''
|
|
635
|
+
log += `BEEF with ${this.bumps.length} BUMPS and ${this.txs.length} Transactions, isValid ${this.isValid()}\n`
|
|
636
|
+
let i = -1
|
|
637
|
+
for (const b of this.bumps) {
|
|
638
|
+
i++
|
|
639
|
+
log += ` BUMP ${i}\n block: ${b.blockHeight}\n txids: [\n${b.path[0].filter(n => !!n.txid).map(n => ` '${n.hash}'`).join(',\n')}\n ]\n`
|
|
640
|
+
}
|
|
641
|
+
i = -1
|
|
642
|
+
for (const t of this.txs) {
|
|
643
|
+
i++
|
|
644
|
+
log += ` TX ${i}\n txid: ${t.txid}\n`
|
|
645
|
+
if (t.bumpIndex !== undefined) {
|
|
646
|
+
log += ` bumpIndex: ${t.bumpIndex}\n`
|
|
647
|
+
}
|
|
648
|
+
if (t.isTxidOnly) {
|
|
649
|
+
log += ' txidOnly\n'
|
|
650
|
+
} else {
|
|
651
|
+
log += ` rawTx length=${t.rawTx.length}\n`
|
|
652
|
+
}
|
|
653
|
+
if (t.inputTxids.length > 0) {
|
|
654
|
+
log += ` inputs: [\n${t.inputTxids.map(it => ` '${it}'`).join(',\n')}\n ]\n`
|
|
655
|
+
}
|
|
543
656
|
}
|
|
657
|
+
return log
|
|
658
|
+
}
|
|
544
659
|
}
|
|
545
660
|
|
|
546
|
-
export default Beef
|
|
661
|
+
export default Beef
|