Skip to content

Instantly share code, notes, and snippets.

@AdamISZ
Last active April 3, 2023 20:09
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save AdamISZ/639333568ad98f50354c933057ee32a4 to your computer and use it in GitHub Desktop.
Save AdamISZ/639333568ad98f50354c933057ee32a4 to your computer and use it in GitHub Desktop.
Simple Python script to find Joinmarket type transactions in blocks
#!/usr/bin/env python
from __future__ import print_function
"""
Find/count JM transactions in blocks.
Ensure your joinmarket-clientserver virtualenv (jmvenv) is activated,
make sure your Bitcoin Core node is available and joinmarket.cfg is appropriately set.
Pass start and end block number:
`python jmtxfinder.py 400000 400200`.
Note it's stupid slow, requiring 10-15 seconds on my machine for a normal-ish block.
Outputs results to console and to jmtxfinder_results.txt and to a csv file.
Ignores coinjoins with < 3 equal sized outputs; vanishingly few Joinmarket txs
have that, and it's not a "proper" Joinmarket join anyway.
Also ignores coinjoins with sizes sub-100000 sats, these are often
false positives (and not economically relevant anyway).
"""
import binascii
import os
import sys
import jmbitcoin as btc
from jmclient import (load_program_config, jm_single,
RegtestBitcoinCoreInterface,
BitcoinCoreInterface)
outfile = "jmtxfinder_results.txt"
csvfile = "jmtxf_summ.csv"
def read_length(x):
bx = binascii.unhexlify(x)
val = bx[0]
if val < 253:
n = 1
elif val == 253:
val = btc.decode(bx[1:3][::-1], 256)
n = 3
elif val == 254:
val = btc.decode(bx[1:5][::-1], 256)
n = 5
elif val == 255:
val = btc.decode(bx[1:9][::-1], 256)
n = 9
else:
assert False
return (val, n)
def assumed_cj_out_num(nout):
"""Return the value ceil(nout/2)
"""
x = nout//2
if nout %2: return x+1
return x
def most_common_value(x):
return max(set(x), key=x.count)
def is_jm_tx(tx):
#rules are: nins >= number of coinjoin outs (equal sized)
#non-equal outs = coinjoin outs or coinjoin outs -1
#at least 3 coinjoin outs (2 technically possible but excluded)
#also possible to try to get clever about fees, but won't bother
#also BlockSci's algo additionally addresses subset sum, so will
#give better quality data, but again keeping it simple for now.
nouts = len(tx["outs"])
nins = len(tx["ins"])
assumed_coinjoin_outs = assumed_cj_out_num(nouts)
if assumed_coinjoin_outs < 3:
return False
if nins < assumed_coinjoin_outs:
return False
outvals = [x["value"] for x in tx["outs"]]
mcov = most_common_value(outvals)
if mcov < 100000:
return False
cjoutvals = [x for x in outvals if x == mcov]
if len(cjoutvals) != assumed_coinjoin_outs:
return False
return [mcov, nouts]
def get_transactions_from_block(blockheight):
block = jm_single().bc_interface.get_block(blockheight)
txdata = block[160:]
ntx, nbytes = read_length(txdata)
print("Got nbytes: ", nbytes)
txdata = txdata[nbytes*2:]
found_txs = []
for i in range(ntx):
tx = btc.deserialize(txdata)
if i != 0:
found_txs.append(tx)
len_tx = len(btc.serialize(tx))
txdata = txdata[len_tx:]
return found_txs
def tee(x, f):
print(x)
f.write((x + "\n").encode())
if __name__ == "__main__":
if len(sys.argv) < 3:
print("error, syntax: python jmtxfinder.py startblocknumber endblocknumber")
sys.exit(1)
start, stop = [int(x) for x in sys.argv[1:3]]
load_program_config()
if stop < 0:
#flag to just dump all serialized transactions in this block
txs = get_transactions_from_block(start)
with open("testtxs-"+str(start)+".txt", "wb") as f:
for tx in txs:
f.write(btc.serialize(tx)+"\n")
print("done")
exit(0)
with open(outfile, "ab+") as f:
with open(csvfile, "ab+") as f2:
for blocknum in range(start, stop + 1):
txs = get_transactions_from_block(blocknum)
header = "*****************************************\n" + str(blocknum) + \
"\n" + str(len(txs)) + " transactions.\n" + \
"*****************************************"
tee(header, f)
found = 0
for t in txs:
res = is_jm_tx(t)
if res:
tee(btc.txhash(btc.serialize(t)) + " : " + str(res[0]) + " " + str(res[1]), f)
found += 1
f2.write((",".join([str(blocknum), btc.txhash(btc.serialize(t)), str(res[0]), str(res[1])])+"\n").encode())
tee("Found in total: " + str(found) + " joinmarket style transactions.", f)
print("done")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment