public object ToJson() { return(new { number = Number?.ToJson(), hash = Hash?.ToJson(), parentHash = ParentHash?.ToJson(), nonce = Nonce?.ToJson(), MixHash = MixHash?.ToJson(), sha3Uncles = Sha3Uncles?.ToJson(), logsBloom = LogsBloom?.ToJson(), transactionsRoot = TransactionsRoot?.ToJson(), stateRoot = StateRoot?.ToJson(), receiptsRoot = ReceiptsRoot?.ToJson(), miner = Miner?.ToJson(), difficulty = Difficulty?.ToJson(), totalDifficulty = TotalDifficulty?.ToJson(), extraData = ExtraData?.ToJson(), size = Size?.ToJson(), gasLimit = GasLimit?.ToJson(), gasUsed = GasUsed?.ToJson(), timestamp = Timestamp?.ToJson(), transactions = Transactions?.Select(x => x.ToJson()).ToArray() ?? TransactionHashes?.Select(x => x.ToJson()).ToArray(), uncles = Uncles?.Select(x => x.ToJson()).ToArray() }); }
public Block CreateBlock() { if (Block is null) { Block = MakeHeader(); if (Block == null) { return(null); } Contract contract = Contract.CreateMultiSigContract(this.M(), Validators); ContractParametersContext sc = new ContractParametersContext(Block); for (int i = 0, j = 0; i < Validators.Length && j < this.M(); i++) { if (CommitPayloads[i]?.ConsensusMessage.ViewNumber != ViewNumber) { continue; } sc.AddSignature(contract, Validators[i], CommitPayloads[i].GetDeserializedMessage <Commit>().Signature); j++; } Block.Witness = sc.GetWitnesses()[0]; Block.Transactions = TransactionHashes.Select(p => Transactions[p]).ToArray(); } return(Block); }
/// <summary> /// Gets the transaction statuses. /// </summary> /// <param name="hashes">The hashes.</param> /// <returns>IObservable<List<TransactionStatusDTO>>.</returns> /// <exception cref="ArgumentNullException">hashes</exception> /// <exception cref="ArgumentException">Collection contains one or more invalid hashes.</exception> public IObservable <List <TransactionStatusDTO> > GetTransactionStatuses(TransactionHashes hashes) { if (hashes == null) { throw new ArgumentNullException(nameof(hashes)); } if (hashes.hashes.Any(hash => hash.Length != 64 || !Regex.IsMatch(hash, @"\A\b[0-9a-fA-F]+\b\Z"))) { throw new ArgumentException("Collection contains one or more invalid hashes."); } return(Observable.FromAsync(async ar => await TransactionRoutesApi.GetTransactionsStatusesAsync(hashes))); }
/// <summary> /// Tries to perform mempool cleanup with the help of the backend. /// </summary> public async Task <bool> TryPerformMempoolCleanupAsync(Func <Uri> destAction, IPEndPoint torSocks) { // If already cleaning, then no need to run it that often. if (Interlocked.CompareExchange(ref _cleanupInProcess, 1, 0) == 1) { return(false); } // This function is designed to prevent forever growing mempool. try { if (!TransactionHashes.Any()) { return(true); // There's nothing to cleanup. } Logger.LogInfo <MempoolService>("Start cleaning out mempool..."); using (var client = new WasabiClient(destAction, torSocks)) { var compactness = 10; var allMempoolHashes = await client.GetMempoolHashesAsync(compactness); var toRemove = TransactionHashes.Where(x => !allMempoolHashes.Contains(x.ToString().Substring(0, compactness))); int removedTxCount = 0; foreach (uint256 tx in toRemove) { if (TransactionHashes.TryRemove(tx)) { removedTxCount++; } } Logger.LogInfo <MempoolService>($"{removedTxCount} transactions were cleaned from mempool."); } return(true); } catch (Exception ex) { Logger.LogWarning <MempoolService>(ex); } finally { Interlocked.Exchange(ref _cleanupInProcess, 0); } return(false); }
/// <summary> /// Get a list of transaction statuses for a given array of transaction hashes /// </summary> /// <param name="transactionHashes">Transaction hashes</param> /// <returns>List<IObservable<TransactionStatus>></returns> public IObservable <List <TransactionStatus> > GetTransactionStatuses(List <string> transactionHashes) { if (transactionHashes.Count < 0) { throw new ArgumentNullException(nameof(transactionHashes)); } var hashes = new TransactionHashes { Hashes = transactionHashes }; var route = $"{BasePath}/transactionStatus/{transactionHashes}"; return(Observable.FromAsync(async ar => await route.PostJsonAsync(transactionHashes).ReceiveJson <List <TransactionStatusDTO> >()) .Select(h => h.Select(hash => new TransactionStatus(hash.Group, hash.Status, hash.Hash, hash.Deadline.ToUInt64(), hash.Height.ToUInt64())).ToList())); }
public Block Add <T>(string hash) where T : ISignable { IsFinalized = false; if (typeof(T) == typeof(Smartcontract)) { SmartcontractHashes.Add(hash); } else if (typeof(T) == typeof(Transaction)) { TransactionHashes.Add(hash); } else { throw new ArgumentException("Wrong type this should never happen!"); } return(this); }
/// <summary> /// Generates the hash of the block /// </summary> /// <param name="block"></param> /// <returns></returns> public IDownloadable GenerateHash() { Curl curl = new Curl(); curl.Absorb(TryteString.FromAsciiString(Height + "").ToTrits()); curl.Absorb(TryteString.FromAsciiString(Time + "").ToTrits()); curl.Absorb(TryteString.FromAsciiString(Owner).ToTrits()); curl.Absorb(TryteString.FromAsciiString(SendTo).ToTrits()); curl.Absorb(TryteString.FromAsciiString(CoinName).ToTrits()); curl.Absorb(TryteString.FromAsciiString(TransactionHashes.HashList(20) + "").ToTrits()); curl.Absorb(TryteString.FromAsciiString(SmartcontractHashes.HashList(20) + "").ToTrits()); var hash = new int[243]; curl.Squeeze(hash); Hash = Converter.TritsToTrytes(hash); return(this); }
public Block CreateBlock() { Block block = MakeHeader(); if (block == null) { return(null); } Contract contract = Contract.CreateMultiSigContract(M, Validators); ContractParametersContext sc = new ContractParametersContext(block); for (int i = 0, j = 0; i < Validators.Length && j < M; i++) { if (Signatures[i] != null) { sc.AddSignature(contract, Validators[i], Signatures[i]); j++; } } sc.Verifiable.Witnesses = sc.GetWitnesses(); block.Transactions = TransactionHashes.Select(p => Transactions[p]).ToArray(); return(block); }
/// <summary> /// Tries to perform mempool cleanup with the help of the connected nodes. /// NOTE: This results in heavy network activity! https://github.com/zkSNACKs/WalletWasabi/issues/1273 /// </summary> public async Task <bool> TryPerformMempoolCleanupAsync(NodesGroup nodes, CancellationToken cancel) { // If already cleaning, then no need to run it that often. if (Interlocked.CompareExchange(ref _cleanupInProcess, 1, 0) == 1) { return(false); } // This function is designed to prevent forever growing mempool. try { if (!TransactionHashes.Any()) { return(true); // There's nothing to cleanup. } var delay = TimeSpan.FromMinutes(1); if (nodes?.ConnectedNodes is null) { return(false); } while (nodes.ConnectedNodes.Count != nodes.MaximumNodeConnection && nodes.ConnectedNodes.All(x => x.IsConnected)) { if (cancel.IsCancellationRequested) { return(false); } Logger.LogInfo <MemPoolService>($"Not all nodes were in a connected state. Delaying mempool cleanup for {delay.TotalSeconds} seconds..."); await Task.Delay(delay, cancel); } Logger.LogInfo <MemPoolService>("Start cleaning out mempool..."); var allTxs = new HashSet <uint256>(); foreach (Node node in nodes.ConnectedNodes) { try { if (!node.IsConnected) { continue; } if (cancel.IsCancellationRequested) { return(false); } uint256[] txs = node.GetMempool(cancel); if (cancel.IsCancellationRequested) { return(false); } allTxs.UnionWith(txs); if (cancel.IsCancellationRequested) { return(false); } } catch (Exception ex) when((ex is InvalidOperationException && ex.Message.StartsWith("The node is not in a connected state", StringComparison.InvariantCultureIgnoreCase)) || ex is OperationCanceledException || ex is TaskCanceledException || ex is TimeoutException) { Logger.LogTrace <MemPoolService>(ex); } catch (Exception ex) { Logger.LogDebug <MemPoolService>(ex); } } uint256[] toRemove = TransactionHashes.Except(allTxs).ToArray(); foreach (uint256 tx in toRemove) { TransactionHashes.TryRemove(tx); } Logger.LogInfo <MemPoolService>($"{toRemove.Count()} transactions were cleaned from mempool."); return(true); } catch (Exception ex) when(ex is OperationCanceledException || ex is TaskCanceledException || ex is TimeoutException) { Logger.LogTrace <MemPoolService>(ex); } catch (Exception ex) { Logger.LogDebug <MemPoolService>(ex); } finally { Interlocked.Exchange(ref _cleanupInProcess, 0); } return(false); }
public byte[] ContainsTransaction(byte[] txHash) { return(TransactionHashes.FirstOrDefault(t => t.BytesEqual(txHash))); }
public bool Process(SmartTransaction tx) { uint256 txId = tx.GetHash(); var walletRelevant = false; bool justUpdate = false; if (tx.Confirmed) { TransactionHashes.TryRemove(txId); // If we have in mempool, remove. if (!tx.Transaction.PossiblyP2WPKHInvolved()) { return(false); // We do not care about non-witness transactions for other than mempool cleanup. } bool isFoundTx = TransactionCache.Contains(tx); // If we have in cache, update height. if (isFoundTx) { SmartTransaction foundTx = TransactionCache.FirstOrDefault(x => x == tx); if (foundTx != default(SmartTransaction)) // Must check again, because it's a concurrent collection! { foundTx.SetHeight(tx.Height, tx.BlockHash, tx.BlockIndex); walletRelevant = true; justUpdate = true; // No need to check for double spend, we already processed this transaction, just update it. } } } else if (!tx.Transaction.PossiblyP2WPKHInvolved()) { return(false); // We do not care about non-witness transactions for other than mempool cleanup. } if (!justUpdate && !tx.Transaction.IsCoinBase) // Transactions we already have and processed would be "double spends" but they shouldn't. { var doubleSpends = new List <SmartCoin>(); foreach (SmartCoin coin in Coins) { var spent = false; foreach (TxoRef spentOutput in coin.SpentOutputs) { foreach (TxIn txIn in tx.Transaction.Inputs) { if (spentOutput.TransactionId == txIn.PrevOut.Hash && spentOutput.Index == txIn.PrevOut.N) // Do not do (spentOutput == txIn.PrevOut), it's faster this way, because it won't check for null. { doubleSpends.Add(coin); spent = true; walletRelevant = true; break; } } if (spent) { break; } } } if (doubleSpends.Any()) { if (tx.Height == Height.Mempool) { // if the received transaction is spending at least one input already // spent by a previous unconfirmed transaction signaling RBF then it is not a double // spanding transaction but a replacement transaction. if (doubleSpends.Any(x => x.IsReplaceable)) { // remove double spent coins (if other coin spends it, remove that too and so on) // will add later if they came to our keys foreach (SmartCoin doubleSpentCoin in doubleSpends.Where(x => !x.Confirmed)) { Coins.TryRemove(doubleSpentCoin); } tx.SetReplacement(); walletRelevant = true; } else { return(false); } } else // new confirmation always enjoys priority { // remove double spent coins recursively (if other coin spends it, remove that too and so on), will add later if they came to our keys foreach (SmartCoin doubleSpentCoin in doubleSpends) { Coins.TryRemove(doubleSpentCoin); } walletRelevant = true; } } } var isLikelyCoinJoinOutput = false; bool hasEqualOutputs = tx.Transaction.GetIndistinguishableOutputs(includeSingle: false).FirstOrDefault() != default; if (hasEqualOutputs) { var receiveKeys = KeyManager.GetKeys(x => tx.Transaction.Outputs.Any(y => y.ScriptPubKey == x.P2wpkhScript)); bool allReceivedInternal = receiveKeys.All(x => x.IsInternal); if (allReceivedInternal) { // It is likely a coinjoin if the diff between receive and sent amount is small and have at least 2 equal outputs. Money spentAmount = Coins.Where(x => tx.Transaction.Inputs.Any(y => y.PrevOut.Hash == x.TransactionId && y.PrevOut.N == x.Index)).Sum(x => x.Amount); Money receivedAmount = tx.Transaction.Outputs.Where(x => receiveKeys.Any(y => y.P2wpkhScript == x.ScriptPubKey)).Sum(x => x.Value); bool receivedAlmostAsMuchAsSpent = spentAmount.Almost(receivedAmount, Money.Coins(0.005m)); if (receivedAlmostAsMuchAsSpent) { isLikelyCoinJoinOutput = true; } } } List <SmartCoin> spentOwnCoins = null; for (var i = 0U; i < tx.Transaction.Outputs.Count; i++) { // If transaction received to any of the wallet keys: var output = tx.Transaction.Outputs[i]; HdPubKey foundKey = KeyManager.GetKeyForScriptPubKey(output.ScriptPubKey); if (foundKey != default) { walletRelevant = true; if (output.Value <= DustThreshold) { continue; } foundKey.SetKeyState(KeyState.Used, KeyManager); spentOwnCoins = spentOwnCoins ?? Coins.Where(x => tx.Transaction.Inputs.Any(y => y.PrevOut.Hash == x.TransactionId && y.PrevOut.N == x.Index)).ToList(); var anonset = tx.Transaction.GetAnonymitySet(i); if (spentOwnCoins.Count != 0) { anonset += spentOwnCoins.Min(x => x.AnonymitySet) - 1; // Minus 1, because do not count own. } SmartCoin newCoin = new SmartCoin(txId, i, output.ScriptPubKey, output.Value, tx.Transaction.Inputs.ToTxoRefs().ToArray(), tx.Height, tx.IsRBF, anonset, isLikelyCoinJoinOutput, foundKey.Label, spenderTransactionId: null, false, pubKey: foundKey); // Do not inherit locked status from key, that's different. // If we did not have it. if (Coins.TryAdd(newCoin)) { TransactionCache.TryAdd(tx); CoinReceived?.Invoke(this, newCoin); // Make sure there's always 21 clean keys generated and indexed. KeyManager.AssertCleanKeysIndexed(isInternal: foundKey.IsInternal); if (foundKey.IsInternal) { // Make sure there's always 14 internal locked keys generated and indexed. KeyManager.AssertLockedInternalKeysIndexed(14); } } else // If we had this coin already. { if (newCoin.Height != Height.Mempool) // Update the height of this old coin we already had. { SmartCoin oldCoin = Coins.FirstOrDefault(x => x.TransactionId == txId && x.Index == i); if (oldCoin != null) // Just to be sure, it is a concurrent collection. { oldCoin.Height = newCoin.Height; } } } } } // If spends any of our coin for (var i = 0; i < tx.Transaction.Inputs.Count; i++) { var input = tx.Transaction.Inputs[i]; var foundCoin = Coins.FirstOrDefault(x => x.TransactionId == input.PrevOut.Hash && x.Index == input.PrevOut.N); if (foundCoin != null) { walletRelevant = true; var alreadyKnown = foundCoin.SpenderTransactionId == txId; foundCoin.SpenderTransactionId = txId; TransactionCache.TryAdd(tx); if (!alreadyKnown) { CoinSpent?.Invoke(this, foundCoin); } if (tx.Confirmed) { SpenderConfirmed?.Invoke(this, foundCoin); } } } return(walletRelevant); }
/// <summary> /// Dispose method for resource cleanup /// </summary> public void Dispose() { Header.Dispose(); TransactionHashes.Dispose(); }
public GetTransactionInfosRequest(IEnumerable <string> transactionHashes) { TransactionHashes.AddRange(transactionHashes); }