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

Wallet can now recover on startup #169

Merged
merged 1 commit into from
Jun 20, 2017
Merged
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
35 changes: 35 additions & 0 deletions Stratis.Bitcoin.IntegrationTests/WalletTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,5 +258,40 @@ public void WalletCanCatchupWithBestChain()
TestHelper.WaitLoop(() => TestHelper.IsNodeSynced(stratisminer));
}
}

[Fact]
public void WalletCanRecoverOnStartup()
{
using (NodeBuilder builder = NodeBuilder.Create())
{
var stratisNodeSync = builder.CreateStratisNode();
builder.StartAll();
stratisNodeSync.NotInIBD();

// get a key from the wallet
var mnemonic = stratisNodeSync.FullNode.WalletManager.CreateWallet("123456", "mywallet");
Assert.Equal(12, mnemonic.Words.Length);
var addr = stratisNodeSync.FullNode.WalletManager.GetUnusedAddress("mywallet", "account 0");
var key = stratisNodeSync.FullNode.WalletManager.GetKeyForAddress("123456", addr).PrivateKey;

stratisNodeSync.SetDummyMinerSecret(key.GetBitcoinSecret(stratisNodeSync.FullNode.Network));
stratisNodeSync.GenerateStratis(10);
TestHelper.WaitLoop(() => TestHelper.IsNodeSynced(stratisNodeSync));

// set the tip of best chain some blocks in the apst
stratisNodeSync.FullNode.Chain.SetTip(stratisNodeSync.FullNode.Chain.GetBlock(stratisNodeSync.FullNode.Chain.Height - 5));

// stop the node it will persist the chain with the reset tip
stratisNodeSync.FullNode.Stop();

var newNodeInstance = builder.CloneStratisNode(stratisNodeSync);

// load the node, this should hit the block store recover code
newNodeInstance.Start();

// check that store recovered to be the same as the best chain.
Assert.Equal(newNodeInstance.FullNode.Chain.Tip.HashBlock, newNodeInstance.FullNode.WalletManager.WalletTipHash);
}
}
}
}
6 changes: 6 additions & 0 deletions Stratis.Bitcoin/Wallet/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ public class Wallet
[JsonConverter(typeof(ByteArrayConverter))]
public byte[] ChainCode { get; set; }

/// <summary>
/// Gets or sets the merkle path.
/// </summary>
[JsonProperty(PropertyName = "blockLocator", ItemConverterType = typeof(UInt256JsonConverter))]
public ICollection<uint256> BlockLocator { get; set; }

/// <summary>
/// The network this wallet is for.
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions Stratis.Bitcoin/Wallet/WalletManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,10 @@ public void UpdateLastBlockSyncedHeight(ChainedBlock chainedBlock)
/// <inheritdoc />
public void UpdateLastBlockSyncedHeight(Wallet wallet, ChainedBlock chainedBlock)
{
// the block locator will help when the wallet
// needs to rewind this will be used to find the fork
wallet.BlockLocator = chainedBlock.GetLocator().Blocks;

// update the wallets with the last processed block height
foreach (var accountRoot in wallet.AccountsRoot.Where(a => a.CoinType == this.coinType))
{
Expand Down
46 changes: 33 additions & 13 deletions Stratis.Bitcoin/Wallet/WalletSyncManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,22 @@ public virtual Task Initialize()

this.walletTip = this.chain.GetBlock(this.walletManager.WalletTipHash);
if (this.walletTip == null)
throw new WalletException("Wallet tip was not found in the best chain, rescan the wallet");

// offline reorg is extreamly reare it will
// only happen if the node crashes during a reorg

//var blockstoremove = new List<uint256>();
//var locators = this.walletManager.Wallets.First().BlockLocator;
//BlockLocator blockLocator = new BlockLocator { Blocks = locators.ToList() };
//var fork = this.chain.FindFork(blockLocator);
//this.walletManager.RemoveBlocks(fork);
{
// the wallet tip was not found int he main chain
// this can happen if the node crashes unexpecdidely
// reo reconver we need to find the first common fork
// with the best chain, as the wallet does not have a
// list of chain headers we use a BlockLocator and persist
// tha tin the wallet, the block locator will help finding
// a common form and bringing the wallet back to a good
// state (behind the best chain)

var locators = this.walletManager.Wallets.First().BlockLocator;
BlockLocator blockLocator = new BlockLocator { Blocks = locators.ToList() };
var fork = this.chain.FindFork(blockLocator);
this.walletManager.RemoveBlocks(fork);
this.walletManager.WalletTipHash = fork.HashBlock;
}

return Task.CompletedTask;
}
Expand Down Expand Up @@ -100,9 +106,23 @@ public virtual void ProcessBlock(Block block)
// will stop til the wallet is up to date.

next = this.chain.GetBlock(next.Height +1);
var nextblock = this.blockStoreCache.GetBlockAsync(next.HashBlock).GetAwaiter().GetResult();
if(nextblock == null)
return; // temporary to allow wallet ot recover when store is behind
Block nextblock = null;
while (true) //replace with cancelation token
{
nextblock = this.blockStoreCache.GetBlockAsync(next.HashBlock).GetAwaiter().GetResult();
if (nextblock == null)
{
// really ugly hack to let store catch up
// this will block the entire consensus pulling
this.logger.LogInformation("Wallet is behind the best chain and the next block is not found in store");
Thread.Sleep(100);
continue;
}

break;
}

this.walletTip = next;
this.walletManager.ProcessBlock(nextblock, next);
}
}
Expand Down