Created
December 8, 2016 21:28
-
-
Save gandrewstone/92237f2e44449909964a0af37a1353ca to your computer and use it in GitHub Desktop.
run performance on 1-to-many and many-to-1 transactions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2 | |
# Copyright (c) 2015-2016 The Bitcoin Unlimited developers | |
# Distributed under the MIT software license, see the accompanying | |
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | |
# | |
# Test re-org scenarios with a mempool that contains transactions | |
# that spend (directly or indirectly) coinbase transactions. | |
# | |
import pdb | |
import binascii | |
import time | |
import math | |
import json | |
import logging | |
logging.basicConfig(format='%(asctime)s.%(levelname)s: %(message)s', level=logging.INFO) | |
from test_framework.test_framework import BitcoinTestFramework | |
from test_framework.util import * | |
def split_transaction(node, prevouts, toAddrs, txfeePer=60,**kwargs): | |
""" | |
Create a transaction that divides the sum of all the passed utxos into all the destination addresses | |
pass: | |
node: (node object) where to send the RPC calls | |
prevouts: a single UTXO description dictionary, or a list of them | |
toAddrs: a list of strings specifying the output addresses | |
"sendtx=False" if you don't want to transaction to be submitted. | |
Returns (transaction in hex, Vin list, Vout list) | |
""" | |
if type(prevouts) == type({}): prevouts = [prevouts] # If the user passes just one transaction then put a list around it | |
txid = None | |
inp = [] | |
decContext = decimal.getcontext().prec | |
try: # try finally block to put the decimal precision back to what it was prior to this routine | |
decimal.getcontext().prec = 8 | |
amount = Decimal(0) | |
for tx in prevouts: | |
inp.append({"txid":str(tx["txid"]),"vout":tx["vout"]}) | |
amount += tx["amount"]*BTC | |
txLen = (len(prevouts)*100) + (len(toAddrs)*100) # Guess the tx Size | |
while 1: | |
outp = {} | |
if amount - Decimal(txfeePer*txLen) < 0: # fee too big, find something smaller | |
txfeePer = (float(amount)/txLen)/1.5 | |
txfee = int(math.ceil(txfeePer*txLen)) | |
amtPer = (Decimal(amount-txfee)/len(toAddrs)).to_integral_value() | |
# print "amount: ", amount, " amount per: ", amtPer, "from :", len(prevouts), "to: ", len(toAddrs), "tx fee: ", txfeePer, txfee | |
for a in toAddrs[0:-1]: | |
if PerfectFractions: | |
outp[str(a)] = str(amtPer/BTC) | |
else: | |
outp[str(a)] = float(amtPer/BTC) | |
a = toAddrs[-1] | |
amtPer = (amount - ((len(toAddrs)-1)*amtPer)) - txfee | |
# print "final amt: ", amtPer | |
if PerfectFractions: | |
outp[str(a)] = str(amtPer/BTC) | |
else: | |
outp[str(a)] = float(amtPer/BTC) | |
txn = node.createrawtransaction(inp, outp) | |
if kwargs.get("sendtx",True): | |
#print time.strftime('%X %x %Z') | |
try: | |
s = str(txn) | |
# print "tx len: ", len(binascii.unhexlify(s)) | |
signedtxn = node.signrawtransaction(s) | |
txLen = len(binascii.unhexlify(signedtxn["hex"])) # Get the actual transaction size for better tx fee estimation the next time around | |
finally: | |
#print time.strftime('%X %x %Z') | |
pass | |
if signedtxn["complete"]: | |
try: | |
print "send txn" | |
txid = node.sendrawtransaction(signedtxn["hex"],True) # In the unit tests, we'll just allow high fees | |
return (txn,inp,outp,txid) | |
except JSONRPCException as e: | |
tmp = e.error["message"] | |
(code, msg) = tmp.split(":") | |
if code == 64: raise # bad transaction | |
print tmp | |
if e.error["code"] == -26: # insufficient priority | |
txfeePer = txfeePer * 2 | |
print str(e) | |
pdb.set_trace() | |
print "Insufficient priority, raising tx fee per byte to: ", txfeePer | |
continue | |
else: | |
raise | |
else: | |
for err in signedtxn["errors"]: | |
print err["error"] | |
else: | |
return (txn,inp,outp,txid) | |
finally: | |
decimal.getcontext().prec = decContext | |
# Create one-input, one-output, no-fee transaction: | |
class TransactionPerformanceTest(BitcoinTestFramework): | |
def setup_chain(self,bitcoinConfDict=None, wallets=None): | |
logging.info("Initializing test directory "+self.options.tmpdir) | |
initialize_chain_clean(self.options.tmpdir, 3, bitcoinConfDict, wallets) | |
def setup_network(self, split=False): | |
self.nodes = start_nodes(3, self.options.tmpdir,timewait=60*60) | |
#connect to a local machine for debugging | |
#url = "http://bitcoinrpc:DP6DvqZtqXarpeNWyN3LZTFchCCyCUuHwNF7E8pX99x1@%s:%d" % ('127.0.0.1', 18332) | |
#proxy = AuthServiceProxy(url) | |
#proxy.url = url # store URL on proxy for info | |
#self.nodes.append(proxy) | |
# Connect each node to the other | |
connect_nodes_bi(self.nodes,0,1) | |
connect_nodes_bi(self.nodes,1,2) | |
connect_nodes_bi(self.nodes,0,2) | |
self.is_network_split=False | |
self.sync_all() | |
def generate_utxos(self,node, count,amt=0.1): | |
if type(node) == type(0): # Convert a node index to a node object | |
node = self.nodes[node] | |
addrs = [] | |
for i in range(0,count): | |
addr = node.getnewaddress() | |
addrs.append(addr) | |
node.sendtoaddress(addr, amt) | |
node.generate(1) | |
self.sync_all() | |
def generate_addresses(self,node, count): | |
addrs = [ node.getnewaddress() for _ in range(count)] | |
return addrs | |
def largeOutput(self): | |
"""This times the validation of 1 to many and many to 1 transactions. Its not needed to be run as a daily unit test""" | |
print "synchronizing" | |
self.sync_all() | |
node = self.nodes[0] | |
start = time.time() | |
print "generating addresses" | |
addrs = self.generate_addresses(node,5000) | |
print "address generation complete" | |
wallet = node.listunspent() | |
wallet.sort(key=lambda x: x["amount"],reverse=True) | |
print "Create 1 to many transaction" | |
(txn,inp,outp,txid) = split_transaction(node, wallet[0], addrs[0:3999], txfee=DEFAULT_TX_FEE_PER_BYTE, sendtx=True) | |
txLen = len(binascii.unhexlify(txn)) # Get the actual transaction size for better tx fee estimation the next time around | |
print "[ 'gen',",txLen,",",len(inp),",",len(outp), "]," | |
startTime = time.time() | |
node.generate(1) | |
elapsedTime = time.time() - startTime | |
print "Generate time: ", elapsedTime | |
print "synchronizing" | |
self.sync_all() | |
elapsedTime = time.time() - startTime | |
print "Sync time: ", elapsedTime | |
# Now join with a tx with a huge number of inputs | |
wallet = self.nodes[0].listunspent() | |
wallet.sort(key=lambda x: x["amount"]) | |
print "Create many to 1 transaction" | |
(txn,inp,outp,txid) = split_transaction(node, wallet[0:4000], [addrs[0]], txfee=DEFAULT_TX_FEE_PER_BYTE, sendtx=False) | |
txLen = len(binascii.unhexlify(txn)) # Get the actual transaction size for better tx fee estimation the next time around | |
print "[ 'gen',",txLen,",",len(inp),",",len(outp), "]," | |
startTime = time.time() | |
signedtxn = node.signrawtransaction(str(txn)) | |
elapsedTime = time.time() - startTime | |
print "Sign time: ", elapsedTime | |
startTime = time.time() | |
txid = node.sendrawtransaction(signedtxn["hex"],True) # In the unit tests, we'll just allow high fees | |
self.sync_all() # time to get the tx into the other mempool | |
elapsedTime = time.time() - startTime | |
print "Send time: ", elapsedTime | |
node.generate(1) | |
elapsedTime = time.time() - startTime | |
print "Send&Gen time: ", elapsedTime | |
print "synchronizing" | |
self.sync_all() | |
elapsedTime = time.time() - startTime | |
print "Sync time: ", elapsedTime | |
pdb.set_trace() | |
def run_test(self): | |
TEST_SIZE=500 # To collect a lot of data points, set the TEST_SIZE to 2000 | |
#prepare some coins for multiple *rawtransaction commands | |
self.nodes[0].generate(200) | |
self.sync_all() | |
# This times the validation of 1 to many and many to 1 transactions. Its not needed to be run as a unit test | |
self.largeOutput() | |
if __name__ == '__main__': | |
tpt = TransactionPerformanceTest() | |
bitcoinConf = { | |
"debug":["net","blk","thin","lck","mempool","req","bench","evict"], | |
"blockprioritysize":2000000 # we don't want any transactions rejected due to insufficient fees... | |
} | |
tpt.main(["--nocleanup","--tmpdir=/ramdisk/t1"],bitcoinConf) | |
def Test(): | |
tpt = TransactionPerformanceTest() | |
bitcoinConf = { | |
"debug":["net","blk","thin","mempool","req","bench","evict"], | |
"blockprioritysize":2000000 # we don't want any transactions rejected due to insufficient fees... | |
} | |
tpt.main(["--nocleanup","--tmpdir=/ramdisk/t1"],bitcoinConf) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment