diff --git a/CHANGES.md b/CHANGES.md index bbd253bfe81..edb319d2769 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,8 @@ To be released. - `Peer.AppProtocolVersion` became nullable to represent `Peer` whose version is unknown. - Added `IStore.ListAddresses()` method. [[#272], [#285]] + - Renamed `BlockChain.GetNonce()` method to `BlockChain.GetNextTxNonce()` + method. [[#294]] ### Added interfaces @@ -22,6 +24,8 @@ To be released. - Added `IncompleteBlockStatesException` class. [[#272], [#285]] - Added `completeStates` option to `BlockChain.GetStates()` method. [[#272], [#285]] + - Added `BlockChain.MakeTransaction(PrivateKey, IEnumerable, + IImmutableSet
, DateTimeOffset?)` method. [[#294]] ### Behavioral changes @@ -65,6 +69,7 @@ To be released. [#281]: https://github.com/planetarium/libplanet/pull/281 [#285]: https://github.com/planetarium/libplanet/pull/285 [#287]: https://github.com/planetarium/libplanet/pull/287 +[#294]: https://github.com/planetarium/libplanet/pull/294 Version 0.3.0 diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.cs b/Libplanet.Tests/Blockchain/BlockChainTest.cs index 7a103f82297..b9eebc911a7 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.cs @@ -488,7 +488,7 @@ public void ForkTxNonce() _fx.MakeTransaction(actions, privateKey: privateKey), }; - Assert.Equal(0, _blockChain.GetNonce(address)); + Assert.Equal(0, _blockChain.GetNextTxNonce(address)); Block b1 = TestUtils.MineNext( genesis, @@ -497,7 +497,7 @@ public void ForkTxNonce() _blockChain.Policy.GetNextBlockDifficulty(_blockChain)); _blockChain.Append(b1); - Assert.Equal(1, _blockChain.GetNonce(address)); + Assert.Equal(1, _blockChain.GetNextTxNonce(address)); Transaction[] txsB = { @@ -513,10 +513,10 @@ public void ForkTxNonce() _blockChain.Policy.GetNextBlockDifficulty(_blockChain)); _blockChain.Append(b2); - Assert.Equal(2, _blockChain.GetNonce(address)); + Assert.Equal(2, _blockChain.GetNextTxNonce(address)); BlockChain forked = _blockChain.Fork(b1.Hash); - Assert.Equal(1, forked.GetNonce(address)); + Assert.Equal(1, forked.GetNextTxNonce(address)); } [Fact] @@ -863,18 +863,18 @@ public void EvaluateActions() } [Fact] - public void GetNonce() + public void GetNextTxNonce() { var privateKey = new PrivateKey(); Address address = privateKey.PublicKey.ToAddress(); var actions = new[] { new DumbAction(_fx.Address1, "foo") }; - Assert.Equal(0, _blockChain.GetNonce(address)); + Assert.Equal(0, _blockChain.GetNextTxNonce(address)); Block genesis = TestUtils.MineGenesis(); _blockChain.Append(genesis); - Assert.Equal(0, _blockChain.GetNonce(address)); + Assert.Equal(0, _blockChain.GetNextTxNonce(address)); Transaction[] txsA = { @@ -888,7 +888,7 @@ public void GetNonce() _blockChain.Policy.GetNextBlockDifficulty(_blockChain)); _blockChain.Append(b1); - Assert.Equal(1, _blockChain.GetNonce(address)); + Assert.Equal(1, _blockChain.GetNextTxNonce(address)); Transaction[] txsB = { @@ -898,7 +898,7 @@ public void GetNonce() _blockChain.StageTransactions(txsB.ToHashSet()); - Assert.Equal(3, _blockChain.GetNonce(address)); + Assert.Equal(3, _blockChain.GetNextTxNonce(address)); Transaction[] txsC = { @@ -907,7 +907,62 @@ public void GetNonce() }; _blockChain.StageTransactions(txsC.ToHashSet()); - Assert.Equal(4, _blockChain.GetNonce(address)); + Assert.Equal(4, _blockChain.GetNextTxNonce(address)); + } + + [Fact] + public void ValidateNonce() + { + var privateKey = new PrivateKey(); + var actions = new[] { new DumbAction() }; + + Block genesis = TestUtils.MineGenesis(); + _blockChain.Append(genesis); + + Transaction[] txsA = + { + _fx.MakeTransaction(actions, privateKey: privateKey, nonce: 1), + _fx.MakeTransaction(actions, privateKey: privateKey, nonce: 0), + }; + Block b1 = TestUtils.MineNext(genesis, txsA); + _blockChain.ValidateNonce(b1); + + Transaction[] txsB = + { + _fx.MakeTransaction(actions, privateKey: privateKey, nonce: 1), + }; + Block b2 = TestUtils.MineNext(genesis, txsB); + Assert.Throws(() => + _blockChain.ValidateNonce(b2)); + } + + [Fact] + public void MakeTransaction() + { + var privateKey = new PrivateKey(); + Address address = privateKey.PublicKey.ToAddress(); + var actions = new[] { new DumbAction(address, "foo") }; + + _blockChain.MakeTransaction(privateKey, actions); + _blockChain.MakeTransaction(privateKey, actions); + + List> txs = _blockChain.Store + .IterateStagedTransactionIds() + .Select(_blockChain.Store.GetTransaction) + .OrderBy(tx => tx.Nonce) + .ToList(); + + Assert.Equal(2, txs.Count()); + + var transaction = txs[0]; + Assert.Equal(0, transaction.Nonce); + Assert.Equal(address, transaction.Signer); + Assert.Equal(actions, transaction.Actions); + + transaction = txs[1]; + Assert.Equal(1, transaction.Nonce); + Assert.Equal(address, transaction.Signer); + Assert.Equal(actions, transaction.Actions); } /// diff --git a/Libplanet/Blockchain/BlockChain.cs b/Libplanet/Blockchain/BlockChain.cs index a974b4dae6b..2032b2dfb0b 100644 --- a/Libplanet/Blockchain/BlockChain.cs +++ b/Libplanet/Blockchain/BlockChain.cs @@ -9,6 +9,7 @@ using Libplanet.Action; using Libplanet.Blockchain.Policies; using Libplanet.Blocks; +using Libplanet.Crypto; using Libplanet.Store; using Libplanet.Tx; @@ -19,6 +20,7 @@ public class BlockChain : IReadOnlyList> where T : IAction, new() { private readonly ReaderWriterLockSlim _rwlock; + private readonly object _txLock; public BlockChain(IBlockPolicy policy, IStore store) : this(policy, store, Guid.NewGuid()) @@ -35,6 +37,7 @@ public BlockChain(IBlockPolicy policy, IStore store, Guid id) _rwlock = new ReaderWriterLockSlim( LockRecursionPolicy.SupportsRecursion); + _txLock = new object(); } ~BlockChain() @@ -291,7 +294,7 @@ public AddressStateMap GetStates( /// the . /// Thrown when the /// is different from - /// result of the + /// result of the /// . public void Append(Block block) => Append(block, DateTimeOffset.UtcNow); @@ -312,7 +315,7 @@ public void Append(Block block) => /// the . /// Thrown when the /// is different from - /// result of the + /// result of the /// . public void Append(Block block, DateTimeOffset currentTime) => Append(block, currentTime, render: true); @@ -348,7 +351,14 @@ public void UnstageTransactions(ISet> transactions) transactions.Select(tx => tx.Id).ToImmutableHashSet()); } - public long GetNonce(Address address) + /// + /// Gets next of the address. + /// + /// The from which to obtain the + /// value. + /// The next value of the + /// . + public long GetNextTxNonce(Address address) { long nonce = Store.GetTxNonce(Id.ToString(), address); @@ -380,11 +390,9 @@ DateTimeOffset currentTime @namespace, index - 1 ); - List> transactions = Store + IEnumerable> transactions = Store .IterateStagedTransactionIds() - .Select(Store.GetTransaction) - .OrderBy(tx => tx.Nonce) - .ToList(); + .Select(Store.GetTransaction); Block block = Block.Mine( index: index, @@ -402,6 +410,41 @@ DateTimeOffset currentTime public Block MineBlock(Address miner) => MineBlock(miner, DateTimeOffset.UtcNow); + /// + /// Creates a new and stage the transaction. + /// + /// A of the account who creates and + /// signs a new transaction. + /// A list of s to include to a new transaction. + /// + /// es whose states affected by + /// . + /// The time this is created and + /// signed. + /// A created new signed by the given + /// . + /// + public Transaction MakeTransaction( + PrivateKey privateKey, + IEnumerable actions, + IImmutableSet
updatedAddresses = null, + DateTimeOffset? timestamp = null) + { + timestamp = timestamp ?? DateTimeOffset.UtcNow; + lock (_txLock) + { + Transaction tx = Transaction.Create( + GetNextTxNonce(privateKey.PublicKey.ToAddress()), + privateKey, + actions, + updatedAddresses, + timestamp); + StageTransactions(new HashSet> { tx }); + + return tx; + } + } + internal void Append( Block block, DateTimeOffset currentTime, diff --git a/Libplanet/Blockchain/MineBlockEventArgs.cs b/Libplanet/Blockchain/MineBlockEventArgs.cs new file mode 100644 index 00000000000..22ba82d9c98 --- /dev/null +++ b/Libplanet/Blockchain/MineBlockEventArgs.cs @@ -0,0 +1,16 @@ +using Libplanet.Action; +using Libplanet.Blocks; + +namespace Libplanet.Blockchain +{ + public class MineBlockEventArgs + where T : IAction, new() + { + public MineBlockEventArgs(Block block) + { + Block = block; + } + + public Block Block { get; } + } +} diff --git a/Libplanet/Blocks/Block.cs b/Libplanet/Blocks/Block.cs index 361cb9fb4dc..99f36a41544 100644 --- a/Libplanet/Blocks/Block.cs +++ b/Libplanet/Blocks/Block.cs @@ -38,7 +38,10 @@ public Block( Miner = miner; PreviousHash = previousHash; Timestamp = timestamp; - Transactions = transactions; + + // FIXME: We need to fix this to solve + // https://github.com/planetarium/libplanet/issues/244. + Transactions = transactions.OrderBy(tx => tx.Nonce).ToList(); Hash = Hashcash.Hash(ToBencodex(false, false)); } diff --git a/Libplanet/Tx/InvalidTxNonceException.cs b/Libplanet/Tx/InvalidTxNonceException.cs index ecd4e563083..cedc2326934 100644 --- a/Libplanet/Tx/InvalidTxNonceException.cs +++ b/Libplanet/Tx/InvalidTxNonceException.cs @@ -6,7 +6,7 @@ namespace Libplanet.Tx { /// /// The exception that is thrown when the - /// is different from result of + /// is different from result of /// the . /// [Serializable] @@ -19,7 +19,7 @@ public sealed class InvalidTxNonceException : InvalidTxException /// The invalid 's /// . It is automatically included to /// the string. - /// + /// /// result of the . /// The actual /// . @@ -44,7 +44,7 @@ public InvalidTxNonceException( } /// - /// result of the + /// result of the /// . /// public long ExpectedNonce { get; }