You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
116 lines
4.4 KiB
116 lines
4.4 KiB
#!/usr/bin/env python3 |
|
|
|
description="""Find SNICKER candidate transactions on |
|
the blockchain. |
|
|
|
Using a connection to Bitcoin Core, which allows retrieving |
|
full blocks, this script will list the transaction IDs of |
|
transactions that fit the pattern of SNICKER, as codified in |
|
https://gist.github.com/AdamISZ/2c13fb5819bd469ca318156e2cf25d79 |
|
and as checked in the `jmbitcoin.snicker` module function |
|
`is_snicker_tx`, and also optionally, transactions that fit the |
|
pattern of Joinmarket coinjoins (see -j). |
|
|
|
Pass a starting and finishing block value as argument. If the |
|
finishing block is not provided, it is assumed to be the latest |
|
block. |
|
|
|
**Note that this is slow.** |
|
|
|
This script does *NOT* require a wallet, but it does require |
|
a connection to Core, so does not work with `no-blockchain`. |
|
Note that this script obviates the need to have txindex enabled |
|
in Bitcoin Core in order to get full transactions, since it |
|
parses the raw blocks. |
|
""" |
|
|
|
import sys |
|
from optparse import OptionParser |
|
from jmbase import bintohex, EXIT_ARGERROR, jmprint |
|
import jmbitcoin as btc |
|
from jmclient import (jm_single, add_base_options, load_program_config, |
|
check_regtest) |
|
from jmclient.configure import get_log |
|
|
|
log = get_log() |
|
|
|
def found_str(ttype, tx, b): |
|
return "Found {} transaction: {} in block: {}".format( |
|
ttype, bintohex(tx.GetTxid()[::-1]), b) |
|
|
|
def write_candidate_to_file(ttype, candidate, blocknum, unspents, filename): |
|
""" Appends the details for the candidate |
|
transaction to the chosen textfile. |
|
""" |
|
with open(filename, "a") as f: |
|
f.write(found_str(ttype, candidate, blocknum) + "\n") |
|
f.write(btc.human_readable_transaction(candidate)+"\n") |
|
f.write("Full transaction hex for creating a proposal is " |
|
"found in the above.\n") |
|
f.write("The unspent indices are: " + " ".join( |
|
(str(u) for u in unspents)) + "\n") |
|
def main(): |
|
parser = OptionParser( |
|
usage= |
|
'usage: %prog [options] startingblock [endingblock]', |
|
description=description |
|
) |
|
add_base_options(parser) |
|
parser.add_option('-f', |
|
'--filename', |
|
action='store', |
|
type='str', |
|
dest='candidate_file_name', |
|
help='filename to write details of candidate ' |
|
'transactions, default ./candidates.txt', |
|
default='candidates.txt') |
|
parser.add_option( |
|
'-j', |
|
'--include-jm', |
|
action='store_true', |
|
dest='include_joinmarket', |
|
default=True, |
|
help="scan for Joinmarket coinjoin outputs, as well as SNICKER.") |
|
|
|
(options, args) = parser.parse_args() |
|
load_program_config(config_path=options.datadir) |
|
if len(args) not in [1,2]: |
|
log.error("Invalid arguments, see --help") |
|
sys.exit(EXIT_ARGERROR) |
|
|
|
startblock = int(args[0]) |
|
if len(args) == 1: |
|
endblock = jm_single().bc_interface.get_current_block_height() |
|
else: |
|
endblock = int(args[1]) |
|
|
|
check_regtest() |
|
|
|
for b in range(startblock, endblock + 1): |
|
block = jm_single().bc_interface.get_block(b) |
|
for t in btc.get_transactions_in_block(block): |
|
if btc.is_snicker_tx(t): |
|
log.info(found_str("SNICKER", t, b)) |
|
# get list of unspent outputs; if empty, skip, |
|
# otherwise, persist to candidate file with unspents |
|
# marked. |
|
unspents = jm_single().bc_interface.get_unspent_indices(t) |
|
if len(unspents) == 0: |
|
continue |
|
write_candidate_to_file("SNICKER", t, b, unspents, |
|
options.candidate_file_name) |
|
# note elif avoids wasting computation if we already found SNICKER: |
|
elif options.include_joinmarket: |
|
cj_amount, n = btc.is_jm_tx(t) |
|
# here we don't care about the stats; the tx is printed anyway. |
|
if cj_amount: |
|
log.info(found_str("Joinmarket coinjoin", t, b)) |
|
unspents = jm_single().bc_interface.get_unspent_indices(t) |
|
if len(unspents) == 0: |
|
continue |
|
write_candidate_to_file("Joinmarket coinjoin", t, b, |
|
unspents, options.candidate_file_name) |
|
log.info("Finished processing block: {}".format(b)) |
|
if __name__ == "__main__": |
|
main() |
|
jmprint('done', "success")
|
|
|