1import unittest
2import json
3import os
4
5from pycoin.encoding.hexbytes import h2b_rev
6from pycoin.symbols.btc import network
7
8
9flags = network.validator.flags
10ValidationFailureError = network.validator.ValidationFailureError
11
12DEBUG_TX_ID_LIST = []
13
14
15TX_VALID_JSON = os.path.dirname(__file__) + '/data/tx_valid.json'
16TX_INVALID_JSON = os.path.dirname(__file__) + '/data/tx_invalid.json'
17
18
19def parse_flags(flag_string):
20    v = 0
21    if len(flag_string) > 0:
22        for f in flag_string.split(","):
23            v |= getattr(flags, "VERIFY_%s" % f)
24    return v
25
26
27def txs_from_json(path):
28    """
29    Read tests from ./data/tx_??valid.json
30    Format is an array of arrays
31    Inner arrays are either [ "comment" ]
32    or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...], serializedTransaction, verifyFlags]
33    ... where all scripts are stringified scripts.
34
35    verifyFlags is a comma separated list of script verification flags to apply, or "NONE"
36    """
37    comments = None
38    with open(path, 'r') as f:
39        for tvec in json.load(f):
40            if len(tvec) == 1:
41                comments = tvec[0]
42                continue
43            assert len(tvec) == 3
44            prevouts = tvec[0]
45            for prevout in prevouts:
46                assert len(prevout) in (3, 4)
47
48            tx_hex = tvec[1]
49
50            flag_mask = parse_flags(tvec[2])
51            try:
52                tx = network.tx.from_hex(tx_hex)
53            except Exception:
54                print("Cannot parse tx_hex: %s" % tx_hex)
55                raise
56
57            spendable_db = {}
58            blank_spendable = network.tx.Spendable(0, b'', b'\0' * 32, 0)
59            for prevout in prevouts:
60                coin_value = 1000000
61                if len(prevout) == 4:
62                    coin_value = prevout[3]
63                spendable = network.tx.Spendable(
64                    coin_value=coin_value, script=network.script.compile(prevout[2]),
65                    tx_hash=h2b_rev(prevout[0]), tx_out_index=prevout[1])
66                spendable_db[(spendable.tx_hash, spendable.tx_out_index)] = spendable
67            unspents = [
68                spendable_db.get((tx_in.previous_hash, tx_in.previous_index), blank_spendable) for tx_in in tx.txs_in]
69            tx.set_unspents(unspents)
70            yield (tx, flag_mask, comments)
71
72
73class TestTx(unittest.TestCase):
74    pass
75
76
77def make_f(tx, flag_mask, comments, expect_ok=True):
78    tx_hex = tx.as_hex(include_unspents=True)
79
80    def test_f(self):
81        why = None
82        try:
83            tx.check()
84        except ValidationFailureError as ex:
85            why = str(ex)
86        bs = 0
87        for tx_in_idx in range(len(tx.txs_in)):
88            try:
89                if DEBUG_TX_ID_LIST:
90                    import pdb
91                    pdb.set_trace()
92                tx.check_solution(tx_in_idx=tx_in_idx, flags=flag_mask)
93            except tx.SolutionChecker.ScriptError as se:
94                bs += 1
95        if bs > 0:
96            why = "bad sig count = %d" % bs
97        if (why is not None) == expect_ok:
98            why = why or "tx unexpectedly validated"
99            f = open("tx-%s-%x-%s.hex" % (tx.w_id(), flag_mask, "valid" if expect_ok else "invalid"), "w")
100            f.write(tx_hex)
101            f.close()
102            self.fail("fail on %s because of %s with hex %s: %s" % (tx.w_id(), why, tx_hex, comments))
103    if DEBUG_TX_ID_LIST and tx.w_id() not in DEBUG_TX_ID_LIST:
104        return lambda self: 0
105    return test_f
106
107
108def inject():
109    for idx, (tx, flag_mask, comments) in enumerate(txs_from_json(TX_VALID_JSON)):
110        name_of_f = "test_valid_%02d_%s" % (idx, tx.w_id())
111        setattr(TestTx, name_of_f, make_f(tx, flag_mask, comments))
112        print("adding %s" % name_of_f)
113
114    for idx, (tx, flag_mask, comments) in enumerate(txs_from_json(TX_INVALID_JSON)):
115        name_of_f = "test_invalid_%02d_%s" % (idx, tx.w_id())
116        setattr(TestTx, name_of_f, make_f(tx, flag_mask, comments, expect_ok=False))
117        print("adding %s" % name_of_f)
118
119
120inject()
121
122
123if __name__ == '__main__':
124    unittest.main()
125
126
127"""
128Test suite for pycoin library: check validity of txs in files
129tx_valid.json and tx_invalid.json. Adapted from Bitcoin Core
130transaction_tests.cpp test suite.
131
132The MIT License (MIT)
133
134Copyright (c) 2015 by Marek Miller
135Copyright (c) 2015 by Richard Kiss
136Copyright (c) 2015 by The Bitcoin Core Developers
137
138Permission is hereby granted, free of charge, to any person obtaining a copy
139of this software and associated documentation files (the "Software"), to deal
140in the Software without restriction, including without limitation the rights
141to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
142copies of the Software, and to permit persons to whom the Software is
143furnished to do so, subject to the following conditions:
144
145The above copyright notice and this permission notice shall be included in
146all copies or substantial portions of the Software.
147
148THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
149IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
150FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
151AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
152LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
153OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
154THE SOFTWARE.
155"""
156