From 9b41a5fba278e9ab56a9b86e7a5fe195dcad0b7a Mon Sep 17 00:00:00 2001
From: Suhas Daftuar <sdaftuar@gmail.com>
Date: Tue, 15 Dec 2015 15:53:10 -0500
Subject: [PATCH] Add more tests to p2p-fullblocktest

---
 qa/rpc-tests/p2p-fullblocktest.py       | 157 ++++++++++++++++++++++--
 qa/rpc-tests/test_framework/mininode.py |   1 +
 2 files changed, 146 insertions(+), 12 deletions(-)

diff --git a/qa/rpc-tests/p2p-fullblocktest.py b/qa/rpc-tests/p2p-fullblocktest.py
index 9555940ce..a6525e679 100755
--- a/qa/rpc-tests/p2p-fullblocktest.py
+++ b/qa/rpc-tests/p2p-fullblocktest.py
@@ -7,7 +7,7 @@
 
 from test_framework.test_framework import ComparisonTestFramework
 from test_framework.util import *
-from test_framework.comptool import TestManager, TestInstance
+from test_framework.comptool import TestManager, TestInstance, RejectResult
 from test_framework.mininode import *
 from test_framework.blocktools import *
 import logging
@@ -15,7 +15,7 @@ import copy
 import time
 import numbers
 from test_framework.key import CECKey
-from test_framework.script import CScript, CScriptOp, SignatureHash, SIGHASH_ALL, OP_TRUE
+from test_framework.script import CScript, CScriptOp, SignatureHash, SIGHASH_ALL, OP_TRUE, OP_FALSE
 
 class PreviousSpendableOutput(object):
     def __init__(self, tx = CTransaction(), n = -1):
@@ -122,13 +122,29 @@ class FullBlockTest(ComparisonTestFramework):
             return TestInstance([[self.tip, True]])
 
         # returns a test case that asserts that the current tip was rejected
-        def rejected():
-            return TestInstance([[self.tip, False]])
+        def rejected(reject = None):
+            if reject is None:
+                return TestInstance([[self.tip, False]])
+            else:
+                return TestInstance([[self.tip, reject]])
        
         # move the tip back to a previous block
         def tip(number):
             self.tip = self.blocks[number]
 
+        # add transactions to a block produced by next_block
+        def update_block(block_number, new_transactions):
+            block = self.blocks[block_number]
+            old_hash = block.sha256
+            self.add_transactions_to_block(block, new_transactions)
+            block.solve()
+            # Update the internal state just like in next_block
+            self.tip = block
+            self.block_heights[block.sha256] = self.block_heights[old_hash]
+            del self.block_heights[old_hash]
+            self.blocks[block_number] = block
+            return block
+
         # creates a new block and advances the tip to that block
         block = self.next_block
 
@@ -141,14 +157,15 @@ class FullBlockTest(ComparisonTestFramework):
 
         # Now we need that block to mature so we can spend the coinbase.
         test = TestInstance(sync_every_block=False)
-        for i in range(100):
+        for i in range(99):
             block(1000 + i)
             test.blocks_and_transactions.append([self.tip, True])
             save_spendable_output()
         yield test
 
 
-        # Start by bulding a couple of blocks on top (which output is spent is in parentheses):
+        # Start by building a couple of blocks on top (which output is spent is
+        # in parentheses):
         #     genesis -> b1 (0) -> b2 (1)
         out0 = get_spendable_output()
         block(1, spend=out0)
@@ -156,8 +173,7 @@ class FullBlockTest(ComparisonTestFramework):
         yield accepted()
 
         out1 = get_spendable_output()
-        block(2, spend=out1)
-        # Inv again, then deliver twice (shouldn't break anything).
+        b2 = block(2, spend=out1)
         yield accepted()
 
 
@@ -168,8 +184,8 @@ class FullBlockTest(ComparisonTestFramework):
         # 
         # Nothing should happen at this point. We saw b2 first so it takes priority.
         tip(1)
-        block(3, spend=out1)
-        # Deliver twice (should still not break anything)
+        b3 = block(3, spend=out1)
+        txout_b3 = PreviousSpendableOutput(b3.vtx[1], 1)
         yield rejected()
 
 
@@ -214,7 +230,7 @@ class FullBlockTest(ComparisonTestFramework):
         #                      \-> b3 (1) -> b4 (2)
         tip(6)
         block(9, spend=out4, additional_coinbase_value=1)
-        yield rejected()
+        yield rejected(RejectResult(16, 'bad-cb-amount'))
 
         
         # Create a fork that ends in a block with too much fee (the one that causes the reorg)
@@ -226,7 +242,7 @@ class FullBlockTest(ComparisonTestFramework):
         yield rejected()
 
         block(11, spend=out4, additional_coinbase_value=1)
-        yield rejected()
+        yield rejected(RejectResult(16, 'bad-cb-amount'))
 
 
         # Try again, but with a valid fork first
@@ -252,6 +268,10 @@ class FullBlockTest(ComparisonTestFramework):
 
         yield TestInstance([[b12, True, b13.sha256]]) # New tip should be b13.
 
+        # Add a block with MAX_BLOCK_SIGOPS and one with one more sigop
+        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
+        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b16 (6)
+        #                      \-> b3 (1) -> b4 (2)
         
         # Test that a block with a lot of checksigs is okay
         lots_of_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50 - 1))
@@ -264,8 +284,121 @@ class FullBlockTest(ComparisonTestFramework):
         out6 = get_spendable_output()
         too_many_checksigs = CScript([OP_CHECKSIG] * (1000000 / 50))
         block(16, spend=out6, script=too_many_checksigs)
