Skip to content
This repository has been archived by the owner on Aug 16, 2021. It is now read-only.

[3.0.6.0] Filter coldstaking transactions #4193

Open
wants to merge 8 commits into
base: release/3.0.6.0-rc
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ private Transaction AddSpendableTransactionToWallet(Wallet.Wallet wallet)
Index = 0,
IsCoinBase = false,
IsCoinStake = false,
IsColdCoinStake = false,
IsPropagated = true,
BlockHash = this.Network.GenesisHash,
ScriptPubKey = address.ScriptPubKey
Expand Down Expand Up @@ -546,6 +547,61 @@ public void SetupColdStakingWithHotWalletSucceeds()
Assert.True(this.mempoolManager.Validator.AcceptToMemoryPool(state, transaction).GetAwaiter().GetResult(), "Transaction failed mempool validation.");
}

[Fact]
public void VerifyThatColdStakeTransactionCanBeFiltered()
{
this.Initialize();
this.CreateMempoolManager();

this.coldStakingManager.CreateWallet(walletPassword, walletName1, walletPassphrase, new Mnemonic(walletMnemonic1));

Wallet.Wallet wallet1 = this.coldStakingManager.GetWallet(walletName1);

// This will add a normal account to our wallet.
Transaction trx1 = this.AddSpendableTransactionToWallet(wallet1);

// This will add a secondary account to our wallet.
Transaction trx2 = this.AddSpendableColdstakingTransactionToWallet(wallet1);

// This will add a cold staking transaction to the secondary normal account address. This simulates activation of cold staking onto any normal address.
Transaction trx3 = this.AddSpendableColdstakingTransactionToNormalWallet(wallet1);

var accounts = wallet1.GetAccounts(Wallet.Wallet.AllAccounts).ToArray();

// We should have 2 accounts in our wallet.
Assert.Equal(2, accounts.Length);

// But not if we use default or specify to only return normal accounts.
Assert.Single(wallet1.GetAccounts().ToArray()); // Defaults to NormalAccounts
Assert.Single(wallet1.GetAccounts(Wallet.Wallet.NormalAccounts).ToArray());

// Verify that we actually have a cold staking activation UTXO in the wallet of 202 coins.
// This should normally not be returned by the GetAllTransactions, and should never be included in balance calculations.
Assert.True(accounts[0].ExternalAddresses.ToArray()[1].Transactions.ToArray()[0].IsColdCoinStake);
Assert.Equal(new Money(202, MoneyUnit.BTC), accounts[0].ExternalAddresses.ToArray()[1].Transactions.ToArray()[0].Amount);

Assert.Single(wallet1.GetAllTransactions().ToArray()); // Default to NormalAccounts, should filter out cold staking (trx3) from normal wallet.
Assert.Single(wallet1.GetAllTransactions(accountFilter: Wallet.Wallet.NormalAccounts).ToArray());
Assert.Single(wallet1.GetAllSpendableTransactions(5, 0, Wallet.Wallet.NormalAccounts).ToArray()); // Default to NormalAccounts
Assert.Equal(2, wallet1.GetAllTransactions(accountFilter: Wallet.Wallet.AllAccounts).ToArray().Length);
Assert.Equal(2, wallet1.GetAllSpendableTransactions(5, 0, Wallet.Wallet.AllAccounts).ToArray().Length); // Specified AllAccounts, should include cold-staking transaction.

// Verify balance on normal account
var balance1 = accounts[0].GetBalances(true);
var balance2 = accounts[0].GetBalances(false);

Assert.Equal(new Money(101, MoneyUnit.BTC), balance1.ConfirmedAmount);
Assert.Equal(new Money(303, MoneyUnit.BTC), balance2.ConfirmedAmount);

// Verify balance on special account.
var balance3 = accounts[1].GetBalances(true);
var balance4 = accounts[1].GetBalances(false);

// The only transaction that exists in the cold staking account is itself a cold staking utxo. So if we exclude it we expect zero.
Assert.Equal(new Money(0, MoneyUnit.BTC), balance3.ConfirmedAmount);
Assert.Equal(new Money(101, MoneyUnit.BTC), balance4.ConfirmedAmount);
}

