Skip to content

Commit fbd9298

Browse files
authored
Add How to create a Crypto Coin example (#4)
1 parent 72bc819 commit fbd9298

File tree

12 files changed

+320
-0
lines changed

12 files changed

+320
-0
lines changed

my-crypto-coin/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.idea
2+
.vscode

my-crypto-coin/cli/mcc/balances.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package main
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"my-crypto-coin/ledger"
6+
"fmt"
7+
"os"
8+
)
9+
10+
func balancesCmd() *cobra.Command {
11+
var balancesCmd = &cobra.Command{
12+
Use: "balances",
13+
Short: "Interact with balances (list...).",
14+
Run: func(cmd *cobra.Command, args []string) {
15+
},
16+
}
17+
18+
balancesCmd.AddCommand(balancesListCmd)
19+
20+
return balancesCmd
21+
}
22+
23+
var balancesListCmd = &cobra.Command{
24+
Use: "list",
25+
Short: "Lists all balances.",
26+
Run: func(cmd *cobra.Command, args []string) {
27+
state, err := ledger.SyncState()
28+
if err != nil {
29+
fmt.Fprintln(os.Stderr, err)
30+
os.Exit(1)
31+
}
32+
defer state.Close()
33+
34+
fmt.Println("Accounts balances:")
35+
fmt.Println("__________________")
36+
fmt.Println("")
37+
for account, balance := range state.Balances {
38+
fmt.Println(fmt.Sprintf("%s: %d", account, balance))
39+
}
40+
},
41+
}

my-crypto-coin/cli/mcc/main.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package main
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"os"
6+
"fmt"
7+
)
8+
9+
func main() {
10+
var mccCmd = &cobra.Command{
11+
Use: "mcc",
12+
Short: "My Crypto Coin CLI",
13+
Run: func(cmd *cobra.Command, args []string) {
14+
},
15+
}
16+
17+
mccCmd.AddCommand(versionCmd)
18+
mccCmd.AddCommand(balancesCmd())
19+
mccCmd.AddCommand(txCmd())
20+
21+
err := mccCmd.Execute()
22+
if err != nil {
23+
fmt.Fprintln(os.Stderr, err)
24+
os.Exit(1)
25+
}
26+
}

my-crypto-coin/cli/mcc/tx.go

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package main
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"my-crypto-coin/ledger"
6+
"fmt"
7+
"os"
8+
)
9+
10+
func txCmd() *cobra.Command {
11+
var txsCmd = &cobra.Command{
12+
Use: "tx",
13+
Short: "Interact with transactions (new...).",
14+
Run: func(cmd *cobra.Command, args []string) {
15+
},
16+
}
17+
18+
txsCmd.AddCommand(newTxCmd())
19+
20+
return txsCmd
21+
}
22+
23+
24+
func newTxCmd() *cobra.Command {
25+
var cmd = &cobra.Command{
26+
Use: "new",
27+
Short: "Adds new TX to the ledger.",
28+
Run: func(cmd *cobra.Command, args []string) {
29+
from, _ := cmd.Flags().GetString("from")
30+
to, _ := cmd.Flags().GetString("to")
31+
value, _ := cmd.Flags().GetUint("value")
32+
33+
tx := ledger.NewTx(ledger.NewAccount(from), ledger.NewAccount(to), value)
34+
35+
state, err := ledger.SyncState()
36+
if err != nil {
37+
fmt.Fprintln(os.Stderr, err)
38+
os.Exit(1)
39+
}
40+
defer state.Close()
41+
42+
err = state.WriteToLedger(tx)
43+
if err != nil {
44+
fmt.Fprintln(os.Stderr, err)
45+
os.Exit(1)
46+
}
47+
48+
fmt.Println("TX successfully added to the ledger.")
49+
},
50+
}
51+
52+
cmd.Flags().String("from", "", "From what account to send coins")
53+
cmd.MarkFlagRequired("from")
54+
55+
cmd.Flags().String("to", "", "To what account to send coins")
56+
cmd.MarkFlagRequired("to")
57+
58+
cmd.Flags().Uint("value", 0, "How many coins to send")
59+
cmd.MarkFlagRequired("value")
60+
61+
return cmd
62+
}

my-crypto-coin/cli/mcc/version.go

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
const Major = "0"
9+
const Minor = "1"
10+
const Fix = "0"
11+
const Verbal = "Genesis"
12+
13+
var versionCmd = &cobra.Command{
14+
Use: "version",
15+
Short: "Describes version.",
16+
Run: func(cmd *cobra.Command, args []string) {
17+
fmt.Println(fmt.Sprintf("Version: %s.%s.%s-beta %s", Major, Minor, Fix, Verbal))
18+
},
19+
}

