diff --git a/CHANGES.md b/CHANGES.md index 5ec4f7a5be5..3dd30054dcc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -80,6 +80,9 @@ To be released. a high latency internet connection. [[#709]] - `Swarm.BootstrapAsync()` became to report `PeerDiscoveryException` instead of `SwarmException` directly. [[#604], [#726]] + - `BlockChain.Append()` became to unstage the staged `Transaction`s + that have lower nonce than the highest nonce of the same signer's transactions + in the same chain, since these virtually never become valid. [[#721], [#728]] ### Bug fixes @@ -116,9 +119,11 @@ To be released. [#709]: https://github.com/planetarium/libplanet/pull/709 [#718]: https://github.com/planetarium/libplanet/pull/718 [#719]: https://github.com/planetarium/libplanet/pull/719 +[#721]: https://github.com/planetarium/libplanet/issues/721 [#725]: https://github.com/planetarium/libplanet/pull/725 [#726]: https://github.com/planetarium/libplanet/pull/726 [#727]: https://github.com/planetarium/libplanet/pull/727 +[#728]: https://github.com/planetarium/libplanet/pull/728 Version 0.7.0 diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.cs b/Libplanet.Tests/Blockchain/BlockChainTest.cs index 5cc53966c9f..dc380da2fa5 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.cs @@ -460,6 +460,64 @@ await Assert.ThrowsAsync(async () => Assert.Equal(1, blockChain.Count); } + [Fact] + public void UnstageAfterAppendComplete() + { + DumbAction.RenderRecords.Value = ImmutableList.Empty; + MinerReward.RenderRecords.Value = ImmutableList.Empty; + + PrivateKey privateKey = new PrivateKey(); + (Address[] addresses, Transaction[] txs) = + MakeFixturesForAppendTests(privateKey); + var genesis = _blockChain.Genesis; + + try + { + Block block1 = TestUtils.MineNext( + genesis, + miner: addresses[4], + difficulty: _blockChain.Policy.GetNextBlockDifficulty(_blockChain), + blockInterval: TimeSpan.FromSeconds(10)); + _blockChain.Append(block1); + Assert.Empty(_blockChain.GetStagedTransactionIds()); + _blockChain.StageTransactions(txs.ToImmutableHashSet()); + Assert.Equal(2, _blockChain.GetStagedTransactionIds().Count); + + Block block2 = TestUtils.MineNext( + block1, + ImmutableHashSet>.Empty.Add(txs[0]), + difficulty: _blockChain.Policy.GetNextBlockDifficulty(_blockChain), + blockInterval: TimeSpan.FromSeconds(10) + ); + _blockChain.Append(block2); + Assert.Equal(1, _blockChain.GetStagedTransactionIds().Count); + _blockChain.StageTransactions(txs.ToImmutableHashSet()); + Assert.Equal(1, _blockChain.GetStagedTransactionIds().Count); + + var actions = new[] { new DumbAction(addresses[0], "foobar") }; + Transaction[] txs2 = + { + _fx.MakeTransaction(actions, privateKey: privateKey, nonce: 0), + }; + _blockChain.StageTransactions(txs2.ToImmutableHashSet()); + Assert.Equal(2, _blockChain.GetStagedTransactionIds().Count); + + Block block3 = TestUtils.MineNext( + block2, + ImmutableHashSet>.Empty.Add(txs[1]), + difficulty: _blockChain.Policy.GetNextBlockDifficulty(_blockChain), + blockInterval: TimeSpan.FromSeconds(10) + ); + _blockChain.Append(block3); + Assert.Empty(_blockChain.GetStagedTransactionIds()); + } + finally + { + DumbAction.RenderRecords.Value = ImmutableList.Empty; + MinerReward.RenderRecords.Value = ImmutableList.Empty; + } + } + [Fact] public async Task RenderAfterAppendComplete() { @@ -1750,7 +1808,7 @@ void BuildIndex(Guid id, Block block) } private (Address[], Transaction[]) - MakeFixturesForAppendTests() + MakeFixturesForAppendTests(PrivateKey privateKey = null) { Address[] addresses = { @@ -1761,7 +1819,7 @@ void BuildIndex(Guid id, Block block) _fx.Address5, }; - PrivateKey privateKey = new PrivateKey(new byte[] + privateKey = privateKey ?? new PrivateKey(new byte[] { 0xa8, 0x21, 0xc7, 0xc2, 0x08, 0xa9, 0x1e, 0x53, 0xbb, 0xb2, 0x71, 0x15, 0xf4, 0x23, 0x5d, 0x82, 0x33, 0x44, 0xd1, 0x16, diff --git a/Libplanet/Blockchain/BlockChain.cs b/Libplanet/Blockchain/BlockChain.cs index f9d2be247d8..c8ffad25cc9 100644 --- a/Libplanet/Blockchain/BlockChain.cs +++ b/Libplanet/Blockchain/BlockChain.cs @@ -767,12 +767,26 @@ bool renderActions Hash = block.Hash, }; TipChanged?.Invoke(this, tipChangedEventArgs); - ISet txIds = block.Transactions - .Select(t => t.Id) - .ToImmutableHashSet(); _logger.Debug("Unstaging transactions..."); + ImmutableDictionary maxNonces = block.Transactions + .GroupBy( + t => t.Signer, + t => t.Nonce, + (signer, nonces) => new + { + signer = signer, + maxNonce = nonces.Max(), + } + ) + .ToImmutableDictionary(t => t.signer, t => t.maxNonce); + ISet txIds = Store.IterateStagedTransactionIds() + .Select(Store.GetTransaction) + .Where(tx => maxNonces.TryGetValue(tx.Signer, out long nonce) && + tx.Nonce <= nonce) + .Select(tx => tx.Id) + .ToImmutableHashSet(); Store.UnstageTransactionIds(txIds); _logger.Debug("Block {blockIndex}: {block} is appended.", block?.Index, block); }