diff --git a/CHANGES.md b/CHANGES.md index e4835e57ecf..4cfdda36507 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,10 @@ To be released. `IActionContext.From` properties to `Signer`. The corresponding parameter names on constructors and methods were also renamed too. + - Old `Transaction.Make()` factory method is replaced by + new `Transaction.Create()` factory method. The `timestamp` parameter + became optional, and the new optional `updatedAddresses` parameter was + added. - Removed `IActionContext.To` property. - Added `AccountStateGetter` delegate to provide a read-only view to account states. diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.cs b/Libplanet.Tests/Blockchain/BlockChainTest.cs index 505c4b517d2..101e14b8b00 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.cs @@ -109,11 +109,9 @@ public void ProcessActions() TargetAddress = _fx.Address1, }, }; - Transaction tx1 = Transaction.Make( + Transaction tx1 = Transaction.Create( new PrivateKey(), - ImmutableHashSet.Create(_fx.Address1), - actions1, - DateTimeOffset.UtcNow + actions1 ); _blockChain.StageTransactions(new HashSet> { tx1 }); @@ -137,11 +135,9 @@ public void ProcessActions() TargetAddress = _fx.Address1, }, }; - Transaction tx2 = Transaction.Make( + Transaction tx2 = Transaction.Create( new PrivateKey(), - ImmutableHashSet.Create(_fx.Address1), - actions2, - DateTimeOffset.UtcNow + actions2 ); _blockChain.StageTransactions(new HashSet> { tx2 }); @@ -266,15 +262,9 @@ public void EvaluateActions() long blockIndex = 0; TestEvaluateAction action = new TestEvaluateAction(); - Transaction tx1 = Transaction.Make( + Transaction tx1 = Transaction.Create( fromPrivateKey, - new[] - { - TestEvaluateAction.SignerKey, - TestEvaluateAction.BlockIndexKey, - }.ToImmutableHashSet(), - new[] { action }, - DateTimeOffset.UtcNow + new[] { action } ); _blockChain.StageTransactions(new HashSet> { tx1 }); diff --git a/Libplanet.Tests/Blocks/BlockTest.cs b/Libplanet.Tests/Blocks/BlockTest.cs index b5239372593..25f33580417 100644 --- a/Libplanet.Tests/Blocks/BlockTest.cs +++ b/Libplanet.Tests/Blocks/BlockTest.cs @@ -186,21 +186,17 @@ public void EvaluateActions() genesis, new[] { - Transaction.Make( + Transaction.Create( _fx.TxFixture.PrivateKey, - ImmutableHashSet
.Empty, new BaseAction[] { MakeAction(addresses[0], 'A'), MakeAction(addresses[1], 'B'), - }, - DateTimeOffset.UtcNow + } ), - Transaction.Make( + Transaction.Create( _fx.TxFixture.PrivateKey, - ImmutableHashSet
.Empty, - new BaseAction[] { MakeAction(addresses[2], 'C') }, - DateTimeOffset.UtcNow + new BaseAction[] { MakeAction(addresses[2], 'C') } ), } ); @@ -233,17 +229,13 @@ public void EvaluateActions() blockIdx1, new[] { - Transaction.Make( + Transaction.Create( _fx.TxFixture.PrivateKey, - ImmutableHashSet
.Empty, - new BaseAction[] { MakeAction(addresses[0], 'D') }, - DateTimeOffset.UtcNow + new BaseAction[] { MakeAction(addresses[0], 'D') } ), - Transaction.Make( + Transaction.Create( _fx.TxFixture.PrivateKey, - ImmutableHashSet
.Empty, - new BaseAction[] { MakeAction(addresses[3], 'E') }, - DateTimeOffset.UtcNow + new BaseAction[] { MakeAction(addresses[3], 'E') } ), } ); @@ -345,11 +337,29 @@ public void ValidateInvalidTxPublicKey() [Fact] public void ValidateInvalidTxUpdatedAddresses() { - Transaction invalidTx = Transaction.Make( - _fx.TxFixture.PrivateKey, - ImmutableHashSet
.Empty, - _fx.TxFixture.TxWithActions.Actions, - DateTimeOffset.UtcNow + ImmutableArray> rawActions = + _fx.TxFixture.TxWithActions + .ToRawTransaction(false).Actions.ToImmutableArray(); + RawTransaction rawTxWithoutSig = new RawTransaction( + _fx.TxFixture.Address.ToByteArray(), + new byte[][] { }, + _fx.TxFixture.PublicKey.Format(false), + DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.ffffffZ"), + rawActions, + new byte[0] + ); + byte[] sig = _fx.TxFixture.PrivateKey.Sign( + new Transaction(rawTxWithoutSig).ToBencodex(false) + ); + var invalidTx = new Transaction( + new RawTransaction( + rawTxWithoutSig.Signer, + rawTxWithoutSig.UpdatedAddresses, + rawTxWithoutSig.PublicKey, + rawTxWithoutSig.Timestamp, + rawTxWithoutSig.Actions, + sig + ) ); Block invalidBlock = MineNext( _fx.Next, diff --git a/Libplanet.Tests/Common/Action/Sleep.cs b/Libplanet.Tests/Common/Action/Sleep.cs index 29a4905c6a9..11fe07b3d3b 100644 --- a/Libplanet.Tests/Common/Action/Sleep.cs +++ b/Libplanet.Tests/Common/Action/Sleep.cs @@ -26,7 +26,11 @@ public override IAccountStateDelta Execute(IActionContext context) public override void LoadPlainValue( IImmutableDictionary plainValue) { - ZoneId = (int)(BigInteger)plainValue["zone_id"]; + object serialized = plainValue["zone_id"]; + + // FIXME: The reason why the type of the serialized value is not + // consistent should be analyzed. + ZoneId = serialized is BigInteger v ? (int)v : (int)serialized; } } } diff --git a/Libplanet.Tests/Net/SwarmTest.cs b/Libplanet.Tests/Net/SwarmTest.cs index 1a6b85c87ef..7795a4da1df 100644 --- a/Libplanet.Tests/Net/SwarmTest.cs +++ b/Libplanet.Tests/Net/SwarmTest.cs @@ -413,7 +413,7 @@ public async Task CanGetBlock() } [Fact] - public async Task CanGetTx() + public async Task GetTx() { Swarm swarmA = _swarms[0]; Swarm swarmB = _swarms[1]; @@ -421,11 +421,9 @@ public async Task CanGetTx() BlockChain chainA = _blockchains[0]; BlockChain chainB = _blockchains[1]; - Transaction tx = Transaction.Make( + Transaction tx = Transaction.Create( new PrivateKey(), - ImmutableHashSet
.Empty, - new BaseAction[] { }, - DateTimeOffset.UtcNow + new BaseAction[0] ); chainB.StageTransactions(new[] { tx }.ToHashSet()); chainB.MineBlock(_fx1.Address1); @@ -457,7 +455,7 @@ public async Task CanGetTx() } [Fact] - public async Task CanBroadcastTx() + public async Task BroadcastTx() { Swarm swarmA = _swarms[0]; Swarm swarmB = _swarms[1]; @@ -467,11 +465,9 @@ public async Task CanBroadcastTx() BlockChain chainB = _blockchains[1]; BlockChain chainC = _blockchains[2]; - Transaction tx = Transaction.Make( + Transaction tx = Transaction.Create( new PrivateKey(), - ImmutableHashSet
.Empty, - new BaseAction[] { }, - DateTimeOffset.UtcNow + new BaseAction[] { } ); chainA.StageTransactions(new[] { tx }.ToHashSet()); diff --git a/Libplanet.Tests/Store/FileStoreFixture.cs b/Libplanet.Tests/Store/FileStoreFixture.cs index 4ae7ec59077..404cd120891 100644 --- a/Libplanet.Tests/Store/FileStoreFixture.cs +++ b/Libplanet.Tests/Store/FileStoreFixture.cs @@ -130,10 +130,10 @@ public void Dispose() var privateKey = new PrivateKey(); var timestamp = new DateTimeOffset(2018, 11, 21, 0, 0, 0, TimeSpan.Zero); - return Transaction.Make( + return Transaction.Create( privateKey, - updatedAddresses ?? ImmutableHashSet
.Empty, actions ?? new BaseAction[0], + updatedAddresses, timestamp ); } diff --git a/Libplanet.Tests/Tx/TransactionTest.cs b/Libplanet.Tests/Tx/TransactionTest.cs index 9c21ce72b12..41443b5ba7f 100644 --- a/Libplanet.Tests/Tx/TransactionTest.cs +++ b/Libplanet.Tests/Tx/TransactionTest.cs @@ -22,7 +22,7 @@ public TransactionTest() } [Fact] - public void Make() + public void Create() { var privateKey = new PrivateKey( new byte[] @@ -33,11 +33,12 @@ public void Make() 0x7b, 0x76, } ); - var timestamp = new DateTimeOffset(2018, 11, 21, 0, 0, 0, TimeSpan.Zero); - Transaction tx = Transaction.Make( + var timestamp = + new DateTimeOffset(2018, 11, 21, 0, 0, 0, TimeSpan.Zero); + Transaction tx = Transaction.Create( privateKey, - ImmutableHashSet
.Empty, new BaseAction[0], + ImmutableHashSet
.Empty, timestamp ); @@ -73,24 +74,72 @@ public void Make() ), tx.Id ); + } + + [Fact] + public void CreateWithDefaultUpdatedAddresses() + { + Transaction emptyTx = Transaction.Create( + _fx.PrivateKey, + new BaseAction[0] + ); + Assert.Empty(emptyTx.UpdatedAddresses); + Transaction tx = Transaction.Create( + _fx.PrivateKey, + _fx.TxWithActions.Actions + ); + Assert.Equal( + new[] { _fx.Address }.ToImmutableHashSet(), + tx.UpdatedAddresses + ); + + Address additionalAddr = new PrivateKey().PublicKey.ToAddress(); + Transaction txWithAddr = Transaction.Create( + _fx.PrivateKey, + _fx.TxWithActions.Actions, + new[] { additionalAddr }.ToImmutableHashSet() + ); + Assert.Equal( + new[] { _fx.Address, additionalAddr }.ToHashSet(), + txWithAddr.UpdatedAddresses.ToHashSet() + ); + } + + [Fact] + public void CreateWithDefaultTimestamp() + { + DateTimeOffset rightBefore = DateTimeOffset.UtcNow; + Transaction tx = Transaction.Create( + _fx.PrivateKey, + new BaseAction[0], + ImmutableHashSet
.Empty + ); + DateTimeOffset rightAfter = DateTimeOffset.UtcNow; + + Assert.InRange(tx.Timestamp, rightBefore, rightAfter); + } + + [Fact] + public void CreateWithMissingRequiredArguments() + { // The privateKey parameter cannot be null. Assert.Throws(() => - Transaction.Make( + Transaction.Create( null, - ImmutableHashSet
.Empty, new BaseAction[0], - timestamp + ImmutableHashSet
.Empty, + DateTimeOffset.UtcNow ) ); // The actions parameter cannot be null. Assert.Throws(() => - Transaction.Make( - privateKey, - ImmutableHashSet
.Empty, + Transaction.Create( + _fx.PrivateKey, null, - timestamp + ImmutableHashSet
.Empty, + DateTimeOffset.UtcNow ) ); } @@ -214,7 +263,7 @@ public void MakeWithSignature() [Fact] public void ToBencodex() { - var expected = new byte[] + byte[] expected = { 0x64, 0x37, 0x3a, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x6c, 0x65, 0x31, 0x30, 0x3a, 0x70, 0x75, 0x62, 0x6c, 0x69, @@ -513,9 +562,8 @@ public void Evaluate() new PrivateKey().PublicKey.ToAddress(), new PrivateKey().PublicKey.ToAddress(), }; - Transaction tx = Transaction.Make( + Transaction tx = Transaction.Create( _fx.PrivateKey, - ImmutableHashSet
.Empty, new BaseAction[] { new Attack @@ -536,8 +584,7 @@ public void Evaluate() Target = "t2", TargetAddress = addresses[0], }, - }, - DateTimeOffset.UtcNow + } ); IAccountStateDelta delta = tx.EvaluateActions( default, @@ -573,11 +620,10 @@ public void Validate() } ); var timestamp = new DateTimeOffset(2018, 11, 21, 0, 0, 0, TimeSpan.Zero); - Transaction tx = Transaction.Make( + Transaction tx = Transaction.Create( privateKey, - ImmutableHashSet
.Empty, new BaseAction[0], - timestamp + timestamp: timestamp ); tx.Validate(); @@ -646,11 +692,10 @@ public void ConvertToRaw() } ); var timestamp = new DateTimeOffset(2018, 11, 21, 0, 0, 0, TimeSpan.Zero); - Transaction tx = Transaction.Make( + Transaction tx = Transaction.Create( privateKey, - ImmutableHashSet
.Empty, new BaseAction[0], - timestamp + timestamp: timestamp ); Assert.Equal( @@ -706,11 +751,9 @@ public void SignatureBufferIsIsolated() public void ActionsAreIsolatedFromOutside() { var actions = new List(); - Transaction t1 = Transaction.Make( + Transaction t1 = Transaction.Create( _fx.PrivateKey, - ImmutableHashSet
.Empty, - actions, - DateTimeOffset.UtcNow + actions ); var t2 = new Transaction( _fx.PrivateKey.PublicKey.ToAddress(), diff --git a/Libplanet.Tests/Tx/TxFixture.cs b/Libplanet.Tests/Tx/TxFixture.cs index fc8c43cdfe0..0a466429c93 100644 --- a/Libplanet.Tests/Tx/TxFixture.cs +++ b/Libplanet.Tests/Tx/TxFixture.cs @@ -24,11 +24,10 @@ public TxFixture() var recipient = new Address(PrivateKey.PublicKey); var timestamp = new DateTimeOffset(2018, 11, 21, 0, 0, 0, TimeSpan.Zero); - Tx = Transaction.Make( + Tx = Transaction.Create( PrivateKey, - ImmutableHashSet
.Empty, new BaseAction[0], - timestamp + timestamp: timestamp ); BaseAction[] actions = { @@ -43,11 +42,10 @@ public TxFixture() ZoneId = 10, }, }; - TxWithActions = Transaction.Make( + TxWithActions = Transaction.Create( PrivateKey, - ImmutableHashSet.Create(recipient), actions, - timestamp + timestamp: timestamp ); } diff --git a/Libplanet/Tx/Transaction.cs b/Libplanet/Tx/Transaction.cs index e6d6ba19a36..0bc038e75d7 100644 --- a/Libplanet/Tx/Transaction.cs +++ b/Libplanet/Tx/Transaction.cs @@ -11,6 +11,7 @@ using Libplanet.Action; using Libplanet.Crypto; using Libplanet.Serialization; +using Uno.Extensions; namespace Libplanet.Tx { @@ -40,6 +41,14 @@ public class Transaction : ISerializable, IEquatable> /// /// Creates a new . + /// This constructor takes all required and only required values + /// for a , so gives you full control of + /// creating a , and in other words, + /// this constructor is only useful when all details of + /// a need to be manually adjusted. + /// For the most cases, the façade factory is more useful. /// /// An of the account /// who signs this transaction. If this is not derived from FromBencodex(byte[] bytes) /// A façade factory to create a new . /// Unlike the - /// constructor, it automatically signs, - /// and fills the appropriate and - /// properties using the given - /// . However, the in itself is not included in the created - /// . + /// constructor, it automatically fills the following values from: + /// + /// + /// Property + /// Parameter the filled value derived from + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// and + /// + /// + /// + /// Note that the in itself is not + /// included in the created . /// + /// + /// This factory method tries its best to fill the property by actually evaluating + /// the given , but remember that its result + /// is approximated in some degree, because the result of + /// are not deterministic until + /// the belongs to a . + /// If an depends on previous states or + /// some randomness to determine what to update, + /// the automatically filled became + /// mismatched from the es + /// actually update after + /// a is mined. + /// Although such case would be rare, a programmer could manually give + /// the parameter + /// the es they predict to be updated. + /// If an oversimplifies the assumption + /// about the it belongs to, + /// runtime exceptions could be thrown from this factory method. + /// Only solution to that is not to oversimplify things. + /// /// A of the account /// who creates and signs a new transaction. This key is used to fill /// the , , and /// properties, but this in itself is not /// included in the transaction. - /// es whose - /// states affected by . This goes to - /// the property. /// A list of s. This /// can be empty, but cannot be null. This goes to - /// the property. + /// the property, and s + /// are evaluated before a is created + /// in order to fill the . See also + /// Remarks section. + /// es whose + /// states affected by . + /// These es are also included in + /// the property, besides + /// es projected by evaluating + /// . See also Remarks section. + /// /// The time this /// is created and signed. This goes to the - /// property. + /// property. If null (which is default) is passed this will + /// be the current time. /// A created new signed by /// the given . /// Thrown when null /// is passed to or /// or . /// - public static Transaction Make( + public static Transaction Create( PrivateKey privateKey, - IImmutableSet
updatedAddresses, IEnumerable actions, - DateTimeOffset timestamp) + IImmutableSet
updatedAddresses = null, + DateTimeOffset? timestamp = null + ) { - if (privateKey == null) + if (ReferenceEquals(privateKey, null)) { throw new ArgumentNullException(nameof(privateKey)); } @@ -270,20 +326,57 @@ public static Transaction FromBencodex(byte[] bytes) PublicKey publicKey = privateKey.PublicKey; var signer = new Address(publicKey); - var tx = new Transaction( + if (ReferenceEquals(updatedAddresses, null)) + { + updatedAddresses = ImmutableHashSet
.Empty; + } + + DateTimeOffset ts = timestamp ?? DateTimeOffset.UtcNow; + + ImmutableArray actionsArray = actions.ToImmutableArray(); + byte[] payload = new Transaction( signer, publicKey, updatedAddresses, - timestamp, - actions - ); + ts, + actionsArray + ).ToBencodex(false); + + if (!actionsArray.IsEmpty) + { + IAccountStateDelta delta = new Transaction( + signer, + publicKey, + updatedAddresses, + ts, + actionsArray + ).EvaluateActions( + default(HashDigest), + 0, + new AccountStateDeltaImpl(_ => null) + ); + if (!updatedAddresses.IsSupersetOf(delta.UpdatedAddresses)) + { + updatedAddresses = + updatedAddresses.Union(delta.UpdatedAddresses); + payload = new Transaction( + signer, + publicKey, + updatedAddresses, + ts, + actionsArray + ).ToBencodex(false); + } + } + + byte[] sig = privateKey.Sign(payload); return new Transaction( signer, publicKey, updatedAddresses, - timestamp, - actions, - privateKey.Sign(tx.ToBencodex(false)) + ts, + actionsArray, + sig ); } @@ -348,7 +441,7 @@ IAccountStateDelta previousStates { int seed = BitConverter.ToInt32(blockHash.ToByteArray(), 0) ^ - BitConverter.ToInt32(Signature, 0); + (Signature.Empty() ? 0 : BitConverter.ToInt32(Signature, 0)); IAccountStateDelta states = previousStates; foreach (T action in Actions) {