class Ciri::P2P::PeerStore

PeerStore store information of all peers we have seen

TODO rewrite with a database(sqlite)

Support score peers

Constants

DEFAULT_SCORE_SCHEMA
PEER_INITIAL_SCORE
PEER_LAST_SEEN_VALID
PING_EXPIRATION_IN

Public Class Methods

new(score_schema:{}) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 68
def initialize(score_schema:{})
  @peers_ping_records = {}
  @peers_seen_records = {}
  @peers = {}
  @bootnodes = []
  @ban_peers = {}
  @score_schema = DEFAULT_SCORE_SCHEMA.merge(score_schema)
end

Public Instance Methods

add_bootnode(node) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 104
def add_bootnode(node)
  @bootnodes << node
  add_node(node)
end
add_node(node) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 162
def add_node(node)
  @peers[node.raw_node_id] = {node: node, score: PEER_INITIAL_SCORE, status: Status::UNKNOWN}
end
add_node_addresses(raw_node_id, addresses) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 149
def add_node_addresses(raw_node_id, addresses)
  node_info = @peers[raw_node_id]
  node = node_info && node_info[:node]
  if node
    node.addresses = (node.addresses + addresses).uniq
  end
end
ban_peer(raw_node_id, now: Time.now, timeout_secs:600) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 119
def ban_peer(raw_node_id, now: Time.now, timeout_secs:600)
  @ban_peers[raw_node_id] = {ban_at: now, timeout_secs: timeout_secs}
end
find_attempt_peers(count) click to toggle source

TODO find high scoring peers

# File lib/ciri/p2p/peer_store.rb, line 138
def find_attempt_peers(count)
  @peers.values.reject do |peer_info|
    # reject already connected peers and bootnodes
    @bootnodes.include?(peer_info[:node]) || peer_status(peer_info[:node].raw_node_id) == Status::CONNECTED
  end.sort_by do |peer_info|
    -peer_info[:score]
  end.map do |peer_info|
    peer_info[:node]
  end.take(count)
end
find_bootnodes(count) click to toggle source

TODO find high scoring peers, use bootnodes as fallback

# File lib/ciri/p2p/peer_store.rb, line 132
def find_bootnodes(count)
  nodes = @bootnodes.sample(count)
  nodes + find_attempt_peers(count - nodes.size)
end
get_node_addresses(raw_node_id) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 157
def get_node_addresses(raw_node_id)
  peer_info = @peers[raw_node_id]
  peer_info && peer_info[:node].addresses
end
has_ban?(raw_node_id, now: Time.now) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 109
def has_ban?(raw_node_id, now: Time.now)
  record = @ban_peers[raw_node_id]
  if record && (record[:ban_at].to_i + record[:timeout_secs]) > now.to_i
    true
  else
    @ban_peers.delete(raw_node_id)
    false
  end
end
has_ping?(raw_node_id, ping_hash, expires_in: PING_EXPIRATION_IN) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 77
def has_ping?(raw_node_id, ping_hash, expires_in: PING_EXPIRATION_IN)
  return false if has_ban?(raw_node_id)
  record = @peers_ping_records[raw_node_id]
  if record && record[:ping_hash] == ping_hash && (record[:ping_at] + expires_in) > Time.now.to_i
    return true
  elsif record
    @peers_ping_records.delete(raw_node_id)
  end
  false
end
has_seen?(raw_node_id, expires_in: PEER_LAST_SEEN_VALID) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 97
def has_seen?(raw_node_id, expires_in: PEER_LAST_SEEN_VALID)
  return false if has_ban?(raw_node_id)
  seen = (last_seen_at = @peers_seen_records[raw_node_id]) && (last_seen_at + expires_in > Time.now.to_i)
  # convert to bool
  !!seen
end
peer_status(raw_node_id) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 166
def peer_status(raw_node_id)
  if (peer_info = @peers[raw_node_id])
    peer_info[:status]
  else
    Status::UNKNOWN
  end
end
report_peer(raw_node_id, behaviour) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 123
def report_peer(raw_node_id, behaviour)
  score = @score_schema[behaviour]
  raise ValueError.new("unsupport report behaviour: #{behaviour}") if score.nil?
  if (node_info = @peers[raw_node_id])
    node_info[:score] += score
  end
end
update_last_seen(raw_node_id, at: Time.now.to_i) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 93
def update_last_seen(raw_node_id, at: Time.now.to_i)
  @peers_seen_records[raw_node_id] = at
end
update_peer_status(raw_node_id, status) click to toggle source
# File lib/ciri/p2p/peer_store.rb, line 174
def update_peer_status(raw_node_id, status)
  if (peer_info = @peers[raw_node_id])
    peer_info[:status] = status
  end
end
update_ping(raw_node_id, ping_hash, ping_at: Time.now.to_i) click to toggle source

record ping message

# File lib/ciri/p2p/peer_store.rb, line 89
def update_ping(raw_node_id, ping_hash, ping_at: Time.now.to_i)
  @peers_ping_records[raw_node_id] = {ping_hash: ping_hash, ping_at: ping_at}
end