Class: BSV::Transaction::Beef

Inherits:
Object
  • Object
show all
Defined in:
lib/bsv/transaction/beef.rb

Overview

Background Evaluation Extended Format (BEEF) for SPV-ready transaction bundles. Encodes one or more transactions together with their merkle proofs (BUMPs), enabling recipients to verify inclusion without querying a block explorer.

Supports BRC-62 (V1), BRC-96 (V2), and BRC-95 (Atomic BEEF) formats.

Examples:

Parse a BEEF bundle and find a transaction

beef = BSV::Transaction::Beef.from_hex(beef_hex)
tx = beef.find_transaction(txid_bytes)

Defined Under Namespace

Classes: BeefTx

Version constants collapse

BEEF_V1 =

Version magic bytes as LE uint32 (matching pack(‘V’) / unpack1(‘V’)). Stream bytes: 01 00 BE EF / 02 00 BE EF / 01 01 01 01

0xEFBE0001
BEEF_V2 =

BRC-62

0xEFBE0002
ATOMIC_BEEF =

BRC-96

0x01010101

Transaction format flags collapse

FORMAT_RAW_TX =

Raw transaction without a merkle proof.

0
FORMAT_RAW_TX_AND_BUMP =

Raw transaction with an associated BUMP index.

1
FORMAT_TXID_ONLY =

Only the transaction ID (no raw data).

2

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(version: BEEF_V1, bumps: [], transactions: []) ⇒ Beef

Returns a new instance of Beef.

Parameters:

  • version (Integer) (defaults to: BEEF_V1)

    BEEF version constant (default: BEEF_V1, matching to_binary’s default for ARC compatibility; from_binary overwrites this with the parsed version)

  • bumps (Array<MerklePath>) (defaults to: [])

    merkle proofs

  • transactions (Array<BeefTx>) (defaults to: [])

    transaction entries



96
97
98
99
100
101
# File 'lib/bsv/transaction/beef.rb', line 96

def initialize(version: BEEF_V1, bumps: [], transactions: [])
  @version = version
  @bumps = bumps
  @transactions = transactions
  @subject_txid = nil
end

Instance Attribute Details

#bumpsArray<MerklePath> (readonly)

Returns merkle proofs (BUMPs) referenced by transactions.

Returns:

  • (Array<MerklePath>)

    merkle proofs (BUMPs) referenced by transactions



84
85
86
# File 'lib/bsv/transaction/beef.rb', line 84

def bumps
  @bumps
end

#subject_txidString? (readonly)

Returns 32-byte subject txid (Atomic BEEF only).

Returns:

  • (String, nil)

    32-byte subject txid (Atomic BEEF only)



90
91
92
# File 'lib/bsv/transaction/beef.rb', line 90

def subject_txid
  @subject_txid
end

#transactionsArray<BeefTx> (readonly)

Returns the transactions in dependency order.

Returns:

  • (Array<BeefTx>)

    the transactions in dependency order



87
88
89
# File 'lib/bsv/transaction/beef.rb', line 87

def transactions
  @transactions
end

#txs_not_validArray<BeefTx>? (readonly)

Returns transactions that could not be sorted due to cycles, or nil.

Populated by #sort_transactions! when cycles are detected (F5.5).

Returns:



634
635
636
# File 'lib/bsv/transaction/beef.rb', line 634

def txs_not_valid
  @txs_not_valid
end

#versionInteger

Returns BEEF version constant.

Returns:

  • (Integer)

    BEEF version constant



81
82
83
# File 'lib/bsv/transaction/beef.rb', line 81

def version
  @version
end

Class Method Details

.from_binary(data) ⇒ Beef

Deserialise a BEEF bundle from binary data.

Supports V1 (BRC-62), V2 (BRC-96), and Atomic (BRC-95) formats. After parsing, input source transactions are wired automatically.

Parameters:

  • data (String)

    raw BEEF binary

Returns:

  • (Beef)

    the parsed BEEF bundle

Raises:

  • (ArgumentError)


112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/bsv/transaction/beef.rb', line 112

