Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MDP Generation #46

Merged
merged 125 commits into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
125 commits
Select commit Hold shift + click to select a range
1d80cdd
mdp: Draft protocol specification
pkel Aug 11, 2023
796071b
mdp: Draft state transitions
pkel Aug 11, 2023
5373fd0
mdp. First successful execution
pkel Aug 14, 2023
1e8ffab
mdp. document protocol spec API
pkel Aug 14, 2023
b8d2ecb
mdp. guide state exploration by number of PoWs
pkel Aug 15, 2023
e6d7532
mdp. improve state compression
pkel Aug 15, 2023
c56f356
mdp. count transitions
pkel Aug 15, 2023
ff53997
mdp. iterate protocol spec API
pkel Aug 15, 2023
7c3d916
mdp. draft bit-packing of state
pkel Aug 15, 2023
c068d02
mdp. bit-pack queued unexplored states
pkel Aug 15, 2023
00ca70e
mdp. update comments
pkel Aug 15, 2023
6331136
mdp. Update todos
pkel Aug 15, 2023
4da5256
mdp. spec parallel PoW
pkel Aug 15, 2023
77ab0e4
mdp. improve bit-packing
pkel Aug 15, 2023
98bc2f0
mdp. try to merging isomorphic states
pkel Aug 16, 2023
3baf675
mdp. merge isomorphic state
pkel Aug 16, 2023
e88b67f
mdp. stop exploration after a given number of PoWs
pkel Aug 16, 2023
7a63272
mdp. create matrices for mdptoolbox
pkel Aug 16, 2023
d669026
mdp. draft interface for specifying mdp models
pkel Aug 16, 2023
f7f6f0b
mdp. carve out selfish mining model spec
pkel Aug 17, 2023
22aaf59
mdp. carve out state exploration loop
pkel Aug 17, 2023
31955b2
mdp. reimplement truncation
pkel Aug 17, 2023
34a5341
mdp. fix matrix generation
pkel Aug 18, 2023
84a75d2
mdp. fix small bug in sm.py action continue
pkel Aug 18, 2023
3a5044c
mdp. implement isomorphism_class
pkel Aug 18, 2023
bef7570
mdp. reimplement merging of isomorphic states
pkel Aug 18, 2023
42b43d1
mdp. calculate mining reward for common history
pkel Aug 18, 2023
b2eb861
mdp. implement stochastic termination in SM model
pkel Aug 18, 2023
636c9c2
mdp. Implement naive value iteration
pkel Aug 18, 2023
bf0bf89
mdp. value iteration w/o matrix math seems 100x faster?
pkel Aug 18, 2023
26cdcda
mdp. draft exploration with symbolic parameters alpha, gamma, horizon
pkel Aug 21, 2023
48e2c6e
mdp. fix selfish mining model.
pkel Aug 21, 2023
90c16c6
mdp. fix termination probability term
pkel Aug 21, 2023
65db4a5
mdp. allow horizon=0 and track progress
pkel Aug 21, 2023
49f5d80
mdp. Try solving some bitcoin MDPs in notebook
pkel Aug 21, 2023
aedb798
mdp. Rename old deprecated approach
pkel Aug 22, 2023
b91ab92
mdp. Implement BarZur @ AFT'20 Bitcoin model and PT-MDP transformation
pkel Aug 22, 2023
7c4d59c
mdp. barzur20aft produces plausible results
pkel Aug 22, 2023
3d21145
mdp. rename sm.py -> sm_v1.py
pkel Aug 22, 2023
be579e9
mdp. Avoid running expensive tests on make test
pkel Aug 22, 2023
910e870
mdp. reimplement state editor and core selfish mining logic
pkel Aug 23, 2023
b1d80a9
mdp. reimplement reward and history truncation
pkel Aug 23, 2023
9a8181c
mdp. update notebooks for recent changes
pkel Aug 23, 2023
d703856
mdp. run value iteration against new sm.py
pkel Aug 23, 2023
51e1937
mdp. shrink state space by removing unreachable blocks
pkel Aug 23, 2023
72bf63d
mdp. fix adjacency permutation but in old SM model
pkel Aug 23, 2023
97a550a
mdp. merge isormophic state w/o breaking stuff
pkel Aug 23, 2023
f5d10ff
mdp. implement two variants of Ethereum
pkel Aug 28, 2023
74de72a
mdp. optimize parents/children with caching
pkel Aug 28, 2023
0200d0c
mdp. remove legacy
pkel Aug 28, 2023
ed31933
mdp. remove more legacy stuff
pkel Aug 28, 2023
38c0642
mdp. put start states into MDP class
pkel Aug 28, 2023
fea5408
mdp. fix ethereum.py
pkel Aug 28, 2023
7a681ae
mdp. new sm_test.py
pkel Aug 28, 2023
f5f4a02
mdp. avoid pickle
pkel Aug 28, 2023
300542f
mdp. remove redundant assert
pkel Aug 28, 2023
c38c291
mdp. reuse mdp for different alpha/gamma
pkel Aug 28, 2023
1a3a842
mdp. use start states in sm.ipynb
pkel Aug 28, 2023
fb547f8
mdp. uncover bug in ethereum/sm
pkel Aug 29, 2023
62c7024
mdp. be more careful when truncating common history
pkel Aug 29, 2023
907079a
mdp. conclude multicore benchmark
pkel Aug 29, 2023
245a710
mdp. record benchmark results
pkel Aug 29, 2023
65fe1a4
mdp. fix python/numpy on nixos
pkel Aug 29, 2023
7e28d24
mdp. Add maximum_height cutoff
pkel Aug 29, 2023
dfd25ca
mdp. explore bitcoin max_height up to one hour
pkel Aug 30, 2023
5e07321
mdp. evaluate barzur bitcoin SM model versus own model
pkel Aug 30, 2023
ec418eb
mdp. limit measure-barzur to 1m transitions
pkel Aug 30, 2023
b0fe983
mdp. make assumption more obvious in measure-barzur.ipynb
pkel Aug 30, 2023
944569b
mdp. script state space exploration
pkel Aug 31, 2023
7995538
mdp. fix parallelism in explore-models.py
pkel Aug 31, 2023
202d0d7
mdp. error handling in explore-models.py
pkel Aug 31, 2023
fb08b1c
mdp. Avoid redundant work in explore-models.py
pkel Aug 31, 2023
d16f2b5
mdp. minor fix
pkel Aug 31, 2023
20dedd3
mdp. plot exploration progress
pkel Aug 31, 2023
7dcc2c3
mdp. evaluate redunancy in the MDPs
pkel Aug 31, 2023
7c246d3
mdp. Fix bug in Ethereum's uncle selection.
pkel Aug 31, 2023
b7b54b6
mdp. avoid erroneous truncation in Ethereum
pkel Aug 31, 2023
151905f
mdp. update explored-models.ipynb
pkel Sep 1, 2023
eb4d2e6
mdp. source measure-barzur experiment from explored-models
pkel Sep 1, 2023
b768699
mdp. conduct value iterations for explored-models
pkel Sep 1, 2023
dce6d15
mdp. Makefile fix
pkel Sep 1, 2023
336cb7e
mdp. update notebooks
pkel Sep 1, 2023
7ad0927
mdp. fix reward calculation
pkel Sep 1, 2023
714bbab
mdp. fix make clear
pkel Sep 1, 2023
10a0963
mdp. try to calculate steady state reward per progress
pkel Sep 1, 2023
0868233
mdp. another approach for steady state revenue
pkel Sep 1, 2023
bc40d92
mdp. improve steady state reward per progress calculation
pkel Sep 1, 2023
99c9ea1
mdp. move reward per progress calculation to mdp.py
pkel Sep 1, 2023
d84d6a3
mdp. try to integrate steady state into pipeline
pkel Sep 1, 2023
b140e87
mdp. explore bigger models
pkel Sep 1, 2023
4a8b112
mdp. fix reward per progress for edge cases
pkel Sep 3, 2023
48a17c3
mdp. use argparse for n_jobs and problem size
pkel Sep 3, 2023
113e3f6
mdp. Update notebooks for intermediate models (100k transitions)
pkel Sep 3, 2023
b6b93d2
mdp. Update notebooks for big models (1m transitions)
pkel Sep 4, 2023
f78a1b5
mdp. improve state space cutoff
pkel Sep 5, 2023
bb38138
mdp. tweak value iteration stopping condition
pkel Sep 7, 2023
e9cc918
mdp. update measure-barzur notebook for 100k transitions
pkel Sep 7, 2023
95e05e4
mdp. bug fixes and safeguards
pkel Sep 7, 2023
686b7a7
mdp. measure-barzur notebook
pkel Sep 7, 2023
59a06b2
mdp. measure-barzur notebook
pkel Sep 7, 2023
74fa993
mdp. plot for paper
pkel Sep 8, 2023
d9e5d8f
mdp. update notebooks for 1m transitions models
pkel Sep 8, 2023
c37fb7d
mdp. investigate unexpected results
pkel Sep 13, 2023
fe47ea7
mdp. tweak figure for paper
pkel Sep 14, 2023
98e893e
mdp. change value iteration stopping condition
pkel Sep 14, 2023
3ca7f64
mdp. clarify note on ambiguity in pynauty API
pkel Sep 14, 2023
b055e73
mdp. draft policy_iteration
pkel Sep 14, 2023
eb0f306
mdp. notes
pkel Sep 14, 2023
bdf48c4
mdp. update notebooks for recent results
pkel Sep 15, 2023
abf81b2
mdp. try a new approach for reward per progress calculation.
pkel Sep 15, 2023
ac50d21
mdp. fix reward per progress calculation
pkel Sep 15, 2023
5a0d57c
mdp. delete todo note
pkel Sep 15, 2023
c739c70
mdp. Tweak notebooks for recent updates (with small state space)
pkel Sep 15, 2023
75b94f1
mdp. update notebooks for 100k transitions
pkel Sep 15, 2023
4d6f0ac
mdp. implement traditional selfish mining like Sapirsthein et al.
pkel Sep 18, 2023
57c4d0f
mdp. fix tab-validation.py
pkel Sep 18, 2023
5cc912c
mdp. report RPP for paper
pkel Sep 18, 2023
a3856b0
mdp. Update validation notebook with big models.
pkel Sep 18, 2023
22aa896
mdp. Double check Bar-Zur model against author implementation
pkel Sep 18, 2023
2cabd48
mdp. tweak paper figure
pkel Sep 19, 2023
df73e8d
mdp. add python dependency
pkel Oct 9, 2023
2c604a5
mdp. Roi found a bug in my implementation.
pkel Oct 9, 2023
42d6d6f
mdp. Roi's fix is effective.
pkel Oct 9, 2023
fc3dbe3
mdp. prepare short term revenue and consistency MDPs
pkel Oct 18, 2023
4305c40
mdp. run first consistency and short term revenue experiments
pkel Oct 19, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions mdp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.pdf
*.pkl
*.pkl.gz
*.tex
explored-models
explored-models.bak
29 changes: 29 additions & 0 deletions mdp/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
now=$(shell date -Im)

