NEO从源码分析看数字资产

0x00 引言

比特币是泡沫么?也许是的。毕竟这东西除了用来炒,干什么实事都感受肉疼。可是有人将比特币泡沫和郁金香泡沫相提并论就很气人了,郁金香什么鬼,长那么一年,开那么几天,泡沫还没破呢,郁金香已经花开花落几个春秋了。比特币就不同了,不只每个区块产出的币都独一无二,并且每一枚币还拥有本身的独一无二的历史。世界上会有两千多万比特币,可是中本聪创世区块的那50枚币什么都替代不了。话说回来,若是当年郁金香泡沫时期开放的郁金香花株能被保存到如今,价值也绝对杠杠的。 可是问题来了,到底是什么限定了加密货币的总量,咱们拥有的“币”又到底是什么呢?做为NEO源码分析希列的第三篇博客,本文将从源码的角度对NEO资产部分的源码进行解析。 前两篇文章连接:git

注: 在接下来的文章中,英文缩写“NEO”指代NEO网络中使用的管理代币 “NEO Coin", 英文缩写"GAS"指代NEO网络中的燃料代币"NEOGas".github

0x01 资产总量

在讲解NEO网络中具体的资产以前须要讲解一下NEO网络中用来注册新资产的类RegisterTransaction,这个类用于注册新的资产,这就意味者任何人均可以基于NEO网络来发布新的资产。RegisterTransaction继承自Transaction,这意味着发布资产的过程也是一个交易的过程,交易的信息会被记录在区块中来保证数据的不可篡改性。RegisterTransaction中的关键字段以下:算法

  • AssetType // 资产类别
  • Name // 资产名称
  • Amount // 代币总量
  • Precision //代币精度
  • Owner // 发行者的公钥
  • Admin // 资产管理员的合约散列值
  • Attributes // 交易特性 :用途及其数据 此外,发布一种新的资产到NEO网络中是很是贵的,须要5000GAS,按照如今的市价,须要人民币大约150万。即使是测试网络中,官方施舍给个人GAS也就只有5000个GAS而已。

在NEO网络中存在两种官方资产,一种是做为管理NEO网络的凭证的管理代币NEO,另外一种是功能和比特币网络中的BitCoin功能相似的燃料货币GAS。由于NEO网络的共识策略采用的是投票机制,持有NEO越多的人,投票权越大,越有机会成为NEO网络中的议员。议员主持NEO网络的平常运转,生成新的区块,领取新生成的GAS做为奖励。除此以外,NEO并无别的用处。而GAS则是用来缴纳区块链网络中平常交易以及合约执行的手续费。 NEO在NEO网络建立之初总量就肯定并写入区块链中没法再进行更改,建立NEO管理代币的代码在BlockChain.cs文件中: 源码位置:neo/Core/BlockChain.csjson

public static readonly RegisterTransaction GoverningToken = new RegisterTransaction
        {
            AssetType = AssetType.GoverningToken,
            Name = "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]",
            Amount = Fixed8.FromDecimal(100000000),  /* NEO管理代币总量一亿份 */
            Precision = 0,   /* 小数点精度为0,意味着NEO最小单位为1, 不可再分 */
            Owner = ECCurve.Secp256r1.Infinity,
            Admin = (new[] { (byte)OpCode.PUSHT }).ToScriptHash(),
            Attributes = new TransactionAttribute[0],
            Inputs = new CoinReference[0],
            Outputs = new TransactionOutput[0],
            Scripts = new Witness[0]
        };

从代码中能够看出,在一开始,NEO的总量就是硬编码进区块链中的,并不涉及到复杂的计算。 一样的道理,在注册NEO资产的代码下面,就是注册GAS资产的代码:小程序

public static readonly RegisterTransaction UtilityToken = new RegisterTransaction
        {
            AssetType = AssetType.UtilityToken,
            Name = "[{\"lang\":\"zh-CN\",\"name\":\"小蚁币\"},{\"lang\":\"en\",\"name\":\"AntCoin\"}]",
            Amount = Fixed8.FromDecimal(GenerationAmount.Sum(p => p) * DecrementInterval), 
            Precision = 8, //精度为小数点后8位
            Owner = ECCurve.Secp256r1.Infinity,
            Admin = (new[] { (byte)OpCode.PUSHF }).ToScriptHash(),
            Attributes = new TransactionAttribute[0],
            Inputs = new CoinReference[0],
            Outputs = new TransactionOutput[0],
            Scripts = new Witness[0]
        };

