如何创建加密货币

货币是商品和服务的交换媒介。如今,货币采用纸币、硬币或中心化数字账本的形式,通常由政府发行,是一种被普遍接受的支付方式。过去,货币以各种金属的形式出现,如金银,甚至是彩色珠子和盐。加密货币是一种以密码学和区块链技术为基础的数字货币形式,主要用作转移价值的一种方式,而无需依赖单一的中心化平台,例如银行。

在本技术教程中,我们将探讨货币和通证之间的区别,你将学习如何开发自己的加密货币。

我们开始吧!

货币和通证的区别

比特币是最流行的加密货币,其主要目的是作为一种交换媒介。还有许多通证具有价值,但除了作为一种形式的交换媒介的作用之外,还有其他用途:例如,治理投票通证授予持有人某些治理特权。 还有 NFT:代表独特事物所有权的不可替代通证。那么,所有这些类型的数字资产有什么区别呢?

从工程的角度来看,货币和通证之间的区别非常简单。货币是区块链的一部分,而通证以智能合约的形式运行在现有区块链上。

例如,BTC 是比特币区块链的币,ETH 是以太坊区块链的币。 BTC 和 ETH 都是货币。 再举几个例子,USDC、AAVE 和 WETH 都是通证,因为它们本质上是托管在以太坊区块链之上的智能合约。 同样的原则也适用于 NFT:它们也是驻留在各种通用区块链上的通证。

要了解如何创建自己的通证,可查看这篇博客文章。要了解有关创建 NFT 的更多信息,可查看这个教程。继续阅读可了解有关创建自己的加密货币的更多信息。

开始

这个项目是用 Go 编写的,但不需要以前有这种语言的经验。 接下来,请查看位于My Crypto Coin文件夹下的Chainlink智能合约示例存储库中的完整工作示例。

git clone https://github.com/smartcontractkit/smart-contract-examples.git
cd smart-contract-examples/my-crypto-coin

下一步是在你的本地机器上安装Go,可以按照官方指南进行操作。这个过程大约需要 10 分钟,可以在这段时间煮点咖啡。

在继续之前,需要验证你的$GOPATH设置是否正确。这是一个必须的步骤。

一般约定是将源代码存储在$GOPATH/src中,将编译后的程序二进制文件存储在$GOPATH/bin中。 导航到$GOPATH/src并创建一个名为my-crypto-coin的新文件夹。

现在我们开始开发。

一切从创世区块开始

货币是区块链分布式账本中的单位。每个区块链都有其初始状态,也称为创世区块。在你新创建的my-crypto-coin项目中,创建一个新文件夹并将其命名为ledger。在ledger文件夹中,创建一个新文件,将其命名为genesis.json,然后粘贴下面的代码。 我们将初始供应量为100万的加密货币分配给Alice。

{
  "genesis_time": "2022-04-12T00:00:00.000000000Z",
  "chain_id": "our-blockchain",
  "balances": {
    "alice": 1000000
  }
}

这是原始状态。交易会改变状态。如果我们的区块链节点出现故障,我们可以使用创世文件和交易历史来重新创建整个账本并将网络的其余部分同步到最新状态。

账户、交易和全局状态

导航到legder文件夹并创建一个tx.go文件。每个帐户将由帐户结构表示。每笔交易将由交易结构体表示,具有以下属性:“from”、“to”和“value”。 我们将添加一个用于创建新帐户和交易的功能。

package ledger

type Account string

type Tx struct {
     From  Account `json:"from"`
     To    Account `json:"to"`
     Value uint    `json:"value"`
}

func NewAccount(value string) Account {
     return Account(value)
}

func NewTx(from Account, to Account, value uint) Tx {
     return Tx{from, to, value}
}

交易将存储在账本中,所以让我们手动添加几个作为演示。在ledger目录中,创建一个新的ledger.db文件并将以下内容粘贴到那里。

{"from":"alice","to":"bob","value":10}
{"from":"alice","to":"alice","value":3}
{"from":"alice","to":"alice","value":500}
{"from":"alice","to":"bob","value":100}
{"from":"bob","to":"alice","value":5}
{"from":"bob","to":"carol","value":10}

创世状态保持不变并保留在genesis.json文件中。我们添加一种以编程方式加载其状态的方法。创建一个名为genesis.go的新文件,该文件将存储账户映射以及创世状态下的相应货币余额。

package ledger

import (
     "io/ioutil"
     "encoding/json"
)

type Genesis struct {
     Balances map[Account]uint `json:"balances"`
}