# transition limit
T=1000000

all: measure-validation.pkl measure-ours.pkl

backup_%:
mkdir -p backup/${now}
if [ -e $* ] ; then mv $* backup/${now}/ ; fi

backup: backup_explored-models
backup: backup_measure-validation.pkl
backup: backup_measure-ours.pkl

clear:
rm -rf explored-models
rm -f measure-validation.pkl
rm -f measure-ours.pkl

explored-models:
mkdir $@
python explore-models.py -t $(T)

measure-validation.pkl: explored-models
python measure-validation.py

measure-ours.pkl: explored-models
python measure-ours.py
287 changes: 287 additions & 0 deletions mdp/aft20barzur.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
# Implementation of Selfish Mining model(s) as described by Bar-Zur et al. at
# AFT '20.

# Roi Bar-Zur, Ittay Eyal, and Aviv Tamar. 2020. Efficient MDP Analysis for
# Selfish-Mining in Blockchains. In 2nd ACM Conference on Advances in Financial
# Technologies (AFT ’20), October 21–23, 2020, New York, NY, USA. ACM, New
# York, NY, USA, 19 pages. https://doi.org/10.1145/3419614.3423264

# Checked against author's implementation:
# https://github.com/roibarzur/pto-selfish-mining/blob/89c408638c9c875457d596dcd30fe82114160422/blockchain_mdps/bitcoin_model.py

