Skip to content

Instantly share code, notes, and snippets.

@ajtowns
Last active June 22, 2021 06:38
Show Gist options
  • Save ajtowns/ec27774113e5d1214b99281f6b7549c4 to your computer and use it in GitHub Desktop.
Save ajtowns/ec27774113e5d1214b99281f6b7549c4 to your computer and use it in GitHub Desktop.
sim address relay
#!/usr/bin/env python3
import random
from math import log1p
from collections import defaultdict
import gc
import sys
def rand_interval(avg):
return avg * -log1p(-random.random())
def stats(nums):
avg = sum(nums)/len(nums)
stddev2 = sum( (n-avg)**2 for n in nums ) / (len(nums)-1)
return avg, stddev2**0.5
class Node:
@classmethod
def set_params(cls, **d):
defaults = dict(
n_outbound = 8,
n_black = 2,
max_inbound = 117,
n_addr_relay = 2,
limit_hops = 10000,
limit_time = 600, # don't relay addr older than 10m
avg_time = 30, # poisson 30s interval between sending addr
)
for k in d:
assert k in defaults, "%s not in defaults" % (k)
for k,v in defaults.items():
if k in d:
v = d[k]
setattr(cls, k, v)
def __init__(self, type):
self.peers_o = []
self.peers_b = []
self.peers_i = []
self.type = type
def enough_outbounds(self):
return len(self.peers_o) >= self.n_outbound
def enough_black(self):
return len(self.peers_b) >= self.n_black
def connect(self, other, black=False):
if black:
assert not self.enough_black()
else:
assert not self.enough_outbounds()
if self is other: return False
if other in self.peers_i: return False
if other in self.peers_o: return False
if other in self.peers_b: return False
if len(other.peers_i) >= self.max_inbound: return False
assert self not in other.peers_i
assert self not in other.peers_o
assert self not in other.peers_b
if black:
self.peers_b.append(other)
else:
self.peers_o.append(other)
other.peers_i.append(self)
return True
def rand_peers(self, cnt):
o = len(self.peers_o)
i = len(self.peers_i)
cnt = min(cnt, o+i)
samp = random.sample(range(o+i), cnt)
res = []
for n in samp:
if n < o:
res.append(self.peers_o[n])
else:
res.append(self.peers_i[n-o])
return res
@classmethod
def _relay_addr(cls, addr, start):
process = [(start,0)]
seen_id = set()
hop = 0
cnts = defaultdict(int)
while process and hop < cls.limit_hops:
hop += 1
next_process = []
for p,time in process:
if id(p) in seen_id: continue
seen_id.add(id(p))
cnts[p.type] += 1
if time > cls.limit_time: continue
ntime = time + rand_interval(cls.avg_time)
r = p.rand_peers(cls.n_addr_relay)
for n in r:
if p in n.peers_b: continue
next_process.append((n,ntime))
process = next_process
return cnts
def relay_addr(self, addr):
return self._relay_addr(addr, self)
def run_sim(listen=10000, priv=50000, n_addr=100):
nodes = [Node('l') for n in range(listen)]
privnodes = [Node('p') for n in range(priv)]
for n in nodes + privnodes:
while not n.enough_outbounds():
n2 = random.choice(nodes)
n.connect(n2)
while not n.enough_black():
n2 = random.choice(nodes)
n.connect(n2, black=True)
res = {'l': [], 'a': []}
for addr in range(n_addr):
n = random.choice(nodes + privnodes)
cnt = n.relay_addr(addr)
rl = cnt['l'] / listen
ra = (cnt['l'] + cnt['p']) / (listen + priv)
res['l'].append(rl)
res['a'].append(ra)
avgl, devl = stats(res["l"])
avga, deva = stats(res["a"])
print("Conn: %d+%d Spread: %d Limit: %d/%d Avg/dev: %.1f%%/%.1f%% %.1f%%/%.1f%%" % (Node.n_outbound, Node.n_black, Node.n_addr_relay, Node.limit_hops, Node.limit_time, avgl*100.0, devl*100.0, avga*100.0, deva*100.0))
del nodes
del privnodes
gc.collect()
#Node.set_params(n_outbound=6, n_black=2)
#run_sim()
#Node.set_params(n_outbound=6, n_black=2, limit_hops=20, limit_time=600*10000)
#run_sim()
def check():
if len(sys.argv) == 4:
Node.set_params(
n_outbound = int(sys.argv[1]),
n_black = int(sys.argv[2]),
n_addr_relay = int(sys.argv[3]),
)
run_sim(n_addr=1000)
return
elif len(sys.argv) != 1:
print("Args: none or outbount blackhold spread")
return
#now
Node.set_params(n_outbound=8, n_black=2)
run_sim()
#with 21528
Node.set_params(n_outbound=8, n_black=0)
run_sim()
#more block-relay-only without 21528
Node.set_params(n_outbound=8, n_black=5)
run_sim()
#more block-relay-only without 21528 but 3-peers-addr-relay
Node.set_params(n_outbound=8, n_black=5, n_addr_relay=3)
run_sim()
#with 21528 and 3-peers-addr-relay
Node.set_params(n_outbound=8, n_black=0, n_addr_relay=3)
run_sim()
if __name__ == "__main__":
check()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment