extra tests for failure modes and remove CRYPTOCONDITION_OVERSIZE error state
This commit is contained in:
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
The tests use Python as a scripting language and each one should "just work" given Linux and a 2.7+ Python interpreter. Each file prefixed with "test_" is a test case.
|
## How to run the tests
|
||||||
|
|
||||||
The tests require `hoek` to be on the $PATH. Included is a script to perform a complete installation of `hoek`.
|
1. Install hoek: https://github.com/libscott/hoek
|
||||||
|
2. Start komodod: `src/komodod -ac_name=CCTEST -ac_supply=21000000 -gen -pubkey=0205a8ad0c1dbc515f149af377981aab58b836af008d4d7ab21bd76faf80550b47 -ac_cc=1`
|
||||||
|
3. Set environment variable pointing to Komodo conf: `export KOMODO_CONF_PATH=~/.komodo/CCTEST/CCTEST.conf`
|
||||||
|
4. Run tests: `python test_integration.py` (you can also use a test runner such as `nose`).
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import time
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import binascii
|
import binascii
|
||||||
|
import struct
|
||||||
from testsupport import *
|
from testsupport import *
|
||||||
|
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ def test_fulfillment_wrong_signature(inp):
|
|||||||
spend = {'inputs': [inp], 'outputs': [nospend]}
|
spend = {'inputs': [inp], 'outputs': [nospend]}
|
||||||
signed = sign(spend)
|
signed = sign(spend)
|
||||||
|
|
||||||
# Set the correct pubkey, signature is wrong
|
# Set the correct pubkey, signature is bob's
|
||||||
signed['tx']['inputs'][0]['script']['fulfillment']['publicKey'] = alice_pk
|
signed['tx']['inputs'][0]['script']['fulfillment']['publicKey'] = alice_pk
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -32,8 +33,8 @@ def test_fulfillment_wrong_signature(inp):
|
|||||||
@fanout_input(2)
|
@fanout_input(2)
|
||||||
def test_fulfillment_wrong_pubkey(inp):
|
def test_fulfillment_wrong_pubkey(inp):
|
||||||
spend = {'inputs': [inp], 'outputs': [nospend]}
|
spend = {'inputs': [inp], 'outputs': [nospend]}
|
||||||
|
|
||||||
signed = sign(spend)
|
signed = sign(spend)
|
||||||
|
|
||||||
# Set the wrong pubkey, signature is correct
|
# Set the wrong pubkey, signature is correct
|
||||||
signed['tx']['inputs'][0]['script']['fulfillment']['publicKey'] = bob_pk
|
signed['tx']['inputs'][0]['script']['fulfillment']['publicKey'] = bob_pk
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ def test_fulfillment_wrong_pubkey(inp):
|
|||||||
|
|
||||||
|
|
||||||
@fanout_input(3)
|
@fanout_input(3)
|
||||||
def test_fulfillment_invalid_fulfillment(inp):
|
def test_invalid_fulfillment_binary(inp):
|
||||||
# Create a valid script with an invalid fulfillment payload
|
# Create a valid script with an invalid fulfillment payload
|
||||||
inp['script'] = binascii.hexlify(b"\007invalid").decode('utf-8')
|
inp['script'] = binascii.hexlify(b"\007invalid").decode('utf-8')
|
||||||
spend = {'inputs': [inp], 'outputs': [nospend]}
|
spend = {'inputs': [inp], 'outputs': [nospend]}
|
||||||
@@ -55,8 +56,41 @@ def test_fulfillment_invalid_fulfillment(inp):
|
|||||||
assert 'Crypto-Condition payload is invalid' in str(e), str(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 evaluated without error but finished with a false/empty top stack element' in str(e), str(e)
|
||||||
|
|
||||||
|
|
||||||
|
@fanout_input(19)
|
||||||
|
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({'tx': spend}), 'should raise an error'
|
||||||
|
except RPCError as e:
|
||||||
|
assert 'scriptsig-size' in str(e), str(e)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
for name, f in globals().items():
|
for name, f in globals().items():
|
||||||
if name.startswith('test_'):
|
if name.startswith('test_'):
|
||||||
logging.info("Running test: %s" % name)
|
logging.info("Running test: %s" % name)
|
||||||
f()
|
f()
|
||||||
|
logging.info("Test OK: %s" % name)
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ from __future__ import print_function
|
|||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
import copy
|
||||||
|
import base64
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
|
||||||
import functools
|
import functools
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
class RPCError(IOError):
|
class RPCError(IOError):
|
||||||
@@ -34,6 +36,10 @@ def run_cmd(cmd):
|
|||||||
return proc.stdout.read()
|
return proc.stdout.read()
|
||||||
|
|
||||||
|
|
||||||
|
def to_hex(s):
|
||||||
|
return base64.b16encode(s).decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
class Hoek(JsonClient):
|
class Hoek(JsonClient):
|
||||||
def _exec(self, method, args):
|
def _exec(self, method, args):
|
||||||
cmd = ['hoek', method, json.dumps(args[0])]
|
cmd = ['hoek', method, json.dumps(args[0])]
|
||||||
@@ -111,7 +117,7 @@ def fanout_input(n):
|
|||||||
if not hasattr(fanout_input, 'txid'):
|
if not hasattr(fanout_input, 'txid'):
|
||||||
fanout_input.txid = get_fanout_txid()
|
fanout_input.txid = get_fanout_txid()
|
||||||
inp = {'txid': fanout_input.txid, 'idx': n, 'script': {'fulfillment': cond_alice}}
|
inp = {'txid': fanout_input.txid, 'idx': n, 'script': {'fulfillment': cond_alice}}
|
||||||
f(inp)
|
f(copy.deepcopy(inp))
|
||||||
return functools.wraps(f)(wrapper)
|
return functools.wraps(f)(wrapper)
|
||||||
return decorate
|
return decorate
|
||||||
|
|
||||||
|
|||||||
Submodule src/cryptoconditions updated: 89325dc293...7ab93ba861
@@ -949,11 +949,6 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, un
|
|||||||
valtype& vchFulfillment = stacktop(-2);
|
valtype& vchFulfillment = stacktop(-2);
|
||||||
valtype& vchCondition = stacktop(-1);
|
valtype& vchCondition = stacktop(-1);
|
||||||
|
|
||||||
// Hard limit fulfillment size
|
|
||||||
if (vchFulfillment.size() > 1024 * 10) {
|
|
||||||
return set_error(serror, SCRIPT_ERR_CRYPTOCONDITION_INVALID_FULFILLMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
CC *cond = (CC*) calloc(1, sizeof(CC));
|
CC *cond = (CC*) calloc(1, sizeof(CC));
|
||||||
char *fulfillmentBin = (char*) vchFulfillment.data();
|
char *fulfillmentBin = (char*) vchFulfillment.data();
|
||||||
int rc = cc_readFulfillmentBinary(cond, fulfillmentBin, vchFulfillment.size());
|
int rc = cc_readFulfillmentBinary(cond, fulfillmentBin, vchFulfillment.size());
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ const char* ScriptErrorString(const ScriptError serror)
|
|||||||
return "NOPx reserved for soft-fork upgrades";
|
return "NOPx reserved for soft-fork upgrades";
|
||||||
case SCRIPT_ERR_PUBKEYTYPE:
|
case SCRIPT_ERR_PUBKEYTYPE:
|
||||||
return "Public key is neither compressed or uncompressed";
|
return "Public key is neither compressed or uncompressed";
|
||||||
|
case SCRIPT_ERR_CRYPTOCONDITION_INVALID_FULFILLMENT:
|
||||||
|
return "Crypto-Condition payload is invalid";
|
||||||
case SCRIPT_ERR_UNKNOWN_ERROR:
|
case SCRIPT_ERR_UNKNOWN_ERROR:
|
||||||
case SCRIPT_ERR_ERROR_COUNT:
|
case SCRIPT_ERR_ERROR_COUNT:
|
||||||
default: break;
|
default: break;
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ typedef enum ScriptError_t
|
|||||||
|
|
||||||
SCRIPT_ERR_ERROR_COUNT,
|
SCRIPT_ERR_ERROR_COUNT,
|
||||||
|
|
||||||
|
/* crypto-condition script errors */
|
||||||
SCRIPT_ERR_CRYPTOCONDITION_VERIFY,
|
SCRIPT_ERR_CRYPTOCONDITION_VERIFY,
|
||||||
SCRIPT_ERR_CRYPTOCONDITION_INVALID_FULFILLMENT
|
SCRIPT_ERR_CRYPTOCONDITION_INVALID_FULFILLMENT
|
||||||
} ScriptError;
|
} ScriptError;
|
||||||
|
|||||||
Reference in New Issue
Block a user