from dataclasses import dataclass, replace
from model import Action, Model, Transition
import mdp

# Bitcoin action space
ADOPT = 0
OVERRIDE = 1
MATCH = 2
WAIT = 3

# Bitcoin match-action state
IRRELEVANT = 0
RELEVANT = 1
ACTIVE = 2


@dataclass(frozen=True, order=True)
class BState: # Bitcoin State
a: int # length of the miner's secret chain
h: int # number of blocks in the public chain since last fork
fork: int # one of the above IRRELEVANT RELEVANT ACTIVE

def __post_init__(self):
assert self.a >= 0
assert self.h >= 0


class BitcoinSM(Model):
def __init__(self, *args, alpha: float, gamma: float, maximum_fork_length: int):
if alpha < 0 or alpha >= 0.5:
raise ValueError("alpha must be between 0 and 1")
if gamma < 0 or gamma > 1:
raise ValueError("gamma must be between 0 and 1")
if maximum_fork_length <= 0:
raise ValueError("maximum_fork_length must be greater 0")

self.alpha = alpha
self.gamma = gamma
self.mfl = maximum_fork_length

def __repr__(self):
return (
f"aft20barzur.BitcoinSM("
f"alpha={self.alpha}, "
f"gamma={self.gamma}, "
f"maximum_fork_length={self.mfl})"
)

