Skip to content

Instantly share code, notes, and snippets.

@gandrewstone
Created December 8, 2016 21:28
Show Gist options
  • Save gandrewstone/92237f2e44449909964a0af37a1353ca to your computer and use it in GitHub Desktop.
Save gandrewstone/92237f2e44449909964a0af37a1353ca to your computer and use it in GitHub Desktop.
run performance on 1-to-many and many-to-1 transactions
#!/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