Class: BSV::Overlay::TopicBroadcaster

Inherits:
Object
  • Object
show all
Defined in:
lib/bsv/overlay/topic_broadcaster.rb

Overview

Broadcasts transactions to overlay topics via SHIP (Service Host Interconnect Protocol).

Discovers interested overlay hosts by querying the ls_ship SLAP service, then dispatches a TaggedBEEF to each host in parallel and verifies acknowledgements according to the configured requirements.

Topic validation

All topics must be non-empty strings beginning with tm_. An empty topics array or a topic without the tm_ prefix raises ArgumentError.

Acknowledgement modes

Three independent ack modes may be combined:

  • require_ack_from_any_host (default: ‘all’) — at least one successful host must satisfy the requirement.

  • require_ack_from_all_hosts (default: []) — every successful host must satisfy the requirement. An empty array disables this check.

  • require_ack_from_specific_hosts (default: {}) — named hosts must each satisfy their individual requirement.

Requirement values:

  • ‘all’ — the host must have acknowledged every one of the broadcaster’s topics.

  • ‘any’ — the host must have acknowledged at least one topic.

  • Array<String> — the host must have acknowledged all topics in the array.

Host caching

SHIP host discovery results are cached for SHIP_CACHE_TTL seconds (5 minutes) to avoid redundant SLAP queries on repeated broadcasts.

Constant Summary collapse

SHIP_CACHE_TTL =

Seconds before the SHIP host cache expires.

300

Instance Method Summary collapse

Constructor Details

#initialize(topics, network_preset: :mainnet, facilitator: nil, resolver: nil, require_ack_from_all_hosts: [], require_ack_from_any_host: 'all', require_ack_from_specific_hosts: {}) ⇒ TopicBroadcaster

Returns a new instance of TopicBroadcaster.

Parameters:

  • topics (Array<String>)

    overlay topic names (must start with tm_)

  • network_preset (Symbol) (defaults to: :mainnet)

    :mainnet, :testnet, or :local

  • facilitator (BroadcastFacilitator, nil) (defaults to: nil)

    injectable facilitator

  • resolver (LookupResolver, nil) (defaults to: nil)

    injectable resolver

  • require_ack_from_all_hosts (Array, String) (defaults to: [])

    requirement all hosts must satisfy

  • require_ack_from_any_host (String) (defaults to: 'all')

    requirement at least one host must satisfy

  • require_ack_from_specific_hosts (Hash) (defaults to: {})

    per-host requirements



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/bsv/overlay/topic_broadcaster.rb', line 51

def initialize(
  topics,
  network_preset: :mainnet,
  facilitator: nil,
  resolver: nil,
  require_ack_from_all_hosts: [],
  require_ack_from_any_host: 'all',
  require_ack_from_specific_hosts: {}
)
  validate_topics!(topics)

  @topics          = topics.dup.freeze
  @network_preset  = network_preset
  @facilitator     = facilitator || default_facilitator
  @resolver        = resolver    || default_resolver

  @require_ack_from_all_hosts      = require_ack_from_all_hosts
  @require_ack_from_any_host       = require_ack_from_any_host
  @require_ack_from_specific_hosts = require_ack_from_specific_hosts

  @ship_cache       = nil
  @ship_cache_at    = nil
  @ship_cache_mutex = Mutex.new
end

Instance Method Details

#broadcast(tx) ⇒ OverlayBroadcastResult

Broadcast a transaction to all interested overlay hosts.

Parameters:

Returns:



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/bsv/overlay/topic_broadcaster.rb', line 80

def broadcast(tx)
  beef = serialise_beef(tx)
  return beef if beef.is_a?(OverlayBroadcastResult)

  if local?
    host_topics = { 'http://localhost:8080' => Set.new(@topics) }
  else
    host_topics = find_interested_hosts
    if host_topics.empty?
      return error_result(
        'ERR_NO_HOSTS_INTERESTED',
        "No #{@network_preset} hosts are interested in receiving this transaction."
      )
    end
  end

  results = dispatch(beef, host_topics)

  successful = results.compact
  if successful.empty?
    return error_result(
      'ERR_ALL_HOSTS_REJECTED',
      "All #{@network_preset} topical hosts have rejected the transaction."
    )
  end

  host_acks = build_host_acks(successful)

  ack_check = check_ack_requirements(host_acks)
  return ack_check if ack_check.is_a?(OverlayBroadcastResult)

  OverlayBroadcastResult.new(
    status: 'success',
    txid: tx.txid_hex,
    message: "Sent to #{successful.size} Overlay Service host(s)."
  )
end

#find_interested_hostsHash{String => Set<String>}

Discover overlay hosts interested in the broadcaster’s topics via SHIP.

Results are cached for SHIP_CACHE_TTL seconds.

Returns:

  • (Hash{String => Set<String>})

    map of host URL to set of interested topics



123
124
125
126
127
128
129
130
131
132
# File 'lib/bsv/overlay/topic_broadcaster.rb', line 123

def find_interested_hosts
  @ship_cache_mutex.synchronize do
    return @ship_cache.transform_values(&:dup) if @ship_cache && (Time.now.to_f - @ship_cache_at) < SHIP_CACHE_TTL

    hosts = fetch_ship_hosts
    @ship_cache    = hosts
    @ship_cache_at = Time.now.to_f
    hosts.transform_values(&:dup)
  end
end