/// <summary>
/// Confirms that cold staking setup with the cold wallet will succeed if no issues (as per above test cases) are encountered.
/// </summary>
Expand Down Expand Up @@ -647,11 +703,13 @@ public void GetColdStakingInfoOnlyConfirmAccountExistenceOnceCreated()
private Transaction AddSpendableColdstakingTransactionToWallet(Wallet.Wallet wallet)
{
// Get first unused cold staking address.
this.coldStakingManager.GetOrCreateColdStakingAccount(wallet.Name, true, walletPassword);
HdAccount account = this.coldStakingManager.GetOrCreateColdStakingAccount(wallet.Name, true, walletPassword);
HdAddress address = this.coldStakingManager.GetFirstUnusedColdStakingAddress(wallet.Name, true);

TxDestination hotPubKey = BitcoinAddress.Create(hotWalletAddress1, wallet.Network).ScriptPubKey.GetDestination(wallet.Network);
TxDestination coldPubKey = BitcoinAddress.Create(coldWalletAddress2, wallet.Network).ScriptPubKey.GetDestination(wallet.Network);
// We can't use the hardcoded cold wallet address here, because we don't know for sure that this is the first account added to the wallet.
// If it is not, the derived addresses will be using a different index and will therefore all be different.
TxDestination coldPubKey = BitcoinAddress.Create(account.ExternalAddresses.First().Address, wallet.Network).ScriptPubKey.GetDestination(wallet.Network);

var scriptPubKey = new Script(OpcodeType.OP_DUP, OpcodeType.OP_HASH160, OpcodeType.OP_ROT, OpcodeType.OP_IF,
OpcodeType.OP_CHECKCOLDSTAKEVERIFY, Op.GetPushOp(hotPubKey.ToBytes()), OpcodeType.OP_ELSE, Op.GetPushOp(coldPubKey.ToBytes()),
Expand All @@ -670,6 +728,7 @@ private Transaction AddSpendableColdstakingTransactionToWallet(Wallet.Wallet wal
Index = 0,
IsCoinBase = false,
IsCoinStake = false,
IsColdCoinStake = true,
IsPropagated = true,
BlockHash = this.Network.GenesisHash,
ScriptPubKey = scriptPubKey
Expand Down Expand Up @@ -765,6 +824,45 @@ public void ColdStakingWithdrawalToColdWalletAccountThrowsWalletException()
Assert.StartsWith("You can't send the money to a cold staking cold wallet account.", error.Message);
}

/// <summary>
/// Adds a spendable cold staking transaction to a normal account, as oppose to dedicated special account.
/// </summary>
/// <param name="wallet">Wallet to add the transaction to.</param>
/// <returns>The spendable transaction that was added to the wallet.</returns>
private Transaction AddSpendableColdstakingTransactionToNormalWallet(Wallet.Wallet wallet, bool script = false)
{
// This will always be added to the secondary address.
HdAddress address = wallet.GetAllAddresses().ToArray()[1];

var transaction = this.Network.CreateTransaction();

// Use the normal wallet address here.
TxDestination hotPubKey = BitcoinAddress.Create(address.Address, wallet.Network).ScriptPubKey.GetDestination(wallet.Network);
TxDestination coldPubKey = BitcoinAddress.Create(coldWalletAddress2, wallet.Network).ScriptPubKey.GetDestination(wallet.Network);

var scriptPubKey = new Script(OpcodeType.OP_DUP, OpcodeType.OP_HASH160, OpcodeType.OP_ROT, OpcodeType.OP_IF,
OpcodeType.OP_CHECKCOLDSTAKEVERIFY, Op.GetPushOp(hotPubKey.ToBytes()), OpcodeType.OP_ELSE, Op.GetPushOp(coldPubKey.ToBytes()),
OpcodeType.OP_ENDIF, OpcodeType.OP_EQUALVERIFY, OpcodeType.OP_CHECKSIG);

transaction.Outputs.Add(new TxOut(Money.Coins(202), script ? scriptPubKey.WitHash.ScriptPubKey : scriptPubKey));

address.Transactions.Add(new TransactionData()
{
Hex = transaction.ToHex(this.Network),
Amount = transaction.Outputs[0].Value,
Id = transaction.GetHash(),
BlockHeight = 0,
Index = 0,
IsCoinBase = false,
IsCoinStake = false,
IsColdCoinStake = true,
IsPropagated = true,
BlockHash = this.Network.GenesisHash,
ScriptPubKey = script ? scriptPubKey.WitHash.ScriptPubKey : scriptPubKey,
});

return transaction;
}

/// <summary>
/// Confirms that trying to withdraw money from a non-existent cold staking account will raise an error.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,22 +154,24 @@ public void ProcessTransactionWithValidColdStakingSetupLoadsTransactionsIntoWall
Assert.Equal(transaction.Outputs[0].ScriptPubKey, changeAddressResult.ScriptPubKey);

// Verify that the transaction has been recorded in the cold wallet.
Assert.Single(coldWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Transactions);
TransactionData destinationColdAddressResult = coldWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Transactions.ElementAt(0);
var coldAccounts = coldWallet.GetAccounts(Wallet.Wallet.AllAccounts);
Assert.Single(coldAccounts.ElementAt(0).ExternalAddresses.ElementAt(0).Transactions);
TransactionData destinationColdAddressResult = coldAccounts.ElementAt(0).ExternalAddresses.ElementAt(0).Transactions.ElementAt(0);
Assert.Equal(transaction.GetHash(), destinationColdAddressResult.Id);
Assert.Equal(transaction.Outputs[1].Value, destinationColdAddressResult.Amount);
Assert.Equal(transaction.Outputs[1].ScriptPubKey, destinationColdAddressResult.ScriptPubKey);

// Verify that the transaction has been recorded in the hot wallet.
Assert.Single(hotWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Transactions);
TransactionData destinationHotAddressResult = hotWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Transactions.ElementAt(0);
var hotAccounts = hotWallet.GetAccounts(Wallet.Wallet.AllAccounts);
Assert.Single(hotAccounts.ElementAt(0).ExternalAddresses.ElementAt(0).Transactions);
TransactionData destinationHotAddressResult = hotAccounts.ElementAt(0).ExternalAddresses.ElementAt(0).Transactions.ElementAt(0);
Assert.Equal(transaction.GetHash(), destinationHotAddressResult.Id);
Assert.Equal(transaction.Outputs[1].Value, destinationHotAddressResult.Amount);
Assert.Equal(transaction.Outputs[1].ScriptPubKey, destinationHotAddressResult.ScriptPubKey);

// Will spend from the cold stake address and send the change back to the same address.
Money balance = walletManager.GetSpendableTransactionsInAccount(new WalletAccountReference(coldWallet.Name, coldWalletAccount.Name), 0).Sum(x => x.Transaction.Amount);
var coldStakeAddress = coldWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0);
var coldStakeAddress = coldWallet.GetAccounts(Wallet.Wallet.AllAccounts).ElementAt(0).ExternalAddresses.ElementAt(0);
Transaction withdrawalTransaction = this.CreateColdStakingWithdrawalTransaction(coldWallet, "password", coldStakeAddress,
withdrawalPubKey, ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(destinationColdPubKey.Hash, destinationHotPubKey.Hash),
new Money(750), new Money(263));
Expand All @@ -186,16 +188,16 @@ public void ProcessTransactionWithValidColdStakingSetupLoadsTransactionsIntoWall
Assert.Equal(withdrawalTransaction.Outputs[1].ScriptPubKey, withdrawalAddressResult.ScriptPubKey);

// Verify that the transaction has been recorded in the cold wallet.
Assert.Equal(2, coldWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Transactions.Count);
TransactionData coldAddressResult = coldWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Transactions
Assert.Equal(2, coldWallet.GetAccounts(Wallet.Wallet.AllAccounts).ElementAt(0).ExternalAddresses.ElementAt(0).Transactions.Count);
TransactionData coldAddressResult = coldWallet.GetAccounts(Wallet.Wallet.AllAccounts).ElementAt(0).ExternalAddresses.ElementAt(0).Transactions
.Where(t => t.Id == withdrawalTransaction.GetHash()).First();
Assert.Equal(withdrawalTransaction.GetHash(), coldAddressResult.Id);
Assert.Equal(withdrawalTransaction.Outputs[0].Value, coldAddressResult.Amount);
Assert.Equal(withdrawalTransaction.Outputs[0].ScriptPubKey, coldAddressResult.ScriptPubKey);

// Verify that the transaction has been recorded in the hot wallet.
Assert.Equal(2, hotWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Transactions.Count);
TransactionData hotAddressResult = hotWallet.AccountsRoot.ElementAt(0).Accounts.ElementAt(0).ExternalAddresses.ElementAt(0).Transactions
Assert.Equal(2, hotWallet.GetAccounts(Wallet.Wallet.AllAccounts).ElementAt(0).ExternalAddresses.ElementAt(0).Transactions.Count);
TransactionData hotAddressResult = hotWallet.GetAccounts(Wallet.Wallet.AllAccounts).ElementAt(0).ExternalAddresses.ElementAt(0).Transactions
.Where(t => t.Id == withdrawalTransaction.GetHash()).First();
Assert.Equal(withdrawalTransaction.GetHash(), hotAddressResult.Id);
Assert.Equal(withdrawalTransaction.Outputs[0].Value, hotAddressResult.Amount);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected override IEnumerable<IClientEvent> GetMessages()
Addresses = this.includeAddressBalances
? account.GetCombinedAddresses().Select(address =>
{
(Money confirmedAmount, Money unConfirmedAmount) = address.GetBalances();
(Money confirmedAmount, Money unConfirmedAmount) = address.GetBalances(account.IsNormalAccount());
return new AddressModel
{
Address = address.Address,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2300,6 +2300,7 @@ public void RemoveAllTransactionsWithSyncEnabledSyncsAfterRemoval()

var walletManager = new Mock<IWalletManager>();
var walletSyncManager = new Mock<IWalletSyncManager>();
walletManager.Setup(manager => manager.GetWallet(It.IsAny<string>())).Returns(wallet);
walletManager.Setup(manager => manager.RemoveAllTransactions(walletName)).Returns(resultModel);
walletSyncManager.Setup(manager => manager.SyncFromHeight(It.IsAny<int>(), It.IsAny<string>()));
ChainIndexer chainIndexer = WalletTestsHelpers.GenerateChainWithHeight(3, this.Network);
Expand Down Expand Up @@ -2379,6 +2380,7 @@ public void RemoveTransactionsWithIdsRemovesAllTransactionsByIds()

var walletManager = new Mock<IWalletManager>();
var walletSyncManager = new Mock<IWalletSyncManager>();
walletManager.Setup(manager => manager.GetWallet(It.IsAny<string>())).Returns(wallet);
walletManager.Setup(manager => manager.RemoveTransactionsByIds(walletName, new[] { trxId1 })).Returns(resultModel);
walletSyncManager.Setup(manager => manager.SyncFromHeight(It.IsAny<int>(), It.IsAny<string>()));
ChainIndexer chainIndexer = WalletTestsHelpers.GenerateChainWithHeight(3, this.Network);
Expand Down
20 changes: 10 additions & 10 deletions src/Stratis.Bitcoin.Features.Wallet.Tests/WalletManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2534,8 +2534,8 @@ public void CheckWalletBalanceEstimationWithConfirmedTransactions()
firstAccount.ExternalAddresses.ElementAt(i).Transactions.Add(new TransactionData { Id = new uint256((ulong)i + 2), Amount = 10 });
}

Assert.Equal(0, firstAccount.GetBalances().ConfirmedAmount);
Assert.Equal(40, firstAccount.GetBalances().UnConfirmedAmount);
Assert.Equal(0, firstAccount.GetBalances(firstAccount.IsNormalAccount()).ConfirmedAmount);
Assert.Equal(40, firstAccount.GetBalances(firstAccount.IsNormalAccount()).UnConfirmedAmount);
}

[Fact]
Expand Down Expand Up @@ -2632,8 +2632,8 @@ public void CheckWalletBalanceEstimationWithUnConfirmedTransactions()
firstAccount.ExternalAddresses.ElementAt(i).Transactions.Add(new TransactionData { Id = 0, Index = i, Amount = 10, BlockHeight = 10, BlockHash = 10 });
}

Assert.Equal(40, firstAccount.GetBalances().ConfirmedAmount);
Assert.Equal(0, firstAccount.GetBalances().UnConfirmedAmount);
Assert.Equal(40, firstAccount.GetBalances(firstAccount.IsNormalAccount()).ConfirmedAmount);
Assert.Equal(0, firstAccount.GetBalances(firstAccount.IsNormalAccount()).UnConfirmedAmount);
}