my-crypto-coin/go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module my-crypto-coin
2+
3+
go 1.16
4+
5+
require github.com/spf13/cobra v1.4.0 // indirect

my-crypto-coin/go.sum

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
2+
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
3+
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
4+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
5+
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
6+
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
7+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
8+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
9+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
10+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

my-crypto-coin/ledger/genesis.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package ledger
2+
3+
import (
4+
"io/ioutil"
5+
"encoding/json"
6+
)
7+
8+
type Genesis struct {
9+
Balances map[Account]uint `json:"balances"`
10+
}
11+
12+
func loadGenesis(path string) (Genesis, error) {
13+
genesisFileContent, err := ioutil.ReadFile(path)
14+
if err != nil {
15+
return Genesis{}, err
16+
}
17+
18+
var loadedGenesis Genesis
19+
20+
err = json.Unmarshal(genesisFileContent, &loadedGenesis)
21+
if err != nil {
22+
return Genesis{}, err
23+
}
24+
25+
return loadedGenesis, nil
26+
}

my-crypto-coin/ledger/genesis.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"genesis_time": "2022-04-12T00:00:00.000000000Z",
3+
"chain_id": "our-blockchain",
4+
"balances": {
5+
"alice": 1000000
6+
}
7+
}

my-crypto-coin/ledger/ledger.db

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{"from":"alice","to":"bob","value":10}
2+
{"from":"alice","to":"alice","value":3}
3+
{"from":"alice","to":"alice","value":500}
4+
{"from":"alice","to":"bob","value":100}
5+
{"from":"bob","to":"alice","value":5}
6+
{"from":"bob","to":"carol","value":10}
7+
{"from":"alice","to":"carol","value":10}

my-crypto-coin/ledger/state.go

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package ledger
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"bufio"
8+
"encoding/json"
9+
)
10+
11+
type State struct {
12+
Balances map[Account]uint
13+
txMempool []Tx
14+
15+
dbFile *os.File
16+
}
17+
18+
19+
func SyncState() (*State, error) {
20+
cwd, err := os.Getwd()
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
gen, err := loadGenesis(filepath.Join(cwd, "ledger", "genesis.json"))
26+
if err != nil {
27+
return nil, err
28+
}
29+
30+
balances := make(map[Account]uint)
31+
for account, balance := range gen.Balances {
32+
balances[account] = balance
33+
}
34+
35+
file, err := os.OpenFile(filepath.Join(cwd, "ledger", "ledger.db"), os.O_APPEND|os.O_RDWR, 0600)
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
scanner := bufio.NewScanner(file)
41+
42+
state := &State{balances, make([]Tx, 0), file}
43+
44+
for scanner.Scan() {
45+
if err := scanner.Err(); err != nil {
46+
return nil, err
47+
}
48+
49+
var transaction Tx
50+
json.Unmarshal(scanner.Bytes(), &transaction)
51+
52+
if err := state.writeTransaction(transaction); err != nil {
53+
return nil, err
54+
}
55+
}
56+
57+
return state, nil
58+
}
59+
60+
func (s *State) writeTransaction(tx Tx) error {
61+
if s.Balances[tx.From] < tx.Value {
62+
return fmt.Errorf("insufficient balance")
63+
}
64+
65+
s.Balances[tx.From] -= tx.Value
66+
s.Balances[tx.To] += tx.Value
67+
68+
return nil
69+
}
70+
71+
func (s *State) Close() {
72+
s.dbFile.Close()
73+
}
74+
75+
func (s *State) WriteToLedger(tx Tx) error {
76+
if err := s.writeTransaction(tx); err != nil {
77+
return err
78+
}
79+
80+
s.txMempool = append(s.txMempool, tx)
81+
82+
mempool := make([]Tx, len(s.txMempool))
83+
copy(mempool, s.txMempool)
84+
85+
for i := 0; i < len(mempool); i++ {
86+
txJson, err := json.Marshal(mempool[i])
87+
if err != nil {
88+
return err
89+
}
90+
91+
if _, err = s.dbFile.Write(append(txJson, '\n')); err != nil {
92+
return err
93+
}
94+
s.txMempool = s.txMempool[1:]
95+
}
96+
97+
return nil
98+
}

my-crypto-coin/ledger/tx.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package ledger
2+
3+
type Account string
4+
5+
type Tx struct {
6+
From Account `json:"from"`
7+
To Account `json:"to"`
8+
Value uint `json:"value"`
9+
}
10+
11+
func NewAccount(value string) Account {
12+
return Account(value)
13+
}
14+
15+
func NewTx(from Account, to Account, value uint) Tx {
16+
return Tx{from, to, value}
17+
}

0 commit comments

Comments
 (0)