+        yield rejected(RejectResult(16, 'bad-blk-sigops'))
+
+
+        # Attempt to spend a transaction created on a different fork
+        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
+        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (b3.vtx[1])
+        #                      \-> b3 (1) -> b4 (2)
+        tip(15)
+        block(17, spend=txout_b3)
+        yield rejected(RejectResult(16, 'bad-txns-inputs-missingorspent'))
+
+        # Attempt to spend a transaction created on a different fork (on a fork this time)
+        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
+        #                                          \-> b12 (3) -> b13 (4) -> b15 (5)
+        #                                                                \-> b18 (b3.vtx[1]) -> b19 (6)
+        #                      \-> b3 (1) -> b4 (2)
+        tip(13)
+        block(18, spend=txout_b3)
         yield rejected()
 
+        block(19, spend=out6)
+        yield rejected()
+
+        # Attempt to spend a coinbase at depth too low
+        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
+        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7)
+        #                      \-> b3 (1) -> b4 (2)
+        tip(15)
+        out7 = get_spendable_output()
+        block(20, spend=out7)
+        yield rejected(RejectResult(16, 'bad-txns-premature-spend-of-coinbase'))
+
+        # Attempt to spend a coinbase at depth too low (on a fork this time)
+        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
+        #                                          \-> b12 (3) -> b13 (4) -> b15 (5)
+        #                                                                \-> b21 (6) -> b22 (5)
+        #                      \-> b3 (1) -> b4 (2)
+        tip(13)
+        block(21, spend=out6)
+        yield rejected()
+
+        block(22, spend=out5)
+        yield rejected()
+
+        # Create a block on either side of MAX_BLOCK_SIZE and make sure its accepted/rejected
+        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
+        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6)
+        #                                                                           \-> b24 (6) -> b25 (7)
+        #                      \-> b3 (1) -> b4 (2)
+        tip(15)
+        b23 = block(23, spend=out6)
+        old_hash = b23.sha256
+        tx = CTransaction()
+        script_length = MAX_BLOCK_SIZE - len(b23.serialize()) - 69
+        script_output = CScript([chr(0)*script_length])
+        tx.vout.append(CTxOut(0, script_output))
+        tx.vin.append(CTxIn(COutPoint(b23.vtx[1].sha256, 1)))
+        b23 = update_block(23, [tx])
+        # Make sure the math above worked out to produce a max-sized block
+        assert_equal(len(b23.serialize()), MAX_BLOCK_SIZE)
+        yield accepted()
+
+        # Make the next block one byte bigger and check that it fails
+        tip(15)
+        b24 = block(24, spend=out6)
+        script_length = MAX_BLOCK_SIZE - len(b24.serialize()) - 69
+        script_output = CScript([chr(0)*(script_length+1)])
+        tx.vout = [CTxOut(0, script_output)]
+        b24 = update_block(24, [tx])
+        assert_equal(len(b24.serialize()), MAX_BLOCK_SIZE+1)
+        yield rejected(RejectResult(16, 'bad-blk-length'))
+
+        b25 = block(25, spend=out7)
+        yield rejected()
+
+        # Create blocks with a coinbase input script size out of range
+        #     genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6  (3)
+        #                                          \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7)
+        #                                                                           \-> ... (6) -> ... (7)
+        #                      \-> b3 (1) -> b4 (2)
+        tip(15)
+        b26 = block(26, spend=out6)
+        b26.vtx[0].vin[0].scriptSig = chr(0)
+        b26.vtx[0].rehash()
+        # update_block causes the merkle root to get updated, even with no new
+        # transactions, and updates the required state.
+        b26 = update_block(26, [])
+        yield rejected(RejectResult(16, 'bad-cb-length'))
+
+        # Extend the b26 chain to make sure bitcoind isn't accepting b26
+        b27 = block(27, spend=out7)
+        yield rejected()
+
+        # Now try a too-large-coinbase script
+        tip(15)
+        b28 = block(28, spend=out6)
+        b28.vtx[0].vin[0].scriptSig = chr(0)*101
+        b28.vtx[0].rehash()
+        b28 = update_block(28, [])
+        yield rejected(RejectResult(16, 'bad-cb-length'))
+
+        # Extend the b28 chain to make sure bitcoind isn't accepted b28
+        b29 = block(29, spend=out7)
+        # TODO: Should get a reject message back with "bad-prevblk", except
+        # there's a bug that prevents this from being detected.  Just note
+        # failure for now, and add the reject result later.
+        yield rejected()
+
+        # b30 has a max-sized coinbase scriptSig.
+        tip(23)
+        b30 = block(30)
+        b30.vtx[0].vin[0].scriptSig = chr(0)*100
+        b30.vtx[0].rehash()
+        b30 = update_block(30, [])
+        yield accepted()
 
 
 if __name__ == '__main__':
diff --git a/qa/rpc-tests/test_framework/mininode.py b/qa/rpc-tests/test_framework/mininode.py
index 9d0fb713a..8e49b5656 100755
--- a/qa/rpc-tests/test_framework/mininode.py
+++ b/qa/rpc-tests/test_framework/mininode.py
@@ -36,6 +36,7 @@ MY_VERSION = 60001  # past bip-31 for ping/pong
 MY_SUBVERSION = "/python-mininode-tester:0.0.1/"
 
 MAX_INV_SZ = 50000
+MAX_BLOCK_SIZE = 1000000
 
 # Keep our own socket map for asyncore, so that we can track disconnects
 # ourselves (to workaround an issue with closing an asyncore socket when