private void TransferRecentStates(GetRecentStates getRecentStates)
        {
            BlockLocator         baseLocator = getRecentStates.BaseLocator;
            HashDigest <SHA256>? @base       = BlockChain.FindBranchPoint(baseLocator);
            HashDigest <SHA256>  target      = getRecentStates.TargetBlockHash;
            IImmutableDictionary <HashDigest <SHA256>,
                                  IImmutableDictionary <string, IValue>
                                  > blockStates = null;
            IImmutableDictionary <string, IImmutableList <HashDigest <SHA256> > > stateRefs = null;
            long nextOffset = -1;
            int  iteration  = 0;

            if (BlockChain.StateStore is IBlockStatesStore blockStatesStore &&
                BlockChain.ContainsBlock(target))
            {
                ReaderWriterLockSlim rwlock = BlockChain._rwlock;
                rwlock.EnterReadLock();
                try
                {
                    Guid chainId = BlockChain.Id;

                    _logger.Debug(
                        "Getting state references from {Offset}",
                        getRecentStates.Offset);

                    long baseIndex =
                        (@base is HashDigest <SHA256> bbh &&
                         _store.GetBlockIndex(bbh) is long bbIdx)
                            ? bbIdx
                            : 0;
                    long lowestIndex = baseIndex + getRecentStates.Offset;
                    long targetIndex =
                        (target is HashDigest <SHA256> tgt &&
                         _store.GetBlockIndex(tgt) is long tgtIdx)
                            ? tgtIdx
                            : long.MaxValue;

                    iteration =
                        (int)Math.Ceiling(
                            (double)(targetIndex - baseIndex + 1) / FindNextStatesChunkSize);

                    long highestIndex = lowestIndex + FindNextStatesChunkSize - 1 > targetIndex
                        ? targetIndex
                        : lowestIndex + FindNextStatesChunkSize - 1;

                    nextOffset = highestIndex == targetIndex
                        ? -1
                        : getRecentStates.Offset + FindNextStatesChunkSize;

                    stateRefs = blockStatesStore.ListAllStateReferences(
                        chainId,
                        lowestIndex: lowestIndex,
                        highestIndex: highestIndex
                        );
                    if (_logger.IsEnabled(LogEventLevel.Verbose))
                    {
                        _logger.Verbose(
                            "List state references from {From} to {To}:\n{StateReferences}",
                            lowestIndex,
                            highestIndex,
                            string.Join(
                                "\n",
                                stateRefs.Select(kv => $"{kv.Key}: {string.Join(", ", kv.Value)}")
                                )
                            );
                    }

                    // GetBlockStates may return null since swarm may not have deep states.
                    blockStates = stateRefs.Values
                                  .Select(refs => refs.Last())
                                  .ToImmutableHashSet()
                                  .Select(bh => (bh, blockStatesStore.GetBlockStates(bh)))
                                  .Where(pair => !(pair.Item2 is null))
                                  .ToImmutableDictionary(
                        pair => pair.Item1,
                        pair => (IImmutableDictionary <string, IValue>)pair.Item2
                        .ToImmutableDictionary(kv => kv.Key, kv => kv.Value)
                        );
                }
                finally
                {
                    rwlock.ExitReadLock();
                }

                if (_logger.IsEnabled(LogEventLevel.Verbose))
                {
                    if (BlockChain.ContainsBlock(target))
                    {
                        var baseString = @base is HashDigest <SHA256> h
                            ? $"{BlockChain[h].Index}:{h}"
                            : null;
                        var targetString = $"{BlockChain[target].Index}:{target}";
                        _logger.Verbose(
                            "State references to send (preload):" +
                            " {StateReferences} ({Base}-{Target})",
                            stateRefs.Select(kv =>
                                             (
                                                 kv.Key,
                                                 string.Join(", ", kv.Value.Select(v => v.ToString()))
                                             )
                                             ).ToArray(),
                            baseString,
                            targetString
                            );
                        _logger.Verbose(
                            "Block states to send (preload): {BlockStates} ({Base}-{Target})",
                            blockStates.Select(kv => (kv.Key, kv.Value)).ToArray(),
                            baseString,
                            targetString
                            );
                    }
                    else
                    {
                        _logger.Verbose(
                            "Nothing to reply because {TargetHash} doesn't exist.", target);
                    }
                }
            }

            var reply = new RecentStates(
                target,
                nextOffset,
                iteration,
                blockStates,
                stateRefs?.ToImmutableDictionary())
            {
                Identity = getRecentStates.Identity,
            };

            Transport.ReplyMessage(reply);
        }
