public async Task <uint160.MutableUint160> BeginOpenChannel( [ModelBinder(BinderType = typeof(TumblerParametersModelBinder))] ClassicTumblerParameters tumblerId, [FromBody] OpenChannelRequest request) { if (tumblerId == null) { throw new ArgumentNullException("tumblerId"); } var height = Services.BlockExplorerService.GetCurrentHeight(); if (Repository.IsUsed(request.CycleStart, request.Nonce)) { throw new ActionResultException(BadRequest("duplicate-query")); } var cycle = GetCycle(request.CycleStart); if (!cycle.IsInPhase(CyclePhase.TumblerChannelEstablishment, height)) { throw new ActionResultException(BadRequest("invalid-phase")); } var fee = await Services.FeeService.GetFeeRateAsync(); try { if (!Parameters.VoucherKey.PublicKey.Verify(request.Signature, NBitcoin.Utils.ToBytes((uint)request.CycleStart, true), request.Nonce)) { throw new ActionResultException(BadRequest("incorrect-voucher")); } if (!Repository.MarkUsedNonce(request.CycleStart, request.Nonce)) { throw new ActionResultException(BadRequest("duplicate-query")); } var escrowKey = new Key(); var escrow = new EscrowScriptPubKeyParameters(); escrow.LockTime = cycle.GetTumblerLockTime(); escrow.Receiver = request.EscrowKey; escrow.Initiator = escrowKey.PubKey; var channelId = new uint160(RandomUtils.GetBytes(20)); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} Asked to open channel"); var txOut = new TxOut(Parameters.Denomination, escrow.ToScript().WitHash.ScriptPubKey.Hash); var unused = Services.WalletService.FundTransactionAsync(txOut, fee) .ContinueWith(async(Task <Transaction> task) => { try { var tx = await task.ConfigureAwait(false); var correlation = new CorrelationId(channelId); Tracker.TransactionCreated(cycle.Start, TransactionType.TumblerEscrow, tx.GetHash(), correlation); //Logging/Broadcast per funding TX one time if (Repository.MarkUsedNonce(cycle.Start, new uint160(tx.GetHash().ToBytes().Take(20).ToArray()))) { var bobCount = Parameters.CountEscrows(tx, Client.Identity.Bob); Logs.Tumbler.LogInformation($"Cycle {cycle.Start} channel created {tx.GetHash()} with {bobCount} users"); await Services.BroadcastService.BroadcastAsync(tx).ConfigureAwait(false); } await Services.BlockExplorerService.TrackAsync(txOut.ScriptPubKey).ConfigureAwait(false); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerEscrow, txOut.ScriptPubKey, correlation); var coin = tx.Outputs.AsCoins().First(o => o.ScriptPubKey == txOut.ScriptPubKey && o.TxOut.Value == txOut.Value); var session = new PromiseServerSession(Parameters.CreatePromiseParamaters()); var redeem = await Services.WalletService.GenerateAddressAsync().ConfigureAwait(false); session.ConfigureEscrowedCoin(channelId, coin.ToScriptCoin(escrow.ToScript()), escrowKey, redeem.ScriptPubKey); var redeemTx = session.CreateRedeemTransaction(fee); Services.TrustedBroadcastService.Broadcast(cycle.Start, TransactionType.TumblerRedeem, correlation, redeemTx); Repository.Save(cycle.Start, session); Tracker.AddressCreated(cycle.Start, TransactionType.TumblerRedeem, redeem.ScriptPubKey, correlation); } catch (Exception ex) { Logs.Tumbler.LogCritical(new EventId(), ex, "Error during escrow transaction callback"); } }); return(channelId.AsBitcoinSerializable()); } catch (NotEnoughFundsException ex) { Logs.Tumbler.LogInformation(ex.Message); throw new ActionResultException(BadRequest("tumbler-insufficient-funds")); } }