Преглед изворни кода

add many more verification tests

Malte Kraus пре 8 година
родитељ
комит
2f345e1c39
3 измењених фајлова са 208 додато и 16 уклоњено
  1. 4 1
      src/blockchain.py
  2. 190 13
      tests/test_verifications.py
  3. 14 2
      tests/utils.py

+ 4 - 1
src/blockchain.py

@@ -135,8 +135,11 @@ class Blockchain:
 
         return int(new_difficulty)
 
-    def compute_blockreward(self, prev_block: 'Block') -> int:
+    def compute_blockreward(self, prev_block: 'Block'=None) -> int:
         """ Compute the block reward that is expected for the block following `prev_block`. """
+        # TODO: remove prev_block param
+        if prev_block is None:
+            prev_block = self.head
         assert prev_block is not None
         reward = 1000
         l = self.block_indices[prev_block.hash]

+ 190 - 13
tests/test_verifications.py

@@ -1,26 +1,35 @@
 from .utils import *
 
-errors = 0
+def block_test(proof_of_work_res=True):
+    """ Immediately runs a test that requires a blockchain. """
+
+    def decorator(fn):
+        def wrapper():
+            orig_proof = src.proof_of_work.verify_proof_of_work
+            src.proof_of_work.verify_proof_of_work = lambda b: proof_of_work_res
+            src.block.verify_proof_of_work = src.proof_of_work.verify_proof_of_work
+
+            chain = Blockchain()
+            assert chain.verify_all()
+
+            try:
+                fn(chain)
+            finally:
+                src.block.verify_proof_of_work = orig_proof
+                src.proof_of_work.verify_proof_of_work = orig_proof
+        return wrapper
+    return decorator
 
 def trans_test(fn):
     """ Immediately runs a test that requires a blockchain, and a transaction with private key in that blockchain. """
 
-    def wrapper():
-        orig_proof = src.proof_of_work.verify_proof_of_work
-        src.proof_of_work.verify_proof_of_work = lambda b: True
-        src.block.verify_proof_of_work = src.proof_of_work.verify_proof_of_work
-
-        gen_chain = Blockchain()
-        assert gen_chain.verify_all()
+    @block_test()
+    def wrapper(gen_chain):
         key = Signing.generate_private_key()
         reward_trans = Transaction([], [TransactionTarget(key, gen_chain.compute_blockreward(gen_chain.head))])
         chain = extend_blockchain(gen_chain, [reward_trans])
 
-        try:
-            fn(chain, reward_trans)
-        finally:
-            src.block.verify_proof_of_work = orig_proof
-            src.proof_of_work.verify_proof_of_work = orig_proof
+        fn(chain, reward_trans)
     return wrapper
 
 @trans_test
@@ -66,3 +75,171 @@ def test_create_money2(chain, reward_trans):
     trans1 = Transaction([trans_as_input(reward_trans)], [target1, target2])
     trans1.sign([key])
     extend_blockchain(chain, [trans1], verify_res=False)
