bsv-sdk 0.24.0 → 0.25.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/lib/bsv/kv_store/entry.rb +15 -0
- data/lib/bsv/kv_store/global.rb +210 -0
- data/lib/bsv/kv_store/interpreter.rb +109 -0
- data/lib/bsv/kv_store/token.rb +10 -0
- data/lib/bsv/kv_store.rb +10 -0
- data/lib/bsv/mcp/tools/helpers.rb +3 -3
- data/lib/bsv/overlay/admin_token_template.rb +2 -2
- data/lib/bsv/overlay/historian.rb +118 -0
- data/lib/bsv/overlay/topic_broadcaster.rb +1 -1
- data/lib/bsv/overlay.rb +1 -0
- data/lib/bsv/primitives/ecies.rb +12 -3
- data/lib/bsv/registry/client.rb +43 -1
- data/lib/bsv/script/bip276.rb +143 -0
- data/lib/bsv/script/push_drop_template.rb +2 -2
- data/lib/bsv/script.rb +1 -0
- data/lib/bsv/storage/downloader.rb +174 -0
- data/lib/bsv/storage/errors.rb +8 -0
- data/lib/bsv/storage/utils.rb +90 -0
- data/lib/bsv/storage.rb +16 -0
- data/lib/bsv/transaction/beef.rb +168 -14
- data/lib/bsv/transaction/beef_party.rb +119 -0
- data/lib/bsv/transaction/chain_tracker.rb +1 -1
- data/lib/bsv/transaction/fee_model.rb +1 -1
- data/lib/bsv/transaction/fee_models/live_policy.rb +1 -1
- data/lib/bsv/transaction/fee_models/satoshis_per_kilobyte.rb +1 -1
- data/lib/bsv/transaction/merkle_path.rb +1 -1
- data/lib/bsv/transaction/p2pkh.rb +1 -1
- data/lib/bsv/transaction/transaction_input.rb +1 -1
- data/lib/bsv/transaction/tx.rb +11 -11
- data/lib/bsv/transaction/unlocking_script_template.rb +2 -2
- data/lib/bsv/transaction.rb +1 -0
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv-sdk.rb +2 -0
- metadata +13 -1
data/lib/bsv/transaction/beef.rb
CHANGED
|
@@ -72,10 +72,10 @@ module BSV
|
|
|
72
72
|
|
|
73
73
|
# A BEEF entry containing a raw transaction without a merkle proof.
|
|
74
74
|
class RawTxEntry < BeefTx
|
|
75
|
-
# @return [Tx] the transaction
|
|
75
|
+
# @return [Transaction::Tx] the transaction
|
|
76
76
|
attr_reader :transaction
|
|
77
77
|
|
|
78
|
-
# @param transaction [Tx] the transaction
|
|
78
|
+
# @param transaction [Transaction::Tx] the transaction
|
|
79
79
|
# @raise [ArgumentError] if transaction is nil
|
|
80
80
|
def initialize(transaction:)
|
|
81
81
|
raise ArgumentError, 'RawTxEntry requires a transaction' if transaction.nil?
|
|
@@ -97,13 +97,13 @@ module BSV
|
|
|
97
97
|
|
|
98
98
|
# A BEEF entry containing a raw transaction with an associated BUMP index.
|
|
99
99
|
class ProvenTxEntry < BeefTx
|
|
100
|
-
# @return [Tx] the transaction
|
|
100
|
+
# @return [Transaction::Tx] the transaction
|
|
101
101
|
attr_reader :transaction
|
|
102
102
|
|
|
103
103
|
# @return [Integer] index into the BEEF bumps array
|
|
104
104
|
attr_reader :bump_index
|
|
105
105
|
|
|
106
|
-
# @param transaction [Tx] the transaction
|
|
106
|
+
# @param transaction [Transaction::Tx] the transaction
|
|
107
107
|
# @param bump_index [Integer] index into the bumps array
|
|
108
108
|
# @raise [ArgumentError] if transaction or bump_index is nil
|
|
109
109
|
def initialize(transaction:, bump_index:)
|
|
@@ -188,7 +188,7 @@ module BSV
|
|
|
188
188
|
# After parsing, input source transactions are wired automatically.
|
|
189
189
|
#
|
|
190
190
|
# @param data [String] raw BEEF binary
|
|
191
|
-
# @return [Beef] the parsed BEEF bundle
|
|
191
|
+
# @return [Transaction::Beef] the parsed BEEF bundle
|
|
192
192
|
def self.from_binary(data)
|
|
193
193
|
raise ArgumentError, "truncated BEEF: need at least 4 bytes for version, got #{data.bytesize}" if data.bytesize < 4
|
|
194
194
|
|
|
@@ -247,7 +247,7 @@ module BSV
|
|
|
247
247
|
# Deserialise a BEEF bundle from a hex string.
|
|
248
248
|
#
|
|
249
249
|
# @param hex [String] hex-encoded BEEF data
|
|
250
|
-
# @return [Beef] the parsed BEEF bundle
|
|
250
|
+
# @return [Transaction::Beef] the parsed BEEF bundle
|
|
251
251
|
def self.from_hex(hex)
|
|
252
252
|
from_binary(BSV::Primitives::Hex.decode(hex, name: 'BEEF hex'))
|
|
253
253
|
end
|
|
@@ -320,7 +320,7 @@ module BSV
|
|
|
320
320
|
# Find a transaction in the bundle by its wire-order transaction ID.
|
|
321
321
|
#
|
|
322
322
|
# @param wtxid [String] 32-byte wire-order wtxid
|
|
323
|
-
# @return [Tx, nil] the matching transaction, or nil
|
|
323
|
+
# @return [Transaction::Tx, nil] the matching transaction, or nil
|
|
324
324
|
def find_transaction(wtxid)
|
|
325
325
|
BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
|
|
326
326
|
BSV.logger&.debug { "[Beef] find_transaction: #{wtxid.reverse.unpack1('H*')} in #{@transactions.length} entries" }
|
|
@@ -353,7 +353,7 @@ module BSV
|
|
|
353
353
|
# Find a transaction with all source_transactions wired for signing.
|
|
354
354
|
#
|
|
355
355
|
# @param wtxid [String] 32-byte wire-order wtxid
|
|
356
|
-
# @return [Tx, nil] the transaction with wired inputs, or nil
|
|
356
|
+
# @return [Transaction::Tx, nil] the transaction with wired inputs, or nil
|
|
357
357
|
def find_transaction_for_signing(wtxid)
|
|
358
358
|
BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
|
|
359
359
|
tx = find_transaction(wtxid)
|
|
@@ -367,7 +367,7 @@ module BSV
|
|
|
367
367
|
# and merkle paths) for atomic proof validation.
|
|
368
368
|
#
|
|
369
369
|
# @param wtxid [String] 32-byte wire-order wtxid
|
|
370
|
-
# @return [Tx, nil] the transaction with full proof tree, or nil
|
|
370
|
+
# @return [Transaction::Tx, nil] the transaction with full proof tree, or nil
|
|
371
371
|
def find_atomic_transaction(wtxid)
|
|
372
372
|
BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
|
|
373
373
|
tx = find_transaction(wtxid)
|
|
@@ -385,6 +385,161 @@ module BSV
|
|
|
385
385
|
to_atomic_binary(subject_wtxid).unpack1('H*')
|
|
386
386
|
end
|
|
387
387
|
|
|
388
|
+
# --- Supporting methods for multi-party BEEF exchange ---
|
|
389
|
+
|
|
390
|
+
# Add a TXID-only entry for +wtxid+ if no entry exists yet.
|
|
391
|
+
#
|
|
392
|
+
# If an entry already exists (in any format), the call is a no-op —
|
|
393
|
+
# TXID-only is the weakest format, so an existing stronger entry is kept.
|
|
394
|
+
#
|
|
395
|
+
# @param wtxid [String] 32-byte wire-order binary txid
|
|
396
|
+
# @return [BeefTx] the existing or newly added entry
|
|
397
|
+
def merge_txid_only(wtxid)
|
|
398
|
+
BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
|
|
399
|
+
existing = @transactions.find { |bt| bt.wtxid == wtxid }
|
|
400
|
+
return existing if existing
|
|
401
|
+
|
|
402
|
+
entry = TxidOnlyEntry.new(known_wtxid: wtxid)
|
|
403
|
+
@transactions << entry
|
|
404
|
+
entry
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Return a shallow copy of this Transaction::Beef.
|
|
408
|
+
#
|
|
409
|
+
# Both +@bumps+ and +@transactions+ arrays are duplicated (new arrays),
|
|
410
|
+
# but the +BeefTx+ and +MerklePath+ objects they contain are shared
|
|
411
|
+
# references. This mirrors the TS SDK's +clone+ contract: entries are
|
|
412
|
+
# effectively immutable once added to a bundle, so shallow semantics are
|
|
413
|
+
# correct. If a deeper copy is ever required, add a separate +deep_dup+
|
|
414
|
+
# rather than changing this method's contract.
|
|
415
|
+
#
|
|
416
|
+
# +@subject_wtxid+ (Atomic BEEF) and +@txs_not_valid+ (cyclic-graph
|
|
417
|
+
# metadata) are preserved on the copy.
|
|
418
|
+
#
|
|
419
|
+
# @return [Transaction::Beef] a new shallow copy
|
|
420
|
+
def clone
|
|
421
|
+
# Use super (Object#clone) rather than self.class.new so subclasses
|
|
422
|
+
# (e.g. Transaction::BeefParty) with different initialize signatures
|
|
423
|
+
# work correctly. Object#clone does a shallow ivar copy without
|
|
424
|
+
# invoking initialize; we then dup the two arrays so the copy's
|
|
425
|
+
# contents can mutate independently.
|
|
426
|
+
c = super
|
|
427
|
+
c.instance_variable_set(:@bumps, @bumps.dup)
|
|
428
|
+
c.instance_variable_set(:@transactions, @transactions.dup)
|
|
429
|
+
c.instance_variable_set(:@txs_not_valid, @txs_not_valid&.dup)
|
|
430
|
+
c
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# Return a new Transaction::Beef with TXID-only entries removed for any
|
|
434
|
+
# wtxid in +known_wtxids+.
|
|
435
|
+
#
|
|
436
|
+
# RAW_TX and RAW_TX_AND_BUMP entries are always retained, even when their
|
|
437
|
+
# wtxid appears in +known_wtxids+. Only +TxidOnlyEntry+ records are
|
|
438
|
+
# candidates for removal (they carry no proof data the recipient needs).
|
|
439
|
+
#
|
|
440
|
+
# After dropping TXID-only entries, any BUMP that is no longer referenced
|
|
441
|
+
# by any remaining +ProvenTxEntry+ is removed, and all +bump_index+
|
|
442
|
+
# fields are renumbered to match the new bumps array (mirrors TS
|
|
443
|
+
# +trimKnownTxids+ at +Beef.ts:861-914+).
|
|
444
|
+
#
|
|
445
|
+
# Does not mutate +self+. Starts from a {#clone} so the caller's state
|
|
446
|
+
# is preserved.
|
|
447
|
+
#
|
|
448
|
+
# @param known_wtxids [Array<String>] binary wtxids the recipient already has
|
|
449
|
+
# @return [Transaction::Beef] a new bundle with the specified entries trimmed
|
|
450
|
+
def trim_known_wtxids(known_wtxids)
|
|
451
|
+
known_set = known_wtxids.to_set
|
|
452
|
+
|
|
453
|
+
trimmed = clone
|
|
454
|
+
trimmed.instance_variable_set(
|
|
455
|
+
:@transactions,
|
|
456
|
+
trimmed.transactions.reject { |bt| bt.is_a?(TxidOnlyEntry) && known_set.include?(bt.wtxid) }
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
# Find which bump indices are still referenced
|
|
460
|
+
referenced = Set.new
|
|
461
|
+
trimmed.transactions.each do |bt|
|
|
462
|
+
referenced.add(bt.bump_index) if bt.is_a?(ProvenTxEntry)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# If all bumps are still referenced, nothing more to do
|
|
466
|
+
return trimmed if referenced.size == trimmed.bumps.length
|
|
467
|
+
|
|
468
|
+
# Build old → new index map for surviving bumps
|
|
469
|
+
index_map = {}
|
|
470
|
+
new_idx = 0
|
|
471
|
+
trimmed.bumps.each_with_index do |_, old_idx|
|
|
472
|
+
if referenced.include?(old_idx)
|
|
473
|
+
index_map[old_idx] = new_idx
|
|
474
|
+
new_idx += 1
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# Drop unreferenced bumps
|
|
479
|
+
trimmed.instance_variable_set(
|
|
480
|
+
:@bumps,
|
|
481
|
+
trimmed.bumps.each_with_index.filter_map { |bump, i| bump if referenced.include?(i) }
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
# Renumber bump_index on all ProvenTxEntry records
|
|
485
|
+
trimmed.instance_variable_set(
|
|
486
|
+
:@transactions,
|
|
487
|
+
trimmed.transactions.map do |bt|
|
|
488
|
+
next bt unless bt.is_a?(ProvenTxEntry)
|
|
489
|
+
|
|
490
|
+
new_bump_idx = index_map.fetch(bt.bump_index)
|
|
491
|
+
ProvenTxEntry.new(transaction: bt.transaction, bump_index: new_bump_idx).tap do |e|
|
|
492
|
+
e.transaction.merkle_path = trimmed.bumps[new_bump_idx]
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
trimmed
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# Return the wtxids of transactions that are "valid" in this bundle.
|
|
501
|
+
#
|
|
502
|
+
# A transaction is valid when it either:
|
|
503
|
+
# - has a merkle proof (is a +ProvenTxEntry+), or
|
|
504
|
+
# - all of its inputs chain back to proven transactions within the bundle.
|
|
505
|
+
#
|
|
506
|
+
# TXID-only entries are excluded. Cyclic transactions (from
|
|
507
|
+
# +@txs_not_valid+) are also excluded.
|
|
508
|
+
#
|
|
509
|
+
# Used by {Transaction::BeefParty} to record which wtxids a party gains
|
|
510
|
+
# knowledge of after a {#merge_beef_from_party} call.
|
|
511
|
+
#
|
|
512
|
+
# @return [Array<String>] binary wire-order wtxids
|
|
513
|
+
def valid_wtxids
|
|
514
|
+
invalid = @txs_not_valid || Set.new
|
|
515
|
+
known = Set.new
|
|
516
|
+
|
|
517
|
+
# Seed with proven entries (excluding any marked cyclic/unsortable)
|
|
518
|
+
@transactions.each do |bt|
|
|
519
|
+
known.add(bt.wtxid) if bt.is_a?(ProvenTxEntry) && !invalid.include?(bt.wtxid)
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# Iteratively resolve unproven entries whose inputs are all known.
|
|
523
|
+
# Skip @txs_not_valid entries — by definition they can't be ordered
|
|
524
|
+
# for validation, so a counterparty receiving them can't validate either.
|
|
525
|
+
changed = true
|
|
526
|
+
while changed
|
|
527
|
+
changed = false
|
|
528
|
+
@transactions.each do |bt|
|
|
529
|
+
next if bt.is_a?(TxidOnlyEntry) || known.include?(bt.wtxid)
|
|
530
|
+
next if invalid.include?(bt.wtxid)
|
|
531
|
+
next unless bt.respond_to?(:transaction)
|
|
532
|
+
|
|
533
|
+
if bt.transaction.inputs.all? { |inp| known.include?(inp.prev_wtxid) }
|
|
534
|
+
known.add(bt.wtxid)
|
|
535
|
+
changed = true
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
known.to_a
|
|
541
|
+
end
|
|
542
|
+
|
|
388
543
|
# --- Merge operations ---
|
|
389
544
|
|
|
390
545
|
# Add or deduplicate a merkle path (BUMP) in this BEEF bundle.
|
|
@@ -440,7 +595,7 @@ module BSV
|
|
|
440
595
|
# (same txid) are upgraded if a stronger format is now available (F5.7):
|
|
441
596
|
# TXID_ONLY → RAW_TX or RAW_TX_AND_BUMP; RAW_TX → RAW_TX_AND_BUMP.
|
|
442
597
|
#
|
|
443
|
-
# @param tx [Tx] the transaction to merge
|
|
598
|
+
# @param tx [Transaction::Tx] the transaction to merge
|
|
444
599
|
# @return [BeefTx] the (possibly existing or upgraded) BeefTx entry
|
|
445
600
|
def merge_transaction(tx)
|
|
446
601
|
wtxid = tx.wtxid
|
|
@@ -512,7 +667,7 @@ module BSV
|
|
|
512
667
|
# BUMP indices are remapped during merge. New BeefTx instances are
|
|
513
668
|
# constructed rather than sharing references with the source bundle (F5.9).
|
|
514
669
|
#
|
|
515
|
-
# @param other [Beef] the BEEF bundle to merge from
|
|
670
|
+
# @param other [Transaction::Beef] the BEEF bundle to merge from
|
|
516
671
|
# @return [self]
|
|
517
672
|
# @raise [ArgumentError] if a transaction in +other+ has a +bump_index+
|
|
518
673
|
# that does not point to any BUMP in +other.bumps+ (i.e. the source
|
|
@@ -800,8 +955,7 @@ module BSV
|
|
|
800
955
|
|
|
801
956
|
input.source_transaction = source
|
|
802
957
|
BSV.logger&.debug do
|
|
803
|
-
"[Beef] wired input #{input.
|
|
804
|
-
"-> source #{source.wtxid.reverse.unpack1('H*')}"
|
|
958
|
+
"[Beef] wired input #{input.dtxid_hex}:#{input.prev_tx_out_index} -> source #{source.dtxid}"
|
|
805
959
|
end
|
|
806
960
|
end
|
|
807
961
|
|
|
@@ -821,7 +975,7 @@ module BSV
|
|
|
821
975
|
# RAW_TX → RAW_TX_AND_BUMP (if bump now available)
|
|
822
976
|
#
|
|
823
977
|
# @param existing [BeefTx] the current entry in @transactions
|
|
824
|
-
# @param tx [Tx, nil] the raw transaction (may be nil for TXID_ONLY → TXID_ONLY)
|
|
978
|
+
# @param tx [Transaction::Tx, nil] the raw transaction (may be nil for TXID_ONLY → TXID_ONLY)
|
|
825
979
|
# @param bump_index [Integer, nil] a BUMP index already validated against @bumps
|
|
826
980
|
# @return [BeefTx, nil] the upgraded entry, or nil when no upgrade is needed
|
|
827
981
|
def upgrade_beef_tx(existing, tx, bump_index: nil)
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BSV
|
|
4
|
+
module Transaction
|
|
5
|
+
# Transaction::Beef subclass that tracks per-party knowledge of wtxids.
|
|
6
|
+
#
|
|
7
|
+
# Used in multi-party BEEF exchange to avoid re-transmitting transaction
|
|
8
|
+
# data and proofs that a counterparty already possesses. Each party is
|
|
9
|
+
# identified by a caller-supplied string and associated with the set of
|
|
10
|
+
# wtxids it is known to hold.
|
|
11
|
+
#
|
|
12
|
+
# @example Two-party exchange
|
|
13
|
+
# party = BSV::Transaction::BeefParty.new(['alice', 'bob'])
|
|
14
|
+
# party.merge_beef_from_party('alice', alice_beef)
|
|
15
|
+
# trimmed = party.trimmed_beef_for_party('bob') # plain Transaction::Beef
|
|
16
|
+
class BeefParty < Beef
|
|
17
|
+
# @param initial_parties [Array<String>] optional initial party identifiers
|
|
18
|
+
# @raise [ArgumentError] if +initial_parties+ contains duplicates
|
|
19
|
+
#
|
|
20
|
+
# Defaults to BEEF_V2 (BRC-96) because TXID-only entries — which are
|
|
21
|
+
# central to BeefParty's purpose — are only valid in V2. Matches the TS
|
|
22
|
+
# SDK's +new Beef()+ default.
|
|
23
|
+
def initialize(initial_parties = [])
|
|
24
|
+
super(version: BEEF_V2)
|
|
25
|
+
@party_knowledge = {}
|
|
26
|
+
initial_parties.each { |p| add_party(p) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @param party_id [String]
|
|
30
|
+
# @return [Boolean] true if +party_id+ has been added
|
|
31
|
+
def party?(party_id)
|
|
32
|
+
@party_knowledge.key?(party_id)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Add a new unique party identifier.
|
|
36
|
+
#
|
|
37
|
+
# @param party_id [String]
|
|
38
|
+
# @raise [ArgumentError] if +party_id+ is already known
|
|
39
|
+
def add_party(party_id)
|
|
40
|
+
raise ArgumentError, "duplicate party #{party_id}" if party?(party_id)
|
|
41
|
+
|
|
42
|
+
@party_knowledge[party_id] = Set.new
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Record additional wtxids as known to +party_id+.
|
|
46
|
+
#
|
|
47
|
+
# Auto-creates the party if it is not yet known (TS parity). Also
|
|
48
|
+
# merges a TXID-only entry into the underlying bundle for each wtxid.
|
|
49
|
+
#
|
|
50
|
+
# @param party_id [String] party identifier (auto-created if unknown)
|
|
51
|
+
# @param wtxids [Array<String>] 32-byte wire-order binary wtxids
|
|
52
|
+
# @raise [ArgumentError] if any element of +wtxids+ is not a valid wtxid
|
|
53
|
+
def add_known_wtxids_for_party(party_id, wtxids)
|
|
54
|
+
add_party(party_id) unless party?(party_id)
|
|
55
|
+
wtxids.each do |wtxid|
|
|
56
|
+
BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
|
|
57
|
+
@party_knowledge[party_id].add(wtxid)
|
|
58
|
+
merge_txid_only(wtxid)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @param party_id [String]
|
|
63
|
+
# @return [Array<String>] 32-byte wire-order binary wtxids known to +party_id+
|
|
64
|
+
# @raise [ArgumentError] if +party_id+ is unknown
|
|
65
|
+
def known_wtxids_for_party(party_id)
|
|
66
|
+
raise ArgumentError, "unknown party #{party_id}" unless party?(party_id)
|
|
67
|
+
|
|
68
|
+
@party_knowledge[party_id].to_a
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Merge a Transaction::Beef received from +party_id+.
|
|
72
|
+
#
|
|
73
|
+
# Merges all transactions and BUMPs from +beef_or_binary+ into +self+,
|
|
74
|
+
# then records the valid wtxids of the merged bundle as known to
|
|
75
|
+
# +party_id+. Auto-creates the party if not yet known.
|
|
76
|
+
#
|
|
77
|
+
# @param party_id [String] party identifier
|
|
78
|
+
# @param beef_or_binary [Transaction::Beef, String] a Transaction::Beef
|
|
79
|
+
# instance or raw binary BEEF bytes
|
|
80
|
+
def merge_beef_from_party(party_id, beef_or_binary)
|
|
81
|
+
beef = beef_or_binary.is_a?(Beef) ? beef_or_binary : Beef.from_binary(beef_or_binary)
|
|
82
|
+
# Capture the set of wtxids the party actually sent before merging.
|
|
83
|
+
beef_wtxids = beef.transactions.map(&:wtxid)
|
|
84
|
+
merge(beef)
|
|
85
|
+
# Record knowledge of any tx the party sent that is now provably valid
|
|
86
|
+
# against the merged state. This captures the cross-bundle case where
|
|
87
|
+
# an unproven tx in +beef+ becomes valid via inputs already proven in
|
|
88
|
+
# +self+ (and conversely doesn't record knowledge the party never sent).
|
|
89
|
+
known = valid_wtxids & beef_wtxids
|
|
90
|
+
add_known_wtxids_for_party(party_id, known)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Return a new Transaction::Beef (not Transaction::BeefParty) with
|
|
94
|
+
# TXID-only entries that are known to +party_id+ removed.
|
|
95
|
+
#
|
|
96
|
+
# RAW_TX and RAW_TX_AND_BUMP entries are always retained even when the
|
|
97
|
+
# party knows the txid — those formats carry proof data. BUMP indices
|
|
98
|
+
# are renumbered if any unreferenced bumps are dropped.
|
|
99
|
+
#
|
|
100
|
+
# Does not mutate +self+.
|
|
101
|
+
#
|
|
102
|
+
# @param party_id [String]
|
|
103
|
+
# @return [Transaction::Beef] a new trimmed bundle (plain Beef, not BeefParty)
|
|
104
|
+
# @raise [ArgumentError] if +party_id+ is unknown
|
|
105
|
+
def trimmed_beef_for_party(party_id)
|
|
106
|
+
raise ArgumentError, "unknown party #{party_id}" unless party?(party_id)
|
|
107
|
+
|
|
108
|
+
known = @party_knowledge[party_id].to_a
|
|
109
|
+
|
|
110
|
+
# Build a plain Beef from our current state so trim_known_wtxids
|
|
111
|
+
# returns a Beef, not a BeefParty.
|
|
112
|
+
plain = Beef.new(version: @version, bumps: @bumps.dup, transactions: @transactions.dup)
|
|
113
|
+
plain.instance_variable_set(:@subject_wtxid, @subject_wtxid)
|
|
114
|
+
plain.instance_variable_set(:@txs_not_valid, @txs_not_valid&.dup)
|
|
115
|
+
plain.trim_known_wtxids(known)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -56,7 +56,7 @@ module BSV
|
|
|
56
56
|
# Return a default ChainTracker backed by the GorillaPool provider.
|
|
57
57
|
#
|
|
58
58
|
# @param testnet [Boolean] when true, uses the testnet provider
|
|
59
|
-
# @return [ChainTracker]
|
|
59
|
+
# @return [Transaction::ChainTracker]
|
|
60
60
|
def self.default(testnet: false)
|
|
61
61
|
new(BSV::Network::Providers::GorillaPool.default(testnet: testnet))
|
|
62
62
|
end
|
|
@@ -17,7 +17,7 @@ module BSV
|
|
|
17
17
|
class FeeModel
|
|
18
18
|
# Compute the fee for a transaction.
|
|
19
19
|
#
|
|
20
|
-
# @param transaction [Tx] the transaction to compute the fee for
|
|
20
|
+
# @param transaction [Transaction::Tx] the transaction to compute the fee for
|
|
21
21
|
# @return [Integer] the fee in satoshis
|
|
22
22
|
# @raise [NotImplementedError] if not overridden by a subclass
|
|
23
23
|
def compute_fee(_transaction)
|
|
@@ -66,7 +66,7 @@ module BSV
|
|
|
66
66
|
|
|
67
67
|
# Compute the fee for a transaction using the latest ARC rate.
|
|
68
68
|
#
|
|
69
|
-
# @param transaction [Tx] the transaction to compute the fee for
|
|
69
|
+
# @param transaction [Transaction::Tx] the transaction to compute the fee for
|
|
70
70
|
# @return [Integer] the fee in satoshis
|
|
71
71
|
def compute_fee(transaction)
|
|
72
72
|
rate = current_rate
|
|
@@ -23,7 +23,7 @@ module BSV
|
|
|
23
23
|
|
|
24
24
|
# Compute the fee for a transaction based on its estimated size.
|
|
25
25
|
#
|
|
26
|
-
# @param transaction [Tx] the transaction to compute the fee for
|
|
26
|
+
# @param transaction [Transaction::Tx] the transaction to compute the fee for
|
|
27
27
|
# @return [Integer] the fee in satoshis
|
|
28
28
|
def compute_fee(transaction)
|
|
29
29
|
size = transaction.estimated_size
|
|
@@ -322,7 +322,7 @@ module BSV
|
|
|
322
322
|
# logic is: reject when `current_height - block_height < 100` (immature).
|
|
323
323
|
#
|
|
324
324
|
# @param dtxid_hex [String] hex-encoded transaction ID (display order)
|
|
325
|
-
# @param chain_tracker [ChainTracker] chain tracker to verify the root against
|
|
325
|
+
# @param chain_tracker [Transaction::ChainTracker] chain tracker to verify the root against
|
|
326
326
|
# @return [Boolean] true if the computed root matches the block at this height
|
|
327
327
|
def verify(dtxid_hex, chain_tracker)
|
|
328
328
|
BSV::Primitives::Hex.validate_dtxid_hex!(dtxid_hex, name: 'dtxid_hex')
|
|
@@ -24,7 +24,7 @@ module BSV
|
|
|
24
24
|
|
|
25
25
|
# Generate the P2PKH unlocking script for the given input.
|
|
26
26
|
#
|
|
27
|
-
# @param tx [Tx] the transaction being signed
|
|
27
|
+
# @param tx [Transaction::Tx] the transaction being signed
|
|
28
28
|
# @param input_index [Integer] the input index to sign
|
|
29
29
|
# @return [Script::Script] the unlocking script (signature + pubkey)
|
|
30
30
|
def sign(tx, input_index)
|
|
@@ -26,7 +26,7 @@ module BSV
|
|
|
26
26
|
# @return [Script::Script, nil] locking script of the source output (needed for sighash)
|
|
27
27
|
attr_accessor :source_locking_script
|
|
28
28
|
|
|
29
|
-
# @return [Tx, nil] the full source transaction (for BEEF wiring)
|
|
29
|
+
# @return [Transaction::Tx, nil] the full source transaction (for BEEF wiring)
|
|
30
30
|
attr_accessor :source_transaction
|
|
31
31
|
|
|
32
32
|
# @return [UnlockingScriptTemplate, nil] template for deferred signing
|
data/lib/bsv/transaction/tx.rb
CHANGED
|
@@ -59,7 +59,7 @@ module BSV
|
|
|
59
59
|
|
|
60
60
|
# Append a transaction input.
|
|
61
61
|
#
|
|
62
|
-
# @param input [TransactionInput] the input to add
|
|
62
|
+
# @param input [Transaction::TransactionInput] the input to add
|
|
63
63
|
# @return [self] for chaining
|
|
64
64
|
def add_input(input)
|
|
65
65
|
@inputs << input
|
|
@@ -68,7 +68,7 @@ module BSV
|
|
|
68
68
|
|
|
69
69
|
# Append a transaction output.
|
|
70
70
|
#
|
|
71
|
-
# @param output [TransactionOutput] the output to add
|
|
71
|
+
# @param output [Transaction::TransactionOutput] the output to add
|
|
72
72
|
# @return [self] for chaining
|
|
73
73
|
def add_output(output)
|
|
74
74
|
@outputs << output
|
|
@@ -144,7 +144,7 @@ module BSV
|
|
|
144
144
|
# Deserialise a transaction from binary data.
|
|
145
145
|
#
|
|
146
146
|
# @param data [String] raw binary transaction
|
|
147
|
-
# @return [Tx] the parsed transaction
|
|
147
|
+
# @return [Transaction::Tx] the parsed transaction
|
|
148
148
|
def self.from_binary(data)
|
|
149
149
|
raise ArgumentError, "truncated transaction: need at least 10 bytes, got #{data.bytesize}" if data.bytesize < 10
|
|
150
150
|
|
|
@@ -182,7 +182,7 @@ module BSV
|
|
|
182
182
|
# Deserialise a transaction from a hex string.
|
|
183
183
|
#
|
|
184
184
|
# @param hex [String] hex-encoded transaction
|
|
185
|
-
# @return [Tx] the parsed transaction
|
|
185
|
+
# @return [Transaction::Tx] the parsed transaction
|
|
186
186
|
def self.from_hex(hex)
|
|
187
187
|
from_binary(BSV::Primitives::Hex.decode(hex, name: 'transaction hex'))
|
|
188
188
|
end
|
|
@@ -190,7 +190,7 @@ module BSV
|
|
|
190
190
|
# Deserialise a transaction from Extended Format (BRC-30) binary data.
|
|
191
191
|
#
|
|
192
192
|
# @param data [String] raw EF binary
|
|
193
|
-
# @return [Tx] the parsed transaction with source data on inputs
|
|
193
|
+
# @return [Transaction::Tx] the parsed transaction with source data on inputs
|
|
194
194
|
# @raise [ArgumentError] if the EF marker is invalid
|
|
195
195
|
def self.from_ef(data)
|
|
196
196
|
raise ArgumentError, "truncated EF transaction: need at least 10 bytes, got #{data.bytesize}" if data.bytesize < 10
|
|
@@ -250,7 +250,7 @@ module BSV
|
|
|
250
250
|
# Deserialise a transaction from an Extended Format hex string.
|
|
251
251
|
#
|
|
252
252
|
# @param hex [String] hex-encoded EF transaction
|
|
253
|
-
# @return [Tx] the parsed transaction with source data on inputs
|
|
253
|
+
# @return [Transaction::Tx] the parsed transaction with source data on inputs
|
|
254
254
|
def self.from_ef_hex(hex)
|
|
255
255
|
from_ef(BSV::Primitives::Hex.decode(hex, name: 'EF transaction hex'))
|
|
256
256
|
end
|
|
@@ -260,7 +260,7 @@ module BSV
|
|
|
260
260
|
#
|
|
261
261
|
# @param data [String] binary data containing the transaction
|
|
262
262
|
# @param offset [Integer] byte offset to start reading from
|
|
263
|
-
# @return [Array(Tx, Integer)] the transaction and bytes consumed
|
|
263
|
+
# @return [Array(Transaction::Tx, Integer)] the transaction and bytes consumed
|
|
264
264
|
def self.from_binary_with_offset(data, offset = 0)
|
|
265
265
|
if data.bytesize < offset + 10
|
|
266
266
|
raise ArgumentError, "truncated transaction: need at least 10 bytes at offset #{offset}, got #{data.bytesize - offset}"
|
|
@@ -370,7 +370,7 @@ module BSV
|
|
|
370
370
|
# +wire_source_transactions+ pass in +Beef.from_binary+.
|
|
371
371
|
#
|
|
372
372
|
# @param data [String] raw BEEF binary
|
|
373
|
-
# @return [Tx, nil] the subject transaction with ancestry wired,
|
|
373
|
+
# @return [Transaction::Tx, nil] the subject transaction with ancestry wired,
|
|
374
374
|
# or nil if the BEEF is empty or contains no raw transaction entries
|
|
375
375
|
def self.from_beef(data)
|
|
376
376
|
beef = Beef.from_binary(data)
|
|
@@ -384,7 +384,7 @@ module BSV
|
|
|
384
384
|
# Parse a BEEF hex string and return the subject transaction.
|
|
385
385
|
#
|
|
386
386
|
# @param hex [String] hex-encoded BEEF
|
|
387
|
-
# @return [Tx, nil] the subject transaction with ancestry wired,
|
|
387
|
+
# @return [Transaction::Tx, nil] the subject transaction with ancestry wired,
|
|
388
388
|
# or nil if the BEEF is empty or contains no raw transaction entries
|
|
389
389
|
def self.from_beef_hex(hex)
|
|
390
390
|
from_beef(BSV::Primitives::Hex.decode(hex, name: 'BEEF hex'))
|
|
@@ -622,7 +622,7 @@ module BSV
|
|
|
622
622
|
# - +:script_failure+ — Ruby raises; TS/Python return +false+; Go also propagates errors
|
|
623
623
|
# - +:missing_source+ — Ruby raises; consistent with TS/Go/Python (all raise/error)
|
|
624
624
|
#
|
|
625
|
-
# @param chain_tracker [ChainTracker] chain tracker for merkle root validation
|
|
625
|
+
# @param chain_tracker [Transaction::ChainTracker] chain tracker for merkle root validation
|
|
626
626
|
# @param fee_model [FeeModel, nil] optional fee model to validate the root transaction's fee
|
|
627
627
|
# @return [true] on successful verification
|
|
628
628
|
# @raise [VerificationError] with code +:invalid_merkle_proof+ if a merkle proof is invalid
|
|
@@ -806,7 +806,7 @@ module BSV
|
|
|
806
806
|
# is required. Otherwise, requires a wired +source_transaction+ and a
|
|
807
807
|
# valid output at +prev_tx_out_index+.
|
|
808
808
|
#
|
|
809
|
-
# @param input [TransactionInput]
|
|
809
|
+
# @param input [Transaction::TransactionInput]
|
|
810
810
|
# @param idx [Integer] input index, used in error messages
|
|
811
811
|
# @return [TransactionOutput, nil]
|
|
812
812
|
def ef_source_output(input, idx)
|
|
@@ -14,7 +14,7 @@ module BSV
|
|
|
14
14
|
class UnlockingScriptTemplate
|
|
15
15
|
# Generate the unlocking script for a transaction input.
|
|
16
16
|
#
|
|
17
|
-
# @param _tx [Tx] the transaction being signed
|
|
17
|
+
# @param _tx [Transaction::Tx] the transaction being signed
|
|
18
18
|
# @param _input_index [Integer] the input index to sign
|
|
19
19
|
# @return [Script::Script] the generated unlocking script
|
|
20
20
|
# @raise [NotImplementedError] if not overridden by a subclass
|
|
@@ -24,7 +24,7 @@ module BSV
|
|
|
24
24
|
|
|
25
25
|
# Estimate the unlocking script length in bytes (for fee estimation).
|
|
26
26
|
#
|
|
27
|
-
# @param _tx [Tx] the transaction
|
|
27
|
+
# @param _tx [Transaction::Tx] the transaction
|
|
28
28
|
# @param _input_index [Integer] the input index
|
|
29
29
|
# @return [Integer] estimated script length in bytes
|
|
30
30
|
# @raise [NotImplementedError] if not overridden by a subclass
|
data/lib/bsv/transaction.rb
CHANGED
|
@@ -19,6 +19,7 @@ module BSV
|
|
|
19
19
|
autoload :ChainTracker, 'bsv/transaction/chain_tracker'
|
|
20
20
|
autoload :ChainTrackers, 'bsv/transaction/chain_trackers'
|
|
21
21
|
autoload :Beef, 'bsv/transaction/beef'
|
|
22
|
+
autoload :BeefParty, 'bsv/transaction/beef_party'
|
|
22
23
|
autoload :UnlockingScriptTemplate, 'bsv/transaction/unlocking_script_template'
|
|
23
24
|
autoload :P2PKH, 'bsv/transaction/p2pkh'
|
|
24
25
|
autoload :Tx, 'bsv/transaction/tx'
|
data/lib/bsv/version.rb
CHANGED
data/lib/bsv-sdk.rb
CHANGED
|
@@ -26,6 +26,8 @@ module BSV
|
|
|
26
26
|
autoload :Identity, 'bsv/identity'
|
|
27
27
|
autoload :Registry, 'bsv/registry'
|
|
28
28
|
autoload :MCP, 'bsv/mcp'
|
|
29
|
+
autoload :Storage, 'bsv/storage'
|
|
30
|
+
autoload :KVStore, 'bsv/kv_store'
|
|
29
31
|
|
|
30
32
|
# Wallet is loaded eagerly to avoid load-path shadowing when bsv-wallet is
|
|
31
33
|
# also in the bundle (bsv-wallet/lib/bsv/wallet.rb would otherwise win).
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bsv-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.25.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Simon Bettison
|
|
@@ -85,6 +85,11 @@ files:
|
|
|
85
85
|
- lib/bsv/identity/constants.rb
|
|
86
86
|
- lib/bsv/identity/identity_parser.rb
|
|
87
87
|
- lib/bsv/identity/types.rb
|
|
88
|
+
- lib/bsv/kv_store.rb
|
|
89
|
+
- lib/bsv/kv_store/entry.rb
|
|
90
|
+
- lib/bsv/kv_store/global.rb
|
|
91
|
+
- lib/bsv/kv_store/interpreter.rb
|
|
92
|
+
- lib/bsv/kv_store/token.rb
|
|
88
93
|
- lib/bsv/mcp.rb
|
|
89
94
|
- lib/bsv/mcp/config.rb
|
|
90
95
|
- lib/bsv/mcp/server.rb
|
|
@@ -118,6 +123,7 @@ files:
|
|
|
118
123
|
- lib/bsv/overlay/broadcast_facilitator.rb
|
|
119
124
|
- lib/bsv/overlay/constants.rb
|
|
120
125
|
- lib/bsv/overlay/errors.rb
|
|
126
|
+
- lib/bsv/overlay/historian.rb
|
|
121
127
|
- lib/bsv/overlay/host_reputation_tracker.rb
|
|
122
128
|
- lib/bsv/overlay/lookup_facilitator.rb
|
|
123
129
|
- lib/bsv/overlay/lookup_resolver.rb
|
|
@@ -152,6 +158,7 @@ files:
|
|
|
152
158
|
- lib/bsv/registry/constants.rb
|
|
153
159
|
- lib/bsv/registry/types.rb
|
|
154
160
|
- lib/bsv/script.rb
|
|
161
|
+
- lib/bsv/script/bip276.rb
|
|
155
162
|
- lib/bsv/script/builder.rb
|
|
156
163
|
- lib/bsv/script/chunk.rb
|
|
157
164
|
- lib/bsv/script/interpreter/error.rb
|
|
@@ -169,8 +176,13 @@ files:
|
|
|
169
176
|
- lib/bsv/script/push_drop_template.rb
|
|
170
177
|
- lib/bsv/script/script.rb
|
|
171
178
|
- lib/bsv/secp256k1_native.bundle
|
|
179
|
+
- lib/bsv/storage.rb
|
|
180
|
+
- lib/bsv/storage/downloader.rb
|
|
181
|
+
- lib/bsv/storage/errors.rb
|
|
182
|
+
- lib/bsv/storage/utils.rb
|
|
172
183
|
- lib/bsv/transaction.rb
|
|
173
184
|
- lib/bsv/transaction/beef.rb
|
|
185
|
+
- lib/bsv/transaction/beef_party.rb
|
|
174
186
|
- lib/bsv/transaction/chain_tracker.rb
|
|
175
187
|
- lib/bsv/transaction/chain_trackers.rb
|
|
176
188
|
- lib/bsv/transaction/chain_trackers/whats_on_chain.rb
|