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

Script: Add bond-calculator.py #1254

Merged
merged 1 commit into from
Jun 19, 2022

Conversation

PulpCattel
Copy link
Member

Add a new script that calculates and shows fidelity bond stats for many possible locktimes.

Depends slightly on #1253, but this works just the same without, those changes are here already to gather feedback.
I'm open to rework/rename any part of this, mostly I'm curious if you find it useful even in this basic form.

Further (potential) ideas, I'd prefer whenever possible to implement these in follow-ups, rather than here, to keep things simple:

  • Add more stats, e.g., given a CoinJoin with 2,3...n counterparties (and configurable bondless_allowance), what are the chances of this bond given the orderbook?
  • Add more options, e.g., more locktime configuration.
  • Add/reuse some orderbook capabilities, e.g., to fetch the orderbook in real time rather than using a JSON file (I'm not sure how useful this is)

My math is spaghetti, please make sure it adds up.

Example

(jmvenv) ~/joinmarket-clientserver$ python3 scripts/bond-calculator.py -o /home/user/Downloads/orderbook.json 1btc
User data location: .joinmarket/
2022-04-23 07:01:23,636 [DEBUG]  rpc: getblockchaininfo []
2022-04-23 07:01:23,640 [DEBUG]  rpc: listwallets []
2022-04-23 07:01:23,640 [DEBUG]  rpc: getwalletinfo []
2022-04-23 07:01:23,641 [DEBUG]  rpc: getblockchaininfo []
Amount locked: 100000000 (1.00000000 btc)
Confirmation time: 2022-04-23 06:24:32
Interest rate: 0.015 (1.5%)
Exponent: 2

FIDELITY BOND VALUES

Locktime: 2022-5
Bond value: 0.0000001008903649
Weight: 0.0 (0.0% of all bonds)
Top 86% of the orderbook by value

Locktime: 2022-6
Bond value: 0.0000025343831549
Weight: 0.0 (0.0% of all bonds)
Top 72% of the orderbook by value

Locktime: 2022-7
Bond value: 0.0000079905419724
Weight: 0.0 (0.0% of all bonds)
Top 70% of the orderbook by value

Locktime: 2022-8
Bond value: 0.0000168452235617
Weight: 0.0 (0.0% of all bonds)
Top 68% of the orderbook by value

Locktime: 2022-9
Bond value: 0.0000289816356483
Weight: 0.0 (0.0% of all bonds)
Top 63% of the orderbook by value

Locktime: 2022-10
Bond value: 0.0000438629861584
Weight: 1e-05 (0.001% of all bonds)
Top 59% of the orderbook by value

Locktime: 2022-11
Bond value: 0.0000624935851250
Weight: 1e-05 (0.001% of all bonds)
Top 58% of the orderbook by value

Locktime: 2022-12
Bond value: 0.0000836831898798
Weight: 1e-05 (0.001% of all bonds)
Top 57% of the orderbook by value

Locktime: 2023-1
Bond value: 0.0001088567322806
Weight: 1e-05 (0.001% of all bonds)
Top 56% of the orderbook by value

Locktime: 2023-2
Bond value: 0.0001373741534484
Weight: 2e-05 (0.002% of all bonds)
Top 56% of the orderbook by value

Locktime: 2023-3
Bond value: 0.0001660165251295
Weight: 2e-05 (0.002% of all bonds)
Top 55% of the orderbook by value

Locktime: 2023-4
Bond value: 0.0002009332301511
Weight: 2e-05 (0.002% of all bonds)
Top 55% of the orderbook by value

@AdamISZ
Copy link
Member

AdamISZ commented Apr 28, 2022

Add more stats, e.g., given a CoinJoin with 2,3...n counterparties (and configurable bondless_allowance), what are the chances of this bond given the orderbook?

Huh. That's a pretty cool idea. Of course it's pretty hard to interpret the data anyway, but still.
Yeah I'd want to finish off with #1247 and #1253 before this. Also it's probably not needed as part of the main repo? There are a lot of useful tools for people that if anything I'd like to go in custom-scripts/ (like that directory node runner script i added recently). But I guess that's debatable.

@PulpCattel
Copy link
Member Author

PulpCattel commented Apr 28, 2022

Yeah I'd want to finish off with #1247 and #1253 before this.

Sure, no rush with this.

There are a lot of useful tools for people that if anything I'd like to go in custom-scripts/

Yep, that's an option too. I have no strong preference either way, no problem moving the PR if we prefer it.
My rationale for preferring here to the custom scripts repo:

  • I hope this helps especially average users (generally amongst the most confused and uncertain about FBs), and as a custom script it's inevitably more complicate and obscure to them.
  • Ideally, if done properly, this could be useful to various degree to ~ all makers (and perhaps a few takers too) (?), rather than just a subset/niche (though makers are already a subset of JM users).
  • Somewhat related to the points above, this is not an "alternative to the default scripts for Joinmarket", it does not patch/replace, nor needs to be chosen instead of, something else (so it would require rewriting a little bit of the README too, at least to add how to use it. Not a big deal).

@AdamISZ
Copy link
Member

AdamISZ commented Apr 28, 2022

Yeah fair points. There might be an argument to make that it could be folded in as a function of an existing script (that would make it even more accessible), though it doesn't fit entirely naturally into any one.

@AdamISZ
Copy link
Member

AdamISZ commented Apr 29, 2022

It suddenly occurs to me as I'm looking at #1253 that ob-watcher.py's output contains fidelity bond calculations in the form of 'defence against external enemies' and 'defence against enemies from within that does something thematically very similar: calculates hypotheticals based on fidelity bond data.
Wouldn't it make sense to add this as another feature in that tool? Have a new html page with a simple form where people can enter their proposed fidelity bond size/time/parameters and see the output as table/graph?

@PulpCattel
Copy link
Member Author

It can work, and it would be cool to render this as tables/graphs. The downsides I can see (plus I don't like writing HTML, but that's just me):

  • ob-watcher.py requires internet connectivity and fetching of the orderbook, while this tool can work without (albeit in a limited form), and this seems desirable.
  • Public ob-watchers would have to deal with this (the computation can be expensive I imagine), maybe it could be an optional HTML page? On the other hand, having this in a public orderbook would be awesome.
  • Being part of ob-watcher makes using this tool with user-made scripts harder.

@AdamISZ
Copy link
Member

AdamISZ commented Apr 29, 2022

optional HTML page?

I mean it would definitely be optional and on a separate page via hyperlink, just like sybil attack calcs today. But fair point that you can't run ob-watcher without being online, although you can run it without Bitcoin Core running.

@PulpCattel
Copy link
Member Author

PulpCattel commented Apr 29, 2022

I mean it would definitely be optional and on a separate page via hyperlink

I was thinking about a startup option (e.g., --with-bond-calculator, or may actually be better --without-bond-calculator to have it by default but with the possibility to opt-out) to add this extra page, so that if someone wants to host a public orderbook but he does not want this for performance reason (the other pages are all "static", contrary to this), he can.

@AdamISZ
Copy link
Member

AdamISZ commented Apr 30, 2022

the other pages are all "static", contrary to this

I'm not sure that's even technically true; the orderbook lets you query for timed out counterparties, but the more important point is that several of the existing pages do fairly heavy computation/data load in the background, specifically the fidelity bond table and the FB sybil resistance calculations, then there's also charts and so on (though I never look at them because I always forget to install matplotlib). A simple calculator form to me is no big deal. But again, I really don't mind that much.

@PulpCattel
Copy link
Member Author

A simple calculator form to me is no big deal. But again, I really don't mind that much.

You can absolutely be right, and I don't mind much either, the idea I think might be relevant is that this (and similar potentially expensive stuff) might not be desirable for people that just want to host a public orderbook for the orderbook, and not for the other fancier stuff.

The "static" thing means that it is always the same calculation, it's not user defined (and if it's that heavy I think we could make the same argument for it. Also, this stuff would be in addition, so another argument could be not to add even more heavy computation).

