@bsv/sdk 1.2.18 → 1.2.19
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/transaction/MerklePath.js +12 -1
- package/dist/cjs/src/transaction/MerklePath.js.map +1 -1
- package/dist/cjs/src/transaction/chaintrackers/WhatsOnChain.js +20 -2
- package/dist/cjs/src/transaction/chaintrackers/WhatsOnChain.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/transaction/MerklePath.js +12 -1
- package/dist/esm/src/transaction/MerklePath.js.map +1 -1
- package/dist/esm/src/transaction/chaintrackers/WhatsOnChain.js +20 -2
- package/dist/esm/src/transaction/chaintrackers/WhatsOnChain.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/transaction/ChainTracker.d.ts +6 -0
- package/dist/types/src/transaction/ChainTracker.d.ts.map +1 -1
- package/dist/types/src/transaction/MerklePath.d.ts +1 -0
- package/dist/types/src/transaction/MerklePath.d.ts.map +1 -1
- package/dist/types/src/transaction/chaintrackers/WhatsOnChain.d.ts +2 -1
- package/dist/types/src/transaction/chaintrackers/WhatsOnChain.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/dist/umd/bundle.js +1 -1
- package/docs/transaction.md +6 -1
- package/package.json +1 -1
- package/src/transaction/ChainTracker.ts +6 -0
- package/src/transaction/MerklePath.ts +13 -1
- package/src/transaction/__tests/MerklePath.test.ts +23 -2
- package/src/transaction/chaintrackers/WhatsOnChain.ts +20 -2
- package/src/transaction/chaintrackers/__tests/WhatsOnChainChainTracker.test.ts +32 -1
package/docs/transaction.md
CHANGED
|
@@ -160,12 +160,16 @@ const chainTracker = {
|
|
|
160
160
|
isValidRootForHeight: async (root, height) => {
|
|
161
161
|
// Implementation to check if the Merkle root is valid for the specified block height.
|
|
162
162
|
}
|
|
163
|
+
currentHeight: async () => {
|
|
164
|
+
// Implementation to get the current block height.
|
|
165
|
+
}
|
|
163
166
|
};
|
|
164
167
|
```
|
|
165
168
|
|
|
166
169
|
```ts
|
|
167
170
|
export default interface ChainTracker {
|
|
168
171
|
isValidRootForHeight: (root: string, height: number) => Promise<boolean>;
|
|
172
|
+
currentHeight: () => Promise<number>;
|
|
169
173
|
}
|
|
170
174
|
```
|
|
171
175
|
|
|
@@ -1101,7 +1105,7 @@ isParty(party: string)
|
|
|
1101
1105
|
|
|
1102
1106
|
Returns
|
|
1103
1107
|
|
|
1104
|
-
`true` if `party` has already
|
|
1108
|
+
`true` if `party` has already been added to this `BeefParty`.
|
|
1105
1109
|
|
|
1106
1110
|
#### Method mergeBeefFromParty
|
|
1107
1111
|
|
|
@@ -2076,6 +2080,7 @@ export default class WhatsOnChain implements ChainTracker {
|
|
|
2076
2080
|
readonly apiKey: string;
|
|
2077
2081
|
constructor(network: "main" | "test" | "stn" = "main", config: WhatsOnChainConfig = {})
|
|
2078
2082
|
async isValidRootForHeight(root: string, height: number): Promise<boolean>
|
|
2083
|
+
async currentHeight(): Promise<number>
|
|
2079
2084
|
}
|
|
2080
2085
|
```
|
|
2081
2086
|
|
package/package.json
CHANGED
|
@@ -10,13 +10,19 @@
|
|
|
10
10
|
* @function isValidRootForHeight - A method to verify the validity of a Merkle root
|
|
11
11
|
* for a given block height.
|
|
12
12
|
*
|
|
13
|
+
* @function currentHeight - A method to get the current block height.
|
|
14
|
+
*
|
|
13
15
|
* @example
|
|
14
16
|
* const chainTracker = {
|
|
15
17
|
* isValidRootForHeight: async (root, height) => {
|
|
16
18
|
* // Implementation to check if the Merkle root is valid for the specified block height.
|
|
17
19
|
* }
|
|
20
|
+
* currentHeight: async () => {
|
|
21
|
+
* // Implementation to get the current block height.
|
|
22
|
+
* }
|
|
18
23
|
* };
|
|
19
24
|
*/
|
|
20
25
|
export default interface ChainTracker {
|
|
21
26
|
isValidRootForHeight: (root: string, height: number) => Promise<boolean>
|
|
27
|
+
currentHeight: () => Promise<number>
|
|
22
28
|
}
|
|
@@ -192,6 +192,11 @@ export default class MerklePath {
|
|
|
192
192
|
return toHex(this.toBinary())
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
//
|
|
196
|
+
private indexOf (txid: string): number {
|
|
197
|
+
return this.path[0].find(l => l.hash === txid).offset
|
|
198
|
+
}
|
|
199
|
+
|
|
195
200
|
/**
|
|
196
201
|
* Computes the Merkle root from the provided transaction ID.
|
|
197
202
|
*
|
|
@@ -204,7 +209,7 @@ export default class MerklePath {
|
|
|
204
209
|
txid = this.path[0].find(leaf => Boolean(leaf?.hash)).hash
|
|
205
210
|
}
|
|
206
211
|
// Find the index of the txid at the lowest level of the Merkle tree
|
|
207
|
-
const index = this.
|
|
212
|
+
const index = this.indexOf(txid)
|
|
208
213
|
if (typeof index !== 'number') {
|
|
209
214
|
throw new Error(`This proof does not contain the txid: ${txid}`)
|
|
210
215
|
}
|
|
@@ -282,6 +287,13 @@ export default class MerklePath {
|
|
|
282
287
|
*/
|
|
283
288
|
async verify (txid: string, chainTracker: ChainTracker): Promise<boolean> {
|
|
284
289
|
const root = this.computeRoot(txid)
|
|
290
|
+
if (this.indexOf(txid) === 0) {
|
|
291
|
+
// Coinbase transaction outputs can only be spent once they're 100 blocks deep.
|
|
292
|
+
const height = await chainTracker.currentHeight()
|
|
293
|
+
if (this.blockHeight + 100 < height) {
|
|
294
|
+
return false
|
|
295
|
+
}
|
|
296
|
+
}
|
|
285
297
|
// Use the chain tracker to determine whether this is a valid merkle root at the given block height
|
|
286
298
|
return await chainTracker.isValidRootForHeight(root, this.blockHeight)
|
|
287
299
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ChainTracker from '../ChainTracker'
|
|
2
|
+
import MerklePath from '../../../dist/cjs/src/transaction/MerklePath.js'
|
|
2
3
|
import invalidBumps from './bump.invalid.vectors'
|
|
3
4
|
import validBumps from './bump.valid.vectors'
|
|
4
5
|
|
|
@@ -111,6 +112,15 @@ const BRC74TXID1 = '304e737fdfcb017a1a322e78b067ecebb5e07b44f0a36ed1f01264d2014f
|
|
|
111
112
|
const BRC74TXID2 = 'd888711d588021e588984e8278a2decf927298173a06737066e43f3e75534e00'
|
|
112
113
|
const BRC74TXID3 = '98c9c5dd79a18f40837061d5e0395ffb52e700a2689e641d19f053fc9619445e'
|
|
113
114
|
|
|
115
|
+
class FakeChainTracker implements ChainTracker {
|
|
116
|
+
async isValidRootForHeight (root: string, height: number): Promise<boolean> {
|
|
117
|
+
return root === 'd5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02' && height === 10000
|
|
118
|
+
}
|
|
119
|
+
async currentHeight (): Promise<number> {
|
|
120
|
+
return 10100
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
114
124
|
describe('MerklePath', () => {
|
|
115
125
|
it('Parses from hex', () => {
|
|
116
126
|
const path = MerklePath.fromHex(BRC74Hex)
|
|
@@ -129,7 +139,8 @@ describe('MerklePath', () => {
|
|
|
129
139
|
it('Verifies using a ChainTracker', async () => {
|
|
130
140
|
const path = new MerklePath(BRC74JSON.blockHeight, BRC74JSON.path)
|
|
131
141
|
const tracker = {
|
|
132
|
-
isValidRootForHeight: jest.fn((root, height) => root === BRC74Root && height === BRC74JSON.blockHeight)
|
|
142
|
+
isValidRootForHeight: jest.fn(async (root, height) => root === BRC74Root && height === BRC74JSON.blockHeight),
|
|
143
|
+
currentHeight: jest.fn(async () => 2029209)
|
|
133
144
|
}
|
|
134
145
|
const result = await path.verify(BRC74TXID1, tracker)
|
|
135
146
|
expect(result).toBe(true)
|
|
@@ -192,4 +203,14 @@ describe('MerklePath', () => {
|
|
|
192
203
|
it('Creates a valid MerklePath from a txid', () => {
|
|
193
204
|
expect(() => MerklePath.fromCoinbaseTxidAndHeight('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02', 1)).not.toThrowError()
|
|
194
205
|
})
|
|
206
|
+
it('Valid for Coinbase if currentHeight is more than 100 blocks prior', async () => {
|
|
207
|
+
const mp = MerklePath.fromCoinbaseTxidAndHeight('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02', 10000)
|
|
208
|
+
const isValid = await mp.verify('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02', new FakeChainTracker())
|
|
209
|
+
expect(isValid).toBe(true)
|
|
210
|
+
})
|
|
211
|
+
it('Invalid for Coinbase if currentheight is less than 100 blocks prior ', async () => {
|
|
212
|
+
const mp = MerklePath.fromCoinbaseTxidAndHeight('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02', 10099)
|
|
213
|
+
const isValid = await mp.verify('d5377a7aba0c0e0dbaef230f8917217b453484c83579e11a14c8299faa57ef02', new FakeChainTracker())
|
|
214
|
+
expect(isValid).toBe(false)
|
|
215
|
+
})
|
|
195
216
|
})
|
|
@@ -40,7 +40,7 @@ export default class WhatsOnChain implements ChainTracker {
|
|
|
40
40
|
async isValidRootForHeight (root: string, height: number): Promise<boolean> {
|
|
41
41
|
const requestOptions = {
|
|
42
42
|
method: 'GET',
|
|
43
|
-
headers: this.
|
|
43
|
+
headers: this.getHttpHeaders()
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
const response = await this.httpClient.request<WhatsOnChainBlockHeader>(`${this.URL}/block/${height}/header`, requestOptions)
|
|
@@ -54,7 +54,25 @@ export default class WhatsOnChain implements ChainTracker {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
async currentHeight (): Promise<number> {
|
|
58
|
+
try {
|
|
59
|
+
const requestOptions = {
|
|
60
|
+
method: 'GET',
|
|
61
|
+
headers: this.getHttpHeaders()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const response = await this.httpClient.request<{ height: number }>(`${this.URL}/block/headers`, requestOptions)
|
|
65
|
+
if (response.ok) {
|
|
66
|
+
return response.data[0].height
|
|
67
|
+
} else {
|
|
68
|
+
throw new Error(`Failed to get current height because of an error: ${JSON.stringify(response.data)} `)
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw new Error(`Failed to get current height because of an error: ${error?.message}`)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private getHttpHeaders () {
|
|
58
76
|
const headers: Record<string, string> = {
|
|
59
77
|
Accept: 'application/json'
|
|
60
78
|
}
|
|
@@ -29,7 +29,7 @@ describe('WhatsOnChain ChainTracker', () => {
|
|
|
29
29
|
it('should verify merkleroot successfully using Node.js https', async () => {
|
|
30
30
|
// Mocking Node.js https module
|
|
31
31
|
mockedHttps(successResponse)
|
|
32
|
-
|
|
32
|
+
if (global.window) global.window = {} as any
|
|
33
33
|
|
|
34
34
|
const chainTracker = new WhatsOnChain(network)
|
|
35
35
|
const response = await chainTracker.isValidRootForHeight(merkleroot, height)
|
|
@@ -87,6 +87,37 @@ describe('WhatsOnChain ChainTracker', () => {
|
|
|
87
87
|
await expect(chainTracker.isValidRootForHeight(merkleroot, height)).rejects.toThrow(/Failed to verify merkleroot for height \d+ because of an error: .*/)
|
|
88
88
|
})
|
|
89
89
|
|
|
90
|
+
it('should return the current height', async () => {
|
|
91
|
+
const mockFetch = mockedFetch({
|
|
92
|
+
status: 200,
|
|
93
|
+
data: [
|
|
94
|
+
{
|
|
95
|
+
hash: '00000000000000000af9958a79fd4dbac1b04a553cc00c80e108718561ccb5a5',
|
|
96
|
+
confirmations: 1,
|
|
97
|
+
size: 29605652,
|
|
98
|
+
height: 875904,
|
|
99
|
+
version: 704643072,
|
|
100
|
+
versionHex: '2a000000',
|
|
101
|
+
merkleroot: '8af5a2d4325ec30e30103b1f365c303d4b49d11e42d26b0d7e9b6866724392e9',
|
|
102
|
+
time: 1734667612,
|
|
103
|
+
mediantime: 1734663717,
|
|
104
|
+
nonce: 157007350,
|
|
105
|
+
bits: '180f2b74',
|
|
106
|
+
difficulty: 72479484799.59058,
|
|
107
|
+
chainwork: '00000000000000000000000000000000000000000160c2f41c8793b90f4500dd',
|
|
108
|
+
previousblockhash: '00000000000000000128f312a7c62ef5f9a91a3f845a4464d10cfbaaecd233a0',
|
|
109
|
+
nextblockhash: '',
|
|
110
|
+
nTx: 0,
|
|
111
|
+
num_tx: 167567
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const chainTracker = new WhatsOnChain(network, { httpClient: new FetchHttpClient(mockFetch) })
|
|
117
|
+
|
|
118
|
+
await expect(await chainTracker.currentHeight()).toBe(875904)
|
|
119
|
+
})
|
|
120
|
+
|
|
90
121
|
function mockedFetch (response) {
|
|
91
122
|
return jest.fn().mockResolvedValue({
|
|
92
123
|
ok: response.status === 200,
|