def start(self) -> list[tuple[BState, float]]:
s = BState(a=0, h=0, fork=IRRELEVANT)
return [(s, 1)]

def actions(self, s: BState) -> list[Action]:
actions = []
# truncation: allow mining only up to a certain point
if self.mfl < 1 or (s.a < self.mfl and s.h < self.mfl):
actions.append(WAIT)
# override/match when it makes sense
if s.a > s.h:
actions.append(OVERRIDE)
if s.a >= s.h and s.fork == RELEVANT:
# NOTE, the paper once says a >= h (p.8 right) and once says a == h
# (p.8 left). I think MATCH can be a good choice even if a > h for
# high gamma. Thus I do a >= h here.
# In the author implementation they do a >= h as well.
actions.append(MATCH)
# giving up is always possible
actions.append(ADOPT)
return actions

def apply_wait(self, s: BState) -> list[Transition]:
t = []
if s.fork != ACTIVE:
# attacker mines block
snew = BState(a=s.a + 1, h=s.h, fork=IRRELEVANT)
t.append(
Transition(
state=snew,
probability=self.alpha,
reward=0.0,
progress=0,
)
)
assert snew.a <= self.mfl

# defender mines block
snew = BState(a=s.a, h=s.h + 1, fork=RELEVANT)
t.append(
Transition(
state=snew,
probability=1.0 - self.alpha,
reward=0.0,
progress=0,
)
)
assert snew.h <= self.mfl
else:
# attacker mines block
snew = BState(a=s.a + 1, h=s.h, fork=ACTIVE)
t.append(
Transition(
state=snew,
probability=self.alpha,
reward=0.0,
progress=0,
)
)
assert snew.a <= self.mfl

# defender mines on top of attacker's chain
# NOTE The paper assigns probability alpha * gamma on p.8
# right which must be a typo.
# The author implementation does it like here.
snew = BState(a=s.a - s.h, h=1, fork=RELEVANT)
t.append(
Transition(
state=snew,
probability=(1 - self.alpha) * self.gamma,
reward=s.h,
progress=s.h,
)
)

# defender mines on top of public chain
# NOTE The paper assigns probability alpha * (1 - gamma) on p.8
# right which must be a typo.
# The author implementation does it like here.
snew = BState(a=s.a, h=s.h + 1, fork=RELEVANT)
t.append(
Transition(
state=snew,
probability=(1 - self.alpha) * (1 - self.gamma),
reward=0,
progress=0,
)
)
assert snew.h <= self.mfl

return t

def apply_adopt(self, s: BState) -> list[Transition]:
snew = BState(a=0, h=0, fork=IRRELEVANT)
t = Transition(state=snew, probability=1.0, reward=0, progress=s.h)
return [t]

def apply_override(self, s: BState) -> list[Transition]:
assert s.a > s.h
snew = BState(a=s.a - s.h - 1, h=0, fork=IRRELEVANT)
t = Transition(
state=snew,
probability=1,
reward=s.h + 1,
progress=s.h + 1,
)
return [t]

def apply_match(self, s: BState) -> list[Transition]:
assert s.fork == RELEVANT
assert s.a >= s.h
snew = BState(a=s.a, h=s.h, fork=ACTIVE)
t = Transition(state=snew, probability=1, reward=0, progress=0)
return [t]

