Merge pull request #5981
2703412
Fix default binary in p2p tests to use environment variable (Suhas Daftuar)29bff0e
Add some travis debugging for python scripts (Suhas Daftuar)d76412b
Add script manipulation tools for use in mininode testing framework (Suhas Daftuar)b93974c
Add comparison tool test runner, built on mininode (Suhas Daftuar)6c1d1ba
Python p2p testing framework (Suhas Daftuar)
This commit is contained in:
commit
da38dc696c
14 changed files with 3467 additions and 4 deletions
|
@ -16,6 +16,7 @@ env:
|
||||||
- CCACHE_COMPRESS=1
|
- CCACHE_COMPRESS=1
|
||||||
- BASE_OUTDIR=$TRAVIS_BUILD_DIR/out
|
- BASE_OUTDIR=$TRAVIS_BUILD_DIR/out
|
||||||
- SDK_URL=https://bitcoincore.org/depends-sources/sdks
|
- SDK_URL=https://bitcoincore.org/depends-sources/sdks
|
||||||
|
- PYTHON_DEBUG=1
|
||||||
cache:
|
cache:
|
||||||
apt: true
|
apt: true
|
||||||
directories:
|
directories:
|
||||||
|
|
|
@ -30,6 +30,8 @@ testScripts=(
|
||||||
'proxy_test.py'
|
'proxy_test.py'
|
||||||
'merkle_blocks.py'
|
'merkle_blocks.py'
|
||||||
# 'forknotify.py'
|
# 'forknotify.py'
|
||||||
|
'maxblocksinflight.py'
|
||||||
|
'invalidblockrequest.py'
|
||||||
);
|
);
|
||||||
if [ "x${ENABLE_BITCOIND}${ENABLE_UTILS}${ENABLE_WALLET}" = "x111" ]; then
|
if [ "x${ENABLE_BITCOIND}${ENABLE_UTILS}${ENABLE_WALLET}" = "x111" ]; then
|
||||||
for (( i = 0; i < ${#testScripts[@]}; i++ ))
|
for (( i = 0; i < ${#testScripts[@]}; i++ ))
|
||||||
|
|
102
qa/rpc-tests/bignum.py
Normal file
102
qa/rpc-tests/bignum.py
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# bignum.py
|
||||||
|
#
|
||||||
|
# This file is copied from python-bitcoinlib.
|
||||||
|
#
|
||||||
|
# Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Bignum routines"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
# generic big endian MPI format
|
||||||
|
|
||||||
|
def bn_bytes(v, have_ext=False):
|
||||||
|
ext = 0
|
||||||
|
if have_ext:
|
||||||
|
ext = 1
|
||||||
|
return ((v.bit_length()+7)//8) + ext
|
||||||
|
|
||||||
|
def bn2bin(v):
|
||||||
|
s = bytearray()
|
||||||
|
i = bn_bytes(v)
|
||||||
|
while i > 0:
|
||||||
|
s.append((v >> ((i-1) * 8)) & 0xff)
|
||||||
|
i -= 1
|
||||||
|
return s
|
||||||
|
|
||||||
|
def bin2bn(s):
|
||||||
|
l = 0
|
||||||
|
for ch in s:
|
||||||
|
l = (l << 8) | ch
|
||||||
|
return l
|
||||||
|
|
||||||
|
def bn2mpi(v):
|
||||||
|
have_ext = False
|
||||||
|
if v.bit_length() > 0:
|
||||||
|
have_ext = (v.bit_length() & 0x07) == 0
|
||||||
|
|
||||||
|
neg = False
|
||||||
|
if v < 0:
|
||||||
|
neg = True
|
||||||
|
v = -v
|
||||||
|
|
||||||
|
s = struct.pack(b">I", bn_bytes(v, have_ext))
|
||||||
|
ext = bytearray()
|
||||||
|
if have_ext:
|
||||||
|
ext.append(0)
|
||||||
|
v_bin = bn2bin(v)
|
||||||
|
if neg:
|
||||||
|
if have_ext:
|
||||||
|
ext[0] |= 0x80
|
||||||
|
else:
|
||||||
|
v_bin[0] |= 0x80
|
||||||
|
return s + ext + v_bin
|
||||||
|
|
||||||
|
def mpi2bn(s):
|
||||||
|
if len(s) < 4:
|
||||||
|
return None
|
||||||
|
s_size = bytes(s[:4])
|
||||||
|
v_len = struct.unpack(b">I", s_size)[0]
|
||||||
|
if len(s) != (v_len + 4):
|
||||||
|
return None
|
||||||
|
if v_len == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
v_str = bytearray(s[4:])
|
||||||
|
neg = False
|
||||||
|
i = v_str[0]
|
||||||
|
if i & 0x80:
|
||||||
|
neg = True
|
||||||
|
i &= ~0x80
|
||||||
|
v_str[0] = i
|
||||||
|
|
||||||
|
v = bin2bn(v_str)
|
||||||
|
|
||||||
|
if neg:
|
||||||
|
return -v
|
||||||
|
return v
|
||||||
|
|
||||||
|
# bitcoin-specific little endian format, with implicit size
|
||||||
|
def mpi2vch(s):
|
||||||
|
r = s[4:] # strip size
|
||||||
|
r = r[::-1] # reverse string, converting BE->LE
|
||||||
|
return r
|
||||||
|
|
||||||
|
def bn2vch(v):
|
||||||
|
return bytes(mpi2vch(bn2mpi(v)))
|
||||||
|
|
||||||
|
def vch2mpi(s):
|
||||||
|
r = struct.pack(b">I", len(s)) # size
|
||||||
|
r += s[::-1] # reverse string, converting LE->BE
|
||||||
|
return r
|
||||||
|
|
||||||
|
def vch2bn(s):
|
||||||
|
return mpi2bn(vch2mpi(s))
|
||||||
|
|
183
qa/rpc-tests/bipdersig-p2p.py
Executable file
183
qa/rpc-tests/bipdersig-p2p.py
Executable file
|
@ -0,0 +1,183 @@
|
||||||
|
#!/usr/bin/env python2
|
||||||
|
#
|
||||||
|
# Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
#
|
||||||
|
|
||||||
|
from test_framework import ComparisonTestFramework
|
||||||
|
from util import *
|
||||||
|
from mininode import CTransaction, NetworkThread
|
||||||
|
from blocktools import create_coinbase, create_block
|
||||||
|
from binascii import hexlify, unhexlify
|
||||||
|
import cStringIO
|
||||||
|
from comptool import TestInstance, TestManager
|
||||||
|
from script import CScript
|
||||||
|
import time
|
||||||
|
|
||||||
|
# A canonical signature consists of:
|
||||||
|
# <30> <total len> <02> <len R> <R> <02> <len S> <S> <hashtype>
|
||||||
|
def unDERify(tx):
|
||||||
|
'''
|
||||||
|
Make the signature in vin 0 of a tx non-DER-compliant,
|
||||||
|
by adding padding after the S-value.
|
||||||
|
'''
|
||||||
|
scriptSig = CScript(tx.vin[0].scriptSig)
|
||||||
|
newscript = []
|
||||||
|
for i in scriptSig:
|
||||||
|
if (len(newscript) == 0):
|
||||||
|
newscript.append(i[0:-1] + '\0' + i[-1])
|
||||||
|
else:
|
||||||
|
newscript.append(i)
|
||||||
|
tx.vin[0].scriptSig = CScript(newscript)
|
||||||
|
|
||||||
|
'''
|
||||||
|
This test is meant to exercise BIP66 (DER SIG).
|
||||||
|
Connect to a single node.
|
||||||
|
Mine 2 (version 2) blocks (save the coinbases for later).
|
||||||
|
Generate 98 more version 2 blocks, verify the node accepts.
|
||||||
|
Mine 749 version 3 blocks, verify the node accepts.
|
||||||
|
Check that the new DERSIG rules are not enforced on the 750th version 3 block.
|
||||||
|
Check that the new DERSIG rules are enforced on the 751st version 3 block.
|
||||||
|
Mine 199 new version blocks.
|
||||||
|
Mine 1 old-version block.
|
||||||
|
Mine 1 new version block.
|
||||||
|
Mine 1 old version block, see that the node rejects.
|
||||||
|
'''
|
||||||
|
|
||||||
|
class BIP66Test(ComparisonTestFramework):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.num_nodes = 1
|
||||||
|
|
||||||
|
def setup_network(self):
|
||||||
|
# Must set the blockversion for this test
|
||||||
|
self.nodes = start_nodes(1, self.options.tmpdir,
|
||||||
|
extra_args=[['-debug', '-whitelist=127.0.0.1', '-blockversion=2']],
|
||||||
|
binary=[self.options.testbinary])
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
test = TestManager(self, self.options.tmpdir)
|
||||||
|
test.add_all_connections(self.nodes)
|
||||||
|
NetworkThread().start() # Start up network handling in another thread
|
||||||
|
test.run()
|
||||||
|
|
||||||
|
def create_transaction(self, node, coinbase, to_address, amount):
|
||||||
|
from_txid = node.getblock(coinbase)['tx'][0]
|
||||||
|
inputs = [{ "txid" : from_txid, "vout" : 0}]
|
||||||
|
outputs = { to_address : amount }
|
||||||
|
rawtx = node.createrawtransaction(inputs, outputs)
|
||||||
|
signresult = node.signrawtransaction(rawtx)
|
||||||
|
tx = CTransaction()
|
||||||
|
f = cStringIO.StringIO(unhexlify(signresult['hex']))
|
||||||
|
tx.deserialize(f)
|
||||||
|
return tx
|
||||||
|
|
||||||
|
def get_tests(self):
|
||||||
|
|
||||||
|
self.coinbase_blocks = self.nodes[0].generate(2)
|
||||||
|
self.tip = int ("0x" + self.nodes[0].getbestblockhash() + "L", 0)
|
||||||
|
self.nodeaddress = self.nodes[0].getnewaddress()
|
||||||
|
self.last_block_time = time.time()
|
||||||
|
|
||||||
|
''' 98 more version 2 blocks '''
|
||||||
|
test_blocks = []
|
||||||
|
for i in xrange(98):
|
||||||
|
block = create_block(self.tip, create_coinbase(2), self.last_block_time + 1)
|
||||||
|
block.nVersion = 2
|
||||||
|
block.rehash()
|
||||||
|
block.solve()
|
||||||
|
test_blocks.append([block, True])
|
||||||
|
self.last_block_time += 1
|
||||||
|
self.tip = block.sha256
|
||||||
|
yield TestInstance(test_blocks, sync_every_block=False)
|
||||||
|
|
||||||
|
''' Mine 749 version 3 blocks '''
|
||||||
|
test_blocks = []
|
||||||
|
for i in xrange(749):
|
||||||
|
block = create_block(self.tip, create_coinbase(2), self.last_block_time + 1)
|
||||||
|
block.nVersion = 3
|
||||||
|
block.rehash()
|
||||||
|
block.solve()
|
||||||
|
test_blocks.append([block, True])
|
||||||
|
self.last_block_time += 1
|
||||||
|
self.tip = block.sha256
|
||||||
|
yield TestInstance(test_blocks, sync_every_block=False)
|
||||||
|
|
||||||
|
'''
|
||||||
|
Check that the new DERSIG rules are not enforced in the 750th
|
||||||
|
version 3 block.
|
||||||
|
'''
|
||||||
|
spendtx = self.create_transaction(self.nodes[0],
|
||||||
|
self.coinbase_blocks[0], self.nodeaddress, 1.0)
|
||||||
|
unDERify(spendtx)
|
||||||
|
spendtx.rehash()
|
||||||
|
|
||||||
|
block = create_block(self.tip, create_coinbase(2), self.last_block_time + 1)
|
||||||
|
block.nVersion = 3
|
||||||
|
block.vtx.append(spendtx)
|
||||||
|
block.hashMerkleRoot = block.calc_merkle_root()
|
||||||
|
block.rehash()
|
||||||
|
block.solve()
|
||||||
|
|
||||||
|
self.last_block_time += 1
|
||||||
|
self.tip = block.sha256
|
||||||
|
yield TestInstance([[block, True]])
|
||||||
|
|
||||||
|
'''
|
||||||
|
Check that the new DERSIG rules are enforced in the 751st version 3
|
||||||
|
block.
|
||||||
|
'''
|
||||||
|
spendtx = self.create_transaction(self.nodes[0],
|
||||||
|
self.coinbase_blocks[1], self.nodeaddress, 1.0)
|
||||||
|
unDERify(spendtx)
|
||||||
|
spendtx.rehash()
|
||||||
|
|
||||||
|
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
|
||||||
|
block.nVersion = 3
|
||||||
|
block.vtx.append(spendtx)
|
||||||
|
block.hashMerkleRoot = block.calc_merkle_root()
|
||||||
|
block.rehash()
|
||||||
|
block.solve()
|
||||||
|
self.last_block_time += 1
|
||||||
|
yield TestInstance([[block, False]])
|
||||||
|
|
||||||
|
''' Mine 199 new version blocks on last valid tip '''
|
||||||
|
test_blocks = []
|
||||||
|
for i in xrange(199):
|
||||||
|
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
|
||||||
|
block.nVersion = 3
|
||||||
|
block.rehash()
|
||||||
|
block.solve()
|
||||||
|
test_blocks.append([block, True])
|
||||||
|
self.last_block_time += 1
|
||||||
|
self.tip = block.sha256
|
||||||
|
yield TestInstance(test_blocks, sync_every_block=False)
|
||||||
|
|
||||||
|
''' Mine 1 old version block '''
|
||||||
|
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
|
||||||
|
block.nVersion = 2
|
||||||
|
block.rehash()
|
||||||
|
block.solve()
|
||||||
|
self.last_block_time += 1
|
||||||
|
self.tip = block.sha256
|
||||||
|
yield TestInstance([[block, True]])
|
||||||
|
|
||||||
|
''' Mine 1 new version block '''
|
||||||
|
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
|
||||||
|
block.nVersion = 3
|
||||||
|
block.rehash()
|
||||||
|
block.solve()
|
||||||
|
self.last_block_time += 1
|
||||||
|
self.tip = block.sha256
|
||||||
|
yield TestInstance([[block, True]])
|
||||||
|
|
||||||
|
''' Mine 1 old version block, should be invalid '''
|
||||||
|
block = create_block(self.tip, create_coinbase(1), self.last_block_time + 1)
|
||||||
|
block.nVersion = 2
|
||||||
|
block.rehash()
|
||||||
|
block.solve()
|
||||||
|
self.last_block_time += 1
|
||||||
|
yield TestInstance([[block, False]])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
BIP66Test().main()
|
127
qa/rpc-tests/blockstore.py
Normal file
127
qa/rpc-tests/blockstore.py
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
# BlockStore: a helper class that keeps a map of blocks and implements
|
||||||
|
# helper functions for responding to getheaders and getdata,
|
||||||
|
# and for constructing a getheaders message
|
||||||
|
#
|
||||||
|
|
||||||
|
from mininode import *
|
||||||
|
import dbm
|
||||||
|
|
||||||
|
class BlockStore(object):
|
||||||
|
def __init__(self, datadir):
|
||||||
|
self.blockDB = dbm.open(datadir + "/blocks", 'c')
|
||||||
|
self.currentBlock = 0L
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.blockDB.close()
|
||||||
|
|
||||||
|
def get(self, blockhash):
|
||||||
|
serialized_block = None
|
||||||
|
try:
|
||||||
|
serialized_block = self.blockDB[repr(blockhash)]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
f = cStringIO.StringIO(serialized_block)
|
||||||
|
ret = CBlock()
|
||||||
|
ret.deserialize(f)
|
||||||
|
ret.calc_sha256()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
# Note: this pulls full blocks out of the database just to retrieve
|
||||||
|
# the headers -- perhaps we could keep a separate data structure
|
||||||
|
# to avoid this overhead.
|
||||||
|
def headers_for(self, locator, hash_stop, current_tip=None):
|
||||||
|
if current_tip is None:
|
||||||
|
current_tip = self.currentBlock
|
||||||
|
current_block = self.get(current_tip)
|
||||||
|
if current_block is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
response = msg_headers()
|
||||||
|
headersList = [ CBlockHeader(current_block) ]
|
||||||
|
maxheaders = 2000
|
||||||
|
while (headersList[0].sha256 not in locator.vHave):
|
||||||
|
prevBlockHash = headersList[0].hashPrevBlock
|
||||||
|
prevBlock = self.get(prevBlockHash)
|
||||||
|
if prevBlock is not None:
|
||||||
|
headersList.insert(0, CBlockHeader(prevBlock))
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
headersList = headersList[:maxheaders] # truncate if we have too many
|
||||||
|
hashList = [x.sha256 for x in headersList]
|
||||||
|
index = len(headersList)
|
||||||
|
if (hash_stop in hashList):
|
||||||
|
index = hashList.index(hash_stop)+1
|
||||||
|
response.headers = headersList[:index]
|
||||||
|
return response
|
||||||
|
|
||||||
|
def add_block(self, block):
|
||||||
|
block.calc_sha256()
|
||||||
|
try:
|
||||||
|
self.blockDB[repr(block.sha256)] = bytes(block.serialize())
|
||||||
|
except TypeError as e:
|
||||||
|
print "Unexpected error: ", sys.exc_info()[0], e.args
|
||||||
|
self.currentBlock = block.sha256
|
||||||
|
|
||||||
|
def get_blocks(self, inv):
|
||||||
|
responses = []
|
||||||
|
for i in inv:
|
||||||
|
if (i.type == 2): # MSG_BLOCK
|
||||||
|
block = self.get(i.hash)
|
||||||
|
if block is not None:
|
||||||
|
responses.append(msg_block(block))
|
||||||
|
return responses
|
||||||
|
|
||||||
|
def get_locator(self, current_tip=None):
|
||||||
|
if current_tip is None:
|
||||||
|
current_tip = self.currentBlock
|
||||||
|
r = []
|
||||||
|
counter = 0
|
||||||
|
step = 1
|
||||||
|
lastBlock = self.get(current_tip)
|
||||||
|
while lastBlock is not None:
|
||||||
|
r.append(lastBlock.hashPrevBlock)
|
||||||
|
for i in range(step):
|
||||||
|
lastBlock = self.get(lastBlock.hashPrevBlock)
|
||||||
|
if lastBlock is None:
|
||||||
|
break
|
||||||
|
counter += 1
|
||||||
|
if counter > 10:
|
||||||
|
step *= 2
|
||||||
|
locator = CBlockLocator()
|
||||||
|
locator.vHave = r
|
||||||
|
return locator
|
||||||
|
|
||||||
|
class TxStore(object):
|
||||||
|
def __init__(self, datadir):
|
||||||
|
self.txDB = dbm.open(datadir + "/transactions", 'c')
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.txDB.close()
|
||||||
|
|
||||||
|
def get(self, txhash):
|
||||||
|
serialized_tx = None
|
||||||
|
try:
|
||||||
|
serialized_tx = self.txDB[repr(txhash)]
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
f = cStringIO.StringIO(serialized_tx)
|
||||||
|
ret = CTransaction()
|
||||||
|
ret.deserialize(f)
|
||||||
|
ret.calc_sha256()
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def add_transaction(self, tx):
|
||||||
|
tx.calc_sha256()
|
||||||
|
try:
|
||||||
|
self.txDB[repr(tx.sha256)] = bytes(tx.serialize())
|
||||||
|
except TypeError as e:
|
||||||
|
print "Unexpected error: ", sys.exc_info()[0], e.args
|
||||||
|
|
||||||
|
def get_transactions(self, inv):
|
||||||
|
responses = []
|
||||||
|
for i in inv:
|
||||||
|
if (i.type == 1): # MSG_TX
|
||||||
|
tx = self.get(i.hash)
|
||||||
|
if tx is not None:
|
||||||
|
responses.append(msg_tx(tx))
|
||||||
|
return responses
|
65
qa/rpc-tests/blocktools.py
Normal file
65
qa/rpc-tests/blocktools.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
# blocktools.py - utilities for manipulating blocks and transactions
|
||||||
|
#
|
||||||
|
# Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
#
|
||||||
|
|
||||||
|
from mininode import *
|
||||||
|
from script import CScript, CScriptOp
|
||||||
|
|
||||||
|
# Create a block (with regtest difficulty)
|
||||||
|
def create_block(hashprev, coinbase, nTime=None):
|
||||||
|
block = CBlock()
|
||||||
|
if nTime is None:
|
||||||
|
import time
|
||||||
|
block.nTime = int(time.time()+600)
|
||||||
|
else:
|
||||||
|
block.nTime = nTime
|
||||||
|
block.hashPrevBlock = hashprev
|
||||||
|
block.nBits = 0x207fffff # Will break after a difficulty adjustment...
|
||||||
|
block.vtx.append(coinbase)
|
||||||
|
block.hashMerkleRoot = block.calc_merkle_root()
|
||||||
|
block.calc_sha256()
|
||||||
|
return block
|
||||||
|
|
||||||
|
def serialize_script_num(value):
|
||||||
|
r = bytearray(0)
|
||||||
|
if value == 0:
|
||||||
|
return r
|
||||||
|
neg = value < 0
|
||||||
|
absvalue = -value if neg else value
|
||||||
|
while (absvalue):
|
||||||
|
r.append(chr(absvalue & 0xff))
|
||||||
|
absvalue >>= 8
|
||||||
|
if r[-1] & 0x80:
|
||||||
|
r.append(0x80 if neg else 0)
|
||||||
|
elif neg:
|
||||||
|
r[-1] |= 0x80
|
||||||
|
return r
|
||||||
|
|
||||||
|
counter=1
|
||||||
|
# Create an anyone-can-spend coinbase transaction, assuming no miner fees
|
||||||
|
def create_coinbase(heightAdjust = 0):
|
||||||
|
global counter
|
||||||
|
coinbase = CTransaction()
|
||||||
|
coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff),
|
||||||
|
ser_string(serialize_script_num(counter+heightAdjust)), 0xffffffff))
|
||||||
|
counter += 1
|
||||||
|
coinbaseoutput = CTxOut()
|
||||||
|
coinbaseoutput.nValue = 50*100000000
|
||||||
|
halvings = int((counter+heightAdjust)/150) # regtest
|
||||||
|
coinbaseoutput.nValue >>= halvings
|
||||||
|
coinbaseoutput.scriptPubKey = ""
|
||||||
|
coinbase.vout = [ coinbaseoutput ]
|
||||||
|
coinbase.calc_sha256()
|
||||||
|
return coinbase
|
||||||
|
|
||||||
|
# Create a transaction with an anyone-can-spend output, that spends the
|
||||||
|
# nth output of prevtx.
|
||||||
|
def create_transaction(prevtx, n, sig, value):
|
||||||
|
tx = CTransaction()
|
||||||
|
assert(n < len(prevtx.vout))
|
||||||
|
tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), sig, 0xffffffff))
|
||||||
|
tx.vout.append(CTxOut(value, ""))
|
||||||
|
tx.calc_sha256()
|
||||||
|
return tx
|
330
qa/rpc-tests/comptool.py
Executable file
330
qa/rpc-tests/comptool.py
Executable file
|
@ -0,0 +1,330 @@
|
||||||
|
#!/usr/bin/env python2
|
||||||
|
#
|
||||||
|
# Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
#
|
||||||
|
|
||||||
|
from mininode import *
|
||||||
|
from blockstore import BlockStore, TxStore
|
||||||
|
from util import p2p_port
|
||||||
|
|
||||||
|
'''
|
||||||
|
This is a tool for comparing two or more bitcoinds to each other
|
||||||
|
using a script provided.
|
||||||
|
|
||||||
|
To use, create a class that implements get_tests(), and pass it in
|
||||||
|
as the test generator to TestManager. get_tests() should be a python
|
||||||
|
generator that returns TestInstance objects. See below for definition.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# TestNode behaves as follows:
|
||||||
|
# Configure with a BlockStore and TxStore
|
||||||
|
# on_inv: log the message but don't request
|
||||||
|
# on_headers: log the chain tip
|
||||||
|
# on_pong: update ping response map (for synchronization)
|
||||||
|
# on_getheaders: provide headers via BlockStore
|
||||||
|
# on_getdata: provide blocks via BlockStore
|
||||||
|
|
||||||
|
class TestNode(NodeConnCB):
|
||||||
|
|
||||||
|
def __init__(self, block_store, tx_store):
|
||||||
|
NodeConnCB.__init__(self)
|
||||||
|
self.create_callback_map()
|
||||||
|
self.conn = None
|
||||||
|
self.bestblockhash = None
|
||||||
|
self.block_store = block_store
|
||||||
|
self.block_request_map = {}
|
||||||
|
self.tx_store = tx_store
|
||||||
|
self.tx_request_map = {}
|
||||||
|
|
||||||
|
# When the pingmap is non-empty we're waiting for
|
||||||
|
# a response
|
||||||
|
self.pingMap = {}
|
||||||
|
self.lastInv = []
|
||||||
|
|
||||||
|
def add_connection(self, conn):
|
||||||
|
self.conn = conn
|
||||||
|
|
||||||
|
def on_headers(self, conn, message):
|
||||||
|
if len(message.headers) > 0:
|
||||||
|
best_header = message.headers[-1]
|
||||||
|
best_header.calc_sha256()
|
||||||
|
self.bestblockhash = best_header.sha256
|
||||||
|
|
||||||
|
def on_getheaders(self, conn, message):
|
||||||
|
response = self.block_store.headers_for(message.locator, message.hashstop)
|
||||||
|
if response is not None:
|
||||||
|
conn.send_message(response)
|
||||||
|
|
||||||
|
def on_getdata(self, conn, message):
|
||||||
|
[conn.send_message(r) for r in self.block_store.get_blocks(message.inv)]
|
||||||
|
[conn.send_message(r) for r in self.tx_store.get_transactions(message.inv)]
|
||||||
|
|
||||||
|
for i in message.inv:
|
||||||
|
if i.type == 1:
|
||||||
|
self.tx_request_map[i.hash] = True
|
||||||
|
elif i.type == 2:
|
||||||
|
self.block_request_map[i.hash] = True
|
||||||
|
|
||||||
|
def on_inv(self, conn, message):
|
||||||
|
self.lastInv = [x.hash for x in message.inv]
|
||||||
|
|
||||||
|
def on_pong(self, conn, message):
|
||||||
|
try:
|
||||||
|
del self.pingMap[message.nonce]
|
||||||
|
except KeyError:
|
||||||
|
raise AssertionError("Got pong for unknown ping [%s]" % repr(message))
|
||||||
|
|
||||||
|
def send_inv(self, obj):
|
||||||
|
mtype = 2 if isinstance(obj, CBlock) else 1
|
||||||
|
self.conn.send_message(msg_inv([CInv(mtype, obj.sha256)]))
|
||||||
|
|
||||||
|
def send_getheaders(self):
|
||||||
|
# We ask for headers from their last tip.
|
||||||
|
m = msg_getheaders()
|
||||||
|
m.locator = self.block_store.get_locator(self.bestblockhash)
|
||||||
|
self.conn.send_message(m)
|
||||||
|
|
||||||
|
# This assumes BIP31
|
||||||
|
def send_ping(self, nonce):
|
||||||
|
self.pingMap[nonce] = True
|
||||||
|
self.conn.send_message(msg_ping(nonce))
|
||||||
|
|
||||||
|
def received_ping_response(self, nonce):
|
||||||
|
return nonce not in self.pingMap
|
||||||
|
|
||||||
|
def send_mempool(self):
|
||||||
|
self.lastInv = []
|
||||||
|
self.conn.send_message(msg_mempool())
|
||||||
|
|
||||||
|
# TestInstance:
|
||||||
|
#
|
||||||
|
# Instances of these are generated by the test generator, and fed into the
|
||||||
|
# comptool.
|
||||||
|
#
|
||||||
|
# "blocks_and_transactions" should be an array of [obj, True/False/None]:
|
||||||
|
# - obj is either a CBlock or a CTransaction, and
|
||||||
|
# - the second value indicates whether the object should be accepted
|
||||||
|
# into the blockchain or mempool (for tests where we expect a certain
|
||||||
|
# answer), or "None" if we don't expect a certain answer and are just
|
||||||
|
# comparing the behavior of the nodes being tested.
|
||||||
|
# sync_every_block: if True, then each block will be inv'ed, synced, and
|
||||||
|
# nodes will be tested based on the outcome for the block. If False,
|
||||||
|
# then inv's accumulate until all blocks are processed (or max inv size
|
||||||
|
# is reached) and then sent out in one inv message. Then the final block
|
||||||
|
# will be synced across all connections, and the outcome of the final
|
||||||
|
# block will be tested.
|
||||||
|
# sync_every_tx: analagous to behavior for sync_every_block, except if outcome
|
||||||
|
# on the final tx is None, then contents of entire mempool are compared
|
||||||
|
# across all connections. (If outcome of final tx is specified as true
|
||||||
|
# or false, then only the last tx is tested against outcome.)
|
||||||
|
|
||||||
|
class TestInstance(object):
|
||||||
|
def __init__(self, objects=[], sync_every_block=True, sync_every_tx=False):
|
||||||
|
self.blocks_and_transactions = objects
|
||||||
|
self.sync_every_block = sync_every_block
|
||||||
|
self.sync_every_tx = sync_every_tx
|
||||||
|
|
||||||
|
class TestManager(object):
|
||||||
|
|
||||||
|
def __init__(self, testgen, datadir):
|
||||||
|
self.test_generator = testgen
|
||||||
|
self.connections = []
|
||||||
|
self.block_store = BlockStore(datadir)
|
||||||
|
self.tx_store = TxStore(datadir)
|
||||||
|
self.ping_counter = 1
|
||||||
|
|
||||||
|
def add_all_connections(self, nodes):
|
||||||
|
for i in range(len(nodes)):
|
||||||
|
# Create a p2p connection to each node
|
||||||
|
self.connections.append(NodeConn('127.0.0.1', p2p_port(i),
|
||||||
|
nodes[i], TestNode(self.block_store, self.tx_store)))
|
||||||
|
# Make sure the TestNode (callback class) has a reference to its
|
||||||
|
# associated NodeConn
|
||||||
|
self.connections[-1].cb.add_connection(self.connections[-1])
|
||||||
|
|
||||||
|
def wait_for_verack(self):
|
||||||
|
sleep_time = 0.05
|
||||||
|
max_tries = 10 / sleep_time # Wait at most 10 seconds
|
||||||
|
while max_tries > 0:
|
||||||
|
done = True
|
||||||
|
for c in self.connections:
|
||||||
|
if c.cb.verack_received is False:
|
||||||
|
done = False
|
||||||
|
break
|
||||||
|
if done:
|
||||||
|
break
|
||||||
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
|
def wait_for_pings(self, counter):
|
||||||
|
received_pongs = False
|
||||||
|
while received_pongs is not True:
|
||||||
|
time.sleep(0.05)
|
||||||
|
received_pongs = True
|
||||||
|
for c in self.connections:
|
||||||
|
if c.cb.received_ping_response(counter) is not True:
|
||||||
|
received_pongs = False
|
||||||
|
break
|
||||||
|
|
||||||
|
# sync_blocks: Wait for all connections to request the blockhash given
|
||||||
|
# then send get_headers to find out the tip of each node, and synchronize
|
||||||
|
# the response by using a ping (and waiting for pong with same nonce).
|
||||||
|
def sync_blocks(self, blockhash, num_blocks):
|
||||||
|
# Wait for nodes to request block (50ms sleep * 20 tries * num_blocks)
|
||||||
|
max_tries = 20*num_blocks
|
||||||
|
while max_tries > 0:
|
||||||
|
results = [ blockhash in c.cb.block_request_map and
|
||||||
|
c.cb.block_request_map[blockhash] for c in self.connections ]
|
||||||
|
if False not in results:
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
max_tries -= 1
|
||||||
|
|
||||||
|
# --> error if not requested
|
||||||
|
if max_tries == 0:
|
||||||
|
# print [ c.cb.block_request_map for c in self.connections ]
|
||||||
|
raise AssertionError("Not all nodes requested block")
|
||||||
|
# --> Answer request (we did this inline!)
|
||||||
|
|
||||||
|
# Send getheaders message
|
||||||
|
[ c.cb.send_getheaders() for c in self.connections ]
|
||||||
|
|
||||||
|
# Send ping and wait for response -- synchronization hack
|
||||||
|
[ c.cb.send_ping(self.ping_counter) for c in self.connections ]
|
||||||
|
self.wait_for_pings(self.ping_counter)
|
||||||
|
self.ping_counter += 1
|
||||||
|
|
||||||
|
# Analogous to sync_block (see above)
|
||||||
|
def sync_transaction(self, txhash, num_events):
|
||||||
|
# Wait for nodes to request transaction (50ms sleep * 20 tries * num_events)
|
||||||
|
max_tries = 20*num_events
|
||||||
|
while max_tries > 0:
|
||||||
|
results = [ txhash in c.cb.tx_request_map and
|
||||||
|
c.cb.tx_request_map[txhash] for c in self.connections ]
|
||||||
|
if False not in results:
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
max_tries -= 1
|
||||||
|
|
||||||
|
# --> error if not requested
|
||||||
|
if max_tries == 0:
|
||||||
|
# print [ c.cb.tx_request_map for c in self.connections ]
|
||||||
|
raise AssertionError("Not all nodes requested transaction")
|
||||||
|
# --> Answer request (we did this inline!)
|
||||||
|
|
||||||
|
# Get the mempool
|
||||||
|
[ c.cb.send_mempool() for c in self.connections ]
|
||||||
|
|
||||||
|
# Send ping and wait for response -- synchronization hack
|
||||||
|
[ c.cb.send_ping(self.ping_counter) for c in self.connections ]
|
||||||
|
self.wait_for_pings(self.ping_counter)
|
||||||
|
self.ping_counter += 1
|
||||||
|
|
||||||
|
# Sort inv responses from each node
|
||||||
|
[ c.cb.lastInv.sort() for c in self.connections ]
|
||||||
|
|
||||||
|
# Verify that the tip of each connection all agree with each other, and
|
||||||
|
# with the expected outcome (if given)
|
||||||
|
def check_results(self, blockhash, outcome):
|
||||||
|
for c in self.connections:
|
||||||
|
if outcome is None:
|
||||||
|
if c.cb.bestblockhash != self.connections[0].cb.bestblockhash:
|
||||||
|
return False
|
||||||
|
elif ((c.cb.bestblockhash == blockhash) != outcome):
|
||||||
|
# print c.cb.bestblockhash, blockhash, outcome
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Either check that the mempools all agree with each other, or that
|
||||||
|
# txhash's presence in the mempool matches the outcome specified.
|
||||||
|
# This is somewhat of a strange comparison, in that we're either comparing
|
||||||
|
# a particular tx to an outcome, or the entire mempools altogether;
|
||||||
|
# perhaps it would be useful to add the ability to check explicitly that
|
||||||
|
# a particular tx's existence in the mempool is the same across all nodes.
|
||||||
|
def check_mempool(self, txhash, outcome):
|
||||||
|
for c in self.connections:
|
||||||
|
if outcome is None:
|
||||||
|
# Make sure the mempools agree with each other
|
||||||
|
if c.cb.lastInv != self.connections[0].cb.lastInv:
|
||||||
|
# print c.rpc.getrawmempool()
|
||||||
|
return False
|
||||||
|
elif ((txhash in c.cb.lastInv) != outcome):
|
||||||
|
# print c.rpc.getrawmempool(), c.cb.lastInv
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
# Wait until verack is received
|
||||||
|
self.wait_for_verack()
|
||||||
|
|
||||||
|
test_number = 1
|
||||||
|
for test_instance in self.test_generator.get_tests():
|
||||||
|
# We use these variables to keep track of the last block
|
||||||
|
# and last transaction in the tests, which are used
|
||||||
|
# if we're not syncing on every block or every tx.
|
||||||
|
[ block, block_outcome ] = [ None, None ]
|
||||||
|
[ tx, tx_outcome ] = [ None, None ]
|
||||||
|
invqueue = []
|
||||||
|
|
||||||
|
for b_or_t, outcome in test_instance.blocks_and_transactions:
|
||||||
|
# Determine if we're dealing with a block or tx
|
||||||
|
if isinstance(b_or_t, CBlock): # Block test runner
|
||||||
|
block = b_or_t
|
||||||
|
block_outcome = outcome
|
||||||
|
# Add to shared block_store, set as current block
|
||||||
|
self.block_store.add_block(block)
|
||||||
|
for c in self.connections:
|
||||||
|
c.cb.block_request_map[block.sha256] = False
|
||||||
|
# Either send inv's to each node and sync, or add
|
||||||
|
# to invqueue for later inv'ing.
|
||||||
|
if (test_instance.sync_every_block):
|
||||||
|
[ c.cb.send_inv(block) for c in self.connections ]
|
||||||
|
self.sync_blocks(block.sha256, 1)
|
||||||
|
if (not self.check_results(block.sha256, outcome)):
|
||||||
|
raise AssertionError("Test failed at test %d" % test_number)
|
||||||
|
else:
|
||||||
|
invqueue.append(CInv(2, block.sha256))
|
||||||
|
else: # Tx test runner
|
||||||
|
assert(isinstance(b_or_t, CTransaction))
|
||||||
|
tx = b_or_t
|
||||||
|
tx_outcome = outcome
|
||||||
|
# Add to shared tx store
|
||||||
|
self.tx_store.add_transaction(tx)
|
||||||
|
for c in self.connections:
|
||||||
|
c.cb.tx_request_map[tx.sha256] = False
|
||||||
|
# Again, either inv to all nodes or save for later
|
||||||
|
if (test_instance.sync_every_tx):
|
||||||
|
[ c.cb.send_inv(tx) for c in self.connections ]
|
||||||
|
self.sync_transaction(tx.sha256, 1)
|
||||||
|
if (not self.check_mempool(tx.sha256, outcome)):
|
||||||
|
raise AssertionError("Test failed at test %d" % test_number)
|
||||||
|
else:
|
||||||
|
invqueue.append(CInv(1, tx.sha256))
|
||||||
|
# Ensure we're not overflowing the inv queue
|
||||||
|
if len(invqueue) == MAX_INV_SZ:
|
||||||
|
[ c.sb.send_message(msg_inv(invqueue)) for c in self.connections ]
|
||||||
|
invqueue = []
|
||||||
|
|
||||||
|
# Do final sync if we weren't syncing on every block or every tx.
|
||||||
|
if (not test_instance.sync_every_block and block is not None):
|
||||||
|
if len(invqueue) > 0:
|
||||||
|
[ c.send_message(msg_inv(invqueue)) for c in self.connections ]
|
||||||
|
invqueue = []
|
||||||
|
self.sync_blocks(block.sha256,
|
||||||
|
len(test_instance.blocks_and_transactions))
|
||||||
|
if (not self.check_results(block.sha256, block_outcome)):
|
||||||
|
raise AssertionError("Block test failed at test %d" % test_number)
|
||||||
|
if (not test_instance.sync_every_tx and tx is not None):
|
||||||
|
if len(invqueue) > 0:
|
||||||
|
[ c.send_message(msg_inv(invqueue)) for c in self.connections ]
|
||||||
|
invqueue = []
|
||||||
|
self.sync_transaction(tx.sha256, len(test_instance.blocks_and_transactions))
|
||||||
|
if (not self.check_mempool(tx.sha256, tx_outcome)):
|
||||||
|
raise AssertionError("Mempool test failed at test %d" % test_number)
|
||||||
|
|
||||||
|
print "Test %d: PASS" % test_number, [ c.rpc.getblockcount() for c in self.connections ]
|
||||||
|
test_number += 1
|
||||||
|
|
||||||
|
self.block_store.close()
|
||||||
|
self.tx_store.close()
|
||||||
|
[ c.disconnect_node() for c in self.connections ]
|
115
qa/rpc-tests/invalidblockrequest.py
Executable file
115
qa/rpc-tests/invalidblockrequest.py
Executable file
|
@ -0,0 +1,115 @@
|
||||||
|
#!/usr/bin/env python2
|
||||||
|
#
|
||||||
|
# Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
#
|
||||||
|
|
||||||
|
from test_framework import ComparisonTestFramework
|
||||||
|
from util import *
|
||||||
|
from comptool import TestManager, TestInstance
|
||||||
|
from mininode import *
|
||||||
|
from blocktools import *
|
||||||
|
import logging
|
||||||
|
import copy
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
In this test we connect to one node over p2p, and test block requests:
|
||||||
|
1) Valid blocks should be requested and become chain tip.
|
||||||
|
2) Invalid block with duplicated transaction should be re-requested.
|
||||||
|
3) Invalid block with bad coinbase value should be rejected and not
|
||||||
|
re-requested.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Use the ComparisonTestFramework with 1 node: only use --testbinary.
|
||||||
|
class InvalidBlockRequestTest(ComparisonTestFramework):
|
||||||
|
|
||||||
|
''' Can either run this test as 1 node with expected answers, or two and compare them.
|
||||||
|
Change the "outcome" variable from each TestInstance object to only do the comparison. '''
|
||||||
|
def __init__(self):
|
||||||
|
self.num_nodes = 1
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
test = TestManager(self, self.options.tmpdir)
|
||||||
|
test.add_all_connections(self.nodes)
|
||||||
|
self.tip = None
|
||||||
|
self.block_time = None
|
||||||
|
NetworkThread().start() # Start up network handling in another thread
|
||||||
|
test.run()
|
||||||
|
|
||||||
|
def get_tests(self):
|
||||||
|
if self.tip is None:
|
||||||
|
self.tip = int ("0x" + self.nodes[0].getbestblockhash() + "L", 0)
|
||||||
|
self.block_time = int(time.time())+1
|
||||||
|
|
||||||
|
'''
|
||||||
|
Create a new block with an anyone-can-spend coinbase
|
||||||
|
'''
|
||||||
|
block = create_block(self.tip, create_coinbase(), self.block_time)
|
||||||
|
self.block_time += 1
|
||||||
|
block.solve()
|
||||||
|
# Save the coinbase for later
|
||||||
|
self.block1 = block
|
||||||
|
self.tip = block.sha256
|
||||||
|
yield TestInstance([[block, True]])
|
||||||
|
|
||||||
|
'''
|
||||||
|
Now we need that block to mature so we can spend the coinbase.
|
||||||
|
'''
|
||||||
|
test = TestInstance(sync_every_block=False)
|
||||||
|
for i in xrange(100):
|
||||||
|
block = create_block(self.tip, create_coinbase(), self.block_time)
|
||||||
|
block.solve()
|
||||||
|
self.tip = block.sha256
|
||||||
|
self.block_time += 1
|
||||||
|
test.blocks_and_transactions.append([block, True])
|
||||||
|
yield test
|
||||||
|
|
||||||
|
'''
|
||||||
|
Now we use merkle-root malleability to generate an invalid block with
|
||||||
|
same blockheader.
|
||||||
|
Manufacture a block with 3 transactions (coinbase, spend of prior
|
||||||
|
coinbase, spend of that spend). Duplicate the 3rd transaction to
|
||||||
|
leave merkle root and blockheader unchanged but invalidate the block.
|
||||||
|
'''
|
||||||
|
block2 = create_block(self.tip, create_coinbase(), self.block_time)
|
||||||
|
self.block_time += 1
|
||||||
|
|
||||||
|
# chr(81) is OP_TRUE
|
||||||
|
tx1 = create_transaction(self.block1.vtx[0], 0, chr(81), 50*100000000)
|
||||||
|
tx2 = create_transaction(tx1, 0, chr(81), 50*100000000)
|
||||||
|
|
||||||
|
block2.vtx.extend([tx1, tx2])
|
||||||
|
block2.hashMerkleRoot = block2.calc_merkle_root()
|
||||||
|
block2.rehash()
|
||||||
|
block2.solve()
|
||||||
|
orig_hash = block2.sha256
|
||||||
|
block2_orig = copy.deepcopy(block2)
|
||||||
|
|
||||||
|
# Mutate block 2
|
||||||
|
block2.vtx.append(tx2)
|
||||||
|
assert_equal(block2.hashMerkleRoot, block2.calc_merkle_root())
|
||||||
|
assert_equal(orig_hash, block2.rehash())
|
||||||
|
assert(block2_orig.vtx != block2.vtx)
|
||||||
|
|
||||||
|
self.tip = block2.sha256
|
||||||
|
yield TestInstance([[block2, False], [block2_orig, True]])
|
||||||
|
|
||||||
|
'''
|
||||||
|
Make sure that a totally screwed up block is not valid.
|
||||||
|
'''
|
||||||
|
block3 = create_block(self.tip, create_coinbase(), self.block_time)
|
||||||
|
self.block_time += 1
|
||||||
|
block3.vtx[0].vout[0].nValue = 100*100000000 # Too high!
|
||||||
|
block3.vtx[0].sha256=None
|
||||||
|
block3.vtx[0].calc_sha256()
|
||||||
|
block3.hashMerkleRoot = block3.calc_merkle_root()
|
||||||
|
block3.rehash()
|
||||||
|
block3.solve()
|
||||||
|
|
||||||
|
yield TestInstance([[block3, False]])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
InvalidBlockRequestTest().main()
|
100
qa/rpc-tests/maxblocksinflight.py
Executable file
100
qa/rpc-tests/maxblocksinflight.py
Executable file
|
@ -0,0 +1,100 @@
|
||||||
|
#!/usr/bin/env python2
|
||||||
|
#
|
||||||
|
# Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
#
|
||||||
|
|
||||||
|
from mininode import *
|
||||||
|
from test_framework import BitcoinTestFramework
|
||||||
|
from util import *
|
||||||
|
import logging
|
||||||
|
|
||||||
|
'''
|
||||||
|
In this test we connect to one node over p2p, send it numerous inv's, and
|
||||||
|
compare the resulting number of getdata requests to a max allowed value. We
|
||||||
|
test for exceeding 128 blocks in flight, which was the limit an 0.9 client will
|
||||||
|
reach. [0.10 clients shouldn't request more than 16 from a single peer.]
|
||||||
|
'''
|
||||||
|
MAX_REQUESTS = 128
|
||||||
|
|
||||||
|
class TestManager(NodeConnCB):
|
||||||
|
# set up NodeConnCB callbacks, overriding base class
|
||||||
|
def on_getdata(self, conn, message):
|
||||||
|
self.log.debug("got getdata %s" % repr(message))
|
||||||
|
# Log the requests
|
||||||
|
for inv in message.inv:
|
||||||
|
if inv.hash not in self.blockReqCounts:
|
||||||
|
self.blockReqCounts[inv.hash] = 0
|
||||||
|
self.blockReqCounts[inv.hash] += 1
|
||||||
|
|
||||||
|
def on_close(self, conn):
|
||||||
|
if not self.disconnectOkay:
|
||||||
|
raise EarlyDisconnectError(0)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
NodeConnCB.__init__(self)
|
||||||
|
self.log = logging.getLogger("BlockRelayTest")
|
||||||
|
self.create_callback_map()
|
||||||
|
|
||||||
|
def add_new_connection(self, connection):
|
||||||
|
self.connection = connection
|
||||||
|
self.blockReqCounts = {}
|
||||||
|
self.disconnectOkay = False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
fail = False
|
||||||
|
self.connection.rpc.generate(1) # Leave IBD
|
||||||
|
|
||||||
|
numBlocksToGenerate = [ 8, 16, 128, 1024 ]
|
||||||
|
for count in range(len(numBlocksToGenerate)):
|
||||||
|
current_invs = []
|
||||||
|
for i in range(numBlocksToGenerate[count]):
|
||||||
|
current_invs.append(CInv(2, random.randrange(0, 1<<256)))
|
||||||
|
if len(current_invs) >= 50000:
|
||||||
|
self.connection.send_message(msg_inv(current_invs))
|
||||||
|
current_invs = []
|
||||||
|
if len(current_invs) > 0:
|
||||||
|
self.connection.send_message(msg_inv(current_invs))
|
||||||
|
|
||||||
|
# Wait and see how many blocks were requested
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
total_requests = 0
|
||||||
|
for key in self.blockReqCounts:
|
||||||
|
total_requests += self.blockReqCounts[key]
|
||||||
|
if self.blockReqCounts[key] > 1:
|
||||||
|
raise AssertionError("Error, test failed: block %064x requested more than once" % key)
|
||||||
|
if total_requests > MAX_REQUESTS:
|
||||||
|
raise AssertionError("Error, too many blocks (%d) requested" % total_requests)
|
||||||
|
print "Round %d: success (total requests: %d)" % (count, total_requests)
|
||||||
|
except AssertionError as e:
|
||||||
|
print "TEST FAILED: ", e.args
|
||||||
|
|
||||||
|
self.disconnectOkay = True
|
||||||
|
self.connection.disconnect_node()
|
||||||
|
|
||||||
|
|
||||||
|
class MaxBlocksInFlightTest(BitcoinTestFramework):
|
||||||
|
def add_options(self, parser):
|
||||||
|
parser.add_option("--testbinary", dest="testbinary",
|
||||||
|
default=os.getenv("BITCOIND", "bitcoind"),
|
||||||
|
help="Binary to test max block requests behavior")
|
||||||
|
|
||||||
|
def setup_chain(self):
|
||||||
|
print "Initializing test directory "+self.options.tmpdir
|
||||||
|
initialize_chain_clean(self.options.tmpdir, 1)
|
||||||
|
|
||||||
|
def setup_network(self):
|
||||||
|
self.nodes = start_nodes(1, self.options.tmpdir,
|
||||||
|
extra_args=[['-debug', '-whitelist=127.0.0.1']],
|
||||||
|
binary=[self.options.testbinary])
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
test = TestManager()
|
||||||
|
test.add_new_connection(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test))
|
||||||
|
NetworkThread().start() # Start up network handling in another thread
|
||||||
|
test.run()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
MaxBlocksInFlightTest().main()
|
1247
qa/rpc-tests/mininode.py
Executable file
1247
qa/rpc-tests/mininode.py
Executable file
File diff suppressed because it is too large
Load diff
896
qa/rpc-tests/script.py
Normal file
896
qa/rpc-tests/script.py
Normal file
|
@ -0,0 +1,896 @@
|
||||||
|
#
|
||||||
|
# script.py
|
||||||
|
#
|
||||||
|
# This file is modified from python-bitcoinlib.
|
||||||
|
#
|
||||||
|
# Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Scripts
|
||||||
|
|
||||||
|
Functionality to build scripts, as well as SignatureHash().
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||||
|
|
||||||
|
from mininode import CTransaction, CTxOut, hash256
|
||||||
|
|
||||||
|
import sys
|
||||||
|
bchr = chr
|
||||||
|
bord = ord
|
||||||
|
if sys.version > '3':
|
||||||
|
long = int
|
||||||
|
bchr = lambda x: bytes([x])
|
||||||
|
bord = lambda x: x
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import struct
|
||||||
|
|
||||||
|
import bignum
|
||||||
|
|
||||||
|
MAX_SCRIPT_SIZE = 10000
|
||||||
|
MAX_SCRIPT_ELEMENT_SIZE = 520
|
||||||
|
MAX_SCRIPT_OPCODES = 201
|
||||||
|
|
||||||
|
OPCODE_NAMES = {}
|
||||||
|
|
||||||
|
_opcode_instances = []
|
||||||
|
class CScriptOp(int):
|
||||||
|
"""A single script opcode"""
|
||||||
|
__slots__ = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode_op_pushdata(d):
|
||||||
|
"""Encode a PUSHDATA op, returning bytes"""
|
||||||
|
if len(d) < 0x4c:
|
||||||
|
return b'' + bchr(len(d)) + d # OP_PUSHDATA
|
||||||
|
elif len(d) <= 0xff:
|
||||||
|
return b'\x4c' + bchr(len(d)) + d # OP_PUSHDATA1
|
||||||
|
elif len(d) <= 0xffff:
|
||||||
|
return b'\x4d' + struct.pack(b'<H', len(d)) + d # OP_PUSHDATA2
|
||||||
|
elif len(d) <= 0xffffffff:
|
||||||
|
return b'\x4e' + struct.pack(b'<I', len(d)) + d # OP_PUSHDATA4
|
||||||
|
else:
|
||||||
|
raise ValueError("Data too long to encode in a PUSHDATA op")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode_op_n(n):
|
||||||
|
"""Encode a small integer op, returning an opcode"""
|
||||||
|
if not (0 <= n <= 16):
|
||||||
|
raise ValueError('Integer must be in range 0 <= n <= 16, got %d' % n)
|
||||||
|
|
||||||
|
if n == 0:
|
||||||
|
return OP_0
|
||||||
|
else:
|
||||||
|
return CScriptOp(OP_1 + n-1)
|
||||||
|
|
||||||
|
def decode_op_n(self):
|
||||||
|
"""Decode a small integer opcode, returning an integer"""
|
||||||
|
if self == OP_0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not (self == OP_0 or OP_1 <= self <= OP_16):
|
||||||
|
raise ValueError('op %r is not an OP_N' % self)
|
||||||
|
|
||||||
|
return int(self - OP_1+1)
|
||||||
|
|
||||||
|
def is_small_int(self):
|
||||||
|
"""Return true if the op pushes a small integer to the stack"""
|
||||||
|
if 0x51 <= self <= 0x60 or self == 0:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self in OPCODE_NAMES:
|
||||||
|
return OPCODE_NAMES[self]
|
||||||
|
else:
|
||||||
|
return 'CScriptOp(0x%x)' % self
|
||||||
|
|
||||||
|
def __new__(cls, n):
|
||||||
|
try:
|
||||||
|
return _opcode_instances[n]
|
||||||
|
except IndexError:
|
||||||
|
assert len(_opcode_instances) == n
|
||||||
|
_opcode_instances.append(super(CScriptOp, cls).__new__(cls, n))
|
||||||
|
return _opcode_instances[n]
|
||||||
|
|
||||||
|
# Populate opcode instance table
|
||||||
|
for n in range(0xff+1):
|
||||||
|
CScriptOp(n)
|
||||||
|
|
||||||
|
|
||||||
|
# push value
|
||||||
|
OP_0 = CScriptOp(0x00)
|
||||||
|
OP_FALSE = OP_0
|
||||||
|
OP_PUSHDATA1 = CScriptOp(0x4c)
|
||||||
|
OP_PUSHDATA2 = CScriptOp(0x4d)
|
||||||
|
OP_PUSHDATA4 = CScriptOp(0x4e)
|
||||||
|
OP_1NEGATE = CScriptOp(0x4f)
|
||||||
|
OP_RESERVED = CScriptOp(0x50)
|
||||||
|
OP_1 = CScriptOp(0x51)
|
||||||
|
OP_TRUE=OP_1
|
||||||
|
OP_2 = CScriptOp(0x52)
|
||||||
|
OP_3 = CScriptOp(0x53)
|
||||||
|
OP_4 = CScriptOp(0x54)
|
||||||
|
OP_5 = CScriptOp(0x55)
|
||||||
|
OP_6 = CScriptOp(0x56)
|
||||||
|
OP_7 = CScriptOp(0x57)
|
||||||
|
OP_8 = CScriptOp(0x58)
|
||||||
|
OP_9 = CScriptOp(0x59)
|
||||||
|
OP_10 = CScriptOp(0x5a)
|
||||||
|
OP_11 = CScriptOp(0x5b)
|
||||||
|
OP_12 = CScriptOp(0x5c)
|
||||||
|
OP_13 = CScriptOp(0x5d)
|
||||||
|
OP_14 = CScriptOp(0x5e)
|
||||||
|
OP_15 = CScriptOp(0x5f)
|
||||||
|
OP_16 = CScriptOp(0x60)
|
||||||
|
|
||||||
|
# control
|
||||||
|
OP_NOP = CScriptOp(0x61)
|
||||||
|
OP_VER = CScriptOp(0x62)
|
||||||
|
OP_IF = CScriptOp(0x63)
|
||||||
|
OP_NOTIF = CScriptOp(0x64)
|
||||||
|
OP_VERIF = CScriptOp(0x65)
|
||||||
|
OP_VERNOTIF = CScriptOp(0x66)
|
||||||
|
OP_ELSE = CScriptOp(0x67)
|
||||||
|
OP_ENDIF = CScriptOp(0x68)
|
||||||
|
OP_VERIFY = CScriptOp(0x69)
|
||||||
|
OP_RETURN = CScriptOp(0x6a)
|
||||||
|
|
||||||
|
# stack ops
|
||||||
|
OP_TOALTSTACK = CScriptOp(0x6b)
|
||||||
|
OP_FROMALTSTACK = CScriptOp(0x6c)
|
||||||
|
OP_2DROP = CScriptOp(0x6d)
|
||||||
|
OP_2DUP = CScriptOp(0x6e)
|
||||||
|
OP_3DUP = CScriptOp(0x6f)
|
||||||
|
OP_2OVER = CScriptOp(0x70)
|
||||||
|
OP_2ROT = CScriptOp(0x71)
|
||||||
|
OP_2SWAP = CScriptOp(0x72)
|
||||||
|
OP_IFDUP = CScriptOp(0x73)
|
||||||
|
OP_DEPTH = CScriptOp(0x74)
|
||||||
|
OP_DROP = CScriptOp(0x75)
|
||||||
|
OP_DUP = CScriptOp(0x76)
|
||||||
|
OP_NIP = CScriptOp(0x77)
|
||||||
|
OP_OVER = CScriptOp(0x78)
|
||||||
|
OP_PICK = CScriptOp(0x79)
|
||||||
|
OP_ROLL = CScriptOp(0x7a)
|
||||||
|
OP_ROT = CScriptOp(0x7b)
|
||||||
|
OP_SWAP = CScriptOp(0x7c)
|
||||||
|
OP_TUCK = CScriptOp(0x7d)
|
||||||
|
|
||||||
|
# splice ops
|
||||||
|
OP_CAT = CScriptOp(0x7e)
|
||||||
|
OP_SUBSTR = CScriptOp(0x7f)
|
||||||
|
OP_LEFT = CScriptOp(0x80)
|
||||||
|
OP_RIGHT = CScriptOp(0x81)
|
||||||
|
OP_SIZE = CScriptOp(0x82)
|
||||||
|
|
||||||
|
# bit logic
|
||||||
|
OP_INVERT = CScriptOp(0x83)
|
||||||
|
OP_AND = CScriptOp(0x84)
|
||||||
|
OP_OR = CScriptOp(0x85)
|
||||||
|
OP_XOR = CScriptOp(0x86)
|
||||||
|
OP_EQUAL = CScriptOp(0x87)
|
||||||
|
OP_EQUALVERIFY = CScriptOp(0x88)
|
||||||
|
OP_RESERVED1 = CScriptOp(0x89)
|
||||||
|
OP_RESERVED2 = CScriptOp(0x8a)
|
||||||
|
|
||||||
|
# numeric
|
||||||
|
OP_1ADD = CScriptOp(0x8b)
|
||||||
|
OP_1SUB = CScriptOp(0x8c)
|
||||||
|
OP_2MUL = CScriptOp(0x8d)
|
||||||
|
OP_2DIV = CScriptOp(0x8e)
|
||||||
|
OP_NEGATE = CScriptOp(0x8f)
|
||||||
|
OP_ABS = CScriptOp(0x90)
|
||||||
|
OP_NOT = CScriptOp(0x91)
|
||||||
|
OP_0NOTEQUAL = CScriptOp(0x92)
|
||||||
|
|
||||||
|
OP_ADD = CScriptOp(0x93)
|
||||||
|
OP_SUB = CScriptOp(0x94)
|
||||||
|
OP_MUL = CScriptOp(0x95)
|
||||||
|
OP_DIV = CScriptOp(0x96)
|
||||||
|
OP_MOD = CScriptOp(0x97)
|
||||||
|
OP_LSHIFT = CScriptOp(0x98)
|
||||||
|
OP_RSHIFT = CScriptOp(0x99)
|
||||||
|
|
||||||
|
OP_BOOLAND = CScriptOp(0x9a)
|
||||||
|
OP_BOOLOR = CScriptOp(0x9b)
|
||||||
|
OP_NUMEQUAL = CScriptOp(0x9c)
|
||||||
|
OP_NUMEQUALVERIFY = CScriptOp(0x9d)
|
||||||
|
OP_NUMNOTEQUAL = CScriptOp(0x9e)
|
||||||
|
OP_LESSTHAN = CScriptOp(0x9f)
|
||||||
|
OP_GREATERTHAN = CScriptOp(0xa0)
|
||||||
|
OP_LESSTHANOREQUAL = CScriptOp(0xa1)
|
||||||
|
OP_GREATERTHANOREQUAL = CScriptOp(0xa2)
|
||||||
|
OP_MIN = CScriptOp(0xa3)
|
||||||
|
OP_MAX = CScriptOp(0xa4)
|
||||||
|
|
||||||
|
OP_WITHIN = CScriptOp(0xa5)
|
||||||
|
|
||||||
|
# crypto
|
||||||
|
OP_RIPEMD160 = CScriptOp(0xa6)
|
||||||
|
OP_SHA1 = CScriptOp(0xa7)
|
||||||
|
OP_SHA256 = CScriptOp(0xa8)
|
||||||
|
OP_HASH160 = CScriptOp(0xa9)
|
||||||
|
OP_HASH256 = CScriptOp(0xaa)
|
||||||
|
OP_CODESEPARATOR = CScriptOp(0xab)
|
||||||
|
OP_CHECKSIG = CScriptOp(0xac)
|
||||||
|
OP_CHECKSIGVERIFY = CScriptOp(0xad)
|
||||||
|
OP_CHECKMULTISIG = CScriptOp(0xae)
|
||||||
|
OP_CHECKMULTISIGVERIFY = CScriptOp(0xaf)
|
||||||
|
|
||||||
|
# expansion
|
||||||
|
OP_NOP1 = CScriptOp(0xb0)
|
||||||
|
OP_NOP2 = CScriptOp(0xb1)
|
||||||
|
OP_NOP3 = CScriptOp(0xb2)
|
||||||
|
OP_NOP4 = CScriptOp(0xb3)
|
||||||
|
OP_NOP5 = CScriptOp(0xb4)
|
||||||
|
OP_NOP6 = CScriptOp(0xb5)
|
||||||
|
OP_NOP7 = CScriptOp(0xb6)
|
||||||
|
OP_NOP8 = CScriptOp(0xb7)
|
||||||
|
OP_NOP9 = CScriptOp(0xb8)
|
||||||
|
OP_NOP10 = CScriptOp(0xb9)
|
||||||
|
|
||||||
|
# template matching params
|
||||||
|
OP_SMALLINTEGER = CScriptOp(0xfa)
|
||||||
|
OP_PUBKEYS = CScriptOp(0xfb)
|
||||||
|
OP_PUBKEYHASH = CScriptOp(0xfd)
|
||||||
|
OP_PUBKEY = CScriptOp(0xfe)
|
||||||
|
|
||||||
|
OP_INVALIDOPCODE = CScriptOp(0xff)
|
||||||
|
|
||||||
|
VALID_OPCODES = {
|
||||||
|
OP_1NEGATE,
|
||||||
|
OP_RESERVED,
|
||||||
|
OP_1,
|
||||||
|
OP_2,
|
||||||
|
OP_3,
|
||||||
|
OP_4,
|
||||||
|
OP_5,
|
||||||
|
OP_6,
|
||||||
|
OP_7,
|
||||||
|
OP_8,
|
||||||
|
OP_9,
|
||||||
|
OP_10,
|
||||||
|
OP_11,
|
||||||
|
OP_12,
|
||||||
|
OP_13,
|
||||||
|
OP_14,
|
||||||
|
OP_15,
|
||||||
|
OP_16,
|
||||||
|
|
||||||
|
OP_NOP,
|
||||||
|
OP_VER,
|
||||||
|
OP_IF,
|
||||||
|
OP_NOTIF,
|
||||||
|
OP_VERIF,
|
||||||
|
OP_VERNOTIF,
|
||||||
|
OP_ELSE,
|
||||||
|
OP_ENDIF,
|
||||||
|
OP_VERIFY,
|
||||||
|
OP_RETURN,
|
||||||
|
|
||||||
|
OP_TOALTSTACK,
|
||||||
|
OP_FROMALTSTACK,
|
||||||
|
OP_2DROP,
|
||||||
|
OP_2DUP,
|
||||||
|
OP_3DUP,
|
||||||
|
OP_2OVER,
|
||||||
|
OP_2ROT,
|
||||||
|
OP_2SWAP,
|
||||||
|
OP_IFDUP,
|
||||||
|
OP_DEPTH,
|
||||||
|
OP_DROP,
|
||||||
|
OP_DUP,
|
||||||
|
OP_NIP,
|
||||||
|
OP_OVER,
|
||||||
|
OP_PICK,
|
||||||
|
OP_ROLL,
|
||||||
|
OP_ROT,
|
||||||
|
OP_SWAP,
|
||||||
|
OP_TUCK,
|
||||||
|
|
||||||
|
OP_CAT,
|
||||||
|
OP_SUBSTR,
|
||||||
|
OP_LEFT,
|
||||||
|
OP_RIGHT,
|
||||||
|
OP_SIZE,
|
||||||
|
|
||||||
|
OP_INVERT,
|
||||||
|
OP_AND,
|
||||||
|
OP_OR,
|
||||||
|
OP_XOR,
|
||||||
|
OP_EQUAL,
|
||||||
|
OP_EQUALVERIFY,
|
||||||
|
OP_RESERVED1,
|
||||||
|
OP_RESERVED2,
|
||||||
|
|
||||||
|
OP_1ADD,
|
||||||
|
OP_1SUB,
|
||||||
|
OP_2MUL,
|
||||||
|
OP_2DIV,
|
||||||
|
OP_NEGATE,
|
||||||
|
OP_ABS,
|
||||||
|
OP_NOT,
|
||||||
|
OP_0NOTEQUAL,
|
||||||
|
|
||||||
|
OP_ADD,
|
||||||
|
OP_SUB,
|
||||||
|
OP_MUL,
|
||||||
|
OP_DIV,
|
||||||
|
OP_MOD,
|
||||||
|
OP_LSHIFT,
|
||||||
|
OP_RSHIFT,
|
||||||
|
|
||||||
|
OP_BOOLAND,
|
||||||
|
OP_BOOLOR,
|
||||||
|
OP_NUMEQUAL,
|
||||||
|
OP_NUMEQUALVERIFY,
|
||||||
|
OP_NUMNOTEQUAL,
|
||||||
|
OP_LESSTHAN,
|
||||||
|
OP_GREATERTHAN,
|
||||||
|
OP_LESSTHANOREQUAL,
|
||||||
|
OP_GREATERTHANOREQUAL,
|
||||||
|
OP_MIN,
|
||||||
|
OP_MAX,
|
||||||
|
|
||||||
|
OP_WITHIN,
|
||||||
|
|
||||||
|
OP_RIPEMD160,
|
||||||
|
OP_SHA1,
|
||||||
|
OP_SHA256,
|
||||||
|
OP_HASH160,
|
||||||
|
OP_HASH256,
|
||||||
|
OP_CODESEPARATOR,
|
||||||
|
OP_CHECKSIG,
|
||||||
|
OP_CHECKSIGVERIFY,
|
||||||
|
OP_CHECKMULTISIG,
|
||||||
|
OP_CHECKMULTISIGVERIFY,
|
||||||
|
|
||||||
|
OP_NOP1,
|
||||||
|
OP_NOP2,
|
||||||
|
OP_NOP3,
|
||||||
|
OP_NOP4,
|
||||||
|
OP_NOP5,
|
||||||
|
OP_NOP6,
|
||||||
|
OP_NOP7,
|
||||||
|
OP_NOP8,
|
||||||
|
OP_NOP9,
|
||||||
|
OP_NOP10,
|
||||||
|
|
||||||
|
OP_SMALLINTEGER,
|
||||||
|
OP_PUBKEYS,
|
||||||
|
OP_PUBKEYHASH,
|
||||||
|
OP_PUBKEY,
|
||||||
|
}
|
||||||
|
|
||||||
|
OPCODE_NAMES.update({
|
||||||
|
OP_0 : 'OP_0',
|
||||||
|
OP_PUSHDATA1 : 'OP_PUSHDATA1',
|
||||||
|
OP_PUSHDATA2 : 'OP_PUSHDATA2',
|
||||||
|
OP_PUSHDATA4 : 'OP_PUSHDATA4',
|
||||||
|
OP_1NEGATE : 'OP_1NEGATE',
|
||||||
|
OP_RESERVED : 'OP_RESERVED',
|
||||||
|
OP_1 : 'OP_1',
|
||||||
|
OP_2 : 'OP_2',
|
||||||
|
OP_3 : 'OP_3',
|
||||||
|
OP_4 : 'OP_4',
|
||||||
|
OP_5 : 'OP_5',
|
||||||
|
OP_6 : 'OP_6',
|
||||||
|
OP_7 : 'OP_7',
|
||||||
|
OP_8 : 'OP_8',
|
||||||
|
OP_9 : 'OP_9',
|
||||||
|
OP_10 : 'OP_10',
|
||||||
|
OP_11 : 'OP_11',
|
||||||
|
OP_12 : 'OP_12',
|
||||||
|
OP_13 : 'OP_13',
|
||||||
|
OP_14 : 'OP_14',
|
||||||
|
OP_15 : 'OP_15',
|
||||||
|
OP_16 : 'OP_16',
|
||||||
|
OP_NOP : 'OP_NOP',
|
||||||
|
OP_VER : 'OP_VER',
|
||||||
|
OP_IF : 'OP_IF',
|
||||||
|
OP_NOTIF : 'OP_NOTIF',
|
||||||
|
OP_VERIF : 'OP_VERIF',
|
||||||
|
OP_VERNOTIF : 'OP_VERNOTIF',
|
||||||
|
OP_ELSE : 'OP_ELSE',
|
||||||
|
OP_ENDIF : 'OP_ENDIF',
|
||||||
|
OP_VERIFY : 'OP_VERIFY',
|
||||||
|
OP_RETURN : 'OP_RETURN',
|
||||||
|
OP_TOALTSTACK : 'OP_TOALTSTACK',
|
||||||
|
OP_FROMALTSTACK : 'OP_FROMALTSTACK',
|
||||||
|
OP_2DROP : 'OP_2DROP',
|
||||||
|
OP_2DUP : 'OP_2DUP',
|
||||||
|
OP_3DUP : 'OP_3DUP',
|
||||||
|
OP_2OVER : 'OP_2OVER',
|
||||||
|
OP_2ROT : 'OP_2ROT',
|
||||||
|
OP_2SWAP : 'OP_2SWAP',
|
||||||
|
OP_IFDUP : 'OP_IFDUP',
|
||||||
|
OP_DEPTH : 'OP_DEPTH',
|
||||||
|
OP_DROP : 'OP_DROP',
|
||||||
|
OP_DUP : 'OP_DUP',
|
||||||
|
OP_NIP : 'OP_NIP',
|
||||||
|
OP_OVER : 'OP_OVER',
|
||||||
|
OP_PICK : 'OP_PICK',
|
||||||
|
OP_ROLL : 'OP_ROLL',
|
||||||
|
OP_ROT : 'OP_ROT',
|
||||||
|
OP_SWAP : 'OP_SWAP',
|
||||||
|
OP_TUCK : 'OP_TUCK',
|
||||||
|
OP_CAT : 'OP_CAT',
|
||||||
|
OP_SUBSTR : 'OP_SUBSTR',
|
||||||
|
OP_LEFT : 'OP_LEFT',
|
||||||
|
OP_RIGHT : 'OP_RIGHT',
|
||||||
|
OP_SIZE : 'OP_SIZE',
|
||||||
|
OP_INVERT : 'OP_INVERT',
|
||||||
|
OP_AND : 'OP_AND',
|
||||||
|
OP_OR : 'OP_OR',
|
||||||
|
OP_XOR : 'OP_XOR',
|
||||||
|
OP_EQUAL : 'OP_EQUAL',
|
||||||
|
OP_EQUALVERIFY : 'OP_EQUALVERIFY',
|
||||||
|
OP_RESERVED1 : 'OP_RESERVED1',
|
||||||
|
OP_RESERVED2 : 'OP_RESERVED2',
|
||||||
|
OP_1ADD : 'OP_1ADD',
|
||||||
|
OP_1SUB : 'OP_1SUB',
|
||||||
|
OP_2MUL : 'OP_2MUL',
|
||||||
|
OP_2DIV : 'OP_2DIV',
|
||||||
|
OP_NEGATE : 'OP_NEGATE',
|
||||||
|
OP_ABS : 'OP_ABS',
|
||||||
|
OP_NOT : 'OP_NOT',
|
||||||
|
OP_0NOTEQUAL : 'OP_0NOTEQUAL',
|
||||||
|
OP_ADD : 'OP_ADD',
|
||||||
|
OP_SUB : 'OP_SUB',
|
||||||
|
OP_MUL : 'OP_MUL',
|
||||||
|
OP_DIV : 'OP_DIV',
|
||||||
|
OP_MOD : 'OP_MOD',
|
||||||
|
OP_LSHIFT : 'OP_LSHIFT',
|
||||||
|
OP_RSHIFT : 'OP_RSHIFT',
|
||||||
|
OP_BOOLAND : 'OP_BOOLAND',
|
||||||
|
OP_BOOLOR : 'OP_BOOLOR',
|
||||||
|
OP_NUMEQUAL : 'OP_NUMEQUAL',
|
||||||
|
OP_NUMEQUALVERIFY : 'OP_NUMEQUALVERIFY',
|
||||||
|
OP_NUMNOTEQUAL : 'OP_NUMNOTEQUAL',
|
||||||
|
OP_LESSTHAN : 'OP_LESSTHAN',
|
||||||
|
OP_GREATERTHAN : 'OP_GREATERTHAN',
|
||||||
|
OP_LESSTHANOREQUAL : 'OP_LESSTHANOREQUAL',
|
||||||
|
OP_GREATERTHANOREQUAL : 'OP_GREATERTHANOREQUAL',
|
||||||
|
OP_MIN : 'OP_MIN',
|
||||||
|
OP_MAX : 'OP_MAX',
|
||||||
|
OP_WITHIN : 'OP_WITHIN',
|
||||||
|
OP_RIPEMD160 : 'OP_RIPEMD160',
|
||||||
|
OP_SHA1 : 'OP_SHA1',
|
||||||
|
OP_SHA256 : 'OP_SHA256',
|
||||||
|
OP_HASH160 : 'OP_HASH160',
|
||||||
|
OP_HASH256 : 'OP_HASH256',
|
||||||
|
OP_CODESEPARATOR : 'OP_CODESEPARATOR',
|
||||||
|
OP_CHECKSIG : 'OP_CHECKSIG',
|
||||||
|
OP_CHECKSIGVERIFY : 'OP_CHECKSIGVERIFY',
|
||||||
|
OP_CHECKMULTISIG : 'OP_CHECKMULTISIG',
|
||||||
|
OP_CHECKMULTISIGVERIFY : 'OP_CHECKMULTISIGVERIFY',
|
||||||
|
OP_NOP1 : 'OP_NOP1',
|
||||||
|
OP_NOP2 : 'OP_NOP2',
|
||||||
|
OP_NOP3 : 'OP_NOP3',
|
||||||
|
OP_NOP4 : 'OP_NOP4',
|
||||||
|
OP_NOP5 : 'OP_NOP5',
|
||||||
|
OP_NOP6 : 'OP_NOP6',
|
||||||
|
OP_NOP7 : 'OP_NOP7',
|
||||||
|
OP_NOP8 : 'OP_NOP8',
|
||||||
|
OP_NOP9 : 'OP_NOP9',
|
||||||
|
OP_NOP10 : 'OP_NOP10',
|
||||||
|
OP_SMALLINTEGER : 'OP_SMALLINTEGER',
|
||||||
|
OP_PUBKEYS : 'OP_PUBKEYS',
|
||||||
|
OP_PUBKEYHASH : 'OP_PUBKEYHASH',
|
||||||
|
OP_PUBKEY : 'OP_PUBKEY',
|
||||||
|
OP_INVALIDOPCODE : 'OP_INVALIDOPCODE',
|
||||||
|
})
|
||||||
|
|
||||||
|
OPCODES_BY_NAME = {
|
||||||
|
'OP_0' : OP_0,
|
||||||
|
'OP_PUSHDATA1' : OP_PUSHDATA1,
|
||||||
|
'OP_PUSHDATA2' : OP_PUSHDATA2,
|
||||||
|
'OP_PUSHDATA4' : OP_PUSHDATA4,
|
||||||
|
'OP_1NEGATE' : OP_1NEGATE,
|
||||||
|
'OP_RESERVED' : OP_RESERVED,
|
||||||
|
'OP_1' : OP_1,
|
||||||
|
'OP_2' : OP_2,
|
||||||
|
'OP_3' : OP_3,
|
||||||
|
'OP_4' : OP_4,
|
||||||
|
'OP_5' : OP_5,
|
||||||
|
'OP_6' : OP_6,
|
||||||
|
'OP_7' : OP_7,
|
||||||
|
'OP_8' : OP_8,
|
||||||
|
'OP_9' : OP_9,
|
||||||
|
'OP_10' : OP_10,
|
||||||
|
'OP_11' : OP_11,
|
||||||
|
'OP_12' : OP_12,
|
||||||
|
'OP_13' : OP_13,
|
||||||
|
'OP_14' : OP_14,
|
||||||
|
'OP_15' : OP_15,
|
||||||
|
'OP_16' : OP_16,
|
||||||
|
'OP_NOP' : OP_NOP,
|
||||||
|
'OP_VER' : OP_VER,
|
||||||
|
'OP_IF' : OP_IF,
|
||||||
|
'OP_NOTIF' : OP_NOTIF,
|
||||||
|
'OP_VERIF' : OP_VERIF,
|
||||||
|
'OP_VERNOTIF' : OP_VERNOTIF,
|
||||||
|
'OP_ELSE' : OP_ELSE,
|
||||||
|
'OP_ENDIF' : OP_ENDIF,
|
||||||
|
'OP_VERIFY' : OP_VERIFY,
|
||||||
|
'OP_RETURN' : OP_RETURN,
|
||||||
|
'OP_TOALTSTACK' : OP_TOALTSTACK,
|
||||||
|
'OP_FROMALTSTACK' : OP_FROMALTSTACK,
|
||||||
|
'OP_2DROP' : OP_2DROP,
|
||||||
|
'OP_2DUP' : OP_2DUP,
|
||||||
|
'OP_3DUP' : OP_3DUP,
|
||||||
|
'OP_2OVER' : OP_2OVER,
|
||||||
|
'OP_2ROT' : OP_2ROT,
|
||||||
|
'OP_2SWAP' : OP_2SWAP,
|
||||||
|
'OP_IFDUP' : OP_IFDUP,
|
||||||
|
'OP_DEPTH' : OP_DEPTH,
|
||||||
|
'OP_DROP' : OP_DROP,
|
||||||
|
'OP_DUP' : OP_DUP,
|
||||||
|
'OP_NIP' : OP_NIP,
|
||||||
|
'OP_OVER' : OP_OVER,
|
||||||
|
'OP_PICK' : OP_PICK,
|
||||||
|
'OP_ROLL' : OP_ROLL,
|
||||||
|
'OP_ROT' : OP_ROT,
|
||||||
|
'OP_SWAP' : OP_SWAP,
|
||||||
|
'OP_TUCK' : OP_TUCK,
|
||||||
|
'OP_CAT' : OP_CAT,
|
||||||
|
'OP_SUBSTR' : OP_SUBSTR,
|
||||||
|
'OP_LEFT' : OP_LEFT,
|
||||||
|
'OP_RIGHT' : OP_RIGHT,
|
||||||
|
'OP_SIZE' : OP_SIZE,
|
||||||
|
'OP_INVERT' : OP_INVERT,
|
||||||
|
'OP_AND' : OP_AND,
|
||||||
|
'OP_OR' : OP_OR,
|
||||||
|
'OP_XOR' : OP_XOR,
|
||||||
|
'OP_EQUAL' : OP_EQUAL,
|
||||||
|
'OP_EQUALVERIFY' : OP_EQUALVERIFY,
|
||||||
|
'OP_RESERVED1' : OP_RESERVED1,
|
||||||
|
'OP_RESERVED2' : OP_RESERVED2,
|
||||||
|
'OP_1ADD' : OP_1ADD,
|
||||||
|
'OP_1SUB' : OP_1SUB,
|
||||||
|
'OP_2MUL' : OP_2MUL,
|
||||||
|
'OP_2DIV' : OP_2DIV,
|
||||||
|
'OP_NEGATE' : OP_NEGATE,
|
||||||
|
'OP_ABS' : OP_ABS,
|
||||||
|
'OP_NOT' : OP_NOT,
|
||||||
|
'OP_0NOTEQUAL' : OP_0NOTEQUAL,
|
||||||
|
'OP_ADD' : OP_ADD,
|
||||||
|
'OP_SUB' : OP_SUB,
|
||||||
|
'OP_MUL' : OP_MUL,
|
||||||
|
'OP_DIV' : OP_DIV,
|
||||||
|
'OP_MOD' : OP_MOD,
|
||||||
|
'OP_LSHIFT' : OP_LSHIFT,
|
||||||
|
'OP_RSHIFT' : OP_RSHIFT,
|
||||||
|
'OP_BOOLAND' : OP_BOOLAND,
|
||||||
|
'OP_BOOLOR' : OP_BOOLOR,
|
||||||
|
'OP_NUMEQUAL' : OP_NUMEQUAL,
|
||||||
|
'OP_NUMEQUALVERIFY' : OP_NUMEQUALVERIFY,
|
||||||
|
'OP_NUMNOTEQUAL' : OP_NUMNOTEQUAL,
|
||||||
|
'OP_LESSTHAN' : OP_LESSTHAN,
|
||||||
|
'OP_GREATERTHAN' : OP_GREATERTHAN,
|
||||||
|
'OP_LESSTHANOREQUAL' : OP_LESSTHANOREQUAL,
|
||||||
|
'OP_GREATERTHANOREQUAL' : OP_GREATERTHANOREQUAL,
|
||||||
|
'OP_MIN' : OP_MIN,
|
||||||
|
'OP_MAX' : OP_MAX,
|
||||||
|
'OP_WITHIN' : OP_WITHIN,
|
||||||
|
'OP_RIPEMD160' : OP_RIPEMD160,
|
||||||
|
'OP_SHA1' : OP_SHA1,
|
||||||
|
'OP_SHA256' : OP_SHA256,
|
||||||
|
'OP_HASH160' : OP_HASH160,
|
||||||
|
'OP_HASH256' : OP_HASH256,
|
||||||
|
'OP_CODESEPARATOR' : OP_CODESEPARATOR,
|
||||||
|
'OP_CHECKSIG' : OP_CHECKSIG,
|
||||||
|
'OP_CHECKSIGVERIFY' : OP_CHECKSIGVERIFY,
|
||||||
|
'OP_CHECKMULTISIG' : OP_CHECKMULTISIG,
|
||||||
|
'OP_CHECKMULTISIGVERIFY' : OP_CHECKMULTISIGVERIFY,
|
||||||
|
'OP_NOP1' : OP_NOP1,
|
||||||
|
'OP_NOP2' : OP_NOP2,
|
||||||
|
'OP_NOP3' : OP_NOP3,
|
||||||
|
'OP_NOP4' : OP_NOP4,
|
||||||
|
'OP_NOP5' : OP_NOP5,
|
||||||
|
'OP_NOP6' : OP_NOP6,
|
||||||
|
'OP_NOP7' : OP_NOP7,
|
||||||
|
'OP_NOP8' : OP_NOP8,
|
||||||
|
'OP_NOP9' : OP_NOP9,
|
||||||
|
'OP_NOP10' : OP_NOP10,
|
||||||
|
'OP_SMALLINTEGER' : OP_SMALLINTEGER,
|
||||||
|
'OP_PUBKEYS' : OP_PUBKEYS,
|
||||||
|
'OP_PUBKEYHASH' : OP_PUBKEYHASH,
|
||||||
|
'OP_PUBKEY' : OP_PUBKEY,
|
||||||
|
}
|
||||||
|
|
||||||
|
class CScriptInvalidError(Exception):
|
||||||
|
"""Base class for CScript exceptions"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CScriptTruncatedPushDataError(CScriptInvalidError):
|
||||||
|
"""Invalid pushdata due to truncation"""
|
||||||
|
def __init__(self, msg, data):
|
||||||
|
self.data = data
|
||||||
|
super(CScriptTruncatedPushDataError, self).__init__(msg)
|
||||||
|
|
||||||
|
# This is used, eg, for blockchain heights in coinbase scripts (bip34)
|
||||||
|
class CScriptNum(object):
|
||||||
|
def __init__(self, d=0):
|
||||||
|
self.value = d
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode(obj):
|
||||||
|
r = bytearray(0)
|
||||||
|
if obj.value == 0:
|
||||||
|
return bytes(r)
|
||||||
|
neg = obj.value < 0
|
||||||
|
absvalue = -obj.value if neg else obj.value
|
||||||
|
while (absvalue):
|
||||||
|
r.append(chr(absvalue & 0xff))
|
||||||
|
absvalue >>= 8
|
||||||
|
if r[-1] & 0x80:
|
||||||
|
r.append(0x80 if neg else 0)
|
||||||
|
elif neg:
|
||||||
|
r[-1] |= 0x80
|
||||||
|
return bytes(bchr(len(r)) + r)
|
||||||
|
|
||||||
|
|
||||||
|
class CScript(bytes):
|
||||||
|
"""Serialized script
|
||||||
|
|
||||||
|
A bytes subclass, so you can use this directly whenever bytes are accepted.
|
||||||
|
Note that this means that indexing does *not* work - you'll get an index by
|
||||||
|
byte rather than opcode. This format was chosen for efficiency so that the
|
||||||
|
general case would not require creating a lot of little CScriptOP objects.
|
||||||
|
|
||||||
|
iter(script) however does iterate by opcode.
|
||||||
|
"""
|
||||||
|
@classmethod
|
||||||
|
def __coerce_instance(cls, other):
|
||||||
|
# Coerce other into bytes
|
||||||
|
if isinstance(other, CScriptOp):
|
||||||
|
other = bchr(other)
|
||||||
|
elif isinstance(other, CScriptNum):
|
||||||
|
if (other.value == 0):
|
||||||
|
other = bchr(CScriptOp(OP_0))
|
||||||
|
else:
|
||||||
|
other = CScriptNum.encode(other)
|
||||||
|
elif isinstance(other, (int, long)):
|
||||||
|
if 0 <= other <= 16:
|
||||||
|
other = bytes(bchr(CScriptOp.encode_op_n(other)))
|
||||||
|
elif other == -1:
|
||||||
|
other = bytes(bchr(OP_1NEGATE))
|
||||||
|
else:
|
||||||
|
other = CScriptOp.encode_op_pushdata(bignum.bn2vch(other))
|
||||||
|
elif isinstance(other, (bytes, bytearray)):
|
||||||
|
other = CScriptOp.encode_op_pushdata(other)
|
||||||
|
return other
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
# Do the coercion outside of the try block so that errors in it are
|
||||||
|
# noticed.
|
||||||
|
other = self.__coerce_instance(other)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# bytes.__add__ always returns bytes instances unfortunately
|
||||||
|
return CScript(super(CScript, self).__add__(other))
|
||||||
|
except TypeError:
|
||||||
|
raise TypeError('Can not add a %r instance to a CScript' % other.__class__)
|
||||||
|
|
||||||
|
def join(self, iterable):
|
||||||
|
# join makes no sense for a CScript()
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __new__(cls, value=b''):
|
||||||
|
if isinstance(value, bytes) or isinstance(value, bytearray):
|
||||||
|
return super(CScript, cls).__new__(cls, value)
|
||||||
|
else:
|
||||||
|
def coerce_iterable(iterable):
|
||||||
|
for instance in iterable:
|
||||||
|
yield cls.__coerce_instance(instance)
|
||||||
|
# Annoyingly on both python2 and python3 bytes.join() always
|
||||||
|
# returns a bytes instance even when subclassed.
|
||||||
|
return super(CScript, cls).__new__(cls, b''.join(coerce_iterable(value)))
|
||||||
|
|
||||||
|
def raw_iter(self):
|
||||||
|
"""Raw iteration
|
||||||
|
|
||||||
|
Yields tuples of (opcode, data, sop_idx) so that the different possible
|
||||||
|
PUSHDATA encodings can be accurately distinguished, as well as
|
||||||
|
determining the exact opcode byte indexes. (sop_idx)
|
||||||
|
"""
|
||||||
|
i = 0
|
||||||
|
while i < len(self):
|
||||||
|
sop_idx = i
|
||||||
|
opcode = bord(self[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if opcode > OP_PUSHDATA4:
|
||||||
|
yield (opcode, None, sop_idx)
|
||||||
|
else:
|
||||||
|
datasize = None
|
||||||
|
pushdata_type = None
|
||||||
|
if opcode < OP_PUSHDATA1:
|
||||||
|
pushdata_type = 'PUSHDATA(%d)' % opcode
|
||||||
|
datasize = opcode
|
||||||
|
|
||||||
|
elif opcode == OP_PUSHDATA1:
|
||||||
|
pushdata_type = 'PUSHDATA1'
|
||||||
|
if i >= len(self):
|
||||||
|
raise CScriptInvalidError('PUSHDATA1: missing data length')
|
||||||
|
datasize = bord(self[i])
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
elif opcode == OP_PUSHDATA2:
|
||||||
|
pushdata_type = 'PUSHDATA2'
|
||||||
|
if i + 1 >= len(self):
|
||||||
|
raise CScriptInvalidError('PUSHDATA2: missing data length')
|
||||||
|
datasize = bord(self[i]) + (bord(self[i+1]) << 8)
|
||||||
|
i += 2
|
||||||
|
|
||||||
|
elif opcode == OP_PUSHDATA4:
|
||||||
|
pushdata_type = 'PUSHDATA4'
|
||||||
|
if i + 3 >= len(self):
|
||||||
|
raise CScriptInvalidError('PUSHDATA4: missing data length')
|
||||||
|
datasize = bord(self[i]) + (bord(self[i+1]) << 8) + (bord(self[i+2]) << 16) + (bord(self[i+3]) << 24)
|
||||||
|
i += 4
|
||||||
|
|
||||||
|
else:
|
||||||
|
assert False # shouldn't happen
|
||||||
|
|
||||||
|
|
||||||
|
data = bytes(self[i:i+datasize])
|
||||||
|
|
||||||
|
# Check for truncation
|
||||||
|
if len(data) < datasize:
|
||||||
|
raise CScriptTruncatedPushDataError('%s: truncated data' % pushdata_type, data)
|
||||||
|
|
||||||
|
i += datasize
|
||||||
|
|
||||||
|
yield (opcode, data, sop_idx)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
"""'Cooked' iteration
|
||||||
|
|
||||||
|
Returns either a CScriptOP instance, an integer, or bytes, as
|
||||||
|
appropriate.
|
||||||
|
|
||||||
|
See raw_iter() if you need to distinguish the different possible
|
||||||
|
PUSHDATA encodings.
|
||||||
|
"""
|
||||||
|
for (opcode, data, sop_idx) in self.raw_iter():
|
||||||
|
if data is not None:
|
||||||
|
yield data
|
||||||
|
else:
|
||||||
|
opcode = CScriptOp(opcode)
|
||||||
|
|
||||||
|
if opcode.is_small_int():
|
||||||
|
yield opcode.decode_op_n()
|
||||||
|
else:
|
||||||
|
yield CScriptOp(opcode)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
# For Python3 compatibility add b before strings so testcases don't
|
||||||
|
# need to change
|
||||||
|
def _repr(o):
|
||||||
|
if isinstance(o, bytes):
|
||||||
|
return "x('%s')" % binascii.hexlify(o).decode('utf8')
|
||||||
|
else:
|
||||||
|
return repr(o)
|
||||||
|
|
||||||
|
ops = []
|
||||||
|
i = iter(self)
|
||||||
|
while True:
|
||||||
|
op = None
|
||||||
|
try:
|
||||||
|
op = _repr(next(i))
|
||||||
|
except CScriptTruncatedPushDataError as err:
|
||||||
|
op = '%s...<ERROR: %s>' % (_repr(err.data), err)
|
||||||
|
break
|
||||||
|
except CScriptInvalidError as err:
|
||||||
|
op = '<ERROR: %s>' % err
|
||||||
|
break
|
||||||
|
except StopIteration:
|
||||||
|
break
|
||||||
|
finally:
|
||||||
|
if op is not None:
|
||||||
|
ops.append(op)
|
||||||
|
|
||||||
|
return "CScript([%s])" % ', '.join(ops)
|
||||||
|
|
||||||
|
def GetSigOpCount(self, fAccurate):
|
||||||
|
"""Get the SigOp count.
|
||||||
|
|
||||||
|
fAccurate - Accurately count CHECKMULTISIG, see BIP16 for details.
|
||||||
|
|
||||||
|
Note that this is consensus-critical.
|
||||||
|
"""
|
||||||
|
n = 0
|
||||||
|
lastOpcode = OP_INVALIDOPCODE
|
||||||
|
for (opcode, data, sop_idx) in self.raw_iter():
|
||||||
|
if opcode in (OP_CHECKSIG, OP_CHECKSIGVERIFY):
|
||||||
|
n += 1
|
||||||
|
elif opcode in (OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY):
|
||||||
|
if fAccurate and (OP_1 <= lastOpcode <= OP_16):
|
||||||
|
n += opcode.decode_op_n()
|
||||||
|
else:
|
||||||
|
n += 20
|
||||||
|
lastOpcode = opcode
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
SIGHASH_ALL = 1
|
||||||
|
SIGHASH_NONE = 2
|
||||||
|
SIGHASH_SINGLE = 3
|
||||||
|
SIGHASH_ANYONECANPAY = 0x80
|
||||||
|
|
||||||
|
def FindAndDelete(script, sig):
|
||||||
|
"""Consensus critical, see FindAndDelete() in Satoshi codebase"""
|
||||||
|
r = b''
|
||||||
|
last_sop_idx = sop_idx = 0
|
||||||
|
skip = True
|
||||||
|
for (opcode, data, sop_idx) in script.raw_iter():
|
||||||
|
if not skip:
|
||||||
|
r += script[last_sop_idx:sop_idx]
|
||||||
|
last_sop_idx = sop_idx
|
||||||
|
if script[sop_idx:sop_idx + len(sig)] == sig:
|
||||||
|
skip = True
|
||||||
|
else:
|
||||||
|
skip = False
|
||||||
|
if not skip:
|
||||||
|
r += script[last_sop_idx:]
|
||||||
|
return CScript(r)
|
||||||
|
|
||||||
|
|
||||||
|
def SignatureHash(script, txTo, inIdx, hashtype):
|
||||||
|
"""Consensus-correct SignatureHash
|
||||||
|
|
||||||
|
Returns (hash, err) to precisely match the consensus-critical behavior of
|
||||||
|
the SIGHASH_SINGLE bug. (inIdx is *not* checked for validity)
|
||||||
|
"""
|
||||||
|
HASH_ONE = b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||||
|
|
||||||
|
if inIdx >= len(txTo.vin):
|
||||||
|
return (HASH_ONE, "inIdx %d out of range (%d)" % (inIdx, len(txTo.vin)))
|
||||||
|
txtmp = CTransaction(txTo)
|
||||||
|
|
||||||
|
for txin in txtmp.vin:
|
||||||
|
txin.scriptSig = b''
|
||||||
|
txtmp.vin[inIdx].scriptSig = FindAndDelete(script, CScript([OP_CODESEPARATOR]))
|
||||||
|
|
||||||
|
if (hashtype & 0x1f) == SIGHASH_NONE:
|
||||||
|
txtmp.vout = []
|
||||||
|
|
||||||
|
for i in range(len(txtmp.vin)):
|
||||||
|
if i != inIdx:
|
||||||
|
txtmp.vin[i].nSequence = 0
|
||||||
|
|
||||||
|
elif (hashtype & 0x1f) == SIGHASH_SINGLE:
|
||||||
|
outIdx = inIdx
|
||||||
|
if outIdx >= len(txtmp.vout):
|
||||||
|
return (HASH_ONE, "outIdx %d out of range (%d)" % (outIdx, len(txtmp.vout)))
|
||||||
|
|
||||||
|
tmp = txtmp.vout[outIdx]
|
||||||
|
txtmp.vout = []
|
||||||
|
for i in range(outIdx):
|
||||||
|
txtmp.vout.append(CTxOut())
|
||||||
|
txtmp.vout.append(tmp)
|
||||||
|
|
||||||
|
for i in range(len(txtmp.vin)):
|
||||||
|
if i != inIdx:
|
||||||
|
txtmp.vin[i].nSequence = 0
|
||||||
|
|
||||||
|
if hashtype & SIGHASH_ANYONECANPAY:
|
||||||
|
tmp = txtmp.vin[inIdx]
|
||||||
|
txtmp.vin = []
|
||||||
|
txtmp.vin.append(tmp)
|
||||||
|
|
||||||
|
s = txtmp.serialize()
|
||||||
|
s += struct.pack(b"<I", hashtype)
|
||||||
|
|
||||||
|
hash = hash256(s)
|
||||||
|
|
||||||
|
return (hash, None)
|
253
qa/rpc-tests/script_test.py
Executable file
253
qa/rpc-tests/script_test.py
Executable file
|
@ -0,0 +1,253 @@
|
||||||
|
#!/usr/bin/env python2
|
||||||
|
#
|
||||||
|
# Distributed under the MIT/X11 software license, see the accompanying
|
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
#
|
||||||
|
|
||||||
|
'''
|
||||||
|
Test notes:
|
||||||
|
This test uses the script_valid and script_invalid tests from the unittest
|
||||||
|
framework to do end-to-end testing where we compare that two nodes agree on
|
||||||
|
whether blocks containing a given test script are valid.
|
||||||
|
|
||||||
|
We generally ignore the script flags associated with each test (since we lack
|
||||||
|
the precision to test each script using those flags in this framework), but
|
||||||
|
for tests with SCRIPT_VERIFY_P2SH, we can use a block time after the BIP16
|
||||||
|
switchover date to try to test with that flag enabled (and for tests without
|
||||||
|
that flag, we use a block time before the switchover date).
|
||||||
|
|
||||||
|
NOTE: This test is very slow and may take more than 40 minutes to run.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from test_framework import ComparisonTestFramework
|
||||||
|
from util import *
|
||||||
|
from comptool import TestInstance, TestManager
|
||||||
|
from mininode import *
|
||||||
|
from blocktools import *
|
||||||
|
from script import *
|
||||||
|
import logging
|
||||||
|
import copy
|
||||||
|
import json
|
||||||
|
|
||||||
|
script_valid_file = "../../src/test/data/script_valid.json"
|
||||||
|
script_invalid_file = "../../src/test/data/script_invalid.json"
|
||||||
|
|
||||||
|
# Pass in a set of json files to open.
|
||||||
|
class ScriptTestFile(object):
|
||||||
|
|
||||||
|
def __init__(self, files):
|
||||||
|
self.files = files
|
||||||
|
self.index = -1
|
||||||
|
self.data = []
|
||||||
|
|
||||||
|
def load_files(self):
|
||||||
|
for f in self.files:
|
||||||
|
self.data.extend(json.loads(open(f).read()))
|
||||||
|
|
||||||
|
# Skip over records that are not long enough to be tests
|
||||||
|
def get_records(self):
|
||||||
|
while (self.index < len(self.data)):
|
||||||
|
if len(self.data[self.index]) >= 3:
|
||||||
|
yield self.data[self.index]
|
||||||
|
self.index += 1
|
||||||
|
|
||||||
|
|
||||||
|
# Helper for parsing the flags specified in the .json files
|
||||||
|
SCRIPT_VERIFY_NONE = 0
|
||||||
|
SCRIPT_VERIFY_P2SH = 1
|
||||||
|
SCRIPT_VERIFY_STRICTENC = 1 << 1
|
||||||
|
SCRIPT_VERIFY_DERSIG = 1 << 2
|
||||||
|
SCRIPT_VERIFY_LOW_S = 1 << 3
|
||||||
|
SCRIPT_VERIFY_NULLDUMMY = 1 << 4
|
||||||
|
SCRIPT_VERIFY_SIGPUSHONLY = 1 << 5
|
||||||
|
SCRIPT_VERIFY_MINIMALDATA = 1 << 6
|
||||||
|
SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS = 1 << 7
|
||||||
|
SCRIPT_VERIFY_CLEANSTACK = 1 << 8
|
||||||
|
|
||||||
|
flag_map = {
|
||||||
|
"": SCRIPT_VERIFY_NONE,
|
||||||
|
"NONE": SCRIPT_VERIFY_NONE,
|
||||||
|
"P2SH": SCRIPT_VERIFY_P2SH,
|
||||||
|
"STRICTENC": SCRIPT_VERIFY_STRICTENC,
|
||||||
|
"DERSIG": SCRIPT_VERIFY_DERSIG,
|
||||||
|
"LOW_S": SCRIPT_VERIFY_LOW_S,
|
||||||
|
"NULLDUMMY": SCRIPT_VERIFY_NULLDUMMY,
|
||||||
|
"SIGPUSHONLY": SCRIPT_VERIFY_SIGPUSHONLY,
|
||||||
|
"MINIMALDATA": SCRIPT_VERIFY_MINIMALDATA,
|
||||||
|
"DISCOURAGE_UPGRADABLE_NOPS": SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS,
|
||||||
|
"CLEANSTACK": SCRIPT_VERIFY_CLEANSTACK,
|
||||||
|
}
|
||||||
|
|
||||||
|
def ParseScriptFlags(flag_string):
|
||||||
|
flags = 0
|
||||||
|
for x in flag_string.split(","):
|
||||||
|
if x in flag_map:
|
||||||
|
flags |= flag_map[x]
|
||||||
|
else:
|
||||||
|
print "Error: unrecognized script flag: ", x
|
||||||
|
return flags
|
||||||
|
|
||||||
|
'''
|
||||||
|
Given a string that is a scriptsig or scriptpubkey from the .json files above,
|
||||||
|
convert it to a CScript()
|
||||||
|
'''
|
||||||
|
# Replicates behavior from core_read.cpp
|
||||||
|
def ParseScript(json_script):
|
||||||
|
script = json_script.split(" ")
|
||||||
|
parsed_script = CScript()
|
||||||
|
for x in script:
|
||||||
|
if len(x) == 0:
|
||||||
|
# Empty string, ignore.
|
||||||
|
pass
|
||||||
|
elif x.isdigit() or (len(x) >= 1 and x[0] == "-" and x[1:].isdigit()):
|
||||||
|
# Number
|
||||||
|
n = int(x, 0)
|
||||||
|
if (n == -1) or (n >= 1 and n <= 16):
|
||||||
|
parsed_script = CScript(bytes(parsed_script) + bytes(CScript([n])))
|
||||||
|
else:
|
||||||
|
parsed_script += CScriptNum(int(x, 0))
|
||||||
|
elif x.startswith("0x"):
|
||||||
|
# Raw hex data, inserted NOT pushed onto stack:
|
||||||
|
for i in xrange(2, len(x), 2):
|
||||||
|
parsed_script = CScript(bytes(parsed_script) + bytes(chr(int(x[i:i+2],16))))
|
||||||
|
elif x.startswith("'") and x.endswith("'") and len(x) >= 2:
|
||||||
|
# Single-quoted string, pushed as data.
|
||||||
|
parsed_script += CScript([x[1:-1]])
|
||||||
|
else:
|
||||||
|
# opcode, e.g. OP_ADD or ADD:
|
||||||
|
tryopname = "OP_" + x
|
||||||
|
if tryopname in OPCODES_BY_NAME:
|
||||||
|
parsed_script += CScriptOp(OPCODES_BY_NAME["OP_" + x])
|
||||||
|
else:
|
||||||
|
print "ParseScript: error parsing '%s'" % x
|
||||||
|
return ""
|
||||||
|
return parsed_script
|
||||||
|
|
||||||
|
class TestBuilder(object):
|
||||||
|
def create_credit_tx(self, scriptPubKey):
|
||||||
|
# self.tx1 is a coinbase transaction, modeled after the one created by script_tests.cpp
|
||||||
|
# This allows us to reuse signatures created in the unit test framework.
|
||||||
|
self.tx1 = create_coinbase() # this has a bip34 scriptsig,
|
||||||
|
self.tx1.vin[0].scriptSig = CScript([0, 0]) # but this matches the unit tests
|
||||||
|
self.tx1.vout[0].nValue = 0
|
||||||
|
self.tx1.vout[0].scriptPubKey = scriptPubKey
|
||||||
|
self.tx1.rehash()
|
||||||
|
def create_spend_tx(self, scriptSig):
|
||||||
|
self.tx2 = create_transaction(self.tx1, 0, CScript(), 0)
|
||||||
|
self.tx2.vin[0].scriptSig = scriptSig
|
||||||
|
self.tx2.vout[0].scriptPubKey = CScript()
|
||||||
|
self.tx2.rehash()
|
||||||
|
def rehash(self):
|
||||||
|
self.tx1.rehash()
|
||||||
|
self.tx2.rehash()
|
||||||
|
|
||||||
|
# This test uses the (default) two nodes provided by ComparisonTestFramework,
|
||||||
|
# specified on the command line with --testbinary and --refbinary.
|
||||||
|
# See comptool.py
|
||||||
|
class ScriptTest(ComparisonTestFramework):
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
# Set up the comparison tool TestManager
|
||||||
|
test = TestManager(self, self.options.tmpdir)
|
||||||
|
test.add_all_connections(self.nodes)
|
||||||
|
|
||||||
|
# Load scripts
|
||||||
|
self.scripts = ScriptTestFile([script_valid_file, script_invalid_file])
|
||||||
|
self.scripts.load_files()
|
||||||
|
|
||||||
|
# Some variables we re-use between test instances (to build blocks)
|
||||||
|
self.tip = None
|
||||||
|
self.block_time = None
|
||||||
|
|
||||||
|
NetworkThread().start() # Start up network handling in another thread
|
||||||
|
test.run()
|
||||||
|
|
||||||
|
def generate_test_instance(self, pubkeystring, scriptsigstring):
|
||||||
|
scriptpubkey = ParseScript(pubkeystring)
|
||||||
|
scriptsig = ParseScript(scriptsigstring)
|
||||||
|
|
||||||
|
test = TestInstance(sync_every_block=False)
|
||||||
|
test_build = TestBuilder()
|
||||||
|
test_build.create_credit_tx(scriptpubkey)
|
||||||
|
test_build.create_spend_tx(scriptsig)
|
||||||
|
test_build.rehash()
|
||||||
|
|
||||||
|
block = create_block(self.tip, test_build.tx1, self.block_time)
|
||||||
|
self.block_time += 1
|
||||||
|
block.solve()
|
||||||
|
self.tip = block.sha256
|
||||||
|
test.blocks_and_transactions = [[block, True]]
|
||||||
|
|
||||||
|
for i in xrange(100):
|
||||||
|
block = create_block(self.tip, create_coinbase(), self.block_time)
|
||||||
|
self.block_time += 1
|
||||||
|
block.solve()
|
||||||
|
self.tip = block.sha256
|
||||||
|
test.blocks_and_transactions.append([block, True])
|
||||||
|
|
||||||
|
block = create_block(self.tip, create_coinbase(), self.block_time)
|
||||||
|
self.block_time += 1
|
||||||
|
block.vtx.append(test_build.tx2)
|
||||||
|
block.hashMerkleRoot = block.calc_merkle_root()
|
||||||
|
block.rehash()
|
||||||
|
block.solve()
|
||||||
|
test.blocks_and_transactions.append([block, None])
|
||||||
|
return test
|
||||||
|
|
||||||
|
# This generates the tests for TestManager.
|
||||||
|
def get_tests(self):
|
||||||
|
self.tip = int ("0x" + self.nodes[0].getbestblockhash() + "L", 0)
|
||||||
|
self.block_time = 1333230000 # before the BIP16 switchover
|
||||||
|
|
||||||
|
'''
|
||||||
|
Create a new block with an anyone-can-spend coinbase
|
||||||
|
'''
|
||||||
|
block = create_block(self.tip, create_coinbase(), self.block_time)
|
||||||
|
self.block_time += 1
|
||||||
|
block.solve()
|
||||||
|
self.tip = block.sha256
|
||||||
|
yield TestInstance(objects=[[block, True]])
|
||||||
|
|
||||||
|
'''
|
||||||
|
Build out to 100 blocks total, maturing the coinbase.
|
||||||
|
'''
|
||||||
|
test = TestInstance(objects=[], sync_every_block=False, sync_every_tx=False)
|
||||||
|
for i in xrange(100):
|
||||||
|
b = create_block(self.tip, create_coinbase(), self.block_time)
|
||||||
|
b.solve()
|
||||||
|
test.blocks_and_transactions.append([b, True])
|
||||||
|
self.tip = b.sha256
|
||||||
|
self.block_time += 1
|
||||||
|
yield test
|
||||||
|
|
||||||
|
''' Iterate through script tests. '''
|
||||||
|
counter = 0
|
||||||
|
for script_test in self.scripts.get_records():
|
||||||
|
''' Reset the blockchain to genesis block + 100 blocks. '''
|
||||||
|
if self.nodes[0].getblockcount() > 101:
|
||||||
|
self.nodes[0].invalidateblock(self.nodes[0].getblockhash(102))
|
||||||
|
self.nodes[1].invalidateblock(self.nodes[1].getblockhash(102))
|
||||||
|
|
||||||
|
self.tip = int ("0x" + self.nodes[0].getbestblockhash() + "L", 0)
|
||||||
|
|
||||||
|
[scriptsig, scriptpubkey, flags] = script_test[0:3]
|
||||||
|
flags = ParseScriptFlags(flags)
|
||||||
|
|
||||||
|
# We can use block time to determine whether the nodes should be
|
||||||
|
# enforcing BIP16.
|
||||||
|
#
|
||||||
|
# We intentionally let the block time grow by 1 each time.
|
||||||
|
# This forces the block hashes to differ between tests, so that
|
||||||
|
# a call to invalidateblock doesn't interfere with a later test.
|
||||||
|
if (flags & SCRIPT_VERIFY_P2SH):
|
||||||
|
self.block_time = 1333238400 + counter # Advance to enforcing BIP16
|
||||||
|
else:
|
||||||
|
self.block_time = 1333230000 + counter # Before the BIP16 switchover
|
||||||
|
|
||||||
|
print "Script test: [%s]" % script_test
|
||||||
|
|
||||||
|
yield self.generate_test_instance(scriptpubkey, scriptsig)
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ScriptTest().main()
|
|
@ -147,3 +147,34 @@ class BitcoinTestFramework(object):
|
||||||
else:
|
else:
|
||||||
print("Failed")
|
print("Failed")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
# Test framework for doing p2p comparison testing, which sets up some bitcoind
|
||||||
|
# binaries:
|
||||||
|
# 1 binary: test binary
|
||||||
|
# 2 binaries: 1 test binary, 1 ref binary
|
||||||
|
# n>2 binaries: 1 test binary, n-1 ref binaries
|
||||||
|
|
||||||
|
class ComparisonTestFramework(BitcoinTestFramework):
|
||||||
|
|
||||||
|
# Can override the num_nodes variable to indicate how many nodes to run.
|
||||||
|
def __init__(self):
|
||||||
|
self.num_nodes = 2
|
||||||
|
|
||||||
|
def add_options(self, parser):
|
||||||
|
parser.add_option("--testbinary", dest="testbinary",
|
||||||
|
default=os.getenv("BITCOIND", "bitcoind"),
|
||||||
|
help="bitcoind binary to test")
|
||||||
|
parser.add_option("--refbinary", dest="refbinary",
|
||||||
|
default=os.getenv("BITCOIND", "bitcoind"),
|
||||||
|
help="bitcoind binary to use for reference nodes (if any)")
|
||||||
|
|
||||||
|
def setup_chain(self):
|
||||||
|
print "Initializing test directory "+self.options.tmpdir
|
||||||
|
initialize_chain_clean(self.options.tmpdir, self.num_nodes)
|
||||||
|
|
||||||
|
def setup_network(self):
|
||||||
|
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir,
|
||||||
|
extra_args=[['-debug', '-whitelist=127.0.0.1']] * self.num_nodes,
|
||||||
|
binary=[self.options.testbinary] +
|
||||||
|
[self.options.refbinary]*(self.num_nodes-1))
|
||||||
|
|
|
@ -88,8 +88,12 @@ def initialize_chain(test_dir):
|
||||||
if i > 0:
|
if i > 0:
|
||||||
args.append("-connect=127.0.0.1:"+str(p2p_port(0)))
|
args.append("-connect=127.0.0.1:"+str(p2p_port(0)))
|
||||||
bitcoind_processes[i] = subprocess.Popen(args)
|
bitcoind_processes[i] = subprocess.Popen(args)
|
||||||
|
if os.getenv("PYTHON_DEBUG", ""):
|
||||||
|
print "initialize_chain: bitcoind started, calling bitcoin-cli -rpcwait getblockcount"
|
||||||
subprocess.check_call([ os.getenv("BITCOINCLI", "bitcoin-cli"), "-datadir="+datadir,
|
subprocess.check_call([ os.getenv("BITCOINCLI", "bitcoin-cli"), "-datadir="+datadir,
|
||||||
"-rpcwait", "getblockcount"], stdout=devnull)
|
"-rpcwait", "getblockcount"], stdout=devnull)
|
||||||
|
if os.getenv("PYTHON_DEBUG", ""):
|
||||||
|
print "initialize_chain: bitcoin-cli -rpcwait getblockcount completed"
|
||||||
devnull.close()
|
devnull.close()
|
||||||
rpcs = []
|
rpcs = []
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
|
@ -158,18 +162,24 @@ def _rpchost_to_args(rpchost):
|
||||||
rv += ['-rpcport=' + rpcport]
|
rv += ['-rpcport=' + rpcport]
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None):
|
def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None):
|
||||||
"""
|
"""
|
||||||
Start a bitcoind and return RPC connection to it
|
Start a bitcoind and return RPC connection to it
|
||||||
"""
|
"""
|
||||||
datadir = os.path.join(dirname, "node"+str(i))
|
datadir = os.path.join(dirname, "node"+str(i))
|
||||||
args = [ os.getenv("BITCOIND", "bitcoind"), "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ]
|
if binary is None:
|
||||||
|
binary = os.getenv("BITCOIND", "bitcoind")
|
||||||
|
args = [ binary, "-datadir="+datadir, "-keypool=1", "-discover=0", "-rest" ]
|
||||||
if extra_args is not None: args.extend(extra_args)
|
if extra_args is not None: args.extend(extra_args)
|
||||||
bitcoind_processes[i] = subprocess.Popen(args)
|
bitcoind_processes[i] = subprocess.Popen(args)
|
||||||
devnull = open("/dev/null", "w+")
|
devnull = open("/dev/null", "w+")
|
||||||
|
if os.getenv("PYTHON_DEBUG", ""):
|
||||||
|
print "start_node: bitcoind started, calling bitcoin-cli -rpcwait getblockcount"
|
||||||
subprocess.check_call([ os.getenv("BITCOINCLI", "bitcoin-cli"), "-datadir="+datadir] +
|
subprocess.check_call([ os.getenv("BITCOINCLI", "bitcoin-cli"), "-datadir="+datadir] +
|
||||||
_rpchost_to_args(rpchost) +
|
_rpchost_to_args(rpchost) +
|
||||||
["-rpcwait", "getblockcount"], stdout=devnull)
|
["-rpcwait", "getblockcount"], stdout=devnull)
|
||||||
|
if os.getenv("PYTHON_DEBUG", ""):
|
||||||
|
print "start_node: calling bitcoin-cli -rpcwait getblockcount returned"
|
||||||
devnull.close()
|
devnull.close()
|
||||||
url = "http://rt:rt@%s:%d" % (rpchost or '127.0.0.1', rpc_port(i))
|
url = "http://rt:rt@%s:%d" % (rpchost or '127.0.0.1', rpc_port(i))
|
||||||
if timewait is not None:
|
if timewait is not None:
|
||||||
|
@ -179,12 +189,13 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None):
|
||||||
proxy.url = url # store URL on proxy for info
|
proxy.url = url # store URL on proxy for info
|
||||||
return proxy
|
return proxy
|
||||||
|
|
||||||
def start_nodes(num_nodes, dirname, extra_args=None, rpchost=None):
|
def start_nodes(num_nodes, dirname, extra_args=None, rpchost=None, binary=None):
|
||||||
"""
|
"""
|
||||||
Start multiple bitcoinds, return RPC connections to them
|
Start multiple bitcoinds, return RPC connections to them
|
||||||
"""
|
"""
|
||||||
if extra_args is None: extra_args = [ None for i in range(num_nodes) ]
|
if extra_args is None: extra_args = [ None for i in range(num_nodes) ]
|
||||||
return [ start_node(i, dirname, extra_args[i], rpchost) for i in range(num_nodes) ]
|
if binary is None: binary = [ None for i in range(num_nodes) ]
|
||||||
|
return [ start_node(i, dirname, extra_args[i], rpchost, binary=binary[i]) for i in range(num_nodes) ]
|
||||||
|
|
||||||
def log_filename(dirname, n_node, logname):
|
def log_filename(dirname, n_node, logname):
|
||||||
return os.path.join(dirname, "node"+str(n_node), "regtest", logname)
|
return os.path.join(dirname, "node"+str(n_node), "regtest", logname)
|
||||||
|
|
Loading…
Reference in a new issue