1
#nullable enable
2
using System;
3
using System.Collections.Concurrent;
4
using System.Collections.Generic;
5
using System.Security.Cryptography;
6
using Libplanet.Action;
7
using Libplanet.Blocks;
8
using Libplanet.Store;
9
using Serilog;
10

11
namespace Libplanet.Blockchain.Renderers
12
{
13
    /// <summary>
14
    /// Decorates an <see cref="IRenderer{T}"/> instance and delays the events until blocks
15
    /// are <em>confirmed</em> the certain number of blocks.  When blocks are recognized
16
    /// the delayed events relevant to these blocks are relayed to the decorated
17
    /// <see cref="IRenderer{T}"/>.
18
    /// </summary>
19
    /// <typeparam name="T">An <see cref="IAction"/> type.  It should match to
20
    /// <see cref="BlockChain{T}"/>'s type parameter.</typeparam>
21
    /// <example>
22
    /// <code><![CDATA[
23
    /// IStore store = GetStore();
24
    /// IRenderer<ExampleAction> renderer = new SomeRenderer();
25
    /// // Wraps the renderer with DelayedRenderer; the SomeRenderer instance becomes to receive
26
    /// // event messages only after the relevent blocks are confirmed by 3+ blocks.
27
    /// renderer = new DelayedRenderer<ExampleAction>(renderer, store, confirmations: 3);
28
    /// // You must pass the same store to the BlockChain<T>() constructor:
29
    /// var chain = new BlockChain<ExampleAction>(..., store: store, renderers: new[] { renderer });
30
    /// ]]></code>
31
    /// </example>
32
    /// <remarks>Since <see cref="IActionRenderer{T}"/> is a subtype of <see cref="IRenderer{T}"/>,
33
    /// <see cref="DelayedRenderer{T}(IRenderer{T}, IStore, int)"/> constructor can take
34
    /// an <see cref="IActionRenderer{T}"/> instance as well.  However, even it takes an action
35
    /// renderer, action-level fine-grained events won't hear.  For action renderers,
36
    /// please use <see cref="DelayedActionRenderer{T}"/> instead.</remarks>
37
    public class DelayedRenderer<T> : IRenderer<T>
38
        where T : IAction, new()
39
    {
40
        private Block<T>? _tip;
41

42
        /// <summary>
43
        /// Creates a new <see cref="DelayedRenderer{T}"/> instance decorating the given
44
        /// <paramref name="renderer"/>.
45
        /// </summary>
46
        /// <param name="renderer">The renderer to decorate which has the <em>actual</em>
47
        /// implementations and receives delayed events.</param>
48
        /// <param name="store">The same store to what <see cref="BlockChain{T}"/> uses.</param>
49
        /// <param name="confirmations">The required number of confirmations to recognize a block.
50
        /// It must be greater than zero (note that zero <paramref name="confirmations"/> mean
51
        /// nothing is delayed so that it is equivalent to the bare <paramref name="renderer"/>).
52
        /// See also the <see cref="Confirmations"/> property.</param>
53
        /// <exception cref="ArgumentOutOfRangeException">Thrown when the argument
54
        /// <paramref name="confirmations"/> is not greater than zero.</exception>
55 1
        public DelayedRenderer(IRenderer<T> renderer, IStore store, int confirmations)
56 1
        {
57 1
            if (confirmations == 0)
58 1
            {
59 1
                string msg =
60 1
                    "Zero confirmations mean nothing is delayed so that it is equivalent to the " +
61 1
                    $"bare {nameof(renderer)}; configure it to more than zero.";
62 1
                throw new ArgumentOutOfRangeException(nameof(confirmations), msg);
63
            }
64 1
            else if (confirmations < 0)
65 1
            {
66 1
                throw new ArgumentOutOfRangeException(
67 1
                    nameof(confirmations),
68 1
                    $"Expected more than zero {nameof(confirmations)}."
69 1
                );
70
            }
71

72 1
            Logger = Log.ForContext(GetType());
73 1
            Renderer = renderer;
74 1
            Store = store;
75 1
            Confirmations = confirmations;
76 1
            Confirmed = new ConcurrentDictionary<HashDigest<SHA256>, uint>();
77 1
        }
78

79
        /// <summary>
80
        /// The inner renderer which has the <em>actual</em> implementations and receives delayed
81
        /// events.
82
        /// </summary>
83 1
        public IRenderer<T> Renderer { get; }
84

85
        /// <summary>
86
        /// The same store to what <see cref="BlockChain{T}"/> uses.
87
        /// </summary>
88 1
        public IStore Store { get; }
89

90
        /// <summary>
91
        /// The required number of confirmations to recognize a block.
92
        /// <para>For example, the required confirmations are 2, the block #N is recognized after
93
        /// the block #N+1 and the block #N+2 are discovered.</para>
94
        /// </summary>
95 1
        public int Confirmations { get; }
96

97
        /// <summary>
98
        /// The <em>recognized</em> topmost block.  If not enough blocks are discovered yet,
99
        /// this property can be <c>null</c>.
100
        /// </summary>
101
        public Block<T>? Tip
102
        {
103 1
            get => _tip;
104
            private set
105 1
            {
106 1
                Block<T>? newTip = value;
107 1
                if (newTip is null || newTip.Equals(_tip))
108 0
                {
109 0
                    return;
110
                }
111

112 1
                if (_tip is null)
113 1
                {
114 1
                    Logger.Verbose(
115 1
                        $"{nameof(DelayedRenderer<T>)}.{nameof(Tip)} is tried to be updated to " +
116 1
                        "#{NewTipIndex} {NewTipHash} (from null).",
117 1
                        newTip.Index,
118 1
                        newTip.Hash
119 1
                    );
120 1
                }
121
                else
122 1
                {
123 1
                    Logger.Verbose(
124 1
                        $"{nameof(DelayedRenderer<T>)}.{nameof(Tip)} is tried to be updated to " +
125 1
                        "#{NewTipIndex} {NewTipHash} (from #{OldTipIndex} {OldTipHash}).",
126 1
                        newTip.Index,
127 1
                        newTip.Hash,
128 1
                        _tip.Index,
129 1
                        _tip.Hash
130 1
                    );
131 1
                }
132

133 1
                Block<T>? oldTip = _tip;
134 1
                _tip = newTip;
135 1
                if (oldTip is null)
136 1
                {
137 1
                    Logger.Debug(
138 1
                        $"{nameof(DelayedRenderer<T>)}.{nameof(Tip)} was updated to " +
139 1
                        "#{NewTipIndex} {NewTipHash} (from null).",
140 1
                        newTip.Index,
141 1
                        newTip.Hash
142 1
                    );
143 1
                }
144
                else
145 1
                {
146 1
                    Logger.Debug(
147 1
                        $"{nameof(DelayedRenderer<T>)}.{nameof(Tip)} was updated to " +
148 1
                        "#{NewTipIndex} {NewTipHash} (from #{OldTipIndex} {OldTipHash}).",
149 1
                        newTip.Index,
150 1
                        newTip.Hash,
151 1
                        oldTip.Index,
152 1
                        oldTip.Hash
153 1
                    );
154 1
                }
155

156 1
                if (oldTip is Block<T> oldTip_ && !oldTip.Equals(newTip))
157 1
                {
158 1
                    Block<T>? branchpoint = null;
159 1
                    if (!newTip.PreviousHash.Equals(oldTip_.Hash))
160 1
                    {
161 1
                        branchpoint = FindBranchpoint(oldTip, newTip);
162 1
                        if (branchpoint.Equals(oldTip) || branchpoint.Equals(newTip))
163 0
                        {
164 0
                            branchpoint = null;
165 0
                        }
166 1
                    }
167

168 1
                    OnTipChanged(oldTip, newTip, branchpoint);
169 1
                }
170 1
            }
171
        }
172

173
        /// <summary>
174
        /// The logger to record internal state changes.
175
        /// </summary>
176 1
        protected ILogger Logger { get; }
177

178 1
        protected ConcurrentDictionary<HashDigest<SHA256>, uint> Confirmed { get; }
179

180
        /// <inheritdoc cref="IRenderer{T}.RenderBlock(Block{T}, Block{T})"/>
181
        public virtual void RenderBlock(Block<T> oldTip, Block<T> newTip)
182 1
        {
183 1
            Confirmed.TryAdd(oldTip.Hash, 0);
184 1
            DiscoverBlock(oldTip, newTip);
185 1
        }
186

187
        /// <inheritdoc cref="IRenderer{T}.RenderReorg(Block{T}, Block{T}, Block{T})"/>
188
        public virtual void RenderReorg(Block<T> oldTip, Block<T> newTip, Block<T> branchpoint)
189 1
        {
190 1
            Confirmed.TryAdd(branchpoint.Hash, 0);
191 1
        }
192

193
        /// <inheritdoc cref="IRenderer{T}.RenderReorgEnd(Block{T}, Block{T}, Block{T})"/>
194
        public virtual void RenderReorgEnd(Block<T> oldTip, Block<T> newTip, Block<T> branchpoint)
195 1
        {
196 1
        }
197

198
        /// <summary>
199
        /// The callback method which is invoked when the new <see cref="Tip"/> is recognized and
200
        /// changed.
201
        /// </summary>
202
        /// <param name="oldTip">The previously recognized topmost block.</param>
203
        /// <param name="newTip">The topmost block recognized this time.</param>
204
        /// <param name="branchpoint">A branchpoint between <paramref name="oldTip"/> and
205
        /// <paramref name="newTip"/> if the tip change is a reorg.  Otherwise <c>null</c>.</param>
206
        protected virtual void OnTipChanged(Block<T> oldTip, Block<T> newTip, Block<T>? branchpoint)
207 1
        {
208 1
            if (branchpoint is Block<T>)
209 1
            {
210 1
                Renderer.RenderReorg(oldTip, newTip, branchpoint);
211 1
            }
212

213 1
            Renderer.RenderBlock(oldTip, newTip);
214

215 1
            if (branchpoint is Block<T>)
216 1
            {
217 1
                Renderer.RenderReorgEnd(oldTip, newTip, branchpoint);
218 1
            }
219 1
        }
220

221
        protected void DiscoverBlock(Block<T> oldTip, Block<T> newTip)
222 1
        {
223 1
            if (Confirmed.ContainsKey(newTip.Hash))
224 1
            {
225 1
                return;
226
            }
227

228 1
            Block<T> branchpoint = FindBranchpoint(oldTip, newTip);
229 1
            var maxDepth = branchpoint.Index < Confirmations
230 1
                ? 0
231 1
                : branchpoint.Index - Confirmations;
232

233 1
            Confirmed.TryAdd(newTip.Hash, 0U);
234

235 1
            var blocksToRender = new Stack<(long, HashDigest<SHA256>)>();
236

237 1
            long prevBlockIndex = newTip.Index - 1;
238 1
            uint accumulatedConfirmations = 0;
239 1
            HashDigest<SHA256>? prev = newTip.PreviousHash;
240 1
            while (
241 1
                prev is HashDigest<SHA256> prevHash
242 1
                && Store.GetBlock<T>(prevHash) is Block<T> prevBlock
243 1
                && prevBlock.Index >= maxDepth)
244 1
            {
245 1
                uint c = Confirmed.GetOrAdd(prevHash, k => 0U);
246

247 1
                if (c >= Confirmations)
248 1
                {
249 1
                    break;
250
                }
251

252 1
                accumulatedConfirmations += 1;
253 1
                blocksToRender.Push((prevBlockIndex, prevHash));
254

255 1
                prevBlockIndex -= 1;
256 1
                prev = prevBlock.PreviousHash;
257 1
            }
258

259 1
            while (blocksToRender.Count > 0)
260 1
            {
261 1
                (long index, HashDigest<SHA256> hash) = blocksToRender.Pop();
262 1
                uint ac = accumulatedConfirmations;
263 1
                uint c = Confirmed.AddOrUpdate(hash, k => 0U, (k, v) => ac);
264

265 1
                Logger.Verbose(
266 1
                    "The block #{BlockIndex} {BlockHash} has {Confirmations} confirmations. ",
267 1
                    index,
268 1
                    hash,
269 1
                    c
270 1
                );
271

272 1
                if (accumulatedConfirmations > 0)
273 1
                {
274 1
                    accumulatedConfirmations -= 1;
275 1
                }
276

277 1
                if (c >= Confirmations)
278 1
                {
279 1
                    var confirmedBlock = Store.GetBlock<T>(hash);
280

281 1
                    if (!(Tip is Block<T> t))
282 1
                    {
283 1
                        Logger.Verbose(
284 1
                            "Promoting #{NewTipIndex} {NewTipHash} as a new tip since there is " +
285 1
                            "no tip yet...",
286 1
                            confirmedBlock.Index,
287 1
                            confirmedBlock.Hash
288 1
                        );
289 1
                        Tip = confirmedBlock;
290 1
                    }
291 1
                    else if (t.TotalDifficulty < confirmedBlock.TotalDifficulty)
292 1
                    {
293 1
                        Logger.Verbose(
294 1
                            "Promoting #{NewTipIndex} {NewTipHash} as a new tip since its total " +
295 1
                            "difficulty is more than the previous tip #{PreviousTipIndex} " +
296 1
                            "{PreviousTipHash} ({NewDifficulty} > {PreviousDifficulty}).",
297 1
                            confirmedBlock.Index,
298 1
                            confirmedBlock.Hash,
299 1
                            t.Index,
300 1
                            t.Hash,
301 1
                            confirmedBlock.TotalDifficulty,
302 1
                            t.TotalDifficulty
303 1
                        );
304 1
                        Tip = confirmedBlock;
305 1
                    }
306
                    else
307 0
                    {
308 0
                        Logger.Verbose(
309 0
                            "Although #{BlockIndex} {BlockHash} has been confirmed enough," +
310 0
                            "its difficulty is less than the current tip #{TipIndex} {TipHash} " +
311 0
                            "({Difficulty} < {TipDifficulty}).",
312 0
                            confirmedBlock.Index,
313 0
                            confirmedBlock.Hash,
314 0
                            t.Index,
315 0
                            t.Hash,
316 0
                            confirmedBlock.TotalDifficulty,
317 0
                            t.TotalDifficulty
318 0
                        );
319 0
                    }
320 1
                }
321 1
            }
322 1
        }
323

324
        private Block<T> FindBranchpoint(Block<T> a, Block<T> b)
325 1
        {
326 1
            while (a is Block<T> && a.Index > b.Index && a.PreviousHash is HashDigest<SHA256> aPrev)
327 0
            {
328 0
                a = Store.GetBlock<T>(aPrev);
329 0
            }
330

331 1
            while (b is Block<T> && b.Index > a.Index && b.PreviousHash is HashDigest<SHA256> bPrev)
332 1
            {
333 1
                b = Store.GetBlock<T>(bPrev);
334 1
            }
335

336 1
            if (a is null || b is null || a.Index != b.Index)
337 0
            {
338 0
                throw new ArgumentException(
339 0
                    "Some previous blocks of two blocks are orphan.",
340 0
                    nameof(a)
341 0
                );
342
            }
343

344 1
            while (a.Index >= 0)
345 1
            {
346 1
                if (a.Equals(b))
347 1
                {
348 1
                    return a;
349
                }
350

351 1
                if (a.PreviousHash is HashDigest<SHA256> aPrev &&
352 1
                    b.PreviousHash is HashDigest<SHA256> bPrev)
353 1
                {
354 1
                    a = Store.GetBlock<T>(aPrev);
355 1
                    b = Store.GetBlock<T>(bPrev);
356 1
                    continue;
357
                }
358

359 0
                break;
360
            }
361

362 0
            throw new ArgumentException(
363 0
                "Two blocks do not have any ancestors in common.",
364 0
                nameof(a)
365 0
            );
366 1
        }
367
    }
368
}

Read our documentation on viewing source code .

Loading