private static ImmutableList <Block <T> > GetRewindPath( BlockChain <T> chain, BlockHash targetHash) { if (!chain.ContainsBlock(targetHash)) { throw new KeyNotFoundException( $"Given chain {chain.Id} must contain target hash {targetHash}"); } Block <T> target = chain[targetHash]; List <Block <T> > path = new List <Block <T> >(); for ( Block <T> current = chain.Tip; current.Index > target.Index && current.PreviousHash is { } ph; current = chain[ph]) { path.Add(current); } return(path.ToImmutableList()); }
internal BlockChain <T> Fork(HashDigest <SHA256> point) { var forked = new BlockChain <T>(Policy, Store, Guid.NewGuid()); try { _rwlock.EnterReadLock(); foreach (var index in Store.IterateIndex(Id.ToString())) { forked.Append(Blocks[index]); if (index == point) { break; } } return(forked); } finally { _rwlock.ExitReadLock(); } }
// FIXME it's very dangerous because replacing Id means // ALL blocks (referenced by MineBlock(), etc.) will be changed. // we need to add a synchronization mechanism to handle this correctly. internal void Swap(BlockChain <T> other, bool render) { // Finds the branch point. Block <T> topmostCommon = null; if (render && !(Tip is null || other.Tip is null)) { long shorterHeight = Math.Min(this.LongCount(), other.LongCount()) - 1; for ( Block <T> t = this[shorterHeight], o = other[shorterHeight]; t.PreviousHash is HashDigest <SHA256> tp && o.PreviousHash is HashDigest <SHA256> op; t = Blocks[tp], o = other.Blocks[op] ) { if (t.Equals(o)) { topmostCommon = t; break; } } } if (render) { // Unrender stale actions. for ( Block <T> b = Tip; !(b is null) && b.Index > (topmostCommon?.Index ?? -1) && b.PreviousHash is HashDigest <SHA256> ph; b = Blocks[ph] ) { List <ActionEvaluation> evaluations = b.EvaluateActionsPerTx(a => GetStates(new[] { a }, b.PreviousHash).GetValueOrDefault(a)) .Select(te => te.Item2).ToList(); if (Policy.BlockAction is IAction) { evaluations.Add(EvaluateBlockAction(b, evaluations)); } evaluations.Reverse(); foreach (var evaluation in evaluations) { evaluation.Action.Unrender( evaluation.InputContext, evaluation.OutputStates ); } } } try { _rwlock.EnterWriteLock(); var tipChangedEventArgs = new TipChangedEventArgs { PreviousHash = Tip?.Hash, PreviousIndex = Tip?.Index, Hash = other.Tip.Hash, Index = other.Tip.Index, }; Guid obsoleteId = Id; Id = other.Id; Store.SetCanonicalChainId(Id); Blocks = new BlockSet <T>(Store); TipChanged?.Invoke(this, tipChangedEventArgs); Transactions = new TransactionSet <T>(Store); Store.DeleteChainId(obsoleteId); } finally { _rwlock.ExitWriteLock(); } if (render) { // Render actions that had been behind. IEnumerable <Block <T> > blocksToRender = topmostCommon is Block <T> branchPoint ? this.SkipWhile(b => b.Index <= branchPoint.Index) : this; foreach (Block <T> b in blocksToRender) { List <ActionEvaluation> evaluations = b.EvaluateActionsPerTx(a => GetStates(new[] { a }, b.PreviousHash).GetValueOrDefault(a)) .Select(te => te.Item2).ToList(); if (Policy.BlockAction is IAction) { evaluations.Add(EvaluateBlockAction(b, evaluations)); } foreach (var evaluation in evaluations) { evaluation.Action.Render( evaluation.InputContext, evaluation.OutputStates ); } } } }
internal BlockChain <T> Fork(HashDigest <SHA256> point) { var forked = new BlockChain <T>(Policy, Store, Guid.NewGuid()); Guid forkedId = forked.Id; try { _rwlock.EnterReadLock(); Store.ForkBlockIndexes(Id, forkedId, point); Block <T> pointBlock = Blocks[point]; var signersToStrip = new Dictionary <Address, int>(); for ( Block <T> block = Tip; block.PreviousHash is HashDigest <SHA256> hash && !block.Hash.Equals(point); block = Blocks[hash]) { IEnumerable <(Address, int)> signers = block .Transactions .GroupBy(tx => tx.Signer) .Select(g => (g.Key, g.Count())); foreach ((Address address, int txCount) in signers) { int existingValue = 0; signersToStrip.TryGetValue(address, out existingValue); signersToStrip[address] = existingValue + txCount; } } Store.ForkStateReferences(Id, forked.Id, pointBlock); foreach (KeyValuePair <Address, long> pair in Store.ListTxNonces(Id)) { Address address = pair.Key; long existingNonce = pair.Value; long txNonce = existingNonce; int staleTxCount = 0; if (signersToStrip.TryGetValue(address, out staleTxCount)) { txNonce -= staleTxCount; } if (txNonce < 0) { throw new InvalidOperationException( $"A tx nonce for {address} in the store seems broken.\n" + $"Existing tx nonce: {existingNonce}\n" + $"# of stale transactions: {staleTxCount}\n" ); } // Note that at this point every address has tx nonce = 0 // it's merely "setting" rather than "increasing." Store.IncreaseTxNonce(forkedId, address, txNonce); } } finally { _rwlock.ExitReadLock(); } return(forked); }
// FIXME it's very dangerous because replacing Id means // ALL blocks (referenced by MineBlock(), etc.) will be changed. // we need to add a synchronization mechanism to handle this correctly. internal void Swap(BlockChain <T> other) { // Finds the branch point. Block <T> topmostCommon = null; long shorterHeight = Math.Min(this.LongCount(), other.LongCount()) - 1; for ( Block <T> t = this[shorterHeight], o = other[shorterHeight]; t.PreviousHash is HashDigest <SHA256> tp && o.PreviousHash is HashDigest <SHA256> op; t = Blocks[tp], o = other.Blocks[op] ) { if (t.Equals(o)) { topmostCommon = t; break; } } // Unrender stale actions. for ( Block <T> b = Tip; !(b is null) && b.Index > (topmostCommon?.Index ?? -1) && b.PreviousHash is HashDigest <SHA256> ph; b = Blocks[ph] ) { var actions = b.EvaluateActionsPerTx(a => GetStates(new[] { a }, b.PreviousHash).GetValueOrDefault(a) ).Reverse(); foreach (var(_, evaluation) in actions) { evaluation.Action.Unrender( evaluation.InputContext, evaluation.OutputStates ); } } try { _rwlock.EnterWriteLock(); Id = other.Id; Blocks = new BlockSet <T>(Store); Transactions = new TransactionSet <T>(Store); } finally { _rwlock.ExitWriteLock(); } // Render actions that had been behind. IEnumerable <Block <T> > blocksToRender = topmostCommon is Block <T> branchPoint ? this.SkipWhile(b => b.Index <= branchPoint.Index) : this; foreach (Block <T> b in blocksToRender) { var actions = b.EvaluateActionsPerTx(a => GetStates(new[] { a }, b.PreviousHash).GetValueOrDefault(a) ); foreach (var(_, evaluation) in actions) { evaluation.Action.Render( evaluation.InputContext, evaluation.OutputStates ); } } }
// FIXME it's very dangerous because replacing Id means // ALL blocks (referenced by MineBlock(), etc.) will be changed. // we need to add a synchronization mechanism to handle this correctly. internal void Swap(BlockChain <T> other, bool render) { if (other?.Tip is null) { throw new ArgumentException( $"The chain to be swapped is invalid. Id: {other?.Id}, Tip: {other?.Tip}", nameof(other)); } _logger.Debug( "Swapping block chain. (from: {fromChainId}) (to: {toChainId})", Id, other.Id); // Finds the branch point. Block <T> topmostCommon = null; if (render && !(Tip is null)) { long shorterHeight = Math.Min(Count, other.Count) - 1; for ( Block <T> t = this[shorterHeight], o = other[shorterHeight]; t.PreviousHash is HashDigest <SHA256> tp && o.PreviousHash is HashDigest <SHA256> op; t = this[tp], o = other[op] ) { if (t.Equals(o)) { topmostCommon = t; break; } } } _logger.Debug( "Branchpoint is {branchPoint} (at {index})", topmostCommon, topmostCommon?.Index); if (render) { _logger.Debug("Unrendering abandoned actions..."); // Unrender stale actions. for ( Block <T> b = Tip; !(b is null) && b.Index > (topmostCommon?.Index ?? -1) && b.PreviousHash is HashDigest <SHA256> ph; b = this[ph] ) { List <ActionEvaluation> evaluations = EvaluateActions(b).ToList(); evaluations.Reverse(); foreach (var evaluation in evaluations) { _logger.Debug("Unrender action {action}", evaluation.Action); evaluation.Action.Unrender( evaluation.InputContext, evaluation.OutputStates ); } } _logger.Debug($"Unrender for {nameof(Swap)}() is completed."); } try { _rwlock.EnterWriteLock(); var tipChangedEventArgs = new TipChangedEventArgs { PreviousHash = Tip?.Hash, PreviousIndex = Tip?.Index, Hash = other.Tip.Hash, Index = other.Tip.Index, }; Guid obsoleteId = Id; Id = other.Id; Store.SetCanonicalChainId(Id); _blocks = new BlockSet <T>(Store); TipChanged?.Invoke(this, tipChangedEventArgs); _transactions = new TransactionSet <T>(Store); Store.DeleteChainId(obsoleteId); } finally { _rwlock.ExitWriteLock(); } if (render) { _logger.Debug("Rendering actions in new chain"); // Render actions that had been behind. long startToRenderIndex = topmostCommon is Block <T> branchPoint ? branchPoint.Index + 1 : 0; RenderBlocks(startToRenderIndex); _logger.Debug($"Render for {nameof(Swap)}() is completed."); } }
internal BlockChain <T> Fork( HashDigest <SHA256> point, DateTimeOffset currentTime) { var forked = new BlockChain <T>(Policy, Store, Guid.NewGuid()); try { _rwlock.EnterReadLock(); foreach (var index in Store.IterateIndex(Id.ToString())) { Store.AppendIndex(forked.Id.ToString(), index); if (index.Equals(point)) { break; } } Block <T> pointBlock = Blocks[point]; var addressesToStrip = new HashSet <Address>(); var signersToStrip = new HashSet <Address>(); for ( Block <T> block = Tip; block.PreviousHash is HashDigest <SHA256> hash && !block.Hash.Equals(point); block = Blocks[hash]) { ImmutableHashSet <Address> addresses = Store.GetBlockStates(block.Hash) .Select(kv => kv.Key) .ToImmutableHashSet(); ImmutableHashSet <Address> signers = block .Transactions .Select(tx => tx.Signer) .ToImmutableHashSet(); addressesToStrip.UnionWith(addresses); signersToStrip.UnionWith(signers); } Store.ForkStateReferences( Id.ToString(), forked.Id.ToString(), pointBlock, addressesToStrip.ToImmutableHashSet()); Store.ForkTxNonce( Id.ToString(), forked.Id.ToString(), pointBlock, signersToStrip.ToImmutableHashSet()); } finally { _rwlock.ExitReadLock(); } return(forked); }
internal System.Action Swap( BlockChain <T> other, bool render, StateCompleterSet <T>?stateCompleters = null) { if (other is null) { throw new ArgumentNullException(nameof(other)); } // As render/unrender processing requires every step's states from the branchpoint // to the new/stale tip, incomplete states need to be complemented anyway... StateCompleterSet <T> completers = stateCompleters ?? StateCompleterSet <T> .Recalculate; if (Tip.Equals(other.Tip)) { // If it's swapped for a chain with the same tip, it means there is no state change. // Hence render is unnecessary. render = false; } else { _logger.Debug( "The blockchain was reorged from " + "{OldChainId} (#{OldTipIndex} {OldTipHash}) " + "to {NewChainId} (#{NewTipIndex} {NewTipHash}).", Id, Tip.Index, Tip.Hash, other.Id, other.Tip.Index, other.Tip.Hash); } System.Action renderSwap = () => { }; _rwlock.EnterUpgradeableReadLock(); try { // Finds the branch point. Block <T> branchpoint = FindTopCommon(this, other); if (branchpoint is null) { const string msg = "A chain cannot be reorged into a heterogeneous chain with a " + "different genesis."; throw new InvalidGenesisBlockException(Genesis.Hash, other.Genesis.Hash, msg); } _logger.Debug( "The branchpoint is #{BranchpointIndex} {BranchpointHash}.", branchpoint.Index, branchpoint ); Block <T> oldTip = Tip, newTip = other.Tip; ImmutableList <Block <T> > rewindPath = GetRewindPath(this, branchpoint.Hash); ImmutableList <Block <T> > fastForwardPath = GetFastForwardPath(other, branchpoint.Hash); // If there is no rewind, it is not considered as a reorg. bool reorg = rewindPath.Count > 0; _rwlock.EnterWriteLock(); try { IEnumerable <long> LongRange(long start, long count) { for (long i = 0; i < count; i++) { yield return(start + i); } } ImmutableHashSet <Transaction <T> > GetTxsWithRange(BlockChain <T> chain, Block <T> start, Block <T> end) => LongRange(start.Index + 1, end.Index - start.Index) .SelectMany(index => chain[index].Transactions) .ToImmutableHashSet(); // It assumes reorg is small size. If it was big, this may be heavy task. ImmutableHashSet <Transaction <T> > txsToStage = GetTxsWithRange(this, branchpoint, Tip); ImmutableHashSet <Transaction <T> > txsToUnstage = GetTxsWithRange(other, branchpoint, other.Tip); foreach (Transaction <T> tx in txsToStage) { StagePolicy.Stage(this, tx); } Guid obsoleteId = Id; Id = other.Id; Store.SetCanonicalChainId(Id); _blocks = new BlockSet <T>(Policy.GetHashAlgorithm, Store); foreach (Transaction <T> tx in txsToUnstage) { StagePolicy.Unstage(this, tx.Id); } TipChanged?.Invoke(this, (oldTip, newTip)); Store.DeleteChainId(obsoleteId); } finally { _rwlock.ExitWriteLock(); } renderSwap = () => RenderSwap( render: render, oldTip: oldTip, newTip: newTip, branchpoint: branchpoint, rewindPath: rewindPath, fastForwardPath: fastForwardPath, stateCompleters: completers); } finally { _rwlock.ExitUpgradeableReadLock(); } return(renderSwap); }
internal BlockChain <T> Fork( HashDigest <SHA256> point, DateTimeOffset currentTime) { var forked = new BlockChain <T>(Policy, Store, Guid.NewGuid()); string id = Id.ToString(); string forkedId = forked.Id.ToString(); try { _rwlock.EnterReadLock(); foreach (var index in Store.IterateIndex(id)) { Store.AppendIndex(forkedId, index); if (index.Equals(point)) { break; } } Block <T> pointBlock = Blocks[point]; var addressesToStrip = new HashSet <Address>(); var signersToStrip = new Dictionary <Address, int>(); for ( Block <T> block = Tip; block.PreviousHash is HashDigest <SHA256> hash && !block.Hash.Equals(point); block = Blocks[hash]) { ImmutableHashSet <Address> addresses = Store.GetBlockStates(block.Hash) .Select(kv => kv.Key) .ToImmutableHashSet(); addressesToStrip.UnionWith(addresses); IEnumerable <(Address, int)> signers = block .Transactions .GroupBy(tx => tx.Signer) .Select(g => (g.Key, g.Count())); foreach ((Address address, int txCount) in signers) { int existingValue = 0; signersToStrip.TryGetValue(address, out existingValue); signersToStrip[address] = existingValue + txCount; } } Store.ForkStateReferences( id, forked.Id.ToString(), pointBlock, addressesToStrip.ToImmutableHashSet()); foreach (KeyValuePair <Address, long> pair in Store.ListTxNonces(id)) { Address address = pair.Key; long existingNonce = pair.Value; long txNonce = existingNonce; int staleTxCount = 0; if (signersToStrip.TryGetValue(address, out staleTxCount)) { txNonce -= staleTxCount; } if (txNonce < 0) { throw new InvalidOperationException( $"A tx nonce for {address} in the store seems broken.\n" + $"Existing tx nonce: {existingNonce}\n" + $"# of stale transactions: {staleTxCount}\n" ); } // Note that at this point every address has tx nonce = 0 // it's merely "setting" rather than "increasing." Store.IncreaseTxNonce(forkedId, address, txNonce); } } finally { _rwlock.ExitReadLock(); } return(forked); }
// FIXME it's very dangerous because replacing Id means // ALL blocks (referenced by MineBlock(), etc.) will be changed. internal System.Action Swap( BlockChain <T> other, bool render, StateCompleterSet <T>?stateCompleters = null) { if (other is null) { throw new ArgumentNullException(nameof(other)); } // As render/unrender processing requires every step's states from the branchpoint // to the new/stale tip, incomplete states need to be complemented anyway... StateCompleterSet <T> completers = stateCompleters ?? StateCompleterSet <T> .Recalculate; if (Tip.Equals(other.Tip)) { // If it's swapped for a chain with the same tip, it means there is no state change. // Hence render is unnecessary. render = false; } else { _logger.Debug( "The blockchain was reorged from " + "{OldChainId} (#{OldTipIndex} {OldTipHash}) " + "to {NewChainId} (#{NewTipIndex} {NewTipHash}).", Id, Tip.Index, Tip.Hash, other.Id, other.Tip.Index, other.Tip.Hash); } System.Action renderSwap = () => { }; _rwlock.EnterUpgradeableReadLock(); try { // Finds the branch point. Block <T> branchpoint = FindTopCommon(this, other); if (branchpoint is null) { const string msg = "A chain cannot be reorged into a heterogeneous chain with a " + "different genesis."; throw new InvalidGenesisBlockException(Genesis.Hash, other.Genesis.Hash, msg); } _logger.Debug( "The branchpoint is #{BranchpointIndex} {BranchpointHash}.", branchpoint.Index, branchpoint ); Block <T> oldTip = Tip, newTip = other.Tip; ImmutableList <Block <T> > rewindPath = GetRewindPath(this, branchpoint.Hash); ImmutableList <Block <T> > fastForwardPath = GetFastForwardPath(other, branchpoint.Hash); // If there is no rewind, it is not considered as a reorg. bool reorg = rewindPath.Count > 0; _rwlock.EnterWriteLock(); try { IEnumerable <Transaction <T> > GetTxsWithRange(BlockChain <T> chain, Block <T> start, Block <T> end) => Enumerable .Range((int)start.Index + 1, (int)(end.Index - start.Index)) .SelectMany(x => { // FIXME: Change the type of IBlockContent<T>.Transactions to // IImmutableSet<Transaction<T>>, and define a distinct property // to Block<T> for this ordering. Block <T> block = chain[x]; return(ActionEvaluator <T> .OrderTxsForEvaluation( block.ProtocolVersion, block.Transactions, block.PreEvaluationHash )); }); // It assumes reorg is small size. If it was big, this may be heavy task. ImmutableHashSet <Transaction <T> > unstagedTxs = GetTxsWithRange(this, branchpoint, Tip).ToImmutableHashSet(); ImmutableHashSet <Transaction <T> > stageTxs = GetTxsWithRange(other, branchpoint, other.Tip).ToImmutableHashSet(); ImmutableHashSet <Transaction <T> > restageTxs = unstagedTxs.Except(stageTxs); foreach (Transaction <T> restageTx in restageTxs) { StagePolicy.Stage(this, restageTx); } Guid obsoleteId = Id; Id = other.Id; Store.SetCanonicalChainId(Id); _blocks = new BlockSet <T>(Policy.GetHashAlgorithm, Store); TipChanged?.Invoke(this, (oldTip, newTip)); Store.DeleteChainId(obsoleteId); } finally { _rwlock.ExitWriteLock(); } renderSwap = () => RenderSwap( render: render, oldTip: oldTip, newTip: newTip, branchpoint: branchpoint, rewindPath: rewindPath, fastForwardPath: fastForwardPath, stateCompleters: completers); } finally { _rwlock.ExitUpgradeableReadLock(); } return(renderSwap); }