zcashd/contrib/simulations/worstcase.py

247 lines
6.7 KiB
Python
Executable File

#!/usr/bin/env python3
import csv
import os
import subprocess
#
# Constants
#
MAX_BLOCK_SIZE = 2000000
#
# Verification times
#
def collect_times():
# Find bench_bitcoin binary
script_dir = os.path.dirname(os.path.realpath(__file__))
base_dir = os.path.dirname(os.path.dirname(script_dir))
bench_bitcoin = os.path.join(base_dir, 'src', 'bench', 'bench_bitcoin')
# Run bench_bitcoin binary
try:
result = subprocess.run([bench_bitcoin], stdout=subprocess.PIPE, universal_newlines=True)
result.check_returncode()
result = result.stdout
except AttributeError:
# Use the older API
result = subprocess.check_output([bench_bitcoin], universal_newlines=True)
# Collect benchmarks
benchmarks = {}
for row in result.strip().split('\n')[1:]: # Skip the headings
parts = row.split(',')
benchmarks[parts[0]] = int(parts[2])
return {
'proof': benchmarks['SaplingOutput'],
'ecdsa': benchmarks['ECDSA'],
'redjubjub': benchmarks['SaplingSpend'] - benchmarks['SaplingOutput'],
'ed25519': benchmarks['JoinSplitSig'],
}
#
# Size calculations
#
def compact_size_size(nSize):
if nSize < 253:
return 1
elif nSize <= 0xFFFF:
return 3
elif nSize <= 0xFFFFFFFF:
return 5
else:
return 9
def script_size(script):
return (
compact_size_size(len(script)) +
len(script)
)
def tx_in_size(scriptSig):
return (
32 + # prevout.hash
4 + # prevout.n
script_size(scriptSig) +
4 # nSequence
)
def tx_out_size(scriptPubKey):
return (
8 + # nValue
script_size(scriptPubKey)
)
def v4_tx_size(vin, vout, nShieldedSpend, nShieldedOutput, nJoinSplit):
return (
4 + # header
4 + # nVersionGroupId
compact_size_size(len(vin)) +
sum([tx_in_size(scriptSig) for scriptSig in vin]) +
compact_size_size(len(vout)) +
sum([tx_out_size(scriptPubKey) for scriptPubKey in vout]) +
4 + # lock_time
4 + # nExpiryHeight
4 + # valueBalance
compact_size_size(nShieldedSpend) +
(384 * nShieldedSpend) +
compact_size_size(nShieldedOutput) +
(948 * nShieldedOutput) +
compact_size_size(nJoinSplit) +
(1698 * nJoinSplit) +
((32 + 64) if nJoinSplit > 0 else 0) + # joinSplitPubKey + joinSplitSig
(64 if (nShieldedSpend + nShieldedOutput) > 0 else 0) # bindingSig
)
def block_size(vtx):
return (
4 + # nVersion
32 + # hashPrevBlock
32 + # hashMerkleRoot
32 + # hashFinalSaplingRoot
4 + # nTime
4 + # nBits
32 + # nNonce
compact_size_size(1344) + # solutionSize
1344 + # solution
compact_size_size(len(vtx)) +
sum([v4_tx_size(**tx) for tx in vtx])
)
#
# Runners
#
def worst_case_many_identical_txs(tx):
vtx = []
while True:
vtx.append(tx)
if block_size(vtx) > MAX_BLOCK_SIZE:
# Keep under the size limit
vtx.pop()
break
return vtx
def worst_case_one_tx_containing(item):
vtx = [{
'vin': [],
'vout': [],
'nShieldedSpend': 0,
'nShieldedOutput': 0,
'nJoinSplit': 0,
}]
while True:
vtx[0][item] += 1
if block_size(vtx) > MAX_BLOCK_SIZE:
# Keep under the size limit
vtx[0][item] -= 1
break
return vtx
def print_makeup(vtx, times):
# One proof per Sapling spend, Sapling output, and JoinSplit
proofs = sum([tx['nShieldedSpend'] + tx['nShieldedOutput'] + tx['nJoinSplit'] for tx in vtx])
# One ECDSA signature per transparent input
ecdsa_sigs = sum([len(tx['vin']) for tx in vtx])
# One RedJubjub signature per Sapling spend (spendAuthSig) and per transaction (bindingSig)
redjubjub_sigs = sum([tx['nShieldedSpend'] + (
1 if tx['nShieldedSpend'] + tx['nShieldedOutput'] > 0 else 0) for tx in vtx])
# One Ed25519 signature per transaction that contains JoinSplits
ed25519_sigs = sum([1 if tx['nJoinSplit'] > 0 else 0 for tx in vtx])
print('- Block size: ', block_size(vtx), 'bytes')
print('- Transactions: ', len(vtx))
print('- Proofs: ', proofs)
print('- ECDSA signatures: ', ecdsa_sigs)
print('- RedJubjub signatures:', redjubjub_sigs)
print('- Ed25519 signatures: ', ed25519_sigs)
print('- Verification time: %0.2f seconds' % (float(
(times['proof'] * proofs) +
(times['ecdsa'] * ecdsa_sigs) +
(times['redjubjub'] * redjubjub_sigs) +
(times['ed25519'] * ed25519_sigs)
) / 10**9))
#
# Worst-case scenarios
#
def worst_case_one_sapling_spend_per_tx(times):
vtx = worst_case_many_identical_txs({
'vin': [],
'vout': [],
'nShieldedSpend': 1,
'nShieldedOutput': 0,
'nJoinSplit': 0,
})
print('One Sapling spend per transaction:')
print_makeup(vtx, times)
print()
def worst_case_one_tx_containing_sapling_spends(times):
vtx = worst_case_one_tx_containing('nShieldedSpend')
print('One transaction containing Sapling spends:')
print_makeup(vtx, times)
print()
def worst_case_one_sapling_output_per_tx(times):
vtx = worst_case_many_identical_txs({
'vin': [],
'vout': [],
'nShieldedSpend': 0,
'nShieldedOutput': 1,
'nJoinSplit': 0,
})
print('One Sapling output per transaction:')
print_makeup(vtx, times)
print()
def worst_case_one_tx_containing_sapling_outputs(times):
vtx = worst_case_one_tx_containing('nShieldedOutput')
print('One transaction containing Sapling outputs:')
print_makeup(vtx, times)
print()
def worst_case_one_joinsplit_per_tx(times):
vtx = worst_case_many_identical_txs({
'vin': [],
'vout': [],
'nShieldedSpend': 0,
'nShieldedOutput': 0,
'nJoinSplit': 1,
})
print('One JoinSplit per transaction:')
print_makeup(vtx, times)
print()
def worst_case_one_tx_containing_joinsplits(times):
vtx = worst_case_one_tx_containing('nJoinSplit')
print('One transaction containing JoinSplits:')
print_makeup(vtx, times)
print()
def run():
print('Collecting benchmarks...')
times = collect_times()
# times = hard_coded_times()
print('Times (ns):', times)
print()
print('Running worst-case simulations...')
worst_case_one_sapling_spend_per_tx(times)
worst_case_one_tx_containing_sapling_spends(times)
worst_case_one_sapling_output_per_tx(times)
worst_case_one_tx_containing_sapling_outputs(times)
worst_case_one_joinsplit_per_tx(times)
worst_case_one_tx_containing_joinsplits(times)
if __name__ == '__main__':
run()