美文网首页
NEO 构建一笔交易分析

NEO 构建一笔交易分析

作者: ShawnYung | 来源:发表于2019-10-28 17:10 被阅读0次

    构建一笔交易

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

    相关文章

      网友评论

          本文标题:NEO 构建一笔交易分析

          本文链接:https://www.haomeiwen.com/subject/usopectx.html