Class: BSV::Network::Services

Inherits:
Object
  • Object
show all
Defined in:
lib/bsv/network/services.rb

Overview

Porcelain routing layer above SDK providers/protocols.

Same call(command, *args, **kwargs) interface as Provider — drop-in replacement. Adds capability-based routing, fallback on retryable errors, per-provider rate limiting, response normalization, and opportunistic sibling data caching.

Examples:

services = BSV::Network::Services.new(providers: [gorilla_pool, woc])
services.call(:broadcast, tx)
services.call(:get_tx, txid)
services.call(:get_utxos, address)

Defined Under Namespace

Classes: TokenBucket

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(providers:) ⇒ Services

Returns a new instance of Services.

Parameters:

  • providers (Array<BSV::Network::Provider>)

    providers in priority order

Raises:

  • (ArgumentError)


22
23
24
25
26
27
28
29
30
31
32
# File 'lib/bsv/network/services.rb', line 22

def initialize(providers:)
  raise ArgumentError, 'at least one provider is required' if providers.nil? || providers.empty?

  @providers = providers.dup.freeze
  @buckets = providers.each_with_object({}) do |p, h|
    h[p] = TokenBucket.new(p.rate_limit) if p.rate_limit
  end
  @sibling_memo = {}
  @broadcast_affinity = {}
  @mutex = Mutex.new
end

Instance Attribute Details

#providersArray<BSV::Network::Provider> (readonly)

Returns the registered providers (frozen at construction time).

Returns:

  • (Array<BSV::Network::Provider>)


125
126
127
# File 'lib/bsv/network/services.rb', line 125

def providers
  @providers
end

Instance Method Details

#call(command, *args, **kwargs) ⇒ BSV::Network::ProtocolResponse

Dispatch a command with provider routing and fallback.

Parameters:

  • command (Symbol)

    SDK command name

  • args (Array)

    positional arguments forwarded to the provider

  • kwargs (Hash)

    keyword arguments forwarded to the provider

Returns:

  • (BSV::Network::ProtocolResponse)


40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/bsv/network/services.rb', line 40

def call(command, *args, **kwargs)
  sym = command.to_sym

  # Serve from sibling memo if available
  memo_result = check_sibling_memo(sym, args, kwargs)
  return memo_result if memo_result

  candidates = candidates_for(sym, args, kwargs)
  return no_provider_response(sym) if candidates.empty?

  last_error = nil

  candidates.each do |provider|
    acquire_rate_limit!(provider)

    result = provider.call(sym, *args, **kwargs)

    if result.http_success?
      stash_siblings(sym, result, args, kwargs)
      normalized = normalize(sym, result)
      record_affinity(sym, provider, normalized)
      return normalized
    end

    return result if result.http_not_found?

    last_error = result
    break unless result.retryable?
  end

  last_error
end

#commandsSet<Symbol>

Union of all commands available across all registered providers.

Returns:

  • (Set<Symbol>)


76
77
78
# File 'lib/bsv/network/services.rb', line 76

def commands
  @providers.reduce(Set.new) { |acc, p| acc | p.commands }
end

#fetch!(entity) ⇒ BSV::Network::ProtocolResponse

Fetch state from the network into an entity.

Calls entity.fetch_command and entity.fetch_args, dispatches through the routing layer, and writes the response back on success.

Parameters:

  • entity (#fetch_command, #fetch_args, #write!)

    a Fetchable entity

Returns:

  • (BSV::Network::ProtocolResponse)


108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/bsv/network/services.rb', line 108

def fetch!(entity)
  command = entity.fetch_command
  args = entity.fetch_args
  response = call(command, **args)

  if response.http_success?
    entity.write!(response)
  else
    BSV.logger&.warn { "[Services] fetch! failed: #{response.error_message}" }
  end

  response
end

#push!(entity) ⇒ BSV::Network::ProtocolResponse

Push an entity to the network.

Calls entity.push_command and entity.push_payload, dispatches through the routing layer, and writes the response back on success.

Parameters:

  • entity (#push_command, #push_payload, #write!)

    a Pushable entity

Returns:

  • (BSV::Network::ProtocolResponse)


87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/bsv/network/services.rb', line 87

def push!(entity)
  command = entity.push_command
  payload = entity.push_payload
  response = call(command, payload)

  if response.http_success?
    entity.write!(response)
  else
    BSV.logger&.warn { "[Services] push! failed: #{response.error_message}" }
  end

  response
end