构建一笔交易
通过MakeTransaction(TransferOutput[] outputs, UInt160 from = null)
函数构建。
UInt160[] accounts;
if (from is null)
{
accounts = GetAccounts().Where(p => !p.Lock && !p.WatchOnly).Select(p => p.ScriptHash).ToArray();
}
else
{
if (!Contains(from))
throw new ArgumentException($"The address {from.ToString()} was not found in the wallet");
accounts = new[] { from };
}
如果未指定from账户,则首先调用GetAccount()
获取钱包中所有非Lock和非WatchOnly的账户列表,按照脚本哈希排序。
若指定from账户,则判断账户是否包含在钱包中,不包含则抛出异常。
将获取的账户加入accounts。
把outputs中的输出按照(assetId, group, sum)分组,其中assetId表示需要输出的资产ID,group表示输出该类型资产的output集合,sum表示输出资产的总量。
对于每一种资产,分别对每个账户构建脚本,进入虚拟机执行得到资产余额,并记录在balances中。最后将所有余额相加得到资产总余额,如果小于需要输出的总量,则抛出余额不足异常。余额充足则进入下一步。
foreach (TransferOutput output in group)
{
balances = balances.OrderBy(p => p.Value).ToList();
var balances_used = FindPayingAccounts(balances, output.Value.Value);
cosigners.UnionWith(balances_used.Select(p => p.Account));
foreach (var (account, value) in balances_used)
{
sb.EmitAppCall(output.AssetId, "transfer", account, output.ScriptHash, value);
sb.Emit(OpCode.THROWIFNOT);
}
}
最主要的便是FindPayingAccounts(balances, output.Value.Value);根据balances和输出的数量选择合适的账户付款。
如果balances刚好与输出相等,则所有账户即为付款账户。
否则,遍历每个账户,如果某个账户的余额等于输出,则该账户为付款账户。如果账户余额大于输出,则该账户为付款账户,数量为需要输出数量,并更新账户余额。
如果所有账户的余额均小于输出,则从余额最多的账户开始向下遍历,依次加入付款账户,直到某账户余额大于需要付款的值,此时从余额最小的账户向上遍历,找到第一个余额大于需要输出的账户,将该账户作为付款账户,并更新余额。
例如有7个账户余额分别为1,2,3,4,5,6,7,输出为15.5。首先会选择7,6,两个账户,此时5>2.5,则从小到大遍历,其中第一个大于2.5的账户为3,所以输出3。最终的付款账户为(7,6,3),值为(7,6,2.5)。
随后记录所有付款账户地址,并按照账户依次构造虚拟机APPCALL转账脚本。计算所有账户Gas余额,并调用
MakeTransaction(snapshot, attributes, script, balances_gas);
该函数首先将脚本进入虚拟机test模式,计算Gas消耗量。如果Gas消耗超过免费额度,则超过部分向上取整作为SystemFee。(对remainder<0不清楚)
接下来计算NetworkFee,
foreach (UInt160 hash in hashes)
{
byte[] witness_script = GetAccount(hash)?.Contract?.Script ?? snapshot.Contracts.TryGet(hash)?.Script;
if (witness_script is null) continue;
if (witness_script.IsSignatureContract())
{
size += 66 + witness_script.GetVarSize();
tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null);
}
else if (witness_script.IsMultiSigContract(out int m, out int n))
{
int size_inv = 65 * m;
size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize();
tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] * m;
using (ScriptBuilder sb = new ScriptBuilder())
tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]];
tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] * n;
using (ScriptBuilder sb = new ScriptBuilder())
tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]];
tx.NetworkFee += InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null) * n;
}
else
{
//We can support more contract types in the future.
}
}
tx.NetworkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
if (value >= tx.SystemFee + tx.NetworkFee) return tx;
NetworkFee分单签和多签的情况。且NetworkFee主要由交易size费用以及操作码费用两部分组成。
对于单签,首先是区块的固定部分size:
int size = Transaction.HeaderSize + attributes.GetVarSize()
+ script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length);
加上脚本的size:
size += 66 + witness_script.GetVarSize();
66代表单签脚本验证时需要添加的PUSHBYTES64+64Bytes的长度(还有1不知道是什么)。
tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] + ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] + InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null);
这部分包括单签验证需要的opcode的费用。
最后加上所有size与每字节的费用:
tx.NetworkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
就得到了NetworkFee的总量。
类似的,对于多签脚本:
int size_inv = 65 * m;
size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize();
tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES64] * m;
using (ScriptBuilder sb = new ScriptBuilder())
tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]];
tx.NetworkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHBYTES33] * n;
using (ScriptBuilder sb = new ScriptBuilder())
tx.NetworkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]];
tx.NetworkFee += InteropService.GetPrice(InteropService.Neo_Crypto_CheckSig, null) * n;
同样是包括总的size费用以及多签验证脚本的操作码对应总费用。
tx.NetworkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
if (value >= tx.SystemFee + tx.NetworkFee) return tx;
最后计算SystemFee和NetworkFee的和,如果Gas余额大于之和,则返回Tx,否则抛出异常。
网友评论