Exemple #2
0
        public void ValidateNextBlockWithManyTransactions()
        {
            var adminPrivateKey = new PrivateKey();
            var adminAddress = new Address(adminPrivateKey.PublicKey);
            var blockPolicySource = new BlockPolicySource(Logger.None);
            IBlockPolicy<PolymorphicAction<ActionBase>> policy = blockPolicySource.GetPolicy(3000, 10);
            Block<PolymorphicAction<ActionBase>> genesis = MakeGenesisBlock(adminAddress, ImmutableHashSet<Address>.Empty);

            using var store = new DefaultStore(null);
            var stateStore = new NoOpStateStore();
            var blockChain = new BlockChain<PolymorphicAction<ActionBase>>(
                policy,
                store,
                stateStore,
                genesis
            );

            int nonce = 0;
            List<Transaction<PolymorphicAction<ActionBase>>> GenerateTransactions(int count)
            {
                var list = new List<Transaction<PolymorphicAction<ActionBase>>>();
                for (int i = 0; i < count; i++)
                {
                    list.Add(Transaction<PolymorphicAction<ActionBase>>.Create(
                        nonce++,
                        adminPrivateKey,
                        genesis.Hash,
                        new PolymorphicAction<ActionBase>[] { }
                    ));
                }

                return list;
            }

            Assert.Equal(1, blockChain.Count);
            Block<PolymorphicAction<ActionBase>> block1 = Block<PolymorphicAction<ActionBase>>.Mine(
                index: 1,
                difficulty: policy.GetNextBlockDifficulty(blockChain),
                previousTotalDifficulty: blockChain.Tip.TotalDifficulty,
                miner: adminAddress,
                previousHash: blockChain.Tip.Hash,
                timestamp: DateTimeOffset.MinValue,
                transactions: GenerateTransactions(5));
            blockChain.Append(block1);
            Assert.Equal(2, blockChain.Count);
            Assert.True(blockChain.ContainsBlock(block1.Hash));
            Block<PolymorphicAction<ActionBase>> block2 = Block<PolymorphicAction<ActionBase>>.Mine(
                index: 2,
                difficulty: policy.GetNextBlockDifficulty(blockChain),
                previousTotalDifficulty: blockChain.Tip.TotalDifficulty,
                miner: adminAddress,
                previousHash: blockChain.Tip.Hash,
                timestamp: DateTimeOffset.MinValue,
                transactions: GenerateTransactions(10));
            blockChain.Append(block2);
            Assert.Equal(3, blockChain.Count);
            Assert.True(blockChain.ContainsBlock(block2.Hash));
            Block<PolymorphicAction<ActionBase>> block3 = Block<PolymorphicAction<ActionBase>>.Mine(
                index: 3,
                difficulty: policy.GetNextBlockDifficulty(blockChain),
                previousTotalDifficulty: blockChain.Tip.TotalDifficulty,
                miner: adminAddress,
                previousHash: blockChain.Tip.Hash,
                timestamp: DateTimeOffset.MinValue,
                transactions: GenerateTransactions(11));
            Assert.Throws<BlockExceedingTransactionsException>(() => blockChain.Append(block3));
            Assert.Equal(3, blockChain.Count);
            Assert.False(blockChain.ContainsBlock(block3.Hash));
        }
