Class: BSV::Wallet::LocalPool

Inherits:
Object
  • Object
show all
Includes:
Interface::UTXOPool
Defined in:
lib/bsv/wallet/utxo_pool/local_pool.rb

Overview

In-process UTXO pool backed by the wallet’s own Store.

LocalPool maintains a named basket of pre-funded outputs that callers can acquire for use as inputs in transactions. Acquired outputs are locked via lock_utxos with no_send: true, making them exempt from release_stale_pending! sweeps (bug #1 fix). Releasing an output returns it to :spendable state.

When the available count drops to or below the low-water mark, acquire signals the optional replenishment worker to top up the pool. The signal uses = (not <) so the worker is triggered exactly at the boundary (bug #3 fix).

Thread safety

All public methods are safe to call concurrently. acquire holds the storage-level mutex via lock_utxos, which performs an atomic find-and-lock. The pool-level @mutex guards @state and @replenisher.

Pool basket naming

The basket name is derived as "pool:#{name}", placing all pool outputs in the structured pool: zone.

State values

status[:state] is one of:

:healthy       — pool has outputs above the low-water mark
:replenishing  — pool is below low-water mark and worker is running
:depleted      — pool is empty and worker cannot replenish
:shutdown      — pool has been shut down

Constant Summary

Constants included from Interface::UTXOPool

Interface::UTXOPool::MAX_RETRIES

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, storage:, wallet_client:, target_count:, target_satoshis:, low_water_mark:) ⇒ LocalPool

Returns a new instance of LocalPool.

Parameters:

  • name (String)

    pool identifier; basket becomes "pool:#{name}"

  • storage (Store)

    wallet storage adapter

  • wallet_client (#create_action)

    wallet client for replenishment

  • target_count (Integer)

    desired number of UTXOs in the pool

  • target_satoshis (Integer)

    satoshi value per pool output

  • low_water_mark (Integer)

    available count at or below which replenishment is triggered



51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/bsv/wallet/utxo_pool/local_pool.rb', line 51

def initialize(name:, storage:, wallet_client:, target_count:, target_satoshis:, low_water_mark:)
  @name             = name
  @basket           = "pool:#{name}"
  @storage          = storage
  @wallet_client    = wallet_client
  @target_count     = target_count
  @target_satoshis  = target_satoshis
  @low_water_mark   = low_water_mark
  @replenisher      = nil
  @state            = :healthy
  @mutex            = Mutex.new
end

Instance Attribute Details

#basketObject (readonly)

Returns the value of attribute basket.



42
43
44
# File 'lib/bsv/wallet/utxo_pool/local_pool.rb', line 42

def basket
  @basket
end

#nameObject (readonly)

Returns the value of attribute name.



42
43
44
# File 'lib/bsv/wallet/utxo_pool/local_pool.rb', line 42

def name
  @name
end

#replenisher=(value) ⇒ Object (writeonly)

Sets the attribute replenisher

Parameters:

  • value

    the value to set the attribute replenisher to.



43
44
45
# File 'lib/bsv/wallet/utxo_pool/local_pool.rb', line 43

def replenisher=(value)
  @replenisher = value
end

#storageObject (readonly)

Returns the value of attribute storage.



42
43
44
# File 'lib/bsv/wallet/utxo_pool/local_pool.rb', line 42

def storage
  @storage
end

#target_countObject (readonly)

Returns the value of attribute target_count.



42
43
44
# File 'lib/bsv/wallet/utxo_pool/local_pool.rb', line 42

def target_count
  @target_count
end

#target_satoshisObject (readonly)

Returns the value of attribute target_satoshis.



42
43
44
# File 'lib/bsv/wallet/utxo_pool/local_pool.rb', line 42

def target_satoshis
  @target_satoshis
end

Instance Method Details

#acquireString

Acquires an available output from the pool and locks it.

Finds spendable outputs in the pool basket, then atomically locks the first candidate via lock_utxos with no_send: true. On contention (another thread claimed the output first), retries up to UTXOPool::MAX_RETRIES times before raising PoolDepletedError.

After a successful acquisition, signals the replenisher if the available count has dropped to or below the low-water mark.

Returns:

  • (String)

    the locked outpoint string ("txid.vout")

Raises:



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/bsv/wallet/utxo_pool/local_pool.rb', line 76

def acquire
  @mutex.synchronize { raise PoolDepletedError, @name if @state == :shutdown }

  MAX_RETRIES.times do
    candidates = @storage.find_spendable_outputs(basket: @basket)
    raise PoolDepletedError, @name if candidates.empty?

    outpoint  = candidates.first[:outpoint]
    reference = "pool-acquire-#{SecureRandom.hex(8)}"
    locked    = @storage.lock_utxos([outpoint], reference: reference, no_send: true)

    next if locked.empty?

    maybe_signal_replenisher(candidates.size - 1)
    return outpoint
  end

  raise PoolDepletedError, @name
end

#release(outpoint) ⇒ void

This method returns an undefined value.

Releases a previously acquired output back to :spendable state.

Parameters:

  • outpoint (String)

    the outpoint string to release

Raises:



101
102
103
# File 'lib/bsv/wallet/utxo_pool/local_pool.rb', line 101

def release(outpoint)
  @storage.update_output_state(outpoint, :spendable)
end

#shutdownvoid

This method returns an undefined value.

Shuts down the pool.

Stops the replenishment worker if one is running and marks the pool as :shutdown. Idempotent — safe to call more than once.



125
126
127
128
129
130
131
132
# File 'lib/bsv/wallet/utxo_pool/local_pool.rb', line 125

def shutdown
  @mutex.synchronize do
    return if @state == :shutdown

    @replenisher&.stop
    @state = :shutdown
  end
end

#statusHash

Returns a summary of the pool’s current state.

Returns:

  • (Hash)

    status hash with keys :available, :target, :satoshis_committed, and :state



109
110
111
112
113
114
115
116
117
# File 'lib/bsv/wallet/utxo_pool/local_pool.rb', line 109

def status
  spendable = @storage.find_spendable_outputs(basket: @basket)
  {
    available: spendable.size,
    target: @target_count,
    satoshis_committed: spendable.sum { |o| o[:satoshis].to_i },
    state: current_state(spendable.size)
  }
end