diff --git a/src/FederationSetup/GenesisMiner.cs b/src/FederationSetup/GenesisMiner.cs index dcc55501b70..518ada1f631 100644 --- a/src/FederationSetup/GenesisMiner.cs +++ b/src/FederationSetup/GenesisMiner.cs @@ -86,7 +86,10 @@ public static Block MineGenesisBlock(SmartContractPoAConsensusFactory consensusF Transaction txNew = consensusFactory.CreateTransaction(); txNew.Version = (uint)version; - txNew.Time = unixTime; + + if (txNew is IPosTransactionWithTime posTx) + posTx.Time = unixTime; + txNew.AddInput(new TxIn() { ScriptSig = new Script( diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/StakeValidatorTests.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/StakeValidatorTests.cs index bb1d4977e06..509bb8ba2e0 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/StakeValidatorTests.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/StakeValidatorTests.cs @@ -1029,6 +1029,7 @@ public void CheckKernel_ValidKernelCheck_DoesNotThrowConsensusError() var outPoint = new OutPoint(transaction, 1); var headerbits = Target.Difficulty1.ToCompact(); + // TODO: Is this intentionally using the time of the stub coinbase instead of the coinstake? This looks like a bug var transactionTime = ((PosTransaction)stakableHeader.Block.Transactions[0]).Time; this.stakeValidator.CheckKernel(new PosRuleContext(), header, headerbits, transactionTime, outPoint); diff --git a/src/Stratis.Bitcoin.Features.Wallet/WalletRPCController.cs b/src/Stratis.Bitcoin.Features.Wallet/WalletRPCController.cs index b4611f7b273..db1c2e42da7 100644 --- a/src/Stratis.Bitcoin.Features.Wallet/WalletRPCController.cs +++ b/src/Stratis.Bitcoin.Features.Wallet/WalletRPCController.cs @@ -363,6 +363,7 @@ public GetTransactionModel GetTransaction(string txid) string hex; if (transactionFromStore != null) { + // TODO: Use block header time only. The transaction times will need to be uniformly set to a fixed value when an anti-malleability softfork activates if (transactionFromStore is IPosTransactionWithTime posTrx) transactionTime = Utils.UnixTimeToDateTime(posTrx.Time); else diff --git a/src/Stratis.Bitcoin.IntegrationTests/Wallet/WalletRPCControllerTest.cs b/src/Stratis.Bitcoin.IntegrationTests/Wallet/WalletRPCControllerTest.cs index 8cb1d40b88f..6a50b35b819 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/Wallet/WalletRPCControllerTest.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/Wallet/WalletRPCControllerTest.cs @@ -253,6 +253,7 @@ public async Task GetTransactionOnUnconfirmedTransactionAsync() resultSendingWallet.BlockIndex.Should().BeNull(); resultSendingWallet.BlockTime.Should().BeNull(); resultSendingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); + resultSendingWallet.TransactionTime.Should().Be(((PosTransaction)trx).Time); resultSendingWallet.Details.Count.Should().Be(1); GetTransactionDetailsModel detailsSendingWallet = resultSendingWallet.Details.Single(); diff --git a/src/Stratis.Features.FederatedPeg.Tests/Wallet/FederationWalletManagerTests.cs b/src/Stratis.Features.FederatedPeg.Tests/Wallet/FederationWalletManagerTests.cs index a690c782463..018a31398cf 100644 --- a/src/Stratis.Features.FederatedPeg.Tests/Wallet/FederationWalletManagerTests.cs +++ b/src/Stratis.Features.FederatedPeg.Tests/Wallet/FederationWalletManagerTests.cs @@ -56,7 +56,7 @@ public void RemoveTransactions_When_OverwritingSpendDetails() // Create a spending transaction that spends transaction A Transaction transactionB = this.network.CreateTransaction(); - transactionB.Time = transactionA.Time + 1; + ((PosTransaction)transactionB).Time = ((PosTransaction)transactionA).Time + 1; transactionB.AddInput(transactionA, 0); transactionB.AddOutput(new TxOut(Money.Coins(5), this.federationMultiSigAddress)); federationWalletManager.ProcessTransaction(transactionB); @@ -74,7 +74,7 @@ public void RemoveTransactions_When_OverwritingSpendDetails() // Create another spending transaction that also spends transaction A Transaction transactionC = this.network.CreateTransaction(); - transactionC.Time = transactionB.Time + 1; + ((PosTransaction)transactionC).Time = ((PosTransaction)transactionB).Time + 1; transactionC.AddInput(transactionA, 0); transactionC.AddOutput(new TxOut(Money.Coins(5), this.federationMultiSigAddress)); federationWalletManager.ProcessTransaction(transactionC); diff --git a/src/Stratis.Features.FederatedPeg/TargetChain/SigningUtils.cs b/src/Stratis.Features.FederatedPeg/TargetChain/SigningUtils.cs index 993a3880dec..35e456c015a 100644 --- a/src/Stratis.Features.FederatedPeg/TargetChain/SigningUtils.cs +++ b/src/Stratis.Features.FederatedPeg/TargetChain/SigningUtils.cs @@ -56,10 +56,12 @@ public static bool TemplatesMatch(Network network, Transaction partialTransactio { if (network.Consensus.IsProofOfStake) { - if (partialTransaction1.Time != partialTransaction2.Time) - { + // As a proof of stake network will be using a PosConsensusFactory, this should never happen, but it is checked for safety. + if (!(partialTransaction1 is IPosTransactionWithTime tx1) || !(partialTransaction2 is IPosTransactionWithTime tx2)) + return false; + + if (tx1.Time != tx2.Time) return false; - } } if ((partialTransaction1.Inputs.Count != partialTransaction2.Inputs.Count) || diff --git a/src/Stratis.Features.FederatedPeg/TargetChain/WithdrawalHistoryProvider.cs b/src/Stratis.Features.FederatedPeg/TargetChain/WithdrawalHistoryProvider.cs index 132afa240a6..50d9b2ec773 100644 --- a/src/Stratis.Features.FederatedPeg/TargetChain/WithdrawalHistoryProvider.cs +++ b/src/Stratis.Features.FederatedPeg/TargetChain/WithdrawalHistoryProvider.cs @@ -64,7 +64,8 @@ public List GetHistory(int maximumEntriesToReturn) var result = new List(); ICrossChainTransfer[] transfers = this.crossChainTransferStore.GetTransfersByStatus(new[] { CrossChainTransferStatus.SeenInBlock }); - foreach (ICrossChainTransfer transfer in transfers.OrderByDescending(t => t.PartialTransaction.Time)) + // TODO: Need to check if this is only used for wallet UI purposes and has no consensus implications + foreach (ICrossChainTransfer transfer in transfers.OrderByDescending(t => t.BlockHeight)) // t.PartialTransaction.Time { if (maximumEntriesToReturn-- <= 0) break; diff --git a/src/Stratis.Features.FederatedPeg/Wallet/FederationWalletManager.cs b/src/Stratis.Features.FederatedPeg/Wallet/FederationWalletManager.cs index 889781038cf..7c4aee8f757 100644 --- a/src/Stratis.Features.FederatedPeg/Wallet/FederationWalletManager.cs +++ b/src/Stratis.Features.FederatedPeg/Wallet/FederationWalletManager.cs @@ -704,7 +704,7 @@ private void AddTransactionToWallet(Transaction transaction, TxOut utxo, int? bl BlockHeight = blockHeight, BlockHash = blockHash, Id = transactionHash, - CreationTime = DateTimeOffset.FromUnixTimeSeconds(block?.Header.Time ?? transaction.Time), + CreationTime = DateTimeOffset.FromUnixTimeSeconds(block?.Header.Time ?? this.dateTimeProvider.GetTime()), Index = index, ScriptPubKey = script }; @@ -843,7 +843,7 @@ private SpendingDetails BuildSpendingDetails(Transaction transaction, { TransactionId = transaction.GetHash(), Payments = payments, - CreationTime = DateTimeOffset.FromUnixTimeSeconds(block?.Header.Time ?? transaction.Time), + CreationTime = DateTimeOffset.FromUnixTimeSeconds(block?.Header.Time ?? this.dateTimeProvider.GetTime()), BlockHeight = blockHeight, BlockHash = blockHash, Transaction = (blockHeight > 0) ? null : transaction, diff --git a/src/Stratis.Features.SQLiteWalletRepository/External/ITransactionsToLists.cs b/src/Stratis.Features.SQLiteWalletRepository/External/ITransactionsToLists.cs index dc45817710f..65937336c4e 100644 --- a/src/Stratis.Features.SQLiteWalletRepository/External/ITransactionsToLists.cs +++ b/src/Stratis.Features.SQLiteWalletRepository/External/ITransactionsToLists.cs @@ -92,15 +92,22 @@ public bool ProcessTransactions(IEnumerable transactions, HashHeigh { TxIn txIn = tx.Inputs[i]; - if (transactionsOfInterest.Contains(txIn.PrevOut, out HashSet addresses)) + if (!transactionsOfInterest.Contains(txIn.PrevOut, out HashSet addresses)) + continue; + + // Record our outputs that are being spent. + foreach (AddressIdentifier address in addresses) { - // Record our outputs that are being spent. - foreach (AddressIdentifier address in addresses) - RecordSpend(block, txIn, address.ScriptPubKey, tx.IsCoinBase | tx.IsCoinStake, tx.Time, tx.TotalOut, txId, i); + // TODO: Probably need to derive time explicitly from the block header. How were PoW transactions being handled here? + long time = 0; + if (tx is IPosTransactionWithTime posTx) + time = posTx.Time; - additions = true; - addSpendTx = true; + RecordSpend(block, txIn, address.ScriptPubKey, tx.IsCoinBase | tx.IsCoinStake, time, tx.TotalOut, txId, i); } + + additions = true; + addSpendTx = true; } // Build temp.Outputs. @@ -119,41 +126,46 @@ public bool ProcessTransactions(IEnumerable transactions, HashHeigh bool containsAddress = addressesOfInterest.Contains(pubKeyScript, out AddressIdentifier address); // Paying to one of our addresses? - if (addSpendTx || containsAddress) + if (!addSpendTx && !containsAddress) + continue; + + // Check if top-up is required. + if (containsAddress && address != null) { - // Check if top-up is required. - if (containsAddress && address != null) + // Get the top-up tracker that applies to this account and address type. + ITopUpTracker tracker = this.GetTopUpTracker(address); + if (!tracker.IsWatchOnlyAccount) { - // Get the top-up tracker that applies to this account and address type. - ITopUpTracker tracker = this.GetTopUpTracker(address); - if (!tracker.IsWatchOnlyAccount) + // If an address inside the address buffer is being used then top-up the buffer. + while (address.AddressIndex >= tracker.NextAddressIndex) { - // If an address inside the address buffer is being used then top-up the buffer. - while (address.AddressIndex >= tracker.NextAddressIndex) - { - AddressIdentifier newAddress = tracker.CreateAddress(); - - // Add the new address to our addresses of interest. - addressesOfInterest.AddTentative(Script.FromHex(newAddress.ScriptPubKey), - new AddressIdentifier() - { - WalletId = newAddress.WalletId, - AccountIndex = newAddress.AccountIndex, - AddressType = newAddress.AddressType, - AddressIndex = newAddress.AddressIndex - }); - } + AddressIdentifier newAddress = tracker.CreateAddress(); + + // Add the new address to our addresses of interest. + addressesOfInterest.AddTentative(Script.FromHex(newAddress.ScriptPubKey), + new AddressIdentifier() + { + WalletId = newAddress.WalletId, + AccountIndex = newAddress.AccountIndex, + AddressType = newAddress.AddressType, + AddressIndex = newAddress.AddressIndex + }); } } + } - // Record outputs received by our wallets. - this.RecordReceipt(block, pubKeyScript, txOut, tx.IsCoinBase | tx.IsCoinStake, tx.Time, txId, i, containsAddress && address.AddressType == 1); + // TODO: Probably need to derive time explicitly from the block header. How were PoW transactions being handled here? + long time = 0; + if (tx is IPosTransactionWithTime posTx) + time = posTx.Time; - additions = true; + // Record outputs received by our wallets. + this.RecordReceipt(block, pubKeyScript, txOut, tx.IsCoinBase | tx.IsCoinStake, time, txId, i, containsAddress && address.AddressType == 1); - if (containsAddress) - transactionsOfInterest.AddTentative(new OutPoint(txId, i), address); - } + additions = true; + + if (containsAddress) + transactionsOfInterest.AddTentative(new OutPoint(txId, i), address); } } } diff --git a/src/Stratis.Sidechains.Networks/CirrusNetwork.cs b/src/Stratis.Sidechains.Networks/CirrusNetwork.cs index 7d2f59967d9..bb589b7f87e 100644 --- a/src/Stratis.Sidechains.Networks/CirrusNetwork.cs +++ b/src/Stratis.Sidechains.Networks/CirrusNetwork.cs @@ -17,7 +17,11 @@ public static NetworksSelector NetworksSelector public static Block CreateGenesis(SmartContractCollateralPoAConsensusFactory consensusFactory, uint genesisTime, uint nonce, uint bits, int version, Money reward, string coinbaseText) { Transaction genesisTransaction = consensusFactory.CreateTransaction(); - genesisTransaction.Time = genesisTime; + + // TODO: If the PoW/PoA networks do not use a PosConsensusFactory, their transactions will now be missing timestamps that were historically included for hashing. + if (genesisTransaction is IPosTransactionWithTime posTx) + posTx.Time = genesisTime; + genesisTransaction.Version = 1; genesisTransaction.AddInput(new TxIn() { diff --git a/src/Stratis.SmartContracts.CLR.Tests/ContractTransferProcessorTests.cs b/src/Stratis.SmartContracts.CLR.Tests/ContractTransferProcessorTests.cs index cd62b7e11a0..4d41c519b92 100644 --- a/src/Stratis.SmartContracts.CLR.Tests/ContractTransferProcessorTests.cs +++ b/src/Stratis.SmartContracts.CLR.Tests/ContractTransferProcessorTests.cs @@ -164,7 +164,8 @@ public void NoBalance_TxValue1_TransferValue1() // Condensing tx generated. 1 input from tx and 2 outputs - 1 for each contract and receiver Transaction internalTransaction = this.transferProcessor.Process(stateMock.Object, contractAddress, txContextMock.Object, transferInfos, false); Assert.NotNull(internalTransaction); - Assert.Equal(txContextMock.Object.Time, internalTransaction.Time); + if (internalTransaction is IPosTransactionWithTime posTx) + Assert.Equal(txContextMock.Object.Time, posTx.Time); Assert.Single(internalTransaction.Inputs); Assert.Equal(2, internalTransaction.Outputs.Count); Assert.Equal(txContextMock.Object.TransactionHash, internalTransaction.Inputs[0].PrevOut.Hash); @@ -242,7 +243,8 @@ public void HasBalance_TxValue1_TransferValue0() // Condensing tx generated. 2 inputs. Current tx and stored spendable output. 1 output. Transaction internalTransaction = this.transferProcessor.Process(stateMock.Object, contractAddress, txContextMock.Object, transferInfos, false); Assert.NotNull(internalTransaction); - Assert.Equal(txContextMock.Object.Time, internalTransaction.Time); + if (internalTransaction is IPosTransactionWithTime posTx) + Assert.Equal(txContextMock.Object.Time, posTx.Time); Assert.Equal(2, internalTransaction.Inputs.Count); Assert.Single(internalTransaction.Outputs); Assert.Equal(txContextMock.Object.TransactionHash, internalTransaction.Inputs[0].PrevOut.Hash); @@ -292,7 +294,8 @@ public void HasBalance_TxValue0_TransferValue1() // Condensing tx generated. 1 input. 2 outputs for each receiver and contract. Transaction internalTransaction = this.transferProcessor.Process(stateMock.Object, contractAddress, txContextMock.Object, transferInfos, false); Assert.NotNull(internalTransaction); - Assert.Equal(txContextMock.Object.Time, internalTransaction.Time); + if (internalTransaction is IPosTransactionWithTime posTx) + Assert.Equal(txContextMock.Object.Time, posTx.Time); Assert.Single(internalTransaction.Inputs); Assert.Equal(2, internalTransaction.Outputs.Count); Assert.Equal(new uint256(1), internalTransaction.Inputs[0].PrevOut.Hash); @@ -345,7 +348,8 @@ public void HasBalance_TxValue1_TransferValue1() // Condensing tx generated. 2 inputs from currently stored utxo and current tx. 2 outputs for each receiver and contract. Transaction internalTransaction = this.transferProcessor.Process(stateMock.Object, contractAddress, txContextMock.Object, transferInfos, false); Assert.NotNull(internalTransaction); - Assert.Equal(txContextMock.Object.Time, internalTransaction.Time); + if (internalTransaction is IPosTransactionWithTime posTx) + Assert.Equal(txContextMock.Object.Time, posTx.Time); Assert.Equal(2, internalTransaction.Inputs.Count); Assert.Equal(2, internalTransaction.Outputs.Count); Assert.Equal(new uint256(123), internalTransaction.Inputs[0].PrevOut.Hash); @@ -425,7 +429,8 @@ public void Transfers_Summed_Correctly() // Condensing tx generated. 1 input. 3 outputs with consolidated balances. Transaction internalTransaction = this.transferProcessor.Process(stateMock.Object, contractAddress, txContextMock.Object, transferInfos, false); Assert.NotNull(internalTransaction); - Assert.Equal(txContextMock.Object.Time, internalTransaction.Time); + if (internalTransaction is IPosTransactionWithTime posTx) + Assert.Equal(txContextMock.Object.Time, posTx.Time); Assert.Single(internalTransaction.Inputs); Assert.Equal(3, internalTransaction.Outputs.Count); Assert.Equal(new uint256(1), internalTransaction.Inputs[0].PrevOut.Hash); @@ -463,7 +468,8 @@ public void Execution_Failure_With_Value_No_Transfers_Creates_Refund() string outputAddress = PayToPubkeyHashTemplate.Instance.ExtractScriptPubKeyParameters(refundTransaction.Outputs[0].ScriptPubKey).GetAddress(this.network).ToString(); Assert.Equal(txContextMock.Object.Sender.ToBase58Address(this.network), outputAddress); - Assert.Equal(txContextMock.Object.Time, refundTransaction.Time); + if (refundTransaction is IPosTransactionWithTime posTx) + Assert.Equal(txContextMock.Object.Time, posTx.Time); } [Fact] diff --git a/src/Stratis.SmartContracts.CLR/ResultProcessors/ContractTransferProcessor.cs b/src/Stratis.SmartContracts.CLR/ResultProcessors/ContractTransferProcessor.cs index b5366c6fbf2..6a165a63752 100644 --- a/src/Stratis.SmartContracts.CLR/ResultProcessors/ContractTransferProcessor.cs +++ b/src/Stratis.SmartContracts.CLR/ResultProcessors/ContractTransferProcessor.cs @@ -68,7 +68,9 @@ public Transaction Process(IStateRepository stateSnapshot, private Transaction CreateRefundTransaction(IContractTransactionContext transactionContext) { Transaction tx = this.network.CreateTransaction(); - tx.Time = transactionContext.Time; + + if (tx is IPosTransactionWithTime posTx) + posTx.Time = transactionContext.Time; // Input from contract call var outpoint = new OutPoint(transactionContext.TransactionHash, transactionContext.Nvout); diff --git a/src/Stratis.SmartContracts.Core/ContractTransactionContext.cs b/src/Stratis.SmartContracts.Core/ContractTransactionContext.cs index 6209e8b0f1e..517b565ee5f 100644 --- a/src/Stratis.SmartContracts.Core/ContractTransactionContext.cs +++ b/src/Stratis.SmartContracts.Core/ContractTransactionContext.cs @@ -70,7 +70,8 @@ public uint Time { get { - return this.transaction.Time; + // TODO: Is a zero fallback value acceptable here? + return (this.transaction is IPosTransactionWithTime posTx) ? posTx.Time : 0; } } diff --git a/src/Stratis.SmartContracts.Core/State/AccountAbstractionLayer/TransactionCondenser.cs b/src/Stratis.SmartContracts.Core/State/AccountAbstractionLayer/TransactionCondenser.cs index 6292cac9058..a8f4fd819ad 100644 --- a/src/Stratis.SmartContracts.Core/State/AccountAbstractionLayer/TransactionCondenser.cs +++ b/src/Stratis.SmartContracts.Core/State/AccountAbstractionLayer/TransactionCondenser.cs @@ -82,7 +82,9 @@ public Transaction CreateCondensingTransaction() private Transaction BuildTransaction() { Transaction tx = this.network.CreateTransaction(); - tx.Time = this.transactionContext.Time; // set to time of actual transaction. + + if (tx is IPosTransactionWithTime posTx) + posTx.Time = this.transactionContext.Time; // set to time of actual transaction. foreach (ContractUnspentOutput vin in this.unspents) { diff --git a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractWalletOnPosNetworkTests.cs b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractWalletOnPosNetworkTests.cs index 39a8d52586c..e7089918224 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractWalletOnPosNetworkTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractWalletOnPosNetworkTests.cs @@ -153,7 +153,9 @@ private Transaction BuildTransferContractTransaction(CoreNode scSender, Transact Transaction transferContractTransaction = scSender.FullNode.NodeService().BuildTransaction(txBuildContext); var updatedTransaction = scSender.FullNode.Network.CreateTransaction(); - updatedTransaction.Time = (uint)scSender.FullNode.NodeService().GetAdjustedTimeAsUnixTimestamp(); + + if (updatedTransaction is IPosTransactionWithTime posTx) + posTx.Time = (uint)scSender.FullNode.NodeService().GetAdjustedTimeAsUnixTimestamp(); foreach (var txIn in transferContractTransaction.Inputs) { diff --git a/src/Stratis.SmartContracts.Networks/SmartContractNetworkUtils.cs b/src/Stratis.SmartContracts.Networks/SmartContractNetworkUtils.cs index 41fb921ae44..afd7941a0c6 100644 --- a/src/Stratis.SmartContracts.Networks/SmartContractNetworkUtils.cs +++ b/src/Stratis.SmartContracts.Networks/SmartContractNetworkUtils.cs @@ -21,7 +21,11 @@ public static NBitcoin.Block CreateGenesis(ConsensusFactory consensusFactory, ui var genesisOutputScript = new Script(Op.GetPushOp(Encoders.Hex.DecodeData("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f")), OpcodeType.OP_CHECKSIG); NBitcoin.Transaction genesisTransaction = consensusFactory.CreateTransaction(); - genesisTransaction.Time = genesisTime; + + // TODO: Similar potential problem to Cirrus genesis definition + if (genesisTransaction is IPosTransactionWithTime posTx) + posTx.Time = genesisTime; + genesisTransaction.Version = 1; genesisTransaction.AddInput(new TxIn() {