Class: Ciri::P2P::PeerStore

Inherits:
Object
  • Object
show all
Includes:
Behaviours, Status
Defined in:
lib/ciri/p2p/peer_store.rb

Overview

PeerStore store information of all peers we have seen TODO rewrite with a database(sqlite) Support score peers

Defined Under Namespace

Modules: Behaviours, Status

Constant Summary collapse

PEER_LAST_SEEN_VALID =

consider peer is valid if we seen it within 12 hours

12 * 3600
PING_EXPIRATION_IN =

allow ping within 10 minutes

10 * 60
PEER_INITIAL_SCORE =
100
DEFAULT_SCORE_SCHEMA =
{
  INVALID_DATA => -50,
  CONNECT => 10,
  PING => 5,
  FAILED_TO_PING => -10,
  FAILED_TO_CONNECT => -10,
  UNEXPECT_DISCONNECT => -20,
}

Constants included from Status

Status::CONNECTED, Status::DISCONNECTED, Status::UNKNOWN

Constants included from Behaviours

Behaviours::CONNECT, Behaviours::FAILED_TO_CONNECT, Behaviours::FAILED_TO_PING, Behaviours::INVALID_DATA, Behaviours::PING, Behaviours::UNEXPECT_DISCONNECT

Instance Method Summary collapse

Constructor Details

#initialize(score_schema: {}) ⇒ PeerStore

Returns a new instance of PeerStore.



68
69
70
71
72
73
74
75
# 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

Instance Method Details

#add_bootnode(node) ⇒ Object



104
105
106
107
# File 'lib/ciri/p2p/peer_store.rb', line 104

def add_bootnode(node)
  @bootnodes << node
  add_node(node)
end

#add_node(node) ⇒ Object



162
163
164
# 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) ⇒ Object



149
150
151
152
153
154
155
# 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) ⇒ Object



119
120
121
# 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) ⇒ Object

TODO find high scoring peers



138
139
140
141
142
143
144
145
146
147
# 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) ⇒ Object

TODO find high scoring peers, use bootnodes as fallback



132
133
134
135
# 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) ⇒ Object



157
158
159
160
# 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) ⇒ Boolean

Returns:

  • (Boolean)


109
110
111
112
113
114
115
116
117
# 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) ⇒ Boolean

Returns:

  • (Boolean)


77
78
79
80
81
82
83
84
85
86
# 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) ⇒ Boolean

Returns:

  • (Boolean)


97
98
99
100
101
102
# 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) ⇒ Object



166
167
168
169
170
171
172
# 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) ⇒ Object

Raises:

  • (ValueError)


123
124
125
126
127
128
129
# 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) ⇒ Object



93
94
95
# 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) ⇒ Object



174
175
176
177
178
# 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) ⇒ Object

record ping message



89
90
91
# 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