func loadGenesis(path string) (Genesis, error) {
     genesisFileContent, err := ioutil.ReadFile(path)
     if err != nil {
          return Genesis{}, err
     }

     var loadedGenesis Genesis

     err = json.Unmarshal(genesisFileContent, &loadedGenesis)
     if err != nil {
          return Genesis{}, err
     }

return loadedGenesis, nil

核心业务逻辑将存储在Store结构体中。创建一个名为state.go的新文件。状态结构体将包含所有账户余额的详细信息,谁将货币转移给谁,以及转移了多少货币。它必须知道如何从genesis文件中读取初始状态。 之后,通过顺序重放ledger.db文件中的所有交易来更新创世状态余额。 最后,在这里我们需要编写一个将新交易添加到帐本的逻辑。

package ledger

import (
     "fmt"
     "os"
     "path/filepath"
     "bufio"
     "encoding/json"
)

type State struct {
     Balances  map[Account]uint
     txMempool []Tx

     dbFile *os.File
}

func SyncState() (*State, error)  {
     cwd, err := os.Getwd()
     if err != nil {
          return nil, err
     }

     gen, err := loadGenesis(filepath.Join(cwd, "ledger", "genesis.json"))
     if err != nil {
          return nil, err
     }

     balances := make(map[Account]uint)
     for account, balance := range gen.Balances {
          balances[account] = balance
     }

     file, err := os.OpenFile(filepath.Join(cwd, "ledger", "ledger.db"), 
os.O_APPEND|os.O_RDWR, 0600)
     if err != nil {
          return nil, err
     }

     scanner := bufio.NewScanner(file)

     state := &State{balances, make([]Tx, 0), file}

     for scanner.Scan() {
          if err := scanner.Err(); err != nil {
               return nil, err
          }

          var transaction Tx
          json.Unmarshal(scanner.Bytes(), &transaction)

          if err := state.writeTransaction(transaction); err != nil {
               return nil, err
          }
     }

     return state, nil
}

func (s *State) writeTransaction(tx Tx) error {
     if s.Balances[tx.From] < tx.Value {
          return fmt.Errorf("insufficient balance")
     }

     s.Balances[tx.From] -= tx.Value
     s.Balances[tx.To] += tx.Value

     return nil
}

func (s *State) Close() {
     s.dbFile.Close()
}

func (s *State) WriteToLedger(tx Tx) error {
     if err := s.writeTransaction(tx); err != nil {
          return err
     }

     s.txMempool = append(s.txMempool, tx)

     mempool := make([]Tx, len(s.txMempool))
     copy(mempool, s.txMempool)

     for i := 0; i < len(mempool); i++ {
          txJson, err := json.Marshal(mempool[i])
          if err != nil {
               return err
          }

          if _, err = s.dbFile.Write(append(txJson, '\n')); err != nil {
               return err
          }
          s.txMempool = s.txMempool[1:]
     }

     return nil
}

构建命令行界面 (CLI)

使用我们的新加密货币最简单的方法是开发一个命令行界面 (CLI)。 在Go中开发基于CLI 的程序的最简单方法是使用一个第三方库Cobra。 要使用这个库,我们需要为我们的项目初始化Go的内置依赖管理器,称为Go模块。 Go模块命令将自动获取你在Go文件中引用的任何库。

echo $GOPATH
cd $GOPATH/src/my-crypto-coin 
go mod init

现在让我们创建一个新文件夹并将其命名为cli。 但是等等,我们还没有正式命名我们的加密货币! 我们暂时称它为 My Crypto Coin。 导航到cli文件夹并创建一个名为mcc的新文件夹,它是 My Crypto Coin 的缩写。 导航到 mcc 文件夹。

创建一个名为main.go的新文件。 这将是我们程序的主要入口。

package main

import (
     "github.com/spf13/cobra"
     "os"
     "fmt"
)

func main() {
     var mccCmd = &cobra.Command{
          Use:   "mcc",
          Short: "My Crypto Coin CLI",
          Run: func(cmd *cobra.Command, args []string) {
          },
     }

     mccCmd.AddCommand(versionCmd)
     mccCmd.AddCommand(balancesCmd())
     mccCmd.AddCommand(txCmd())

     err := mccCmd.Execute()
     if err != nil {
          fmt.Fprintln(os.Stderr, err)
          os.Exit(1)
     }
}

接下来,创建一个version.go文件并粘贴下面的内容。

package main

import (
     "fmt"
     "github.com/spf13/cobra"
)

const Major = "0"
const Minor = "1"
const Fix = "0"
const Verbal = "Genesis"

var versionCmd = &cobra.Command{
     Use:   "version",
     Short: "Describes version.",
     Run: func(cmd *cobra.Command, args []string) {
          fmt.Println(fmt.Sprintf("Version: %s.%s.%s-beta %s", Major, Minor, Fix, Verbal))
     },
}

之后,让我们创建一个从帐本中读取所有帐户余额的机制。创建一个新的balances.go文件。

package main

import (
     "github.com/spf13/cobra"
     "my-crypto-coin/ledger"
     "fmt"
     "os"
)

func balancesCmd() *cobra.Command {
     var balancesCmd = &cobra.Command{
          Use:   "balances",
          Short: "Interact with balances (list...).",
          Run: func(cmd *cobra.Command, args []string) {
          },
     }

     balancesCmd.AddCommand(balancesListCmd)

     return balancesCmd
}

var balancesListCmd = &cobra.Command{
     Use:   "list",
     Short: "Lists all balances.",
     Run: func(cmd *cobra.Command, args []string) {
          state, err := ledger.SyncState()
          if err != nil {
               fmt.Fprintln(os.Stderr, err)
               os.Exit(1)
          }
          defer state.Close()

          fmt.Println("Accounts balances:")
          fmt.Println("__________________")
          fmt.Println("")
          for account, balance := range state.Balances {
               fmt.Println(fmt.Sprintf("%s: %d", account, balance))
          }
     },
}

最后,让我们添加一个将交易写入账本的命令。 创建新的tx.go文件。

package main

import (
     "github.com/spf13/cobra"
     "my-crypto-coin/ledger"
     "fmt"
     "os"
)

func txCmd() *cobra.Command {
     var txsCmd = &cobra.Command{
          Use:   "tx",
          Short: "Interact with transactions (new...).",
          Run: func(cmd *cobra.Command, args []string) {
          },
     }

     txsCmd.AddCommand(newTxCmd())

     return txsCmd
}

func newTxCmd() *cobra.Command {
     var cmd = &cobra.Command{
          Use:   "new",
          Short: "Adds new TX to the ledger.",
          Run: func(cmd *cobra.Command, args []string) {
               from, _ := cmd.Flags().GetString("from")
               to, _ := cmd.Flags().GetString("to")
               value, _ := cmd.Flags().GetUint("value")

               tx := ledger.NewTx(ledger.NewAccount(from), ledger.NewAccount(to), 
value)

               state, err := ledger.SyncState()
               if err != nil {
                    fmt.Fprintln(os.Stderr, err)
                    os.Exit(1)
               }
               defer state.Close()
               err = state.WriteToLedger(tx)
               if err != nil {
                    fmt.Fprintln(os.Stderr, err)
                    os.Exit(1)
               }

               fmt.Println("TX successfully added to the ledger.")
          },
     }

     cmd.Flags().String("from", "", "From what account to send coins")
     cmd.MarkFlagRequired("from")

     cmd.Flags().String("to", "", "To what account to send coins")
     cmd.MarkFlagRequired("to")

     cmd.Flags().Uint("value", 0, "How many coins to send")
     cmd.MarkFlagRequired("value")

     return cmd
}

使用以下命令编译程序:

go install $GOPATH/src/my-crypto-coin/cli/mcc/…

Go将检测缺失的库并在编译程序之前自动获取它们。根据你的$GOPATH,生成的程序将保存在$GOPATH/bin文件夹中。

要验证安装是否成功,请运行以下命令:

which mcc

你应该在终端中看到与此类似的路径:/Users/yourname/go/bin/mcc。 让我们看看所有可用的命令。 运行:

mcc --help

要查看 CLI 的当前版本,请运行:

mcc version

要查看当前用户余额,请运行以下命令:

mcc balances list

输出应该是:

Accounts balances:
__________________

alice: 999895
bob: 95
carol: 10

现在让我们使用 CLI 进行第一个交易。 输入以下命令:

mcc tx new --from alice --to carol --value 10

如果你打开ledger/ledger.db 文件,你应该能够看到额外的一行:

{"from":"alice","to":"carol","value":10}

让我们再次使用 mcc balances list 命令列出余额。 输出应该是:

Accounts balances:
__________________

alice: 999885
bob: 95
carol: 20

下一步是什么?

目前,我们在帐本中用名字表示用户。但是如果有两个Bob会发生什么呢? 我们需要添加一种使用通用散列算法唯一表示帐户的方法。

下一个问题是任何人都可以使用我们的命令行转移其他人的货币。我们需要一种方法来允许用户使用公钥密码技术仅转移他们自己拥有的货币。

如果我们的机器出现故障会发生什么?由于我们是网络中唯一的节点,因此无法重新创建我们的网络。我们需要激励人们加入我们的网络并成为节点。 一旦我们的网络增长,我们将需要一种通用的方法来确定哪些交易是有效的,哪些不是,并验证网络的当前状态。 我们需要一个共识算法和机制。

总结

在本文中,你学习了如何使用Go开发基本的加密货币,并且我们已经介绍了货币和通证之间的主要区别。 要了解更多信息,可前往Chainlink 智能合约示例存储库并开始试验这个和其他示例项目。

通过访问chain.link或阅读docs.chain.link 上的文档可了解有关Chainlink 的更多信息。 要讨论集成,可联系专家

Need Integration Support?
Talk to an expert
Faucets
Get testnet tokens
Read the Docs
Technical documentation