/// <summary> /// 估算交易费率 /// </summary> /// <returns></returns> public long EstimateSmartFee() { //对象初始化 var txDac = new TransactionDac(); var transactionMsgs = new List <TransactionMsg>(); var txPool = TransactionPool.Instance; long totalSize = 0; long totalFee = 0; //设置最大上限 long maxSize = BlockSetting.MAX_BLOCK_SIZE - (1 * 1024); //交易池中的项目按照费率从高到低排列 List <TransactionPoolItem> poolItemList = txPool.MainPool.OrderByDescending(t => t.FeeRate).ToList(); var index = 0; while (totalSize < maxSize && index < poolItemList.Count) { //获取totalFee和totalSize TransactionMsg tx = poolItemList[index].Transaction; //判断交易Hash是否在交易Msg中 if (tx != null && transactionMsgs.Where(t => t.Hash == tx.Hash).Count() == 0) { totalFee += Convert.ToInt64(poolItemList[index].FeeRate * tx.Serialize().LongLength / 1024.0); if (txDac.SelectByHash(tx.Hash) == null) { transactionMsgs.Add(tx); totalSize += tx.Size; } else { txPool.RemoveTransaction(tx.Hash); } } /* * else * { * break; * } */ index++; } //获取费率 if (poolItemList.Count == 0) { return(1024); } long feeRate = Convert.ToInt64(Math.Ceiling((totalFee / (totalSize / 1024.0)) / poolItemList.Count)); if (feeRate < 1024) { feeRate = 1024; } return(feeRate); }
public IRpcMethodResult EstimateTxFeeForSendToAddress(string toAddress, long amount, string comment, string commentTo, bool deductFeeFromAmount) { try { EstimateTxFeeOM result = new EstimateTxFeeOM(); var utxoComponent = new UtxoComponent(); var txComponent = new TransactionComponent(); var settingComponent = new SettingComponent(); var addressBookComponent = new AddressBookComponent(); var accountComponent = new AccountComponent(); if (!AccountIdHelper.AddressVerify(toAddress)) { throw new CommonException(ErrorCode.Service.Transaction.TO_ADDRESS_INVALID); } var setting = settingComponent.GetSetting(); var utxos = utxoComponent.GetAllConfirmedOutputs(); var tx = new TransactionMsg(); var totalSize = tx.Serialize().Length; var output = new OutputMsg(); output.Amount = amount; output.Index = 0; output.LockScript = Script.BuildLockScipt(toAddress); output.Size = output.LockScript.Length; tx.Outputs.Add(output); totalSize += output.Serialize().Length; var blockComponent = new BlockComponent(); var lastBlockHeight = blockComponent.GetLatestHeight(); var totalInput = 0L; var index = 0; double totalAmount = amount; double totalFee = setting.FeePerKB * ((double)totalSize / 1024.0); while (index < utxos.Count) { var account = accountComponent.GetAccountById(utxos[index].AccountId); if (account != null && !string.IsNullOrWhiteSpace(account.PrivateKey)) { var utxoTX = txComponent.GetTransactionMsgByHash(utxos[index].TransactionHash); Block utxoBlock = blockComponent.GetBlockEntiytByHash(utxos[index].BlockHash); if (utxoTX == null || utxoBlock == null) { index++; continue; } if (!utxoBlock.IsVerified) { index++; continue; } if (Time.EpochTime < utxoTX.Locktime) { index++; continue; } if (utxoTX.InputCount == 1 && utxoTX.Inputs[0].OutputTransactionHash == Base16.Encode(HashHelper.EmptyHash())) { var blockHeight = utxoBlock.Height; if (lastBlockHeight - blockHeight < 100L) { index++; continue; } } var input = new InputMsg(); input.OutputTransactionHash = utxos[index].TransactionHash; input.OutputIndex = utxos[index].OutputIndex; input.UnlockScript = Script.BuildUnlockScript(input.OutputTransactionHash, input.OutputIndex, Base16.Decode(decryptPrivateKey(account.PrivateKey)), Base16.Decode(account.PublicKey)); input.Size = input.UnlockScript.Length; tx.Inputs.Add(input); var size = input.Serialize().Length; totalSize += size; totalFee += setting.FeePerKB * ((double)size / 1024.0); totalInput += utxos[index].Amount; } else { index++; continue; } if (!deductFeeFromAmount) { totalAmount = amount + totalFee; } if (totalInput >= (long)Math.Ceiling(totalAmount)) { var size = output.Serialize().Length; if ((totalInput - (long)Math.Ceiling(totalAmount)) > (setting.FeePerKB * (double)size / 1024.0)) { totalSize += size; totalFee += setting.FeePerKB * ((double)size / 1024.0); } break; } index++; } if (totalInput < totalAmount) { throw new CommonException(ErrorCode.Service.Transaction.BALANCE_NOT_ENOUGH); } if (deductFeeFromAmount) { output.Amount -= (long)Math.Ceiling(totalFee); if (output.Amount <= 0) { throw new CommonException(ErrorCode.Service.Transaction.SEND_AMOUNT_LESS_THAN_FEE); } } result.totalFee = Convert.ToInt64(totalFee); result.totalSize = Convert.ToInt32(totalSize); return(Ok(result)); } catch (CommonException ce) { return(Error(ce.ErrorCode, ce.Message, ce)); } catch (Exception ex) { return(Error(ErrorCode.UNKNOWN_ERROR, ex.Message, ex)); } }
public IRpcMethodResult SendMany(string fromAccount, SendManyOutputIM[] receivers, string[] feeDeductAddresses) { try { string result = null; var utxoComponent = new UtxoComponent(); var txComponent = new TransactionComponent(); var settingComponent = new SettingComponent(); var addressBookComponent = new AddressBookComponent(); var accountComponent = new AccountComponent(); var transactionCommentComponent = new TransactionCommentComponent(); var blockComponent = new BlockComponent(); var lastBlockHeight = blockComponent.GetLatestHeight(); var setting = settingComponent.GetSetting(); var utxos = utxoComponent.GetAllConfirmedOutputs(); var tx = new TransactionMsg(); double totalOutput = 0; var totalSize = tx.Serialize().Length; if (receivers == null || receivers.Length == 0) { throw new CommonException(ErrorCode.Service.Transaction.TO_ADDRESS_INVALID); } foreach (var receiver in receivers) { if (!AccountIdHelper.AddressVerify(receiver.address)) { throw new CommonException(ErrorCode.Service.Transaction.TO_ADDRESS_INVALID); } var output = new OutputMsg(); output.Amount = receiver.amount; output.Index = tx.Outputs.Count; output.LockScript = Script.BuildLockScipt(receiver.address); output.Size = output.LockScript.Length; tx.Outputs.Add(output); totalSize += output.Serialize().Length; totalOutput += receiver.amount; } foreach (var address in feeDeductAddresses) { if (receivers.Where(r => r.address == address).Count() == 0) { throw new CommonException(ErrorCode.Service.Transaction.FEE_DEDUCT_ADDRESS_INVALID); } } var totalInput = 0L; var index = 0; double totalFee = setting.FeePerKB * ((double)totalSize / 1024.0); double totalAmount = totalOutput; while (index < utxos.Count) { var account = accountComponent.GetAccountById(utxos[index].AccountId); if (account != null && !string.IsNullOrWhiteSpace(account.PrivateKey)) { var utxoTX = txComponent.GetTransactionMsgByHash(utxos[index].TransactionHash); Block utxoBlock = blockComponent.GetBlockEntiytByHash(utxos[index].BlockHash); if (utxoTX == null || utxoBlock == null) { index++; continue; } if (!utxoBlock.IsVerified) { index++; continue; } if (Time.EpochTime < utxoTX.Locktime) { index++; continue; } if (utxoTX.InputCount == 1 && utxoTX.Inputs[0].OutputTransactionHash == Base16.Encode(HashHelper.EmptyHash())) { var blockHeight = utxoBlock.Height; if (lastBlockHeight - blockHeight < 100L) { index++; continue; } } var input = new InputMsg(); input.OutputTransactionHash = utxos[index].TransactionHash; input.OutputIndex = utxos[index].OutputIndex; input.UnlockScript = Script.BuildUnlockScript(input.OutputTransactionHash, input.OutputIndex, Base16.Decode(decryptPrivateKey(account.PrivateKey)), Base16.Decode(account.PublicKey)); input.Size = input.UnlockScript.Length; tx.Inputs.Add(input); var size = input.Serialize().Length; totalSize += size; totalFee += setting.FeePerKB * ((double)size / 1024.0); totalInput += utxos[index].Amount; } else { index++; continue; } if (feeDeductAddresses == null || feeDeductAddresses.Length == 0) { totalAmount = totalOutput + totalFee; } if (totalInput >= (long)Math.Ceiling(totalAmount)) { var size = tx.Outputs[0].Serialize().Length; if ((totalInput - (long)Math.Ceiling(totalAmount)) > (setting.FeePerKB * (double)size / 1024.0)) { totalSize += size; totalFee += setting.FeePerKB * ((double)size / 1024.0); if (feeDeductAddresses == null || feeDeductAddresses.Length == 0) { totalAmount = totalOutput + totalFee; } var newAccount = accountComponent.GenerateNewAccount(); if (setting.Encrypt) { if (!string.IsNullOrWhiteSpace(_cache.Get <string>("WalletPassphrase"))) { newAccount.PrivateKey = AES128.Encrypt(newAccount.PrivateKey, _cache.Get <string>("WalletPassphrase")); accountComponent.UpdatePrivateKeyAr(newAccount); } else { throw new CommonException(ErrorCode.Service.Wallet.WALLET_HAS_BEEN_LOCKED); } } var newOutput = new OutputMsg(); newOutput.Amount = totalInput - (long)Math.Ceiling(totalAmount); newOutput.Index = tx.Outputs.Count; newOutput.LockScript = Script.BuildLockScipt(newAccount.Id); newOutput.Size = newOutput.LockScript.Length; tx.Outputs.Add(newOutput); } break; } index++; } if (totalInput < totalAmount) { throw new CommonException(ErrorCode.Service.Transaction.BALANCE_NOT_ENOUGH); } if (feeDeductAddresses != null && feeDeductAddresses.Length > 0) { var averageFee = totalFee / feeDeductAddresses.Length; for (int i = 0; i < receivers.Length; i++) { if (feeDeductAddresses.Contains(receivers[i].address)) { tx.Outputs[i].Amount -= (long)Math.Ceiling(averageFee); if (tx.Outputs[i].Amount <= 0) { throw new CommonException(ErrorCode.Service.Transaction.SEND_AMOUNT_LESS_THAN_FEE); } } } } tx.Timestamp = Time.EpochTime; tx.Hash = tx.GetHash(); txComponent.AddTransactionToPool(tx); Startup.P2PBroadcastTransactionAction(tx.Hash); result = tx.Hash; for (int i = 0; i < receivers.Length; i++) { var receiver = receivers[i]; if (!string.IsNullOrWhiteSpace(receiver.tag)) { addressBookComponent.SetTag(receiver.address, receiver.tag); } if (!string.IsNullOrWhiteSpace(receiver.comment)) { transactionCommentComponent.Add(tx.Hash, i, receiver.comment); } } return(Ok(result)); } catch (CommonException ce) { return(Error(ce.ErrorCode, ce.Message, ce)); } catch (Exception ex) { return(Error(ErrorCode.UNKNOWN_ERROR, ex.Message, ex)); } }
public bool VerifyTransaction(TransactionMsg transaction, out long txFee, out long totalOutput, out long totalInput) { var blockComponent = new BlockComponent(); txFee = 0; //compatible with old node if (transaction.Locktime > 0 && transaction.ExpiredTime == transaction.Locktime) { transaction.ExpiredTime = 0; } //step 0 if (transaction.Hash != transaction.GetHash()) { LogHelper.Error("Tx Hash Error:" + transaction.Hash); LogHelper.Error("Timestamp:" + transaction.Timestamp); LogHelper.Error("Locktime:" + transaction.Locktime); LogHelper.Error("ExpiredTime:" + transaction.ExpiredTime); LogHelper.Error("InputCount:" + transaction.InputCount); LogHelper.Error("OutputCount:" + transaction.OutputCount); throw new CommonException(ErrorCode.Engine.Transaction.Verify.TRANSACTION_HASH_ERROR); } //step 1 if (transaction.InputCount == 0 || transaction.OutputCount == 0) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.INPUT_AND_OUTPUT_CANNOT_BE_EMPTY); } //step 2 if (transaction.Hash == Base16.Encode(HashHelper.EmptyHash())) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.HASH_CANNOT_BE_EMPTY); } //step 3 if (transaction.Locktime < 0 || transaction.Locktime > (Time.EpochTime + BlockSetting.LOCK_TIME_MAX)) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.LOCK_TIME_EXCEEDED_THE_LIMIT); } //step 4 if (transaction.Serialize().Length < BlockSetting.TRANSACTION_MIN_SIZE) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.TRANSACTION_SIZE_BELOW_THE_LIMIT); } //step 5 if (this.existsInDB(transaction.Hash)) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.TRANSACTION_HAS_BEEN_EXISTED); } totalOutput = 0; totalInput = 0; foreach (var output in transaction.Outputs) { if (output.Amount <= 0 || output.Amount > BlockSetting.OUTPUT_AMOUNT_MAX) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.OUTPUT_EXCEEDED_THE_LIMIT); } if (!Script.VerifyLockScriptFormat(output.LockScript)) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.SCRIPT_FORMAT_ERROR); } totalOutput += output.Amount; } var count = transaction.Inputs.Distinct().Count(); if (count != transaction.Inputs.Count) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.UTXO_DUPLICATED_IN_ONE_TRANSACTION); } foreach (var input in transaction.Inputs) { if (!Script.VerifyUnlockScriptFormat(input.UnlockScript)) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.SCRIPT_FORMAT_ERROR); } var utxo = UtxoSetDac.Default.Get(input.OutputTransactionHash, input.OutputIndex); if (utxo != null) { if (!utxo.IsConfirmed(GlobalParameters.LocalHeight)) { if (utxo.IsCoinbase) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.COINBASE_NEED_100_CONFIRMS); } else { return(false); } } if (Time.EpochTime < utxo.Locktime) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.TRANSACTION_IS_LOCKED); } if (utxo.IsSpent()) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.UTXO_HAS_BEEN_SPENT); } if (!Script.VerifyLockScriptByUnlockScript(input.OutputTransactionHash, input.OutputIndex, utxo.LockScript, input.UnlockScript)) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.UTXO_UNLOCK_FAIL); } totalInput += utxo.Amount; } else { //not found output, wait for other transactions or blocks; txFee = 0; return(false); } } if (totalOutput >= totalInput) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.OUTPUT_LARGE_THAN_INPUT); } if ((totalInput - totalOutput) < BlockSetting.TRANSACTION_MIN_FEE) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.TRANSACTION_FEE_IS_TOO_FEW); } if (totalInput > BlockSetting.INPUT_AMOUNT_MAX) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.INPUT_EXCEEDED_THE_LIMIT); } if (totalOutput > BlockSetting.OUTPUT_AMOUNT_MAX) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.OUTPUT_EXCEEDED_THE_LIMIT); } txFee = totalInput - totalOutput; return(true); }
public bool VerifyTransaction(TransactionMsg transaction, out long txFee) { var blockComponent = new BlockComponent(); //step 0 if (transaction.Hash != transaction.GetHash()) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.TRANSACTION_HASH_ERROR); } //step 1 if (transaction.InputCount == 0 || transaction.OutputCount == 0) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.INPUT_AND_OUTPUT_CANNOT_BE_EMPTY); } //step 2 if (transaction.Hash == Base16.Encode(HashHelper.EmptyHash())) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.HASH_CANNOT_BE_EMPTY); } //step 3 if (transaction.Locktime < 0 || transaction.Locktime > (Time.EpochTime + BlockSetting.LOCK_TIME_MAX)) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.LOCK_TIME_EXCEEDED_THE_LIMIT); } //step 4 if (transaction.Serialize().Length < BlockSetting.TRANSACTION_MIN_SIZE) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.TRANSACTION_SIZE_BELOW_THE_LIMIT); } //step 5 if (this.existsInDB(transaction.Hash)) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.TRANSACTION_HAS_BEEN_EXISTED); } long totalOutput = 0; long totalInput = 0; foreach (var output in transaction.Outputs) { if (output.Amount <= 0 || output.Amount > BlockSetting.OUTPUT_AMOUNT_MAX) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.OUTPUT_EXCEEDED_THE_LIMIT); } if (!Script.VerifyLockScriptFormat(output.LockScript)) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.SCRIPT_FORMAT_ERROR); } totalOutput += output.Amount; } foreach (var input in transaction.Inputs) { if (!Script.VerifyUnlockScriptFormat(input.UnlockScript)) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.SCRIPT_FORMAT_ERROR); } long amount; string lockScript; long blockHeight; var result = this.getOutput(input.OutputTransactionHash, input.OutputIndex, out amount, out lockScript, out blockHeight); if (result) { var utxoTx = this.GetTransactionMsgByHash(input.OutputTransactionHash); if (utxoTx.InputCount == 1 && utxoTx.Inputs[0].OutputTransactionHash == Base16.Encode(HashHelper.EmptyHash()) && (blockHeight < 0 || (blockComponent.GetLatestHeight() - blockHeight < 100))) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.COINBASE_NEED_100_CONFIRMS); } if (Time.EpochTime < utxoTx.Locktime) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.TRANSACTION_IS_LOCKED); } //check whether contain two or more same utxo in one transaction var count = transaction.Inputs.Where(i => i.OutputTransactionHash == input.OutputTransactionHash && i.OutputIndex == input.OutputIndex).Count(); if (count > 1 || this.checkOutputSpent(transaction.Hash, input.OutputTransactionHash, input.OutputIndex)) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.UTXO_HAS_BEEN_SPENT); } if (!Script.VerifyLockScriptByUnlockScript(input.OutputTransactionHash, input.OutputIndex, lockScript, input.UnlockScript)) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.UTXO_UNLOCK_FAIL); } totalInput += amount; } else { //not found output, wait for other transactions or blocks; txFee = 0; return(false); } } if (totalOutput >= totalInput) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.OUTPUT_LARGE_THAN_INPUT); } if ((totalInput - totalOutput) < BlockSetting.TRANSACTION_MIN_FEE) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.TRANSACTION_FEE_IS_TOO_FEW); } if (totalInput > BlockSetting.INPUT_AMOUNT_MAX) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.INPUT_EXCEEDED_THE_LIMIT); } if (totalOutput > BlockSetting.OUTPUT_AMOUNT_MAX) { throw new CommonException(ErrorCode.Engine.Transaction.Verify.OUTPUT_EXCEEDED_THE_LIMIT); } txFee = totalInput - totalOutput; return(true); }