#!/usr/bin/python import json, re import random import sys import bitcoin as b import sys import json import os try: from urllib.request import build_opener except: from urllib2 import build_opener # Timestamp of sale start: midnight CET Jul 22 start = 1406066400 # Initial sale rate initial_rate = 2000 # Initial sale rate duration initial_period = 14 * 86400 # step size for declining rate phase rate_decline = 30 # Length of step rate_period = 86400 # Number of declining periods rate_periods = 22 # Final rate final_rate = 1337 # Period during which final rate is effective final_period = 6 * 86400 + 3600 # 1h of slack # Accept post-sale purchases? post_rate = 0 # Exodus address exodus = '36PrZ1KHYMpqSyAQXSG8VwbUiq2EogxLo2' # Minimum satoshis accepted minimum = 1000000 # Maximum satoshis accepted maximum = 150000000000 # Create caches directory caches = {} # Foundation address foundation_address = '5abfec25f74cd88437631a7731906932776356f9' try: os.mkdir('caches') except: pass INSIGHT_ADDR = 'http://178.19.221.38:3000' # Makes a request to a given URL (first arg) and optional params (second arg) def make_request(*args): opener = build_opener() opener.addheaders = [('User-agent', 'Mozilla/5.0'+str(random.randrange(1000000)))] try: return opener.open(*args).read().strip() except Exception as e: try: p = e.read().strip() except: p = e raise Exception(p) # Grab history from an insight server (if desired) def insight_history(a): hashes = json.loads(make_request(INSIGHT_ADDR + '/api/addr/'+a))["transactions"] o = [] for i in range(0, len(hashes), 10): h = hashes[i:i+10] t = json.loads(make_request(INSIGHT_ADDR + '/api/multitx/'+','.join(h))) sys.stderr.write('Getting txs: %d\n' % i) if isinstance(t, dict): t = [t] for tee in t: for i, out in enumerate(tee["vout"]): if a in out["scriptPubKey"]["addresses"]: o.append({"output": tee["txid"]+':'+str(i), "block_height": tee["confirmedIn"], "value": out["valueSat"]}) return o # Grab a block timestamp from an insight server (if desired) def insight_get_block_timestamp(a): addrtail = ','.join([str(x) for x in a]) if isinstance(a, list) else str(a) o = json.loads(make_request(INSIGHT_ADDR + '/api/blockheader-by-index/'+addrtail)) if isinstance(o, list): return [x['time'] for x in o] else: return o['time'] # Fetch a transaction from an insight server (if desired) def insight_fetchtx(a): addrtail = ','.join(a) if isinstance(a, list) else a return json.loads(make_request(INSIGHT_ADDR + '/api/rawmultitx/'+addrtail)) # Get our network data grabbing methods either from BCI/blockr or from insight, # depending on which one the user prefers. Use --insight to use insight or # --insight 1.2.3.4:30303 to use one's own insight server (need the custom # batch-query-compatible version from http://github.com/vbuterin/insight-api ) if '--insight' in sys.argv: ipport = (sys.argv+[None])[sys.argv.index('--insight') + 1] if ipport: INSIGHT_ADDR = 'http://'+ipport _fetchtx = insight_fetchtx _history = insight_history _get_block_timestamp = insight_get_block_timestamp else: _fetchtx = b.blockr_fetchtx _history = b.history _get_block_timestamp = b.get_block_timestamp # Grab the extra data command line argument if '--extradata' in sys.argv: d = (sys.argv+[None])[sys.argv.index('--extradata') + 1] EXTRADATA = (d[2:] if d[:2] == '0x' else d).decode('hex') else: EXTRADATA = '' # Cache methods that get networking data. Important since this script takes # a very long time, and will almost certainly be interrupted multiple times # while in progress def cache_method_factory(method, filename): def new_method(arg): if filename not in caches: try: caches[filename] = json.load(open(filename, 'r')) except: caches[filename] = {} c = caches[filename] if str(arg) not in c: # Try to get the result five times have_problem = False for tried in range(4): try: c[str(arg)] = method(arg) tried = 'done' break except Exception: sys.stderr.write('API not returning data. Retrying\n') have_problem = True pass if tried != 'done': c[str(arg)] = method(arg) elif have_problem: sys.stderr.write('Data received, all good\n') json.dump(c, open(filename, 'w')) return c[str(arg)] return new_method # Cached versions of the BCI/blockr or insight methods that we need get_block_timestamp = cache_method_factory(_get_block_timestamp, 'caches/blocktimestamps.json') fetchtx = cache_method_factory(_fetchtx, 'caches/fetchtx.json') history = cache_method_factory(_history, 'caches/history.json') # Get a dictionary of the transactions and block heights, taking as input # a history produced by pybitcointools def get_txs_and_heights(outs): txs = {} heights = {} for i in range(0, len(outs), 20): txhashes = [] fetched_heights = [] for j in range(i, min(i + 20, len(outs))): if outs[j]['output'][65:] == '0': txhashes.append(outs[j]['output'][:64]) fetched_heights.append(outs[j]['block_height']) else: sys.stderr.write("Non-purchase tx found (genesis output index not zero): %s\n" % outs[j]['output'][:64]) fetched_txs = fetchtx(txhashes) assert len(fetched_txs) == len(txhashes) == len(fetched_heights) for h, tx, ht in zip(txhashes, fetched_txs, fetched_heights): assert b.txhash(str(tx)) == h txs[h] = tx heights[h] = ht sys.stderr.write('Processed transactions: %d\n' % len(txs)) return {"txs": txs, "heights": heights} # Produce a json list of purchases, taking as input a dictionary of # transactions and heights def list_purchases(obj): txs, heights = obj['txs'], obj['heights'] process_queue = [] for h in txs: txhex = str(txs[h]) txouts = b.deserialize(txhex)['outs'] if len(txouts) >= 2 and txouts[0]['value'] >= minimum - 30000: addr = b.script_to_address(txouts[0]['script']) if addr == exodus: v = txouts[0]['value'] + 30000 process_queue.append({ "tx": h, "addr": b.b58check_to_hex(b.script_to_address( txouts[1]['script'])), "value": v, "height": heights[h] }) else: sys.stderr.write("Non-purchase tx found (not to exodus): %s\n" % h) elif len(txouts) == 1: sys.stderr.write("Non-purchase tx found (single output): %s\n" % h) else: sys.stderr.write("Non-purchase tx found (insufficient value): %s\n" % h) sys.stderr.write('Gathered outputs, collecting block timestamps\n') # Determine the timestamp for every block height. We care about # the timestamp of the previous confirmed block before a transaction. # Save the results as a dictionary of transaction data o = [] for i in range(0, len(process_queue), 20): subpq = process_queue[i:i+20] t = get_block_timestamp([x['height'] - 1 for x in subpq]) assert len(t) == len(subpq), [x['height'] - 1 for x in subpq] o.extend([{ "tx": _a["tx"], "addr": _a["addr"], "value": _a["value"], "time": _b } for _a, _b in zip(subpq, t)]) sys.stderr.write('Collected timestamps: %d\n' % len(o)) return o # Compute ether value from BTC value, using as input objects containing # ether address, value and time and saving a map of ether address => balance def evaluate_purchases(purchases): balances = {} for p in purchases: if p["time"] < start + initial_period: rate = initial_rate elif p["time"] < start + initial_period + rate_period * rate_periods: pid = (p["time"] - (start + initial_period)) // rate_period + 1 rate = initial_rate - rate_decline * pid elif p["time"] < start + initial_period + rate_period * \ rate_periods + final_period: rate = final_rate else: rate = post_rate # Round to the nearest finney balance_to_add = (p["value"] * rate // 10**5) * 10**15 balances[p["addr"]] = balances.get(p["addr"], 0) + balance_to_add return {k: balances[k] for k in sorted(balances.keys())} # Compute a genesis block from purchase balances def mk_genesis_block(balances): o = {k: {"balance": str(v)} for k, v in balances.items()} total_purchased = sum(balances.values()) o[foundation_address] = { "balance": str(total_purchased * 198 // 1000) } sys.stderr.write("Finished, total purchased: %d\n" % total_purchased) sys.stderr.write("Foundation wallet creator address: %s\n" % foundation_address) sys.stderr.write("Foundation balance: %s\n" % (total_purchased * 198 // 1000)) return { "nonce": "0x0000000000000042", "timestamp": "0x00", "difficulty": "0x400000000", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x"+EXTRADATA.encode('hex'), "gasLimit": "0x1388", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "coinbase": "0x0000000000000000000000000000000000000000", "alloc": o } def evaluate(): outs = history(exodus) sys.stderr.write('Gathered history: %d\n' % len(outs)) th = get_txs_and_heights(outs) sys.stderr.write('Gathered txs and heights\n') p = list_purchases(th) sys.stderr.write('Listed purchases\n') o = evaluate_purchases(p) sys.stderr.write('Computed purchases\n') g = mk_genesis_block(o) return g if __name__ == '__main__': print json.dumps(evaluate(), indent=4)