[Fact]
Expand All @@ -2660,8 +2660,8 @@ public void CheckWalletBalanceEstimationWithSpentTransactions()
firstAccount.ExternalAddresses.ElementAt(i).Transactions.Add(new TransactionData { Id = 0, Index = i, Amount = 10, BlockHeight = 10, SpendingDetails = new SpendingDetails() });
}

Assert.Equal(0, firstAccount.GetBalances().ConfirmedAmount);
Assert.Equal(0, firstAccount.GetBalances().UnConfirmedAmount);
Assert.Equal(0, firstAccount.GetBalances(firstAccount.IsNormalAccount()).ConfirmedAmount);
Assert.Equal(0, firstAccount.GetBalances(firstAccount.IsNormalAccount()).UnConfirmedAmount);
}

[Fact]
Expand Down Expand Up @@ -2694,8 +2694,8 @@ public void CheckWalletBalanceEstimationWithSpentAndConfirmedTransactions()
firstAccount.ExternalAddresses.ElementAt(i).Transactions.Add(new TransactionData { Id = new uint256((ulong)i), Amount = 10, BlockHeight = 10, BlockHash = 10 });
}

Assert.Equal(40, firstAccount.GetBalances().ConfirmedAmount);
Assert.Equal(0, firstAccount.GetBalances().UnConfirmedAmount);
Assert.Equal(40, firstAccount.GetBalances(firstAccount.IsNormalAccount()).ConfirmedAmount);
Assert.Equal(0, firstAccount.GetBalances(firstAccount.IsNormalAccount()).UnConfirmedAmount);
}

