import unittest
import binascii
import enum
from copy import deepcopy
from bitarray import bitarray
from neo3.network import capabilities
from neo3.network.payloads import (
    address,
    empty,
    transaction,
    block,
    verification,
    inventory,
    ping,
    filter as filterpayload,
    version,
)
from neo3.core import types, serialization, cryptography as crypto
from neo3.settings import settings


class AddrTestCase(unittest.TestCase):
    """
    Tests both Network addresses and the AddrPayload
    """

    @classmethod
    def setUpClass(cls) -> None:
        """
        var capability = new FullNodeCapability(123);

        var ip = IPAddress.Parse("127.0.0.1");
        var network_addr = NetworkAddressWithTime.Create(ip, 0, capability);
        Console.WriteLine($"len: {network_addr.Size}");
        Console.WriteLine($"b\'{BitConverter.ToString(network_addr.ToArray()).Replace("-", "")}\'");
        """
        cls.network_addr = address.NetworkAddress(
            address="127.0.0.1:0",
            timestamp=0,
            capabilities=[capabilities.FullNodeCapability(start_height=123)],
        )

        cls.addr_payload = address.AddrPayload([cls.network_addr])

    def test_network_addr_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 26
        self.assertEqual(expected_len, len(self.network_addr))

    def test_network_addr_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(
            b"0000000000000000000000000000FFFF7F00000101107B000000"
        )
        self.assertEqual(expected_data, self.network_addr.to_array())

    def test_network_addr_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_addr = address.NetworkAddress.deserialize_from_bytes(
            self.network_addr.to_array()
        )
        self.assertEqual(self.network_addr.timestamp, deserialized_addr.timestamp)
        self.assertEqual(self.network_addr.address, deserialized_addr.address)
        self.assertEqual(
            self.network_addr.capabilities[0].start_height,
            deserialized_addr.capabilities[0].start_height,
        )

    def test_addrpayload_len(self):
        expected_len = 27
        self.assertEqual(expected_len, len(self.addr_payload))

    def test_addrpayload_serialization(self):
        expected_data = binascii.unhexlify(
            b"010000000000000000000000000000FFFF7F00000101107B000000"
        )
        self.assertEqual(expected_data, self.addr_payload.to_array())

    def test_addrpayload_deserialization(self):
        deserialized_addr_payload = address.AddrPayload.deserialize_from_bytes(
            self.addr_payload.to_array()
        )
        self.assertEqual(
            len(self.addr_payload.addresses), len(deserialized_addr_payload.addresses)
        )
        self.assertEqual(
            self.addr_payload.addresses[0].capabilities[0].start_height,
            deserialized_addr_payload.addresses[0].capabilities[0].start_height,
        )

    def test_addrpayload_deserialization2(self):
        capa = capabilities.ServerCapability(
            n_type=capabilities.NodeCapabilityType.TCPSERVER, port=123
        )
        network_addr = address.NetworkAddress(
            address="127.0.0.1:0", timestamp=0, capabilities=[capa]
        )
        addr_payload = address.AddrPayload([network_addr])

        deserialized_addr_payload = address.AddrPayload.deserialize_from_bytes(
            addr_payload.to_array()
        )
        self.assertEqual(
            123, deserialized_addr_payload.addresses[0].capabilities[0].port
        )

    def test_equality(self):
        addr1 = address.NetworkAddress(address="127.0.0.1:0")
        addr2 = address.NetworkAddress(address="127.0.0.1:0")
        addr3 = address.NetworkAddress(address="127.0.0.2:0")
        self.assertTrue(addr1 == addr2)
        self.assertFalse(addr1 == addr3)
        self.assertFalse(addr1 == object())

    def test_various_dunder(self):
        addr1 = address.NetworkAddress(address="127.0.0.1:0")
        self.assertEqual("127.0.0.1:0", str(addr1))
        self.assertNotEqual("127.0.0.1", str(addr1))

        formatted = f"{addr1:>12}"
        self.assertEqual(" 127.0.0.1:0", formatted)

        # an object has to be hashable in order for it to be acceptable as a _dictionary key
        # this tests the __hash__ dunder
        try:
            x = {addr1: 123}
        except Exception as e:
            self.fail(f"Failed to add NetworkAddress to dictionary. Can't hash?: {e}")

        # test __repr__
        self.assertIn("127.0.0.1", repr(addr1))
        self.assertIn("(NEW)", repr(addr1))

    def test_properties(self):
        addr1 = address.NetworkAddress(address="127.0.0.1:30333")

        self.assertEqual("127.0.0.1", addr1.ip)
        self.assertEqual(30333, addr1.port)

        self.assertTrue(addr1.is_state_new)
        addr1.set_state_connected()
        self.assertFalse(addr1.is_state_new)

        self.assertTrue(addr1.is_state_connected)
        addr1.set_state_poor()
        self.assertFalse(addr1.is_state_connected)

        self.assertTrue(addr1.is_state_poor)
        addr1.set_state_dead()
        self.assertFalse(addr1.is_state_poor)

        self.assertTrue(addr1.is_state_dead)
        addr1.set_state_new()
        self.assertFalse(addr1.is_state_dead)


class BlockTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        Transaction tx = new Transaction();
        tx.Nonce = 123;
        tx.SystemFee = 456;
        tx.NetworkFee = 789;
        tx.ValidUntilBlock = 1;
        tx.Attributes = new TransactionAttribute[0];
        tx.Signers = new Signer[] { new Signer() { Account = UInt160.Parse("0xe239c7228fa6b46cc0cf43623b2f934301d0b4f7"), Scopes = WitnessScope.CalledByEntry}};
        tx.Script = new byte[] { 0x1 };
        tx.Witnesses = new Witness[] {new Witness { InvocationScript = new byte[0], VerificationScript = new byte[] { 0x55 } }};

        Block b = new Block
        {
            Header = new Header
            {
                Version = 0,
                PrevHash = UInt256.Parse("0xf782c7fbb2eef6afe629b96c0d53fb525eda64ce5345057caf975ac3c2b9ae0a"),
                Timestamp = 123,
                Nonce = 0,
                Index = 1,
                PrimaryIndex = 0,
                NextConsensus = UInt160.Parse("0xd7678dd97c000be3f33e9362e673101bac4ca654"),
                Witness = new Witness { InvocationScript = new byte[0], VerificationScript = new byte[] { 0x55 } },
                MerkleRoot = UInt256.Zero
            },
            Transactions = new Transaction[] { tx }
        };
        b.Header.MerkleRoot = MerkleTree.ComputeRoot(b.Transactions.Select(p => p.Hash).ToArray());
        Console.WriteLine($"{b.Size}");
        Console.WriteLine($"{BitConverter.ToString(b.ToArray()).Replace("-", "")}");

        var trimmedBlock = new TrimmedBlock
        {
            Header = b.Header,
            Hashes = b.Transactions.Select(p => p.Hash).ToArray()
        };
        Console.WriteLine($"{trimmedBlock.Size}");
        Console.WriteLine($"{BitConverter.ToString(trimmedBlock.ToArray()).Replace("-", "")}");

        """
        cls.tx = transaction.Transaction(
            version=0,
            nonce=123,
            system_fee=456,
            network_fee=789,
            valid_until_block=1,
            attributes=[],
            signers=[
                verification.Signer(
                    types.UInt160.from_string(
                        "e239c7228fa6b46cc0cf43623b2f934301d0b4f7"
                    )
                )
            ],
            script=b"\x01",
            witnesses=[
                verification.Witness(invocation_script=b"", verification_script=b"\x55")
            ],
        )

        cls.header = block.Header(
            version=0,
            prev_hash=types.UInt256.from_string(
                "f782c7fbb2eef6afe629b96c0d53fb525eda64ce5345057caf975ac3c2b9ae0a"
            ),
            timestamp=123,
            nonce=0,
            index=1,
            primary_index=0,
            next_consensus=types.UInt160.from_string(
                "d7678dd97c000be3f33e9362e673101bac4ca654"
            ),
            witness=verification.Witness(
                invocation_script=b"", verification_script=b"\x55"
            ),
        )
        cls.block = block.Block(cls.header, transactions=[cls.tx])
        cls.block.rebuild_merkle_root()

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 168
        self.assertEqual(expected_len, len(self.block))

    def test_equals(self):
        self.assertFalse(None == self.block)
        self.assertFalse(self.block == object())

        # test different hashes
        modified_block = deepcopy(self.block)
        modified_block.header.timestamp = 1
        self.assertFalse(self.block == modified_block)
        self.assertTrue(self.block == self.block)

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(
            "000000000AAEB9C2C35A97AF7C054553CE64DA5E52FB530D6CB929E6AFF6EEB2FBC782F7120207967449B8E964E2E731A5CBF440CA39FABDD1EA47845B701790E301246B7B000000000000000000000000000000010000000054A64CAC1B1073E662933EF3E30B007CD98D67D70100015501007B000000C80100000000000015030000000000000100000001F7B4D00143932F3B6243CFC06CB4A68F22C739E20100010101000155"
        )
        self.assertEqual(expected_data, self.block.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_block = block.Block.deserialize_from_bytes(self.block.to_array())
        self.assertEqual(self.block.version, deserialized_block.version)
        self.assertEqual(self.block.prev_hash, deserialized_block.prev_hash)
        self.assertEqual(self.block.timestamp, deserialized_block.timestamp)
        self.assertEqual(self.block.nonce, deserialized_block.nonce)
        self.assertEqual(self.block.index, deserialized_block.index)
        self.assertEqual(self.block.primary_index, deserialized_block.primary_index)
        self.assertEqual(self.block.next_consensus, deserialized_block.next_consensus)
        self.assertEqual(
            self.block.witness.invocation_script,
            deserialized_block.witness.invocation_script,
        )
        self.assertEqual(
            self.block.witness.verification_script,
            deserialized_block.witness.verification_script,
        )
        self.assertEqual(1, len(deserialized_block.transactions))

    def test_deserialization_no_duplicate_transactions(self):
        # A block should not have duplicate transactions
        block_copy = deepcopy(self.block)
        block_copy.transactions.append(block_copy.transactions[0])
        with self.assertRaises(ValueError) as context:
            block.Block.deserialize_from_bytes(block_copy.to_array())
        self.assertIn(
            "Deserialization error - block contains duplicate transaction",
            str(context.exception),
        )

    def test_deserialization_wrong_merkle_root(self):
        block_copy = deepcopy(self.block)
        block_copy.header.merkle_root = types.UInt256.zero()
        with self.assertRaises(ValueError) as context:
            block.Block.deserialize_from_bytes(block_copy.to_array())
        self.assertIn(
            "Deserialization error - merkle root mismatch", str(context.exception)
        )

    def test_inventory_type(self):
        self.assertEqual(inventory.InventoryType.BLOCK, self.block.inventory_type)

    def test_trim(self):
        trimmed_block = self.block.trim()
        self.assertIsInstance(trimmed_block, block.TrimmedBlock)
        # captured from C#, see setUpClass() for the capture code
        expected_len = 146
        self.assertEqual(expected_len, len(trimmed_block))

        expected_data = binascii.unhexlify(
            "000000000AAEB9C2C35A97AF7C054553CE64DA5E52FB530D6CB929E6AFF6EEB2FBC782F7120207967449B8E964E2E731A5CBF440CA39FABDD1EA47845B701790E301246B7B000000000000000000000000000000010000000054A64CAC1B1073E662933EF3E30B007CD98D67D70100015501120207967449B8E964E2E731A5CBF440CA39FABDD1EA47845B701790E301246B"
        )
        self.assertEqual(expected_data, trimmed_block.to_array())

        deserialized_trimmed_block = block.TrimmedBlock.deserialize_from_bytes(
            trimmed_block.to_array()
        )
        self.assertEqual(trimmed_block.header, deserialized_trimmed_block.header)
        self.assertEqual(trimmed_block.hashes, deserialized_trimmed_block.hashes)
        self.assertEqual(1, len(deserialized_trimmed_block.hashes))


class SignerTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        Signer co = new Signer();
        co.Account = UInt160.Parse("0xd7678dd97c000be3f33e9362e673101bac4ca654");
        co.Scopes = WitnessScope.CustomContracts | WitnessScope.CustomGroups;
        co.AllowedContracts = new UInt160[] { UInt160.Parse("5b7074e873973a6ed3708862f219a6fbf4d1c411") };

        ECPoint p = ECPoint.Parse("026241e7e26b38bb7154b8ad49458b97fb1c4797443dc921c5ca5774f511a2bbfc", ECCurve.Secp256r1);
        co.AllowedGroups = new ECPoint[] { p };
        co.Rules = new WitnessRule[0];

        Console.WriteLine($"{co.Size}");
        Console.WriteLine($"{BitConverter.ToString(co.ToArray()).Replace("-","")}");
        Console.Write(co.ToJson());
        """
        cls.signer = verification.Signer(
            types.UInt160.from_string("d7678dd97c000be3f33e9362e673101bac4ca654")
        )
        cls.signer.scope = (
            verification.WitnessScope.CUSTOM_CONTRACTS
            | verification.WitnessScope.CUSTOM_GROUPS
        )
        cls.signer.allowed_contracts = [
            types.UInt160.from_string("5b7074e873973a6ed3708862f219a6fbf4d1c411")
        ]
        point = crypto.ECPoint(
            binascii.unhexlify(
                "026241e7e26b38bb7154b8ad49458b97fb1c4797443dc921c5ca5774f511a2bbfc"
            ),
            crypto.ECCCurve.SECP256R1,
            True,
        )
        cls.signer.allowed_groups = [point]

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 76
        self.assertEqual(expected_len, len(self.signer))

    def test_equals(self):
        self.assertFalse(self.signer == None)
        self.assertFalse(self.signer == object())
        signer2 = verification.Signer._serializable_init()
        self.assertFalse(self.signer == signer2)
        self.assertTrue(self.signer == self.signer)

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(
            b"54A64CAC1B1073E662933EF3E30B007CD98D67D7300111C4D1F4FBA619F2628870D36E3A9773E874705B01026241E7E26B38BB7154B8AD49458B97FB1C4797443DC921C5CA5774F511A2BBFC"
        )
        self.assertEqual(expected_data, self.signer.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_signer = verification.Signer.deserialize_from_bytes(
            self.signer.to_array()
        )
        self.assertEqual(self.signer.account, deserialized_signer.account)
        self.assertEqual(self.signer.scope, deserialized_signer.scope)
        self.assertEqual(
            self.signer.allowed_contracts, deserialized_signer.allowed_contracts
        )
        self.assertEqual(self.signer.allowed_groups, deserialized_signer.allowed_groups)

    def test_deserialization_invalid_scope(self):
        data = bytearray(self.signer.to_array())
        # the scope is serialized after the UInt160 `account` attribute
        # we modify that byte to an invalid scope type
        data[20] = 0xFF

        with self.assertRaises(ValueError) as context:
            verification.Signer.deserialize_from_bytes(data)
        self.assertEqual(
            "Deserialization error - invalid scope. GLOBAL scope not allowed with other scope types",
            str(context.exception),
        )

    def test_to_json(self):
        # captured from C#, see setUpClass() for the capture code
        expected = {
            "account": "0xd7678dd97c000be3f33e9362e673101bac4ca654",
            "scopes": "CustomContracts, CustomGroups",
            "allowedcontracts": ["0x5b7074e873973a6ed3708862f219a6fbf4d1c411"],
            "allowedgroups": [
                "026241e7e26b38bb7154b8ad49458b97fb1c4797443dc921c5ca5774f511a2bbfc"
            ],
        }
        self.assertEqual(expected, self.signer.to_json())

    def test_from_json(self):
        captured = {
            "account": "0xd7678dd97c000be3f33e9362e673101bac4ca654",
            "scopes": "CustomContracts, CustomGroups",
            "allowedcontracts": ["0x5b7074e873973a6ed3708862f219a6fbf4d1c411"],
            "allowedgroups": [
                "026241e7e26b38bb7154b8ad49458b97fb1c4797443dc921c5ca5774f511a2bbfc"
            ],
        }
        signer = verification.Signer.from_json(captured)
        # relies on test_to_json() to pass
        self.assertEqual(captured, signer.to_json())


class FilterAddTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        FilterAddPayload fa = new FilterAddPayload { Data = new byte[] { 0x1, 0x2 } };
        Console.WriteLine($"{fa.Size}");
        Console.WriteLine($"{BitConverter.ToString(fa.ToArray()).Replace("-", "")}");
        """
        cls.filter = filterpayload.FilterAddPayload(b"\x01\x02")

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 3
        self.assertEqual(expected_len, len(self.filter))

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(b"020102")
        self.assertEqual(expected_data, self.filter.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_filter = filterpayload.FilterAddPayload.deserialize_from_bytes(
            self.filter.to_array()
        )
        self.assertEqual(self.filter.data, deserialized_filter.data)


class FilterLoadTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        BloomFilter bf = new BloomFilter(8, 2, 345, new byte[] { 0x1, 0x2 });
        FilterLoadPayload fl = FilterLoadPayload.Create(bf);
        Console.WriteLine($"{fl.Size}");
        Console.WriteLine($"{BitConverter.ToString(fl.ToArray()).Replace("-", "")}");
        """
        bloom = crypto.BloomFilter(8, 2, 345, b"\x01\x02")
        cls.filter = filterpayload.FilterLoadPayload(bloom)

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 7
        self.assertEqual(expected_len, len(self.filter))

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(b"01010259010000")
        self.assertEqual(expected_data, self.filter.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_filter = filterpayload.FilterLoadPayload.deserialize_from_bytes(
            self.filter.to_array()
        )
        self.assertEqual(self.filter.filter, deserialized_filter.filter)
        self.assertEqual(self.filter.K, deserialized_filter.K)
        self.assertEqual(self.filter.tweak, deserialized_filter.tweak)

    def test_deserialization_with_invalid_K_value(self):
        invalid_K = 51
        bloom = crypto.BloomFilter(8, invalid_K, 345, b"\x01\x02")
        filter = filterpayload.FilterLoadPayload(bloom)

        with self.assertRaises(ValueError) as context:
            filterpayload.FilterLoadPayload.deserialize_from_bytes(filter.to_array())
        self.assertIn(
            "Deserialization error - K exceeds limit of 50", str(context.exception)
        )


class GetBlocksPayloadTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        UInt256 hash_start = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01");
        GetBlocksPayload payload = GetBlocksPayload.Create(hash_start, 2);
        Console.WriteLine($"len: {payload.Size}");
        Console.WriteLine($"b\'{BitConverter.ToString(payload.ToArray()).Replace("-", "")}\'");
        """
        cls.hash_start = types.UInt256.from_string(
            "a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"
        )
        cls.payload = block.GetBlocksPayload(hash_start=cls.hash_start, count=2)

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 34
        self.assertEqual(expected_len, len(self.payload))

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(
            b"01FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A40200"
        )
        self.assertEqual(expected_data, self.payload.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_payload = block.GetBlocksPayload.deserialize_from_bytes(
            self.payload.to_array()
        )
        self.assertEqual(self.payload.hash_start, deserialized_payload.hash_start)
        self.assertEqual(2, deserialized_payload.count)


class GetBlockByIndexPayloadTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        GetBlockByIndexPayload payload = GetBlockByIndexPayload.Create(1, 2);
        Console.WriteLine($"len: {payload.Size}");
        Console.WriteLine($"b\'{BitConverter.ToString(payload.ToArray()).Replace("-", "")}\'");
        """
        cls.payload = block.GetBlockByIndexPayload(index_start=1, count=2)

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 6
        self.assertEqual(expected_len, len(self.payload))

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(b"010000000200")
        self.assertEqual(expected_data, self.payload.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_payload = block.GetBlockByIndexPayload.deserialize_from_bytes(
            self.payload.to_array()
        )
        self.assertEqual(self.payload.index_start, deserialized_payload.index_start)
        self.assertEqual(2, deserialized_payload.count)

    def test_deserialization_error(self):
        # test exceed max count
        payload = block.GetBlockByIndexPayload(
            index_start=1, count=block.HeadersPayload.MAX_HEADERS_COUNT + 1
        )
        with self.assertRaises(ValueError) as context:
            block.GetBlockByIndexPayload.deserialize_from_bytes(payload.to_array())
        self.assertIn("Deserialization error - invalid count", str(context.exception))

        # test 0 count
        payload = block.GetBlockByIndexPayload(index_start=1, count=0)
        with self.assertRaises(ValueError) as context:
            block.GetBlockByIndexPayload.deserialize_from_bytes(payload.to_array())
        self.assertIn("Deserialization error - invalid count", str(context.exception))

        # test negative count
        payload = block.GetBlockByIndexPayload(index_start=1, count=-10)
        with self.assertRaises(ValueError) as context:
            block.GetBlockByIndexPayload.deserialize_from_bytes(payload.to_array())
        self.assertIn("Deserialization error - invalid count", str(context.exception))


class HeaderTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        Header h = new Header
        {
            Version = 0,
            PrevHash = UInt256.Parse("a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"),
            MerkleRoot = UInt256.Parse("a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff02"),
            Timestamp = 0,
            Nonce = 0,
            Index = 123,
            PrimaryIndex = 0,
            NextConsensus = UInt160.Parse("0xe239c7228fa6b46cc0cf43623b2f934301d0b4f7"),
            Witness = new Witness
            {
                InvocationScript = new byte[] { 0x1, 0x2},
                VerificationScript = new byte[] { 0x3, 0x4}
            }
        };
        Console.WriteLine(h.Hash);
        Console.WriteLine(h.Size);
        Console.WriteLine($"b\'{BitConverter.ToString(h.ToArray()).Replace("-", "")}\'");
        """
        version = 0
        previous_hash = types.UInt256.from_string(
            "a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"
        )
        merkleroot = types.UInt256.from_string(
            "a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff02"
        )
        timestamp = 0
        nonce = 0
        index = 123
        primary_index = 0
        next_consensus = types.UInt160.from_string(
            "e239c7228fa6b46cc0cf43623b2f934301d0b4f7"
        )
        witness = verification.Witness(
            invocation_script=b"\x01\x02", verification_script=b"\x03\x04"
        )

        cls.header = block.Header(
            version,
            previous_hash,
            timestamp,
            nonce,
            index,
            primary_index,
            next_consensus,
            witness,
            merkleroot,
        )

    def test_len_and_hash(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 116
        expected_hash = types.UInt256.from_string(
            "f8e49a63506ed1273ec79ec6fc0134803aafc251c7e67ffaf355a443617672c5"
        )
        self.assertEqual(expected_len, len(self.header))
        self.assertEqual(expected_hash, self.header.hash())

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(
            b"0000000001FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A402FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A4000000000000000000000000000000007B00000000F7B4D00143932F3B6243CFC06CB4A68F22C739E201020102020304"
        )
        self.assertEqual(expected_data, self.header.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_header = block.Header.deserialize_from_bytes(
            self.header.to_array()
        )
        self.assertEqual(self.header.hash(), deserialized_header.hash())
        self.assertEqual(self.header.version, deserialized_header.version)
        self.assertEqual(self.header.prev_hash, deserialized_header.prev_hash)
        self.assertEqual(self.header.merkle_root, deserialized_header.merkle_root)
        self.assertEqual(self.header.timestamp, deserialized_header.timestamp)
        self.assertEqual(self.header.nonce, deserialized_header.nonce)
        self.assertEqual(self.header.index, deserialized_header.index)
        self.assertEqual(self.header.next_consensus, deserialized_header.next_consensus)
        self.assertEqual(
            self.header.witness.invocation_script,
            deserialized_header.witness.invocation_script,
        )
        self.assertEqual(
            self.header.witness.verification_script,
            deserialized_header.witness.verification_script,
        )

    def test_deserialization_failure1(self):
        # there should be a 1 byte witness object count (fixed to value 1) before the actual witness object.
        # see https://github.com/neo-project/neo/issues/1128
        raw_data = binascii.unhexlify(
            b"0000000001FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A402FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A4000000000000000000000000000000007B00000000F7B4D00143932F3B6243CFC06CB4A68F22C739E200020102020304"
        )
        deserialized_header = block.Header._serializable_init()

        with self.assertRaises(ValueError) as context:
            with serialization.BinaryReader(raw_data) as br:
                deserialized_header.deserialize(br)
        self.assertIn("Deserialization error", str(context.exception))
        self.assertIn("Witness object count is 0 must be 1", str(context.exception))

    def test_equals(self):
        self.assertFalse(None == self.header)
        self.assertFalse(self.header == object())

        # test different hashes
        modified_header = deepcopy(self.header)
        modified_header.timestamp = 1
        self.assertFalse(self.header == modified_header)
        self.assertTrue(self.header == self.header)


class HeadersPayloadTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        Header h1 = new Header
        {
            Version = 0,
            PrevHash = UInt256.Parse("a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"),
            MerkleRoot = UInt256.Parse("a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff02"),
            Timestamp = 0,
            Nonce = 0,
            Index = 123,
            PrimaryIndex = 0,
            NextConsensus = UInt160.Parse("8a2b438eaca8b4b2ab6b4524b5a69a45d920c351"),
            Witness = new Witness
            {
                InvocationScript = new byte[] {0x1, 0x2},
                VerificationScript = new byte[] {0x3, 0x4}
            }
        };
        Header h2;
        using (BinaryReader reader = new BinaryReader(new MemoryStream(h1.ToArray())))
        {
            h2 = reader.ReadSerializable<Header>();
        }

        HeadersPayload hp = HeadersPayload.Create(new Header[] { h1, h2 });
        Console.WriteLine(hp.Size);
        Console.WriteLine($"b\'{BitConverter.ToString(hp.ToArray()).Replace("-", "")}\'");
        """
        version = 0
        previous_hash = types.UInt256.from_string(
            "a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"
        )
        merkleroot = types.UInt256.from_string(
            "a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff02"
        )
        timestamp = 0
        nonce = 0
        index = 123
        primary_index = 0
        next_consensus = types.UInt160.from_string(
            "8a2b438eaca8b4b2ab6b4524b5a69a45d920c351"
        )
        witness = verification.Witness(
            invocation_script=b"\x01\x02", verification_script=b"\x03\x04"
        )

        h1 = block.Header(
            version,
            previous_hash,
            timestamp,
            nonce,
            index,
            primary_index,
            next_consensus,
            witness,
            merkleroot,
        )
        h2 = block.Header(
            version,
            previous_hash,
            timestamp,
            nonce,
            index,
            primary_index,
            next_consensus,
            witness,
            merkleroot,
        )
        cls.payload = block.HeadersPayload([h1, h2])

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 233
        self.assertEqual(expected_len, len(self.payload))

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(
            b"020000000001FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A402FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A4000000000000000000000000000000007B0000000051C320D9459AA6B524456BABB2B4A8AC8E432B8A010201020203040000000001FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A402FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A4000000000000000000000000000000007B0000000051C320D9459AA6B524456BABB2B4A8AC8E432B8A01020102020304"
        )
        self.assertEqual(expected_data, self.payload.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_payload = block.HeadersPayload.deserialize_from_bytes(
            self.payload.to_array()
        )
        self.assertEqual(len(self.payload.headers), len(deserialized_payload.headers))
        self.assertEqual(2, len(self.payload.headers))
        self.assertIsInstance(deserialized_payload.headers[0], block.Header)
        self.assertIsInstance(deserialized_payload.headers[1], block.Header)


class InventoryPayloadTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        var u1 = UInt256.Parse("a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01");
        var u2 = UInt256.Parse("a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff02");
        var payload = InvPayload.Create(InventoryType.Block, new UInt256[] { u1, u2 });
        Console.WriteLine(payload.Size);
        Console.WriteLine($"b\'{BitConverter.ToString(payload.ToArray()).Replace("-", "")}\'");
        """
        cls.u1 = types.UInt256.from_string(
            "a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"
        )
        cls.u2 = types.UInt256.from_string(
            "a400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff02"
        )
        cls.inv = inventory.InventoryPayload(
            inventory.InventoryType.BLOCK, [cls.u1, cls.u2]
        )

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 66
        self.assertEqual(expected_len, len(self.inv))

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(
            b"2C0201FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A402FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00A4"
        )
        self.assertEqual(expected_data, self.inv.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_inv_payload = inventory.InventoryPayload.deserialize_from_bytes(
            self.inv.to_array()
        )
        self.assertEqual(self.inv.type, deserialized_inv_payload.type)
        self.assertEqual(len(self.inv.hashes), len(deserialized_inv_payload.hashes))
        self.assertEqual(self.inv.hashes[0], deserialized_inv_payload.hashes[0])


class MerkleBlockPayloadTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        Transaction tx = new Transaction();
        tx.Nonce = 123;
        tx.SystemFee = 456;
        tx.NetworkFee = 789;
        tx.ValidUntilBlock = 1;
        tx.Attributes = new TransactionAttribute[0];
        tx.Signers = new Signer[] { new Signer() { Account = UInt160.Parse("0xe239c7228fa6b46cc0cf43623b2f934301d0b4f7"), Scopes = WitnessScope.CalledByEntry}};
        tx.Script = new byte[] { 0x1 };
        tx.Witnesses = new Witness[0];

        Block b = new Block
        {
            Header = new Header
            {
                Version = 0,
                PrevHash = UInt256.Parse("0xf782c7fbb2eef6afe629b96c0d53fb525eda64ce5345057caf975ac3c2b9ae0a"),
                Timestamp = 123,
                Nonce = 0,
                Index = 1,
                PrimaryIndex = 0,
                NextConsensus = UInt160.Parse("0xd7678dd97c000be3f33e9362e673101bac4ca654"),
                Witness = new Witness { InvocationScript = new byte[0], VerificationScript = new byte[] { 0x55 } },
                MerkleRoot = UInt256.Zero
            },
            Transactions = new Transaction[] { tx }
        };
        b.Header.MerkleRoot = MerkleTree.ComputeRoot(b.Transactions.Select(p => p.Hash).ToArray());

        byte[] bytes = { 0x1 };
        BitArray flags = new BitArray(bytes);
        MerkleBlockPayload mbp = MerkleBlockPayload.Create(b, flags);
        Console.WriteLine($"b\'{BitConverter.ToString(mbp.ToArray()).Replace("-", "")}\'");
        """

        cls.tx = transaction.Transaction(
            version=0,
            nonce=123,
            system_fee=456,
            network_fee=789,
            valid_until_block=1,
            attributes=[],
            signers=[
                verification.Signer(
                    types.UInt160.from_string(
                        "e239c7228fa6b46cc0cf43623b2f934301d0b4f7"
                    )
                )
            ],
            script=b"\x01",
            witnesses=[
                verification.Witness(invocation_script=b"", verification_script=b"\x55")
            ],
        )

        cls.header = block.Header(
            version=0,
            prev_hash=types.UInt256.from_string(
                "f782c7fbb2eef6afe629b96c0d53fb525eda64ce5345057caf975ac3c2b9ae0a"
            ),
            timestamp=123,
            nonce=0,
            index=1,
            primary_index=0,
            next_consensus=types.UInt160.from_string(
                "d7678dd97c000be3f33e9362e673101bac4ca654"
            ),
            witness=verification.Witness(
                invocation_script=b"", verification_script=b"\x55"
            ),
        )
        cls.block = block.Block(cls.header, transactions=[cls.tx])
        cls.block.rebuild_merkle_root()
        flags = bitarray()
        flags.frombytes(b"\x01")
        cls.merkle_payload = block.MerkleBlockPayload(cls.block, flags)

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 152
        self.assertEqual(expected_len, len(self.merkle_payload))

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(
            b"000000000AAEB9C2C35A97AF7C054553CE64DA5E52FB530D6CB929E6AFF6EEB2FBC782F7120207967449B8E964E2E731A5CBF440CA39FABDD1EA47845B701790E301246B7B000000000000000000000000000000010000000054A64CAC1B1073E662933EF3E30B007CD98D67D7010001550101120207967449B8E964E2E731A5CBF440CA39FABDD1EA47845B701790E301246B0101"
        )
        self.assertEqual(expected_data, self.merkle_payload.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_merkle_payload = block.MerkleBlockPayload.deserialize_from_bytes(
            self.merkle_payload.to_array()
        )
        # not testing all properties again. It re-uses the same block as created in the Block test case
        # only testing new properties
        self.assertEqual(
            self.merkle_payload.tx_count, deserialized_merkle_payload.tx_count
        )
        self.assertEqual(
            len(self.merkle_payload.hashes), len(deserialized_merkle_payload.hashes)
        )
        self.assertEqual(
            self.merkle_payload.hashes[0], deserialized_merkle_payload.hashes[0]
        )
        self.assertEqual(self.merkle_payload.flags, deserialized_merkle_payload.flags)


class PingTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        var payload = PingPayload.Create(123, 456);
        payload.Timestamp = 888;
        Console.WriteLine(payload.Size);
        Console.WriteLine($"b\'{BitConverter.ToString(payload.ToArray()).Replace("-", "")}\'");
        """
        cls.ping = ping.PingPayload(123)
        cls.ping.nonce = 456
        cls.ping.timestamp = 888

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 12
        self.assertEqual(expected_len, len(self.ping))

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(b"7B00000078030000C8010000")
        self.assertEqual(expected_data, self.ping.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_ping = ping.PingPayload.deserialize_from_bytes(
            self.ping.to_array()
        )
        self.assertEqual(self.ping.timestamp, deserialized_ping.timestamp)
        self.assertEqual(self.ping.nonce, deserialized_ping.nonce)
        self.assertEqual(self.ping.current_height, deserialized_ping.current_height)


class TestAttributeTypeEnum(enum.IntEnum):
    TYPE_ZERO = 0x00


class TestTXAttribute(transaction.TransactionAttribute):
    def __init__(self):
        super(TestTXAttribute, self).__init__()

        self.type_ = TestAttributeTypeEnum.TYPE_ZERO
        self.test_member = True

    def __len__(self):
        size_bool = 1
        return super(TestTXAttribute, self).__len__() + size_bool

    def _deserialize_without_type(self, reader: serialization.BinaryReader) -> None:
        self.test_member = reader.read_bool()

    def _serialize_without_type(self, writer: serialization.BinaryWriter) -> None:
        writer.write_bool(self.test_member)


class TransactionAttributeTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        Requires a custom attribute

        class TestTXAttribute : TransactionAttribute
        {
            public override TransactionAttributeType Type { get; }
            public override bool AllowMultiple { get => true; }

            public bool test_member = true;

            public override int Size { get => base.Size + sizeof(bool); }

            protected override void DeserializeWithoutType(BinaryReader reader)
            {
                test_member = reader.ReadBoolean();
            }

            protected override void SerializeWithoutType(BinaryWriter writer)
            {
                writer.Write(test_member);
            }
        }

        TransactionAttribute ta = new TestTXAttribute();
        Console.WriteLine($"{ta.Size}");
        Console.WriteLine($"{BitConverter.ToString(ta.ToArray()).Replace("-", "")}");
        """
        cls.attribute = TestTXAttribute()

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 2
        self.assertEqual(expected_len, len(self.attribute))

    def test_equals(self):
        self.assertFalse(self.attribute == None)
        self.assertFalse(self.attribute == object())
        self.assertTrue(self.attribute == TestTXAttribute())

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(b"0001")
        self.assertEqual(expected_data, self.attribute.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_attribute = self.attribute.deserialize_from_bytes(
            self.attribute.to_array()
        )
        self.assertEqual(self.attribute.type_, deserialized_attribute.type_)
        self.assertEqual(self.attribute.test_member, deserialized_attribute.test_member)

    def test_deserialization_wrong_type(self):
        stream_with_type_one = b"\x01"
        attribute = TestTXAttribute()
        with self.assertRaises(ValueError) as context:
            attribute.deserialize_from_bytes(stream_with_type_one)
        self.assertEqual(
            "Deserialization error - transaction attribute type mismatch",
            str(context.exception),
        )

    def test_deserialization_from(self):
        stream_with_type_zero = b"\x00\x01"
        with serialization.BinaryReader(stream_with_type_zero) as reader:
            ta = transaction.TransactionAttribute.deserialize_from(reader)
        self.assertIsInstance(ta, TestTXAttribute)

    def test_deserialization_from_failure(self):
        stream_with_invalid_type = b"\xFF"
        with serialization.BinaryReader(stream_with_invalid_type) as reader:
            with self.assertRaises(ValueError) as context:
                transaction.TransactionAttribute.deserialize_from(reader)
            self.assertEqual(
                "Deserialization error - unknown transaction attribute type",
                str(context.exception),
            )


class TransactionTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        Transaction tx = new Transaction();
        tx.Nonce = 123;
        tx.SystemFee = 456;
        tx.NetworkFee = 789;
        tx.ValidUntilBlock = 1;
        tx.Attributes = new TransactionAttribute[] { };

        Signer co = new Signer();
        co.Account = UInt160.Parse("0xd7678dd97c000be3f33e9362e673101bac4ca654");
        co.Scopes = WitnessScope.None;
        tx.Signers = new Signer[] { co };

        tx.Script = new byte[] { 0x1, 0x2 };
        tx.Witnesses = new Witness[] { new Witness { InvocationScript=new byte[0], VerificationScript = new byte[] { 0x55 } } };

        Console.WriteLine($"{tx.Size}");
        Console.WriteLine($"{BitConverter.ToString(tx.ToArray()).Replace("-", "")}");
        Console.WriteLine(tx.Hash);
        Console.WriteLine(tx.FeePerByte);
        """
        signer = verification.Signer(
            account=types.UInt160.from_string(
                "d7678dd97c000be3f33e9362e673101bac4ca654"
            ),
            scope=verification.WitnessScope.NONE,
        )

        witness = verification.Witness(
            invocation_script=b"", verification_script=b"\x55"
        )

        cls.tx = transaction.Transaction(
            version=0,
            nonce=123,
            system_fee=456,
            network_fee=789,
            valid_until_block=1,
            attributes=[],
            signers=[signer],
            script=b"\x01\x02",
            witnesses=[witness],
        )

    def tearDown(cls) -> None:
        settings.reset_settings_to_default()

    def test_len_and_hash(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 55
        expected_hash = types.UInt256.from_string(
            "da0343daadf88f95ece657fed6c20e05256d37f0d68757034da1d34f534d2c2c"
        )
        self.assertEqual(expected_len, len(self.tx))
        self.assertEqual(expected_hash, self.tx.hash())

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(
            b"007B000000C8010000000000001503000000000000010000000154A64CAC1B1073E662933EF3E30B007CD98D67D7000002010201000155"
        )
        self.assertEqual(expected_data, self.tx.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_tx = transaction.Transaction.deserialize_from_bytes(
            self.tx.to_array()
        )
        self.assertEqual(self.tx.version, deserialized_tx.version)
        self.assertEqual(self.tx.nonce, deserialized_tx.nonce)
        self.assertEqual(self.tx.system_fee, deserialized_tx.system_fee)
        self.assertEqual(self.tx.network_fee, deserialized_tx.network_fee)
        self.assertEqual(self.tx.valid_until_block, deserialized_tx.valid_until_block)
        self.assertEqual(len(self.tx.attributes), len(deserialized_tx.attributes))
        self.assertEqual(len(self.tx.signers), len(deserialized_tx.signers))
        self.assertEqual(self.tx.signers[0].account, deserialized_tx.signers[0].account)
        self.assertEqual(self.tx.signers[0].scope, deserialized_tx.signers[0].scope)
        self.assertEqual(self.tx.script, deserialized_tx.script)
        self.assertEqual(len(self.tx.witnesses), len(deserialized_tx.witnesses))
        self.assertEqual(
            self.tx.witnesses[0].invocation_script,
            deserialized_tx.witnesses[0].invocation_script,
        )
        self.assertEqual(
            self.tx.witnesses[0].verification_script,
            deserialized_tx.witnesses[0].verification_script,
        )

    def test_deserialization_version_error(self):
        tx = deepcopy(self.tx)
        tx.version = 1
        with self.assertRaises(ValueError) as context:
            transaction.Transaction.deserialize_from_bytes(tx.to_array())
        self.assertEqual(
            "Deserialization error - invalid version", str(context.exception)
        )

    def test_deserialization_system_fee_error(self):
        tx = deepcopy(self.tx)
        tx.system_fee = -1
        with self.assertRaises(ValueError) as context:
            transaction.Transaction.deserialize_from_bytes(tx.to_array())
        self.assertEqual(
            "Deserialization error - negative system fee", str(context.exception)
        )

    def test_deserialization_network_fee_error(self):
        tx = deepcopy(self.tx)
        tx.network_fee = -1
        with self.assertRaises(ValueError) as context:
            transaction.Transaction.deserialize_from_bytes(tx.to_array())
        self.assertEqual(
            "Deserialization error - negative network fee", str(context.exception)
        )

    def test_deserialization_script_length_error(self):
        tx = deepcopy(self.tx)
        tx.script = b""
        with self.assertRaises(ValueError) as context:
            transaction.Transaction.deserialize_from_bytes(tx.to_array())
        self.assertEqual(
            "Deserialization error - invalid script length 0", str(context.exception)
        )

    def test_deserialization_duplicate_attribute_error(self):
        tx = deepcopy(self.tx)
        tx.attributes = [TestTXAttribute(), TestTXAttribute()]
        with self.assertRaises(ValueError) as context:
            transaction.Transaction.deserialize_from_bytes(tx.to_array())
        self.assertEqual(
            "Deserialization error - duplicate transaction attribute",
            str(context.exception),
        )

    def test_deserialization_empty_signers_error(self):
        tx = deepcopy(self.tx)
        tx.signers = []
        with self.assertRaises(ValueError) as context:
            transaction.Transaction.deserialize_from_bytes(tx.to_array())
        self.assertEqual(
            "Deserialization error - signers can't be empty", str(context.exception)
        )

    def test_deserialization_duplicate_signer(self):
        tx = deepcopy(self.tx)
        tx.signers[0].scope = verification.WitnessScope.GLOBAL
        tx.signers.append(deepcopy(tx.signers[0]))
        with self.assertRaises(ValueError) as context:
            transaction.Transaction.deserialize_from_bytes(tx.to_array())
        self.assertEqual(
            "Deserialization error - duplicate signer", str(context.exception)
        )

    def test_fee_per_byte(self):
        # captured from C#, see setUpClass() for the capture code
        expected_fee = 14
        self.assertEqual(expected_fee, self.tx.fee_per_byte())

    def test_equals(self):
        self.assertFalse(None == self.tx)
        self.assertFalse(self.tx == object())

        # test different hashes
        modified_tx = deepcopy(self.tx)
        modified_tx.version = 1
        self.assertFalse(self.tx == modified_tx)
        self.assertTrue(self.tx == self.tx)

    def test_inventory_type(self):
        self.assertEqual(inventory.InventoryType.TX, self.tx.inventory_type)

    def test_special_serialization(self):
        tx_special = deepcopy(self.tx)
        tx_special.block_height = 1
        with serialization.BinaryWriter() as bw:
            tx_special.serialize_special(bw)
            tx_special_bytes = bw.to_array()

        with serialization.BinaryReader(tx_special_bytes) as br:
            tx_from_bytes = transaction.Transaction._serializable_init()
            tx_from_bytes.deserialize_special(br)
        self.assertEqual(tx_special.block_height, tx_from_bytes.block_height)

    def test_sender(self):
        self.assertEqual(self.tx.signers[0].account, self.tx.sender)

        tx = deepcopy(self.tx)
        tx.signers = []
        self.assertEqual(types.UInt160.zero(), tx.sender)

    def test_protocol_magic(self):
        # test proper initialization
        tx = transaction.Transaction._serializable_init()
        # test default
        self.assertEqual(0x4F454E, tx.protocol_magic)
        # test init supplied
        tx = transaction.Transaction(0, 0, 0, 0, 0, protocol_magic=123)
        self.assertEqual(123, tx.protocol_magic)
        # test settings supplied
        settings.network.magic = 456
        tx = transaction.Transaction._serializable_init()
        self.assertEqual(456, tx.protocol_magic)

    def test_scripthashes_for_verifying(self):
        tx = transaction.Transaction._serializable_init()

        account1 = types.UInt160.from_string("d7678dd97c000be3f33e9362e673101bac4ca654")
        account2 = types.UInt160.zero()
        signer1 = verification.Signer(
            account=account1, scope=verification.WitnessScope.NONE
        )
        signer2 = verification.Signer(
            account=account2, scope=verification.WitnessScope.NONE
        )

        tx.signers = [signer1, signer2]

        hashes = tx.get_script_hashes_for_verifying(None)
        self.assertEqual([account1, account2], hashes)

    def test_to_json(self):
        tx = deepcopy(self.tx)
        tx.attributes = [transaction.HighPriorityAttribute()]

        expected = {
            "hash": "0x61a72c37793ea32b9181105931e7a935eb57c915e9dbeb84dc37068c21df2b20",
            "size": 56,
            "version": 0,
            "nonce": 123,
            "sender": "NTdZAZXWiMSDUTkEFU3nHpT7FEEwwtjwfC",
            "sysfee": "456",
            "netfee": "789",
            "validuntilblock": 1,
            "signers": [
                {
                    "account": "0xd7678dd97c000be3f33e9362e673101bac4ca654",
                    "scopes": "None",
                }
            ],
            "attributes": [{"type": "HighPriority"}],
            "script": "AQI=",
            "witnesses": [{"invocation": "", "verification": "VQ=="}],
        }
        self.assertEqual(expected, tx.to_json())

    def test_from_json(self):
        expected = expected = {
            "hash": "0x61a72c37793ea32b9181105931e7a935eb57c915e9dbeb84dc37068c21df2b20",
            "size": 56,
            "version": 0,
            "nonce": 123,
            "sender": "NTdZAZXWiMSDUTkEFU3nHpT7FEEwwtjwfC",
            "sysfee": "456",
            "netfee": "789",
            "validuntilblock": 1,
            "signers": [
                {
                    "account": "0xd7678dd97c000be3f33e9362e673101bac4ca654",
                    "scopes": "None",
                }
            ],
            "attributes": [{"type": "HighPriority"}],
            "script": "AQI=",
            "witnesses": [{"invocation": "", "verification": "VQ=="}],
        }
        tx = transaction.Transaction.from_json(expected)
        self.assertEqual(expected, tx.to_json())


class VersionTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        var capabilities = new List<NodeCapability>
        {
            new FullNodeCapability(123)
        };

        capabilities.Add(new ServerCapability(NodeCapabilityType.TcpServer, (ushort)456));
        NodeCapability[] caps = capabilities.ToArray();
        ISerializable message = VersionPayload.Create(888, "my-user-agent", capabilities.ToArray());
        ((VersionPayload)message).Magic = 769;
        ((VersionPayload)message).Timestamp = 0;
        Console.WriteLine($"b\'{BitConverter.ToString(message.ToArray()).Replace("-", "")}\'");
        """
        settings.network.magic = 769
        capa = [
            capabilities.FullNodeCapability(start_height=123),
            capabilities.ServerCapability(
                capabilities.NodeCapabilityType.TCPSERVER, port=456
            ),
        ]
        cls.vp = version.VersionPayload(
            nonce=888, user_agent="my-user-agent", capabilities=capa
        )
        cls.vp.timestamp = 0

    @classmethod
    def tearDownClass(cls) -> None:
        settings.reset_settings_to_default()

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 39
        self.assertEqual(expected_len, len(self.vp))

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(
            b"010300000000000000000000780300000D6D792D757365722D6167656E7402107B00000001C801"
        )
        self.assertEqual(expected_data, self.vp.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_vp = version.VersionPayload.deserialize_from_bytes(
            self.vp.to_array()
        )
        self.assertEqual(len(self.vp.capabilities), len(deserialized_vp.capabilities))
        self.assertIsInstance(
            deserialized_vp.capabilities[0], capabilities.FullNodeCapability
        )
        self.assertEqual(
            self.vp.capabilities[0].start_height,
            deserialized_vp.capabilities[0].start_height,
        )
        self.assertIsInstance(
            deserialized_vp.capabilities[1], capabilities.ServerCapability
        )
        self.assertEqual(
            self.vp.capabilities[1].port, deserialized_vp.capabilities[1].port
        )


class WitnessTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        """
        Witness witness = new Witness();
        witness.VerificationScript = new byte[] { 0x01, 0x02 };
        witness.InvocationScript = new byte[] { 0x03, 0x04 };
        Console.WriteLine($"b\'{BitConverter.ToString(witness.ToArray()).Replace("-","")}\'");
        Console.Write(witness.ToJson());
        """
        cls.witness = verification.Witness(
            verification_script=b"\x01\x02", invocation_script=b"\x03\x04"
        )

    def test_len(self):
        # captured from C#, see setUpClass() for the capture code
        expected_len = 6
        self.assertEqual(expected_len, len(self.witness))

    def test_script_hash(self):
        # captured from C#, see setUpClass() for the capture code
        expected_script_hash = types.UInt160.from_string(
            "9132aaf67ab75c0a604419d920c5cb91e149cc15"
        )
        h = self.witness.script_hash()
        self.assertIsInstance(h, types.UInt160)
        self.assertEqual(expected_script_hash, h)

    def test_serialization(self):
        # captured from C#, see setUpClass() for the capture code
        expected_data = binascii.unhexlify(b"020304020102")
        self.assertEqual(expected_data, self.witness.to_array())

    def test_deserialization(self):
        # if the serialization() test for this class passes, we can use that as a reference to test deserialization against
        deserialized_witness = verification.Witness.deserialize_from_bytes(
            self.witness.to_array()
        )
        self.assertEqual(
            self.witness.verification_script, deserialized_witness.verification_script
        )
        self.assertEqual(
            self.witness.invocation_script, deserialized_witness.invocation_script
        )

    def test_to_json(self):
        # captured from C#, see setUpClass() for the capture code
        expected = {"invocation": "AwQ=", "verification": "AQI="}
        self.assertEqual(expected, self.witness.to_json())

        expected_empty = {"invocation": "", "verification": ""}
        empty_witness = verification.Witness(b"", b"")
        self.assertEqual(expected_empty, empty_witness.to_json())

    def test_from_json(self):
        witness_json = {"invocation": "AwQ=", "verification": "AQI="}
        witness = verification.Witness.from_json(witness_json)
        self.assertEqual(witness_json, witness.to_json())


class EmptyPayloadTestCase(unittest.TestCase):
    def test_empty(self):
        e = empty.EmptyPayload()
        self.assertEqual(0, len(e))
        self.assertEqual(0, len(e.to_array()))
        bogus_data = b"\x01\x02\x03"
        self.assertIsInstance(
            empty.EmptyPayload.deserialize_from_bytes(bogus_data), empty.EmptyPayload
        )
