@bsv/sdk 1.1.30 → 1.1.33

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.
Files changed (36) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/transaction/Beef.js +195 -50
  3. package/dist/cjs/src/transaction/Beef.js.map +1 -1
  4. package/dist/cjs/src/transaction/BeefParty.js +16 -0
  5. package/dist/cjs/src/transaction/BeefParty.js.map +1 -1
  6. package/dist/cjs/src/transaction/BeefTx.js +6 -1
  7. package/dist/cjs/src/transaction/BeefTx.js.map +1 -1
  8. package/dist/cjs/src/transaction/Transaction.js +25 -10
  9. package/dist/cjs/src/transaction/Transaction.js.map +1 -1
  10. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  11. package/dist/esm/src/transaction/Beef.js +194 -49
  12. package/dist/esm/src/transaction/Beef.js.map +1 -1
  13. package/dist/esm/src/transaction/BeefParty.js +16 -0
  14. package/dist/esm/src/transaction/BeefParty.js.map +1 -1
  15. package/dist/esm/src/transaction/BeefTx.js +6 -1
  16. package/dist/esm/src/transaction/BeefTx.js.map +1 -1
  17. package/dist/esm/src/transaction/Transaction.js +25 -10
  18. package/dist/esm/src/transaction/Transaction.js.map +1 -1
  19. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  20. package/dist/types/src/transaction/Beef.d.ts +60 -4
  21. package/dist/types/src/transaction/Beef.d.ts.map +1 -1
  22. package/dist/types/src/transaction/BeefParty.d.ts +11 -0
  23. package/dist/types/src/transaction/BeefParty.d.ts.map +1 -1
  24. package/dist/types/src/transaction/BeefTx.d.ts +6 -1
  25. package/dist/types/src/transaction/BeefTx.d.ts.map +1 -1
  26. package/dist/types/src/transaction/Transaction.d.ts +8 -2
  27. package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
  28. package/dist/types/tsconfig.types.tsbuildinfo +1 -1
  29. package/dist/umd/bundle.js +1 -1
  30. package/docs/transaction.md +166 -9
  31. package/package.json +1 -1
  32. package/src/transaction/Beef.ts +221 -52
  33. package/src/transaction/BeefParty.ts +17 -0
  34. package/src/transaction/BeefTx.ts +6 -1
  35. package/src/transaction/Transaction.ts +25 -10
  36. package/src/transaction/__tests/Beef.test.ts +74 -15
