private static List <Recipient> GetRecipients(IServiceNodeRegistrationConfig registrationConfig, RegistrationToken registrationToken)
        {
            byte[] tokenBytes  = registrationToken.GetRegistrationTokenBytes(registrationConfig.PrivateKey);
            byte[] markerBytes = Encoding.UTF8.GetBytes(RegistrationToken.Marker);

            var recipients = new List <Recipient>
            {
                new Recipient
                {
                    Amount       = registrationConfig.TxOutputValue,
                    ScriptPubKey = TxNullDataTemplate.Instance.GenerateScriptPubKey(markerBytes)
                }
            };

            recipients.AddRange(BlockChainDataConversions.BytesToPubKeys(tokenBytes).Select(pk => new Recipient
            {
                Amount       = registrationConfig.TxOutputValue,
                ScriptPubKey = pk.ScriptPubKey
            }));

            if (!recipients.Any())
            {
                throw new Exception("ERROR: No recipients for registration transaction, cannot proceed");
            }

            return(recipients);
        }
        public void ConvertPubKeyTest()
        {
            byte[] input = Encoding.ASCII.GetBytes("abc");

            List <PubKey> keys = BlockChainDataConversions.BytesToPubKeys(input);

            byte[] converted = BlockChainDataConversions.PubKeysToBytes(keys);

            Assert.Equal(0x61, converted[0]);
            Assert.Equal(0x62, converted[1]);
            Assert.Equal(0x63, converted[2]);
        }
        public void AddressesToBytes_ShortMessage()
        {
            byte[] expectedBytes = new byte[] {
                0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            };

            var inputAddresses = new List <BitcoinAddress> {
                BitcoinAddress.Create("TJp6YfcXar2jF1LA3QjmfzfeD26hp9qXNn", RedstoneNetworks.TestNet)
            };

            byte[] output = BlockChainDataConversions.AddressesToBytes(inputAddresses);

            Assert.Equal(expectedBytes, output);
        }
        public void AddressesToBytes_ShortMessage()
        {
            var expectedBytes = new byte[] {
                0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            };

            List <BitcoinAddress> inputAddresses = new List <BitcoinAddress>();

            inputAddresses.Add(BitcoinAddress.Create("mpMqqfKnF9M2rwk9Ai4RymBqADx6TssFuM"));

            var output = BlockChainDataConversions.AddressesToBytes(inputAddresses);

            Assert.Equal(expectedBytes, output);
        }