能够看到这个GAS的总量是计算获得的,GenerationAmount数组中定义的是随着时间每生成一个区块奖励的GAS数量,DecrementInterval则是生成GAS数量的衰减速度:每生成200万个区块,新生成的区块奖励GAS数按GenerationAmount数组中的值衰减。我用计算器很是快速的算了一下,这个总量也是一亿,和白皮书中定义的一致。微信小程序

可是问题来了,要发布一个新的资产,须要消耗5000GAS,可是若是GAS不发布则NEO网络中不可能有GAS存在。发布GAS须要GAS,这是个悖论来着。固然,这在我眼中是悖论,在Core开发者眼里不是,NEO和GAS资产的注册是直接被硬编码在了创世区块里做为创世区块交易中的一部分的。然后随着新组网的节点被同步到整个世界各地。创世区块中硬编码写入的交易以下: 源码地址:neo/Core/BlockChain.cs/GenesisBlock数组

Transactions = new Transaction[]
            {
                new MinerTransaction // 建立矿工交易
                {
                    Nonce = 2083236893,
                    Attributes = new TransactionAttribute[0],
                    Inputs = new CoinReference[0],
                    Outputs = new TransactionOutput[0],
                    Scripts = new Witness[0]
                },
                GoverningToken,  // 发布NEO
                UtilityToken,         // 发布GAS
                new IssueTransaction // 用于分发资产的特殊交易
                {
                    // 代码省略
                }
            }

0x02 资产分发

新的资产类型建立了以后,那些资产去了哪里呢?有是如何得到本身建立的资产的呢? 在0x01小节中我将创世区块生成代码中的IssueTransaction交易的详情略去了,由于这部分须要详细讲解,下面先贴上详细代码:缓存

源码地址:neo/Core/BlockChain.cs/GenesisBlock微信

new IssueTransaction
                {
                    Attributes = new TransactionAttribute[0],  // 交易属性
                    Inputs = new CoinReference[0],  
                    Outputs = new[]  //
                    {
                        new TransactionOutput
                        {
                            AssetId = GoverningToken.Hash,
                            Value = GoverningToken.Amount, // 直接分发所有NEO
                            ScriptHash = Contract.CreateMultiSigRedeemScript(StandbyValidators.Length / 2 + 1, StandbyValidators).ToScriptHash() 
                        }
                    },
                    Scripts = new[]
                    {
                       // 代码省略
                    }
                }

IssueTransaction继承自Transaction,是一种用于分发资产的特殊交易。这种交易最大的特殊性就在于,你须要交一笔系统交易费,这笔费用的定义在protocol.json文件中:网络

源码位置:neo/protocol.json

"SystemFee": {
              "EnrollmentTransaction": 1000,
              "IssueTransaction": 500,
              "PublishTransaction": 500,
              "RegisterTransaction": 10000
    }

在创世区块中的IssueTransaction交易中,直接将全部的NEO所有分发出去,这意味着什么呢?意味者,若是你是StandbyValidators之一,那么你如今已经实现了人生的几十个小目标。

GAS的分发就相对比较复杂,由于GAS是须要挖掘的,并且还有一个衰减期。挖掘GAS涉及到NEO网络的共识过程,对NEO网络共识算法感兴趣的同窗能够看个人另外一篇博文《NEO从源码分析看共识协议》。在每一个视图周期开始的时候,议长添加矿工交易并将本地缓存的交易信息签名后广播给议员,议员进行验证,在验证经过的议员数量合法以后,议长建立新的区块。每一个区块奖励GAS数的计算在建立矿工交易的时候进行:

源码位置:neo/Consensus/ConsensusService.cs/CreateMinerTransaction

Fixed8 amount_netfee = Block.CalculateNetFee(transactions); // 获取手续费(in-out-sysfee)
TransactionOutput[] outputs = amount_netfee == Fixed8.Zero ? new TransactionOutput[0] : new[] { new TransactionOutput
{
          AssetId = Blockchain.UtilityToken.Hash,
          Value = amount_netfee,
          ScriptHash = wallet.GetChangeAddress()
 } };

能够看到这里调用了Block的CalculateNetFee方法来计算当前区块应该获取的手续费,当前区块的奖励也天然归属于生当前区块的帐户。

0x03 帐户余额

前面讲了那么多,可是仍是没有把一个概念讲清楚----"{'CH':'币','EN':'Coin'}" ,币究竟是什么呢?咱们NEO钱包中显示的余额到底是什么呢? 在NEO网络世界里,“币”流通的惟一途径就是交易,币的整个生命周期都在交易中度过。注册一种薪资产的RegisterTransaction方法是交易,资产分发的IssueTransaction 也是一种特殊交易,向矿工支付手续费的MinerTransaction也是交易,甚至每一个区块的奖励分发ClaimTransaction方法也是一个交易。因此咱们就先看看这个全部交易类型之父----交易基类Transaction。 Transaction关键字段以下:

源码位置:neo/Core/Transaction.cs

/// <summary>
        /// 交易类型
        /// </summary>
        public readonly TransactionType Type;
        /// <summary>
        /// 版本
        /// </summary>
        public byte Version;
        /// <summary>
        /// 该交易所具有的额外特性
        /// </summary>
        public TransactionAttribute[] Attributes;
        /// <summary>
        /// 输入列表
        /// </summary>
        public CoinReference[] Inputs;
        /// <summary>
        /// 输出列表
        /// </summary>
        public TransactionOutput[] Outputs;
        /// <summary>
        /// 用于验证该交易的脚本列表
        /// </summary>
        public Witness[] Scripts { get; set; }

能够看出,对于每一个交易,须要明确指定交易资产的来源Inputs以及交易资产的去向Outputs。每一个钱包在组网同步区块链时候,会对区块链上面的每一笔交易进行检查,若是这笔交易有Outputs指向本身的帐户,就会新建CoinReference对象来记录这个转帐,而后尝试在本地记录的资产列表里查找,若是这笔转帐已经被记录过,则将这笔资产状态修改成已确认。若是当前转帐未被记录过,则将reference对象做为KEY,新建Coin对象做为Value保存在本身的资产列表中: 源码位置:neo/Wallets/WalletIndexer.cs/ProcessBlock

for (ushort index = 0; index < tx.Outputs.Length; index++)
                {
                    TransactionOutput output = tx.Outputs[index];
                    if (accounts_tracked.ContainsKey(output.ScriptHash))
                    {
                        CoinReference reference = new CoinReference
                        {
                            PrevHash = tx.Hash,
                            PrevIndex = index
                        };
                        if (coins_tracked.TryGetValue(reference, out Coin coin))
                        {
                            coin.State |= CoinState.Confirmed;
                        }
                        else
                        {
                            accounts_tracked[output.ScriptHash].Add(reference);
                            coins_tracked.Add(reference, coin = new Coin
                            {
                                Reference = reference,
                                Output = output,
                                State = CoinState.Confirmed
                            });
                        }
                        batch.Put(SliceBuilder.Begin(DataEntryPrefix.ST_Coin).Add(reference), SliceBuilder.Begin().Add(output).Add((byte)coin.State));
                        accounts_changed.Add(output.ScriptHash);
                    }

而每笔交易的资产来源也就来自于这个资产列表中记录的数据。因为每一笔资产的都会记录prehash,这也就意味着每笔资产都是能够在区块链中进行溯源的,同时,咱们也能够知道了另外一个问题的答案,就是在NEO网络中,“币”只是个数字概念,并无实体。 资产在用户之间流通的示意图以下:

资产在用户之间流通

能够看到资产在被挖掘出来以后,整个流通的过程随着交易的过程是个树状的结构。可是对于每一份资产来讲,它的结构是这样的:

资产流通结构

从示意图中能够看出,针对每一份资产,其来源能够一直追随到其最初被开采出来的那个区块。

0x04 发布新资产

NEO网络是支持用户发布属于本身的资产的,前文也已经提到过,NEO和GAS都是在创世区块中经过特殊交易的形式发布的资产。那用户如何发布本身的资产呢? 这部分代码我从neo-gui-nel项目的源码中找到的入口: 源码位置:neo-gui-nel/neo-gui/UI/AssetRegisterDialog.cs

using (ScriptBuilder sb = new ScriptBuilder())
            {
                sb.EmitSysCall("Neo.Asset.Create", asset_type, name, amount, precision, owner, admin, issuer);
                return new InvocationTransaction
                {
                    Attributes = new[]
                    {
                        new TransactionAttribute
                        {
                            Usage = TransactionAttributeUsage.Script,
                            Data = Contract.CreateSignatureRedeemScript(owner).ToScriptHash().ToArray()
                        }
                    },
                    Script = sb.ToArray()
                };
            }

能够看到这里是进行了系统调用"Neo.Asset.Create",这个命令会触发StateMachine.cs中的Asset_Create方法:

源码位置:neo/SmartContract/StateMachie.cs/StateMachine

Register("Neo.Asset.Create", Asset_Create);

在Asset_Create方法中,根据传入的新资产的属性信息来构造合约。智能合约部分的讲解将在接下来的博客中进行,此处再也不详细解释。

最后: 本人正在进行NEO轻钱包微信小程序的开发,主要使用wepy框架,欢迎感兴趣的朋友参与进来。NEOThinWallet for Wechat Miniprogram

相关文章
相关标签/搜索