@@ -857,7 +857,7 @@ export default class BeefTx {
857
857
  _rawTx?: number[];
858
858
  _txid?: string;
859
859
  inputTxids: string[] = [];
860
- degree: number = 0;
860
+ isValid?: boolean = undefined;
861
861
  get bumpIndex(): number | undefined
862
862
  set bumpIndex(v: number | undefined)
863
863
  get hasProof(): boolean
@@ -888,6 +888,16 @@ Argument Details
888
888
  + **bumpIndex**
889
889
  + If transaction already has a proof in the beef to which it will be added.
890
890
 
891
+ #### Property isValid
892
+
893
+ true if `hasProof` or all inputs chain to `hasProof`.
894
+
895
+ Typically set by sorting transactions by proven dependency chains.
896
+
897
+ ```ts
898
+ isValid?: boolean = undefined
899
+ ```
900
+
891
901
  </details>
892
902
 
893
903
  Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
@@ -900,9 +910,13 @@ export class Beef {
900
910
  bumps: MerklePath[] = [];
901
911
  txs: BeefTx[] = [];
902
912
  version: BeefVersion = undefined;
913
+ atomicTxid: string | undefined = undefined;
903
914
  constructor(version?: BeefVersion)
904
915
  get magic(): number
905
916
  findTxid(txid: string): BeefTx | undefined
917
+ findBump(txid: string): MerklePath | undefined
918
+ findTransactionForSigning(txid: string): Transaction | undefined
919
+ findAtomicTransaction(txid: string): Transaction | undefined
906
920
  mergeBump(bump: MerklePath): number
907
921
  mergeRawTx(rawTx: number[], bumpIndex?: number): BeefTx
908
922
  mergeTransaction(tx: Transaction): BeefTx
@@ -912,14 +926,23 @@ export class Beef {
912
926
  mergeBeef(beef: number[] | Beef)
913
927
  isValid(allowTxidOnly?: boolean): boolean
914
928
  async verify(chainTracker: ChainTracker, allowTxidOnly?: boolean): Promise<boolean>
929
+ toWriter(writer: Writer)
915
930
  toBinary(): number[]
931
+ toBinaryAtomic(txid: string)
916
932
  toHex(): string
917
933
  static fromReader(br: Reader): Beef
918
934
  static fromBinary(bin: number[]): Beef
919
935
  static fromString(s: string, enc?: "hex" | "utf8" | "base64"): Beef
920
- sortTxs(): string[]
936
+ sortTxs(): {
937
+ missingInputs: string[];
938
+ notValid: string[];
939
+ valid: string[];
940
+ withMissingInputs: string[];
941
+ txidOnly: string[];
942
+ }
921
943
  clone(): Beef
922
944
  trimKnownTxids(knownTxids: string[])
945
+ getValidTxids(): string[]
923
946
  toLogString(): string
924
947
  }
925
948
  ```
@@ -938,6 +961,55 @@ Returns
938
961
 
939
962
  a shallow copy of this beef
940
963
 
964
+ #### Method findAtomicTransaction
965
+
966
+ Builds the proof tree rooted at a specific `Transaction`.
967
+
968
+ To succeed, the Beef must contain all the required transaction and merkle path data.
969
+
970
+ ```ts
971
+ findAtomicTransaction(txid: string): Transaction | undefined
972
+ ```
973
+
974
+ Returns
975
+
976
+ Transaction with input `SourceTransaction` and `MerklePath` populated from this Beef.
977
+
978
+ Argument Details
979
+
980
+ + **txid**
981
+ + The id of the target transaction.
982
+
983
+ #### Method findBump
984
+
985
+ ```ts
986
+ findBump(txid: string): MerklePath | undefined
987
+ ```
988
+
989
+ Returns
990
+
991
+ `MerklePath` with level zero hash equal to txid or undefined.
992
+
993
+ #### Method findTransactionForSigning
994
+
995
+ Finds a Transaction in this `Beef`
996
+ and adds any missing input SourceTransactions from this `Beef`.
997
+
998
+ The result is suitable for signing.
999
+
1000
+ ```ts
1001
+ findTransactionForSigning(txid: string): Transaction | undefined
1002
+ ```
1003
+
1004
+ Returns
1005
+
1006
+ Transaction with all available input `SourceTransaction`s from this Beef.
1007
+
1008
+ Argument Details
1009
+
1010
+ + **txid**
1011
+ + The id of the target transaction.
1012
+
941
1013
  #### Method findTxid
942
1014
 
943
1015
  ```ts
@@ -989,6 +1061,16 @@ Argument Details
989
1061
  + **enc**
990
1062
  + The encoding of the string value from which BEEF should be constructed
991
1063
 
1064
+ #### Method getValidTxids
1065
+
1066
+ ```ts
1067
+ getValidTxids(): string[]
1068
+ ```
1069
+
1070
+ Returns
1071
+
1072
+ array of transaction txids that either have a proof or whose inputs chain back to a proven transaction.
1073
+
992
1074
  #### Method isValid
993
1075
 
994
1076
  Sorts `txs` and checks structural validity of beef.
@@ -1074,15 +1156,26 @@ Argument Details
1074
1156
 
1075
1157
  #### Method sortTxs
1076
1158
 
1077
- Sort the `txs` by input txid dependency order.
1159
+ Sort the `txs` by input txid dependency order:
1160
+ - Oldest Tx Anchored by Path
1161
+ - Newer Txs depending on Older parents
1162
+ - Newest Tx
1163
+
1164
+ with proof (MerklePath) last, longest chain of dependencies first
1078
1165
 
1079
1166
  ```ts
1080
- sortTxs(): string[]
1167
+ sortTxs(): {
1168
+ missingInputs: string[];
1169
+ notValid: string[];
1170
+ valid: string[];
1171
+ withMissingInputs: string[];
1172
+ txidOnly: string[];
1173
+ }
1081
1174
  ```
1082
1175
 
1083
1176
  Returns
1084
1177
 
1085
- array of input txids of unproven transactions that aren't included in txs.
1178
+ `{ missingInputs, notValid, valid, withMissingInputs }`
1086
1179
 
1087
1180
  #### Method toBinary
1088
1181
 
@@ -1096,6 +1189,21 @@ Returns
1096
1189
 
1097
1190
  A binary array representing the BEEF
1098
1191
 
1192
+ #### Method toBinaryAtomic
1193
+
1194
+ Serialize this Beef as AtomicBEEF.
1195
+
1196
+ `txid` must exist and be the last transaction
1197
+ in sorted (dependency) order.
1198
+
1199
+ ```ts
1200
+ toBinaryAtomic(txid: string)
1201
+ ```
1202
+
1203
+ Returns
1204
+
1205
+ serialized contents of this Beef with AtomicBEEF prefix.
1206
+
1099
1207
  #### Method toHex
1100
1208
 
1101
1209
  Returns a hex string representing the serialized BEEF
@@ -1118,6 +1226,14 @@ Returns
1118
1226
 
1119
1227
  Summary of `Beef` contents as multi-line string.
1120
1228
 
1229
+ #### Method toWriter
1230
+
1231
+ Serializes this data to `writer`
1232
+
1233
+ ```ts
1234
+ toWriter(writer: Writer)
1235
+ ```
1236
+
1121
1237
  #### Method trimKnownTxids
1122
1238
 
1123
1239
  Ensure that all the txids in `knownTxids` are txidOnly
@@ -1219,8 +1335,8 @@ export default class Transaction {
1219
1335
  id(enc: "hex"): string;
1220
1336
  id(enc?: "hex"): number[] | string
1221
1337
  async verify(chainTracker: ChainTracker | "scripts only" = defaultChainTracker(), feeModel?: FeeModel): Promise<boolean>
1222
- toBEEF(): number[]
1223
- toAtomicBEEF(): number[]
1338
+ toBEEF(allowPartial?: boolean): number[]
1339
+ toAtomicBEEF(allowPartial?: boolean): number[]
1224
1340
  }
1225
1341
  ```
1226
1342
 
@@ -1554,25 +1670,43 @@ and then the BEEF data containing only the subject transaction and its dependenc
1554
1670
  This format ensures that the BEEF structure is atomic and contains no unrelated transactions.
1555
1671
 
1556
1672
  ```ts
1557
- toAtomicBEEF(): number[]
1673
+ toAtomicBEEF(allowPartial?: boolean): number[]
1558
1674
  ```
1559
1675
 
1560
1676
  Returns
1561
1677
 
1562
1678
  - The serialized Atomic BEEF structure.
1563
1679
 
1680
+ Argument Details
1681
+
1682
+ + **allowPartial**
1683
+ + If true, error will not be thrown if there are any missing sourceTransactions.
1684
+
1685
+ Throws
1686
+
1687
+ Error if there are any missing sourceTransactions unless `allowPartial` is true.
1688
+
1564
1689
  #### Method toBEEF
1565
1690
 
1566
1691
  Serializes this transaction, together with its inputs and the respective merkle proofs, into the BEEF (BRC-62) format. This enables efficient verification of its compliance with the rules of SPV.
1567
1692
 
1568
1693
  ```ts
1569
- toBEEF(): number[]
1694
+ toBEEF(allowPartial?: boolean): number[]
1570
1695
  ```
1571
1696
 
1572
1697
  Returns
1573
1698
 
1574
1699
  The serialized BEEF structure
1575
1700
 
1701
+ Argument Details
1702
+
1703
+ + **allowPartial**
1704
+ + If true, error will not be thrown if there are any missing sourceTransactions.
1705
+
1706
+ Throws
1707
+
1708
+ Error if there are any missing sourceTransactions unless `allowPartial` is true.
1709
+
1576
1710
  #### Method toBinary
1577
1711
 
1578
1712
  Converts the transaction to a binary array format.
@@ -1716,6 +1850,7 @@ export class BeefParty extends Beef {
1716
1850
  getKnownTxidsForParty(party: string): string[]
1717
1851
  getTrimmedBeefForParty(party: string): Beef
1718
1852
  addKnownTxidsForParty(party: string, knownTxids: string[])
1853
+ mergeBeefFromParty(party: string, beef: number[] | Beef)
1719
1854
  }
1720
1855
  ```
1721
1856
 
@@ -1794,6 +1929,18 @@ Returns
1794
1929
 
1795
1930
  `true` if `party` has already beed added to this `BeefParty`.
1796
1931
 
1932
+ #### Method mergeBeefFromParty
1933
+
1934
+ Merge a `beef` received from a specific `party`.
1935
+
1936
+ Updates this `BeefParty` to track all the txids
1937
+ corresponding to transactions for which `party`
1938
+ has raw transaction and validity proof data.
1939
+
1940
+ ```ts
1941
+ mergeBeefFromParty(party: string, beef: number[] | Beef)
1942
+ ```
1943
+
1797
1944
  </details>
1798
1945
 
1799
1946
  Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
@@ -1925,6 +2072,7 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
1925
2072
 
1926
2073
  | |
1927
2074
  | --- |
2075
+ | [ATOMIC_BEEF](#variable-atomic_beef) |
1928
2076
  | [BEEF_MAGIC](#variable-beef_magic) |
1929
2077
  | [BEEF_MAGIC_TXID_ONLY_EXTENSION](#variable-beef_magic_txid_only_extension) |
1930
2078
  | [BEEF_MAGIC_V2](#variable-beef_magic_v2) |
@@ -1960,3 +2108,12 @@ BEEF_MAGIC_TXID_ONLY_EXTENSION = 4022206465
1960
2108
  Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
1961
2109
 
1962
2110
  ---
2111
+ ### Variable: ATOMIC_BEEF
2112
+
2113
+ ```ts
2114
+ ATOMIC_BEEF = 16843009
2115
+ ```
2116
+
2117
+ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Variables](#variables)
2118
+
2119
+ ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.1.30",
3
+ "version": "1.1.33",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -7,6 +7,7 @@ import { Reader, Writer, toHex, toArray } from '../primitives/utils.js'
7
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
 
@@ -16,6 +17,14 @@ export type BeefVersion = undefined | 'V1' | 'V2'
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
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
19
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
@@ -48,11 +57,18 @@ export type BeefVersion = undefined | 'V1' | 'V2'
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
68
  bumps: MerklePath[] = []
54
69
  txs: BeefTx[] = []
55
70
  version: BeefVersion = undefined
71
+ atomicTxid: string | undefined = undefined
56
72
 
57
73
  constructor (version?: BeefVersion) {
58
74
  this.version = version
@@ -82,6 +98,81 @@ export class Beef {
82
98
  return this.txs.find(tx => tx.txid === txid)
83
99
  }
84
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
+ }
129
+ }
130
+
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
156
+ }
157
+ }
158
+ if (i.sourceTransaction) {
159
+ const mp = beef.findBump(i.sourceTransaction.id('hex'))
160
+ if (mp) {
161
+ i.sourceTransaction.merklePath = mp
162
+ }
163
+ else {
164
+ addInputProof(beef, i.sourceTransaction)
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ addInputProof(this, beefTx.tx)
172
+
173
+ return beefTx.tx
174
+ }
175
+
85
176
  /**
86
177
  * Merge a MerklePath that is assumed to be fully valid.
87
178
  * @param bump
@@ -305,11 +396,10 @@ export class Beef {
305
396
  }
306
397
 
307
398
  /**
308
- * Returns a binary array representing the serialized BEEF
309
- * @returns A binary array representing the BEEF
310
- */
311
- toBinary (): number[] {
312
- const writer = new Writer()
399
+ * Serializes this data to `writer`
400
+ * @param writer
401
+ */
402
+ toWriter (writer: Writer) {
313
403
  writer.writeUInt32LE(this.magic)
314
404
 
315
405
  writer.writeVarIntNum(this.bumps.length)
@@ -321,7 +411,38 @@ export class Beef {
321
411
  for (const tx of this.txs) {
322
412
  tx.toWriter(writer, this.magic)
323
413
  }
414
+ }
415
+
416
+ /**
417
+ * Returns a binary array representing the serialized BEEF
418
+ * @returns A binary array representing the BEEF
419
+ */
420
+ toBinary (): number[] {
421
+ const writer = new Writer()
422
+ this.toWriter(writer)
423
+ return writer.toArray()
424
+ }
324
425
 
426
+ /**
427
+ * Serialize this Beef as AtomicBEEF.
428
+ *
429
+ * `txid` must exist and be the last transaction
430
+ * in sorted (dependency) order.
431
+ *
432
+ * @param txid
433
+ * @returns serialized contents of this Beef with AtomicBEEF prefix.
434
+ */
435
+ toBinaryAtomic(txid: string) {
436
+ this.sortTxs()
437
+ const tx = this.findTxid(txid)
438
+ if (!tx)
439
+ throw new Error(`${txid} does not exist in this Beef`)
440
+ if (this.txs[this.txs.length - 1] !== tx)
441
+ throw new Error(`${txid} is not the last transaction in this Beef`)
442
+ const writer = new Writer()
443
+ writer.writeUInt32LE(ATOMIC_BEEF)
444
+ writer.write(toArray(txid, 'hex'))
445
+ this.toWriter(writer)
325
446
  return writer.toArray()
326
447
  }
327
448
 
@@ -334,7 +455,13 @@ export class Beef {
334
455
  }
335
456
 
336
457
  static fromReader (br: Reader): Beef {
337
- const version = br.readUInt32LE()
458
+ let version = br.readUInt32LE()
459
+ let atomicTxid: string | undefined = undefined
460
+ if (version === ATOMIC_BEEF) {
461
+ // Skip the txid and re-read the BEEF version
462
+ atomicTxid = toHex(br.read(32))
463
+ version = br.readUInt32LE()
464
+ }
338
465
  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}`) }
339
466
  const beef = new Beef(version === BEEF_MAGIC_V2 ? 'V2' : undefined)
340
467
  const bumpsLength = br.readVarIntNum()
@@ -347,6 +474,7 @@ export class Beef {
347
474
  const beefTx = BeefTx.fromReader(br, version)
348
475
  beef.txs.push(beefTx)
349
476
  }
477
+ beef.atomicTxid = atomicTxid
350
478
  return beef
351
479
  }
352
480
 
@@ -395,71 +523,104 @@ export class Beef {
395
523
  }
396
524
 
397
525
  /**
398
- * Sort the `txs` by input txid dependency order.
399
- * @returns array of input txids of unproven transactions that aren't included in txs.
526
+ * Sort the `txs` by input txid dependency order:
527
+ * - Oldest Tx Anchored by Path
528
+ * - Newer Txs depending on Older parents
529
+ * - Newest Tx
530
+ *
531
+ * with proof (MerklePath) last, longest chain of dependencies first
532
+ *
533
+ * @returns `{ missingInputs, notValid, valid, withMissingInputs }`
400
534
  */
401
- sortTxs (): string[] {
402
- const missingInputs: Record<string, boolean> = {}
403
-
535
+ sortTxs ()
536
+ : {
537
+ missingInputs: string[],
538
+ notValid: string[],
539
+ valid: string[],
540
+ withMissingInputs: string[],
541
+ txidOnly: string[]
542
+ } {
543
+ // Hashtable of valid txids (with proof or all inputs chain to proof)
544
+ const validTxids: Record<string, boolean> = {}
545
+
546
+ // Hashtable of all transaction txids to transaction
404
547
  const txidToTx: Record<string, BeefTx> = {}
405
548
 
549
+ // queue of unsorted transactions ...
550
+ let queue: BeefTx[] = []
551
+
552
+ // sorted transactions: hasProof to with longest dependency chain
553
+ const result: BeefTx[] = []
554
+
555
+ const txidOnly: BeefTx[] = []
556
+
406
557
  for (const tx of this.txs) {
407
558
  txidToTx[tx.txid] = tx
408
- // All transactions in this beef start at degree zero.
409
- tx.degree = 0
559
+ tx.isValid = tx.hasProof
560
+ if (tx.isValid) {
561
+ validTxids[tx.txid] = true
562
+ result.push(tx)
563
+ } else if (tx.isTxidOnly)
564
+ txidOnly.push(tx)
565
+ else
566
+ queue.push(tx)
410
567
  }
411
568
 
412
- for (const tx of this.txs) {
413
- if (tx.bumpIndex === undefined) {
569
+ // Hashtable of unknown input txids used to fund transactions without their own proof.
570
+ const missingInputs: Record<string, boolean> = {}
571
+ // transactions with one or more missing inputs
572
+ const txsMissingInputs: BeefTx[] = []
573
+
574
+ const possiblyMissingInputs = queue
575
+ queue = []
576
+
577
+ for (const tx of possiblyMissingInputs) {
578
+ let hasMissingInput = false
579
+ if (!tx.isValid) {
414
580
  // For all the unproven transactions,
415
581
  // link their inputs that exist in this beef,
416
582
  // make a note of missing inputs.
417
583
  for (const inputTxid of tx.inputTxids) {
418
- if (!txidToTx[inputTxid]) { missingInputs[inputTxid] = true }
584
+ if (!txidToTx[inputTxid]) {
585
+ missingInputs[inputTxid] = true
586
+ hasMissingInput = true
587
+ }
419
588
  }
420
589
  }
421
- }
422
-
423
- // queue of transactions that no unsorted transactions depend upon...
424
- const queue: BeefTx[] = []
425
- // sorted transactions
426
- const result: BeefTx[] = []
427
-
428
- // Increment each txid's degree for every input reference to it by another txid
429
- for (const tx of this.txs) {
430
- for (const inputTxid of tx.inputTxids) {
431
- const tx = txidToTx[inputTxid]
432
- if (tx) { tx.degree++ }
433
- }
434
- }
435
- // Since circular dependencies aren't possible, start with the txids no one depends on.
436
- // These are the transactions that should be sent last...
437
- for (const tx of this.txs) {
438
- if (tx.degree === 0) {
590
+ if (hasMissingInput)
591
+ txsMissingInputs.push(tx)
592
+ else
439
593
  queue.push(tx)
440
- }
441
594
  }
442
- // As long as we have transactions to send...
595
+
596
+ // As long as we have unsorted transactions...
443
597
  while (queue.length > 0) {
444
- const tx = queue.shift()!
445
- // Add it as new first to send
446
- result.unshift(tx)
447
- // And remove its impact on degree
448
- // noting that any tx achieving a
449
- // value of zero can be sent...
450
- for (const inputTxid of tx.inputTxids) {
451
- const inputTx = txidToTx[inputTxid]
452
- if (inputTx) {
453
- inputTx.degree--
454
- if (inputTx.degree === 0) {
455
- queue.push(inputTx)
456
- }
457
- }
598
+ const oldQueue = queue
599
+ queue = []
600
+ for (const tx of oldQueue) {
601
+ if (tx.inputTxids.every(txid => validTxids[txid])) {
602
+ validTxids[tx.txid] = true
603
+ result.push(tx)
604
+ } else
605
+ queue.push(tx)
458
606
  }
607
+ if (oldQueue.length === queue.length)
608
+ break;
459
609
  }
460
- this.txs = result
461
610
 
462
- return Object.keys(missingInputs)
611
+ // transactions that don't have proofs and don't chain to proofs
612
+ const txsNotValid = queue
613
+
614
+ // New order of txs is sorted, unsortable, txidOnly (no raw transaction)
615
+ this.txs = result.concat(queue).concat(txidOnly).concat(txsMissingInputs)
616
+
617
+ return {
618
+ missingInputs: Object.keys(missingInputs),
619
+ notValid: txsNotValid.map(tx => tx.txid),
620
+ valid: Object.keys(validTxids),
621
+ withMissingInputs: txsMissingInputs.map(tx => tx.txid),
622
+ txidOnly: txidOnly.map(tx => tx.txid)
623
+ }
463
624
  }
464
625
 
465
626
  /**
@@ -488,6 +649,14 @@ export class Beef {
488
649
  // TODO: bumps could be trimmed to eliminate unreferenced proofs.
489
650
  }
490
651
 
652
+ /**
653
+ * @returns array of transaction txids that either have a proof or whose inputs chain back to a proven transaction.
654
+ */
655
+ getValidTxids() : string[] {
656
+ const r = this.sortTxs()
657
+ return r.valid
658
+ }
659
+
491
660
  /**
492
661
  * @returns Summary of `Beef` contents as multi-line string.
493
662
  */
@@ -91,6 +91,23 @@ export class BeefParty extends Beef {
91
91
  this.mergeTxidOnly(txid)
92
92
  }
93
93
  }
94
+
95
+ /**
96
+ * Merge a `beef` received from a specific `party`.
97
+ *
98
+ * Updates this `BeefParty` to track all the txids
99
+ * corresponding to transactions for which `party`
100
+ * has raw transaction and validity proof data.
101
+ *
102
+ * @param party
103
+ * @param beef
104
+ */
105
+ mergeBeefFromParty (party: string, beef: number[] | Beef) {
106
+ const b: Beef = Array.isArray(beef) ? Beef.fromBinary(beef) : beef
107
+ const knownTxids = b.getValidTxids()
108
+ this.mergeBeef(b)
109
+ this.addKnownTxidsForParty(party, knownTxids)
110
+ }
94
111
  }
95
112
 
96
113
  export default BeefParty