-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
- In case of reorganized block the Bech32UtxoSet recovered to its previous state #48
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,67 @@ public class IndexBuilderService | |
private List<FilterModel> Index { get; } | ||
private AsyncLock IndexLock { get; } | ||
|
||
private Dictionary<OutPoint, Script> Bech32UtxoSet { get; } | ||
private Dictionary<OutPoint, Script> Bech32UtxoSet { get; } //Change to observable concurrent dictionary? https://www.nuget.org/packages/ParallelExtensionsExtras/ | ||
|
||
|
||
|
||
internal static class ActionHistoryHelper | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you make it non-static? While it'll be a singleton for now, later or during tests it could cause problems. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a very special class only for this special purpose. I do not want anybody to create more instances or make it visible from outside. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please make it. My concern is if
I guess you can make it private, too. If you don't want to write tests for it. I think tests are not needed. |
||
{ | ||
public enum Operation | ||
{ | ||
Add, | ||
Remove | ||
} | ||
private static readonly List<ActionItem> ActionHistory = new List<ActionItem>(); | ||
|
||
public class ActionItem | ||
{ | ||
public Operation Action { get; } | ||
public OutPoint OutPoint { get; } | ||
public Script Script { get; } | ||
|
||
public ActionItem(Operation action, OutPoint outPoint, Script script ) | ||
{ | ||
Action = action; | ||
OutPoint = outPoint; | ||
Script = script; | ||
} | ||
} | ||
|
||
public static void ClearActionHistory() | ||
{ | ||
ActionHistory.Clear(); | ||
} | ||
|
||
public static void StoreAction(ActionItem actionItem) | ||
{ | ||
ActionHistory.Add(actionItem); | ||
} | ||
public static void StoreAction(Operation action, OutPoint outpoint, Script script) | ||
{ | ||
StoreAction(new ActionItem(action, outpoint, script)); | ||
} | ||
|
||
public static void Rollback(Dictionary<OutPoint, Script> toRollBack) | ||
{ | ||
for (var i = ActionHistory.Count-1; i >= 0; i--) | ||
{ | ||
ActionItem act = ActionHistory[i]; | ||
switch (act.Action) | ||
{ | ||
case Operation.Add: | ||
toRollBack.Remove(act.OutPoint); | ||
break; | ||
case Operation.Remove: | ||
toRollBack.Add(act.OutPoint,act.Script); | ||
break; | ||
default: | ||
throw new ArgumentOutOfRangeException(); | ||
} | ||
} | ||
ActionHistory.Clear(); | ||
} | ||
} | ||
|
||
public Height StartingHeight // First possible bech32 transaction ever. | ||
{ | ||
|
@@ -146,7 +206,6 @@ public void Syncronize() | |
|
||
if (prevHash != null) | ||
{ | ||
// ToDo: IMPORTANT! The Bech32UtxoSet MUST be recovered to its previous state, too! | ||
if (prevHash != block.Header.HashPrevBlock) // reorg | ||
{ | ||
using (await IndexLock.LockAsync()) | ||
|
@@ -157,10 +216,13 @@ public void Syncronize() | |
// remove last line | ||
var lines = File.ReadAllLines(IndexFilePath); | ||
File.WriteAllLines(IndexFilePath, lines.Take(lines.Length - 1).ToArray()); | ||
continue; | ||
ActionHistoryHelper.Rollback(Bech32UtxoSet); //The Bech32UtxoSet MUST be recovered to its previous state | ||
continue; //skip the current block | ||
} | ||
} | ||
|
||
ActionHistoryHelper.ClearActionHistory(); //reset history. | ||
|
||
var scripts = new HashSet<Script>(); | ||
|
||
foreach (var tx in block.Transactions) | ||
|
@@ -170,7 +232,9 @@ public void Syncronize() | |
var output = tx.Outputs[i]; | ||
if (!output.ScriptPubKey.IsPayToScriptHash && output.ScriptPubKey.IsWitness) | ||
{ | ||
Bech32UtxoSet.Add(new OutPoint(tx.GetHash(), i), output.ScriptPubKey); | ||
var outpoint = new OutPoint(tx.GetHash(), i); | ||
Bech32UtxoSet.Add(outpoint, output.ScriptPubKey); | ||
ActionHistoryHelper.StoreAction(ActionHistoryHelper.Operation.Add, outpoint, output.ScriptPubKey); | ||
scripts.Add(output.ScriptPubKey); | ||
} | ||
} | ||
|
@@ -180,7 +244,9 @@ public void Syncronize() | |
var found = Bech32UtxoSet.SingleOrDefault(x => x.Key == input.PrevOut); | ||
if (found.Key != default) | ||
{ | ||
Script val=Bech32UtxoSet[input.PrevOut]; | ||
Bech32UtxoSet.Remove(input.PrevOut); | ||
ActionHistoryHelper.StoreAction(ActionHistoryHelper.Operation.Remove, input.PrevOut, val); | ||
scripts.Add(found.Value); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While concurrent collections don't throw exceptions, not all threading issues can be ignored. And they can work in a quite misleading way: https://stackoverflow.com/a/1966462/2061103
I want it to throw an exception if something is wrong. As you see, right above this line there is an
IndexLock
for theIndex
list. This is, because the Index may encounter threading issues, but as the code is todayBech32UtxoSet
won't encounter threading issues, since it's an "internal database" that's being touched in a proper way.Also performance is quite an issue here, so that's another con against concurrent collections.
Generally I use concurrent collections against lock, whenever
Discussion on this topic can be found here: stratisproject/StratisBitcoinFullNode#617
If you agree, please remove the todo comment here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure I will remove. P.S.: I would use the observable part of the dictionary to subcribe the add/remove/clear event to ensure that all changes of the dictionary get noticed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another way is to implement my own observable dictionary without concurrency: something like this
https://stackoverflow.com/questions/5663395/net-observabledictionary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better as it is now, since it's simpler, if it gets more complex we can change. Btw, I created my ObservableConcurrentDictionary if you ever need such: https://github.com/nopara73/HiddenWallet/blob/cc8c948ac7a1b9173610145e60c303d71a8c0a66/HiddenWallet/Helpers/ConcurrentObservableDictionary.cs