From fd7f08ac21cf25a2c41c9e4c71fa76821dfd3d00 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Tue, 3 Mar 2015 20:47:34 -0800 Subject: [PATCH] Add sidechain-manipulation.py script to automate sending/claiming --- contrib/sidechain-manipulation.py | 243 ++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100755 contrib/sidechain-manipulation.py diff --git a/contrib/sidechain-manipulation.py b/contrib/sidechain-manipulation.py new file mode 100755 index 0000000000..be3512790a --- /dev/null +++ b/contrib/sidechain-manipulation.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python2 + +import sys, os, traceback +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../python-bitcoinrpc")) +from bitcoinrpc.authproxy import AuthServiceProxy, JSONRPCException +from decimal import * + +# VARIOUS SETTINGS... +sidechain_url = "http://user:pass@127.0.0.1:14241" +bitcoin_url = "http://user:pass@127.0.0.1:18332" + +redeem_script = "512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd51ae" +secondScriptPubKey = "OP_DROP 144 OP_LESSTHANOREQUAL" +secondScriptPubKeyHash = "9eac001049d5c38ece8996485418421f4a01e2d7" + +#Bitcoin: +bitcoin_genesis_hash = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" +#Testnet: +#bitcoin_genesis_hash = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" + +sidechain_tx_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../sidechain-tx") + +contracthashtool_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../contracthashtool/contracthashtool") +is_testnet = 1 + + +# CODE THINGS... +sidechain = AuthServiceProxy(sidechain_url) +bitcoin = AuthServiceProxy(bitcoin_url) + +testnet_arg = "" +if is_testnet == 1: + testnet_arg = "-t" + +inverse_bitcoin_genesis_hash = "".join(reversed([bitcoin_genesis_hash[i:i+2] for i in range(0, len(bitcoin_genesis_hash), 2)])) + +def help(): + print("HELP: %s (generate-one-of-one-multisig (sidechain-wallet|mainchain-wallet))|(send-to-sidechain SIDECHAIN_ADDRESS AMOUNT)|(claim-on-sidechain SIDECHAIN_ADDRESS NONCE SEND_TXID)|(spend-from-claim SIDECHAIN_TX_HASH ONE_OF_ONE_ADDRESS)" % sys.argv[0]) + exit(1) + +class UTXOFinder: + in_txid = "" + in_vout = -1 + in_value = -1 + + def check_tx(self, tx): + for vout, output in enumerate(tx["vout"]): + if output["scriptPubKey"]["type"] == "withdraw": + if output["value"] >= value: + self.in_txid = tx["txid"] + self.in_vout = vout + txo = sidechain.gettxout(self.in_txid, self.in_vout, True) + if txo == None: + continue + self.in_value = Decimal(txo["value"]) + if self.in_value > self.target_value: + break + + def __init__(self, value): + self.target_value = value + + for tx in sidechain.batch_([["getrawtransaction", txhash, 1] for txhash in sidechain.getrawmempool()]): + self.check_tx(tx) + if self.in_value > self.target_value: + break + + for i in range(sidechain.getblockcount() + 1)[::-1]: + if i == 0: + # Special-case the genesis txo + self.in_txid = sidechain.getblock(sidechain.getblockhash(0))["tx"][0] + self.in_vout = 0 + txo = sidechain.gettxout(self.in_txid, self.in_vout, True) + if txo == None: + continue + self.in_value = Decimal(txo["value"]) + else: + block = sidechain.getblock(sidechain.getblockhash(i)) + for tx in sidechain.batch_([["getrawtransaction", txhash, 1] for txhash in block["tx"]]): + self.check_tx(tx) + if self.in_value > self.target_value: + break + if self.in_value > self.target_value: + break + + assert(self.in_value > self.target_value) + +try: + if len(sys.argv) < 2: + help() + elif sys.argv[1] == "generate-one-of-one-multisig": + if len(sys.argv) != 3: + help() + + if sys.argv[2] == "sidechain-wallet": + address = sidechain.getnewaddress() + p2sh_res = sidechain.addmultisigaddress(1, [address]) + elif sys.argv[2] == "mainchain-wallet": + address = bitcoin.getnewaddress() + p2sh_res = bitcoin.addmultisigaddress(1, [address]) + else: + help() + print("One-of-one address: %s" % address) + print("P2SH address: %s" % p2sh_res) + elif sys.argv[1] == "send-to-sidechain": + if len(sys.argv) != 4: + help() + + cht = os.popen("%s %s -g -r %s -d %s" % (contracthashtool_path, testnet_arg, redeem_script, sys.argv[2])) + cht_read = cht.read() + nonce = cht_read.split("\n")[0 + is_testnet][7:] + full_contract = cht_read.split("\n")[1 + is_testnet][26:] + send_address = cht_read.split("\n")[3 + is_testnet][40:] + assert(cht.close() == None) + if full_contract[0:8] != "50325348": + print("You must use a P2SH address") + exit(1) + + print("Sending %s to %s..." % (sys.argv[3], send_address)) + print("(nonce: %s)" % nonce) + + txid = bitcoin.sendtoaddress(send_address, Decimal(sys.argv[3])) + print("Sent tx with id %s" % txid) + elif sys.argv[1] == "claim-on-sidechain": + if len(sys.argv) != 5: + help() + + raw_bitcoin_tx = bitcoin.getrawtransaction(sys.argv[4], 1) + if not "confirmations" in raw_bitcoin_tx or raw_bitcoin_tx["confirmations"] <= 10: + print("Please wait for at least 10 confirmations on the bitcoin transaction first") + exit(1) + raw_bitcoin_tx_hex = bitcoin.getrawtransaction(sys.argv[4], 0) + + # Might need to calculate the scriptSig here + secondScriptSig = "1" + + bitcoin_block = bitcoin.getblock(raw_bitcoin_tx["blockhash"]) + coinbase_txid = bitcoin_block["tx"][0] + raw_coinbase_tx_hex = bitcoin.getrawtransaction(coinbase_txid, 0) + + spv_proof = bitcoin.gettxoutproof([coinbase_txid, sys.argv[4]]) + + cht = os.popen("%s %s -g -r %s -d %s -n %s" % (contracthashtool_path, testnet_arg, redeem_script, sys.argv[2], sys.argv[3])) + cht_read = cht.read() + assert(cht.close() == None) + raw_dest = cht_read.split("\n")[1 + is_testnet][66:] + full_contract = cht_read.split("\n")[1 + is_testnet][26:] + send_address = cht_read.split("\n")[3 + is_testnet][40:] + assert(len(raw_dest) == 40) + + nout = -1 + value = -1 + for vout, output in enumerate(raw_bitcoin_tx["vout"]): + if output["scriptPubKey"]["type"] == "scripthash" and output["scriptPubKey"]["addresses"][0] == send_address: + nout = vout + value = Decimal(output["value"]) + break + assert(nout != -1) + + #TODO: This is a pretty shitty way to find a lock utxo + utxo = UTXOFinder(value) + in_txid = utxo.in_txid + in_vout = utxo.in_vout + in_value = utxo.in_value + + print("Redeeming from utxo %s:%.16g (value %.16g, refund %d)" % (in_txid, in_vout, in_value, in_value - value)) + + withdrawkeys = 'withdrawkeys:{"contract": "%s", "txoutproof": "%s", "tx": "%s", "nout": %d, "secondScriptPubKey": "%s", "secondScriptSig": "%s", "coinbase": "%s"}' % (full_contract, spv_proof, raw_bitcoin_tx_hex, nout, secondScriptPubKey, secondScriptSig, raw_coinbase_tx_hex) + out_scriptPubKey = "OP_IF %d 0x20%s %d 0 0x14%s 0x20%s OP_REORGPROOFVERIFY OP_ELSE 144 OP_NOP3 OP_DROP OP_HASH160 0x14%s OP_EQUAL OP_ENDIF" % (bitcoin_block["height"], sys.argv[4], nout, secondScriptPubKeyHash, inverse_bitcoin_genesis_hash, raw_dest) + relock_scriptPubKey = "0x20%s 0x14%s OP_WITHDRAWPROOFVERIFY" % (inverse_bitcoin_genesis_hash, secondScriptPubKeyHash) + + cht = os.popen('%s -create \'set=%s\' in=%s:%d outscript=%s:"%s" outscript=%s:"%s" withdrawsign' % (sidechain_tx_path, withdrawkeys, in_txid, in_vout, str(value), out_scriptPubKey, str(in_value - value), relock_scriptPubKey)) + res_tx = cht.read().split("\n")[0] + assert(cht.close() == None) + + txid = sidechain.sendrawtransaction(res_tx) + print("Success!") + print("Resulting txid: " + str(txid)) + + elif sys.argv[1] == "spend-from-claim": + #TODO: Maybe make wallets recognize this as theirs? + if len(sys.argv) != 4: + help() + + prev_tx = sidechain.getrawtransaction(sys.argv[2], 1) + prev_out = prev_tx["vout"][0] + assert(prev_out["scriptPubKey"]["type"] == "withdrawout") + prev_script = prev_out["scriptPubKey"]["asm"].split(" ") + assert(prev_script[10] == "OP_NOP3") + if "confirmations" not in prev_tx or prev_tx["confirmations"] < int(prev_script[9]): + print("You must wait for at least %s confirmations to claim this output (have %d)" % (prev_script[9], prev_tx["confirmations"])) + exit(1) + + p2sh_res = sidechain.createmultisig(1, [sys.argv[3]]) + + cht = os.popen('%s %s -create in=%s:%d:%s outaddr=%s:"%s"' % (sidechain_tx_path, "-testnet" if is_testnet == 1 else "", sys.argv[2], 0, 0x100000000 - int(prev_script[9]) - 1, str(prev_out["value"]), sidechain.getnewaddress())) + tx_hex = cht.read().split("\n")[0] + assert(cht.close() == None) + + tx_hex = sidechain.signrawtransaction(tx_hex, [{"txid": sys.argv[2], "vout": 0, "scriptPubKey": prev_out["scriptPubKey"]["hex"], "redeemScript": p2sh_res["redeemScript"]}], [sidechain.dumpprivkey(sys.argv[3])]) + if tx_hex["complete"] != True: + print("Got incomplete transaction (signing failed to create spendable transaction):") + print(tx_hex["hex"]) + else: + print("Submitting tx to mempool...") + sidechain.sendrawtransaction(tx_hex["hex"]) + print("Success!") + + elif sys.argv[1] == "send-to-mainchain": + if len(sys.argv) != 4: + help() + + p2sh_tx_test = bitcoin.decoderawtransaction(bitcoin.createrawtransaction([], {sys.argv[2]: 0.1}))["vout"][0]["scriptPubKey"] + if p2sh_tx_test["type"] != "scripthash": + print("You must use a P2SH address") + exit(1) + p2sh_hex = p2sh_tx_test["asm"].split(" ")[1] + + cht = os.popen('%s -create outscript=%s:"0x1850325348%s OP_DROP 0x20%s 0x14%s WITHDRAWPROOFVERIFY"' % (sidechain_tx_path, sys.argv[3], p2sh_hex, inverse_bitcoin_genesis_hash, secondScriptPubKeyHash)) + res_tx = cht.read().split("\n")[0] + assert(cht.close() == None) + + donation = sidechain.fundrawtransaction(res_tx)["fee"] + if donation > 0: + if donation < 550: # Probably dust + donation = 550 + cht = os.popen('%s -create outscript=%s:"0x1850325348%s OP_DROP 0x20%s 0x14%s WITHDRAWPROOFVERIFY" outscript=%s:"RETURN"' % (sidechain_tx_path, sys.argv[3], p2sh_hex, inverse_bitcoin_genesis_hash, secondScriptPubKeyHash, str(Decimal(donation) / Decimal(100000000)))) + res_tx = cht.read().split("\n")[0] + assert(cht.close() == None) + + res_tx = sidechain.fundrawtransaction(res_tx) + fee = res_tx["fee"] + res_tx = sidechain.signrawtransaction(res_tx["hex"])["hex"] + + print("Sending %s to functionaries for withdraw to %s (fee of %s satoshis)..." % (sys.argv[3], sys.argv[2], str(fee + donation))) + txid = sidechain.sendrawtransaction(res_tx) + print("Sent tx with id %s" % txid) + + else: + help() +except JSONRPCException as e: + print(traceback.format_exc()) + print("Got the following error from an RPC Call:") + print(e.error)