Exemple #3
0
        public void ValidateNextBlockWithManyTransactionsPerSigner()
        {
            var adminPrivateKey   = new PrivateKey();
            var adminPublicKey    = adminPrivateKey.PublicKey;
            var blockPolicySource = new BlockPolicySource(Logger.None);
            IBlockPolicy <PolymorphicAction <ActionBase> > policy = blockPolicySource.GetPolicy(
                minimumDifficulty: 10_000,
                hashAlgorithmTypePolicy: null,
                maxBlockBytesPolicy: null,
                minTransactionsPerBlockPolicy: null,
                maxTransactionsPerBlockPolicy: MaxTransactionsPerBlockPolicy
                .Default
                .Add(new SpannedSubPolicy <int>(0, null, null, 10)),
                maxTransactionsPerSignerPerBlockPolicy: MaxTransactionsPerSignerPerBlockPolicy
                .Default
                .Add(new SpannedSubPolicy <int>(2, null, null, 5)),
                authorizedMinersPolicy: null,
                permissionedMinersPolicy: null);
            IStagePolicy <PolymorphicAction <ActionBase> > stagePolicy =
                new VolatileStagePolicy <PolymorphicAction <ActionBase> >();
            Block <PolymorphicAction <ActionBase> > genesis =
                MakeGenesisBlock(adminPublicKey.ToAddress(), ImmutableHashSet <Address> .Empty);

            using var store = new DefaultStore(null);
            var stateStore = new TrieStateStore(new MemoryKeyValueStore());
            var blockChain = new BlockChain <PolymorphicAction <ActionBase> >(
                policy,
                stagePolicy,
                store,
                stateStore,
                genesis
                );

            int nonce = 0;

            List <Transaction <PolymorphicAction <ActionBase> > > GenerateTransactions(int count)
            {
                var list = new List <Transaction <PolymorphicAction <ActionBase> > >();

                for (int i = 0; i < count; i++)
                {
                    list.Add(Transaction <PolymorphicAction <ActionBase> > .Create(
                                 nonce++,
                                 adminPrivateKey,
                                 genesis.Hash,
                                 new PolymorphicAction <ActionBase>[] { }
                                 ));
                }

                return(list);
            }

            Assert.Equal(1, blockChain.Count);
            Block <PolymorphicAction <ActionBase> > block1 = new BlockContent <PolymorphicAction <ActionBase> >
            {
                Index           = 1,
                Difficulty      = policy.GetNextBlockDifficulty(blockChain),
                TotalDifficulty = blockChain.Tip.Difficulty + policy.GetNextBlockDifficulty(blockChain),
                PublicKey       = adminPublicKey,
                PreviousHash    = blockChain.Tip.Hash,
                Timestamp       = DateTimeOffset.MinValue,
                Transactions    = GenerateTransactions(10),
            }.Mine(policy.GetHashAlgorithm(1)).Evaluate(adminPrivateKey, blockChain);

            // Should be fine since policy hasn't kicked in yet.
            blockChain.Append(block1);
            Assert.Equal(2, blockChain.Count);
            Assert.True(blockChain.ContainsBlock(block1.Hash));

            Block <PolymorphicAction <ActionBase> > block2 = new BlockContent <PolymorphicAction <ActionBase> >
            {
                Index           = 2,
                Difficulty      = policy.GetNextBlockDifficulty(blockChain),
                TotalDifficulty = blockChain.Tip.Difficulty + policy.GetNextBlockDifficulty(blockChain),
                PublicKey       = adminPublicKey,
                PreviousHash    = blockChain.Tip.Hash,
                Timestamp       = DateTimeOffset.MinValue,
                Transactions    = GenerateTransactions(10),
            }.Mine(policy.GetHashAlgorithm(2)).Evaluate(adminPrivateKey, blockChain);

            // Subpolicy kicks in.
            Assert.Throws <InvalidBlockTxCountPerSignerException>(() => blockChain.Append(block2));
            Assert.Equal(2, blockChain.Count);
            Assert.False(blockChain.ContainsBlock(block2.Hash));
            // Since failed, roll back nonce.
            nonce -= 10;

            // Limit should also pass.
            Block <PolymorphicAction <ActionBase> > block3 = new BlockContent <PolymorphicAction <ActionBase> >
            {
                Index           = 2,
                Difficulty      = policy.GetNextBlockDifficulty(blockChain),
                TotalDifficulty = blockChain.Tip.Difficulty + policy.GetNextBlockDifficulty(blockChain),
                PublicKey       = adminPublicKey,
                PreviousHash    = blockChain.Tip.Hash,
                Timestamp       = DateTimeOffset.MinValue,
                Transactions    = GenerateTransactions(5),
            }.Mine(policy.GetHashAlgorithm(2)).Evaluate(adminPrivateKey, blockChain);

            blockChain.Append(block3);
            Assert.Equal(3, blockChain.Count);
            Assert.True(blockChain.ContainsBlock(block3.Hash));
        }