+
+@trans_test
+def test_dupl_block_reward(chain, reward_trans):
+    key = reward_trans.targets[0].recipient_pk
+    target1 = TransactionTarget(key, 1)
+    trans1 = Transaction([], [target1], iv=b"1")
+    trans2 = Transaction([], [target1], iv=b"2")
+    extend_blockchain(chain, [trans1, trans2], verify_res=False)
+
+@trans_test
+def test_negative_block_reward(chain, reward_trans):
+    key = reward_trans.targets[0].recipient_pk
+    target1 = TransactionTarget(key, -1)
+    trans1 = Transaction([], [target1], iv=b"1")
+    extend_blockchain(chain, [trans1], verify_res=False)
+
+@trans_test
+def test_zero_block_reward(chain, reward_trans):
+    extend_blockchain(chain, [], verify_res=True)
+
+@trans_test
+def test_too_large_block_reward(chain, reward_trans):
+    key = reward_trans.targets[0].recipient_pk
+    target1 = TransactionTarget(key, chain.compute_blockreward() + 1)
+    trans1 = Transaction([], [target1], iv=b"1")
+    extend_blockchain(chain, [trans1], verify_res=False)
+
+    trans2 = new_trans(reward_trans, fee=1)
+    target2 = TransactionTarget(key, chain.compute_blockreward() + 2)
+    trans3 = Transaction([], [target2], iv=b"2")
+    extend_blockchain(chain, [trans2, trans3], verify_res=False)
+
+@trans_test
+def test_max_block_reward(chain, reward_trans):
+    key = reward_trans.targets[0].recipient_pk
+    target1 = TransactionTarget(key, chain.compute_blockreward())
+    trans1 = Transaction([], [target1], iv=b"1")
+    extend_blockchain(chain, [trans1], verify_res=True)
+
+    trans2 = new_trans(reward_trans, fee=1)
+    target2 = TransactionTarget(key, chain.compute_blockreward() + 1)
+    trans3 = Transaction([], [target2], iv=b"2")
+    extend_blockchain(chain, [trans2, trans3], verify_res=True)
+
+@trans_test
+def test_spend_too_much(chain, reward_trans):
+    trans = new_trans(reward_trans, fee=-1)
+    assert trans.targets[0].amount == reward_trans.targets[0].amount + 1
+    extend_blockchain(chain, [trans], verify_res=False)
+
+@trans_test
+def test_spend_unknown_coin(chain, reward_trans):
+    key = reward_trans.targets[0].recipient_pk
+    inp1 = TransactionInput(reward_trans.get_hash(), len(reward_trans.targets))
+    trans1 = Transaction([inp1], [])
+    trans1.sign([key])
+    extend_blockchain(chain, [trans1], verify_res=False)
+
+    inp2 = TransactionInput(b"invalid", 0)
+    trans2 = Transaction([inp2], [])
+    trans2.sign([key])
+    extend_blockchain(chain, [trans2], verify_res=False)
+
+@trans_test
+def test_send_zero(chain, reward_trans):
+    trans = new_trans(reward_trans, fee=reward_trans.targets[0].amount)
+    assert trans.targets[0].amount == 0
+    extend_blockchain(chain, [trans], verify_res=False)
+
+@trans_test
+def test_invalid_signature(chain, reward_trans):
+    trans1 = new_trans(reward_trans, fee=0)
+    trans2 = new_trans(reward_trans, fee=1)
+    trans1.signatures, trans2.signatures = trans2.signatures, trans1.signatures
+    extend_blockchain(chain, [trans1], verify_res=False)
+    extend_blockchain(chain, [trans2], verify_res=False)
+
+    trans3 = Transaction(trans1.inputs, trans1.targets, signatures=trans1.signatures+trans2.signatures)
+    extend_blockchain(chain, [trans3], verify_res=False)
+
+    trans4 = Transaction(trans1.inputs, trans1.targets, signatures=[])
+    extend_blockchain(chain, [trans4], verify_res=False)
+
+    # too few signatures:
+    key = reward_trans.targets[0].recipient_pk
+    target1 = TransactionTarget(key, 1)
+    target2 = TransactionTarget(key, 1)
+    trans5 = Transaction(trans1.inputs, [target1, target2])
+    trans5.sign([key])
+    extend_blockchain(chain, [trans5], verify_res=True)
+    input1 = TransactionInput(trans5.get_hash(), 0)
+    input2 = TransactionInput(trans5.get_hash(), 1)
+    trans6 = Transaction([input1, input2], [])
+    trans6.sign([key, key])
+    trans6.signatures.pop()
+    extend_blockchain(chain, [trans6], verify_res=False)
+
+
+
+@block_test(proof_of_work_res=False)
+def test_invalid_proof_of_work(chain):
+    block = Block.create(chain, [])
+    assert chain.try_append(block) is None
+
+@block_test()
+def test_invalid_hash(chain):
+    block = Block.create(chain, [])
+    block.hash = b"invalid"
+    assert chain.try_append(block) is None
+
+@block_test()
+def test_invalid_prev_hash(chain):
+    block = create_block(chain, prev_block_hash="0001020304")
+    assert chain.try_append(block) is None
+
+@block_test()
+def test_invalid_merkle_root_hash(chain):
+    block = create_block(chain, merkle_root_hash="0001020304")
+    assert chain.try_append(block) is None
+
+@block_test()
+def test_monotonic_time(chain):
+    block = create_block(chain, time="1900-01-01T00:00:00.000000 UTC")
+    assert chain.try_append(block) is None
+
+@block_test()
+def test_future_time(chain):
+    block = create_block(chain, time="2900-01-01T00:00:00.000000 UTC")
+    assert chain.try_append(block) is None
+
+@block_test()
+def test_invalid_height(chain):
+    block = create_block(chain, height=-1)
+    assert chain.try_append(block) is None
+    block = create_block(chain, height=chain.head.height)
+    assert chain.try_append(block) is None
+    block = create_block(chain, height=chain.head.height ** 42)
+    assert chain.try_append(block) is None
+    block = create_block(chain, height=chain.head.height ** 42)
+    assert chain.try_append(block) is None
+
+@block_test()
+def test_invalid_difficulty(chain):
+    block = create_block(chain, difficulty=-1)
+    assert chain.try_append(block) is None
+    block = create_block(chain, difficulty=chain.head.difficulty + 1)
+    assert chain.try_append(block) is None
+    block = create_block(chain, difficulty=chain.head.difficulty - 1)
+    assert chain.try_append(block) is None
+    block = create_block(chain, difficulty=chain.head.difficulty ** 42)
+    assert chain.try_append(block) is None
+
+@block_test()
+def test_invalid_height_difficulty(chain):
+    block = create_block(chain, height=chain.head.height - 1, difficulty=-1)
+    assert chain.try_append(block) is None
+    block = create_block(chain, height=chain.head.height, difficulty=0)
+    assert chain.try_append(block) is None
+    block = create_block(chain, height=chain.head.height + 1, difficulty=1)
+    assert chain.try_append(block) is None
+    block = create_block(chain, height=chain.head.height * 42, difficulty=chain.head.height * 41)
+    assert chain.try_append(block) is None
+    block = create_block(chain, height=chain.head.height + chain.head.difficulty + 1,
+                                difficulty=chain.head.difficulty + 1)
+    assert chain.try_append(block) is None
+    block = create_block(chain, height=chain.head.height + chain.head.difficulty - 1,
+                                difficulty=chain.head.difficulty - 1)
+    assert chain.try_append(block) is None

+ 14 - 2
tests/utils.py

@@ -19,12 +19,24 @@ def extend_blockchain(chain, trans:list=None, verify_res=True):
     assert (new_chain is not None) == verify_res
     return new_chain
 
+def create_block(chain, **manipulate_fields):
+    block = Block.create(chain, [], chain.head.time)
+    block.hash = b""
+    obj = block.to_json_compatible()
+    for k, v in manipulate_fields.items():
+        assert k != "hash", "manipulating hash not supported"
+        assert k in obj, "setting an unknown field is useless"
+        obj[k] = v
+    block = Block.from_json_compatible(obj)
+    block.hash = block.get_hash()
+    return block
+
 def trans_as_input(trans, out_idx=0):
     assert len(trans.targets) > out_idx
     return TransactionInput(trans.get_hash(), out_idx)
 
-def new_trans(old_trans, out_idx=0):
-    amount = old_trans.targets[out_idx].amount
+def new_trans(old_trans, out_idx=0, fee=0):
+    amount = old_trans.targets[out_idx].amount - fee
     key = Signing.generate_private_key()
     trans = Transaction([trans_as_input(old_trans, out_idx)],
                         [TransactionTarget(key, amount)])