Skip to content

Commit 43261a7

Browse files
sgbettclaude
andcommitted
docs: add v0.10.0 release plan (Chronicle + Arcade + directory restructure)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 72af785 commit 43261a7

File tree

1 file changed

+254
-0
lines changed

1 file changed

+254
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# Plan: bsv-sdk 0.10.0 — Chronicle + Arcade + Directory Restructure
2+
3+
**Previous release**: bsv-sdk 0.9.0 (cross-SDK compliance rollout)
4+
**Plan**: `.claude/plans/20260408-v090-compliance-rollout.md`
5+
6+
## Context
7+
8+
The tenth minor release bundles three pillars into a cohesive milestone:
9+
10+
1. **Directory restructure** (cosmetic, already merged — PR #327)
11+
2. **Chronicle opcode support** (big ticket — implementing BSV's restored opcodes)
12+
3. **Arcade switch** (infrastructure — Chaintracks chain tracker, default broadcaster, batch broadcast)
13+
14+
0.9.0 shipped "raise first" fail-safes for 8 Chronicle opcodes (F7.1/F7.2). This release replaces those raises with correct implementations. The Arcade work adds a proper chain tracker for SPV verification — the missing link that makes `Beef#verify(chain_tracker)` actually useful out of the box.
15+
16+
---
17+
18+
## Pillar 1: Directory Restructure (DONE)
19+
20+
PR #327 merged. Files now live under `gem/bsv-sdk/`, `gem/bsv-wallet/`, `gem/bsv-attest/`. Part of the release narrative; no further work needed.
21+
22+
---
23+
24+
## Pillar 2: Chronicle Opcodes
25+
26+
### Opcodes to implement
27+
28+
| Opcode | Byte | Category | Semantics | Current state |
29+
|--------|------|----------|-----------|---------------|
30+
| OP_SUBSTR | 0xb3 | splice | Pop len, offset, buf → push `buf[offset, len]` | raises UnimplementedOpcode |
31+
| OP_LEFT | 0xb4 | splice | Pop len, buf → push first len bytes | raises UnimplementedOpcode |
32+
| OP_RIGHT | 0xb5 | splice | Pop len, buf → push last len bytes | raises UnimplementedOpcode |
33+
| OP_LSHIFTNUM | 0xb6 | arithmetic | Pop bits, n → push `n << bits` (arithmetic) | raises UnimplementedOpcode |
34+
| OP_RSHIFTNUM | 0xb7 | arithmetic | Pop bits, n → push `n >> bits` (sign-preserving) | raises UnimplementedOpcode |
35+
| OP_VER | 0x62 | flow control | Push 4-byte LE tx version | raises UnimplementedOpcode |
36+
| OP_VERIF | 0x65 | flow control | Pop expected version; match → conditional true | raises UnimplementedOpcode |
37+
| OP_VERNOTIF | 0x66 | flow control | Pop expected version; no match → conditional true | raises UnimplementedOpcode |
38+
| OP_2MUL | 0x8d | arithmetic | Pop n → push n×2 | raises DisabledOpcode |
39+
| OP_2DIV | 0x8e | arithmetic | Pop n → push n÷2 (truncated toward zero) | raises DisabledOpcode |
40+
41+
### Reference implementations
42+
43+
- **Go SDK**: `go-sdk/script/interpreter/chronicle_opcodes_test.go` (~609 lines, ~50 test cases)
44+
- **TS SDK**: `ts-sdk/src/script/Spend.ts` (implementations), `ts-sdk/src/script/__tests/ChronicleOpcodes.test.ts` (~650 lines, ~80 test cases)
45+
46+
### File changes
47+
48+
**`gem/bsv-sdk/lib/bsv/script/interpreter/error.rb`**
49+
- Add `MISSING_TX_CONTEXT = :missing_tx_context` error code (for OP_VER without tx)
50+
51+
**`gem/bsv-sdk/lib/bsv/script/interpreter/operations/splice.rb`**
52+
- Add `op_substr`: pop len (int), offset (int), data (bytes). Validate `offset >= 0 && offset < size && len >= 0 && len <= size - offset`. Push `data.byteslice(offset, len)`. Raise `INVALID_INPUT_LENGTH` on out-of-range.
53+
- Add `op_left`: pop len (int), data (bytes). Validate `len >= 0 && len <= size`. Push `data.byteslice(0, len)`. Raise on out-of-range. (Matches TS/Go — does NOT clamp to size.)
54+
- Add `op_right`: pop len (int), data (bytes). Validate `len >= 0 && len <= size`. Push `data.byteslice(size - len, len)`. Raise on out-of-range.
55+
56+
**`gem/bsv-sdk/lib/bsv/script/interpreter/operations/arithmetic.rb`**
57+
- Add `op_2mul`: pop n (int), push `n * ScriptNumber.new(2)`. Replaces `op_disabled`.
58+
- Add `op_2div`: pop n (int), push `n / ScriptNumber.new(2)`. Replaces `op_disabled`.
59+
- Add `op_lshiftnum`: pop bits (int), pop value (int). Validate bits >= 0. Push `ScriptNumber.new(value.value << bits.to_i32)`. Stack memory cap catches oversized results.
60+
- Add `op_rshiftnum`: pop bits (int), pop value (int). Validate bits >= 0. Push `ScriptNumber.new(value.value >> bits.to_i32)`. Ruby's `Integer#>>` is arithmetic (sign-preserving), which is correct.
61+
- Remove `op_disabled` method (dead code after OP_2MUL/OP_2DIV restoration).
62+
63+
**`gem/bsv-sdk/lib/bsv/script/interpreter/operations/flow_control.rb`**
64+
- Add `op_ver`: raise `MISSING_TX_CONTEXT` if `@tx.nil?`. Encode `@tx.version` as 4-byte LE (`[@tx.version].pack('V')`). Push bytes.
65+
- Add `op_verif`: conditional opcode. In executing branch: pop bytes, compare raw 4-byte LE against `@tx.version`. Match → push `:true` to `@cond_stack`. Mismatch or wrong size → push `:false`. In non-executing branch: push `:false` (nesting tracking, no stack pop). Push `false` to `@else_stack`.
66+
- Add `op_vernotif`: same as `op_verif` but inverted — match → `:false`, mismatch → `:true`.
67+
- Add private helper `tx_version_bytes` returning `[@tx.version].pack('V')`.
68+
- `op_unimplemented` becomes dead code — leave for safety but no dispatch routes to it.
69+
70+
**`gem/bsv-sdk/lib/bsv/script/interpreter/interpreter.rb`**
71+
- Add `Opcodes::OP_VERIF, Opcodes::OP_VERNOTIF` to `CONDITIONAL_OPCODES` array (they must be dispatched in non-executing branches for nesting tracking).
72+
- Replace the combined unimplemented `when` clause (lines 185-188) with individual dispatch:
73+
- `when Opcodes::OP_VER then op_ver`
74+
- `when Opcodes::OP_VERIF then op_verif`
75+
- `when Opcodes::OP_VERNOTIF then op_vernotif`
76+
- `when Opcodes::OP_SUBSTR then op_substr`
77+
- `when Opcodes::OP_LEFT then op_left`
78+
- `when Opcodes::OP_RIGHT then op_right`
79+
- `when Opcodes::OP_LSHIFTNUM then op_lshiftnum`
80+
- `when Opcodes::OP_RSHIFTNUM then op_rshiftnum`
81+
- Replace `OP_2MUL, OP_2DIV → op_disabled` (line 229-230) with:
82+
- `when Opcodes::OP_2MUL then op_2mul`
83+
- `when Opcodes::OP_2DIV then op_2div`
84+
85+
### Test file
86+
87+
**`spec/bsv/script/interpreter/operations/chronicle_spec.rb`** (new)
88+
89+
~55 test cases ported from Go and TS reference tests, covering:
90+
91+
- **OP_SUBSTR**: "HelloWorld" extraction, single char, full string, out-of-range errors, negative offset/len, empty data
92+
- **OP_LEFT/OP_RIGHT**: first/last N bytes, zero bytes → empty, full length → whole string, exceeds size → error
93+
- **OP_LSHIFTNUM/OP_RSHIFTNUM**: 1<<2=4, identity (shift by 0), shift to zero, negative number sign preservation, large shifts
94+
- **OP_2MUL/OP_2DIV**: basic multiplication/division, zero, negative, truncation
95+
- **OP_VER**: pushes 4-byte LE version 1, version 2, SIZE check = 4, no tx context → MISSING_TX_CONTEXT
96+
- **OP_VERIF**: matching version → true branch, non-matching → else branch, non-4-byte item → false, nested conditionals
97+
- **OP_VERNOTIF**: inverse of OP_VERIF
98+
99+
Also update:
100+
- `spec/bsv/script/interpreter/hardening_spec.rb` — remove or update the "raise UnimplementedOpcode" tests (they should now test correct execution instead)
101+
- `spec/bsv/script/interpreter/operations/flow_control_spec.rb` — update OP_VER/VERIF/VERNOTIF tests
102+
- `spec/bsv/script/interpreter/operations/arithmetic_spec.rb` — remove any `op_disabled` tests for OP_2MUL/OP_2DIV
103+
104+
---
105+
106+
## Pillar 3: Arcade Switch
107+
108+
### New: Chaintracks chain tracker
109+
110+
**`gem/bsv-sdk/lib/bsv/transaction/chain_trackers/chaintracks.rb`** (new, ~90 lines)
111+
112+
Follows `WhatsOnChain` pattern:
113+
114+
```ruby
115+
class Chaintracks < ChainTracker
116+
MAINNET_URL = 'https://arcade.gorillapool.io'
117+
TESTNET_URL = 'https://testnet.arcade.gorillapool.io'
118+
119+
def initialize(url: MAINNET_URL, api_key: nil, http_client: nil)
120+
def valid_root_for_height?(root, height)
121+
# GET /chaintracks/v2/header/height/{height}
122+
# Compare response body 'merkleRoot' with root (case-insensitive)
123+
end
124+
def current_height
125+
# GET /chaintracks/v2/tip
126+
# Return response body 'height'
127+
end
128+
end
129+
```
130+
131+
Uses Arcade's Chaintracks v2 API (not the legacy v1 or the /api/v1/chain/merkleroot/verify pattern from some reference SDKs). The v2 endpoints return full block headers with `merkleRoot` field — simpler and more direct.
132+
133+
**`gem/bsv-sdk/lib/bsv/transaction/chain_trackers.rb`** (modify)
134+
- Add `autoload :Chaintracks, 'bsv/transaction/chain_trackers/chaintracks'`
135+
- Add `self.default(testnet: false, api_key: nil)` factory returning a `Chaintracks` instance
136+
137+
### Modified: ARC broadcaster
138+
139+
**`gem/bsv-sdk/lib/bsv/network/arc.rb`** (modify)
140+
141+
Three additions:
142+
143+
1. **`self.default(testnet: false, **opts)`** — class method factory
144+
- Mainnet: `https://arc.gorillapool.io`
145+
- Testnet: `https://testnet.arc.gorillapool.io`
146+
- Matches TS/Go/Py default broadcaster pattern
147+
148+
2. **`broadcast_many(txs, wait_for: nil, skip_fee_validation: nil, skip_script_validation: nil)`** — batch broadcast
149+
- POST to `{@url}/v1/txs`
150+
- Body: JSON array of `{ rawTx: hex }` (each tx independently EF-fallback encoded)
151+
- Returns array of `BroadcastResponse`
152+
- Handles per-tx failure detection (same rejected-status logic as single broadcast)
153+
154+
3. **Skip-validation keyword args** on `broadcast` and `broadcast_many`:
155+
- `skip_fee_validation: true``X-SkipFeeValidation: true` header
156+
- `skip_script_validation: true``X-SkipScriptValidation: true` header
157+
- Extract header building into private `build_request_headers(wait_for:, skip_fee_validation:, skip_script_validation:)` to avoid duplication
158+
159+
### Test files
160+
161+
**`spec/bsv/transaction/chain_trackers/chaintracks_spec.rb`** (new, ~100 lines)
162+
- `valid_root_for_height?` true on match, false on mismatch, false on 404, raises ChainProviderError on 5xx
163+
- `current_height` returns height from tip, raises on failure
164+
- API key authentication header
165+
- URL construction for mainnet/testnet
166+
- `.default` factory method
167+
168+
**`spec/bsv/network/arc_spec.rb`** (modify)
169+
- `describe '.default'` — mainnet/testnet URL construction, option passthrough
170+
- `describe '#broadcast_many'` — batch POST to `/v1/txs`, JSON body format, mixed success/failure, EF fallback
171+
- `describe 'skip-validation headers'` — X-SkipFeeValidation and X-SkipScriptValidation set when requested
172+
173+
### LivePolicy verification
174+
175+
`LivePolicy` already defaults to `https://arc.gorillapool.io` and fetches `/v1/policy`. Arcade serves this endpoint for ARC compatibility. Verify during integration testing that the response shape parses correctly — the Arcade policy response has `miningFeeBytes` and `miningFeeSatoshis` at root level, while the existing `extract_rate` method looks for `policy.fees.miningFee.{satoshis,bytes}`. May need to add a fallback extraction path for the flat structure.
176+
177+
---
178+
179+
## Sequencing
180+
181+
```
182+
Phase 0: Verify 0.9.0 is clean (all specs pass, master is stable)
183+
184+
Phase 1: Chronicle opcodes (Pillar 2)
185+
1a. Error code addition (error.rb)
186+
1b. Splice ops: op_substr, op_left, op_right (splice.rb) ─┐
187+
1c. Arithmetic ops: op_2mul, op_2div, op_lshiftnum, │ parallel
188+
op_rshiftnum (arithmetic.rb) ─┘
189+
1d. Version ops: op_ver, op_verif, op_vernotif (flow_control.rb)
190+
1e. Dispatch table rewiring + CONDITIONAL_OPCODES (interpreter.rb)
191+
1f. Chronicle test suite (chronicle_spec.rb) + update existing specs
192+
193+
Phase 2: Arcade switch (Pillar 3) — can run in parallel with Phase 1
194+
2a. Chaintracks chain tracker (chaintracks.rb + spec)
195+
2b. ARC default/broadcast_many/skip-validation (arc.rb + spec)
196+
2c. ChainTrackers.default factory + LivePolicy verification
197+
198+
Phase 3: Release
199+
3a. Update version.rb to 0.10.0
200+
3b. CHANGELOG.md with Chronicle and Arcade sections
201+
3c. Full test suite pass + RuboCop
202+
```
203+
204+
Phases 1 and 2 are independent — no cross-dependencies.
205+
206+
---
207+
208+
## HLR structure
209+
210+
Two HLRs (Pillar 1 is already done):
211+
212+
- **HLR: Chronicle opcode implementation** — F7.1/F7.2 full semantics. Label `project:hlr`. Covers all 10 opcodes + test suite.
213+
- **HLR: Arcade integration** — Chaintracks chain tracker, default broadcaster, batch broadcast. Label `project:hlr`.
214+
215+
---
216+
217+
## Release criteria
218+
219+
- [ ] All existing specs pass (zero regressions)
220+
- [ ] All 10 Chronicle opcodes execute correctly (~55 new test cases)
221+
- [ ] Chaintracks chain tracker works with mock HTTP client (~15 new test cases)
222+
- [ ] ARC.default, broadcast_many, skip-validation work (~10 new test cases)
223+
- [ ] No `op_unimplemented` calls remain in dispatch table
224+
- [ ] No `op_disabled` calls remain in dispatch table
225+
- [ ] `CONDITIONAL_OPCODES` includes OP_VERIF and OP_VERNOTIF
226+
- [ ] RuboCop clean
227+
- [ ] CHANGELOG updated
228+
- [ ] Version = 0.10.0
229+
230+
---
231+
232+
## Key files
233+
234+
### Chronicle (modify)
235+
- `gem/bsv-sdk/lib/bsv/script/interpreter/error.rb`
236+
- `gem/bsv-sdk/lib/bsv/script/interpreter/interpreter.rb`
237+
- `gem/bsv-sdk/lib/bsv/script/interpreter/operations/flow_control.rb`
238+
- `gem/bsv-sdk/lib/bsv/script/interpreter/operations/splice.rb`
239+
- `gem/bsv-sdk/lib/bsv/script/interpreter/operations/arithmetic.rb`
240+
241+
### Chronicle (new)
242+
- `spec/bsv/script/interpreter/operations/chronicle_spec.rb`
243+
244+
### Arcade (modify)
245+
- `gem/bsv-sdk/lib/bsv/network/arc.rb`
246+
- `gem/bsv-sdk/lib/bsv/transaction/chain_trackers.rb`
247+
248+
### Arcade (new)
249+
- `gem/bsv-sdk/lib/bsv/transaction/chain_trackers/chaintracks.rb`
250+
- `spec/bsv/transaction/chain_trackers/chaintracks_spec.rb`
251+
252+
### Release
253+
- `gem/bsv-sdk/lib/bsv/version.rb`
254+
- `CHANGELOG.md`

0 commit comments

Comments
 (0)