def self.from_binary(data)
  raise ArgumentError, "truncated BEEF: need at least 4 bytes for version, got #{data.bytesize}" if data.bytesize < 4

  offset = 0

  version = data.byteslice(offset, 4).unpack1('V')
  offset += 4

  # F5.12: reject unknown version magic bytes
  unless [BEEF_V1, BEEF_V2, ATOMIC_BEEF].include?(version)
    raise ArgumentError,
          format('unknown BEEF version 0x%<ver>08X: expected BEEF_V1 (0x%<v1>08X), ' \
                 'BEEF_V2 (0x%<v2>08X), or ATOMIC_BEEF (0x%<ab>08X)',
                 ver: version, v1: BEEF_V1, v2: BEEF_V2, ab: ATOMIC_BEEF)
  end

  beef = new(version: version)

  if version == ATOMIC_BEEF
    if data.bytesize < offset + 36
      remaining = data.bytesize - offset
      raise ArgumentError, "truncated Atomic BEEF: need 36 bytes at offset #{offset}, got #{remaining}"
    end

    # Atomic BEEF stores the subject txid in internal byte order (little-endian
    # hash order), matching JS and Go SDKs. Reverse to display order for internal use.
    beef.instance_variable_set(:@subject_txid, data.byteslice(offset, 32).reverse)
    offset += 32
    inner_version = data.byteslice(offset, 4).unpack1('V')
    offset += 4

    # Validate inner version — must be V1 or V2
    unless [BEEF_V1, BEEF_V2].include?(inner_version)
      raise ArgumentError,
            format('unknown inner BEEF version 0x%<ver>08X inside Atomic BEEF: expected BEEF_V1 or BEEF_V2',
                   ver: inner_version)
    end

    beef.version = inner_version
  end

  offset = read_bumps(beef, data, offset)

  case version == ATOMIC_BEEF ? beef.version : version
  when BEEF_V2
    read_v2_transactions(beef, data, offset)
  when BEEF_V1
    read_v1_transactions(beef, data, offset)
  end

  wire_source_transactions(beef)

  beef
end

.from_hex(hex) ⇒ Beef

Deserialise a BEEF bundle from a hex string.

Parameters:

  • hex (String)

    hex-encoded BEEF data

Returns:

  • (Beef)

    the parsed BEEF bundle



171
172
173
# File 'lib/bsv/transaction/beef.rb', line 171

def self.from_hex(hex)
  from_binary(BSV::Primitives::Hex.decode(hex, name: 'BEEF hex'))
end

Instance Method Details

#find_atomic_transaction(txid) ⇒ Transaction?

Find a transaction and recursively wire its ancestry (source transactions and merkle paths) for atomic proof validation.

Parameters:

  • txid (String)

    32-byte txid in display byte order

Returns:

  • (Transaction, nil)

    the transaction with full proof tree, or nil



287
288
289
290
291
292
293
# File 'lib/bsv/transaction/beef.rb', line 287

def find_atomic_transaction(txid)
  tx = find_transaction(txid)
  return unless tx

  wire_ancestry(tx)
  tx
end

#find_bump(txid) ⇒ MerklePath?

Find the merkle path (BUMP) for a transaction by its txid.

First checks the transaction-table entries, then scans @bumps directly for a BUMP whose level-0 leaves contain the txid.

Parameters:

  • txid (String)

    32-byte txid in display byte order

Returns:

  • (MerklePath, nil)

    the merkle path, or nil if not found



258
259
260
261
262
263
264
265
266
267
268
# File 'lib/bsv/transaction/beef.rb', line 258

def find_bump(txid)
  # Check transaction-table entries first (fast path)
  bt = @transactions.find { |entry| entry.txid == txid && entry.format == FORMAT_RAW_TX_AND_BUMP }
  return bt.transaction&.merkle_path || (bt.bump_index && @bumps[bt.bump_index]) if bt

  # F5.8: also scan @bumps directly for a path containing the txid leaf
  txid_internal = txid.reverse
  @bumps.find do |bump|
    bump.path[0]&.any? { |leaf| leaf.hash == txid_internal }
  end
end

#find_transaction(txid) ⇒ Transaction?

Find a transaction in the bundle by its transaction ID.

