NEO 构建一笔交易分析

构建一笔交易

通过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,否则抛出异常。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值