static void Main(string[] args) { try { string root = Directory.GetCurrentDirectory(); string pluginFolder = TxBuilder.GetPluginsFolder(root); if (pluginFolder == null) { Console.WriteLine("plugins subfolder not found"); return; } string progressId = ""; bool ignoreOldProgress = false; if (args.Length > 0 && args[0].StartsWith(progressParamPrefix)) { progressId = args[0].Substring(progressParamPrefix.Length); if (progressId[0] == '*') { ignoreOldProgress = true; progressId = progressId.Substring(1); } args = args.Skip(1).ToArray(); } string progress = Path.Combine(pluginFolder, $"progress{progressId}.txt"); if (ignoreOldProgress) { File.Delete(progress); } if (File.Exists(progress)) { Console.WriteLine("Found unfinished action, retrying..."); args = File.ReadAllText(progress).Split(); } else if (args.Length > 0) { File.WriteAllText(progress, string.Join(' ', args)); } if (args.Length < 1) { Console.WriteLine("Usage: ccclient [-progress:[*]progressId] [-config:configFileName] [-txid] command [parameters]"); Console.WriteLine("commands:"); Console.WriteLine("sighash"); Console.WriteLine("tip [numBlocksBelow]"); Console.WriteLine("list Settings"); Console.WriteLine("list Wallets"); Console.WriteLine("list Addresses"); Console.WriteLine("list Transfers"); Console.WriteLine("list AskOrders"); Console.WriteLine("list BidOrders"); Console.WriteLine("list Offers"); Console.WriteLine("list DealOrders"); Console.WriteLine("list RepaymentOrders"); Console.WriteLine("show Balance sighash|0"); Console.WriteLine("show Address sighash|0 blockchain address network"); Console.WriteLine("show MatchingOrders sighash|0"); Console.WriteLine("show CurrentOffers sighash|0"); Console.WriteLine("show CreditHistory sighash|0"); Console.WriteLine("show NewDeals sighash|0"); Console.WriteLine("show Transfer sighash|0 orderId"); Console.WriteLine("show CurrentLoans sighash|0"); Console.WriteLine("show NewRepaymentOrders sighash|0"); Console.WriteLine("show CurrentRepaymentOrders sighash|0"); Console.WriteLine("creditcoin SendFunds amount sighash "); Console.WriteLine("creditcoin RegisterAddress blockchain address network"); Console.WriteLine("creditcoin RegisterTransfer gain orderId txId"); Console.WriteLine("creditcoin AddAskOrder addressId amount interest maturity fee expiration"); Console.WriteLine("creditcoin AddBidOrder addressId amount interest maturity fee expiration"); Console.WriteLine("creditcoin AddOffer askOrderId bidOrderId expiration"); Console.WriteLine("creditcoin AddDealOrder offerId expiration"); Console.WriteLine("creditcoin CompleteDealOrder dealOrderId transferId"); Console.WriteLine("creditcoin LockDealOrder dealOrderId"); Console.WriteLine("creditcoin CloseDealOrder dealOrderId transferId"); Console.WriteLine("creditcoin Exempt dealOrderId transferId"); Console.WriteLine("creditcoin AddRepaymentOrder dealOrderId addressId amount expiration"); Console.WriteLine("creditcoin CompleteRepaymentOrder repaymentOrderId"); Console.WriteLine("creditcoin CloseRepaymentOrder repaymentOrderId transferId"); Console.WriteLine("creditcoin CollectCoins addressId amount txId"); Console.WriteLine("bitcoin RegisterTransfer gain orderId sourceTxId"); Console.WriteLine("ethereum RegisterTransfer gain orderId"); Console.WriteLine("ethereum CollectCoins amount"); return; } string configFile = null; if (args.Length > 0 && args[0].StartsWith(configParamPrefix)) { configFile = args[0].Substring(configParamPrefix.Length); args = args.Skip(1).ToArray(); if (!File.Exists(configFile)) { configFile = Path.Combine(pluginFolder, configFile); if (!File.Exists(configFile)) { Console.WriteLine("Cannot find the specified config file"); return; } } } bool txid = false; if (args.Length > 0 && args[0].Equals(txidParam)) { args = args.Skip(1).ToArray(); txid = true; } string action; string[] command; var builder = new ConfigurationBuilder() .AddJsonFile("appsettings.json", true, false) #if DEBUG .AddJsonFile("appsettings.dev.json", true, false) #endif ; if (configFile != null) { builder.AddJsonFile(configFile, true, false); } IConfiguration config = builder.Build(); string creditcoinRestApiURL = config["creditcoinRestApiURL"]; if (!string.IsNullOrWhiteSpace(creditcoinRestApiURL)) { creditcoinUrl = creditcoinRestApiURL; } Signer signer = getSigner(config); if (args.Length < 1) { Console.WriteLine("Command is not provided"); return; } action = args[0].ToLower(); command = args.Skip(1).ToArray(); bool inProgress = false; if (action.Equals("sighash")) { Console.WriteLine(TxBuilder.getSighash(signer)); } else if (action.Equals("tip")) { BigInteger headIdx = GetHeadIdx(); if (command.Length == 1) { BigInteger num; if (!BigInteger.TryParse(command[0], out num)) { throw new Exception("Invalid numerics"); } headIdx -= num; } Console.WriteLine(headIdx); } else if (action.Equals("list")) { if (command.Length > 2) { throw new Exception("1 or 2 parametersd expected"); } string id = null; if (command.Length == 2) { id = command[1]; } if (command[0].Equals("settings", StringComparison.OrdinalIgnoreCase)) { filter(RpcHelper.settingNamespace, (string objid, byte[] protobuf) => { if (id == null || id != null && id.Equals(objid)) { Setting setting = Setting.Parser.ParseFrom(protobuf); foreach (var entry in setting.Entries) { Console.WriteLine($"{entry.Key}: {entry.Value}"); } } }); } else if (command[0].Equals("wallets", StringComparison.OrdinalIgnoreCase)) { filter(RpcHelper.creditCoinNamespace + RpcHelper.walletPrefix, (string objid, byte[] protobuf) => { if (id == null || id != null && id.Equals(objid)) { Wallet wallet = Wallet.Parser.ParseFrom(protobuf); Console.WriteLine($"wallet({objid}) amount:{wallet.Amount}"); } }); } else if (command[0].Equals("addresses", StringComparison.OrdinalIgnoreCase)) { filter(RpcHelper.creditCoinNamespace + RpcHelper.addressPrefix, (string objid, byte[] protobuf) => { if (id == null || id != null && id.Equals(objid)) { Address address = Address.Parser.ParseFrom(protobuf); Console.WriteLine($"address({objid}) blockchain:{address.Blockchain} value:{address.Value} network:{address.Network} sighash:{address.Sighash}"); } }); } else if (command[0].Equals("transfers", StringComparison.OrdinalIgnoreCase)) { filter(RpcHelper.creditCoinNamespace + RpcHelper.transferPrefix, (string objid, byte[] protobuf) => { if (id == null || id != null && id.Equals(objid)) { Transfer transfer = Transfer.Parser.ParseFrom(protobuf); Console.WriteLine($"transfer({objid}) blockchain:{transfer.Blockchain} srcAddress:{transfer.SrcAddress} dstAddress:{transfer.DstAddress} order:{transfer.Order} amount:{transfer.Amount} tx:{transfer.Tx} block:{transfer.Block} processed:{transfer.Processed} sighash:{transfer.Sighash}"); } }); } else if (command[0].Equals("askOrders", StringComparison.OrdinalIgnoreCase)) { filter(RpcHelper.creditCoinNamespace + RpcHelper.askOrderPrefix, (string objid, byte[] protobuf) => { if (id == null || id != null && id.Equals(objid)) { AskOrder askOrder = AskOrder.Parser.ParseFrom(protobuf); Console.WriteLine($"askOrder({objid}) blockchain:{askOrder.Blockchain} address:{askOrder.Address} amount:{askOrder.Amount} interest:{askOrder.Interest} maturity:{askOrder.Maturity} fee:{askOrder.Fee} expiration:{askOrder.Expiration} block:{askOrder.Block} sighash:{askOrder.Sighash}"); } }); } else if (command[0].Equals("bidOrders", StringComparison.OrdinalIgnoreCase)) { filter(RpcHelper.creditCoinNamespace + RpcHelper.bidOrderPrefix, (string objid, byte[] protobuf) => { if (id == null || id != null && id.Equals(objid)) { BidOrder bidOrder = BidOrder.Parser.ParseFrom(protobuf); Console.WriteLine($"bidOrder({objid}) blockchain:{bidOrder.Blockchain} address:{bidOrder.Address} amount:{bidOrder.Amount} interest:{bidOrder.Interest} maturity:{bidOrder.Maturity} fee:{bidOrder.Fee} expiration:{bidOrder.Expiration} block:{bidOrder.Block} sighash:{bidOrder.Sighash}"); } }); } else if (command[0].Equals("offers", StringComparison.OrdinalIgnoreCase)) { filter(RpcHelper.creditCoinNamespace + RpcHelper.offerPrefix, (string objid, byte[] protobuf) => { if (id == null || id != null && id.Equals(objid)) { Offer offer = Offer.Parser.ParseFrom(protobuf); Console.WriteLine($"offer({objid}) blockchain:{offer.Blockchain} askOrder:{offer.AskOrder} bidOrder:{offer.BidOrder} expiration:{offer.Expiration} block:{offer.Block}"); } }); } else if (command[0].Equals("dealOrders", StringComparison.OrdinalIgnoreCase)) { filter(RpcHelper.creditCoinNamespace + RpcHelper.dealOrderPrefix, (string objid, byte[] protobuf) => { if (id == null || id != null && id.Equals(objid)) { DealOrder dealOrder = DealOrder.Parser.ParseFrom(protobuf); Console.WriteLine($"dealOrder({objid}) blockchain:{dealOrder.Blockchain} srcAddress:{dealOrder.SrcAddress} dstAddress:{dealOrder.DstAddress} amount:{dealOrder.Amount} interest:{dealOrder.Interest} maturity:{dealOrder.Maturity} fee:{dealOrder.Fee} expiration:{dealOrder.Expiration} block:{dealOrder.Block} loanTransfer:{(dealOrder.LoanTransfer.Equals(string.Empty) ? "*" : dealOrder.LoanTransfer)} repaymentTransfer:{(dealOrder.RepaymentTransfer.Equals(string.Empty) ? "*" : dealOrder.RepaymentTransfer)} lock:{(dealOrder.Lock.Equals(string.Empty) ? "*" : dealOrder.Lock)} sighash:{dealOrder.Sighash}"); } }); } else if (command[0].Equals("repaymentOrders", StringComparison.OrdinalIgnoreCase)) { filter(RpcHelper.creditCoinNamespace + RpcHelper.repaymentOrderPrefix, (string objid, byte[] protobuf) => { if (id == null || id != null && id.Equals(objid)) { RepaymentOrder repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); Console.WriteLine($"repaymentOrder({objid}) blockchain:{repaymentOrder.Blockchain} srcAddress:{repaymentOrder.SrcAddress} dstAddress:{repaymentOrder.DstAddress} amount:{repaymentOrder.Amount} expiration:{repaymentOrder.Expiration} block:{repaymentOrder.Block} deal:{repaymentOrder.Deal} previousOwner:{(repaymentOrder.PreviousOwner.Equals(string.Empty)? "*": repaymentOrder.PreviousOwner)} transfer:{(repaymentOrder.Transfer.Equals(string.Empty) ? "*" : repaymentOrder.Transfer)} sighash:{repaymentOrder.Sighash}"); } }); } } else if (action.Equals("show")) { bool success = true; BigInteger headIdx = GetHeadIdx(); if (command.Length <= 1) { throw new Exception("1 or more parametersd expected"); } string sighash; if (command[1].Equals("0")) { sighash = TxBuilder.getSighash(signer); } else { sighash = command[1]; } if (command[0].Equals("balance", StringComparison.OrdinalIgnoreCase)) { if (command.Length != 2) { throw new Exception("2 parametersd expected"); } string prefix = RpcHelper.creditCoinNamespace + RpcHelper.walletPrefix; string id = prefix + sighash; string amount = "0"; filter(prefix, (string objid, byte[] protobuf) => { Wallet wallet = Wallet.Parser.ParseFrom(protobuf); if (objid.Equals(id)) { amount = wallet.Amount; } }); Console.WriteLine($"{amount}"); } else if (command[0].Equals("address", StringComparison.OrdinalIgnoreCase)) { if (command.Length != 5) { throw new Exception("5 parametersd expected"); } var blockchain = command[2].ToLower(); var addr = command[3]; var network = command[4].ToLower(); filter(RpcHelper.creditCoinNamespace + RpcHelper.addressPrefix, (string objid, byte[] protobuf) => { Address address = Address.Parser.ParseFrom(protobuf); if (address.Sighash == sighash && address.Blockchain == blockchain && address.Value == addr && address.Network == network) { Console.WriteLine(objid); } }); } else if (command[0].Equals("matchingOrders", StringComparison.OrdinalIgnoreCase)) { if (command.Length != 2) { throw new Exception("2 parametersd expected"); } var askOrders = new Dictionary <string, AskOrder>(); filter(RpcHelper.creditCoinNamespace + RpcHelper.askOrderPrefix, (string objid, byte[] protobuf) => { AskOrder askOrder = AskOrder.Parser.ParseFrom(protobuf); BigInteger block; if (!BigInteger.TryParse(askOrder.Block, out block)) { throw new Exception("Invalid numerics"); } if (block + askOrder.Expiration > headIdx) { askOrders.Add(objid, askOrder); } }); var bidOrders = new Dictionary <string, BidOrder>(); filter(RpcHelper.creditCoinNamespace + RpcHelper.bidOrderPrefix, (string objid, byte[] protobuf) => { BidOrder bidOrder = BidOrder.Parser.ParseFrom(protobuf); BigInteger block; if (!BigInteger.TryParse(bidOrder.Block, out block)) { throw new Exception("Invalid numerics"); } if (block + bidOrder.Expiration > headIdx) { bidOrders.Add(objid, bidOrder); } }); match(sighash, askOrders, bidOrders); } else if (command[0].Equals("currentOffers", StringComparison.OrdinalIgnoreCase)) { if (command.Length != 2) { throw new Exception("2 parametersd expected"); } var bidOrders = new Dictionary <string, BidOrder>(); filter(RpcHelper.creditCoinNamespace + RpcHelper.bidOrderPrefix, (string objid, byte[] protobuf) => { BidOrder bidOrder = BidOrder.Parser.ParseFrom(protobuf); bidOrders.Add(objid, bidOrder); }); filter(RpcHelper.creditCoinNamespace + RpcHelper.offerPrefix, (string objid, byte[] protobuf) => { Offer offer = Offer.Parser.ParseFrom(protobuf); BidOrder bidOrder = bidOrders[offer.BidOrder]; if (bidOrder.Sighash == sighash) { BigInteger block; if (!BigInteger.TryParse(offer.Block, out block)) { throw new Exception("Invalid numerics"); } if (block + offer.Expiration > headIdx) { Console.WriteLine(objid); } } }); } else if (command[0].Equals("creditHistory", StringComparison.OrdinalIgnoreCase)) { if (command.Length != 2) { throw new Exception("2 parametersd expected"); } filterDeals(null, sighash, (string dealAddress, DealOrder dealOrder) => { var status = dealOrder.LoanTransfer.Equals(string.Empty) ? "NEW" : "COMPLETE"; if (!dealOrder.RepaymentTransfer.Equals(string.Empty)) { status = "CLOSED"; } Console.WriteLine($"status:{status}, amount:{dealOrder.Amount}, blockchain:{dealOrder.Blockchain}"); }); } else if (command[0].Equals("newDeals", StringComparison.OrdinalIgnoreCase)) { if (command.Length != 2) { throw new Exception("2 parametersd expected"); } filterDeals(sighash, null, (string dealAddress, DealOrder dealOrder) => { if (dealOrder.LoanTransfer.Equals(string.Empty)) { BigInteger block; if (!BigInteger.TryParse(dealOrder.Block, out block)) { throw new Exception("Invalid numerics"); } if (block + dealOrder.Expiration > headIdx) { Console.WriteLine(dealAddress); } } }); } else if (command[0].Equals("transfer", StringComparison.OrdinalIgnoreCase)) { if (command.Length != 3) { throw new Exception("3 parametersd expected"); } var orderId = command[2]; filter(RpcHelper.creditCoinNamespace + RpcHelper.transferPrefix, (string objid, byte[] protobuf) => { Transfer transfer = Transfer.Parser.ParseFrom(protobuf); if (transfer.Sighash == sighash && transfer.Order == orderId && !transfer.Processed) { Console.WriteLine(objid); } }); } else if (command[0].Equals("currentLoans", StringComparison.OrdinalIgnoreCase)) { if (command.Length != 2) { throw new Exception("2 parametersd expected"); } filterDeals(null, sighash, (string dealAddress, DealOrder dealOrder) => { if (!dealOrder.LoanTransfer.Equals(string.Empty) && dealOrder.RepaymentTransfer.Equals(string.Empty)) { Console.WriteLine(dealAddress); } }); } else if (command[0].Equals("newRepaymentOrders", StringComparison.OrdinalIgnoreCase)) { if (command.Length != 2) { throw new Exception("2 parametersd expected"); } var addresses = new Dictionary <string, Address>(); filter(RpcHelper.creditCoinNamespace + RpcHelper.addressPrefix, (string objid, byte[] protobuf) => { Address address = Address.Parser.ParseFrom(protobuf); addresses.Add(objid, address); }); var dealOrders = new Dictionary <string, DealOrder>(); filter(RpcHelper.creditCoinNamespace + RpcHelper.dealOrderPrefix, (string objid, byte[] protobuf) => { DealOrder dealOrder = DealOrder.Parser.ParseFrom(protobuf); dealOrders.Add(objid, dealOrder); }); filter(RpcHelper.creditCoinNamespace + RpcHelper.repaymentOrderPrefix, (string objid, byte[] protobuf) => { RepaymentOrder repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); DealOrder deal = dealOrders[repaymentOrder.Deal]; Address address = addresses[deal.SrcAddress]; if (repaymentOrder.Transfer.Equals(string.Empty) && repaymentOrder.PreviousOwner.Equals(string.Empty) && address.Sighash.Equals(sighash)) { BigInteger block; if (!BigInteger.TryParse(repaymentOrder.Block, out block)) { throw new Exception("Invalid numerics"); } if (block + repaymentOrder.Expiration > headIdx) { Console.WriteLine(objid); } } }); } else if (command[0].Equals("currentRepaymentOrders", StringComparison.OrdinalIgnoreCase)) { filter(RpcHelper.creditCoinNamespace + RpcHelper.repaymentOrderPrefix, (string objid, byte[] protobuf) => { RepaymentOrder repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); if (repaymentOrder.Transfer.Equals(string.Empty) && !repaymentOrder.PreviousOwner.Equals(string.Empty) && repaymentOrder.Sighash.Equals(sighash)) { Console.WriteLine(objid); } }); } else { Console.WriteLine("Invalid command " + command[0]); success = false; } if (success) { Console.WriteLine("Success"); } } else { var txBuilder = new TxBuilder(signer); if (action.Equals("creditcoin")) { string msg; var tx = txBuilder.BuildTx(command, out msg); if (tx == null) { Debug.Assert(msg != null); Console.WriteLine(msg); } else { Debug.Assert(msg == null); var content = new ByteArrayContent(tx); content.Headers.Add("Content-Type", "application/octet-stream"); Console.WriteLine(RpcHelper.CompleteBatch(httpClient, creditcoinUrl, "batches", content, txid)); } } else { var loader = new Loader <ICCClientPlugin>(); var msgs = new List <string>(); loader.Load(pluginFolder, msgs); foreach (var msg in msgs) { Console.WriteLine(msg); } ICCClientPlugin plugin = loader.Get(action); var pluginConfig = config.GetSection(action); if (plugin == null) { Console.WriteLine("Error: Unknown action " + action); } else { string msg; var settings = getSettings(); bool done = plugin.Run(txid, pluginConfig, httpClient, txBuilder, settings, progressId, pluginFolder, creditcoinUrl, command, out inProgress, out msg); if (done) { if (msg == null) { msg = "Success"; } Console.WriteLine(msg); } else { Console.WriteLine("Error: " + msg); } } } } if (!inProgress) { File.Delete(progress); } } catch (Exception x) { Console.WriteLine($"Error: unexpected failure - {x.Message}"); } }
public bool Run(IConfiguration cfg, HttpClient httpClient, ITxBuilder txBuilder, Dictionary <string, string> settings, string pluginsFolder, string url, string[] command, out bool inProgress, out string msg) { Debug.Assert(command != null); Debug.Assert(command.Length > 1); if (command[0].Equals("registerTransfer", StringComparison.OrdinalIgnoreCase)) { // ethereum RegisterTransfer registeredSourceId amount string progress = Path.Combine(pluginsFolder, $"{name}_progress.txt"); inProgress = false; Debug.Assert(command.Length == 3 || command.Length == 4); string registeredSourceId = command[1]; string amountString = command[2]; bool erc20 = command.Length == 4; string secret = cfg["secret"]; if (string.IsNullOrWhiteSpace(secret)) { msg = "ethereum.secret is not set"; return(false); } var ethereumPrivateKey = secret; // TODO disable confirmation count config for release build string confirmationsCount = cfg["confirmationsCount"]; if (string.IsNullOrWhiteSpace(confirmationsCount)) { msg = "ethereum.confirmationsCount is not set"; return(false); } if (!int.TryParse(confirmationsCount, out int confirmationsExpected)) { msg = "ethereum.confirmationsCount is not an int"; return(false); } string rpcUrl = cfg["rpc"]; if (string.IsNullOrWhiteSpace(rpcUrl)) { msg = "ethereum.rpc is not set"; return(false); } var web3 = new Nethereum.Web3.Web3(rpcUrl); string payTxId; BigInteger fee; if (File.Exists(progress)) { Console.WriteLine("Found unfinished action, retrying..."); var data = File.ReadAllText(progress).Split(':'); if (data.Length != 2) { msg = "Invalid progress data"; return(false); } payTxId = data[0]; if (!BigInteger.TryParse(data[1], out fee)) { msg = "Invalid progress data"; return(false); } } else { var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{registeredSourceId}", out msg); if (protobuf == null) { return(false); } var address = Address.Parser.ParseFrom(protobuf); string destinationAddress; if (!settings.TryGetValue("sawtooth.escrow." + name, out destinationAddress)) { msg = "Escrow not found for " + name; return(false); } BigInteger transferAmount; if (!BigInteger.TryParse(amountString, out transferAmount) || transferAmount <= 0) { msg = "Invalid amount"; return(false); } if (!address.Blockchain.Equals(name)) { msg = $"ethereum RegisterTransfer can only transfer ether.\nThis source is registered for {address.Blockchain}"; return(false); } string sourceAddress = EthECKey.GetPublicAddress(ethereumPrivateKey); if (!sourceAddress.Equals(address.Address_, StringComparison.OrdinalIgnoreCase)) { msg = "The deal is for a different client"; return(false); } var txCount = web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(sourceAddress).Result; TransactionSigner signer = new TransactionSigner(); // TODO maybe add a y/n choice for user to decline or configurable gas price override var gasPrice = web3.Eth.GasPrice.SendRequestAsync().Result; Console.WriteLine("gasPrice: " + gasPrice.Value.ToString()); string to; string data; BigInteger amount; HexBigInteger gasLimit; if (erc20) { string creditcoinContract = cfg["creditcoinContract"]; string creditcoinContractAbi = cfg["creditcoinContractAbi"]; to = creditcoinContract; var contract = web3.Eth.GetContract(creditcoinContractAbi, creditcoinContract); var burn = contract.GetFunction("exchange"); var functionInput = new object[] { transferAmount, address.Sighash }; data = burn.GetData(functionInput); gasLimit = burn.EstimateGasAsync(functionInput).Result; fee = gasLimit.Value * gasPrice.Value; amount = 0; } else { gasLimit = web3.Eth.Transactions.EstimateGas.SendRequestAsync(new Nethereum.RPC.Eth.DTOs.CallInput(registeredSourceId, destinationAddress, new Nethereum.Hex.HexTypes.HexBigInteger(transferAmount))).Result; Console.WriteLine("gasLimit: " + gasLimit.Value.ToString()); fee = gasLimit.Value * gasPrice.Value; to = destinationAddress; data = address.Sighash; amount = transferAmount + fee; } string txRaw = signer.SignTransaction(ethereumPrivateKey, to, amount, txCount, gasPrice, gasLimit, data); payTxId = web3.Eth.Transactions.SendRawTransaction.SendRequestAsync("0x" + txRaw).Result; Console.WriteLine("Ethereum Transaction ID: " + payTxId); File.WriteAllText(progress, $"{payTxId}:{fee.ToString()}"); } inProgress = true; while (true) { var receipt = web3.Eth.TransactionManager.TransactionReceiptService.PollForReceiptAsync(payTxId).Result; if (receipt.BlockNumber != null) { var blockNumber = web3.Eth.Blocks.GetBlockNumber.SendRequestAsync().Result; if (blockNumber.Value - receipt.BlockNumber.Value >= confirmationsExpected) { break; } } Thread.Sleep(1000); } File.Delete(progress); inProgress = false; command = new string[] { command[0], registeredSourceId, amountString, erc20 ? "0" : fee.ToString(), payTxId, erc20 ? "creditcoin" : "1" }; var tx = txBuilder.BuildTx(command, out msg); if (tx == null) { return(false); } Debug.Assert(msg == null); var content = new ByteArrayContent(tx); content.Headers.Add("Content-Type", "application/octet-stream"); msg = RpcHelper.CompleteBatch(httpClient, $"{url}/batches", content); return(true); } else { inProgress = false; msg = "Unknown command: " + command[0]; return(false); } }
public bool Run(bool txid, IConfiguration cfg, HttpClient httpClient, ITxBuilder txBuilder, Dictionary <string, string> settings, string progressId, string pluginsFolder, string url, string[] command, out bool inProgress, out string msg) { Debug.Assert(command != null); if (command.Length < 2) { inProgress = false; msg = "invalid parameter count"; return(false); } bool erc20 = command[0].Equals("collectCoins", StringComparison.OrdinalIgnoreCase); if (erc20 || command[0].Equals("registerTransfer", StringComparison.OrdinalIgnoreCase)) { // ethereum RegisterTransfer gain orderId // ethereum CollectCoins amount string progress = Path.Combine(pluginsFolder, $"{name}_progress{progressId}.txt"); inProgress = false; if (erc20 && command.Length != 2 || !erc20 && command.Length != 3) { msg = "invalid parameter count"; return(false); } string gainString = "0"; string orderId = null; string amountString = null; if (erc20) { amountString = command[1]; } else { gainString = command[1]; orderId = command[2]; } string secret = cfg["secret"]; if (string.IsNullOrWhiteSpace(secret)) { msg = "ethereum.secret is not set"; return(false); } var ethereumPrivateKey = secret; #if DEBUG string confirmationsCount = cfg["confirmationsCount"]; if (int.TryParse(confirmationsCount, out int parsedCount)) { mConfirmationsExpected = parsedCount; } #endif string rpcUrl = cfg["rpc"]; if (string.IsNullOrWhiteSpace(rpcUrl)) { msg = "ethereum.rpc is not set"; return(false); } string ethSrcAddress = EthECKey.GetPublicAddress(ethereumPrivateKey); var web3 = new Nethereum.Web3.Web3(rpcUrl); string srcAddressId = null; string dstAddressId = null; if (!erc20) { var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{orderId}", out msg); if (protobuf == null) { msg = "failed to extract address data through RPC"; return(false); } if (orderId.StartsWith(RpcHelper.creditCoinNamespace + RpcHelper.dealOrderPrefix)) { var dealOrder = DealOrder.Parser.ParseFrom(protobuf); if (gainString.Equals("0")) { srcAddressId = dealOrder.SrcAddress; dstAddressId = dealOrder.DstAddress; } else { dstAddressId = dealOrder.SrcAddress; srcAddressId = dealOrder.DstAddress; } amountString = dealOrder.Amount; } else if (orderId.StartsWith(RpcHelper.creditCoinNamespace + RpcHelper.repaymentOrderPrefix)) { var repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); if (gainString.Equals("0")) { srcAddressId = repaymentOrder.SrcAddress; dstAddressId = repaymentOrder.DstAddress; } else { dstAddressId = repaymentOrder.SrcAddress; srcAddressId = repaymentOrder.DstAddress; } amountString = repaymentOrder.Amount; } else { msg = "unexpected referred order"; return(false); } } string payTxId; if (File.Exists(progress)) { Console.WriteLine("Found unfinished action, retrying..."); payTxId = File.ReadAllText(progress); } else { string ethDstAddress = null; if (!erc20) { var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{srcAddressId}", out msg); if (protobuf == null) { msg = "failed to extract address data through RPC"; return(false); } var srcAddress = Address.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{dstAddressId}", out msg); if (protobuf == null) { msg = "failed to extract address data through RPC"; return(false); } Address dstAddress = Address.Parser.ParseFrom(protobuf); if (!srcAddress.Blockchain.Equals(name) || !dstAddress.Blockchain.Equals(name)) { msg = $"ethereum RegisterTransfer can only transfer ether.\nThis source is registered for {srcAddress.Blockchain} and destination for {dstAddress.Blockchain}"; return(false); } ethDstAddress = dstAddress.Value; if (!ethSrcAddress.Equals(srcAddress.Value, StringComparison.OrdinalIgnoreCase)) { msg = "The deal is for a different client"; return(false); } } BigInteger transferAmount; if (!BigInteger.TryParse(amountString, out transferAmount) || transferAmount <= 0) { msg = "Invalid amount"; return(false); } BigInteger gain; if (!BigInteger.TryParse(gainString, out gain)) { msg = "Invalid amount"; return(false); } transferAmount = transferAmount + gain; if (transferAmount < 0) { msg = "Invalid amount"; return(false); } var txCount = web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(ethSrcAddress).Result; TransactionSigner signer = new TransactionSigner(); HexBigInteger gasPrice; string gasPriceInGweiString = cfg["gasPriceInGwei"]; if (int.TryParse(gasPriceInGweiString, out int gasPriceOverride)) { gasPrice = new HexBigInteger(Nethereum.Util.UnitConversion.Convert.ToWei(gasPriceOverride, Nethereum.Util.UnitConversion.EthUnit.Gwei)); } else { gasPrice = web3.Eth.GasPrice.SendRequestAsync().Result; } Console.WriteLine("gasPrice: " + gasPrice.Value.ToString()); string to; string data; BigInteger amount; HexBigInteger gasLimit; if (erc20) { string creditcoinContract = cfg["creditcoinContract"]; string creditcoinContractAbi = cfg["creditcoinContractAbi"]; to = creditcoinContract; var contract = web3.Eth.GetContract(creditcoinContractAbi, creditcoinContract); var burn = contract.GetFunction("exchange"); var functionInput = new object[] { transferAmount, txBuilder.getSighash() }; data = burn.GetData(functionInput); gasLimit = burn.EstimateGasAsync(functionInput).Result; amount = 0; } else { gasLimit = web3.Eth.Transactions.EstimateGas.SendRequestAsync(new Nethereum.RPC.Eth.DTOs.CallInput(orderId, ethDstAddress, new Nethereum.Hex.HexTypes.HexBigInteger(transferAmount))).Result; Console.WriteLine("gasLimit: " + gasLimit.Value.ToString()); to = ethDstAddress; data = orderId; amount = transferAmount; } string txRaw = signer.SignTransaction(ethereumPrivateKey, to, amount, txCount, gasPrice, gasLimit, data); payTxId = web3.Eth.Transactions.SendRawTransaction.SendRequestAsync("0x" + txRaw).Result; Console.WriteLine("Ethereum Transaction ID: " + payTxId); File.WriteAllText(progress, $"{payTxId}"); } inProgress = true; while (true) { var receipt = web3.Eth.TransactionManager.TransactionReceiptService.PollForReceiptAsync(payTxId).Result; if (receipt.BlockNumber != null) { var blockNumber = web3.Eth.Blocks.GetBlockNumber.SendRequestAsync().Result; if (blockNumber.Value - receipt.BlockNumber.Value >= mConfirmationsExpected) { break; } } Thread.Sleep(1000); } File.Delete(progress); inProgress = false; if (erc20) { command = new string[] { command[0], ethSrcAddress, amountString, payTxId }; } else { command = new string[] { command[0], gainString, orderId, payTxId }; } var tx = txBuilder.BuildTx(command, out msg); if (tx == null) { return(false); } Debug.Assert(msg == null); var content = new ByteArrayContent(tx); content.Headers.Add("Content-Type", "application/octet-stream"); msg = RpcHelper.CompleteBatch(httpClient, url, "batches", content, txid); return(true); } else { inProgress = false; msg = "Unknown command: " + command[0]; return(false); } }
public bool Run(IConfiguration cfg, string signerHexStr, string[] command, out string msg) { Debug.Assert(command != null); Debug.Assert(command.Length > 0); if (command[0].Equals("verify")) { Debug.Assert(command.Length == 7); string txId = command[1]; string destinationAdressString = command[2]; string destinationAmount = command[3]; string sighash = command[4]; string sourceAddressString = command[5]; string networkId = command[6]; string rpcAddress = cfg["rpc"]; if (string.IsNullOrWhiteSpace(rpcAddress)) { msg = "bitcoin.rpc is not set"; return(false); } string credential = cfg["credential"]; if (string.IsNullOrWhiteSpace(credential)) { msg = "bitcoin.credential is not set"; return(false); } // TODO disable confirmation customization for release build string confirmationsCount = cfg["confirmationsCount"]; if (string.IsNullOrWhiteSpace(confirmationsCount)) { msg = "bitcoin.confirmationsCount is not set"; return(false); } var confirmationsExpected = 1; if (!int.TryParse(confirmationsCount, out confirmationsExpected)) { msg = "bitcoin.confirmationsCount is not an int"; return(false); } Network network = ((networkId == "1") ? Network.Main : Network.TestNet); var rpcClient = new RPCClient(credential, new Uri(rpcAddress), network); if (!uint256.TryParse(txId, out var transactionId)) { msg = "Invalid transaction: transaction ID invalid"; return(false); } var transactionInfoResponse = rpcClient.GetRawTransactionInfo(transactionId); if (transactionInfoResponse.Confirmations < confirmationsExpected) { msg = "Invalid transaction: not enough confirmations"; return(false); } if (transactionInfoResponse.Transaction.Outputs.Count < 2 || transactionInfoResponse.Transaction.Outputs.Count > 3) { msg = "Invalid transaction: unexpected amount of output"; return(false); } var destinationAddress = BitcoinAddress.Create(destinationAdressString, network); var outputCoins = transactionInfoResponse.Transaction.Outputs.AsCoins(); var paymentCoin = outputCoins.SingleOrDefault(oc => oc.ScriptPubKey == destinationAddress.ScriptPubKey); if (paymentCoin == null) { msg = "Invalid transaction: wrong destinationAdressString"; return(false); } if (paymentCoin.TxOut.Value.Satoshi.ToString() != destinationAmount.ToString()) { msg = "Invalid transaction: wrong amount"; return(false); } var bytes = Encoding.UTF8.GetBytes(sighash); var nullDataCoin = outputCoins.SingleOrDefault(oc => oc.ScriptPubKey == TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes)); if (nullDataCoin == null) { msg = "Invalid transaction: wrong sighash"; return(false); } if (nullDataCoin.TxOut.Value.CompareTo(Money.Zero) != 0) { msg = "Invalid transaction: expecting a message"; return(false); } var sourceAddress = BitcoinAddress.Create(sourceAddressString, network); var input = transactionInfoResponse.Transaction.Inputs[0]; if (!Script.VerifyScript(input.ScriptSig, sourceAddress.ScriptPubKey, transactionInfoResponse.Transaction, 0)) { msg = "Invalid transaction: wrong sourceAddressString"; return(false); } msg = null; return(true); } else if (command[0].Equals("unlock")) { var ccSigner = new Signer(RpcHelper.HexToBytes(signerHexStr)); var txBuilder = new TxBuilder(ccSigner); Debug.Assert(command.Length == 5); string kind = command[1]; string addressFromString = command[2]; HttpClient httpClient = new HttpClient(); string txFrom; string amountString; string feeString; string addressToString; string networkId; string ccCommand; if (kind.Equals("funds")) { string dealOrderId = command[3]; string addressToUnlockFundsTo = command[4]; var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrderId}", out msg); if (protobuf == null) { return(false); } var dealOrder = DealOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrder.AskOrderId}", out msg); if (protobuf == null) { return(false); } var askOrder = AskOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{askOrder.TransferId}", out msg); if (protobuf == null) { return(false); } var transfer = Transfer.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{addressToUnlockFundsTo}", out msg); if (protobuf == null) { return(false); } var address = Address.Parser.ParseFrom(protobuf); if (!askOrder.Sighash.Equals(address.Sighash)) { msg = "The adress doesn't match the ask order"; return(false); } txFrom = transfer.Txid; amountString = transfer.Amount; feeString = transfer.Fee; addressToString = address.Address_; networkId = transfer.Network; ccCommand = "UnlockFunds"; } else if (kind.Equals("collateral")) { string repaymentOrderId = command[3]; string addressToUnlockCollateralsTo = command[4]; var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{repaymentOrderId}", out msg); if (protobuf == null) { return(false); } var repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{repaymentOrder.DealId}", out msg); if (protobuf == null) { return(false); } var dealOrder = DealOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrder.AskOrderId}", out msg); if (protobuf == null) { return(false); } var askOrder = AskOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{askOrder.TransferId}", out msg); if (protobuf == null) { return(false); } var transfer = Transfer.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{addressToUnlockCollateralsTo}", out msg); if (protobuf == null) { return(false); } var address = Address.Parser.ParseFrom(protobuf); if (!askOrder.Sighash.Equals(address.Sighash)) { msg = "The adress doesn't match the ask order"; return(false); } txFrom = transfer.Txid; amountString = transfer.Amount; feeString = transfer.Fee; addressToString = address.Address_; networkId = transfer.Network; ccCommand = "UnlockCollateral"; } else { msg = "unknown unlock kind"; return(false); } string rpcAddress = cfg["rpc"]; if (string.IsNullOrWhiteSpace(rpcAddress)) { msg = "bitcoin.rpc is not set"; return(false); } string credential = cfg["credential"]; if (string.IsNullOrWhiteSpace(credential)) { msg = "bitcoin.credential is not set"; return(false); } string secret = cfg["secret"]; if (string.IsNullOrWhiteSpace(secret)) { msg = "bitcoin.secret is not set"; return(false); } // TODO disable confirmation config for release build string confirmationsCount = cfg["confirmationsCount"]; if (string.IsNullOrWhiteSpace(confirmationsCount)) { msg = "bitcoin.confirmationsCount is not set"; return(false); } var confirmationsExpected = 1; if (!int.TryParse(confirmationsCount, out confirmationsExpected)) { msg = "bitcoin.confirmationsCount is not an int"; return(false); } Network network = ((networkId == "1") ? Network.Main : Network.TestNet); var rpcClient = new RPCClient(credential, new Uri(rpcAddress), network); var transactionId = uint256.Parse(txFrom); var bitcoinPrivateKey = new BitcoinSecret(secret); if (bitcoinPrivateKey.Network != network) { msg = "Mismatching networks"; return(false); } var transactionResponse = rpcClient.GetRawTransaction(transactionId); Money transferAmount; if (!Money.TryParse(amountString, out transferAmount) || transferAmount <= 0) { msg = "Invalid amount"; return(false); } if (!int.TryParse(feeString, out int fee)) { msg = "bitcoin.fee is not an int"; return(false); } var receivedCoins = transactionResponse.Outputs.AsCoins(); OutPoint outPointToSpend = null; TxOut outTxToSpend = null; Money amount = null; foreach (var coin in receivedCoins) { if (coin.TxOut.ScriptPubKey == bitcoinPrivateKey.ScriptPubKey) { outPointToSpend = coin.Outpoint; outTxToSpend = coin.TxOut; amount = (Money)coin.Amount; if (amount.CompareTo(transferAmount + fee * 2) < 0) { msg = $"Invalid transaction - needed: {transferAmount}, has: {amount.ToString()}"; return(false); } break; } } if (outPointToSpend == null) { msg = "Invalid transaction - no outputs that the client can spend"; return(false); } var addressFrom = BitcoinAddress.Create(addressFromString, network); var addressTo = BitcoinAddress.Create(addressToString, network); var bitcoinTransactionBuilder = new TransactionBuilder(); var transaction = bitcoinTransactionBuilder .AddCoins(new Coin(outPointToSpend, outTxToSpend)) .AddKeys(bitcoinPrivateKey) .Send(addressTo.ScriptPubKey, transferAmount) .SendFees(new Money(fee, MoneyUnit.Satoshi)) .SetChange(addressFrom.ScriptPubKey) .BuildTransaction(true); if (!bitcoinTransactionBuilder.Verify(transaction)) { msg = "failed verify transaction"; return(false); } uint256 payTxId; try { payTxId = rpcClient.SendRawTransaction(transaction); } catch (RPCException e) { msg = $"failed to broadcast - error: {e.RPCCode}, reason: {e.RPCCodeMessage}"; return(false); } if (payTxId == null) { msg = "failed to broadcast - unknown error"; return(false); } while (true) //TODO: this may lock for a very long time or forever, fix --------------------------------------------------------------------------------------------------------------------------------------- { var transactionInfo = rpcClient.GetRawTransactionInfo(payTxId); if (transactionInfo != null && transactionInfo.BlockHash != null && transactionInfo.Confirmations >= confirmationsExpected) { break; } Thread.Sleep(1000); } command = new string[] { ccCommand, command[3], command[4] }; var tx = txBuilder.BuildTx(command, out msg); Debug.Assert(tx != null); Debug.Assert(msg == null); var content = new ByteArrayContent(tx); content.Headers.Add("Content-Type", "application/octet-stream"); msg = RpcHelper.CompleteBatch(httpClient, $"{creditcoinUrl}/batches", content); } msg = "Unknown command: " + command[0]; return(false); }
private const int SKIP_TO_GET_60 = 512 / 8 * 2 - 60; // 512 - hash size, 8 - bits in byte, 2 - hex digits for byte, 60 - merkle address length (70) without namespace length (6) and prexix length (4) static void Main(string[] args) { try { IConfiguration config = new ConfigurationBuilder() .AddJsonFile("appsettings.json", true, false) #if DEBUG .AddJsonFile("appsettings.dev.json", true, false) #endif .Build(); string signerHexStr = config["signer"]; if (string.IsNullOrWhiteSpace(signerHexStr)) { Console.WriteLine("Signer is not configured"); return; } var signer = new Signer(RpcHelper.HexToBytes(signerHexStr)); string creditcoinRestApiURL = config["creditcoinRestApiURL"]; if (!string.IsNullOrWhiteSpace(creditcoinRestApiURL)) { creditcoinUrl = creditcoinRestApiURL; } string root = Directory.GetCurrentDirectory(); string folder = TxBuilder.GetPluginsFolder(root); if (folder == null) { Console.WriteLine("plugins subfolder not found"); return; } string progress = Path.Combine(folder, "progress.txt"); string action; string[] command; if (File.Exists(progress)) { Console.WriteLine("Found unfinished action, retrying..."); args = File.ReadAllText(progress).Split(); } else { File.WriteAllText(progress, string.Join(' ', args)); } action = args[0].ToLower(); command = args.Skip(1).ToArray(); var txBuilder = new TxBuilder(signer); var settings = new Dictionary <string, string>(); filter(settingNamespace, (string address, byte[] protobuf) => { Setting setting = Setting.Parser.ParseFrom(protobuf); foreach (var entry in setting.Entries) { settings.Add(entry.Key, entry.Value); } }); bool inProgress = false; // API: // list Settings|Wallets|Addresses|Transfers|AskOrders|BidOrders|DealOrders|RepaymentOrders| // list Balance // list Sighash // list Address blockchain address // list UnusedTransfers addressId amount // list MatchingOrders // list CreditHistory sighash // list NewDeals // list RunningDeals // list NewRepaymentOrders // creditcoin AddFunds amount sighash (creditcoin AddFunds 1000000 16de574ac8ac3067977df056ecff51345672d25d528303b3555ab2aa4cd5) // AddFunds only works for a registered signer // creditcoin SendFunds amount sighash (creditcoin SendFunds 200000 8704a4f77befea5c8082d414f98dc16e4ba82a0898422d031f41693260a0) // creditcoin RegisterAddress blockchain address (creditcoin RegisterAddress bitcoin <BITCOIN-ADDRESS>) // creditcoin AddAskOrder blockchain amount interest blockchain collateral fee expiration capitalLockTransferId (creditcoin AddAskOrder bitcoin 1000000 10 bitcoin 50 100 1565386152 <TRANSFER-ID>) // creditcoin AddBidOrder blockchain amount interest blockchain collateral fee expiration (creditcoin AddAskOrder bitcoin 1000000 10 bitcoin 50 100 1565386152) // creditcoin AddDealOrder askOrderId bidOrderId // creditcoin CompleteDealOrder dealOrderId collateralLockTransferId // creditcoin AddRepaymentOrder dealOrderId // creditcoin CompleteRepaymentOrder repaymentOrderId transferId // creditcoin CollectCoins transferId // <blockchain> RegisterTransfer ... // bitcoin RegisterTransfer registeredAddressId amount sourceTxId // ethereum RegisterTransfer registeredAddressId amount [erc20] // unlock Funds dealOrderId addressToUnlockFundsTo // unlock Collateral repaymentOrderId addressToUnlockCollateralTo if (action.Equals("unlock")) { string externalGatewayAddress; if (!settings.TryGetValue("sawtooth.validator.gateway", out externalGatewayAddress)) { Console.WriteLine("Error: external gateway is not configured"); } else { if (!externalGatewayAddress.StartsWith("tcp://")) { externalGatewayAddress = "tcp://" + externalGatewayAddress; } bool success = true; switch (command[0].ToLower()) { case "funds": Debug.Assert(command.Length == 3); { var dealOrderId = command[1].ToLower(); var addressToUnlockFundsTo = command[2].ToLower(); string msg; string blockchain = null; var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrderId}", out msg); if (protobuf != null) { var dealOrder = DealOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrder.AskOrderId}", out msg); if (protobuf != null) { var askOrder = AskOrder.Parser.ParseFrom(protobuf); blockchain = askOrder.Blockchain; } } if (blockchain == null) { success = false; Console.WriteLine("Error: " + msg); } else { string escrow; if (!settings.TryGetValue("sawtooth.escrow." + blockchain, out escrow)) { success = false; Console.WriteLine("Error: escrow is not configured for " + blockchain); } else { using (var socket = new RequestSocket()) { socket.Connect(externalGatewayAddress); var request = $"{blockchain} unlock funds {escrow} {dealOrderId} {addressToUnlockFundsTo}"; socket.SendFrame(request); string response = socket.ReceiveFrameString(); if (response != "good") { success = false; Console.WriteLine("Error: failed to execute the global gateway command"); } } } } } break; case "collateral": Debug.Assert(command.Length == 3); { var repaymentOrderId = command[1].ToLower(); var addressToUnlockCollateralsTo = command[2].ToLower(); string msg; string blockchain = null; var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{repaymentOrderId}", out msg); if (protobuf != null) { var repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{repaymentOrder.DealId}", out msg); if (protobuf != null) { var dealOrder = DealOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrder.AskOrderId}", out msg); if (protobuf != null) { var askOrder = AskOrder.Parser.ParseFrom(protobuf); blockchain = askOrder.Blockchain; } } } if (blockchain == null) { success = false; Console.WriteLine("Error: " + msg); } else { string escrow; if (!settings.TryGetValue("sawtooth.escrow." + blockchain, out escrow)) { success = false; Console.WriteLine("Error: escrow is not configured for " + blockchain); } else { using (var socket = new RequestSocket()) { socket.Connect(externalGatewayAddress); var request = $"{blockchain} unlock collateral {escrow} {repaymentOrderId} {addressToUnlockCollateralsTo}"; socket.SendFrame(request); string response = socket.ReceiveFrameString(); if (response != "good") { success = false; Console.WriteLine("Error: failed to execute the global gateway command"); } } } } } break; } if (success) { Console.WriteLine("Success"); } } } else if (action.Equals("list")) { bool success = true; if (command[0].Equals("settings", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); filter(settingNamespace, (string objid, byte[] protobuf) => { Setting setting = Setting.Parser.ParseFrom(protobuf); foreach (var entry in setting.Entries) { Console.WriteLine($"{entry.Key}: {entry.Value}"); } }); } else if (command[0].Equals("wallets", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); filter(creditCoinNamespace + walletPrefix, (string objid, byte[] protobuf) => { Wallet wallet = Wallet.Parser.ParseFrom(protobuf); Console.WriteLine($"wallet({objid}) amount:{wallet.Amount}"); }); } else if (command[0].Equals("balance", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); { string sighash = sha256(signer.GetPublicKey().ToHexString()); string prefix = creditCoinNamespace + walletPrefix; string id = prefix + sighash; filter(prefix, (string objid, byte[] protobuf) => { Wallet wallet = Wallet.Parser.ParseFrom(protobuf); if (objid.Equals(id)) { Console.WriteLine($"balance for {sighash} is {wallet.Amount}"); } }); } } else if (command[0].Equals("addresses", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); filter(creditCoinNamespace + addressPrefix, (string objid, byte[] protobuf) => { Address address = Address.Parser.ParseFrom(protobuf); Console.WriteLine($"address({objid}) sighash:{address.Sighash}, blockchain: {address.Blockchain}, address:{address.Address_}"); }); } else if (command[0].Equals("transfers", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); filter(creditCoinNamespace + transferPrefix, (string objid, byte[] protobuf) => { Transfer transfer = Transfer.Parser.ParseFrom(protobuf); Console.WriteLine($"transfer({objid}) sighash:{transfer.Sighash}, blockchain: {transfer.Blockchain}, amount:{transfer.Amount}, fee:{transfer.Fee}, txid:{transfer.Txid}"); }); } else if (command[0].Equals("askOrders", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); filter(creditCoinNamespace + askOrderPrefix, (string objid, byte[] protobuf) => { AskOrder askOrder = AskOrder.Parser.ParseFrom(protobuf); Console.WriteLine($"askOrder({objid}) sighash:{askOrder.Sighash}, blockchain: {askOrder.Blockchain}, amount:{askOrder.Amount}, interest:{askOrder.Interest}, collateralBlockchain:{askOrder.CollateralBlockchain}, collateral:{askOrder.Collateral}, fee:{askOrder.Fee}, expiration:{askOrder.Expiration}, transferId:{askOrder.TransferId}"); }); } else if (command[0].Equals("bidOrders", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); filter(creditCoinNamespace + bidOrderPrefix, (string objid, byte[] protobuf) => { BidOrder bidOrder = BidOrder.Parser.ParseFrom(protobuf); Console.WriteLine($"bidOrder({objid}) sighash:{bidOrder.Sighash}, blockchain: {bidOrder.Blockchain}, amount:{bidOrder.Amount}, interest:{bidOrder.Interest}, collateralBlockchain:{bidOrder.CollateralBlockchain}, collateral:{bidOrder.Collateral}, fee:{bidOrder.Fee}, expiration:{bidOrder.Expiration}"); }); } else if (command[0].Equals("dealOrders", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); filter(creditCoinNamespace + dealOrderPrefix, (string objid, byte[] protobuf) => { DealOrder dealOrder = DealOrder.Parser.ParseFrom(protobuf); Console.WriteLine($"dealOrder({objid}) askOrderId:{dealOrder.AskOrderId}, bidOrderId: {dealOrder.BidOrderId}, collateralTransferId:{dealOrder.CollateralTransferId}"); }); } else if (command[0].Equals("repaymentOrders", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); filter(creditCoinNamespace + repaymentOrderPrefix, (string objid, byte[] protobuf) => { RepaymentOrder repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); Console.WriteLine($"repaymentOrder({objid}) dealId:{repaymentOrder.DealId}, transferId:{repaymentOrder.TransferId}"); }); } else if (command[0].Equals("sighash", StringComparison.OrdinalIgnoreCase)) { Console.WriteLine(sha256(signer.GetPublicKey().ToHexString())); } else if (command[0].Equals("address", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 3); var blockchain = command[1].ToLower(); var addr = command[2]; filter(creditCoinNamespace + addressPrefix, (string objid, byte[] protobuf) => { Address address = Address.Parser.ParseFrom(protobuf); if (address.Blockchain == blockchain && address.Address_ == addr) { Console.WriteLine(objid); } }); } else if (command[0].Equals("unusedTransfers", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 3); var addressId = command[1]; var amount = command[2]; var sighash = sha256(signer.GetPublicKey().ToHexString()); Address address = null; filter(creditCoinNamespace + addressPrefix, (string objid, byte[] protobuf) => { if (objid == addressId) { address = Address.Parser.ParseFrom(protobuf); } }); if (address == null) { Console.WriteLine("Invalid command " + command[0]); success = false; } else { filter(creditCoinNamespace + transferPrefix, (string objid, byte[] protobuf) => { Transfer transfer = Transfer.Parser.ParseFrom(protobuf); if (transfer.Sighash == sighash && transfer.Orderid.Equals(string.Empty) && transfer.Blockchain == address.Blockchain && transfer.Amount == amount) { Console.WriteLine(objid); } }); } } else if (command[0].Equals("matchingOrders", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); var askOrders = new Dictionary <string, AskOrder>(); filter(creditCoinNamespace + askOrderPrefix, (string objid, byte[] protobuf) => { AskOrder askOrder = AskOrder.Parser.ParseFrom(protobuf); askOrders.Add(objid, askOrder); }); var bidOrders = new Dictionary <string, BidOrder>(); filter(creditCoinNamespace + bidOrderPrefix, (string objid, byte[] protobuf) => { BidOrder bidOrder = BidOrder.Parser.ParseFrom(protobuf); bidOrders.Add(objid, bidOrder); }); match(signer, askOrders, bidOrders); } else if (command[0].Equals("creditHistory", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 2); var fundraiser = command[1].ToLower(); filterDeals(null, fundraiser, (string dealAddress, DealOrder dealOrder, AskOrder askOrder, BidOrder bidOrder) => { Debug.Assert(askOrder == null); var status = dealOrder.CollateralTransferId.Equals(string.Empty) ? "INCOMPLETE" : "COMPLETE"; if (!dealOrder.UnlockCollateralDestinationAddressId.Equals(string.Empty)) { status = "CLOSED"; } else if (!dealOrder.UnlockFundsDestinationAddressId.Equals(string.Empty)) { status = "ACTIVE"; } Console.WriteLine($"status: {status}, amount:{bidOrder.Amount}, blockchain: {bidOrder.Blockchain}, collateral:{bidOrder.Collateral}"); }); } else if (command[0].Equals("newDeals", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); var fundraiser = sha256(signer.GetPublicKey().ToHexString()); filterDeals(null, fundraiser, (string dealAddress, DealOrder dealOrder, AskOrder askOrder, BidOrder bidOrder) => { Debug.Assert(askOrder == null); if (dealOrder.CollateralTransferId.Equals(string.Empty)) { Console.WriteLine(dealAddress); } }); } else if (command[0].Equals("runningDeals", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); var investor = sha256(signer.GetPublicKey().ToHexString()); var deals = new List <string>(); filterDeals(investor, null, (string dealAddress, DealOrder dealOrder, AskOrder askOrder, BidOrder bidOrder) => { Debug.Assert(bidOrder == null); if (!dealOrder.CollateralTransferId.Equals(string.Empty) && dealOrder.UnlockCollateralDestinationAddressId.Equals(string.Empty)) { deals.Add(dealAddress); } }); filter(creditCoinNamespace + repaymentOrderPrefix, (string objid, byte[] protobuf) => { RepaymentOrder repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); if (deals.SingleOrDefault(x => x.Equals(repaymentOrder.DealId)) != null) { deals.Remove(repaymentOrder.DealId); } }); foreach (var deal in deals) { Console.WriteLine(deal); } } else if (command[0].Equals("newRepaymentOrders", StringComparison.OrdinalIgnoreCase)) { Debug.Assert(command.Length == 1); var fundraiser = sha256(signer.GetPublicKey().ToHexString()); var deals = new List <string>(); filterDeals(null, fundraiser, (string dealAddress, DealOrder dealOrder, AskOrder askOrder, BidOrder bidOrder) => { Debug.Assert(askOrder == null); if (!dealOrder.CollateralTransferId.Equals(string.Empty) && dealOrder.UnlockCollateralDestinationAddressId.Equals(string.Empty)) { deals.Add(dealAddress); } }); filter(creditCoinNamespace + repaymentOrderPrefix, (string objid, byte[] protobuf) => { RepaymentOrder repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); if (repaymentOrder.TransferId.Equals(string.Empty) && deals.SingleOrDefault(x => x.Equals(repaymentOrder.DealId)) != null) { Console.WriteLine(objid); } }); } else { Console.WriteLine("Invalid command " + command[0]); success = false; } if (success) { Console.WriteLine("Success"); } } else if (action.Equals("creditcoin")) { string msg; var tx = txBuilder.BuildTx(command, out msg); if (tx == null) { Debug.Assert(msg != null); Console.WriteLine(msg); } else { Debug.Assert(msg == null); var content = new ByteArrayContent(tx); content.Headers.Add("Content-Type", "application/octet-stream"); Console.WriteLine(RpcHelper.CompleteBatch(httpClient, $"{creditcoinUrl}/batches", content)); } } else { var loader = new Loader <ICCClientPlugin>(); var msgs = new List <string>(); loader.Load(folder, msgs); foreach (var msg in msgs) { Console.WriteLine(msg); } ICCClientPlugin plugin = loader.Get(action); var pluginConfig = config.GetSection(action); if (plugin == null) { Console.WriteLine("Error: Unknown action " + action); } else { string msg; bool done = plugin.Run(pluginConfig, httpClient, txBuilder, settings, folder, creditcoinUrl, command, out inProgress, out msg); if (done) { if (msg == null) { msg = "Success"; } Console.WriteLine(msg); } else { Console.WriteLine("Error: " + msg); } } } if (!inProgress) { File.Delete(progress); } } catch (Exception x) { Console.WriteLine($"Error: unexpected failure - {x.Message}"); } }
public bool Run(IConfiguration cfg, HttpClient httpClient, ITxBuilder txBuilder, Dictionary <string, string> settings, string pluginsFolder, string url, string[] command, out bool inProgress, out string msg) { Debug.Assert(command != null); Debug.Assert(command.Length > 1); if (command[0].Equals("registerTransfer", StringComparison.OrdinalIgnoreCase)) { // bitcoin RegisterTransfer registeredSourceId amount sourceTxId string progress = Path.Combine(pluginsFolder, $"{name}_progress.txt"); inProgress = false; Debug.Assert(command.Length == 4); string registeredSourceId = command[1]; string amountString = command[2]; string sourceTxIdString = command[3]; string secret = cfg["secret"]; if (string.IsNullOrWhiteSpace(secret)) { msg = "bitcoin.secret is not set"; return(false); } string rpcAddress = cfg["rpc"]; if (string.IsNullOrWhiteSpace(rpcAddress)) { msg = "bitcoin.rpc is not set"; return(false); } string credential = cfg["credential"]; if (string.IsNullOrWhiteSpace(credential)) { msg = "bitcoin.credential is not set"; return(false); } //TODO disable confirmation count config for release build string confirmationsCount = cfg["confirmationsCount"]; if (string.IsNullOrWhiteSpace(confirmationsCount)) { msg = "bitcoin.confirmationsCount is not set"; return(false); } int confirmationsExpected = 1; if (!int.TryParse(confirmationsCount, out confirmationsExpected)) { msg = "bitcoin.confirmationsCount is not an int"; return(false); } string feeString = cfg["fee"]; if (string.IsNullOrWhiteSpace(feeString)) { msg = "bitcoin.fee is not set"; return(false); } if (!int.TryParse(feeString, out int fee)) { msg = "bitcoin.fee is not an int"; return(false); } var bitcoinPrivateKey = new BitcoinSecret(secret); var network = bitcoinPrivateKey.Network; var rpcClient = new RPCClient(credential, new Uri(rpcAddress), network); string payTxIdHash; uint256 payTxId; if (File.Exists(progress)) { Console.WriteLine("Found unfinished action, retrying..."); payTxIdHash = File.ReadAllText(progress); if (!uint256.TryParse(payTxIdHash, out payTxId)) { msg = "corrupted progress file"; return(false); } } else { var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{registeredSourceId}", out msg); if (protobuf == null) { return(false); } var address = Address.Parser.ParseFrom(protobuf); string destinationAddress; if (!settings.TryGetValue("sawtooth.escrow." + name, out destinationAddress)) { msg = "Escrow not found for " + name; return(false); } Money transferAmount; if (!Money.TryParse(amountString, out transferAmount) || transferAmount <= 0) { msg = "Invalid amount"; return(false); } if (!address.Blockchain.Equals(name)) { msg = $"bitcoin RegisterTransfer can only transfer bitcoins.\nThis source is registered for {address.Blockchain}"; return(false); } var sourceAddress = bitcoinPrivateKey.GetAddress(); if (!sourceAddress.ToString().Equals(address.Address_, StringComparison.OrdinalIgnoreCase)) { msg = "The deal is for a different client"; return(false); } #if RELEASE if (network != NBitcoin.Network.Main) { msg = "bitcoin.secret is not defined for main network"; return(false); } #endif var sourceTxId = uint256.Parse(sourceTxIdString); var transactionResponse = rpcClient.GetRawTransaction(sourceTxId); //TODO: fix (assuming that the transaction has one output intended for the private key with the exact amount) var receivedCoins = transactionResponse.Outputs.AsCoins(); OutPoint outPointToSpend = null; TxOut outTxToSpend = null; Money amount = null; foreach (var coin in receivedCoins) { if (coin.TxOut.ScriptPubKey == bitcoinPrivateKey.ScriptPubKey) { outPointToSpend = coin.Outpoint; outTxToSpend = coin.TxOut; amount = (Money)coin.Amount; if (amount.CompareTo(transferAmount + fee * 2) < 0) { msg = $"Invalid transaction - needed: {transferAmount}, has: {amount.ToString()}"; return(false); } break; } } if (outPointToSpend == null) { msg = "Invalid transaction - no outputs that the client can spend"; return(false); } var addressFrom = BitcoinAddress.Create(address.Address_, network); var addressTo = BitcoinAddress.Create(destinationAddress, network); var message = address.Sighash; var bytes = Encoding.UTF8.GetBytes(message); var bitcoinTransactionBuilder = new TransactionBuilder(); var transaction = bitcoinTransactionBuilder .AddCoins(new Coin(outPointToSpend, outTxToSpend)) .AddKeys(bitcoinPrivateKey) .Send(addressTo.ScriptPubKey, transferAmount + fee) .Send(TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes), Money.Zero) .SendFees(new Money(fee, MoneyUnit.Satoshi)) .SetChange(addressFrom.ScriptPubKey) .BuildTransaction(true); if (!bitcoinTransactionBuilder.Verify(transaction)) { msg = "failed verify transaction"; return(false); } try { payTxId = rpcClient.SendRawTransaction(transaction); } catch (RPCException e) { msg = $"failed to broadcast - error: {e.RPCCode}, reason: {e.RPCCodeMessage}"; return(false); } if (payTxId == null) { msg = "failed to broadcast - unknown error"; return(false); } payTxIdHash = payTxId.ToString(); File.WriteAllText(progress, payTxIdHash); } inProgress = true; while (true) { var transactionResponse = rpcClient.GetRawTransactionInfo(payTxId); if (transactionResponse != null && transactionResponse.BlockHash != null && transactionResponse.Confirmations >= confirmationsExpected) { break; } Thread.Sleep(1000); } command = new string[] { command[0], registeredSourceId, amountString, feeString, payTxIdHash, ((network == Network.Main) ? "1" : "0") }; var tx = txBuilder.BuildTx(command, out msg); Debug.Assert(tx != null); Debug.Assert(msg == null); var content = new ByteArrayContent(tx); content.Headers.Add("Content-Type", "application/octet-stream"); msg = RpcHelper.CompleteBatch(httpClient, $"{url}/batches", content); File.Delete(progress); inProgress = false; return(true); } else { inProgress = false; msg = "Unknown command: " + command[0]; return(false); } }
public bool Run(IConfiguration cfg, string signerHexStr, string[] command, out string msg) { Debug.Assert(command != null); Debug.Assert(command.Length > 0); if (command[0].Equals("verify")) { Debug.Assert(command.Length == 7); string txId = command[1]; string destinationAddressString = command[2]; string destinationAmount = command[3]; string sighash = command[4]; string sourceAddressString = command[5]; string networkId = command[6]; // TODO disable confirmation count config for release build string confirmationsCount = cfg["confirmationsCount"]; if (string.IsNullOrWhiteSpace(confirmationsCount)) { msg = "ethereum.confirmationsCount is not set"; return(false); } if (!int.TryParse(confirmationsCount, out int confirmationsExpected)) { msg = "ethereum.confirmationsCount is not an int"; return(false); } string rpcUrl = cfg["rpc"]; if (string.IsNullOrWhiteSpace(rpcUrl)) { msg = "ethereum.rpc is not set"; return(false); } var web3 = new Nethereum.Web3.Web3(rpcUrl); var tx = web3.Eth.Transactions.GetTransactionByHash.SendRequestAsync(txId).Result; int confirmations = 0; if (tx.BlockNumber != null) { var blockNumber = web3.Eth.Blocks.GetBlockNumber.SendRequestAsync().Result; confirmations = (int)(blockNumber.Value - tx.BlockNumber.Value); } if (confirmations < confirmationsExpected) { msg = "Invalid transaction: not enough confirmations"; return(false); } if (!sourceAddressString.Equals(tx.From, System.StringComparison.OrdinalIgnoreCase)) { msg = "Invalid transaction: wrong sourceAddressString"; return(false); } if (networkId.Equals("creditcoin")) { string creditcoinContract = cfg["creditcoinContract"]; if (string.IsNullOrWhiteSpace(creditcoinContract)) { msg = "ethereum.creditcoinContract is not set"; return(false); } string creditcoinContractAbi = cfg["creditcoinContractAbi"]; if (string.IsNullOrWhiteSpace(creditcoinContractAbi)) { msg = "ethereum.creditcoinContractAbi is not set"; return(false); } var contract = web3.Eth.GetContract(creditcoinContractAbi, creditcoinContract); var burn = contract.GetFunction("exchange"); var inputs = burn.DecodeInput(tx.Input); Debug.Assert(inputs.Count == 2); var value = inputs[0].Result.ToString(); if (destinationAmount != value) { msg = "Invalid transaction: wrong amount"; return(false); } var tag = inputs[1].Result.ToString(); if (!tag.Equals(sighash)) { msg = "Invalid transaction: wrong sighash"; return(false); } } else { if (destinationAmount != tx.Value.Value.ToString()) { msg = "Invalid transaction: wrong amount"; return(false); } if (tx.Input == null) { msg = "Invalid transaction: expecting data"; return(false); } if (!sighash.StartsWith("0x")) { sighash = "0x" + sighash; } if (!tx.Input.Equals(sighash, System.StringComparison.OrdinalIgnoreCase)) { msg = "Invalid transaction: wrong sighash"; return(false); } if (!tx.To.Equals(destinationAddressString, System.StringComparison.OrdinalIgnoreCase)) { msg = "Invalid transaction: wrong destinationAddressString"; return(false); } } msg = null; return(true); } else if (command[0].Equals("unlock")) { var ccSigner = new Signer(RpcHelper.HexToBytes(signerHexStr)); var txBuilder = new TxBuilder(ccSigner); Debug.Assert(command.Length == 5); string kind = command[1]; string addressFromString = command[2]; HttpClient httpClient = new HttpClient(); string txFrom; string amountString; string feeString; string addressToString; string networkId; string ccCommand; if (kind.Equals("funds")) { string dealOrderId = command[3]; string addressToUnlockFundsTo = command[4]; var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrderId}", out msg); if (protobuf == null) { return(false); } var dealOrder = DealOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrder.AskOrderId}", out msg); if (protobuf == null) { return(false); } var askOrder = AskOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{askOrder.TransferId}", out msg); if (protobuf == null) { return(false); } var transfer = Transfer.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{addressToUnlockFundsTo}", out msg); if (protobuf == null) { return(false); } var address = Address.Parser.ParseFrom(protobuf); if (!askOrder.Sighash.Equals(address.Sighash)) { msg = "The address doesn't match the ask order"; return(false); } txFrom = transfer.Txid; amountString = transfer.Amount; feeString = transfer.Fee; addressToString = address.Address_; networkId = transfer.Network; ccCommand = "UnlockFunds"; } else if (kind.Equals("collateral")) { string repaymentOrderId = command[3]; string addressToUnlockCollateralsTo = command[4]; var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{repaymentOrderId}", out msg); if (protobuf == null) { return(false); } var repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{repaymentOrder.DealId}", out msg); if (protobuf == null) { return(false); } var dealOrder = DealOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{dealOrder.AskOrderId}", out msg); if (protobuf == null) { return(false); } var askOrder = AskOrder.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{askOrder.TransferId}", out msg); if (protobuf == null) { return(false); } var transfer = Transfer.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{creditcoinUrl}/state/{addressToUnlockCollateralsTo}", out msg); if (protobuf == null) { return(false); } var address = Address.Parser.ParseFrom(protobuf); if (!askOrder.Sighash.Equals(address.Sighash)) { msg = "The address doesn't match the ask order"; return(false); } txFrom = transfer.Txid; amountString = transfer.Amount; feeString = transfer.Fee; addressToString = address.Address_; networkId = transfer.Network; ccCommand = "UnlockCollateral"; } else { msg = "unknown unlock kind"; return(false); } string secret = cfg["secret"]; if (string.IsNullOrWhiteSpace(secret)) { msg = "ethereum.secret is not set"; return(false); } var ethereumPrivateKey = secret; // TODO disable confirmation count config for release build string confirmationsCount = cfg["confirmationsCount"]; if (string.IsNullOrWhiteSpace(confirmationsCount)) { msg = "ethereum.confirmationsCount is not set"; return(false); } if (!int.TryParse(confirmationsCount, out int confirmationsExpected)) { msg = "ethereum.confirmationsCount is not an int"; return(false); } string rpcUrl = cfg["rpc"]; if (string.IsNullOrWhiteSpace(rpcUrl)) { msg = "ethereum.rpc is not set"; return(false); } BigInteger fee; if (!BigInteger.TryParse(feeString, out fee)) { msg = "Invalid progress data"; return(false); } var web3 = new Nethereum.Web3.Web3(rpcUrl); BigInteger transferAmount; if (!BigInteger.TryParse(amountString, out transferAmount) || transferAmount <= 0) { msg = "Invalid amount"; return(false); } string sourceAddress = EthECKey.GetPublicAddress(ethereumPrivateKey); if (!sourceAddress.Equals(addressFromString, StringComparison.OrdinalIgnoreCase)) { msg = "The deal is for a different client"; return(false); } var txCount = web3.Eth.Transactions.GetTransactionCount.SendRequestAsync(sourceAddress).Result; TransactionSigner signer = new TransactionSigner(); var gasLimit = web3.Eth.Transactions.EstimateGas.SendRequestAsync(new Nethereum.RPC.Eth.DTOs.CallInput(string.Empty, addressToString, new Nethereum.Hex.HexTypes.HexBigInteger(transferAmount))).Result; var gasPrice = web3.Eth.GasPrice.SendRequestAsync().Result; string txRaw = signer.SignTransaction(ethereumPrivateKey, addressToString, transferAmount + fee, txCount, gasPrice, gasLimit, string.Empty); string payTxId = web3.Eth.Transactions.SendRawTransaction.SendRequestAsync("0x" + txRaw).Result; while (true) { var receipt = web3.Eth.TransactionManager.TransactionReceiptService.PollForReceiptAsync(payTxId).Result; if (receipt.BlockNumber != null) { var blockNumber = web3.Eth.Blocks.GetBlockNumber.SendRequestAsync().Result; if (blockNumber.Value - receipt.BlockNumber.Value >= confirmationsExpected) { break; } } Thread.Sleep(1000); } command = new string[] { ccCommand, command[3], command[4] }; var tx = txBuilder.BuildTx(command, out msg); Debug.Assert(tx != null); Debug.Assert(msg == null); var content = new ByteArrayContent(tx); content.Headers.Add("Content-Type", "application/octet-stream"); msg = RpcHelper.CompleteBatch(httpClient, $"{creditcoinUrl}/batches", content); } msg = "Unknown command: " + command[0]; return(false); }
public bool Run(bool txid, IConfiguration cfg, HttpClient httpClient, ITxBuilder txBuilder, Dictionary <string, string> settings, string progressId, string pluginsFolder, string url, string[] command, out bool inProgress, out string msg) { Debug.Assert(command != null); if (command.Length < 2) { inProgress = false; msg = "invalid parameter count"; return(false); } if (command[0].Equals("registerTransfer", StringComparison.OrdinalIgnoreCase)) { // bitcoin RegisterTransfer srcAddressId dstAddressId orderId amount sourceTxId string progress = Path.Combine(pluginsFolder, $"{name}_progress{progressId}.txt"); inProgress = false; if (command.Length != 4) { msg = "invalid parameter count"; return(false); } string gainString = command[1]; string orderId = command[2]; string sourceTxIdString = command[3]; string secret = cfg["secret"]; if (string.IsNullOrWhiteSpace(secret)) { msg = "bitcoin.secret is not set"; return(false); } string rpcAddress = cfg["rpc"]; if (string.IsNullOrWhiteSpace(rpcAddress)) { msg = "bitcoin.rpc is not set"; return(false); } string credential = cfg["credential"]; if (string.IsNullOrWhiteSpace(credential)) { msg = "bitcoin.credential is not set"; return(false); } #if DEBUG string confirmationsCount = cfg["confirmationsCount"]; if (int.TryParse(confirmationsCount, out int parsedCount)) { mConfirmationsExpected = parsedCount; } #endif string feeString = cfg["fee"]; if (string.IsNullOrWhiteSpace(feeString)) { msg = "bitcoin.fee is not set"; return(false); } if (!int.TryParse(feeString, out int fee)) { msg = "bitcoin.fee is not an int"; return(false); } var bitcoinPrivateKey = new BitcoinSecret(secret); var network = bitcoinPrivateKey.Network; var rpcClient = new RPCClient(credential, new Uri(rpcAddress), network); string srcAddressId; string dstAddressId; string amountString; var protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{orderId}", out msg); if (protobuf == null) { msg = "failed to extract address data through RPC"; return(false); } if (orderId.StartsWith(RpcHelper.creditCoinNamespace + RpcHelper.dealOrderPrefix)) { var dealOrder = DealOrder.Parser.ParseFrom(protobuf); if (gainString.Equals("0")) { srcAddressId = dealOrder.SrcAddress; dstAddressId = dealOrder.DstAddress; } else { dstAddressId = dealOrder.SrcAddress; srcAddressId = dealOrder.DstAddress; } amountString = dealOrder.Amount; } else if (orderId.StartsWith(RpcHelper.creditCoinNamespace + RpcHelper.repaymentOrderPrefix)) { var repaymentOrder = RepaymentOrder.Parser.ParseFrom(protobuf); if (gainString.Equals("0")) { srcAddressId = repaymentOrder.SrcAddress; dstAddressId = repaymentOrder.DstAddress; } else { dstAddressId = repaymentOrder.SrcAddress; srcAddressId = repaymentOrder.DstAddress; } amountString = repaymentOrder.Amount; } else { msg = "unexpected referred order"; return(false); } string payTxIdHash; uint256 payTxId; if (File.Exists(progress)) { Console.WriteLine("Found unfinished action, retrying..."); payTxIdHash = File.ReadAllText(progress); if (!uint256.TryParse(payTxIdHash, out payTxId)) { msg = "corrupted progress file"; return(false); } } else { protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{srcAddressId}", out msg); if (protobuf == null) { msg = "failed to extract address data through RPC"; return(false); } var srcAddress = Address.Parser.ParseFrom(protobuf); protobuf = RpcHelper.ReadProtobuf(httpClient, $"{url}/state/{dstAddressId}", out msg); if (protobuf == null) { msg = "failed to extract address data through RPC"; return(false); } var dstAddress = Address.Parser.ParseFrom(protobuf); long transferAmount; if (!long.TryParse(amountString, out transferAmount) || transferAmount <= 0) { msg = "Invalid amount"; return(false); } long gain; if (!long.TryParse(gainString, out gain)) { msg = "Invalid amount"; return(false); } if (transferAmount + gain < transferAmount) { msg = "Overflow"; return(false); } transferAmount = transferAmount + gain; if (transferAmount < 0) { msg = "Invalid amount"; return(false); } if (transferAmount + fee < transferAmount) { msg = "Overflow"; return(false); } if (!srcAddress.Blockchain.Equals(name) || !dstAddress.Blockchain.Equals(name)) { msg = $"bitcoin RegisterTransfer can only transfer bitcoins.\nThis source is registered for {srcAddress.Blockchain} and destination for {dstAddress.Blockchain}"; return(false); } var sourceAddress = bitcoinPrivateKey.GetAddress(); if (!sourceAddress.ToString().Equals(srcAddress.Value, StringComparison.OrdinalIgnoreCase)) { msg = "The deal is for a different client"; return(false); } #if RELEASE if (network != NBitcoin.Network.Main) { msg = "bitcoin.secret is not defined for main network"; return(false); } #endif var sourceTxId = uint256.Parse(sourceTxIdString); var transactionResponse = rpcClient.GetRawTransaction(sourceTxId); //TODO: fix (assuming that the transaction has one output intended for the private key with the exact amount) var receivedCoins = transactionResponse.Outputs.AsCoins(); OutPoint outPointToSpend = null; TxOut outTxToSpend = null; foreach (var coin in receivedCoins) { if (coin.TxOut.ScriptPubKey == bitcoinPrivateKey.ScriptPubKey) { outPointToSpend = coin.Outpoint; outTxToSpend = coin.TxOut; long amount = coin.Amount.Satoshi; if (amount < transferAmount + fee) { msg = $"Invalid transaction - needed: {transferAmount} + {fee}, has: {amount.ToString()}"; return(false); } break; } } if (outPointToSpend == null) { msg = "Invalid transaction - no outputs that the client can spend"; return(false); } var addressFrom = BitcoinAddress.Create(srcAddress.Value, network); var addressTo = BitcoinAddress.Create(dstAddress.Value, network); var message = orderId; var bytes = Encoding.UTF8.GetBytes(message); var bitcoinTransactionBuilder = new TransactionBuilder(); var transaction = bitcoinTransactionBuilder .AddCoins(new Coin(outPointToSpend, outTxToSpend)) .AddKeys(bitcoinPrivateKey) .Send(addressTo.ScriptPubKey, transferAmount) .Send(TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes), Money.Zero) .SendFees(new Money(fee, MoneyUnit.Satoshi)) .SetChange(addressFrom.ScriptPubKey) .BuildTransaction(true); if (!bitcoinTransactionBuilder.Verify(transaction)) { msg = "failed verify transaction"; return(false); } try { payTxId = rpcClient.SendRawTransaction(transaction); } catch (RPCException e) { msg = $"failed to broadcast - error: {e.RPCCode}, reason: {e.RPCCodeMessage}"; return(false); } if (payTxId == null) { msg = "failed to broadcast - unknown error"; return(false); } payTxIdHash = payTxId.ToString(); File.WriteAllText(progress, payTxIdHash); } inProgress = true; while (true) { var transactionResponse = rpcClient.GetRawTransactionInfo(payTxId); if (transactionResponse != null && transactionResponse.BlockHash != null && transactionResponse.Confirmations >= mConfirmationsExpected) { break; } Thread.Sleep(1000); } command = new string[] { command[0], gainString, orderId, payTxIdHash }; var tx = txBuilder.BuildTx(command, out msg); Debug.Assert(tx != null); Debug.Assert(msg == null); var content = new ByteArrayContent(tx); content.Headers.Add("Content-Type", "application/octet-stream"); msg = RpcHelper.CompleteBatch(httpClient, url, "batches", content, txid); File.Delete(progress); inProgress = false; return(true); } else { inProgress = false; msg = "Unknown command: " + command[0]; return(false); } }