Example #1
0
        static void Main(string[] args)
        {
            //Specify bitcoin network
            Network network = Network.TestNet;

            KeyService keyService = new KeyService(network);

            //Generates a brand new bitcoin address and private keys.
            MultiSigAddress address2 = keyService.GenerateNewAddress(2, 3);

            //Returns existing bitcoin address using existing private keys.
            MultiSigAddress address1 = keyService.GetExistingAddress();

            //Create me some private keys for fun
            List <BitcoinSecret> keys = keyService.CreatePrivateKeys(3);


            ///////////////////////////////////////////
            //Ignore this stuff. Just testing out some ideas.

            //_bitcoinSession = new BitcoinSession(_network);

            //BitCoinTransactionService bcService = new BitCoinTransactionService(_bitCoinSession);
            //bcService.GetTransactionInfo("1f5dd12cfc57647bc2fe12832582fda1e46eb6cb1ea1b6cce35d1817ac06c691");


            Console.ReadLine();
        }
        public void StoringDepositsWhenTargetIsMultisigIsIgnoredIffOurMultisig()
        {
            var dataFolder = new DataFolder(TestBase.CreateTestDir(this));

            this.Init(dataFolder);
            this.AddFunding();
            this.AppendBlocks(WithdrawalTransactionBuilder.MinConfirmations);

            MultiSigAddress multiSigAddress = this.wallet.MultiSigAddress;

            using (ICrossChainTransferStore crossChainTransferStore = this.CreateStore())
            {
                crossChainTransferStore.Initialize();
                crossChainTransferStore.Start();

                TestBase.WaitLoopMessage(() => (this.ChainIndexer.Tip.Height == crossChainTransferStore.TipHashAndHeight.Height, $"ChainIndexer.Height:{this.ChainIndexer.Tip.Height} Store.TipHashHeight:{crossChainTransferStore.TipHashAndHeight.Height}"));
                Assert.Equal(this.ChainIndexer.Tip.HashBlock, crossChainTransferStore.TipHashAndHeight.HashBlock);

                // Forwarding money already in the multisig address to the multisig address is ignored.
                BitcoinAddress address1 = multiSigAddress.RedeemScript.Hash.GetAddress(this.network);
                BitcoinAddress address2 = new Script("").Hash.GetAddress(this.network);

                var deposit1 = new Deposit(0, new Money(160m, MoneyUnit.BTC), address1.ToString(), crossChainTransferStore.NextMatureDepositHeight, 1);
                var deposit2 = new Deposit(1, new Money(160m, MoneyUnit.BTC), address2.ToString(), crossChainTransferStore.NextMatureDepositHeight, 1);

                MaturedBlockDepositsModel[] blockDeposits = new[] { new MaturedBlockDepositsModel(
                                                                        new MaturedBlockInfoModel()
                    {
                        BlockHash   = 1,
                        BlockHeight = crossChainTransferStore.NextMatureDepositHeight
                    },
                                                                        new[] { deposit1, deposit2 }) };

                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposits).GetAwaiter().GetResult();

                Transaction[] partialTransactions   = crossChainTransferStore.GetTransfersByStatus(new[] { CrossChainTransferStatus.Partial }).Select(x => x.PartialTransaction).ToArray();
                Transaction[] suspendedTransactions = crossChainTransferStore.GetTransfersByStatus(new [] { CrossChainTransferStatus.Suspended }).Select(x => x.PartialTransaction).ToArray();

                // Only the deposit going towards a different multisig address is accepted. The other is ignored.
                Assert.Single(partialTransactions);
                Assert.Empty(suspendedTransactions);

                IWithdrawal withdrawal = this.withdrawalExtractor.ExtractWithdrawalFromTransaction(partialTransactions[0], null, 1);
                Assert.Equal((uint256)1, withdrawal.DepositId);
            }
        }