def apply(self, a: Action, s: BState) -> list[Transition]:
# handle action
if a == ADOPT:
return self.apply_adopt(s)
if a == OVERRIDE:
return self.apply_override(s)
if a == MATCH:
return self.apply_match(s)
if a == WAIT:
return self.apply_wait(s)
assert False, "invalid action"


def ptmdp(old: mdp.MDP, *args, horizon: int):
"""
Transform given MDP into a probabilistically terminating MDP.

We add one terminal state (at the end / with the highest id).
"""
assert horizon > 0

# terminal state is new last state
terminal = old.n_states
n_states = old.n_states + 1

# setup new tab, all empty
tab = [dict() for _ in range(n_states)]
n_transitions = 0

# iterate old transitions, split progress > 0 transition in two
for src, actions in enumerate(old.tab):
for act, transitions in actions.items():
new_transitions = list()
for t in transitions:
if t.progress == 0.0:
new_transitions.append(t)
n_transitions += 1
else:
Dt = t.progress
H = horizon
term_prob = 1.0 - ((1.0 - (1.0 / H)) ** Dt)
new_transitions.append(
mdp.Transition(
destination=terminal,
probability=term_prob * t.probability,
reward=t.reward,
progress=t.progress,
effect=t.effect,
)
)
new_transitions.append(
mdp.Transition(
destination=t.destination,
probability=(1 - term_prob) * t.probability,
reward=t.reward,
progress=t.progress,
effect=t.effect,
)
)
n_transitions += 2

tab[src][act] = new_transitions

# construct check and return updated MDP
new = mdp.MDP(
n_states=n_states,
n_transitions=n_transitions,
tab=tab,
n_actions=old.n_actions,
start=old.start,
)
new.check()
return new


mappable_params = dict(alpha=0.125, gamma=0.25)


def map_params(m: mdp.MDP, *args, alpha: float, gamma: float):
assert alpha >= 0 and alpha <= 1
assert gamma >= 0 and gamma <= 1

a = mappable_params["alpha"]
g = mappable_params["gamma"]
mapping = dict()
mapping[1] = 1
mapping[a] = alpha
mapping[1 - a] = 1 - alpha
mapping[(1 - a) * g] = (1 - alpha) * gamma
mapping[(1 - a) * (1 - g)] = (1 - alpha) * (1 - gamma)

assert len(set(mapping.keys())) == 5, "mappable_params are not mappable"

# map probabilities
tab = []
for actions in m.tab:
new_actions = dict()
for act, transitions in actions.items():
new_transitions = []
for t in transitions:
new_t = replace(t, probability=mapping[t.probability])
new_transitions.append(new_t)
new_actions[act] = new_transitions
tab.append(new_actions)

start = dict()
for state, prob in m.start.items():
start[state] = mapping[prob]

new = replace(m, start=start, tab=tab)

assert new.check()
return new
44 changes: 44 additions & 0 deletions mdp/aft20barzur_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import aft20barzur
from compiler import Compiler
import pprint
import psutil

pp = pprint.PrettyPrinter(indent=2)


def peek(c):
x = c.queue.get()
c.queue.put(x)
return x


def compile(*args, verbose=False, **kwargs):
model = aft20barzur.BitcoinSM(*args, **kwargs)
c = Compiler(model)
while c.explore(steps=10000):
if verbose:
process = psutil.Process()
info = dict(
n_states_explored=len(c.explored),
n_states_queued=c.queue.qsize(),
n_states_seen=len(c.state_map),
n_actions=len(c.action_map),
n_transitions=c._mdp.n_transitions,
ram_usage_gb=process.memory_info().rss / 1024**3,
)
info["queuing_factor"] = info["n_states_queued"] / info["n_states_explored"]
pp.pprint(info)
mdp = c.mdp()
print(
f"mfl {kwargs['maximum_fork_length']}: {mdp.n_states} states "
f"and {mdp.n_transitions} transitions"
)
return mdp


if __name__ == "__main__":
mdp = compile(alpha=0.25, gamma=0.5, maximum_fork_length=95, verbose=True)
for i in range(10, 20):
compile(alpha=0.25, gamma=0.5, maximum_fork_length=i)

ptmdp = aft20barzur.ptmdp(mdp, horizon=1000)
Loading
Loading