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); }
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)); }
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)); }
#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; } }