NEO的智能合约部署与调用

2019-10-30  本文已影响0人  ShawnYung

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);
        }
上一篇下一篇

猜你喜欢

热点阅读