Exemple #4
0
#pragma warning disable MEN003
        private async Task <BlockChain <T> > FillBlocksAsync(
            BoundPeer peer,
            BlockChain <T> blockChain,
            BlockHash?stop,
            IProgress <BlockDownloadState> progress,
            long totalBlockCount,
            long receivedBlockCount,
            bool evaluateActions,
            TimeSpan timeout,
            int logSessionId,
            CancellationToken cancellationToken
            )
        {
            var            sessionRandom = new Random();
            const string   fname         = nameof(FillBlocksAsync);
            BlockChain <T> workspace     = blockChain;
            var            scope         = new List <Guid>();
            bool           renderActions = evaluateActions;
            bool           renderBlocks  = true;

            try
            {
                while (!cancellationToken.IsCancellationRequested)
                {
                    int       subSessionId = sessionRandom.Next();
                    Block <T> tip          = workspace?.Tip;

                    _logger.Debug(
                        "{SessionId}/{SubSessionId}: Trying to find branchpoint...",
                        logSessionId,
                        subSessionId
                        );
                    BlockLocator locator = workspace.GetBlockLocator();
                    _logger.Debug(
                        "{SessionId}/{SubSessionId}: Locator's length: {LocatorLength}",
                        logSessionId,
                        subSessionId,
                        locator.Count()
                        );
                    IAsyncEnumerable <Tuple <long, BlockHash> > hashesAsync = GetBlockHashes(
                        peer: peer,
                        locator: locator,
                        stop: stop,
                        timeout: timeout,
                        logSessionIds: (logSessionId, subSessionId),
                        cancellationToken: cancellationToken
                        );
                    IEnumerable <Tuple <long, BlockHash> > hashes = await hashesAsync.ToArrayAsync();

                    if (!hashes.Any())
                    {
                        _logger.Debug(
                            "{SessionId}/{SubSessionId}: Peer {0} returned no hashes; ignored.",
                            logSessionId,
                            subSessionId,
                            peer.Address.ToHex()
                            );
                        return(workspace);
                    }

                    hashes.First().Deconstruct(
                        out long branchIndex,
                        out BlockHash branchpoint
                        );

                    _logger.Debug(
                        "{SessionId}/{SubSessionId}: Branchpoint is #{BranchIndex} {BranchHash}.",
                        logSessionId,
                        subSessionId,
                        branchIndex,
                        branchpoint
                        );

                    if (tip is null || branchpoint.Equals(tip.Hash))
                    {
                        _logger.Debug(
                            "{SessionId}/{SubSessionId}: It doesn't need to fork.",
                            logSessionId,
                            subSessionId
                            );
                    }
                    else if (!workspace.ContainsBlock(branchpoint))
                    {
                        // FIXME: This behavior can unexpectedly terminate the swarm (and the game
                        // app) if it encounters a peer having a different blockchain, and therefore
                        // can be exploited to remotely shut down other nodes as well.
                        // Since the intention of this behavior is to prevent mistakes to try to
                        // connect incorrect seeds (by a user), this behavior should be limited for
                        // only seed peers.
                        var msg =
                            $"Since the genesis block is fixed to {BlockChain.Genesis} " +
                            "protocol-wise, the blockchain which does not share " +
                            "any mutual block is not acceptable.";
                        throw new InvalidGenesisBlockException(
                                  branchpoint,
                                  workspace.Genesis.Hash,
                                  msg);
                    }
                    else
                    {
                        _logger.Debug(
                            "{SessionId}/{SubSessionId}: Needs to fork; trying to fork...",
                            logSessionId,
                            subSessionId
                            );
                        workspace = workspace.Fork(branchpoint);
                        Guid workChainId = workspace.Id;
                        scope.Add(workChainId);
                        renderActions = false;
                        renderBlocks  = false;
                        _logger.Debug(
                            "{SessionId}/{SubSessionId}: Fork finished.",
                            logSessionId,
                            subSessionId
                            );
                    }

                    if (!(workspace.Tip is null))
                    {
                        hashes = hashes.Skip(1);
                    }

                    _logger.Debug(
                        "{SessionId}/{SubSessionId}: Trying to fill up previous blocks...",
                        logSessionId,
                        subSessionId
                        );

                    var hashesAsArray = hashes as Tuple <long, BlockHash>[] ?? hashes.ToArray();
                    if (!hashesAsArray.Any())
                    {
                        break;
                    }

                    int hashCount = hashesAsArray.Count();
                    _logger.Debug(
                        "{SessionId}/{SubSessionId}: Required {Hashes} hashes " +
                        "(tip: #{TipIndex} {TipHash}).",
                        logSessionId,
                        subSessionId,
                        hashCount,
                        workspace.Tip?.Index,
                        workspace.Tip?.Hash
                        );

                    totalBlockCount = Math.Max(totalBlockCount, receivedBlockCount + hashCount);

                    IAsyncEnumerable <Block <T> > blocks = GetBlocksAsync(
                        peer,
                        hashesAsArray.Select(pair => pair.Item2),
                        cancellationToken
                        );

                    var receivedBlockCountCurrentLoop = 0;
                    await foreach (Block <T> block in blocks)
                    {
                        const string startMsg =
                            "{SessionId}/{SubSessionId}: Try to append a block " +
                            "#{BlockIndex} {BlockHash}...";
                        _logger.Debug(
                            startMsg,
                            logSessionId,
                            subSessionId,
                            block.Index,
                            block.Hash
                            );

                        cancellationToken.ThrowIfCancellationRequested();

                        workspace.Append(
                            block,
                            DateTimeOffset.UtcNow,
                            evaluateActions: evaluateActions,
                            renderBlocks: renderBlocks,
                            renderActions: renderActions
                            );
                        receivedBlockCountCurrentLoop++;
                        progress?.Report(new BlockDownloadState
                        {
                            TotalBlockCount    = totalBlockCount,
                            ReceivedBlockCount = receivedBlockCount + receivedBlockCountCurrentLoop,
                            ReceivedBlockHash  = block.Hash,
                            SourcePeer         = peer,
                        });
                        const string endMsg =
                            "{SessionId}/{SubSessionId}: Block #{BlockIndex} {BlockHash} " +
                            "was appended.";
                        _logger.Debug(endMsg, logSessionId, subSessionId, block.Index, block.Hash);
                    }

                    receivedBlockCount += receivedBlockCountCurrentLoop;
                    var isEndedFirstTime = receivedBlockCount == receivedBlockCountCurrentLoop &&
                                           receivedBlockCount < FindNextHashesChunkSize - 1;

                    if (receivedBlockCountCurrentLoop < FindNextHashesChunkSize && isEndedFirstTime)
                    {
                        _logger.Debug(
                            "{SessionId}/{SubSessionId}: Got {Blocks} blocks from Peer {Peer} " +
                            "(tip: #{TipIndex} {TipHash})",
                            logSessionId,
                            subSessionId,
                            receivedBlockCountCurrentLoop,
                            peer.Address.ToHex(),
                            workspace.Tip?.Index,
                            workspace.Tip?.Hash
                            );
                        break;
                    }
                }