@PulpCattel
Copy link
Member Author

PulpCattel commented May 4, 2022

So, to sum up the idea after some thinking, the underlying main goal is to give users as much information as possible before committing to a fidelity bond, and to give them a better understanding of what to expect from a yieldgenerator.
Asking users to guess, or to scan the orderbook by hand, or to search for graphs/formulas online, etc., is not great if we can help them on-demand (or rather, they can help themselves) with a simple script.
There are a billion different ways this PR can go, and I'm not yet sure if we want this, let alone how we want it, it seems to me some version of this can be useful, but meh, IDK.

Add more stats, e.g., given a CoinJoin with 2,3...n counterparties (and configurable bondless_allowance), what are the chances of this bond given the orderbook?

PulpCattel@c0f6401 adds a naive version of that using a quick and dirty Monte Carlo simulation (admittedly so I didn't have to work out a formula), it's just meant as a proof of concept.
It tries to simulate this code:

def fidelity_bond_weighted_order_choose(orders, n):
"""
choose orders based on fidelity bond for improved sybil resistance
* with probability `bondless_makers_allowance`: will revert to previous default
order choose (random_under_max_order_choose)
* with probability `1 - bondless_makers_allowance`: if there are no bond offerings, revert
to previous default as above. If there are, choose randomly from those, with weighting
being the fidelity bond values.
"""
if random.random() < get_bondless_makers_allowance():
return random_under_max_order_choose(orders, n)
#remove orders without fidelity bonds
filtered_orders = list(filter(lambda x: x[0]["fidelity_bond_value"] != 0, orders))
if len(filtered_orders) == 0:
return random_under_max_order_choose(orders, n)
weights = list(map(lambda x: x[0]["fidelity_bond_value"], filtered_orders))
weights = [x / sum(weights) for x in weights]
return filtered_orders[rand_weighted_choice(len(filtered_orders), weights)]

These are a couple of examples from that branch (don't trust these numbers too much), it shows that creating a bond like the top fidelity bond in the orderbook, a gentleman with over 120btc locked for ~1 year, has (kinda unsurprisingly) a ~100% chances of being picked (thug life!) in a CoinJoin with 10 parties (i.e., the taker picks 10 makers)

These calculations all use current defaults (i.e., 12.5% bondless_allowance and exponent 2) and the orderbook as of today, with around 50 fidelity bonds and ~400 non-bond offers. They also assume, but it is configurable too, that the taker does not filter the orderbook neither by amount offered nor by fee, i.e., any offer is fair game.

Locktime: 2022-09
Bond value: 3.8143266727398246
Weight: 0.32241 (32.241% of all bonds)
Top 1% of the orderbook by value
Running simulation for 10 trials (4600 CoinJoin each)...
Estimated picking chance (95% confidence): 99.922% +/- 0.101%

Creating a bond like the one, not as god-like, but still serious, with over 7btc locked up for more than a year, has something like 5% chance

Locktime: 2022-12
Bond value: 0.0140503075982573
Weight: 0.00165 (0.165% of all bonds)
Top 25% of the orderbook by value
Running simulation for 10 trials (4660 CoinJoin each)...
Estimated picking chance (95% confidence): 5.414% +/- 0.551%

On the other hand, a gentleman in the middle of the bond distribution, with ~1btc locked for ~10 months, has a ~0.4% estimated chance of being picked (not too bad! Although most of it is the bondless_allowance, by setting that to 0 the mean probability drops to 0.114% +/- 0.101%). It's nice to see that by doubling the number of counterparties (from 10 to 20), his chance spikes to 3.805% +/- 0.630%

Locktime: 2023-01
Bond value: 0.0001437714156658
Weight: 0.00002 (0.002% of all bonds)
Top 55% of the orderbook by value
Running simulation for 10 trials (4600 CoinJoin each)...
Estimated picking chance (95% confidence): 0.398% +/- 0.121%

The same two fidelity bonds (the one with >120btc and the one with 1btc), and ~ the same orderbook (all bond values in the orderbook were ricalculated), but with the exponent changed to 1.3 as #1253 proposes .

Locktime: 2022-09
Bond value: 0.0000059968284488
Weight: 0.21754 (21.754% of all bonds)
Top 1% of the orderbook by value
Running simulation for 10 trials (4720 CoinJoin each)...
Estimated picking chance (95% confidence): 97.242% +/- 0.569%
Locktime: 2023-01
Bond value: 0.0000000079889032
Weight: 0.00037 (0.037% of all bonds)
Top 54% of the orderbook by value
Running simulation for 10 trials (4720 CoinJoin each)...
Estimated picking chance (95% confidence): 0.907% +/- 0.358%

@kristapsk
Copy link
Member

Concept ACK. I like this being a script in main repo, not custom scripts, as is something useful for most people planning to run yield generator.

@AdamISZ
Copy link
Member

AdamISZ commented May 31, 2022

Asking users to guess, or to scan the orderbook by hand, or to search for graphs/formulas online, etc., is not great if we can help them on-demand (or rather, they can help themselves) with a simple script.

Please consider making this a module in jmclient, with the script in /scripts/ being only a thin wrapper, using options/args as args to a function in that. It'd be a pretty small module, I can see, since the main work is in the FidelityBondMixin, but nevertheless. This way we can have the same calculation algorithms exposed in the RPC, which I think is mostly where we want development for UI to go in future. In fact as soon as this is merged I may want to add an RPC endpoint for it so the people building with the API can do the same thing.

scripts/bond-calculator.py Outdated Show resolved Hide resolved
help="For how many months to calculate the fidelity bond values, each month has its own stats (default 12)",
default=12,
)
parser.add_option(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing you already thought about this but we could do this without a file export ... but, it's another reason to fold it into ob-watcher which already does the quite obscure jobs needed to get the orderbook without a fully fledged JM bot ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we discussed this a little earlier. The main problem I have with folding it into ob-watcher is requiring the ob-watcher machinery every time.
If we decide to go the way of a dedicated module, could we have both? That way this functionality would be everywhere, as dedicated script (using a .json file), as ob-watcher extra page, and as RPC (I'm not sure here what the best solution would be to pass orderbook data).

scripts/bond-calculator.py Outdated Show resolved Hide resolved
@PulpCattel
Copy link
Member Author

Please consider making this a module in jmclient, with the script in /scripts/ being only a thin wrapper, using options/args as args to a function in that.

Thank you, sounds good to me, I didn't think about the RPC (in retrospect seems obvious possible use case).
I'll get back to this PR soon.

@PulpCattel
Copy link
Member Author

1637fbb adds a new helper module, is it more or less what you have in mind? (I'm not attached to it as is, please feel free to suggest reworks/refactors/what have you).
I've also added some basic tests for it.

Now that we have this module, if this is the direction we want to go, we could move the functions in obwatch/sybil_attack_calculations.py to it. and there might be other too that fit nicely in there.

scripts/bond-calculator.py Outdated Show resolved Hide resolved
interest = 0.015
exponent = 1.3
jm_single().config.set("POLICY", "interest_rate", str(interest))
jm_single().config.set("POLICY", "bond_value_exponent", str(exponent))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same problem as was mentioned in https://github.com/JoinMarket-Org/joinmarket-clientserver/pull/1254/files#r890587671

We address this same point in a bunch of other tests by a teardown/cleanup function, or you can do it directly in the test function etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6f68498 removes these two config.set, but it does introduce another one at the end (now that get_bond_values() reset the exponent, we have to set it manually in the test to be able to compare the bond value result).
Added a reset at the end of the test.

@AdamISZ
Copy link
Member

AdamISZ commented Jun 6, 2022

Thanks, a thorough piece of work :)

adds a new helper module, is it more or less what you have in mind?

Yeah it is.
Re: orderbook, as much as it counts as scope expansion, we could maybe refactor the 'dummy' bot for orderbook reading from ob-watcher.py and then reuse it here. But that raises another point, even if you completely ditch the idea of folding it in with ob-watcher : all of that functionality is actually part of the jmdaemon package not the jmclient package. Probably, that's not much of an issue, we already have the same with ob-watcher.py, i.e. it needs both packages.

Also related, I think you already mentioned, but we should consider whether sybil-attack-calculations should also be refactored to be part of the package and not part of the external script, so it's also reusable.

@PulpCattel
Copy link
Member Author

we should consider whether sybil-attack-calculations should also be refactored to be part of the package and not part of the external script, so it's also reusable.

Agreed. If nobody else does, I'll eventually open a follow-up PR to do that.

@kristapsk
Copy link
Member

I'm having error now:

$ ./scripts/bond-calculator.py 
Traceback (most recent call last):
  File "/home/user/git/joinmarket-clientserver/./scripts/bond-calculator.py", line 10, in <module>
    from jmclient import add_base_options, load_program_config, jm_single, get_bond_values
  File "/home/user/git/joinmarket-clientserver/jmclient/jmclient/__init__.py", line 73, in <module>
    from .bond_calc import get_bond_values
  File "/home/user/git/joinmarket-clientserver/jmclient/jmclient/bond_calc.py", line 9, in <module>
    from dateutil.relativedelta import relativedelta
ModuleNotFoundError: No module named 'dateutil'

@PulpCattel
Copy link
Member Author

@kristapsk

My bad, unfortunately dateutil is third-party package, I missed it because it comes preinstalled in Debian (and Ubuntu too) with the python3-dateutil package.

That's a bit sad because it was the cleanest solution by far, it can be replaced with something like:

year = dt.year + dt.month // 12
month = dt.month % 12 + 1
return datetime(year, month, 1)

The new version is clearly not a general one and would be fidelity bond specific, but I guess that should be okay.

Add a new helper module to calculate fidelity bonds values and stats, and a script to use it.
The goal is to give as much information as possible to the user before
committing to a fidelity bond.
@PulpCattel
Copy link
Member Author

@kristapsk

dateutil removed in 72bf447

@kristapsk
Copy link
Member

tACK. Apart from minor details, like updated docs, this looks good to me.

@AdamISZ
Copy link
Member

AdamISZ commented Jun 19, 2022

OK, thanks. This seems complete now, merging.

Hopefully we can extend this by putting it into the RPC.

@AdamISZ AdamISZ merged commit 9e41885 into JoinMarket-Org:master Jun 19, 2022
@PulpCattel PulpCattel deleted the bond-calculator branch January 26, 2023 13:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants