#!/usr/bin/env python3
# Copyright (c) 2017-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php.
"""Test recovery from a crash during chainstate writing.

- 4 nodes
  * node0, node1, and node2 will have different dbcrash ratios, and different
    dbcache sizes
  * node3 will be a regular node, with no crashing.
  * The nodes will not connect to each other.

- use default test framework starting chain. initialize starting_tip_height to
  tip height.

- Main loop:
  * generate lots of transactions on node3, enough to fill up a block.
  * uniformly randomly pick a tip height from starting_tip_height to
    tip_height; with probability 1/(height_difference+4), invalidate this block.
  * mine enough blocks to overtake tip_height at start of loop.
  * for each node in [node0,node1,node2]:
     - for each mined block:
       * submit block to node
       * if node crashed on/after submitting:
         - restart until recovery succeeds
         - check that utxo matches node3 using gettxoutsetinfo
"""

import errno
import http.client
import random
import sys
import time

from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, ToHex
from test_framework.test_framework import PivxTestFramework
from test_framework.util import assert_equal, create_confirmed_utxos, hex_str_to_bytes


HTTP_DISCONNECT_ERRORS = [http.client.CannotSendRequest]
try:
    HTTP_DISCONNECT_ERRORS.append(http.client.RemoteDisconnected)
except AttributeError:
    pass


class ChainstateWriteCrashTest(PivxTestFramework):

    def set_test_params(self):
        self.num_nodes = 4
        self.rpc_timewait = 600
        self.setup_clean_chain = False
        # Need a bit of extra time for the nodes to start up for this test

        self.chain_params = ['-nuparams=v5_shield:90000', '-nuparams=PIVX_v4.0:90000',
                             '-nuparams=PIVX_v3.4:90000', '-nuparams=Zerocoin_Public:90000',
                             '-nuparams=Zerocoin_v2:90000', '-nuparams=Zerocoin:90000',
                             '-nuparams=PoS_v2:90000', '-nuparams=PoS:90000']
        # Set -maxmempool=0 to turn off mempool memory sharing with dbcache
        # Set -rpcservertimeout=900 to reduce socket disconnects in this
        # long-running test
        self.base_args = ["-limitdescendantsize=0", "-maxmempool=0", "-rpcservertimeout=900"] + self.chain_params

        # Set different crash ratios and cache sizes.  Note that not all of
        # -dbcache goes to the in-memory coins cache.
        self.node0_args = ["-dbcrashratio=8", "-dbcache=4", "-dbbatchsize=200000"] + self.base_args
        self.node1_args = ["-dbcrashratio=16", "-dbcache=8", "-dbbatchsize=200000"] + self.base_args
        self.node2_args = ["-dbcrashratio=24", "-dbcache=16", "-dbbatchsize=200000"] + self.base_args

        # Node3 is a normal node with default args, except will mine full blocks
        self.node3_args = ["-blockmaxsize=1999000"] + self.chain_params # future: back port blockmaxweight
        self.extra_args = [self.node0_args, self.node1_args, self.node2_args, self.node3_args]

    def setup_network(self):
        self.add_nodes(self.num_nodes, extra_args=self.extra_args)
        self.start_nodes()