NEO從源碼分析看數字資產

0x00 引言

比特幣是泡沫麼?也許是的。畢竟這東西除了用來炒,幹什麼實事都感受肉疼。可是有人將比特幣泡沫和鬱金香泡沫相提並論就很氣人了,鬱金香什麼鬼,長那麼一年,開那麼幾天,泡沫還沒破呢,鬱金香已經花開花落幾個春秋了。比特幣就不同了,不只每個區塊產出的幣都獨一無二,並且每一枚幣還擁有本身的獨一無二的歷史。世界上會有兩千多萬比特幣,可是中本聰創世區塊的那50枚幣什麼都替代不了。話說回來,若是當年鬱金香泡沫時期開放的鬱金香花株能被保存到如今,價值也絕對槓槓的。 可是問題來了,到底是什麼限定了加密貨幣的總量,咱們擁有的「幣」又到底是什麼呢?做爲NEO源碼分析希列的第三篇博客,本文將從源碼的角度對NEO資產部分的源碼進行解析。 前兩篇文章連接:git

注: github

在接下來的文章中,英文縮寫「NEO」指代NEO網絡中使用的管理代幣 「NEO Coin", 英文縮寫"GAS"指代NEO網絡中的燃料代幣"NEOGas".

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/GenesisBlockbash

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


羣交流:795681763

原文:https://my.oschina.net/u/2276921/blog/1622364

相關文章
相關標籤/搜索