Exemple #5
0
        public Transaction CreateBreezeRegistrationTx(Network network, byte[] data, Money outputValue)
        {
            // Funding of the transaction is handled by the 'fundrawtransaction' RPC
            // call or its equivalent reimplementation.

            // Only construct the transaction outputs; the change address is handled
            // automatically by the funding logic

            // You need to control *where* the change address output appears inside the
            // transaction to prevent decoding errors with the addresses. Note that if
            // the fundrawtransaction RPC call is used there is an option that can be
            // passed to specify the position of the change output (it is randomly
            // positioned otherwise)

            Transaction sendTx = new Transaction();

            // Recognisable string used to tag the transaction within the blockchain
            byte[] bytes = Encoding.UTF8.GetBytes("BREEZE_REGISTRATION_MARKER");
            sendTx.Outputs.Add(new TxOut()
            {
                Value        = outputValue,
                ScriptPubKey = TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes)
            });

            // Add each data-encoding PubKey as a TxOut
            foreach (PubKey pubKey in BlockChainDataConversions.BytesToPubKeys(data))
            {
                TxOut destTxOut = new TxOut()
                {
                    Value        = outputValue,
                    ScriptPubKey = pubKey.ScriptPubKey
                };

                sendTx.Outputs.Add(destTxOut);
            }

            if (sendTx.Outputs.Count == 0)
            {
                throw new Exception("ERROR: No outputs in registration transaction, cannot proceed");
            }

            return(sendTx);
        }
        public void BytesToAddresses_ShortMessage()
        {
            // a - TJp6YfcXar2jF1LA3QjmfzfeD26hp9qXNn <- correct version on redstone testnet

            string inputMessage = "a";

            byte[] inputMessageBytes     = Encoding.ASCII.GetBytes(inputMessage);
            List <BitcoinAddress> output = BlockChainDataConversions.BytesToAddresses(RedstoneNetworks.TestNet, inputMessageBytes);

            var expectedOutput = new List <BitcoinAddress> {
                BitcoinAddress.Create("TJp6YfcXar2jF1LA3QjmfzfeD26hp9qXNn")
            };

            /* Worked example
             * // 0x00 - Bitcoin Mainnet
             * // 0x6F - Bitcoin Testnet
             * // 0x41 - Redstone test <----
             * // 0x3c - Redstone main
             *
             * // Literal 'a' is 0x61 hex
             * var keyBytes = new byte[] {0x41, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
             * var algorithm = SHA256.Create();
             * var hash = algorithm.ComputeHash(algorithm.ComputeHash(keyBytes));
             *
             * //First 4 bytes of double SHA256: 15, 146, 165, 196
             * //Need to concatenate them to keyBytes
             *
             * var keyBytes2 = new byte[] {0x41,
             *  0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             *  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             *  134, 212, 83, 7};
             *
             * var finalEncodedAddress = Encoders.Base58.EncodeData(keyBytes2);
             * /*
             * Result should be "TJp6YfcXar2jF1LA3QjmfzfeD26hp9qXNn"
             */

            Assert.Equal(expectedOutput, output);
        }
        public void BytesToAddresses_ShortMessage()
        {
            // a - mpMqqfKnF9M2rwk9Ai4RymBqADx6TssFuM <- correct version on testnet
            //     mpMqqfKnF9M2rwk9Ai4RymBqADx6TUnBkb <- incorrect version with 00000000 checksum bytes

            string inputMessage = "a";

            byte[] inputMessageBytes             = Encoding.ASCII.GetBytes(inputMessage);
            List <BitcoinAddress> output         = BlockChainDataConversions.BytesToAddresses(Network.TestNet, inputMessageBytes);
            List <BitcoinAddress> expectedOutput = new List <BitcoinAddress>();

            expectedOutput.Add(BitcoinAddress.Create("mpMqqfKnF9M2rwk9Ai4RymBqADx6TssFuM"));

            /* Worked example
             * // 0x00 - Mainnet
             * // 0x6F - Testnet
             * // 0x?? - Stratis mainnet
             *
             * // Literal 'a' is 0x61 hex
             *
             * var keyBytes = new byte[] {0x6F, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
             * var algorithm = SHA256.Create();
             * var hash = algorithm.ComputeHash(algorithm.ComputeHash(keyBytes));
             *
             * First 4 bytes of double SHA256: 15, 146, 165, 196
             * Need to concatenate them to keyBytes
             *
             * var keyBytes2 = new byte[] {0x6F,
             *  0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             *  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             *  15, 146, 165, 196};
             *
             * var finalEncodedAddress = Encoders.Base58.EncodeData(keyBytes2);
             *
             * Result should be "mpMqqfKnF9M2rwk9Ai4RymBqADx6TssFuM"
             */

            Assert.Equal(expectedOutput, output);
        }
        public async Task DummyRegistration(string originWalletName, string originWalletPassword)
        {
            // TODO: Move this functionality into the tests
            var token = new List <byte>();

            // Server ID
            token.AddRange(Encoding.ASCII.GetBytes("".PadRight(34)));

            // IPv4 address
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);

            // IPv6 address
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);

            // Onion address
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);

            // Port number
            byte[] portNumber = BitConverter.GetBytes(37123);
            token.Add(portNumber[0]);
            token.Add(portNumber[1]);

            // RSA sig length
            byte[] rsaLength = BitConverter.GetBytes(256);
            token.Add(rsaLength[0]);
            token.Add(rsaLength[1]);

            // RSA signature
            byte[] rsaSig = new byte[256];
            token.AddRange(rsaSig);

            // ECDSA sig length
            byte[] ecdsaLength = BitConverter.GetBytes(128);
            token.Add(ecdsaLength[0]);
            token.Add(ecdsaLength[1]);

            // ECDSA signature
            byte[] ecdsaSig = new byte[128];
            token.AddRange(ecdsaSig);

            // Configuration hash
            token.AddRange(Encoding.ASCII.GetBytes("aa4e984c5655a677716539acc8cbc0ce29331429"));

            // Finally add protocol byte and computed length to beginning of header
            byte[] protocolVersionByte = BitConverter.GetBytes(254);
            byte[] headerLength        = BitConverter.GetBytes(token.Count);

            token.Insert(0, protocolVersionByte[0]);
            token.Insert(1, headerLength[0]);
            token.Insert(2, headerLength[1]);

            Money outputValue = new Money(0.0001m, MoneyUnit.BTC);

            Transaction sendTx = new Transaction();

            // Recognisable string used to tag the transaction within the blockchain
            byte[] bytes = Encoding.UTF8.GetBytes("BREEZE_REGISTRATION_MARKER");
            sendTx.Outputs.Add(new TxOut()
            {
                Value        = outputValue,
                ScriptPubKey = TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes)
            });

            // Add each data-encoding PubKey as a TxOut
            foreach (PubKey pubKey in BlockChainDataConversions.BytesToPubKeys(token.ToArray()))
            {
                TxOut destTxOut = new TxOut()
                {
                    Value        = outputValue,
                    ScriptPubKey = pubKey.ScriptPubKey
                };

                sendTx.Outputs.Add(destTxOut);
            }

            HdAccount highestAcc = null;

            foreach (HdAccount account in this.walletManager.GetAccounts(originWalletName))
            {
                if (highestAcc == null)
                {
                    highestAcc = account;
                }

                if (account.GetSpendableAmount().ConfirmedAmount > highestAcc.GetSpendableAmount().ConfirmedAmount)
                {
                    highestAcc = account;
                }
            }

            // This fee rate is primarily for regtest, testnet and mainnet have actual estimators that work
            FeeRate feeRate = new FeeRate(new Money(10000, MoneyUnit.Satoshi));
            WalletAccountReference  accountRef     = new WalletAccountReference(originWalletName, highestAcc.Name);
            List <Recipient>        recipients     = new List <Recipient>();
            TransactionBuildContext txBuildContext = new TransactionBuildContext(accountRef, recipients);

            txBuildContext.WalletPassword   = originWalletPassword;
            txBuildContext.OverrideFeeRate  = feeRate;
            txBuildContext.Sign             = true;
            txBuildContext.MinConfirmations = 0;

            this.walletTransactionHandler.FundTransaction(txBuildContext, sendTx);

            this.logger.LogDebug("Trying to broadcast transaction: " + sendTx.GetHash());

            await this.broadcasterManager.BroadcastTransactionAsync(sendTx).ConfigureAwait(false);

            var bcResult = this.broadcasterManager.GetTransaction(sendTx.GetHash()).State;

            switch (bcResult)
            {
            case Stratis.Bitcoin.Broadcasting.State.Broadcasted:
            case Stratis.Bitcoin.Broadcasting.State.Propagated:
                this.logger.LogDebug("Broadcasted transaction: " + sendTx.GetHash());
                break;

            case Stratis.Bitcoin.Broadcasting.State.ToBroadcast:
                // Wait for propagation
                var waited = TimeSpan.Zero;
                var period = TimeSpan.FromSeconds(1);
                while (TimeSpan.FromSeconds(21) > waited)
                {
                    // Check BroadcasterManager for broadcast success
                    var transactionEntry = this.broadcasterManager.GetTransaction(sendTx.GetHash());
                    if (transactionEntry != null &&
                        transactionEntry.State == Stratis.Bitcoin.Broadcasting.State.Propagated)
                    {
                        // TODO: This is cluttering up the console, only need to log it once
                        this.logger.LogDebug("Propagated transaction: " + sendTx.GetHash());
                    }
                    await Task.Delay(period).ConfigureAwait(false);

                    waited += period;
                }
                break;

            case Stratis.Bitcoin.Broadcasting.State.CantBroadcast:
                // Do nothing
                break;
            }

            this.logger.LogDebug("Uncertain if transaction was propagated: " + sendTx.GetHash());
        }
        public void ParseTransaction(Transaction tx, Network network)
        {
            var markerIndex = GetMarkerIndex(tx);

            if (markerIndex == -1)
            {
                throw new Exception("Missing Redstone registration marker from first transaction output");
            }

            // Peek at first non-nulldata address to get the length information,
            // this indicates how many outputs have been used for encoding, and
            // by extension indicates if there will be a change address output

            PubKey[] tempPubKeyArray = tx.Outputs[markerIndex + 1].ScriptPubKey.GetDestinationPublicKeys(network);

            if (tempPubKeyArray.Length > 1)
            {
                // This can't have been generated by a server registration, we don't use
                // multiple signatures for the registration transaction outputs by design
                throw new Exception("Registration transaction output has too many PubKeys");
            }

            byte[] secondOutputData = BlockChainDataConversions.PubKeyToBytes(tempPubKeyArray[0]);

            var protocolVersion = (int)secondOutputData[0];

            var headerLength = (secondOutputData[2] << 8) + secondOutputData[1];

            // 64 = number of bytes we can store per output
            int numPubKeys = headerLength / 64;

            // Is there a partially 'full' PubKey holding the remainder of the bytes?
            if (headerLength % 64 != 0)
            {
                numPubKeys++;
            }

            if (tx.Outputs.Count < numPubKeys + 1)
            {
                throw new Exception("Too few transaction outputs, registration transaction incomplete");
            }

            PubKey[] tempPK;
            var      pubKeyList = new List <PubKey>();

            for (int i = markerIndex + 1; i < numPubKeys + markerIndex + 1; i++)
            {
                tempPK = tx.Outputs[i].ScriptPubKey.GetDestinationPublicKeys(network);

                if (tempPK.Length > 1)
                {
                    // This can't have been generated by a server registration, we don't use
                    // multiple signatures for the registration transaction outputs by design
                    throw new Exception("Registration transaction output has too many PubKeys");
                }

                pubKeyList.Add(tempPK[0]);
            }

            byte[] bitstream = BlockChainDataConversions.PubKeysToBytes(pubKeyList);

            // Need to consume X bytes at a time off the bitstream and convert them to various
            // data types, then set member variables to the retrieved values.

            // Skip over protocol version and header length bytes
            int position = 3;

            this.ProtocolVersion = protocolVersion;

            byte[] collateralPubKeyHashTemp = GetSubArray(bitstream, position, 20);
            this.CollateralPubKeyHash = new KeyId(collateralPubKeyHashTemp);
            position += 20;

            byte[] rewaredPubKeyHashTemp = GetSubArray(bitstream, position, 20);
            this.RewardPubKeyHash = new KeyId(rewaredPubKeyHashTemp);
            position += 20;

            // Either a valid IPv4 address, or all zero bytes
            bool allZeroes = true;

            byte[] ipv4temp = GetSubArray(bitstream, position, 4);

            for (int i = 0; i < ipv4temp.Length; i++)
            {
                if (ipv4temp[i] != 0)
                {
                    allZeroes = false;
                }
            }

            if (!allZeroes)
            {
                this.Ipv4Addr = new IPAddress(ipv4temp);
            }
            else
            {
                this.Ipv4Addr = IPAddress.None;
            }

            position += 4;

            // Either a valid IPv6 address, or all zero bytes
            allZeroes = true;
            byte[] ipv6temp = GetSubArray(bitstream, position, 16);

            for (int i = 0; i < ipv6temp.Length; i++)
            {
                if (ipv6temp[i] != 0)
                {
                    allZeroes = false;
                }
            }

            if (!allZeroes)
            {
                this.Ipv6Addr = new IPAddress(ipv6temp);
            }
            else
            {
                this.Ipv6Addr = IPAddress.IPv6None;
            }

            position += 16;

            // Either a valid onion address, or all zero bytes
            allZeroes = true;
            byte[] onionTemp = GetSubArray(bitstream, position, 16);

            for (int i = 0; i < onionTemp.Length; i++)
            {
                if (onionTemp[i] != 0)
                {
                    allZeroes = false;
                }
            }

            if (!allZeroes)
            {
                this.OnionAddress = Encoding.ASCII.GetString(onionTemp);
            }
            else
            {
                this.OnionAddress = null;
            }

            position += 16;

            var temp = GetSubArray(bitstream, position, 2);

            this.Port = (temp[1] << 8) + temp[0];
            position += 2;

            temp = GetSubArray(bitstream, position, 2);
            var servicEndpointLength = (temp[1] << 8) + temp[0];

            position += 4;

            var serviceEndpointTemp = GetSubArray(bitstream, position, servicEndpointLength);

            this.ServiceEndpoint = new Uri(Encoding.ASCII.GetString(serviceEndpointTemp));
            position            += servicEndpointLength;

            temp = GetSubArray(bitstream, position, 2);
            var ecdsaLength = (temp[1] << 8) + temp[0];

            position += 4;

            this.Signature = GetSubArray(bitstream, position, ecdsaLength);
            position      += ecdsaLength;

            temp = GetSubArray(bitstream, position, 2);
            var ecdsaPubKeyLength = (temp[1] << 8) + temp[0];

            position += 2;

            this.PubKey = new PubKey(GetSubArray(bitstream, position, ecdsaPubKeyLength));
            position   += ecdsaPubKeyLength;

            // TODO: Validate signatures
        }
        public void RegistrationTest()
        {
            using (NodeBuilder builder = NodeBuilder.Create())
            {
                CoreNode node1 = builder.CreateStratisPosNode(true, fullNodeBuilder =>
                {
                    fullNodeBuilder
                    .UseStratisConsensus()
                    .UseBlockStore()
                    .UseMempool()
                    .UseBlockNotification()
                    .UseTransactionNotification()
                    .UseWallet()
                    .UseWatchOnlyWallet()
                    .AddPowPosMining()
                    //.AddMining()
                    //.UseApi()
                    .AddRPC();
                });

                CoreNode node2 = builder.CreateStratisPosNode(true, fullNodeBuilder =>
                {
                    fullNodeBuilder
                    .UseStratisConsensus()
                    .UseBlockStore()
                    .UseMempool()
                    .UseBlockNotification()
                    .UseTransactionNotification()
                    .UseWallet()
                    .UseWatchOnlyWallet()
                    .AddPowPosMining()
                    //.AddMining()
                    //.UseApi()
                    .AddRPC()
                    .UseRegistration();
                });

                node1.NotInIBD();
                node2.NotInIBD();

                var rpc1 = node1.CreateRPCClient();
                var rpc2 = node2.CreateRPCClient();

                // addnode RPC call does not seem to work, so connect directly
                node1.FullNode.ConnectionManager.AddNodeAddress(node2.Endpoint);

                // Create the originating node's wallet
                var wm1 = node1.FullNode.NodeService <IWalletManager>() as WalletManager;
                wm1.CreateWallet("Registration1", "registration");

                var wallet1  = wm1.GetWalletByName("registration");
                var account1 = wallet1.GetAccountsByCoinType((CoinType)node1.FullNode.Network.Consensus.CoinType).First();
                var address1 = account1.GetFirstUnusedReceivingAddress();
                var secret1  = wallet1.GetExtendedPrivateKeyForAddress("Registration1", address1);
                node1.SetDummyMinerSecret(new BitcoinSecret(secret1.PrivateKey, node1.FullNode.Network));

                // Generate a block so we have some funds to create a transaction with
                node1.GenerateStratisWithMiner(52);

                TestHelper.TriggerSync(node1);
                TestHelper.TriggerSync(node2);

                TestHelper.WaitLoop(() => rpc1.GetBestBlockHash() == rpc2.GetBestBlockHash());

                var rsa           = new RsaKey();
                var ecdsa         = new Key().GetBitcoinSecret(Network.RegTest);
                var serverAddress = ecdsa.GetAddress().ToString();

                var token = new RegistrationToken(1,
                                                  serverAddress,
                                                  IPAddress.Parse("127.0.0.1"),
                                                  IPAddress.Parse("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
                                                  "0123456789ABCDEF",
                                                  "",
                                                  37123,
                                                  ecdsa.PubKey);

                var cryptoUtils = new CryptoUtils(rsa, ecdsa);
                token.RsaSignature   = cryptoUtils.SignDataRSA(token.GetHeaderBytes().ToArray());
                token.EcdsaSignature = cryptoUtils.SignDataECDSA(token.GetHeaderBytes().ToArray());

                byte[] msgBytes = token.GetRegistrationTokenBytes(rsa, ecdsa);

                Transaction sendTx      = new Transaction();
                Money       outputValue = new Money(0.01m, MoneyUnit.BTC);
                Money       feeValue    = new Money(0.01m, MoneyUnit.BTC);

                byte[] bytes = Encoding.UTF8.GetBytes("BREEZE_REGISTRATION_MARKER");
                sendTx.Outputs.Add(new TxOut()
                {
                    Value        = outputValue,
                    ScriptPubKey = TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes)
                });

                foreach (PubKey pubKey in BlockChainDataConversions.BytesToPubKeys(msgBytes))
                {
                    TxOut destTxOut = new TxOut()
                    {
                        Value        = outputValue,
                        ScriptPubKey = pubKey.ScriptPubKey
                    };

                    sendTx.Outputs.Add(destTxOut);
                }

                var wth1 = node1.FullNode.NodeService <IWalletTransactionHandler>() as WalletTransactionHandler;

                List <Recipient> recipients = new List <Recipient>();

                foreach (TxOut txOut in sendTx.Outputs)
                {
                    recipients.Add(new Recipient()
                    {
                        Amount = txOut.Value, ScriptPubKey = txOut.ScriptPubKey
                    });
                }

                var walletReference = new WalletAccountReference()
                {
                    // Default to the first wallet & first account
                    AccountName = wm1.Wallets.First().GetAccountsByCoinType((CoinType)node1.FullNode.Network.Consensus.CoinType).First().Name,
                    WalletName  = wm1.Wallets.First().Name
                };

                var context = new TransactionBuildContext(
                    walletReference,
                    recipients,
                    "Registration1")
                {
                    MinConfirmations = 0,
                    OverrideFeeRate  = new FeeRate(new Money(0.001m, MoneyUnit.BTC)),
                    Shuffle          = false,
                    Sign             = true
                };

                var tx = wth1.BuildTransaction(context);
                node1.AddToStratisPosMempool(tx);

                TestHelper.WaitLoop(() => rpc1.GetRawMempool().Length > 0);

                node1.GenerateStratisWithMiner(1);

                Thread.Sleep(10000);

                TestHelper.WaitLoop(() => rpc1.GetBestBlockHash() == rpc2.GetBestBlockHash());

                Console.WriteLine("Checking if registration was received...");

                var rm2 = node2.FullNode.NodeService <RegistrationManager>();
                var rs2 = rm2.GetRegistrationStore();

                foreach (var record in rs2.GetAll())
                {
                    Console.WriteLine("Received registration: " + record.RecordTxId);
                }

                Console.WriteLine(rs2.GetAll().Count);

                Thread.Sleep(10000);

                node1.Kill();
                node2.Kill();
            }
        }