Parameters:

  • txid (String)

    32-byte txid in display byte order

Returns:

  • (Transaction, nil)

    the matching transaction, or nil



244
245
246
247
248
249
# File 'lib/bsv/transaction/beef.rb', line 244

def find_transaction(txid)
  @transactions.each do |beef_tx|
    return beef_tx.transaction if beef_tx.transaction&.txid == txid
  end
  nil
end

#find_transaction_for_signing(txid) ⇒ Transaction?

Find a transaction with all source_transactions wired for signing.

Parameters:

  • txid (String)

    32-byte txid in display byte order

Returns:

  • (Transaction, nil)

    the transaction with wired inputs, or nil



274
275
276
277
278
279
280
# File 'lib/bsv/transaction/beef.rb', line 274

def find_transaction_for_signing(txid)
  tx = find_transaction(txid)
  return unless tx

  wire_inputs(tx)
  tx
end

#make_txid_only(txid) ⇒ BeefTx?

Convert a transaction entry to TXID-only format.

Parameters:

  • txid (String)

    32-byte txid in display byte order

Returns:

  • (BeefTx, nil)

    the converted entry, or nil if not found



487
488
489
490
491
492
# File 'lib/bsv/transaction/beef.rb', line 487

def make_txid_only(txid)
  idx = @transactions.index { |bt| bt.txid == txid }
  return unless idx

  @transactions[idx] = BeefTx.new(format: FORMAT_TXID_ONLY, known_txid: txid)
end

#merge(other) ⇒ self

Merge all BUMPs and transactions from another BEEF bundle.

BUMP indices are remapped during merge. New BeefTx instances are constructed rather than sharing references with the source bundle (F5.9).

Parameters:

  • other (Beef)

    the BEEF bundle to merge from

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if a transaction in other has a bump_index that does not point to any BUMP in other.bumps (i.e. the source bundle is internally inconsistent)



439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/bsv/transaction/beef.rb', line 439

def merge(other)
  # Build index remap for BUMPs
  bump_remap = {}
  other.bumps.each_with_index do |bump, old_idx|
    bump_remap[old_idx] = merge_bump(bump)
  end

  # Merge transactions with remapped BUMP indices, constructing new
  # BeefTx instances rather than sharing source references (F5.9).
  other.transactions.each do |beef_tx|
    case beef_tx.format
    when FORMAT_TXID_ONLY
      next if @transactions.any? { |bt| bt.txid == beef_tx.known_txid }

      @transactions << BeefTx.new(format: FORMAT_TXID_ONLY, known_txid: beef_tx.known_txid)
    else
      next if @transactions.any? { |bt| bt.txid == beef_tx.txid }

      if beef_tx.format == FORMAT_RAW_TX_AND_BUMP && beef_tx.bump_index
        new_idx = bump_remap[beef_tx.bump_index]
        if new_idx.nil?
          raise ArgumentError,
                "source BEEF has inconsistent bump_index #{beef_tx.bump_index} " \
                "(source has #{other.bumps.length} bumps); refusing to write a stale reference"
        end

        # F5.9: construct a new BeefTx with a dup'd Transaction
        # so mutations to the merged bundle don't affect the source.
        tx = beef_tx.transaction.dup
        tx.merkle_path = @bumps[new_idx]
        @transactions << BeefTx.new(
          format: FORMAT_RAW_TX_AND_BUMP,
          transaction: tx,
          bump_index: new_idx
        )
      else
        @transactions << BeefTx.new(format: FORMAT_RAW_TX, transaction: beef_tx.transaction.dup)
      end
    end
  end

  self
end

#merge_bump(merkle_path) ⇒ Integer

Add or deduplicate a merkle path (BUMP) in this BEEF bundle.

