美文网首页
NEO的智能合约部署与调用

NEO的智能合约部署与调用

作者: ShawnYung | 来源:发表于2019-10-30 16:15 被阅读0次

    1.智能合约的部署

    1. 首先在gui里加载已经编写好的合约avm,然后填入相关信息以及参数列表和返回值。这里我们的合约输入是两个string,输出为一个string,所以参数列表填入0707,返回值07。
      snipaste_20181018_182245.png
    2. 调用GetTransaction(),其作用为从gui里读出合约相关信息,然后根据信息创建一个合约脚本。
    public InvocationTransaction GetTransaction()
            {
                byte[] script = textBox8.Text.HexToBytes();
                byte[] parameter_list = textBox6.Text.HexToBytes();
                ContractParameterType return_type = textBox7.Text.HexToBytes().Select(p => (ContractParameterType?)p).FirstOrDefault() ?? ContractParameterType.Void;
                ContractPropertyState properties = ContractPropertyState.NoProperty;
                if (checkBox1.Checked) properties |= ContractPropertyState.HasStorage;
                if (checkBox2.Checked) properties |= ContractPropertyState.HasDynamicInvoke;
                string name = textBox1.Text;
                string version = textBox2.Text;
                string author = textBox3.Text;
                string email = textBox4.Text;
                string description = textBox5.Text;
                using (ScriptBuilder sb = new ScriptBuilder())
                {
                    sb.EmitSysCall("Neo.Contract.Create", script, parameter_list, return_type, properties, name, version, author, email, description);
                    return new InvocationTransaction
                    {
                        Script = sb.ToArray()
                    };
                }
    

    EmitSysCall后面会在加载虚拟机时执行下面的方法构造一个智能合约。

    private bool Contract_Create(ExecutionEngine engine)
            {
                TR.Enter();
                byte[] script = engine.EvaluationStack.Pop().GetByteArray();
                if (script.Length > 1024 * 1024) return TR.Exit(false);
                ContractParameterType[] parameter_list = engine.EvaluationStack.Pop().GetByteArray().Select(p => (ContractParameterType)p).ToArray();
                if (parameter_list.Length > 252) return TR.Exit(false);
                ContractParameterType return_type = (ContractParameterType)(byte)engine.EvaluationStack.Pop().GetBigInteger();
                ContractPropertyState contract_properties = (ContractPropertyState)(byte)engine.EvaluationStack.Pop().GetBigInteger();
                if (engine.EvaluationStack.Peek().GetByteArray().Length > 252) return TR.Exit(false);
                string name = Encoding.UTF8.GetString(engine.EvaluationStack.Pop().GetByteArray());
                if (engine.EvaluationStack.Peek().GetByteArray().Length > 252) return TR.Exit(false);
                string version = Encoding.UTF8.GetString(engine.EvaluationStack.Pop().GetByteArray());
                if (engine.EvaluationStack.Peek().GetByteArray().Length > 252) return TR.Exit(false);
                string author = Encoding.UTF8.GetString(engine.EvaluationStack.Pop().GetByteArray());
                if (engine.EvaluationStack.Peek().GetByteArray().Length > 252) return TR.Exit(false);
                string email = Encoding.UTF8.GetString(engine.EvaluationStack.Pop().GetByteArray());
                if (engine.EvaluationStack.Peek().GetByteArray().Length > 65536) return TR.Exit(false);
                string description = Encoding.UTF8.GetString(engine.EvaluationStack.Pop().GetByteArray());
                UInt160 hash = script.ToScriptHash();
                ContractState contract = contracts.TryGet(hash);
                if (contract == null)
                {
                    contract = new ContractState
                    {
                        Script = script,
                        ParameterList = parameter_list,
                        ReturnType = return_type,
                        ContractProperties = contract_properties,
                        Name = name,
                        CodeVersion = version,
                        Author = author,
                        Email = email,
                        Description = description
                    };
                    contracts.Add(hash, contract);
                    contracts_created.Add(hash, new UInt160(engine.CurrentContext.ScriptHash));
                }
                engine.EvaluationStack.Push(StackItem.FromInterface(contract));
                return TR.Exit(true);
            }
    

    最后返回了一个InvocationTransaction,其Script包含合约的信息。

    textBox9.Text = textBox8.Text.HexToBytes().ToScriptHash().ToString();
    

    即Script Hash的值为智能合约代码的hash值,后面合约调用也是根据这个hash区寻找指定的脚本。这里可能会造成一个问题,如果你和别人的智能合约代码完全相同,则这两个脚本会指向同一个地址,可能会出现异常。

    1. 点击部署完成后会自动弹出调用合约的界面,之前生成的脚本会自动显示在上方的文本框中。


      snipaste_20181019_121437.png

      这里必须先点击试运行,当试运行通过之后才可以点击调用。
      点击试运行会调用一下代码:

    private void button5_Click(object sender, EventArgs e)
            {
                byte[] script;
                try
                {
                    script = textBox6.Text.Trim().HexToBytes();
                }
                catch (FormatException ex)
                {
                    MessageBox.Show(ex.Message);
                    return;
                }
                if (tx == null) tx = new InvocationTransaction();
                tx.Version = 1;
                tx.Script = script;
                if (tx.Attributes == null) tx.Attributes = new TransactionAttribute[0];
                if (tx.Inputs == null) tx.Inputs = new CoinReference[0];
                if (tx.Outputs == null) tx.Outputs = new TransactionOutput[0];
                if (tx.Scripts == null) tx.Scripts = new Witness[0];
                ApplicationEngine engine = ApplicationEngine.Run(tx.Script, tx);
                StringBuilder sb = new StringBuilder();
                sb.AppendLine($"VM State: {engine.State}");
                sb.AppendLine($"Gas Consumed: {engine.GasConsumed}");
                sb.AppendLine($"Evaluation Stack: {new JArray(engine.EvaluationStack.Select(p => p.ToParameter().ToJson()))}");
                textBox7.Text = sb.ToString();
                if (!engine.State.HasFlag(VMState.FAULT))
                {
                    tx.Gas = engine.GasConsumed - Fixed8.FromDecimal(10);
                    if (tx.Gas < Fixed8.Zero) tx.Gas = Fixed8.Zero;
                    tx.Gas = tx.Gas.Ceiling();
                    Fixed8 fee = tx.Gas.Equals(Fixed8.Zero) ? net_fee : tx.Gas;
                    label7.Text = fee + " gas";
                    button3.Enabled = true;
                }
                else
                {
                    MessageBox.Show(Strings.ExecutionFailed);
                }
            }
    

    ApplicationEngine.Run(tx.Script, tx);此时会将tx放入虚拟机中进行运行。

    public static ApplicationEngine Run(byte[] script, IScriptContainer container = null, Block persisting_block = null)
            {
                TR.Enter();
                if (persisting_block == null)
                    persisting_block = new Block
                    {
                        Version = 0,
                        PrevHash = Blockchain.Default.CurrentBlockHash,
                        MerkleRoot = new UInt256(),
                        Timestamp = Blockchain.Default.GetHeader(Blockchain.Default.Height).Timestamp + Blockchain.SecondsPerBlock,
                        Index = Blockchain.Default.Height + 1,
                        ConsensusData = 0,
                        NextConsensus = Blockchain.Default.GetHeader(Blockchain.Default.Height).NextConsensus,
                        Script = new Witness
                        {
                            InvocationScript = new byte[0],
                            VerificationScript = new byte[0]
                        },
                        Transactions = new Transaction[0]
                    };
                DataCache<UInt160, AccountState> accounts = Blockchain.Default.GetStates<UInt160, AccountState>();
                DataCache<UInt256, AssetState> assets = Blockchain.Default.GetStates<UInt256, AssetState>();
                DataCache<UInt160, ContractState> contracts = Blockchain.Default.GetStates<UInt160, ContractState>();
                DataCache<StorageKey, StorageItem> storages = Blockchain.Default.GetStates<StorageKey, StorageItem>();
                CachedScriptTable script_table = new CachedScriptTable(contracts);
                using (StateMachine service = new StateMachine(persisting_block, accounts, assets, contracts, storages))
                {
                    ApplicationEngine engine = new ApplicationEngine(TriggerType.Application, container, script_table, service, Fixed8.Zero, true);
                    engine.LoadScript(script, false);
                    engine.Execute();
                    return TR.Exit(engine);
                }
            }
    

    首先会注册service,然后加载脚本至虚拟机,运行虚拟机。

    public new bool Execute()
            {
                TR.Enter();
                try
                {
                    while (!State.HasFlag(VMState.HALT) && !State.HasFlag(VMState.FAULT))
                    {
                        if (CurrentContext.InstructionPointer < CurrentContext.Script.Length)
                        {
                            OpCode nextOpcode = CurrentContext.NextInstruction;
    
                            gas_consumed = checked(gas_consumed + GetPrice(nextOpcode) * ratio);
                            if (!testMode && gas_consumed > gas_amount)
                            {
                                State |= VMState.FAULT;
                                return TR.Exit(false);
                            }
    
                            if (!CheckItemSize(nextOpcode) ||
                                !CheckStackSize(nextOpcode) ||
                                !CheckArraySize(nextOpcode) ||
                                !CheckInvocationStack(nextOpcode) ||
                                !CheckBigIntegers(nextOpcode) ||
                                !CheckDynamicInvoke(nextOpcode))
                            {
                                State |= VMState.FAULT;
                                return TR.Exit(false);
                            }
                        }
                        StepInto();
                    }
                }
                catch
                {
                    State |= VMState.FAULT;
                    return TR.Exit(false);
                }
                return TR.Exit(!State.HasFlag(VMState.FAULT));
            }
    

    这部分运行和虚拟机的运行基本类似,只是多了一步计算gas消耗的操作,GetPrice(),官方规定了不同操作收取不同的费用 http://docs.neo.org/zh-cn/sc/systemfees.html
    其中大部分操作有固定的费用,其他操作根据指令有不同情况。对于多签验证,每个签名收取0.1gas(应该是一个公钥0.1gas?)。

    case OpCode.CHECKMULTISIG:
                        {
                            if (EvaluationStack.Count == 0) return TR.Exit(1);
                            int n = (int)EvaluationStack.Peek().GetBigInteger();
                            if (n < 1) return TR.Exit(1);
                            return TR.Exit(100 * n);
                        }
    

    对于Contract.Create和Contract.Migrate类型,ContractProperties位于contract第四的位置,所以将contract_properties找出来,看是否需要存储和动态调用, 创建智能合约与迁移智能合约目前是根据合约所需功能进行收费。其中基础的费用为 100GAS,需要存储区 +400GAS,需要动态调用 +500GAS。

     case "AntShares.Contract.Migrate":
                        long fee = 100L;
    
                        ContractPropertyState contract_properties = (ContractPropertyState)(byte)EvaluationStack.Peek(3).GetBigInteger();
    
                        if (contract_properties.HasFlag(ContractPropertyState.HasStorage))
                        {
                            fee += 400L;
                        }
                        if (contract_properties.HasFlag(ContractPropertyState.HasDynamicInvoke))
                        {
                            fee += 500L;
                        }
                        return TR.Exit(fee * 100000000L / ratio);
    

    运行完成后会返回engine,然后将相关信息显示

                StringBuilder sb = new StringBuilder();
                sb.AppendLine($"VM State: {engine.State}");
                sb.AppendLine($"Gas Consumed: {engine.GasConsumed}");
                sb.AppendLine($"Evaluation Stack: {new JArray(engine.EvaluationStack.Select(p => p.ToParameter().ToJson()))}");
                textBox7.Text = sb.ToString();
                if (!engine.State.HasFlag(VMState.FAULT))
                {
                    tx.Gas = engine.GasConsumed - Fixed8.FromDecimal(10);
                    if (tx.Gas < Fixed8.Zero) tx.Gas = Fixed8.Zero;
                    tx.Gas = tx.Gas.Ceiling();
                    Fixed8 fee = tx.Gas.Equals(Fixed8.Zero) ? net_fee : tx.Gas;
                    label7.Text = fee + " gas";
                    button3.Enabled = true;
                }
    

    tx的gas消耗为所有操作的gas消耗综合减去10gas的免费额度后的值。并将调用按钮变为可用。


    snipaste_20181019_141410.png

    点击调用后,由tx构建一个InvocationTransaction,调用了MakeTransaction<T>。

    public T MakeTransaction<T>(T tx, UInt160 from = null, UInt160 change_address = null, Fixed8 fee = default(Fixed8)) where T : Transaction
            {
                TR.Enter();
                if (tx.Outputs == null) tx.Outputs = new TransactionOutput[0];
                if (tx.Attributes == null) tx.Attributes = new TransactionAttribute[0];
                fee += tx.SystemFee;   // tx.SystemFee = tx.Gas
                var pay_total = (typeof(T) == typeof(IssueTransaction) ? new TransactionOutput[0] : tx.Outputs).GroupBy(p => p.AssetId, (k, g) => new
                {
                    AssetId = k,
                    Value = g.Sum(p => p.Value)
                }).ToDictionary(p => p.AssetId);
                if (fee > Fixed8.Zero)
                {
                    if (pay_total.ContainsKey(Blockchain.UtilityToken.Hash))
                    {
                        pay_total[Blockchain.UtilityToken.Hash] = new
                        {
                            AssetId = Blockchain.UtilityToken.Hash,
                            Value = pay_total[Blockchain.UtilityToken.Hash].Value + fee
                        };
                    }
                    else
                    {
                        pay_total.Add(Blockchain.UtilityToken.Hash, new
                        {
                            AssetId = Blockchain.UtilityToken.Hash,
                            Value = fee
                        });
                    }
                }
                var pay_coins = pay_total.Select(p => new
                {
                    AssetId = p.Key,
                    Unspents = from == null ? FindUnspentCoins(p.Key, p.Value.Value) : FindUnspentCoins(p.Key, p.Value.Value, from)
                }).ToDictionary(p => p.AssetId);
                if (pay_coins.Any(p => p.Value.Unspents == null)) return null;
                var input_sum = pay_coins.Values.ToDictionary(p => p.AssetId, p => new
                {
                    p.AssetId,
                    Value = p.Unspents.Sum(q => q.Output.Value)
                });
                if (change_address == null) change_address = GetChangeAddress();
                List<TransactionOutput> outputs_new = new List<TransactionOutput>(tx.Outputs);
                foreach (UInt256 asset_id in input_sum.Keys)
                {
                    if (input_sum[asset_id].Value > pay_total[asset_id].Value)
                    {
                        outputs_new.Add(new TransactionOutput
                        {
                            AssetId = asset_id,
                            Value = input_sum[asset_id].Value - pay_total[asset_id].Value,
                            ScriptHash = change_address
                        });
                    }
                }
                tx.Inputs = pay_coins.Values.SelectMany(p => p.Unspents).Select(p => p.Reference).ToArray();
                tx.Outputs = outputs_new.ToArray();
                return TR.Exit(tx);
            }
    

    paytotal = gas,会根据这个新建一个outputs.之后得到一个完整的tx。

    1. SignAndShowInformation(tx) 这部分与之前讲的一样。
    public static void SignAndShowInformation(Transaction tx)
            {
                if (tx == null)
                {
                    MessageBox.Show(Strings.InsufficientFunds);
                    return;
                }
                ContractParametersContext context;
                try
                {
                    context = new ContractParametersContext(tx);
                }
                catch (InvalidOperationException)
                {
                    MessageBox.Show(Strings.UnsynchronizedBlock);
                    return;
                }
                Program.CurrentWallet.Sign(context); //签名
                if (context.Completed) //如果签名完成
                {
                    context.Verifiable.Scripts = context.GetScripts();
                    Program.CurrentWallet.ApplyTransaction(tx);
                    Program.LocalNode.Relay(tx); //广播至其他节点
                    InformationBox.Show(tx.Hash.ToString(), Strings.SendTxSucceedMessage, Strings.SendTxSucceedTitle);
                }
                else
                {
                    InformationBox.Show(context.ToString(), Strings.IncompletedSignatureMessage, Strings.IncompletedSignatureTitle);
                }
            }
    

    首先是签名部分,

    public bool Sign(ContractParametersContext context)
            {
                TR.Enter();
                bool fSuccess = false;
                foreach (UInt160 scriptHash in context.ScriptHashes) // 找到交易所有输入对应的地址
                {
                    WalletAccount account = GetAccount(scriptHash); // 查看钱包是否有对应的账户地址
                    if (account?.HasKey != true) continue;
                    KeyPair key = account.GetKey(); //获取账户秘钥对
                    byte[] signature = context.Verifiable.Sign(key); //签名
                    fSuccess |= context.AddSignature(account.Contract, key.PublicKey, signature); //将签名添加到参数表中
                }
                return TR.Exit(fSuccess);
            }
    

    主要是AddSignature()

    public bool AddSignature(Contract contract, ECPoint pubkey, byte[] signature)
            {
                TR.Enter();
                if (contract.IsMultiSigContract()) //判断多签
                {
                    ContextItem item = CreateItem(contract);
                    if (item == null) return TR.Exit(false);
                    if (item.Parameters.All(p => p.Value != null)) return TR.Exit(false);
                    if (item.Signatures == null)
                        item.Signatures = new Dictionary<ECPoint, byte[]>();
                    else if (item.Signatures.ContainsKey(pubkey))
                        return TR.Exit(false);
                    List<ECPoint> points = new List<ECPoint>(); //需要签名的地址列表
                    {
                        int i = 0;
                        switch (contract.Script[i++])
                        {
                            case 1:
                                ++i;
                                break;
                            case 2:
                                i += 2;
                                break;
                        }
                        while (contract.Script[i++] == 33)
                        {
                            points.Add(ECPoint.DecodePoint(contract.Script.Skip(i).Take(33).ToArray(), ECCurve.Secp256r1));
                            i += 33;
                        }
                    }
                    if (!points.Contains(pubkey)) return TR.Exit(false); //检测是不是该这个用户签这个签名
                    item.Signatures.Add(pubkey, signature);
                    if (item.Signatures.Count == contract.ParameterList.Length)
                    {
                        Dictionary<ECPoint, int> dic = points.Select((p, i) => new
                        {
                            PublicKey = p,
                            Index = i
                        }).ToDictionary(p => p.PublicKey, p => p.Index);
                        byte[][] sigs = item.Signatures.Select(p => new
                        {
                            Signature = p.Value,
                            Index = dic[p.Key]
                        }).OrderByDescending(p => p.Index).Select(p => p.Signature).ToArray();
                        for (int i = 0; i < sigs.Length; i++) //按照顺序依次插入签名。
                            if (!Add(contract, i, sigs[i]))
                                throw new InvalidOperationException();
                        item.Signatures = null;
                    }
                    return TR.Exit(true);
                }
                else
                {
                    int index = -1;
                    for (int i = 0; i < contract.ParameterList.Length; i++)
                        if (contract.ParameterList[i] == ContractParameterType.Signature)
                            if (index >= 0)
                                throw new NotSupportedException();
                            else
                                index = i;
    
                    if(index == -1) {
                        // unable to find ContractParameterType.Signature in contract.ParameterList 
                        // return now to prevent array index out of bounds exception
                        return TR.Exit(false);
                    }
                    return TR.Exit(Add(contract, index, signature));
                }
            }
    

    首先判断是否是多签:


    snipaste_20181022_183129.png
    public virtual bool IsMultiSigContract()
            {
                TR.Enter();
                int m, n = 0;
                int i = 0;
                if (Script.Length < 37) return TR.Exit(false); //m一个字节+push公钥一个字节+最少一个公钥+n一个字节+CHECKMULTISIG一个字节,最少是37字节。
                if (Script[i] > (byte)OpCode.PUSH16) return TR.Exit(false);
                if (Script[i] < (byte)OpCode.PUSH1 && Script[i] != 1 && Script[i] != 2) return TR.Exit(false);
                switch (Script[i])
                {
                    case 1:
                        m = Script[++i];
                        ++i;
                        break;
                    case 2:
                        m = Script.ToUInt16(++i);
                        i += 2;
                        break;
                    default:
                        m = Script[i++] - 80;
                        break;
                }
                if (m < 1 || m > 1024) return TR.Exit(false);
                while (Script[i] == 33)
                {
                    i += 34;
                    if (Script.Length <= i) return TR.Exit(false);
                    ++n;
                }
                if (n < m || n > 1024) return TR.Exit(false);
                switch (Script[i])
                {
                    case 1:
                        if (n != Script[++i]) return TR.Exit(false);
                        ++i;
                        break;
                    case 2:
                        if (n != Script.ToUInt16(++i)) return TR.Exit(false);
                        i += 2;
                        break;
                    default:
                        if (n != Script[i++] - 80) return TR.Exit(false);
                        break;
                }
                if (Script[i++] != (byte)OpCode.CHECKMULTISIG) return TR.Exit(false);
                if (Script.Length != i) return TR.Exit(false);
                return TR.Exit(true);
            }
    

    如果是多签,则首先获取所有需要签名的地址列表,然后检测是否有需要该用户签名的,如果是,则把签名添加到签名列表中。当所有签名完毕时,对所有签名排序。
    如果是单签,则找到参数列表中签名参数所在的下标,将签名 signature 加入到合约的参数变量列表里面。

    随后会判断签名是否已经完成,如果多签签名数不满足条件,则需要其他账户继续签名;如果满足条件,则根据参数和脚本构建witness,将交易发送至其他节点。到此便完成了一个智能合约的部署。


    snipaste_20181024_154912.png

    2.智能合约的调用

    这里主要介绍利用Script hash进行函数调用。在gui中打开函数调用,如下图,输入之前我们生成合约时的Script hash,然后点击搜索的按钮。


    snipaste_20181019_161158.png

    之后的代码如下:

    private void button1_Click(object sender, EventArgs e)
            {
                script_hash = UInt160.Parse(textBox1.Text);
                ContractState contract = Blockchain.Default.GetContract(script_hash);
                if (contract == null) return;
                parameters = contract.ParameterList.Select(p => new ContractParameter(p)).ToArray();
                textBox2.Text = contract.Name;
                textBox3.Text = contract.CodeVersion;
                textBox4.Text = contract.Author;
                textBox5.Text = string.Join(", ", contract.ParameterList);
                button2.Enabled = parameters.Length > 0;
                UpdateScript();
            }
    

    这里调用Blockchain.Default.GetContract(script_hash),通过合约的哈希去数据库中寻找对应的合约。

    public override ContractState GetContract(UInt160 hash)
            {
                TR.Enter();
                return TR.Exit(db.TryGet<ContractState>(ReadOptions.Default, DataEntryPrefix.ST_Contract, hash));
            }
    

    返回一个ContractState ,然后将部分信息显示出来。这里可以给参数列表赋值。例如


    snipaste_20181019_163640.png

    每次更新参数的值都会执行下面的代码:

    private void button1_Click(object sender, EventArgs e)
            {
                if (listView1.SelectedIndices.Count == 0) return;
                ContractParameter parameter = (ContractParameter)listView1.SelectedItems[0].Tag;
                try
                {
                    parameter.SetValue(textBox2.Text);
                    listView1.SelectedItems[0].SubItems["value"].Text = parameter.ToString();
                    textBox1.Text = listView1.SelectedItems[0].SubItems["value"].Text;
                    textBox2.Clear();
                }
                catch(Exception err)
                {
                    MessageBox.Show(err.Message);
                }
            }
    

    重点是SetValue:

     public void SetValue(string text)
            {
                TR.Enter();
                switch (Type)
                {
                    case ContractParameterType.Signature:
                        byte[] signature = text.HexToBytes();
                        if (signature.Length != 64) throw new FormatException();
                        Value = signature;
                        break;
                    case ContractParameterType.Boolean:
                        Value = string.Equals(text, bool.TrueString, StringComparison.OrdinalIgnoreCase);
                        break;
                    case ContractParameterType.Integer:
                        Value = BigInteger.Parse(text);
                        break;
                    case ContractParameterType.Hash160:
                        Value = UInt160.Parse(text);
                        break;
                    case ContractParameterType.Hash256:
                        Value = UInt256.Parse(text);
                        break;
                    case ContractParameterType.ByteArray:
                        Value = text.HexToBytes();
                        break;
                    case ContractParameterType.PublicKey:
                        Value = ECPoint.Parse(text, ECCurve.Secp256r1);
                        break;
                    case ContractParameterType.String:
                        Value = text;
                        break;
                    default:
                        throw new ArgumentException();
                }
                TR.Exit();
            }
    

    之后合约的字节码会变成这样:


    snipaste_20181022_135128.png

    分开看如下图:


    snipaste_20181022_141256.png
    点击试运行后过程与部署合约部分相同,最后可以看到试运行结果是一个ByteArray,值为helloworld。
    snipaste_20181022_144533.png

    3.智能合约消耗GAS的处理

    部署和调用智能合约都是作为一笔交易发送到其他节点的,其中交易的SystemFee值为试运行过程中计算出的gas消耗量。Gas的值最终会写入区块当中,并且在生成gas的claimgas中会计算指定高度区间的系统费用总量。

    public T MakeTransaction<T>(T tx, UInt160 from = null, UInt160 change_address = null, Fixed8 fee = default(Fixed8)) where T : Transaction
            {
                TR.Enter();
                if (tx.Outputs == null) tx.Outputs = new TransactionOutput[0];
                if (tx.Attributes == null) tx.Attributes = new TransactionAttribute[0];
                fee += tx.SystemFee;  // SystemFee在合约交易中等于gas消耗费用
                var pay_total = (typeof(T) == typeof(IssueTransaction) ? new TransactionOutput[0] : tx.Outputs).GroupBy(p => p.AssetId, (k, g) => new
                {
                    AssetId = k,
                    Value = g.Sum(p => p.Value)
                }).ToDictionary(p => p.AssetId);
                if (fee > Fixed8.Zero) // gas消耗大于0,则添加进pay_total
                {
                    if (pay_total.ContainsKey(Blockchain.UtilityToken.Hash))
                    {
                        pay_total[Blockchain.UtilityToken.Hash] = new
                        {
                            AssetId = Blockchain.UtilityToken.Hash,
                            Value = pay_total[Blockchain.UtilityToken.Hash].Value + fee
                        };
                    }
                    else
                    {
                        pay_total.Add(Blockchain.UtilityToken.Hash, new
                        {
                            AssetId = Blockchain.UtilityToken.Hash,
                            Value = fee
                        });
                    }
                }
                var pay_coins = pay_total.Select(p => new
                {
                    AssetId = p.Key,
                    Unspents = from == null ? FindUnspentCoins(p.Key, p.Value.Value) : FindUnspentCoins(p.Key, p.Value.Value, from)
                }).ToDictionary(p => p.AssetId);
                if (pay_coins.Any(p => p.Value.Unspents == null)) return null;
                var input_sum = pay_coins.Values.ToDictionary(p => p.AssetId, p => new
                {
                    p.AssetId,
                    Value = p.Unspents.Sum(q => q.Output.Value)
                });
                if (change_address == null) change_address = GetChangeAddress();
                List<TransactionOutput> outputs_new = new List<TransactionOutput>(tx.Outputs);
                foreach (UInt256 asset_id in input_sum.Keys)
                {
                    if (input_sum[asset_id].Value > pay_total[asset_id].Value)
                    {
                        outputs_new.Add(new TransactionOutput
                        {
                            AssetId = asset_id,
                            Value = input_sum[asset_id].Value - pay_total[asset_id].Value,
                            ScriptHash = change_address
                        });
                    }
                }
                tx.Inputs = pay_coins.Values.SelectMany(p => p.Unspents).Select(p => p.Reference).ToArray();
                tx.Outputs = outputs_new.ToArray();
                return TR.Exit(tx);
            }
    

    共识节点处理交易时

    private void FillContext()
            {
                TR.Enter();
                IEnumerable<Transaction> mem_pool = LocalNode.GetMemoryPool().Where(p => CheckPolicy(p));
                foreach (PolicyPlugin plugin in PolicyPlugin.Instances)
                    mem_pool = plugin.Filter(mem_pool);
                List<Transaction> transactions = mem_pool.ToList();
                Fixed8 amount_netfee = Block.CalculateNetFee(transactions);
                TransactionOutput[] outputs = amount_netfee == Fixed8.Zero ? new TransactionOutput[0] : new[] { new TransactionOutput
                {
                    AssetId = Blockchain.UtilityToken.Hash,
                    Value = amount_netfee,
                    ScriptHash = wallet.GetChangeAddress()
                } };
                while (true)
                {
                    ulong nonce = GetNonce();
                    MinerTransaction tx = new MinerTransaction
                    {
                        Nonce = (uint)(nonce % (uint.MaxValue + 1ul)),
                        Attributes = new TransactionAttribute[0],
                        Inputs = new CoinReference[0],
                        Outputs = outputs,
                        Scripts = new Witness[0]
                    };
                    if (Blockchain.Default.GetTransaction(tx.Hash) == null)
                    {
                        context.Nonce = nonce;
                        transactions.Insert(0, tx);
                        break;
                    }
                }
                context.TransactionHashes = transactions.Select(p => p.Hash).ToArray();
                context.Transactions = transactions.ToDictionary(p => p.Hash);
                context.NextConsensus = Blockchain.GetConsensusAddress(Blockchain.Default.GetValidators(transactions).ToArray());
                TR.Exit();
            }
    

    这里的amount_netfee是网络费用的数量,计算方法为amount_in - amount_out - amount_sysfee:

    public static Fixed8 CalculateNetFee(IEnumerable<Transaction> transactions)
            {
                TR.Enter();
                Transaction[] ts = transactions.Where(p => p.Type != TransactionType.MinerTransaction && p.Type != TransactionType.ClaimTransaction).ToArray();
                Fixed8 amount_in = ts.SelectMany(p => p.References.Values.Where(o => o.AssetId == Blockchain.UtilityToken.Hash)).Sum(p => p.Value);
                Fixed8 amount_out = ts.SelectMany(p => p.Outputs.Where(o => o.AssetId == Blockchain.UtilityToken.Hash)).Sum(p => p.Value);
                Fixed8 amount_sysfee = ts.Sum(p => p.SystemFee);
                return TR.Exit(amount_in - amount_out - amount_sysfee);
            }
    

    在claimgas当中,最后一步会加上系统费用,相当于最后把系统费用分给了所有neo持有者。

    private static Fixed8 CalculateBonusInternal(IEnumerable<SpentCoin> unclaimed)
            {
                TR.Enter();
                Fixed8 amount_claimed = Fixed8.Zero;
                foreach (var group in unclaimed.GroupBy(p => new { p.StartHeight, p.EndHeight }))
                {
                    uint amount = 0;
                    uint ustart = group.Key.StartHeight / DecrementInterval;
                    if (ustart < GenerationAmount.Length)
                    {
                        uint istart = group.Key.StartHeight % DecrementInterval;
                        uint uend = group.Key.EndHeight / DecrementInterval;
                        uint iend = group.Key.EndHeight % DecrementInterval;
                        if (uend >= GenerationAmount.Length)
                        {
                            uend = (uint)GenerationAmount.Length;
                            iend = 0;
                        }
                        if (iend == 0)
                        {
                            uend--;
                            iend = DecrementInterval;
                        }
                        while (ustart < uend)
                        {
                            amount += (DecrementInterval - istart) * GenerationAmount[ustart];
                            ustart++;
                            istart = 0;
                        }
                        amount += (iend - istart) * GenerationAmount[ustart];
                    }
                    amount += (uint)(Default.GetSysFeeAmount(group.Key.EndHeight - 1) - (group.Key.StartHeight == 0 ? 0 : Default.GetSysFeeAmount(group.Key.StartHeight - 1)));
                    amount_claimed += group.Sum(p => p.Value) / 100000000 * amount;
                }
                return TR.Exit(amount_claimed);
            }
    

    相关文章

      网友评论

          本文标题:NEO的智能合约部署与调用

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