pub struct HoprTicketManager<S, Q> {
pub(crate) out_idx_tracker: OutgoingIndexCache,
pub(crate) channel_tickets: DashMap<ChannelId, ChannelTicketQueue<ValueCachedQueue<Q>>>,
pub(crate) store: Arc<RwLock<S>>,
}Expand description
Keeps track of indices for outgoing tickets and (optionally) of incoming redeemable tickets.
The capabilities of the HoprTicketManager are given by the store S:
- if the store implements
OutgoingIndexStore, the outgoing index tracking functions are available. - if the store implements
TicketQueueStore, the incoming redeemable ticket management is available.
It is possible to have both implementations in the same store object. Some store implementations may offer
persistence, others may not. The HoprTicketManager takes ownership of the store object, and other
processes should not attempt to change the store externally. For this reason, stores should not be cloneable.
The HOPR node type gives typical use-cases of the HoprTicketManager:
- Entry/Exit nodes only need to provide an
OutgoingIndexStore, since they are dealing with outgoing tickets only. - Relay nodes need to provide a store which implements both
OutgoingIndexStore + TicketQueueStore, because they need to deal with both outgoing tickets and incoming redeemable tickets.
To synchronize the on-chain state with the store, it is advised to call
sync_outgoing_channels (and
sync_incoming_channels if applicable to the chosen store) early
after the construction of the manager, to make sure outdated data is discarded early. This is typically done only
once after construction and not needed to be done during the life-time of the manager.
The manager is safe to be shared via an Arc. It is typically shared between the packet processing pipelines
(outgoing on Entry/Exit nodes, incoming on Relay nodes) and some higher level component
that performs redeemable ticket extractions (in the case of a Relay node).
§Usage in outgoing packet pipeline
The outgoing packet pipeline usually just calls the
create_multihop_ticket to create a ticket for the next hop on a
multi-hop path. To create zero/last-hop tickets, the ticket manager is not needed as these tickets essentially
contain bogus data and there’s no channel required.
The outgoing indices are not automatically synchronized back to the underlying store for performance reasons.
The user is responsible for calling save_outgoing_indices to save
the outgoing indices to the store.
This usage is typical for all kinds of nodes (Entry/Relay/Exit).
§Usage in incoming packet pipeline
The incoming packet pipeline usually just calls the
insert_incoming_ticket whenever a new winning, redeemable ticket is
received on an incoming channel.
This usage is typical for Relay nodes only.
§Redeemable ticket extraction
On Relay nodes, the manager maintains FIFO queues of redeemable tickets per incoming channel. There are two ways to extract tickets from the queue on a Relay:
- redeeming them via
redeem_stream - neglecting them via
neglect_tickets
Both of these operations extract the tickets in the FIFO order from the queue, making sure that they are always processed in their natural order (by epoch and index).
Both ticket extraction operations are mutually exclusive and cannot be performed simultaneously.
§Locking and lock-contention
There are several methods in the HoprTicketManager object that are expected to be called
in the highly performance-sensitive code, on a per-packet basis.
§Outgoing ticket creation
The create_multihop_ticket method is designed to be
high-performance and to be called per each outgoing packet. It is using only atomics to track the outgoing
ticket index for a channel. The synchronization to the underlying storage is done on-demand by calling
save_outgoing_indices, making quick snapshots of the current state of outgoing indices.
No significant contention is expected unless save_outgoing_indices is called very frequently.`
§Incoming winning ticket retrieval
The insert_incoming_ticket method is designed to be
high-performance and to be called per each incoming packet after it has been forwarded to a next hop.
This operation acquires the write-part of an RW lock (per incoming channel). This may block the hot-path only if one of the following (also write) operations is performed: 1. Ticket redemption has just finished in that particular channel, and the redeemed ticket is dropped from the same incoming channel queue. 2. Ticket neglection has just finished in that particular channel, and the neglected ticket is dropped from the same incoming channel queue.
Both of these operations happen rarely, and the write lock is usually held only for a short time. In addition, incoming winning tickets are not supposed to usually happen very often. Therefore, high contention on the write lock is not expected.
§Incoming unacknowledged ticket verification
The unrealized_value method is designed to be high-performance
and to be called per each incoming packet before it is forwarded to a next hop.
This operation acquires the read-part of an RW lock (per incoming channel). This may block the hot-path only if one of the following (write) operations is performed at the same moment: 1. A new incoming winning ticket is inserted into the same incoming channel queue. 2. Ticket redemption has just finished in that particular channel, and the redeemed ticket is dropped from the same incoming channel queue. 3. Ticket neglection has just finished in that particular channel, and the neglected ticket is dropped from the same incoming channel queue.
All 3 of these operations are not expected to happen very often on a single channel; therefore, high contention on the RW lock is not expected.
Fields§
§out_idx_tracker: OutgoingIndexCache§channel_tickets: DashMap<ChannelId, ChannelTicketQueue<ValueCachedQueue<Q>>>§store: Arc<RwLock<S>>Implementations§
Source§impl<S, Q> HoprTicketManager<S, Q>
impl<S, Q> HoprTicketManager<S, Q>
Sourcepub fn new(store: S) -> Result<Self, TicketManagerError>
pub fn new(store: S) -> Result<Self, TicketManagerError>
Creates a new ticket manager instance given the desired store.
The instance is supposed to take complete ownership of the store object. The store
implementations should not allow
It is advised to call HoprTicketManager::sync_from_outgoing_channels and
HoprTicketManager::sync_from_incoming_channels at least once before the manager
is used any further.
Sourcepub(crate) fn next_outgoing_ticket_index(&self, channel: &ChannelEntry) -> u64
pub(crate) fn next_outgoing_ticket_index(&self, channel: &ChannelEntry) -> u64
Gets the next usable ticket index for an outgoing ticket in the given channel and epoch.
This operation is fast and does not immediately put the index into the OutgoingIndexStore.
The returned value is always guaranteed to be greater or equal to the ticket index on the given channel.
Sourcepub fn save_outgoing_indices(&self) -> Result<(), TicketManagerError>
pub fn save_outgoing_indices(&self) -> Result<(), TicketManagerError>
Saves outgoing ticket indices back to the store.
The operation does nothing if there were no new tickets created on any tracked channel.
Sourcepub fn sync_from_outgoing_channels(
&self,
outgoing_channels: &[ChannelEntry],
) -> Result<(), TicketManagerError>
pub fn sync_from_outgoing_channels( &self, outgoing_channels: &[ChannelEntry], ) -> Result<(), TicketManagerError>
Synchronizes the outgoing index counters based on the current on-chain channel
state given by outgoing_channels.
Outgoing indices for channels that either are not present in outgoing_channels or
not present as opened channels will be removed from the store.
Outgoing indices for existing open channels in outgoing_channels will be either:
- added to the store with their current index and epoch (if not present in the store), or
- updated to the maximum of the two index values (if present in the store)
It is advised to call this function early after the construction of the HoprTicketManager
to ensure pruning of dangling or out-of-date values.
Sourcepub fn next_multihop_ticket(
&self,
channel: &ChannelEntry,
current_path_pos: u8,
winning_prob: WinningProbability,
ticket_price: HoprBalance,
) -> Result<TicketBuilder, TicketManagerError>
pub fn next_multihop_ticket( &self, channel: &ChannelEntry, current_path_pos: u8, winning_prob: WinningProbability, ticket_price: HoprBalance, ) -> Result<TicketBuilder, TicketManagerError>
Creates a ticket for the next hop on a multi-hop path.
The current_path_pos indicates the position of the current hop in the multi-hop path.
It is used to determine the value of the ticket: price * (current_path_pos - 1) / winning_prob.
The function does not make sense for current_path_pos <= 1 and returns an error if such an argument is
provided.
For last-hop tickets (current_path_pos equal to 1), a zero hop ticket should be
created instead.
The function will fail for channels that are not opened or do not have enough funds to cover the ticket value.
The ticket index of the returned ticket is guaranteed to be greater or equal to the ticket index on the
given channel argument.
Source§impl<S> HoprTicketManager<S, S::Queue>
impl<S> HoprTicketManager<S, S::Queue>
Sourcepub fn sync_from_incoming_channels(
&self,
incoming_channels: &[ChannelEntry],
) -> Result<Vec<VerifiedTicket>, TicketManagerError>
pub fn sync_from_incoming_channels( &self, incoming_channels: &[ChannelEntry], ) -> Result<Vec<VerifiedTicket>, TicketManagerError>
Synchronizes the existing incoming redeemable ticket queues with the state of the
current incoming_channels.
Any incoming ticket queues that correspond to a channel that is no longer open or effectively open (in
incoming_channels) will be dropped and the tickets neglected.
For all opened or effectively opened incoming channels inside incoming_channels, either an existing
ticket queue is opened or a new one is created (without any tickets in it).
All the neglected tickets are returned from the function to make further accounting possible, but they are no longer redeemable.
It is advised to call this function early after the construction of the HoprTicketManager
to ensure pruning of dangling or out-of-date values.
Sourcepub fn insert_incoming_ticket(
&self,
ticket: RedeemableTicket,
) -> Result<Vec<VerifiedTicket>, TicketManagerError>
pub fn insert_incoming_ticket( &self, ticket: RedeemableTicket, ) -> Result<Vec<VerifiedTicket>, TicketManagerError>
Inserts a new incoming winning redeemable ticket into the ticket manager.
On success, the method returns all tickets that have been neglected in the ticket queue of this channel, in case the inserted ticket has a greater channel epoch than the next extractable ticket in the queue. This situation can happen when unredeemed tickets are left in the queue, while the corresponding channel restarts its lifecycle and a new winning ticket is received. Otherwise, the returned vector is empty.
Sourcepub fn unrealized_value(
&self,
channel_id: &ChannelId,
min_index: Option<u64>,
) -> Result<Option<HoprBalance>, TicketManagerError>
pub fn unrealized_value( &self, channel_id: &ChannelId, min_index: Option<u64>, ) -> Result<Option<HoprBalance>, TicketManagerError>
Returns the total value of unredeemed tickets in the given channel and its latest epoch.
NOTE: The function is less efficient when the min_index is specified, as
a full scan of the queue is required to calculate the unrealized value.
Trait Implementations§
Source§impl<S> TicketManagement for HoprTicketManager<S, S::Queue>
impl<S> TicketManagement for HoprTicketManager<S, S::Queue>
Source§fn redeem_stream<C: ChainWriteTicketOperations + Send + Sync + 'static>(
&self,
chain: C,
channel_id: ChannelId,
min_amount: Option<HoprBalance>,
) -> Result<impl Stream<Item = Result<RedemptionResult, Self::Error>> + Send, Self::Error>
fn redeem_stream<C: ChainWriteTicketOperations + Send + Sync + 'static>( &self, chain: C, channel_id: ChannelId, min_amount: Option<HoprBalance>, ) -> Result<impl Stream<Item = Result<RedemptionResult, Self::Error>> + Send, Self::Error>
Creates a stream that redeems tickets in-order one by one in the given channel,
using the given [ChainWriteTicketOperations] on-chain client
implementation.
If min_redeem_value is given, all the tickets that are lower than the given value are neglected in the
process.
If there’s already an existing redeem stream for the channel, an error is returned without creating a new stream.
The stream terminates when there are no more tickets to process in the queue, or an error is encountered.
Source§fn neglect_tickets(
&self,
channel_id: &ChannelId,
up_to_index: Option<u64>,
) -> Result<Vec<VerifiedTicket>, TicketManagerError>
fn neglect_tickets( &self, channel_id: &ChannelId, up_to_index: Option<u64>, ) -> Result<Vec<VerifiedTicket>, TicketManagerError>
Removes all the tickets in the given [ChannelId], optionally only up to the given ticket index (inclusive).
If the up_to_index is given and lower than the lowest index of an unredeemed ticket in the queue,
the function does nothing.
If there’s ticket redemption ongoing in the same channel and the neglection intersects with the redeemed range, the redemption will be cut short, with remaining unredeemed tickets neglected.
Source§fn ticket_stats(
&self,
channel: Option<&ChannelId>,
) -> Result<ChannelStats, TicketManagerError>
fn ticket_stats( &self, channel: Option<&ChannelId>, ) -> Result<ChannelStats, TicketManagerError>
Computes statistics for the given channel or for all channels if None is given.
If the given channel does not exist, it returns zero statistics instead of an error.
Apart from unredeemed_value, the statistics are not persistent.
type Error = TicketManagerError
Auto Trait Implementations§
impl<S, Q> Freeze for HoprTicketManager<S, Q>
impl<S, Q> !RefUnwindSafe for HoprTicketManager<S, Q>
impl<S, Q> Send for HoprTicketManager<S, Q>
impl<S, Q> Sync for HoprTicketManager<S, Q>
impl<S, Q> Unpin for HoprTicketManager<S, Q>
impl<S, Q> !UnwindSafe for HoprTicketManager<S, Q>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more