private async Task TryRegisterCoinsAsync(CcjClientRound inputRegistrableRound) { try { // Select the most suitable coins to regiter. List <TxoRef> registrableCoins = State.GetRegistrableCoins( inputRegistrableRound.State.MaximumInputCountPerPeer, inputRegistrableRound.State.Denomination, inputRegistrableRound.State.FeePerInputs, inputRegistrableRound.State.FeePerOutputs).ToList(); // If there are no suitable coins to register return. if (!registrableCoins.Any()) { return; } (HdPubKey change, IEnumerable <HdPubKey> actives)outputAddresses = GetOutputsToRegister(inputRegistrableRound.State.Denomination, inputRegistrableRound.State.SchnorrPubKeys.Count(), registrableCoins); SchnorrPubKey[] schnorrPubKeys = inputRegistrableRound.State.SchnorrPubKeys.ToArray(); List <Requester> requesters = new List <Requester>(); var blindedOutputScriptHashes = new List <uint256>(); var registeredAddresses = new List <BitcoinAddress>(); for (int i = 0; i < schnorrPubKeys.Length; i++) { if (outputAddresses.actives.Count() <= i) { break; } BitcoinAddress address = outputAddresses.actives.Select(x => x.GetP2wpkhAddress(Network)).ElementAt(i); SchnorrPubKey schnorrPubKey = schnorrPubKeys[i]; var outputScriptHash = new uint256(Hashes.SHA256(address.ScriptPubKey.ToBytes())); var requester = new Requester(); uint256 blindedOutputScriptHash = requester.BlindMessage(outputScriptHash, schnorrPubKey); requesters.Add(requester); blindedOutputScriptHashes.Add(blindedOutputScriptHash); registeredAddresses.Add(address); } byte[] blindedOutputScriptHashesByte = ByteHelpers.Combine(blindedOutputScriptHashes.Select(x => x.ToBytes())); uint256 blindedOutputScriptsHash = new uint256(Hashes.SHA256(blindedOutputScriptHashesByte)); var inputProofs = new List <InputProofModel>(); foreach (TxoRef coinReference in registrableCoins) { SmartCoin coin = State.GetSingleOrDefaultFromWaitingList(coinReference); if (coin is null) { throw new NotSupportedException("This is impossible."); } coin.Secret = coin.Secret ?? KeyManager.GetSecrets(SaltSoup(), coin.ScriptPubKey).Single(); var inputProof = new InputProofModel { Input = coin.GetTxoRef(), Proof = coin.Secret.PrivateKey.SignCompact(blindedOutputScriptsHash) }; inputProofs.Add(inputProof); } AliceClient aliceClient = null; try { aliceClient = await AliceClient.CreateNewAsync(inputRegistrableRound.RoundId, registeredAddresses, schnorrPubKeys, requesters, Network, outputAddresses.change.GetP2wpkhAddress(Network), blindedOutputScriptHashes, inputProofs, CcjHostUriAction, TorSocks5EndPoint); } catch (HttpRequestException ex) when(ex.Message.Contains("Input is banned", StringComparison.InvariantCultureIgnoreCase)) { string[] parts = ex.Message.Split(new[] { "Input is banned from participation for ", " minutes: " }, StringSplitOptions.RemoveEmptyEntries); string minutesString = parts[1]; int minuteInt = int.Parse(minutesString); string bannedInputString = parts[2].TrimEnd('.'); string[] bannedInputStringParts = bannedInputString.Split(':', StringSplitOptions.RemoveEmptyEntries); TxoRef coinReference = new TxoRef(new uint256(bannedInputStringParts[1]), uint.Parse(bannedInputStringParts[0])); SmartCoin coin = State.GetSingleOrDefaultFromWaitingList(coinReference); if (coin is null) { throw new NotSupportedException("This is impossible."); } coin.BannedUntilUtc = DateTimeOffset.UtcNow + TimeSpan.FromMinutes(minuteInt); Logger.LogWarning <CcjClient>(ex.Message.Split('\n')[1]); await DequeueCoinsFromMixNoLockAsync(coinReference, "Failed to register the coin with the coordinator."); aliceClient?.Dispose(); return; } catch (HttpRequestException ex) when(ex.Message.Contains("Provided input is not unspent", StringComparison.InvariantCultureIgnoreCase)) { string[] parts = ex.Message.Split(new[] { "Provided input is not unspent: " }, StringSplitOptions.RemoveEmptyEntries); string spentInputString = parts[1].TrimEnd('.'); string[] bannedInputStringParts = spentInputString.Split(':', StringSplitOptions.RemoveEmptyEntries); TxoRef coinReference = new TxoRef(new uint256(bannedInputStringParts[1]), uint.Parse(bannedInputStringParts[0])); SmartCoin coin = State.GetSingleOrDefaultFromWaitingList(coinReference); if (coin is null) { throw new NotSupportedException("This is impossible."); } coin.SpentAccordingToBackend = true; Logger.LogWarning <CcjClient>(ex.Message.Split('\n')[1]); await DequeueCoinsFromMixNoLockAsync(coinReference, "Failed to register the coin with the coordinator. The coin is already spent."); aliceClient?.Dispose(); return; } catch (HttpRequestException ex) when(ex.Message.Contains("No such running round in InputRegistration.", StringComparison.InvariantCultureIgnoreCase)) { Logger.LogInfo <CcjClient>("Client tried to register a round that is not in InputRegistration anymore. Trying again later."); aliceClient?.Dispose(); return; } catch (HttpRequestException ex) when(ex.Message.Contains("too-long-mempool-chain", StringComparison.InvariantCultureIgnoreCase)) { Logger.LogInfo <CcjClient>("Coordinator failed because too much unconfirmed parent transactions. Trying again later."); aliceClient?.Dispose(); return; } var coinsRegistered = new List <SmartCoin>(); foreach (TxoRef coinReference in registrableCoins) { var coin = State.GetSingleOrDefaultFromWaitingList(coinReference); if (coin is null) { throw new NotSupportedException("This is impossible."); } coinsRegistered.Add(coin); State.RemoveCoinFromWaitingList(coin); } var registration = new ClientRoundRegistration(aliceClient, coinsRegistered, outputAddresses.change.GetP2wpkhAddress(Network)); CcjClientRound roundRegistered = State.GetSingleOrDefaultRound(aliceClient.RoundId); if (roundRegistered is null) { // If our SatoshiClient does not yet know about the round, because of delay, then delay the round registration. DelayedRoundRegistration?.Dispose(); DelayedRoundRegistration = registration; } roundRegistered.Registration = registration; } catch (Exception ex) { Logger.LogError <CcjClient>(ex); } }
public void ClearRegistration() { Registration = null; }