If an existing BUMP shares the same block_height and merkle root, it is combined (via MerklePath#combine) and the existing index is returned. Otherwise the BUMP is appended.

After the BUMP is stored, any existing FORMAT_RAW_TX transactions whose txid appears in the new BUMP’s level-0 leaves are retroactively upgraded to FORMAT_RAW_TX_AND_BUMP (F5.6).

Parameters:

Returns:

  • (Integer)

    the index of the (possibly merged) BUMP



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'lib/bsv/transaction/beef.rb', line 317

def merge_bump(merkle_path)
  root = merkle_path.compute_root
  idx = nil
  @bumps.each_with_index do |existing, i|
    next unless existing.block_height == merkle_path.block_height

    next unless existing.compute_root == root

    existing.combine(merkle_path)
    idx = i
    break
  end

  if idx.nil?
    @bumps << merkle_path
    idx = @bumps.length - 1
  end

  # F5.6: retroactively link existing FORMAT_RAW_TX entries whose txid
  # appears in the new BUMP's level-0 leaves.
  bump = @bumps[idx]
  level0_leaves = bump.path[0] || []
  level0_internal = level0_leaves.map(&:hash).compact.to_set
  @transactions.each_with_index do |bt, i|
    next unless bt.format == FORMAT_RAW_TX && bt.transaction
    next unless level0_internal.include?(bt.transaction.txid.reverse)

    bt.transaction.merkle_path ||= bump
    @transactions[i] = BeefTx.new(
      format: FORMAT_RAW_TX_AND_BUMP,
      transaction: bt.transaction,
      bump_index: idx
    )
  end

  idx
end

#merge_raw_tx(raw_bytes, bump_index: nil) ⇒ BeefTx

Add a transaction from raw binary data.

If the transaction already exists, upgrades weaker entries to stronger formats when a raw tx or bump_index is now available (F5.7).

Parameters:

  • raw_bytes (String)

    raw transaction binary

  • bump_index (Integer, nil) (defaults to: nil)

    optional BUMP index

Returns:

  • (BeefTx)

    the new or upgraded BeefTx entry



400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
# File 'lib/bsv/transaction/beef.rb', line 400

def merge_raw_tx(raw_bytes, bump_index: nil)
  tx = Transaction.from_binary(raw_bytes)

  if bump_index
    unless bump_index.is_a?(Integer) && bump_index >= 0 && bump_index < @bumps.length
      raise ArgumentError,
            "bump_index #{bump_index.inspect} out of range (have #{@bumps.length} bumps)"
    end

    tx.merkle_path = @bumps[bump_index]
  end

  existing_idx = @transactions.index { |bt| bt.txid == tx.txid }
  if existing_idx
    existing = @transactions[existing_idx]
    upgraded = upgrade_beef_tx(existing, tx, bump_index: bump_index)
    @transactions[existing_idx] = upgraded if upgraded
    return @transactions[existing_idx]
  end

  entry = if bump_index
            BeefTx.new(format: FORMAT_RAW_TX_AND_BUMP, transaction: tx, bump_index: bump_index)
          else
            BeefTx.new(format: FORMAT_RAW_TX, transaction: tx)
          end
  @transactions << entry
  entry
end

#merge_transaction(tx) ⇒ BeefTx

Add a transaction to this BEEF bundle.

Recursively merges the transaction’s ancestors (via source_transaction references on inputs) and their merkle paths. Duplicate transactions (same txid) are upgraded if a stronger format is now available (F5.7): TXID_ONLY → RAW_TX or RAW_TX_AND_BUMP; RAW_TX → RAW_TX_AND_BUMP.

Parameters:

Returns:

  • (BeefTx)

    the (possibly existing or upgraded) BeefTx entry



364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/bsv/transaction/beef.rb', line 364

def merge_transaction(tx)
  txid = tx.txid

  # Check for existing entry and upgrade if a stronger format is available
  existing_idx = @transactions.index { |bt| bt.txid == txid }
  if existing_idx
    existing = @transactions[existing_idx]
    upgraded = upgrade_beef_tx(existing, tx)
    @transactions[existing_idx] = upgraded if upgraded
    return @transactions[existing_idx]
  end

  # Recursively merge ancestors first (dependency order)
  tx.inputs.each do |input|
    merge_transaction(input.source_transaction) if input.source_transaction
  end

  # Merge this transaction's BUMP if it has one
  entry = if tx.merkle_path
            bump_idx = merge_bump(tx.merkle_path)
            BeefTx.new(format: FORMAT_RAW_TX_AND_BUMP, transaction: tx, bump_index: bump_idx)
          else
            BeefTx.new(format: FORMAT_RAW_TX, transaction: tx)
          end
  @transactions << entry
  entry
end

#sort_transactions!self

Sort transactions in topological (dependency) order in place.

After sorting, every transaction’s input ancestors appear before it in the array. This is required for correct BEEF serialisation.

Transactions that form a cycle (i.e. cannot be topologically sorted) are moved to @txs_not_valid rather than being silently dropped (F5.5).

Returns:

  • (self)


584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
# File 'lib/bsv/transaction/beef.rb', line 584

def sort_transactions!
  return self if @transactions.length <= 1

  txid_index = {}
  @transactions.each_with_index { |bt, i| txid_index[bt.txid] = i }

  # Build adjacency: for each tx, which other txs must come before it?
  in_degree = Array.new(@transactions.length, 0)
  dependents = Array.new(@transactions.length) { [] }

  @transactions.each_with_index do |bt, i|
    next unless bt.transaction

    bt.transaction.inputs.each do |input|
      dep_idx = txid_index[input.prev_tx_id.reverse]
      next unless dep_idx

      dependents[dep_idx] << i
      in_degree[i] += 1
    end
  end

  # Kahn's algorithm
  queue = (0...@transactions.length).select { |i| in_degree[i].zero? }
  sorted = []

  until queue.empty?
    idx = queue.shift
    sorted << @transactions[idx]
    dependents[idx].each do |dep|
      in_degree[dep] -= 1
      queue << dep if in_degree[dep].zero?
    end
  end

  # F5.5: preserve unsortable (cyclic) transactions rather than silently dropping them
  if sorted.length < @transactions.length
    sorted_set = sorted.to_set(&:txid)
    @txs_not_valid = @transactions.reject { |bt| sorted_set.include?(bt.txid) }
  end

  @transactions = sorted
  self
end

#to_atomic_binary(subject_txid) ⇒ String

Serialise as Atomic BEEF (BRC-95), wrapping V2 data with a subject txid.

Parameters:

  • subject_txid (String)

    32-byte subject transaction ID

Returns:

  • (String)

    raw Atomic BEEF binary



228
229
230
231
232
233
234
235
236
# File 'lib/bsv/transaction/beef.rb', line 228

def to_atomic_binary(subject_txid)
  buf = [ATOMIC_BEEF].pack('V')
  # Write subject txid in internal byte order (reverse of display order),
  # matching JS and Go SDK conventions for Bitcoin binary formats.
  buf << subject_txid.b.reverse
  # BRC-95: inner envelope is always V2
  buf << to_binary(version: BEEF_V2)
  buf
end

#to_atomic_hex(subject_txid) ⇒ String

Serialise as Atomic BEEF (BRC-95) hex string.

Parameters:

  • subject_txid (String)

    32-byte subject transaction ID

Returns:

  • (String)

    hex-encoded Atomic BEEF



299
300
301
# File 'lib/bsv/transaction/beef.rb', line 299

def to_atomic_hex(subject_txid)
  to_atomic_binary(subject_txid).unpack1('H*')
end

#to_binary(version: BEEF_V1) ⇒ String

Serialise the BEEF bundle to binary format.

Defaults to V1 (BRC-62) for compatibility with ARC and the reference TS SDK. Pass version: BEEF_V2 for BRC-96 format.

Parameters:

  • version (Integer) (defaults to: BEEF_V1)

    BEEF_V1 (default) or BEEF_V2

Returns:

  • (String)

    raw BEEF binary

Raises:

  • (ArgumentError)

    if version is BEEF_V1 and the bundle contains any FORMAT_TXID_ONLY entries (V1 / BRC-62 has no TXID-only format; pass version: BEEF_V2 to serialise such bundles)



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/bsv/transaction/beef.rb', line 187

def to_binary(version: BEEF_V1)
  if version == BEEF_V1 && @transactions.any? { |bt| bt.format == FORMAT_TXID_ONLY }
    raise ArgumentError,
          'BEEF V1 (BRC-62) does not support FORMAT_TXID_ONLY entries; pass version: BEEF_V2 to serialise this bundle'
  end

  # F5.5/F5.20: ensure transactions are in dependency order before serialising
  sort_transactions!

  buf = [version].pack('V')

  buf << VarInt.encode(@bumps.length)
  @bumps.each { |bump| buf << bump.to_binary }

  buf << VarInt.encode(@transactions.length)
  @transactions.each do |beef_tx|
    if version == BEEF_V2
      write_v2_tx(buf, beef_tx)
    else
      write_v1_tx(buf, beef_tx)
    end
  end

  buf
end

#to_hexString

Serialise the BEEF bundle to a hex string.

Uses the bundle’s own @version, so a BEEF parsed from V2 round-trips to V2 hex, and a BEEF parsed from V1 (or freshly constructed via the default constructor) round-trips to V1 hex.

Returns:

  • (String)

    hex-encoded BEEF data



220
221
222
# File 'lib/bsv/transaction/beef.rb', line 220

def to_hex
  to_binary(version: @version).unpack1('H*')
end

#valid?(allow_txid_only: false) ⇒ Boolean

Check structural validity of the BEEF bundle.

A valid BEEF has every transaction either:

  • proven (has a BUMP / merkle_path), or

  • all its inputs reference transactions that are themselves valid within this bundle.

For FORMAT_RAW_TX_AND_BUMP entries, the BUMP linkage and computed root are also verified (F5.4).

Parameters:

  • allow_txid_only (Boolean) (defaults to: false)

    whether TXID-only entries count as valid (default: false)

Returns:

  • (Boolean)

    true if structurally valid



508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
# File 'lib/bsv/transaction/beef.rb', line 508

def valid?(allow_txid_only: false)
  # TXID-only entries are invalid unless explicitly allowed
  has_txid_only = @transactions.any? { |bt| bt.format == FORMAT_TXID_ONLY }
  return false if has_txid_only && !allow_txid_only

  # F5.4: verify BUMP linkage and computed root for each proven transaction
  @transactions.each do |bt|
    next unless bt.format == FORMAT_RAW_TX_AND_BUMP

    # Must have a BUMP
    bump = bt.transaction&.merkle_path || (bt.bump_index && @bumps[bt.bump_index])
    return false unless bump

    # The txid must appear as a leaf in the BUMP and compute a valid root
    begin
      bump.compute_root(bt.transaction.txid.reverse)
    rescue ArgumentError
      return false
    end
  end

  known_txids = build_known_txids(allow_txid_only)

  pending = @transactions.select { |bt| bt.transaction && !known_txids.include?(bt.txid) }

  # Iteratively resolve: if all inputs of a tx are known, it becomes known
  changed = true
  while changed
    changed = false
    pending.reject! do |bt|
      all_inputs_known = bt.transaction.inputs.all? do |input|
        known_txids.include?(input.prev_tx_id.reverse)
      end
      if all_inputs_known
        known_txids.add(bt.txid)
        changed = true
      end
      all_inputs_known
    end
  end

  pending.empty?
end

#verify(chain_tracker = nil, allow_txid_only: false) ⇒ Boolean

Verify the BEEF bundle against a chain tracker (SPV).

Calls #valid? first for structural checks, then optionally verifies each BUMP’s computed merkle root against the chain tracker (F5.3).

Parameters:

  • chain_tracker (#valid_root_for_height?) (defaults to: nil)

    optional chain tracker (see ChainTracker) that responds to valid_root_for_height?(root_hex, block_height)

  • allow_txid_only (Boolean) (defaults to: false)

    passed to #valid?

Returns:

  • (Boolean)

    true if the bundle is structurally valid and, when a chain tracker is provided, all BUMP roots are confirmed by the chain



563
564
565
566
567
568
569
570
571
572
573
# File 'lib/bsv/transaction/beef.rb', line 563

def verify(chain_tracker = nil, allow_txid_only: false)
  return false unless valid?(allow_txid_only: allow_txid_only)
  return true unless chain_tracker

  @bumps.each do |bump|
    root_hex = bump.compute_root.reverse.unpack1('H*')
    return false unless chain_tracker.valid_root_for_height?(root_hex, bump.block_height)
  end

  true
end