Skip to content

Commit 5cb995c

Browse files
authored
Merge pull request #372 from bsv-blockchain/fix/sequence-defaults
[ Fix ] sequence defaults
2 parents c686f23 + 19374b6 commit 5cb995c

File tree

12 files changed

+186
-48
lines changed

12 files changed

+186
-48
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file. The format
55
## Table of Contents
66

77
- [Unreleased](#unreleased)
8+
- [1.8.11 - 2025-10-30](#1811---2025-10-30)
9+
- [1.8.10 - 2025-10-28](#1810---2025-10-28)
810
- [1.8.9 - 2025-10-27](#189---2025-10-27)
911
- [1.8.8 - 2025-10-22](#188---2025-10-22)
1012
- [1.8.7 - 2025-10-22](#187---2025-10-22)
@@ -169,6 +171,14 @@ All notable changes to this project will be documented in this file. The format
169171

170172
---
171173

174+
### [1.8.11] - 2025-10-30
175+
176+
### Fixed
177+
178+
- **Transaction**: Default sequence value changed from 0 to 0xffffffff (max sequence) for transaction inputs in serialization and script validation. This aligns with the default set within the preimage calculation. This change ensures that the transaction is marked as final by default, which is the expected behavior for most transactions. addInput had this build in already, this extends that to the constructor approach.
179+
180+
---
181+
172182
### [1.8.10] - 2025-10-28
173183

174184
### Added

docs/reference/wallet.md

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3426,7 +3426,7 @@ static unknownToJson(error: any): string
34263426

34273427
Returns
34283428

3429-
stringified JSON representation of the error such that it can be desirialized to a WalletError.
3429+
stringified JSON representation of the error such that it can be deserialized to a WalletError.
34303430

34313431
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
34323432

@@ -4562,17 +4562,17 @@ Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](
45624562

45634563
| | | |
45644564
| --- | --- | --- |
4565-
| [isHexString](#function-ishexstring) | [validateDiscoverByAttributesArgs](#function-validatediscoverbyattributesargs) | [validateOutpointString](#function-validateoutpointstring) |
4566-
| [parseWalletOutpoint](#function-parsewalletoutpoint) | [validateDiscoverByIdentityKeyArgs](#function-validatediscoverbyidentitykeyargs) | [validatePositiveIntegerOrZero](#function-validatepositiveintegerorzero) |
4567-
| [toOriginHeader](#function-tooriginheader) | [validateInteger](#function-validateinteger) | [validateProveCertificateArgs](#function-validateprovecertificateargs) |
4568-
| [validateAbortActionArgs](#function-validateabortactionargs) | [validateInternalizeActionArgs](#function-validateinternalizeactionargs) | [validateRelinquishCertificateArgs](#function-validaterelinquishcertificateargs) |
4569-
| [validateAcquireDirectCertificateArgs](#function-validateacquiredirectcertificateargs) | [validateInternalizeOutput](#function-validateinternalizeoutput) | [validateRelinquishOutputArgs](#function-validaterelinquishoutputargs) |
4570-
| [validateAcquireIssuanceCertificateArgs](#function-validateacquireissuancecertificateargs) | [validateListActionsArgs](#function-validatelistactionsargs) | [validateSatoshis](#function-validatesatoshis) |
4565+
| [isHexString](#function-ishexstring) | [validateCreateActionOutput](#function-validatecreateactionoutput) | [validateOriginator](#function-validateoriginator) |
4566+
| [parseWalletOutpoint](#function-parsewalletoutpoint) | [validateDiscoverByAttributesArgs](#function-validatediscoverbyattributesargs) | [validateOutpointString](#function-validateoutpointstring) |
4567+
| [toOriginHeader](#function-tooriginheader) | [validateDiscoverByIdentityKeyArgs](#function-validatediscoverbyidentitykeyargs) | [validatePositiveIntegerOrZero](#function-validatepositiveintegerorzero) |
4568+
| [validateAbortActionArgs](#function-validateabortactionargs) | [validateInteger](#function-validateinteger) | [validateProveCertificateArgs](#function-validateprovecertificateargs) |
4569+
| [validateAcquireDirectCertificateArgs](#function-validateacquiredirectcertificateargs) | [validateInternalizeActionArgs](#function-validateinternalizeactionargs) | [validateRelinquishCertificateArgs](#function-validaterelinquishcertificateargs) |
4570+
| [validateAcquireIssuanceCertificateArgs](#function-validateacquireissuancecertificateargs) | [validateInternalizeOutput](#function-validateinternalizeoutput) | [validateRelinquishOutputArgs](#function-validaterelinquishoutputargs) |
4571+
| [validateBase64String](#function-validatebase64string) | [validateListActionsArgs](#function-validatelistactionsargs) | [validateSatoshis](#function-validatesatoshis) |
45714572
| [validateBasketInsertion](#function-validatebasketinsertion) | [validateListCertificatesArgs](#function-validatelistcertificatesargs) | [validateSignActionArgs](#function-validatesignactionargs) |
45724573
| [validateCreateActionArgs](#function-validatecreateactionargs) | [validateListOutputsArgs](#function-validatelistoutputsargs) | [validateSignActionOptions](#function-validatesignactionoptions) |
45734574
| [validateCreateActionInput](#function-validatecreateactioninput) | [validateOptionalInteger](#function-validateoptionalinteger) | [validateStringLength](#function-validatestringlength) |
45744575
| [validateCreateActionOptions](#function-validatecreateactionoptions) | [validateOptionalOutpointString](#function-validateoptionaloutpointstring) | [validateWalletPayment](#function-validatewalletpayment) |
4575-
| [validateCreateActionOutput](#function-validatecreateactionoutput) | [validateOriginator](#function-validateoriginator) | |
45764576

45774577
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
45784578

@@ -4692,6 +4692,36 @@ when args contain fields invalid for issuance
46924692

46934693
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
46944694

4695+
---
4696+
### Function: validateBase64String
4697+
4698+
Validate a Base64 string (structure and decoded size).
4699+
4700+
```ts
4701+
export function validateBase64String(s: string, name: string, min?: number, max?: number): string
4702+
```
4703+
4704+
Returns
4705+
4706+
validated base64 string
4707+
4708+
Argument Details
4709+
4710+
+ **s**
4711+
+ base64 string
4712+
+ **name**
4713+
+ parameter name used in error messages
4714+
+ **min**
4715+
+ optional minimum decoded byte length
4716+
+ **max**
4717+
+ optional maximum decoded byte length
4718+
4719+
Throws
4720+
4721+
WERR_INVALID_PARAMETER when invalid
4722+
4723+
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes), [Functions](#functions), [Types](#types), [Enums](#enums), [Variables](#variables)
4724+
46954725
---
46964726
### Function: validateBasketInsertion
46974727

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@bsv/sdk",
3-
"version": "1.8.10",
3+
"version": "1.8.11",
44
"type": "module",
55
"description": "BSV Blockchain Software Development Kit",
66
"main": "dist/cjs/mod.js",

src/primitives/__tests/utils.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import {
77
fromBase58,
88
toBase58,
99
fromBase58Check,
10-
toBase58Check
10+
toBase58Check,
11+
verifyNotNull
1112
} from '../../primitives/utils'
1213

1314
describe('utils', () => {
@@ -207,3 +208,28 @@ describe('utils', () => {
207208
expect(toArray(input)).toEqual(expected)
208209
})
209210
})
211+
212+
describe('verifyNotNull', () => {
213+
it('should return the value if it is not null or undefined', () => {
214+
expect(verifyNotNull(42)).toBe(42)
215+
expect(verifyNotNull('hello')).toBe('hello')
216+
expect(verifyNotNull({})).toEqual({})
217+
expect(verifyNotNull([])).toEqual([])
218+
})
219+
220+
it('should throw an error with default message if value is null', () => {
221+
expect(() => verifyNotNull(null)).toThrow('Expected a valid value, but got undefined or null.')
222+
})
223+
224+
it('should throw an error with default message if value is undefined', () => {
225+
expect(() => verifyNotNull(undefined)).toThrow('Expected a valid value, but got undefined or null.')
226+
})
227+
228+
it('should throw an error with custom message if value is null', () => {
229+
expect(() => verifyNotNull(null, 'Custom error')).toThrow('Custom error')
230+
})
231+
232+
it('should throw an error with custom message if value is undefined', () => {
233+
expect(() => verifyNotNull(undefined, 'Another custom error')).toThrow('Another custom error')
234+
})
235+
})

src/primitives/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,3 +819,20 @@ export const minimallyEncode = (buf: number[]): number[] => {
819819

820820
const OverflowInt64 = new BigNumber(2).pow(new BigNumber(63))
821821
const OverflowUint64 = new BigNumber(2).pow(new BigNumber(64))
822+
823+
/**
824+
* Verifies that a value is not null or undefined, throwing an error if it is.
825+
*
826+
* @template T - The type of the value being verified
827+
* @param {T | undefined | null} value - The value to verify
828+
* @param {string} errorMessage - The error message to throw if the value is null or undefined
829+
* @returns {T} - The verified value
830+
* @throws {Error} - If the value is null or undefined
831+
*
832+
* @example
833+
* const myValue = verifyNotNull(someValue, 'someValue must be defined')
834+
*/
835+
export function verifyNotNull<T> (value: T | undefined | null, errorMessage: string = 'Expected a valid value, but got undefined or null.'): T {
836+
if (value == null) throw new Error(errorMessage)
837+
return value
838+
}

src/script/__tests/Spend.test.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,30 @@ import RPuzzle from '../../script/templates/RPuzzle'
77
import Transaction from '../../transaction/Transaction'
88
import LockingScript from '../../script/LockingScript'
99
import UnlockingScript from '../../script/UnlockingScript'
10-
10+
import MerklePath from '../../transaction/MerklePath'
11+
import ChainTracker from '../../transaction/ChainTracker'
1112
import spendValid from './spend.valid.vectors'
13+
import Script from '../../script/Script'
14+
15+
export class MockChain implements ChainTracker {
16+
mock: { blockheaders: string[] }
17+
18+
constructor(mock: { blockheaders: string[] }) {
19+
this.mock = mock
20+
}
21+
22+
addBlock(merkleRoot: string) {
23+
this.mock.blockheaders.push(merkleRoot)
24+
}
25+
26+
async isValidRootForHeight(root: string, height: number): Promise<boolean> {
27+
return this.mock.blockheaders[height] === root
28+
}
29+
30+
async currentHeight(): Promise<number> {
31+
return this.mock.blockheaders.length
32+
}
33+
}
1234

1335
describe('Spend', () => {
1436
it('Successfully validates a P2PKH spend', async () => {
@@ -368,4 +390,55 @@ describe('Spend', () => {
368390
expect(spend.validate()).toBe(true)
369391
})
370392
}
393+
394+
it('Successfully validates a spend where sequence is set to undefined', async () => {
395+
const sourceTransaction = new Transaction(
396+
1,
397+
[{
398+
sourceTXID: '0000000000000000000000000000000000000000000000000000000000000000',
399+
sourceOutputIndex: 0,
400+
unlockingScript: Script.fromASM('OP_TRUE'),
401+
sequence: 0xffffffff
402+
}],
403+
[
404+
{
405+
lockingScript: Script.fromASM('OP_NOP'),
406+
satoshis: 2
407+
}
408+
],
409+
0
410+
)
411+
const txid = sourceTransaction.id('hex')
412+
sourceTransaction.merklePath = MerklePath.fromCoinbaseTxidAndHeight(txid, 0)
413+
const chain = new MockChain({ blockheaders: [] })
414+
chain.addBlock(txid)
415+
416+
const spendTx = new Transaction(
417+
1,
418+
[
419+
{
420+
unlockingScript: Script.fromASM('OP_TRUE'),
421+
sourceTransaction,
422+
sourceOutputIndex: 0
423+
}
424+
],
425+
[{
426+
lockingScript: Script.fromASM('OP_NOP'),
427+
satoshis: 1
428+
}],
429+
0
430+
)
431+
432+
const valid = await spendTx.verify(chain)
433+
434+
expect(valid).toBe(true)
435+
436+
const b = spendTx.toBinary()
437+
const t = Transaction.fromBinary(b)
438+
expect(t.inputs[0].sequence).toBe(0xffffffff)
439+
440+
const b2 = spendTx.toEF()
441+
const t2 = Transaction.fromEF(b2)
442+
expect(t2.inputs[0].sequence).toBe(0xffffffff)
443+
})
371444
})

src/script/__tests/SpendComplex.test.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Script from '../../script/Script'
22
import Spend from '../../script/Spend'
33
import Transaction from '../../transaction/Transaction'
4+
import { verifyNotNull } from '../../primitives/utils'
45

56
describe('SpendComplex', () => {
67
it('complex unlock script validation', () => {
@@ -19,11 +20,6 @@ describe('SpendComplex', () => {
1920
})
2021
})
2122

22-
function verifyTruthy<T> (v: T | undefined): T {
23-
if (v == null) throw new Error('must have value')
24-
return v
25-
}
26-
2723
export function validateUnlockScript (
2824
spendingRawTx: string,
2925
vin: number,
@@ -34,16 +30,16 @@ export function validateUnlockScript (
3430
const ls = Script.fromHex(lockingScript)
3531

3632
const spend = new Spend({
37-
sourceTXID: verifyTruthy(spendingTx.inputs[vin].sourceTXID),
38-
sourceOutputIndex: verifyTruthy(spendingTx.inputs[vin].sourceOutputIndex),
33+
sourceTXID: verifyNotNull(spendingTx.inputs[vin].sourceTXID, 'sourceTXID must have value'),
34+
sourceOutputIndex: verifyNotNull(spendingTx.inputs[vin].sourceOutputIndex, 'sourceOutputIndex must have value'),
3935
sourceSatoshis: amount,
4036
lockingScript: ls,
4137
transactionVersion: spendingTx.version,
4238
otherInputs: spendingTx.inputs.filter((v, i) => i !== vin),
4339
inputIndex: vin,
44-
unlockingScript: verifyTruthy(spendingTx.inputs[vin].unlockingScript),
40+
unlockingScript: verifyNotNull(spendingTx.inputs[vin].unlockingScript, 'unlockingScript must have value'),
4541
outputs: spendingTx.outputs,
46-
inputSequence: verifyTruthy(spendingTx.inputs[vin].sequence),
42+
inputSequence: verifyNotNull(spendingTx.inputs[vin].sequence, 'sequence must have value'),
4743
lockTime: spendingTx.lockTime
4844
})
4945

src/script/templates/P2PKH.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import OP from '../OP.js'
22
import ScriptTemplate from '../ScriptTemplate.js'
3-
import { fromBase58Check } from '../../primitives/utils.js'
3+
import { fromBase58Check, verifyNotNull } from '../../primitives/utils.js'
44
import LockingScript from '../LockingScript.js'
55
import UnlockingScript from '../UnlockingScript.js'
66
import Transaction from '../../transaction/Transaction.js'
@@ -9,11 +9,6 @@ import TransactionSignature from '../../primitives/TransactionSignature.js'
99
import { sha256 } from '../../primitives/Hash.js'
1010
import Script from '../Script.js'
1111

12-
function verifyTruthy<T>(v: T | undefined): T {
13-
if (v == null) throw new Error('must have value')
14-
return v
15-
}
16-
1712
/**
1813
* P2PKH (Pay To Public Key Hash) class implementing ScriptTemplate.
1914
*
@@ -125,13 +120,13 @@ export default class P2PKH implements ScriptTemplate {
125120

126121
const preimage = TransactionSignature.format({
127122
sourceTXID,
128-
sourceOutputIndex: verifyTruthy(input.sourceOutputIndex),
123+
sourceOutputIndex: verifyNotNull(input.sourceOutputIndex, 'input.sourceOutputIndex must have value'),
129124
sourceSatoshis,
130125
transactionVersion: tx.version,
131126
otherInputs,
132127
inputIndex,
133128
outputs: tx.outputs,
134-
inputSequence: verifyTruthy(input.sequence),
129+
inputSequence: verifyNotNull(input.sequence, 'input.sequence must have value'),
135130
subscript: lockingScript,
136131
lockTime: tx.lockTime,
137132
scope: signatureScope

src/script/templates/PushDrop.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@ import {
99
import { WalletInterface } from '../../wallet/Wallet.interfaces.js'
1010
import { Transaction } from '../../transaction/index.js'
1111
import { WalletProtocol } from '../../wallet/Wallet.interfaces.js'
12-
13-
function verifyTruthy<T>(v: T | undefined): T {
14-
if (v == null) throw new Error('must have value')
15-
return v
16-
}
12+
import { verifyNotNull } from '../../primitives/utils.js'
1713

1814
/**
1915
* For a given piece of data to push onto the stack in script, creates the correct minimally-encoded script chunk,
@@ -71,7 +67,7 @@ export default class PushDrop implements ScriptTemplate {
7167
fields: number[][]
7268
} {
7369
const lockingPublicKey = PublicKey.fromString(
74-
Utils.toHex(verifyTruthy(script.chunks[0].data)) // ✅ Ensure not undefined
70+
Utils.toHex(verifyNotNull(script.chunks[0].data, 'script.chunks[0].data must have value'))
7571
)
7672

7773
const fields: number[][] = []
@@ -249,7 +245,7 @@ export default class PushDrop implements ScriptTemplate {
249245

250246
const preimage = TransactionSignature.format({
251247
sourceTXID,
252-
sourceOutputIndex: verifyTruthy(input.sourceOutputIndex),
248+
sourceOutputIndex: verifyNotNull(input.sourceOutputIndex, 'input.sourceOutputIndex must have value'),
253249
sourceSatoshis,
254250
transactionVersion: tx.version,
255251
otherInputs,

0 commit comments

Comments
 (0)