Example #3
0
        internal static void EnsureDefaultMultisigAddress(string passphrase, X1WalletFile x1WalletFile)
        {
            //if (x1WalletFile.ColdStakingAddresses.Count > 0)
            //    return;

            var decryptedSeed = VCL.DecryptWithPassphrase(passphrase, x1WalletFile.HdSeed);

            KeyMaterial myKeyMaterial = KeyHelper.CreateHdKeyMaterial(decryptedSeed, passphrase, C.Network.Consensus.CoinType, AddressType.MultiSig, C.External, 0);
            PubKey      myPubKey      = myKeyMaterial.GetKey(passphrase).PubKey.Compress();

            // other Key
            KeyMaterial otherKeyMaterial = KeyHelper.CreateHdKeyMaterial(decryptedSeed, passphrase, C.Network.Consensus.CoinType, AddressType.MultiSig, C.External, 1);
            var         otherPubKey      = otherKeyMaterial.GetKey(passphrase).PubKey.Compress();

            // The redeem script looks like:
            // 1 03fad6426522dbda5c5a9f8cab24a54ccc374517ad8790bf7e5a14308afc1bf77b 0340ecf2e20978075a49369e35269ecf0651d2f48061ebbf918f3eb1964051f65c 2 OP_CHECKMULTISIG
            Script redeemScript = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(1, myPubKey, otherPubKey);

            // The address looks like:
            // odx1qvar8r29r8llzj53q5utmcewpju59263h38250ws33lp2q45lmalqg5lmdd
            string bech32ScriptAddress = redeemScript.WitHash.GetAddress(C.Network).ToString();

            // In P2SH payments, we refer to the hash of the Redeem Script as the scriptPubKey. It looks like:
            // 0 674671a8a33ffe295220a717bc65c19728556a3789d547ba118fc2a0569fdf7e
            Script scriptPubKey = redeemScript.WitHash.ScriptPubKey;
            var    scp          = scriptPubKey.ToString();


            var multiSigAddress = new MultiSigAddress
            {
                OwnKey             = myKeyMaterial, Address = bech32ScriptAddress, AddressType = AddressType.MultiSig,
                Label              = "Default 1-of-2 MultiSig", MaxSignatures = 2,
                LastSeenHeight     = null,
                SignaturesRequired = 1,
                RedeemScriptHex    = redeemScript.ToBytes().ToHexString(),
                ScriptPubKeyHex    = scriptPubKey.ToBytes().ToHexString(),
                OtherPublicKeys    = new System.Collections.Generic.Dictionary <string, string>(),
            };

            multiSigAddress.OtherPublicKeys.Add(otherPubKey.ToBytes().ToHexString(), "Bob");



            x1WalletFile.MultiSigAddresses[multiSigAddress.Address] = multiSigAddress;
        }
        public void StoringDepositsWhenWalletBalanceSufficientSucceedsWithDeterministicTransactions()
        {
            var dataFolder = new DataFolder(TestBase.CreateTestDir(this));

            this.Init(dataFolder);
            this.AddFunding();
            this.AppendBlocks(WithdrawalTransactionBuilder.MinConfirmations);

            MultiSigAddress multiSigAddress = this.wallet.MultiSigAddress;

            using (ICrossChainTransferStore crossChainTransferStore = this.CreateStore())
            {
                crossChainTransferStore.Initialize();
                crossChainTransferStore.Start();

                TestBase.WaitLoopMessage(() => (this.ChainIndexer.Tip.Height == crossChainTransferStore.TipHashAndHeight.Height, $"ChainIndexer.Height:{this.ChainIndexer.Tip.Height} Store.TipHashHeight:{crossChainTransferStore.TipHashAndHeight.Height}"));
                Assert.Equal(this.ChainIndexer.Tip.HashBlock, crossChainTransferStore.TipHashAndHeight.HashBlock);

                BitcoinAddress address1 = (new Key()).PubKey.Hash.GetAddress(this.network);
                BitcoinAddress address2 = (new Key()).PubKey.Hash.GetAddress(this.network);

                var deposit1 = new Deposit(0, new Money(160m, MoneyUnit.BTC), address1.ToString(), crossChainTransferStore.NextMatureDepositHeight, 1);
                var deposit2 = new Deposit(1, new Money(60m, MoneyUnit.BTC), address2.ToString(), crossChainTransferStore.NextMatureDepositHeight, 1);

                MaturedBlockDepositsModel[] blockDeposits = new[] { new MaturedBlockDepositsModel(
                                                                        new MaturedBlockInfoModel()
                    {
                        BlockHash   = 1,
                        BlockHeight = crossChainTransferStore.NextMatureDepositHeight
                    },
                                                                        new[] { deposit1, deposit2 }) };

                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposits).GetAwaiter().GetResult();

                Transaction[] transactions = crossChainTransferStore.GetTransfersByStatus(new [] { CrossChainTransferStatus.Partial }).Select(x => x.PartialTransaction).ToArray();

                Assert.Equal(2, transactions.Length);

                // Transactions[0] inputs. Ordered deterministically, roughly a mixture of time and canonical ordering.
                Assert.Equal(2, transactions[0].Inputs.Count);
                Assert.Equal(this.fundingTransactions[0].GetHash(), transactions[0].Inputs[0].PrevOut.Hash);
                Assert.Equal((uint)0, transactions[0].Inputs[0].PrevOut.N);
                Assert.Equal(this.fundingTransactions[0].GetHash(), transactions[0].Inputs[1].PrevOut.Hash);
                Assert.Equal((uint)1, transactions[0].Inputs[1].PrevOut.N);

                // Transaction[0] outputs.
                Assert.Equal(3, transactions[0].Outputs.Count);

                // Transaction[0] output value - change.
                Assert.Equal(new Money(10m, MoneyUnit.BTC), transactions[0].Outputs[0].Value);
                Assert.Equal(multiSigAddress.ScriptPubKey, transactions[0].Outputs[0].ScriptPubKey);

                // Transaction[0] output value - recipient 1.
                Assert.Equal(new Money(159.99m, MoneyUnit.BTC), transactions[0].Outputs[1].Value);
                Assert.Equal(address1.ScriptPubKey, transactions[0].Outputs[1].ScriptPubKey);

                // Transaction[0] output value - op_return.
                Assert.Equal(new Money(0m, MoneyUnit.BTC), transactions[0].Outputs[2].Value);
                new OpReturnDataReader(this.loggerFactory, this.federatedPegOptions).TryGetTransactionId(transactions[0], out string actualDepositId);
                Assert.Equal(deposit1.Id.ToString(), actualDepositId);

                // Transactions[1] inputs.
                Assert.Single(transactions[1].Inputs);
                Assert.Equal(this.fundingTransactions[1].GetHash(), transactions[1].Inputs[0].PrevOut.Hash);
                Assert.Equal((uint)0, transactions[1].Inputs[0].PrevOut.N);

                // Transaction[1] outputs.
                Assert.Equal(3, transactions[1].Outputs.Count);

                // Transaction[1] output value - change.
                Assert.Equal(new Money(10m, MoneyUnit.BTC), transactions[1].Outputs[0].Value);
                Assert.Equal(multiSigAddress.ScriptPubKey, transactions[1].Outputs[0].ScriptPubKey);

                // Transaction[1] output value - recipient 2.
                Assert.Equal(new Money(59.99m, MoneyUnit.BTC), transactions[1].Outputs[1].Value);
                Assert.Equal(address2.ScriptPubKey, transactions[1].Outputs[1].ScriptPubKey);

                // Transaction[1] output value - op_return.
                Assert.Equal(new Money(0m, MoneyUnit.BTC), transactions[1].Outputs[2].Value);
                new OpReturnDataReader(this.loggerFactory, this.federatedPegOptions).TryGetTransactionId(transactions[1], out string actualDepositId2);
                Assert.Equal(deposit2.Id.ToString(), actualDepositId2);

                ICrossChainTransfer[] transfers = crossChainTransferStore.GetAsync(new uint256[] { 0, 1 }).GetAwaiter().GetResult().ToArray();

                Assert.Equal(2, transfers.Length);
                Assert.Equal(CrossChainTransferStatus.Partial, transfers[0].Status);
                Assert.Equal(deposit1.Amount, new Money(transfers[0].DepositAmount));
                Assert.Equal(address1.ScriptPubKey, transfers[0].DepositTargetAddress);
                Assert.Equal(CrossChainTransferStatus.Partial, transfers[1].Status);
                Assert.Equal(deposit2.Amount, new Money(transfers[1].DepositAmount));
                Assert.Equal(address2.ScriptPubKey, transfers[1].DepositTargetAddress);
            }
        }
        public void StoringDepositsAfterRewindIsPrecededByClearingInvalidTransientsAndSettingNextMatureDepositHeightCorrectly()
        {
            var dataFolder = new DataFolder(TestBase.CreateTestDir(this));

            this.Init(dataFolder);

            // Creates two consecutive blocks of funding transactions with 100 coins each.
            (Transaction fundingTransaction1, ChainedHeader fundingBlock1) = AddFundingTransaction(new Money[] { Money.COIN * 100 });
            (Transaction fundingTransaction2, ChainedHeader fundingBlock2) = AddFundingTransaction(new Money[] { Money.COIN * 100 });

            this.AppendBlocks(WithdrawalTransactionBuilder.MinConfirmations);

            MultiSigAddress multiSigAddress = this.wallet.MultiSigAddress;

            using (ICrossChainTransferStore crossChainTransferStore = this.CreateStore())
            {
                crossChainTransferStore.Initialize();
                crossChainTransferStore.Start();

                TestBase.WaitLoopMessage(() => (this.ChainIndexer.Tip.Height == crossChainTransferStore.TipHashAndHeight.Height, $"ChainIndexer.Height:{this.ChainIndexer.Tip.Height} Store.TipHashHeight:{crossChainTransferStore.TipHashAndHeight.Height}"));
                Assert.Equal(this.ChainIndexer.Tip.HashBlock, crossChainTransferStore.TipHashAndHeight.HashBlock);

                BitcoinAddress address1 = (new Key()).PubKey.Hash.GetAddress(this.network);
                BitcoinAddress address2 = (new Key()).PubKey.Hash.GetAddress(this.network);

                // First deposit.
                var deposit1 = new Deposit(1, new Money(100m, MoneyUnit.BTC), address1.ToString(), crossChainTransferStore.NextMatureDepositHeight, 1);

                MaturedBlockDepositsModel[] blockDeposit1 = new[] { new MaturedBlockDepositsModel(
                                                                        new MaturedBlockInfoModel()
                    {
                        BlockHash   = 1,
                        BlockHeight = crossChainTransferStore.NextMatureDepositHeight
                    },
                                                                        new[] { deposit1 }) };

                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposit1).GetAwaiter().GetResult();

                ICrossChainTransfer transfer1 = crossChainTransferStore.GetAsync(new[] { deposit1.Id }).GetAwaiter().GetResult().FirstOrDefault();
                Assert.Equal(CrossChainTransferStatus.Partial, transfer1?.Status);

                // Second deposit.
                var deposit2 = new Deposit(2, new Money(100m, MoneyUnit.BTC), address2.ToString(), crossChainTransferStore.NextMatureDepositHeight, 2);

                MaturedBlockDepositsModel[] blockDeposit2 = new[] { new MaturedBlockDepositsModel(
                                                                        new MaturedBlockInfoModel()
                    {
                        BlockHash   = 2,
                        BlockHeight = crossChainTransferStore.NextMatureDepositHeight
                    },
                                                                        new[] { deposit2 }) };

                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposit2).GetAwaiter().GetResult();

                ICrossChainTransfer transfer2 = crossChainTransferStore.GetAsync(new[] { deposit2.Id }).GetAwaiter().GetResult().FirstOrDefault();
                Assert.Equal(CrossChainTransferStatus.Partial, transfer2?.Status);

                // Both partial transactions have been created. Now rewind the wallet.
                this.ChainIndexer.SetTip(fundingBlock1);
                this.federationWalletSyncManager.ProcessBlock(fundingBlock1.Block);

                TestBase.WaitLoopMessage(() => (this.ChainIndexer.Tip.Height == this.federationWalletSyncManager.WalletTip.Height, $"ChainIndexer.Height:{this.ChainIndexer.Tip.Height} SyncManager.TipHashHeight:{this.federationWalletSyncManager.WalletTip.Height}"));

                // Synchronize the store using a dummy get.
                crossChainTransferStore.GetAsync(new uint256[] { }).GetAwaiter().GetResult();

                // See if the NextMatureDepositHeight was rewound for the replay of deposit 2.
                Assert.Equal(deposit2.BlockNumber, crossChainTransferStore.NextMatureDepositHeight);

                // That's great. Now let's redo deposit 2 which had its funding wiped out.
                (fundingTransaction2, fundingBlock2) = AddFundingTransaction(new Money[] { Money.COIN * 100 });

                // Ensure that the new funds are mature.
                this.AppendBlocks(WithdrawalTransactionBuilder.MinConfirmations);

                // Recreate the second deposit.
                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposit2).GetAwaiter().GetResult();

                // Check that its status is partial.
                transfer2 = crossChainTransferStore.GetAsync(new[] { deposit2.Id }).GetAwaiter().GetResult().FirstOrDefault();
                Assert.Equal(CrossChainTransferStatus.Partial, transfer2?.Status);

                Assert.Equal(2, this.wallet.MultiSigAddress.Transactions.Count);
            }
        }
        public void StoringDepositsWhenWalletBalanceInSufficientSucceedsWithSuspendStatus()
        {
            var dataFolder = new DataFolder(TestBase.CreateTestDir(this));

            this.Init(dataFolder);
            this.AddFunding();
            this.AppendBlocks(WithdrawalTransactionBuilder.MinConfirmations);

            MultiSigAddress multiSigAddress = this.wallet.MultiSigAddress;

            using (ICrossChainTransferStore crossChainTransferStore = this.CreateStore())
            {
                crossChainTransferStore.Initialize();
                crossChainTransferStore.Start();

                TestBase.WaitLoopMessage(() => (this.ChainIndexer.Tip.Height == crossChainTransferStore.TipHashAndHeight.Height, $"ChainIndexer.Height:{this.ChainIndexer.Tip.Height} Store.TipHashHeight:{crossChainTransferStore.TipHashAndHeight.Height}"));
                TestBase.WaitLoop(() => this.wallet.LastBlockSyncedHeight == this.ChainIndexer.Tip.Height);
                Assert.Equal(this.ChainIndexer.Tip.HashBlock, crossChainTransferStore.TipHashAndHeight.HashBlock);

                uint256 txId1     = 0;
                uint256 txId2     = 1;
                uint256 blockHash = 2;

                BitcoinAddress address1 = (new Key()).PubKey.Hash.GetAddress(this.network);
                BitcoinAddress address2 = (new Key()).PubKey.Hash.GetAddress(this.network);

                var deposit1 = new Deposit(txId1, new Money(160m, MoneyUnit.BTC), address1.ToString(), crossChainTransferStore.NextMatureDepositHeight, blockHash);
                var deposit2 = new Deposit(txId2, new Money(100m, MoneyUnit.BTC), address2.ToString(), crossChainTransferStore.NextMatureDepositHeight, blockHash);

                MaturedBlockDepositsModel[] blockDeposits = new[] { new MaturedBlockDepositsModel(
                                                                        new MaturedBlockInfoModel()
                    {
                        BlockHash   = blockHash,
                        BlockHeight = crossChainTransferStore.NextMatureDepositHeight
                    },
                                                                        new[] { deposit1, deposit2 }) };

                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposits).GetAwaiter().GetResult();

                ICrossChainTransfer[] transfers = crossChainTransferStore.GetAsync(new uint256[] { txId1, txId2 }).GetAwaiter().GetResult().ToArray();

                Transaction[] transactions = transfers.Select(t => t.PartialTransaction).ToArray();

                Assert.Equal(2, transactions.Length);

                // Transactions[0] inputs. Ordered deterministically.
                Assert.Equal(2, transactions[0].Inputs.Count);
                Assert.Equal(this.fundingTransactions[0].GetHash(), transactions[0].Inputs[0].PrevOut.Hash);
                Assert.Equal((uint)0, transactions[0].Inputs[0].PrevOut.N);
                Assert.Equal(this.fundingTransactions[0].GetHash(), transactions[0].Inputs[1].PrevOut.Hash);
                Assert.Equal((uint)1, transactions[0].Inputs[1].PrevOut.N);

                // Transaction[0] outputs.
                Assert.Equal(3, transactions[0].Outputs.Count);

                // Transaction[0] output value - change.
                Assert.Equal(new Money(10m, MoneyUnit.BTC), transactions[0].Outputs[0].Value);
                Assert.Equal(multiSigAddress.ScriptPubKey, transactions[0].Outputs[0].ScriptPubKey);

                // Transaction[0] output value - recipient 1, but minus 0.01 for the tx fee.
                Assert.Equal(new Money(159.99m, MoneyUnit.BTC), transactions[0].Outputs[1].Value);
                Assert.Equal(address1.ScriptPubKey, transactions[0].Outputs[1].ScriptPubKey);

                // Transaction[0] output value - op_return.
                Assert.Equal(new Money(0m, MoneyUnit.BTC), transactions[0].Outputs[2].Value);
                new OpReturnDataReader(this.loggerFactory, this.federatedPegOptions).TryGetTransactionId(transactions[0], out string actualDepositId);
                Assert.Equal(deposit1.Id.ToString(), actualDepositId);

                Assert.Null(transactions[1]);

                Assert.Equal(2, transfers.Length);
                Assert.Equal(CrossChainTransferStatus.Partial, transfers[0].Status);
                Assert.Equal(deposit1.Amount, new Money(transfers[0].DepositAmount));
                Assert.Equal(address1.ScriptPubKey, transfers[0].DepositTargetAddress);
                Assert.Equal(CrossChainTransferStatus.Suspended, transfers[1].Status);

                // Add more funds and resubmit the deposits.
                AddFundingTransaction(new Money[] { Money.COIN * 1000 });
                TestBase.WaitLoop(() => this.wallet.LastBlockSyncedHeight == this.ChainIndexer.Tip.Height);
                crossChainTransferStore.RecordLatestMatureDepositsAsync(blockDeposits).GetAwaiter().GetResult();
                transfers    = crossChainTransferStore.GetAsync(new uint256[] { txId1, txId2 }).GetAwaiter().GetResult().ToArray();
                transactions = transfers.Select(t => t.PartialTransaction).ToArray();

                // Transactions[1] inputs.
                Assert.Equal(2, transactions[1].Inputs.Count);
                Assert.Equal(this.fundingTransactions[1].GetHash(), transactions[1].Inputs[0].PrevOut.Hash);
                Assert.Equal((uint)0, transactions[1].Inputs[0].PrevOut.N);

                // Transaction[1] outputs.
                Assert.Equal(3, transactions[1].Outputs.Count);

                // Transaction[1] output value - change.
                Assert.Equal(new Money(970m, MoneyUnit.BTC), transactions[1].Outputs[0].Value);
                Assert.Equal(multiSigAddress.ScriptPubKey, transactions[1].Outputs[0].ScriptPubKey);

                // Transaction[1] output value - recipient 2, but minus 0.01 for the tx fee.
                Assert.Equal(new Money(99.99m, MoneyUnit.BTC), transactions[1].Outputs[1].Value);
                Assert.Equal(address2.ScriptPubKey, transactions[1].Outputs[1].ScriptPubKey);

                // Transaction[1] output value - op_return.
                Assert.Equal(new Money(0m, MoneyUnit.BTC), transactions[1].Outputs[2].Value);
                new OpReturnDataReader(this.loggerFactory, this.federatedPegOptions).TryGetTransactionId(transactions[1], out string actualDepositId2);
                Assert.Equal(deposit2.Id.ToString(), actualDepositId2);

                Assert.Equal(2, transfers.Length);
                Assert.Equal(CrossChainTransferStatus.Partial, transfers[1].Status);
                Assert.Equal(deposit2.Amount, new Money(transfers[1].DepositAmount));
                Assert.Equal(address2.ScriptPubKey, transfers[1].DepositTargetAddress);

                (Money confirmed, Money unconfirmed)spendable = this.wallet.GetSpendableAmount();

                Assert.Equal(new Money(980m, MoneyUnit.BTC), spendable.unconfirmed);
            }
        }