cpp test suite for cryptoconditions integration
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
# Integration tests for Crypto-Conditions
|
||||
|
||||
These tests are for the [Crypto-Conditions](https://github.com/rfcs/crypto-conditions) functionality in Komodod, using [Hoek](https://github.com/libscott/hoek) as a tool to create and sign transactions.
|
||||
|
||||
## How to run the tests
|
||||
|
||||
1. Install hoek: https://github.com/libscott/hoek
|
||||
1. Allow peer-less block creation: set "fMiningRequiresPeers = false" in src/chainparams.cpp, then build komodod.
|
||||
1. Start komodod: `src/komodod -ac_name=CCTEST -ac_supply=21000000 -gen -pubkey=0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47 -ac_cc=1`
|
||||
1. Set environment variable pointing to Komodo conf: `export KOMODO_CONF_PATH=~/.komodo/CCTEST/CCTEST.conf`
|
||||
1. Run tests: `python test_integration.py` (you can also use a test runner such as `nose`).
|
||||
@@ -1,223 +0,0 @@
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import binascii
|
||||
import struct
|
||||
from testsupport import *
|
||||
|
||||
|
||||
SCRIPT_FALSE = 'Script evaluated without error but finished with a false/empty top stack element'
|
||||
|
||||
|
||||
@fanout_input(0)
|
||||
def test_basic_spend(inp):
|
||||
spend = {'inputs': [inp], "outputs": [nospend]}
|
||||
spend_txid = submit(sign(spend))
|
||||
assert rpc.getrawtransaction(spend_txid)
|
||||
|
||||
|
||||
@fanout_input(1)
|
||||
def test_fulfillment_wrong_signature(inp):
|
||||
# Set other pubkey and sign
|
||||
inp['script']['fulfillment']['publicKey'] = bob_pk
|
||||
spend = {'inputs': [inp], 'outputs': [nospend]}
|
||||
signed = sign(spend)
|
||||
|
||||
# Set the correct pubkey, signature is bob's
|
||||
signed['inputs'][0]['script']['fulfillment']['publicKey'] = alice_pk
|
||||
|
||||
try:
|
||||
assert not submit(signed), 'should raise an error'
|
||||
except RPCError as e:
|
||||
assert SCRIPT_FALSE in str(e), str(e)
|
||||
|
||||
|
||||
@fanout_input(2)
|
||||
def test_fulfillment_wrong_pubkey(inp):
|
||||
spend = {'inputs': [inp], 'outputs': [nospend]}
|
||||
signed = sign(spend)
|
||||
|
||||
# Set the wrong pubkey, signature is correct
|
||||
signed['inputs'][0]['script']['fulfillment']['publicKey'] = bob_pk
|
||||
|
||||
try:
|
||||
assert not submit(signed), 'should raise an error'
|
||||
except RPCError as e:
|
||||
assert SCRIPT_FALSE in str(e), str(e)
|
||||
|
||||
|
||||
@fanout_input(3)
|
||||
def test_invalid_fulfillment_binary(inp):
|
||||
# Create a valid script with an invalid fulfillment payload
|
||||
inp['script'] = binascii.hexlify(b"\007invalid").decode('utf-8')
|
||||
spend = {'inputs': [inp], 'outputs': [nospend]}
|
||||
|
||||
try:
|
||||
assert not submit(spend), 'should raise an error'
|
||||
except RPCError as e:
|
||||
assert 'Crypto-Condition payload is invalid' in str(e), str(e)
|
||||
|
||||
|
||||
@fanout_input(4)
|
||||
def test_invalid_condition(inp):
|
||||
# Create a valid output script with an invalid cryptocondition binary
|
||||
outputscript = to_hex(b"\007invalid\xcc")
|
||||
spend = {'inputs': [inp], 'outputs': [{'amount': 1000, 'script': outputscript}]}
|
||||
spend_txid = submit(sign(spend))
|
||||
|
||||
spend1 = {
|
||||
'inputs': [{'txid': spend_txid, 'idx': 0, 'script': {'fulfillment': cond_alice}}],
|
||||
'outputs': [nospend],
|
||||
}
|
||||
|
||||
try:
|
||||
assert not submit(sign(spend1)), 'should raise an error'
|
||||
except RPCError as e:
|
||||
assert SCRIPT_FALSE in str(e), str(e)
|
||||
|
||||
|
||||
@fanout_input(5)
|
||||
def test_oversize_fulfillment(inp):
|
||||
# Create oversize fulfillment script where the total length is <2000
|
||||
binscript = b'\x4d%s%s' % (struct.pack('h', 2000), b'a' * 2000)
|
||||
inp['script'] = to_hex(binscript)
|
||||
spend = {'inputs': [inp], 'outputs': [nospend]}
|
||||
|
||||
try:
|
||||
assert not submit(spend), 'should raise an error'
|
||||
except RPCError as e:
|
||||
assert 'scriptsig-size' in str(e), str(e)
|
||||
|
||||
|
||||
@fanout_input(6)
|
||||
def test_aux_basic(inp):
|
||||
aux_cond = {
|
||||
'type': 'aux-sha-256',
|
||||
'method': 'equals',
|
||||
'conditionAux': 'LTE',
|
||||
'fulfillmentAux': 'LTE'
|
||||
}
|
||||
|
||||
# Setup some aux outputs
|
||||
spend0 = {
|
||||
'inputs': [inp],
|
||||
'outputs': [
|
||||
{'amount': 500, 'script': {'condition': aux_cond}},
|
||||
{'amount': 500, 'script': {'condition': aux_cond}}
|
||||
]
|
||||
}
|
||||
spend0_txid = submit(sign(spend0))
|
||||
assert rpc.getrawtransaction(spend0_txid)
|
||||
|
||||
# Test a good fulfillment
|
||||
spend1 = {
|
||||
'inputs': [{'txid': spend0_txid, 'idx': 0, 'script': {'fulfillment': aux_cond}}],
|
||||
'outputs': [{'amount': 500, 'script': {'condition': aux_cond}}]
|
||||
}
|
||||
spend1_txid = submit(sign(spend1))
|
||||
assert rpc.getrawtransaction(spend1_txid)
|
||||
|
||||
# Test a bad fulfillment
|
||||
aux_cond['fulfillmentAux'] = 'WYW'
|
||||
spend2 = {
|
||||
'inputs': [{'txid': spend0_txid, 'idx': 1, 'script': {'fulfillment': aux_cond}}],
|
||||
'outputs': [{'amount': 500, 'script': {'condition': aux_cond}}]
|
||||
}
|
||||
try:
|
||||
assert not submit(sign(spend2)), 'should raise an error'
|
||||
except RPCError as e:
|
||||
assert SCRIPT_FALSE in str(e), str(e)
|
||||
|
||||
|
||||
@fanout_input(7)
|
||||
def test_aux_complex(inp):
|
||||
aux_cond = {
|
||||
'type': 'aux-sha-256',
|
||||
'method': 'inputIsReturn',
|
||||
'conditionAux': '',
|
||||
'fulfillmentAux': 'AQ' # \1 (tx.vout[1])
|
||||
}
|
||||
|
||||
# Setup some aux outputs
|
||||
spend0 = {
|
||||
'inputs': [inp],
|
||||
'outputs': [
|
||||
{'amount': 500, 'script': {'condition': aux_cond}},
|
||||
{'amount': 500, 'script': {'condition': aux_cond}}
|
||||
]
|
||||
}
|
||||
spend0_txid = submit(sign(spend0))
|
||||
assert rpc.getrawtransaction(spend0_txid)
|
||||
|
||||
# Test a good fulfillment
|
||||
spend1 = {
|
||||
'inputs': [{'txid': spend0_txid, 'idx': 0, 'script': {'fulfillment': aux_cond}}],
|
||||
'outputs': [
|
||||
{'amount': 250, 'script': {'condition': aux_cond}},
|
||||
{'amount': 250, 'script': "6A0B68656C6C6F207468657265"} # OP_RETURN somedata
|
||||
]
|
||||
}
|
||||
spend1_txid = submit(sign(spend1))
|
||||
assert rpc.getrawtransaction(spend1_txid)
|
||||
|
||||
# Test a bad fulfillment
|
||||
spend2 = {
|
||||
'inputs': [{'txid': spend0_txid, 'idx': 1, 'script': {'fulfillment': aux_cond}}],
|
||||
'outputs': [
|
||||
{'amount': 500, 'script': "6A0B68656C6C6F207468657265"} # OP_RETURN somedata
|
||||
]
|
||||
}
|
||||
try:
|
||||
assert not submit(sign(spend2)), 'should raise an error'
|
||||
except RPCError as e:
|
||||
assert SCRIPT_FALSE in str(e), str(e)
|
||||
|
||||
|
||||
@fanout_input(8)
|
||||
def test_secp256k1_condition(inp):
|
||||
ec_cond = {
|
||||
'type': 'secp256k1-sha-256',
|
||||
'publicKey': notary_pk
|
||||
}
|
||||
|
||||
# Create some secp256k1 outputs
|
||||
spend0 = {
|
||||
'inputs': [inp],
|
||||
'outputs': [
|
||||
{'amount': 500, 'script': {'condition': ec_cond}},
|
||||
{'amount': 500, 'script': {'condition': ec_cond}}
|
||||
]
|
||||
}
|
||||
spend0_txid = submit(sign(spend0))
|
||||
assert rpc.getrawtransaction(spend0_txid)
|
||||
|
||||
# Test a good fulfillment
|
||||
spend1 = {
|
||||
'inputs': [{'txid': spend0_txid, 'idx': 0, 'script': {'fulfillment': ec_cond}}],
|
||||
'outputs': [{'amount': 500, 'script': {'condition': ec_cond}}]
|
||||
}
|
||||
spend1_txid = submit(sign(spend1))
|
||||
assert rpc.getrawtransaction(spend1_txid)
|
||||
|
||||
# Test a bad fulfillment
|
||||
spend2 = {
|
||||
'inputs': [{'txid': spend0_txid, 'idx': 1, 'script': {'fulfillment': ec_cond}}],
|
||||
'outputs': [{'amount': 500, 'script': {'condition': ec_cond}}]
|
||||
}
|
||||
signed = sign(spend2)
|
||||
signed['inputs'][0]['script']['fulfillment']['publicKey'] = \
|
||||
'0275cef12fc5c49be64f5aab3d1fbba08cd7b0d02908b5112fbd8504218d14bc7d'
|
||||
try:
|
||||
assert not submit(signed), 'should raise an error'
|
||||
except RPCError as e:
|
||||
assert SCRIPT_FALSE in str(e), str(e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
for name, f in globals().items():
|
||||
if name.startswith('test_'):
|
||||
logging.info("Running test: %s" % name)
|
||||
f()
|
||||
logging.info("Test OK: %s" % name)
|
||||
@@ -1,151 +0,0 @@
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import copy
|
||||
import base64
|
||||
import logging
|
||||
import functools
|
||||
import subprocess
|
||||
|
||||
|
||||
class RPCError(IOError):
|
||||
pass
|
||||
|
||||
|
||||
class JsonClient(object):
|
||||
def __getattr__(self, method):
|
||||
if method[0] == '_':
|
||||
return getattr(super(JsonClient, self), method)
|
||||
def inner(*args):
|
||||
return self._exec(method, args)
|
||||
return inner
|
||||
|
||||
def load_response(self, data):
|
||||
data = json.loads(data.decode("utf-8"))
|
||||
if data.get('error'):
|
||||
raise RPCError(data['error'])
|
||||
if 'result' in data:
|
||||
return data['result']
|
||||
return data
|
||||
|
||||
|
||||
def run_cmd(cmd):
|
||||
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
||||
assert proc.wait() == 0
|
||||
return proc.stdout.read()
|
||||
|
||||
|
||||
def to_hex(s):
|
||||
return base64.b16encode(s).decode('utf-8')
|
||||
|
||||
|
||||
class Hoek(JsonClient):
|
||||
def _exec(self, method, args):
|
||||
cmd = ['hoek', method, json.dumps(args[0])]
|
||||
return self.load_response(run_cmd(cmd))
|
||||
|
||||
|
||||
class Komodod(JsonClient):
|
||||
def _exec(self, method, args):
|
||||
if not hasattr(self, '_url'):
|
||||
urltpl = 'http://$rpcuser:$rpcpassword@${rpchost:-127.0.0.1}:$rpcport'
|
||||
cmd = ['bash', '-c', '. $KOMODO_CONF_PATH && echo -n %s' % urltpl]
|
||||
self._url = run_cmd(cmd)
|
||||
|
||||
req = {'method': method, 'params': args, 'id': 1}
|
||||
cmd = ['curl', '-s', '-H', 'Content-Type: application/json', '-d', json.dumps(req), self._url]
|
||||
return self.load_response(run_cmd(cmd))
|
||||
|
||||
|
||||
rpc = Komodod()
|
||||
hoek = Hoek()
|
||||
|
||||
|
||||
def wait_for_block(height):
|
||||
logging.info("Waiting for block height %s" % height)
|
||||
for i in range(100):
|
||||
try:
|
||||
return rpc.getblock(str(height))
|
||||
except RPCError as e:
|
||||
time.sleep(3)
|
||||
raise Exception('Time out waiting for block at height %s' % height)
|
||||
|
||||
|
||||
def sign(tx):
|
||||
signed = hoek.signTxBitcoin({'tx': tx, 'privateKeys': [notary_sk]})
|
||||
signed = hoek.signTxEd25519({'tx': signed, 'privateKeys': [alice_sk, bob_sk]})
|
||||
return hoek.signTxSecp256k1({'tx': signed, 'privateKeys': [notary_sk]})
|
||||
|
||||
|
||||
def submit(tx):
|
||||
encoded = hoek.encodeTx(tx)
|
||||
try:
|
||||
rpc.getrawtransaction(encoded['txid'])
|
||||
logging.info("Transaction already in chain: %s" % encoded['txid'])
|
||||
return encoded['txid']
|
||||
except RPCError:
|
||||
pass
|
||||
logging.info("submit transaction: %s:%s" % (encoded['txid'], json.dumps(tx)))
|
||||
return rpc.sendrawtransaction(encoded['hex'])
|
||||
|
||||
|
||||
def get_fanout_txid():
|
||||
block = wait_for_block(1)
|
||||
reward_txid = block['tx'][0]
|
||||
reward_tx_raw = rpc.getrawtransaction(reward_txid)
|
||||
reward_tx = hoek.decodeTx({'hex': reward_tx_raw})
|
||||
balance = reward_tx['outputs'][0]['amount']
|
||||
|
||||
n_outs = 16
|
||||
remainder = balance - n_outs * 1000
|
||||
|
||||
fanout = {
|
||||
'inputs': [
|
||||
{'txid': reward_txid, 'idx': 0, 'script': {'pubkey': notary_pk}}
|
||||
],
|
||||
"outputs": (n_outs * [
|
||||
{"amount": 1000, "script": {"condition": cond_alice}}
|
||||
] + [{"amount": remainder, 'script': {'address': notary_addr}}])
|
||||
}
|
||||
|
||||
return submit(sign(fanout))
|
||||
|
||||
|
||||
def fanout_input(n):
|
||||
def decorate(f):
|
||||
def wrapper():
|
||||
if not hasattr(fanout_input, 'txid'):
|
||||
fanout_input.txid = get_fanout_txid()
|
||||
inp = {'txid': fanout_input.txid, 'idx': n, 'script': {'fulfillment': cond_alice}}
|
||||
f(copy.deepcopy(inp))
|
||||
return functools.wraps(f)(wrapper)
|
||||
return decorate
|
||||
|
||||
|
||||
def decode_base64(data):
|
||||
"""Decode base64, padding being optional.
|
||||
|
||||
:param data: Base64 data as an ASCII byte string
|
||||
:returns: The decoded byte string.
|
||||
"""
|
||||
missing_padding = len(data) % 4
|
||||
if missing_padding:
|
||||
data += '=' * (4 - missing_padding)
|
||||
return base64.urlsafe_b64decode(data)
|
||||
|
||||
|
||||
def encode_base64(data):
|
||||
return base64.urlsafe_b64encode(data).rstrip(b'=')
|
||||
|
||||
|
||||
notary_addr = 'RXSwmXKtDURwXP7sdqNfsJ6Ga8RaxTchxE'
|
||||
notary_pk = '0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47'
|
||||
notary_sk = 'UxFWWxsf1d7w7K5TvAWSkeX4H95XQKwdwGv49DXwWUTzPTTjHBbU'
|
||||
alice_pk = '8ryTLBMnozUK4xUz7y49fjzZhxDDMK7c4mucLdbVY6jW'
|
||||
alice_sk = 'E4ER7uYvaSTdpQFzTXNNSTkR6jNRJyqhZPJMGuU899nY'
|
||||
cond_alice = {"type": "ed25519-sha-256", "publicKey": alice_pk}
|
||||
bob_pk = 'C8MfEjKiFxDguacHvcM7MV83cRQ55RAHacC73xqg8qeu'
|
||||
bob_sk = 'GrP1fZdUxUc1NYmu7kiNkJV4p7PKpshp1yBY7hogPUWT'
|
||||
cond_bob = {"type": "ed25519-sha-256", "publicKey": bob_pk}
|
||||
nospend = {"amount": 1000, "script": {"address": notary_addr}}
|
||||
Reference in New Issue
Block a user