co.rsk.peg.BridgeSerializationUtilsTest.java Source code

Java tutorial

Introduction

Here is the source code for co.rsk.peg.BridgeSerializationUtilsTest.java

Source

/*
 * This file is part of RskJ
 * Copyright (C) 2017 RSK Labs Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package co.rsk.peg;

import co.rsk.bitcoinj.core.*;
import co.rsk.core.RskAddress;
import co.rsk.peg.whitelist.LockWhitelist;
import co.rsk.peg.whitelist.LockWhitelistEntry;
import co.rsk.peg.whitelist.OneOffWhiteListEntry;
import com.google.common.primitives.UnsignedBytes;
import org.apache.commons.lang3.tuple.Pair;
import org.ethereum.util.RLP;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyVararg;
import static org.mockito.Mockito.mock;
import static org.powermock.api.mockito.PowerMockito.when;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ RLP.class, BridgeSerializationUtils.class, RskAddress.class })
public class BridgeSerializationUtilsTest {
    @Test
    public void serializeMapOfHashesToLong() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_encodeElement();
        mock_RLP_encodeBigInteger();
        mock_RLP_encodeList();

        Map<Sha256Hash, Long> sample = new HashMap<>();
        sample.put(Sha256Hash.wrap(charNTimes('b', 64)), 1L);
        sample.put(Sha256Hash.wrap(charNTimes('d', 64)), 2L);
        sample.put(Sha256Hash.wrap(charNTimes('a', 64)), 3L);
        sample.put(Sha256Hash.wrap(charNTimes('c', 64)), 4L);

        byte[] result = BridgeSerializationUtils.serializeMapOfHashesToLong(sample);
        String hexResult = Hex.toHexString(result);
        StringBuilder expectedBuilder = new StringBuilder();
        char[] sorted = new char[] { 'a', 'b', 'c', 'd' };
        for (char c : sorted) {
            String key = charNTimes(c, 64);
            expectedBuilder.append("dd");
            expectedBuilder.append(key);
            expectedBuilder.append("ff");
            expectedBuilder
                    .append(Hex.toHexString(BigInteger.valueOf(sample.get(Sha256Hash.wrap(key))).toByteArray()));
        }
        assertEquals(expectedBuilder.toString(), hexResult);
    }

    @Test
    public void desserializeMapOfHashesToLong_emptyOrNull() throws Exception {
        assertEquals(BridgeSerializationUtils.deserializeMapOfHashesToLong(null), new HashMap<>());
        assertEquals(BridgeSerializationUtils.deserializeMapOfHashesToLong(new byte[] {}), new HashMap<>());
    }

    @Test
    public void desserializeMapOfHashesToLong_nonEmpty() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2_forMapOfHashesToLong();

        byte[] sample = new byte[] { (byte) 'b', 7, (byte) 'd', 76, (byte) 'a', 123 };
        Map<Sha256Hash, Long> result = BridgeSerializationUtils.deserializeMapOfHashesToLong(sample);
        assertEquals(3, result.size());
        assertEquals(7L, result.get(Sha256Hash.wrap(charNTimes('b', 64))).longValue());
        assertEquals(76L, result.get(Sha256Hash.wrap(charNTimes('d', 64))).longValue());
        assertEquals(123L, result.get(Sha256Hash.wrap(charNTimes('a', 64))).longValue());
    }

    @Test
    public void desserializeMapOfHashesToLong_nonEmptyOddSize() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2_forMapOfHashesToLong();

        boolean thrown = false;
        try {
            byte[] sample = new byte[] { (byte) 'b', 7, (byte) 'd', 76, (byte) 'a' };
            BridgeSerializationUtils.deserializeMapOfHashesToLong(sample);
        } catch (RuntimeException e) {
            thrown = true;
        }
        Assert.assertTrue(thrown);
    }

    @Test
    public void serializeFederation() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_encodeBigInteger();
        mock_RLP_encodeList();
        mock_RLP_encodeElement();

        byte[][] publicKeyBytes = new byte[][] { BtcECKey.fromPrivate(BigInteger.valueOf(100)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(200)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(300)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(400)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(500)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(600)).getPubKey(), };

        Federation federation = new Federation(
                Arrays.asList(new BtcECKey[] { BtcECKey.fromPublicOnly(publicKeyBytes[0]),
                        BtcECKey.fromPublicOnly(publicKeyBytes[1]), BtcECKey.fromPublicOnly(publicKeyBytes[2]),
                        BtcECKey.fromPublicOnly(publicKeyBytes[3]), BtcECKey.fromPublicOnly(publicKeyBytes[4]),
                        BtcECKey.fromPublicOnly(publicKeyBytes[5]), }),
                Instant.ofEpochMilli(0xabcdef), //
                42L, NetworkParameters.fromID(NetworkParameters.ID_REGTEST));

        byte[] result = BridgeSerializationUtils.serializeFederation(federation);
        StringBuilder expectedBuilder = new StringBuilder();
        expectedBuilder.append("ff00abcdef"); // Creation time
        expectedBuilder.append("ff2a"); // Creation block number
        federation.getPublicKeys().stream().sorted(BtcECKey.PUBKEY_COMPARATOR).forEach(key -> {
            expectedBuilder.append("dd");
            expectedBuilder.append(Hex.toHexString(key.getPubKey()));
        });
        byte[] expected = Hex.decode(expectedBuilder.toString());
        Assert.assertTrue(Arrays.equals(expected, result));
    }

    @Test
    public void desserializeFederation_ok() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2(InnerListMode.LAST_ELEMENT);

        byte[][] publicKeyBytes = Arrays.asList(100, 200, 300, 400, 500, 600).stream()
                .map(k -> BtcECKey.fromPrivate(BigInteger.valueOf(k))).sorted(BtcECKey.PUBKEY_COMPARATOR)
                .map(k -> k.getPubKey()).toArray(byte[][]::new);

        StringBuilder sampleBuilder = new StringBuilder();
        sampleBuilder.append("03"); // Length of outer list
        sampleBuilder.append("02"); // Length of first element
        sampleBuilder.append("02"); // Length of second element
        sampleBuilder.append("cd"); // Length of third element
        sampleBuilder.append("1388"); // First element (creation date -> 5000 milliseconds from epoch)
        sampleBuilder.append("002a"); // Second element block number 42
        sampleBuilder.append("06212121212121"); // third element (inner list, public keys). 6 elements of 33 bytes (0x21 bytes) each.
        for (int i = 0; i < publicKeyBytes.length; i++) {
            sampleBuilder.append(Hex.toHexString(publicKeyBytes[i]));
        }
        byte[] sample = Hex.decode(sampleBuilder.toString());

        Federation deserializedFederation = BridgeSerializationUtils.deserializeFederation(sample,
                NetworkParameters.fromID(NetworkParameters.ID_REGTEST));

        Assert.assertEquals(5000, deserializedFederation.getCreationTime().toEpochMilli());
        Assert.assertEquals(4, deserializedFederation.getNumberOfSignaturesRequired());
        Assert.assertEquals(6, deserializedFederation.getPublicKeys().size());
        Assert.assertThat(deserializedFederation.getCreationBlockNumber(), is(42L));
        for (int i = 0; i < 6; i++) {
            Assert.assertTrue(
                    Arrays.equals(publicKeyBytes[i], deserializedFederation.getPublicKeys().get(i).getPubKey()));
        }
        Assert.assertEquals(NetworkParameters.fromID(NetworkParameters.ID_REGTEST),
                deserializedFederation.getBtcParams());
    }

    @Test
    public void desserializeFederation_wrongListSize() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2(InnerListMode.NONE);

        StringBuilder sampleBuilder = new StringBuilder();
        sampleBuilder.append("04"); // Length of outer list
        sampleBuilder.append("02"); // Length of first element
        sampleBuilder.append("01"); // Length of second element
        sampleBuilder.append("01"); // Length of third element
        sampleBuilder.append("04"); // Length of fourth element
        sampleBuilder.append("1388"); // First element (creation date -> 5000 milliseconds from epoch)
        sampleBuilder.append("03"); // Second element (# of signatures required - 3)
        sampleBuilder.append("03"); // Third element
        sampleBuilder.append("aabbccdd"); // Fourth element
        byte[] sample = Hex.decode(sampleBuilder.toString());

        boolean thrown = false;
        try {
            BridgeSerializationUtils.deserializeFederation(sample,
                    NetworkParameters.fromID(NetworkParameters.ID_REGTEST));
        } catch (Exception e) {
            Assert.assertTrue(e.getMessage().contains("Expected 3 elements"));
            thrown = true;
        }
        Assert.assertTrue(thrown);
    }

    @Test
    public void serializePendingFederation() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_encodeList();
        mock_RLP_encodeElement();

        byte[][] publicKeyBytes = new byte[][] { BtcECKey.fromPrivate(BigInteger.valueOf(100)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(200)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(300)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(400)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(500)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(600)).getPubKey(), };

        PendingFederation pendingFederation = new PendingFederation(Arrays.asList(new BtcECKey[] {
                BtcECKey.fromPublicOnly(publicKeyBytes[0]), BtcECKey.fromPublicOnly(publicKeyBytes[1]),
                BtcECKey.fromPublicOnly(publicKeyBytes[2]), BtcECKey.fromPublicOnly(publicKeyBytes[3]),
                BtcECKey.fromPublicOnly(publicKeyBytes[4]), BtcECKey.fromPublicOnly(publicKeyBytes[5]), }));

        byte[] result = BridgeSerializationUtils.serializePendingFederation(pendingFederation);
        StringBuilder expectedBuilder = new StringBuilder();
        pendingFederation.getPublicKeys().stream().sorted(BtcECKey.PUBKEY_COMPARATOR).forEach(key -> {
            expectedBuilder.append("dd");
            expectedBuilder.append(Hex.toHexString(key.getPubKey()));
        });
        byte[] expected = Hex.decode(expectedBuilder.toString());
        Assert.assertTrue(Arrays.equals(expected, result));
    }

    @Test
    public void deserializePendingFederation() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2(InnerListMode.NONE);

        byte[][] publicKeyBytes = Arrays.asList(100, 200, 300, 400, 500, 600).stream()
                .map(k -> BtcECKey.fromPrivate(BigInteger.valueOf(k))).sorted(BtcECKey.PUBKEY_COMPARATOR)
                .map(k -> k.getPubKey()).toArray(byte[][]::new);

        StringBuilder sampleBuilder = new StringBuilder();
        sampleBuilder.append("06212121212121"); // 6 elements of 33 bytes (0x21 bytes) each.
        for (int i = 0; i < publicKeyBytes.length; i++) {
            sampleBuilder.append(Hex.toHexString(publicKeyBytes[i]));
        }
        byte[] sample = Hex.decode(sampleBuilder.toString());

        PendingFederation deserializedPendingFederation = BridgeSerializationUtils
                .deserializePendingFederation(sample);

        Assert.assertEquals(6, deserializedPendingFederation.getPublicKeys().size());
        for (int i = 0; i < 6; i++) {
            Assert.assertTrue(Arrays.equals(publicKeyBytes[i],
                    deserializedPendingFederation.getPublicKeys().get(i).getPubKey()));
        }
    }

    @Test
    public void serializeElection() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_encodeElement();
        mock_RLP_encodeList();

        AddressBasedAuthorizer mockedAuthorizer = mock(AddressBasedAuthorizer.class);
        when(mockedAuthorizer.isAuthorized(any(RskAddress.class))).thenReturn(true);

        Map<ABICallSpec, List<RskAddress>> sampleVotes = new HashMap<>();
        sampleVotes.put(new ABICallSpec("one-function", new byte[][] {}),
                Arrays.asList(mockAddress("8899"), mockAddress("aabb")));
        sampleVotes.put(new ABICallSpec("another-function", new byte[][] { Hex.decode("01"), Hex.decode("0203") }),
                Arrays.asList(mockAddress("ccdd"), mockAddress("eeff"), mockAddress("0011")));
        sampleVotes.put(new ABICallSpec("yet-another-function", new byte[][] { Hex.decode("0405") }),
                Arrays.asList(mockAddress("fa"), mockAddress("ca")));

        ABICallElection sample = new ABICallElection(mockedAuthorizer, sampleVotes);

        byte[] result = BridgeSerializationUtils.serializeElection(sample);
        String hexResult = Hex.toHexString(result);

        StringBuilder expectedBuilder = new StringBuilder();

        expectedBuilder.append("dd");
        expectedBuilder.append(Hex.toHexString("another-function".getBytes(StandardCharsets.UTF_8)));
        expectedBuilder.append("dd01dd0203");
        expectedBuilder.append("dd0011ddccddddeeff");

        expectedBuilder.append("dd");
        expectedBuilder.append(Hex.toHexString("one-function".getBytes(StandardCharsets.UTF_8)));
        expectedBuilder.append("dd8899ddaabb");

        expectedBuilder.append("dd");
        expectedBuilder.append(Hex.toHexString("yet-another-function".getBytes(StandardCharsets.UTF_8)));
        expectedBuilder.append("dd0405");
        expectedBuilder.append("ddcaddfa");

        assertEquals(expectedBuilder.toString(), hexResult);
    }

    @Test
    public void deserializeElection_emptyOrNull() throws Exception {
        AddressBasedAuthorizer mockAuthorizer = mock(AddressBasedAuthorizer.class);
        ABICallElection election;
        election = BridgeSerializationUtils.deserializeElection(null, mockAuthorizer);
        Assert.assertEquals(0, election.getVotes().size());
        election = BridgeSerializationUtils.deserializeElection(new byte[] {}, mockAuthorizer);
        Assert.assertEquals(0, election.getVotes().size());
    }

    @Test
    public void deserializeElection_nonEmpty() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2(InnerListMode.STARTING_WITH_FF_RECURSIVE);

        mockAddressInstantiation("aa");
        mockAddressInstantiation("bbccdd");
        mockAddressInstantiation("55");
        mockAddressInstantiation("66");
        mockAddressInstantiation("77");
        mockAddressInstantiation("1111");
        mockAddressInstantiation("3333");
        mockAddressInstantiation("5555");

        AddressBasedAuthorizer mockedAuthorizer = mock(AddressBasedAuthorizer.class);
        when(mockedAuthorizer.isAuthorized(any(RskAddress.class))).thenReturn(true);

        StringBuilder sampleBuilder = new StringBuilder();
        sampleBuilder.append("06"); // Total of three specs, two entries for each
        sampleBuilder.append("0a");
        sampleBuilder.append("07");
        sampleBuilder.append("17");
        sampleBuilder.append("07");
        sampleBuilder.append("14");
        sampleBuilder.append("0c");

        // First spec + votes
        sampleBuilder.append("020502");
        sampleBuilder.append(Hex.toHexString("funct".getBytes(StandardCharsets.UTF_8)));
        sampleBuilder.append("ff00");

        sampleBuilder.append("020103");
        sampleBuilder.append("aabbccdd");

        // Second spec + votes
        sampleBuilder.append("020b09");
        sampleBuilder.append(Hex.toHexString("other-funct".getBytes(StandardCharsets.UTF_8)));
        sampleBuilder.append("ff020203");
        sampleBuilder.append("1122");
        sampleBuilder.append("334455");

        sampleBuilder.append("03010101");
        sampleBuilder.append("556677");

        // Third spec + votes
        sampleBuilder.append("020c05");
        sampleBuilder.append(Hex.toHexString("random-funct".getBytes(StandardCharsets.UTF_8)));
        sampleBuilder.append("ff0102");
        sampleBuilder.append("aabb");

        sampleBuilder.append("0402020201");
        sampleBuilder.append("11113333555577");

        byte[] sample = Hex.decode(sampleBuilder.toString());

        ABICallElection election = BridgeSerializationUtils.deserializeElection(sample, mockedAuthorizer);

        Assert.assertEquals(3, election.getVotes().size());
        List<RskAddress> voters;
        ABICallSpec spec;

        spec = new ABICallSpec("funct", new byte[][] {});
        Assert.assertTrue(election.getVotes().containsKey(spec));
        voters = Arrays.asList(mockAddress("aa"), mockAddress("bbccdd"));

        Assert.assertArrayEquals(voters.get(0).getBytes(), election.getVotes().get(spec).get(0).getBytes());
        Assert.assertArrayEquals(voters.get(1).getBytes(), election.getVotes().get(spec).get(1).getBytes());

        spec = new ABICallSpec("other-funct", new byte[][] { Hex.decode("1122"), Hex.decode("334455") });
        Assert.assertTrue(election.getVotes().containsKey(spec));
        voters = Arrays.asList(mockAddress("55"), mockAddress("66"), mockAddress("77"));

        Assert.assertArrayEquals(voters.get(0).getBytes(), election.getVotes().get(spec).get(0).getBytes());
        Assert.assertArrayEquals(voters.get(1).getBytes(), election.getVotes().get(spec).get(1).getBytes());
        Assert.assertArrayEquals(voters.get(2).getBytes(), election.getVotes().get(spec).get(2).getBytes());

        spec = new ABICallSpec("random-funct", new byte[][] { Hex.decode("aabb") });
        Assert.assertTrue(election.getVotes().containsKey(spec));
        voters = Arrays.asList(mockAddress("1111"), mockAddress("3333"), mockAddress("5555"), mockAddress("77"));

        Assert.assertArrayEquals(voters.get(0).getBytes(), election.getVotes().get(spec).get(0).getBytes());
        Assert.assertArrayEquals(voters.get(1).getBytes(), election.getVotes().get(spec).get(1).getBytes());
        Assert.assertArrayEquals(voters.get(2).getBytes(), election.getVotes().get(spec).get(2).getBytes());
        Assert.assertArrayEquals(voters.get(3).getBytes(), election.getVotes().get(spec).get(3).getBytes());
    }

    @Test
    public void deserializeElection_unevenOuterList() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2(InnerListMode.STARTING_WITH_FF_RECURSIVE);

        AddressBasedAuthorizer mockedAuthorizer = mock(AddressBasedAuthorizer.class);
        when(mockedAuthorizer.isAuthorized(any(RskAddress.class))).thenReturn(true);

        StringBuilder sampleBuilder = new StringBuilder();
        sampleBuilder.append("05"); // Five elements, uneven
        sampleBuilder.append("0101010101");
        sampleBuilder.append("1122334455");

        byte[] sample = Hex.decode(sampleBuilder.toString());

        try {
            BridgeSerializationUtils.deserializeElection(sample, mockedAuthorizer);
        } catch (RuntimeException e) {
            Assert.assertTrue(e.getMessage().contains("expected an even number of entries, but odd given"));
            return;
        }

        Assert.fail();
    }

    @Test
    public void deserializeElection_invalidCallSpec() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2(InnerListMode.STARTING_WITH_FF_RECURSIVE);

        AddressBasedAuthorizer mockedAuthorizer = mock(AddressBasedAuthorizer.class);
        when(mockedAuthorizer.isAuthorized(any(RskAddress.class))).thenReturn(true);

        StringBuilder sampleBuilder = new StringBuilder();
        sampleBuilder.append("02");
        sampleBuilder.append("07");
        sampleBuilder.append("01");

        sampleBuilder.append("03010101aabbcc"); // Invalid call spec, should have exactly two elements
        sampleBuilder.append("aa"); // Doesn't matter

        byte[] sample = Hex.decode(sampleBuilder.toString());

        try {
            BridgeSerializationUtils.deserializeElection(sample, mockedAuthorizer);
        } catch (RuntimeException e) {
            Assert.assertTrue(e.getMessage().contains("Invalid serialized ABICallSpec"));
            return;
        }

        Assert.fail();
    }

    @Test
    public void serializeLockWhitelist() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_encodeBigInteger();
        mock_RLP_encodeList();
        mock_RLP_encodeElement();

        byte[][] addressesBytes = new byte[][] { BtcECKey.fromPrivate(BigInteger.valueOf(100)).getPubKeyHash(),
                BtcECKey.fromPrivate(BigInteger.valueOf(200)).getPubKeyHash(),
                BtcECKey.fromPrivate(BigInteger.valueOf(300)).getPubKeyHash(),
                BtcECKey.fromPrivate(BigInteger.valueOf(400)).getPubKeyHash(),
                BtcECKey.fromPrivate(BigInteger.valueOf(500)).getPubKeyHash(),
                BtcECKey.fromPrivate(BigInteger.valueOf(600)).getPubKeyHash(), };
        Coin maxToTransfer = Coin.CENT;

        LockWhitelist lockWhitelist = new LockWhitelist(Arrays.stream(addressesBytes)
                .map(bytes -> new Address(NetworkParameters.fromID(NetworkParameters.ID_REGTEST), bytes)).collect(
                        Collectors.toMap(Function.identity(), k -> new OneOffWhiteListEntry(k, maxToTransfer))),
                0);

        byte[] result = BridgeSerializationUtils.serializeOneOffLockWhitelist(
                Pair.of(lockWhitelist.getAll(OneOffWhiteListEntry.class), lockWhitelist.getDisableBlockHeight()));
        StringBuilder expectedBuilder = new StringBuilder();
        Arrays.stream(addressesBytes).sorted(UnsignedBytes.lexicographicalComparator()).forEach(bytes -> {
            expectedBuilder.append("dd");
            expectedBuilder.append(Hex.toHexString(bytes));
            expectedBuilder.append("ff");
            expectedBuilder.append(Hex.toHexString(BigInteger.valueOf(maxToTransfer.value).toByteArray()));
        });
        expectedBuilder.append("ff00");
        byte[] expected = Hex.decode(expectedBuilder.toString());
        Assert.assertThat(result, is(expected));
    }

    @Test
    public void deserializeOneOffLockWhitelistAndDisableBlockHeight() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2(InnerListMode.NONE);

        byte[][] addressesBytes = Arrays.asList(100, 200, 300, 400).stream()
                .map(k -> BtcECKey.fromPrivate(BigInteger.valueOf(k))).sorted(BtcECKey.PUBKEY_COMPARATOR)
                .map(k -> k.getPubKeyHash()).toArray(byte[][]::new);

        StringBuilder sampleBuilder = new StringBuilder();
        sampleBuilder.append("09140314031403140302"); // 9 elements: 8 of 20 bytes (0x14 bytes) and 3 bytes interleaved plus one more element of 2 bytes
        for (int i = 0; i < addressesBytes.length; i++) {
            sampleBuilder.append(Hex.toHexString(addressesBytes[i]));
            sampleBuilder.append("0186a0"); // Coin.MILLICOIN
        }
        sampleBuilder.append("002a");
        byte[] sample = Hex.decode(sampleBuilder.toString());

        Pair<HashMap<Address, OneOffWhiteListEntry>, Integer> deserializedLockWhitelist = BridgeSerializationUtils
                .deserializeOneOffLockWhitelistAndDisableBlockHeight(sample,
                        NetworkParameters.fromID(NetworkParameters.ID_REGTEST));

        Assert.assertThat(deserializedLockWhitelist.getLeft().size(), is(addressesBytes.length));
        Assert.assertThat(deserializedLockWhitelist.getLeft().keySet().stream().map(Address::getHash160)
                .collect(Collectors.toList()), containsInAnyOrder(addressesBytes));
        Set<Coin> deserializedCoins = deserializedLockWhitelist.getLeft().values().stream()
                .map(entry -> ((OneOffWhiteListEntry) entry).maxTransferValue()).collect(Collectors.toSet());
        Assert.assertThat(deserializedCoins, hasSize(1));
        Assert.assertThat(deserializedCoins, hasItem(Coin.MILLICOIN));
        Assert.assertThat(deserializedLockWhitelist.getRight(), is(42));
    }

    @Test
    public void deserializeOneOffLockWhitelistAndDisableBlockHeight_null() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2(InnerListMode.NONE);

        Pair<HashMap<Address, OneOffWhiteListEntry>, Integer> deserializedLockWhitelist = BridgeSerializationUtils
                .deserializeOneOffLockWhitelistAndDisableBlockHeight(null,
                        NetworkParameters.fromID(NetworkParameters.ID_REGTEST));

        Assert.assertNull(deserializedLockWhitelist);

        Pair<HashMap<Address, OneOffWhiteListEntry>, Integer> deserializedLockWhitelist2 = BridgeSerializationUtils
                .deserializeOneOffLockWhitelistAndDisableBlockHeight(new byte[] {},
                        NetworkParameters.fromID(NetworkParameters.ID_REGTEST));

        Assert.assertNull(deserializedLockWhitelist2);
    }

    @Test
    public void serializeDeserializeOneOffLockWhitelistAndDisableBlockHeight() {
        NetworkParameters btcParams = NetworkParameters.fromID(NetworkParameters.ID_REGTEST);
        Map<Address, LockWhitelistEntry> whitelist = new HashMap<>();
        Address address = BtcECKey.fromPrivate(BigInteger.valueOf(100L)).toAddress(btcParams);
        whitelist.put(address, new OneOffWhiteListEntry(address, Coin.COIN));

        LockWhitelist originalLockWhitelist = new LockWhitelist(whitelist, 0);
        byte[] serializedLockWhitelist = BridgeSerializationUtils
                .serializeOneOffLockWhitelist(Pair.of(originalLockWhitelist.getAll(OneOffWhiteListEntry.class),
                        originalLockWhitelist.getDisableBlockHeight()));
        Pair<HashMap<Address, OneOffWhiteListEntry>, Integer> deserializedLockWhitelist = BridgeSerializationUtils
                .deserializeOneOffLockWhitelistAndDisableBlockHeight(serializedLockWhitelist, btcParams);

        List<Address> originalAddresses = originalLockWhitelist.getAddresses();
        List<Address> deserializedAddresses = new ArrayList(deserializedLockWhitelist.getLeft().keySet());
        Assert.assertThat(originalAddresses, hasSize(1));
        Assert.assertThat(deserializedAddresses, hasSize(1));
        Assert.assertThat(originalAddresses, is(deserializedAddresses));
        Assert.assertThat(
                ((OneOffWhiteListEntry) originalLockWhitelist.get(originalAddresses.get(0))).maxTransferValue(),
                is((deserializedLockWhitelist.getLeft().get(deserializedAddresses.get(0))).maxTransferValue()));
    }

    @Test
    public void serializeAndDeserializeFederationWithRealRLP() {
        NetworkParameters networkParms = NetworkParameters.fromID(NetworkParameters.ID_REGTEST);
        byte[][] publicKeyBytes = new byte[][] { BtcECKey.fromPrivate(BigInteger.valueOf(100)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(200)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(300)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(400)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(500)).getPubKey(),
                BtcECKey.fromPrivate(BigInteger.valueOf(600)).getPubKey(), };

        Federation federation = new Federation(
                Arrays.asList(BtcECKey.fromPublicOnly(publicKeyBytes[0]),
                        BtcECKey.fromPublicOnly(publicKeyBytes[1]), BtcECKey.fromPublicOnly(publicKeyBytes[2]),
                        BtcECKey.fromPublicOnly(publicKeyBytes[3]), BtcECKey.fromPublicOnly(publicKeyBytes[4]),
                        BtcECKey.fromPublicOnly(publicKeyBytes[5])),
                Instant.ofEpochMilli(0xabcdef), 42L, networkParms);

        byte[] result = BridgeSerializationUtils.serializeFederation(federation);
        Federation deserializedFederation = BridgeSerializationUtils.deserializeFederation(result, networkParms);
        Assert.assertThat(federation, is(deserializedFederation));
    }

    @Test
    public void serializeRequestQueue() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_encodeElement();
        mock_RLP_encodeBigInteger();
        mock_RLP_encodeList();

        List<ReleaseRequestQueue.Entry> sampleEntries = Arrays.asList(
                new ReleaseRequestQueue.Entry(mockAddressHash160("ccdd"), Coin.valueOf(10)),
                new ReleaseRequestQueue.Entry(mockAddressHash160("bb"), Coin.valueOf(50)),
                new ReleaseRequestQueue.Entry(mockAddressHash160("bb"), Coin.valueOf(20)),
                new ReleaseRequestQueue.Entry(mockAddressHash160("aa"), Coin.valueOf(30)));
        ReleaseRequestQueue sample = new ReleaseRequestQueue(sampleEntries);

        byte[] result = BridgeSerializationUtils.serializeReleaseRequestQueue(sample);
        String hexResult = Hex.toHexString(result);
        StringBuilder expectedBuilder = new StringBuilder();
        expectedBuilder.append("ddccdd");
        expectedBuilder.append("ff0a");
        expectedBuilder.append("ddbb");
        expectedBuilder.append("ff32");
        expectedBuilder.append("ddbb");
        expectedBuilder.append("ff14");
        expectedBuilder.append("ddaa");
        expectedBuilder.append("ff1e");
        assertEquals(expectedBuilder.toString(), hexResult);
    }

    @Test
    public void deserializeRequestQueue_emptyOrNull() throws Exception {
        assertEquals(0, BridgeSerializationUtils
                .deserializeReleaseRequestQueue(null, NetworkParameters.fromID(NetworkParameters.ID_REGTEST))
                .getEntries().size());
        assertEquals(0, BridgeSerializationUtils.deserializeReleaseRequestQueue(new byte[] {},
                NetworkParameters.fromID(NetworkParameters.ID_REGTEST)).getEntries().size());
    }

    @Test
    public void deserializeRequestQueue_nonEmpty() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2(InnerListMode.NONE);

        NetworkParameters params = NetworkParameters.fromID(NetworkParameters.ID_REGTEST);

        Address a1 = Address.fromBase58(params, "mynmcQfJnVjheAqh9XL6htnxPZnaDFbqkB");
        Address a2 = Address.fromBase58(params, "mfrfxeo5L2f5NDURS6YTtCNfVw2t5HAfty");
        Address a3 = Address.fromBase58(params, "myw7AMh5mpKHao6MArhn7EvkeASGsGJzrZ");
        List<ReleaseRequestQueue.Entry> expectedEntries = Arrays.asList(
                new ReleaseRequestQueue.Entry(a1, Coin.valueOf(10)),
                new ReleaseRequestQueue.Entry(a2, Coin.valueOf(7)),
                new ReleaseRequestQueue.Entry(a3, Coin.valueOf(8)));

        StringBuilder sampleBuilder = new StringBuilder();
        sampleBuilder.append("06140114011401");
        sampleBuilder.append(Hex.toHexString(a1.getHash160()));
        sampleBuilder.append("0a");
        sampleBuilder.append(Hex.toHexString(a2.getHash160()));
        sampleBuilder.append("07");
        sampleBuilder.append(Hex.toHexString(a3.getHash160()));
        sampleBuilder.append("08");
        byte[] sample = Hex.decode(sampleBuilder.toString());
        ReleaseRequestQueue result = BridgeSerializationUtils.deserializeReleaseRequestQueue(sample, params);
        List<ReleaseRequestQueue.Entry> entries = result.getEntries();
        assertEquals(expectedEntries, entries);
    }

    @Test
    public void deserializeRequestQueue_nonEmptyOddSize() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2(InnerListMode.NONE);

        try {
            byte[] sample = new byte[] { (byte) 'b', 7, (byte) 'd', 76, (byte) 'a' };
            BridgeSerializationUtils.deserializeReleaseRequestQueue(sample,
                    NetworkParameters.fromID(NetworkParameters.ID_REGTEST));
        } catch (RuntimeException e) {
            return;
        }
        Assert.fail();
    }

    @Test
    public void serializeTransactionSet() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_encodeElement();
        mock_RLP_encodeBigInteger();
        mock_RLP_encodeList();

        Set<ReleaseTransactionSet.Entry> sampleEntries = new HashSet<>(
                Arrays.asList(new ReleaseTransactionSet.Entry(mockBtcTransactionSerialize("ccdd"), 10L),
                        new ReleaseTransactionSet.Entry(mockBtcTransactionSerialize("bb"), 20L),
                        new ReleaseTransactionSet.Entry(mockBtcTransactionSerialize("ba"), 30L),
                        new ReleaseTransactionSet.Entry(mockBtcTransactionSerialize("aa"), 40L)));
        ReleaseTransactionSet sample = new ReleaseTransactionSet(sampleEntries);

        byte[] result = BridgeSerializationUtils.serializeReleaseTransactionSet(sample);
        String hexResult = Hex.toHexString(result);
        StringBuilder expectedBuilder = new StringBuilder();
        expectedBuilder.append("ddaa");
        expectedBuilder.append("ff28");
        expectedBuilder.append("ddba");
        expectedBuilder.append("ff1e");
        expectedBuilder.append("ddbb");
        expectedBuilder.append("ff14");
        expectedBuilder.append("ddccdd");
        expectedBuilder.append("ff0a");
        assertEquals(expectedBuilder.toString(), hexResult);
    }

    @Test
    public void deserializeTransactionSet_emptyOrNull() throws Exception {
        assertEquals(0, BridgeSerializationUtils
                .deserializeReleaseTransactionSet(null, NetworkParameters.fromID(NetworkParameters.ID_REGTEST))
                .getEntries().size());
        assertEquals(0, BridgeSerializationUtils.deserializeReleaseTransactionSet(new byte[] {},
                NetworkParameters.fromID(NetworkParameters.ID_REGTEST)).getEntries().size());
    }

    @Test
    public void deserializeTransactionSet_nonEmpty() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2(InnerListMode.NONE);

        NetworkParameters params = NetworkParameters.fromID(NetworkParameters.ID_REGTEST);

        BtcTransaction input = new BtcTransaction(params);
        input.addOutput(Coin.FIFTY_COINS, Address.fromBase58(params, "mvc8mwDcdLEq2jGqrL43Ub3sxTR13tB8LL"));

        BtcTransaction t1 = new BtcTransaction(params);
        t1.addInput(input.getOutput(0));
        t1.addOutput(Coin.COIN, Address.fromBase58(params, "n3CaAPu2PR7FDdGK8tFwe8thr7hV7zz599"));
        BtcTransaction t2 = new BtcTransaction(params);
        t2.addInput(input.getOutput(0));
        t2.addOutput(Coin.COIN.multiply(10), Address.fromBase58(params, "n3CaAPu2PR7FDdGK8tFwe8thr7hV7zz599"));
        BtcTransaction t3 = new BtcTransaction(params);
        t3.addInput(input.getOutput(0));
        t3.addOutput(Coin.valueOf(15), Address.fromBase58(params, "n3CaAPu2PR7FDdGK8tFwe8thr7hV7zz599"));
        BtcTransaction t4 = new BtcTransaction(params);
        t4.addInput(input.getOutput(0));
        t4.addOutput(Coin.MILLICOIN, Address.fromBase58(params, "n3CaAPu2PR7FDdGK8tFwe8thr7hV7zz599"));

        Set<ReleaseTransactionSet.Entry> expectedEntries = new HashSet<>(
                Arrays.asList(new ReleaseTransactionSet.Entry(t1, 32L), new ReleaseTransactionSet.Entry(t2, 14L),
                        new ReleaseTransactionSet.Entry(t3, 102L), new ReleaseTransactionSet.Entry(t4, 20L)));

        StringBuilder sampleBuilder = new StringBuilder();
        sampleBuilder.append("08");
        sampleBuilder.append(Integer.toHexString(t1.bitcoinSerialize().length));
        sampleBuilder.append("01");
        sampleBuilder.append(Integer.toHexString(t2.bitcoinSerialize().length));
        sampleBuilder.append("01");
        sampleBuilder.append(Integer.toHexString(t3.bitcoinSerialize().length));
        sampleBuilder.append("01");
        sampleBuilder.append(Integer.toHexString(t4.bitcoinSerialize().length));
        sampleBuilder.append("01");
        sampleBuilder.append(Hex.toHexString(t1.bitcoinSerialize()));
        sampleBuilder.append("20");
        sampleBuilder.append(Hex.toHexString(t2.bitcoinSerialize()));
        sampleBuilder.append("0e");
        sampleBuilder.append(Hex.toHexString(t3.bitcoinSerialize()));
        sampleBuilder.append("66");
        sampleBuilder.append(Hex.toHexString(t4.bitcoinSerialize()));
        sampleBuilder.append("14");
        byte[] sample = Hex.decode(sampleBuilder.toString());
        ReleaseTransactionSet result = BridgeSerializationUtils.deserializeReleaseTransactionSet(sample, params);
        Set<ReleaseTransactionSet.Entry> entries = result.getEntries();
        assertEquals(expectedEntries, entries);
    }

    @Test
    public void deserializeTransactionSet_nonEmptyOddSize() throws Exception {
        PowerMockito.mockStatic(RLP.class);
        mock_RLP_decode2(InnerListMode.NONE);

        try {
            byte[] sample = new byte[] { (byte) 'b', 7, (byte) 'd', 76, (byte) 'a' };
            BridgeSerializationUtils.deserializeReleaseTransactionSet(sample,
                    NetworkParameters.fromID(NetworkParameters.ID_REGTEST));
        } catch (RuntimeException e) {
            return;
        }
        Assert.fail();
    }

    @Test
    public void serializeDeserializeCoin() {
        byte[] serialized1 = BridgeSerializationUtils.serializeCoin(Coin.COIN);
        Assert.assertThat(BridgeSerializationUtils.deserializeCoin(serialized1), is(Coin.COIN));
        byte[] serialized2 = BridgeSerializationUtils.serializeCoin(Coin.valueOf(Long.MAX_VALUE));
        Assert.assertThat(BridgeSerializationUtils.deserializeCoin(serialized2), is(Coin.valueOf(Long.MAX_VALUE)));
        byte[] serialized3 = BridgeSerializationUtils.serializeCoin(Coin.ZERO);
        Assert.assertThat(BridgeSerializationUtils.deserializeCoin(serialized3), is(Coin.ZERO));
        Assert.assertThat(BridgeSerializationUtils.deserializeCoin(null), nullValue());
        Assert.assertThat(BridgeSerializationUtils.deserializeCoin(new byte[0]), nullValue());
    }

    private Address mockAddressHash160(String hash160) {
        Address result = mock(Address.class);
        when(result.getHash160()).thenReturn(Hex.decode(hash160));
        return result;
    }

    private BtcTransaction mockBtcTransactionSerialize(String serialized) {
        BtcTransaction result = mock(BtcTransaction.class);
        when(result.bitcoinSerialize()).thenReturn(Hex.decode(serialized));
        return result;
    }

    private void mock_RLP_encodeElement() {
        // Identity prepending byte '0xdd'
        when(RLP.encodeElement(any(byte[].class))).then((InvocationOnMock invocation) -> {
            byte[] arg = invocation.getArgumentAt(0, byte[].class);
            byte[] result = new byte[arg.length + 1];
            result[0] = (byte) 0xdd;
            for (int i = 0; i < arg.length; i++)
                result[i + 1] = arg[i];
            return result;
        });
    }

    private void mock_RLP_encodeBigInteger() {
        // To byte array prepending byte '0xff'
        when(RLP.encodeBigInteger(any(BigInteger.class))).then((InvocationOnMock invocation) -> {
            byte[] arg = (invocation.getArgumentAt(0, BigInteger.class)).toByteArray();
            byte[] result = new byte[arg.length + 1];
            result[0] = (byte) 0xff;
            for (int i = 0; i < arg.length; i++)
                result[i + 1] = arg[i];
            return result;
        });
    }

    private void mock_RLP_encodeList() {
        // To flat byte array
        when(RLP.encodeList(anyVararg())).then((InvocationOnMock invocation) -> {
            Object[] args = invocation.getArguments();
            byte[][] bytes = new byte[args.length][];
            for (int i = 0; i < args.length; i++)
                bytes[i] = (byte[]) args[i];
            return flatten(bytes);
        });
    }

    private void mock_RLP_decode2_forMapOfHashesToLong() {
        // Plain list with first elements being the size
        // Sizes are 1 byte long
        // e.g., for list [a,b,c] and a.size = 5, b.size = 7, c.size = 4, then:
        // 03050704[a bytes][b bytes][c bytes]
        when(RLP.decode2(any(byte[].class))).then((InvocationOnMock invocation) -> {
            RLPList result = new RLPList();
            byte[] arg = invocation.getArgumentAt(0, byte[].class);
            // Even byte -> hash of 64 bytes with same char from byte
            // Odd byte -> long from byte
            for (int i = 0; i < arg.length; i++) {
                byte[] element;
                if (i % 2 == 0) {
                    element = Hex.decode(charNTimes((char) arg[i], 64));
                } else {
                    element = new byte[] { arg[i] };
                }
                result.add(new RLPItem(element));
            }
            return new ArrayList<>(Arrays.asList(result));
        });
    }

    private enum InnerListMode {
        NONE, LAST_ELEMENT, STARTING_WITH_FF_RECURSIVE
    };

    private void mock_RLP_decode2(InnerListMode mode) {
        when(RLP.decode2(any(byte[].class))).then((InvocationOnMock invocation) -> {
            byte[] bytes = invocation.getArgumentAt(0, byte[].class);
            return new ArrayList<>(Arrays.asList(decodeTwoMock(bytes, mode)));
        });
    }

    private RLPList decodeTwoMock(byte[] bytes, InnerListMode mode) {
        RLPList list = decodeList(bytes);
        if (mode == InnerListMode.LAST_ELEMENT) {
            byte[] lastElementBytes = list.get(list.size() - 1).getRLPData();
            RLPList innerList = decodeList(lastElementBytes);
            list.set(list.size() - 1, innerList);
        } else if (mode == InnerListMode.STARTING_WITH_FF_RECURSIVE) {
            for (int i = 0; i < list.size(); i++) {
                byte[] elementBytes = list.get(i).getRLPData();
                if (elementBytes.length > 0 && elementBytes[0] == -1) {
                    RLPList innerList = decodeTwoMock(Arrays.copyOfRange(elementBytes, 1, elementBytes.length),
                            mode);
                    list.set(i, innerList);
                }
            }
        }
        return list;
    }

    private RLPList decodeList(byte[] bytes) {
        // First byte => length of list (n)
        // Subsequent n bytes => length of each of the n elements
        // Subsequent bytes => elements
        RLPList decoded = new RLPList();
        int size = Byte.toUnsignedInt(bytes[0]);
        int offset = size + 1;
        for (int i = 1; i <= size; i++) {
            int elementSize = Byte.toUnsignedInt(bytes[i]);
            byte[] element = Arrays.copyOfRange(bytes, offset, offset + elementSize);
            decoded.add(new RLPItem(element));
            offset += elementSize;
        }
        return decoded;
    }

    private String charNTimes(char c, int n) {
        StringBuilder sb = new StringBuilder(n);
        for (int i = 0; i < n; i++)
            sb.append(c);
        return sb.toString();
    }

    private byte[] flatten(byte[][] bytes) {
        int totalLength = 0;
        for (int i = 0; i < bytes.length; i++)
            totalLength += bytes[i].length;

        byte[] result = new byte[totalLength];
        int offset = 0;
        for (int i = 0; i < bytes.length; i++) {
            for (int j = 0; j < bytes[i].length; j++)
                result[offset + j] = bytes[i][j];
            offset += bytes[i].length;
        }
        return result;
    }

    private void mockAddressInstantiation(String addr) throws Exception {
        PowerMockito.whenNew(RskAddress.class).withArguments(Hex.decode(addr))
                .then((InvocationOnMock invocation) -> mockAddress(addr));
    }

    private RskAddress mockAddress(String addr) {
        RskAddress mock = PowerMockito.mock(RskAddress.class);
        Mockito.when(mock.getBytes()).thenReturn(Hex.decode(addr));
        return mock;
    }
}