[Fact]
Expand Down Expand Up @@ -2728,8 +2728,8 @@ public void CheckWalletBalanceEstimationWithSpentAndUnConfirmedTransactions()
firstAccount.ExternalAddresses.ElementAt(i).Transactions.Add(new TransactionData { Id = 0, Index = i, Amount = 10 });
}

Assert.Equal(0, firstAccount.GetBalances().ConfirmedAmount);
Assert.Equal(40, firstAccount.GetBalances().UnConfirmedAmount);
Assert.Equal(0, firstAccount.GetBalances(firstAccount.IsNormalAccount()).ConfirmedAmount);
Assert.Equal(40, firstAccount.GetBalances(firstAccount.IsNormalAccount()).UnConfirmedAmount);
}

// TODO: Investigate the relevance of this test and remove it or fix it.
Expand Down
9 changes: 6 additions & 3 deletions src/Stratis.Bitcoin.Features.Wallet/AccountRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,15 @@ public bool Remove(HdAccount account)

public IEnumerable<HdAccount> GetAccounts(Func<HdAccount, bool> accountFilter = null)
{
var accounts = (this.walletRepository == null) ? this.accounts : this.walletRepository.GetAccounts(this.AccountRoot.Wallet);
if (accountFilter == null)
accountFilter = Wallet.NormalAccounts;

var repoAccounts = (this.walletRepository == null) ? this.accounts : this.walletRepository.GetAccounts(this.AccountRoot.Wallet);

if (accountFilter != null)
accounts = accounts.Where(accountFilter);
repoAccounts = repoAccounts.Where(accountFilter);

foreach (HdAccount account in accounts)
foreach (HdAccount account in repoAccounts)
{
account.WalletAccounts = this;

Expand Down
Loading