mirror of
https://github.com/number571/blockchain.git
synced 2026-09-12 19:50:00 +05:00
436 lines
8.5 KiB
Go
436 lines
8.5 KiB
Go
package main
|
|
|
|
import (
|
|
bc "./blockchain"
|
|
nt "./network"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"io/ioutil"
|
|
"bytes"
|
|
"database/sql"
|
|
"encoding/hex"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
"io"
|
|
"strconv"
|
|
"sync"
|
|
"sort"
|
|
)
|
|
|
|
func readFile(filename string) string {
|
|
data, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return string(data)
|
|
}
|
|
|
|
func userNew(filename string) *bc.User {
|
|
user := bc.NewUser()
|
|
if user == nil {
|
|
return nil
|
|
}
|
|
err := writeFile(filename, user.Purse())
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return user
|
|
}
|
|
|
|
func userLoad(filename string) *bc.User {
|
|
priv := readFile(filename)
|
|
if priv == "" {
|
|
return nil
|
|
}
|
|
user := bc.LoadUser(priv)
|
|
if user == nil {
|
|
return nil
|
|
}
|
|
return user
|
|
}
|
|
|
|
var (
|
|
Addresses []string
|
|
User *bc.User
|
|
)
|
|
|
|
func writeFile(filename string, data string) error {
|
|
return ioutil.WriteFile(filename, []byte(data), 0644)
|
|
}
|
|
|
|
const (
|
|
ADD_BLOCK = iota + 1
|
|
ADD_TRNSX
|
|
GET_BLOCK
|
|
GET_LHASH
|
|
GET_BLNCE
|
|
GET_CSIZE
|
|
)
|
|
|
|
func init() {
|
|
if len(os.Args) < 2 {
|
|
panic("failed: len(os.Args) < 2")
|
|
}
|
|
var (
|
|
serveStr = ""
|
|
addrStr = ""
|
|
userNewStr = ""
|
|
userLoadStr = ""
|
|
chainNewStr = ""
|
|
chainLoadStr = ""
|
|
)
|
|
var (
|
|
serveExist = false
|
|
addrExist = false
|
|
userNewExist = false
|
|
userLoadExist = false
|
|
chainNewExist = false
|
|
chainLoadExist = false
|
|
)
|
|
for i := 1; i < len(os.Args); i++ {
|
|
arg := os.Args[i]
|
|
switch {
|
|
case strings.HasPrefix(arg, "-serve:"):
|
|
serveStr = strings.Replace(arg, "-serve:", "", 1)
|
|
serveExist = true
|
|
case strings.HasPrefix(arg, "-loadaddr:"):
|
|
addrStr = strings.Replace(arg, "-loadaddr:", "", 1)
|
|
addrExist = true
|
|
case strings.HasPrefix(arg, "-newuser:"):
|
|
userNewStr = strings.Replace(arg, "-newuser:", "", 1)
|
|
userNewExist = true
|
|
case strings.HasPrefix(arg, "-loaduser:"):
|
|
userLoadStr = strings.Replace(arg, "-loaduser:", "", 1)
|
|
userLoadExist = true
|
|
case strings.HasPrefix(arg, "-newchain:"):
|
|
chainNewStr = strings.Replace(arg, "-newchain:", "", 1)
|
|
chainNewExist = true
|
|
case strings.HasPrefix(arg, "-loadchain:"):
|
|
chainLoadStr = strings.Replace(arg, "-loadchain:", "", 1)
|
|
chainLoadExist = true
|
|
}
|
|
}
|
|
if !(userNewExist || userLoadExist) || !(chainNewExist || chainLoadExist) ||
|
|
!serveExist || !addrExist {
|
|
panic("failed: !(userNewExist || userLoadExist)"+
|
|
"|| !(chainNewExist || chainLoadExist) || !serveExist || !addrExist")
|
|
}
|
|
|
|
Serve = serveStr
|
|
var addresses []string
|
|
err := json.Unmarshal([]byte(readFile(addrStr)), &addresses)
|
|
if err != nil {
|
|
panic("failed: load addresses")
|
|
}
|
|
var mapaddr = make(map[string]bool)
|
|
for _, addr := range addresses {
|
|
if addr == Serve {
|
|
continue
|
|
}
|
|
if _, ok := mapaddr[addr]; ok {
|
|
continue
|
|
}
|
|
mapaddr[addr] = true
|
|
Addresses = append(Addresses, addr)
|
|
}
|
|
if userNewExist {
|
|
User = userNew(userNewStr)
|
|
}
|
|
if userLoadExist {
|
|
User = userLoad(userLoadStr)
|
|
}
|
|
if User == nil {
|
|
panic("failed: load user")
|
|
}
|
|
if chainNewExist {
|
|
Filename = chainNewStr
|
|
Chain = chainNew(chainNewStr)
|
|
}
|
|
if chainLoadExist {
|
|
Filename = chainLoadStr
|
|
Chain = chainLoad(chainLoadStr)
|
|
}
|
|
if Chain == nil {
|
|
panic("failed: load chain")
|
|
}
|
|
Block = bc.NewBlock(User.Address(), Chain.LastHash())
|
|
}
|
|
|
|
var (
|
|
Filename string
|
|
Serve string
|
|
Chain *bc.BlockChain
|
|
Block *bc.Block
|
|
)
|
|
|
|
func chainNew(filename string) *bc.BlockChain {
|
|
err := bc.NewChain(filename, User.Address())
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return bc.LoadChain(filename)
|
|
}
|
|
|
|
func chainLoad(filename string) *bc.BlockChain {
|
|
chain := bc.LoadChain(filename)
|
|
if chain == nil {
|
|
return nil
|
|
}
|
|
return chain
|
|
}
|
|
|
|
func main() {
|
|
nt.Listen(Serve, handleServer)
|
|
for {
|
|
fmt.Scanln()
|
|
}
|
|
}
|
|
|
|
func handleServer(conn nt.Conn, pack *nt.Package) {
|
|
nt.Handle(ADD_BLOCK, conn, pack, addBlock)
|
|
nt.Handle(ADD_TRNSX, conn, pack, addTransaction)
|
|
nt.Handle(GET_BLOCK, conn, pack, getBlock)
|
|
nt.Handle(GET_LHASH, conn, pack, getLastHash)
|
|
nt.Handle(GET_BLNCE, conn, pack, getBalance)
|
|
nt.Handle(GET_CSIZE, conn, pack, getChainSize)
|
|
}
|
|
|
|
func addBlock(pack *nt.Package) string {
|
|
splited := strings.Split(pack.Data, SEPARATOR)
|
|
if len(splited) != 3 {
|
|
return "fail"
|
|
}
|
|
block := bc.DeserializeBlock(splited[2])
|
|
if !block.IsValid(Chain, Chain.Size()) {
|
|
currSize := Chain.Size()
|
|
num, err := strconv.Atoi(splited[1])
|
|
if err != nil {
|
|
return "fail"
|
|
}
|
|
if currSize < uint64(num) {
|
|
go compareChains(splited[0], uint64(num))
|
|
return "ok "
|
|
}
|
|
return "fail"
|
|
}
|
|
Mutex.Lock()
|
|
Chain.AddBlock(block)
|
|
Block = bc.NewBlock(User.Address(), Chain.LastHash())
|
|
Mutex.Unlock()
|
|
if IsMining {
|
|
BreakMining <- true
|
|
IsMining = false
|
|
}
|
|
return "ok"
|
|
}
|
|
|
|
func addTransaction(pack *nt.Package) string {
|
|
var tx = bc.DeserializeTX(pack.Data)
|
|
if tx == nil || len(Block.Transactions) == bc.TXS_LIMIT {
|
|
return "fail"
|
|
}
|
|
Mutex.Lock()
|
|
err := Block.AddTransaction(Chain, tx)
|
|
Mutex.Unlock()
|
|
if err != nil {
|
|
return "fail"
|
|
}
|
|
if len(Block.Transactions) == bc.TXS_LIMIT {
|
|
go func() {
|
|
Mutex.Lock()
|
|
block := *Block
|
|
IsMining = true
|
|
Mutex.Unlock()
|
|
res := (&block).Accept(Chain, User, BreakMining)
|
|
Mutex.Lock()
|
|
IsMining = false
|
|
if res == nil && bytes.Equal(block.PrevHash, Block.PrevHash) {
|
|
Chain.AddBlock(&block)
|
|
pushBlockToNet(&block)
|
|
}
|
|
Block = bc.NewBlock(User.Address(), Chain.LastHash())
|
|
Mutex.Unlock()
|
|
}()
|
|
}
|
|
return "ok"
|
|
}
|
|
|
|
func getBlock(pack *nt.Package) string {
|
|
num, err := strconv.Atoi(pack.Data)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
size := Chain.Size()
|
|
if uint64(num) < size {
|
|
return selectBlock(Chain, num)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func getLastHash(pack *nt.Package) string {
|
|
return bc.Base64Encode(Chain.LastHash())
|
|
}
|
|
|
|
func getBalance(pack *nt.Package) string {
|
|
return fmt.Sprintf("%d", Chain.Balance(pack.Data, Chain.Size()))
|
|
}
|
|
|
|
func getChainSize(pack *nt.Package) string {
|
|
return fmt.Sprintf("%d", Chain.Size())
|
|
}
|
|
|
|
const (
|
|
SEPARATOR = "_SEPARATOR_"
|
|
)
|
|
|
|
var (
|
|
IsMining bool
|
|
BreakMining = make(chan bool)
|
|
)
|
|
|
|
func compareChains(address string, num uint64) {
|
|
filename := "temp_" + hex.EncodeToString(bc.GenerateRandomBytes(8))
|
|
file, err := os.Create(filename)
|
|
if err != nil {
|
|
return
|
|
}
|
|
file.Close()
|
|
defer func() {
|
|
os.Remove(filename)
|
|
}()
|
|
res := nt.Send(address, &nt.Package{
|
|
Option: GET_BLOCK,
|
|
Data: fmt.Sprintf("%d", 0),
|
|
})
|
|
if res == nil {
|
|
return
|
|
}
|
|
genesis := bc.DeserializeBlock(res.Data)
|
|
if genesis == nil {
|
|
return
|
|
}
|
|
if !bytes.Equal(genesis.CurrHash, hashBlock(genesis)) {
|
|
return
|
|
}
|
|
db, err := sql.Open("sqlite3", filename)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer db.Close()
|
|
_, err = db.Exec(bc.CREATE_TABLE)
|
|
chain := &bc.BlockChain{
|
|
DB: db,
|
|
}
|
|
chain.AddBlock(genesis)
|
|
for i := uint64(1); i < num; i++ {
|
|
res := nt.Send(address, &nt.Package{
|
|
Option: GET_BLOCK,
|
|
Data: fmt.Sprintf("%d", i),
|
|
})
|
|
if res == nil {
|
|
return
|
|
}
|
|
block := bc.DeserializeBlock(res.Data)
|
|
if block == nil {
|
|
return
|
|
}
|
|
if !block.IsValid(chain, i) {
|
|
return
|
|
}
|
|
chain.AddBlock(block)
|
|
}
|
|
Mutex.Lock()
|
|
Chain.DB.Close()
|
|
os.Remove(Filename)
|
|
copyFile(filename, Filename)
|
|
Chain = bc.LoadChain(Filename)
|
|
Block = bc.NewBlock(User.Address(), Chain.LastHash())
|
|
Mutex.Unlock()
|
|
if IsMining {
|
|
BreakMining <- true
|
|
IsMining = false
|
|
}
|
|
}
|
|
|
|
var (
|
|
Mutex sync.Mutex
|
|
)
|
|
|
|
func pushBlockToNet(block *bc.Block) {
|
|
var (
|
|
sblock = bc.SerializeBlock(block)
|
|
msg = Serve + SEPARATOR + fmt.Sprintf("%d", Chain.Size()) + SEPARATOR + sblock
|
|
)
|
|
for _, addr := range Addresses {
|
|
go nt.Send(addr, &nt.Package{
|
|
Option: ADD_BLOCK,
|
|
Data: msg,
|
|
})
|
|
}
|
|
}
|
|
|
|
func selectBlock(chain *bc.BlockChain, i int) string {
|
|
var block string
|
|
row := chain.DB.QueryRow("SELECT Block FROM BlockChain WHERE Id=$1", i+1)
|
|
row.Scan(&block)
|
|
return block
|
|
}
|
|
|
|
func hashBlock(block *bc.Block) []byte {
|
|
var tempHash []byte
|
|
for _, tx := range block.Transactions {
|
|
tempHash = bc.HashSum(bytes.Join(
|
|
[][]byte{
|
|
tempHash,
|
|
tx.CurrHash,
|
|
},
|
|
[]byte{},
|
|
))
|
|
}
|
|
var list []string
|
|
for hash := range block.Mapping {
|
|
list = append(list, hash)
|
|
}
|
|
sort.Strings(list)
|
|
for _, hash := range list {
|
|
tempHash = bc.HashSum(bytes.Join(
|
|
[][]byte{
|
|
tempHash,
|
|
[]byte(hash),
|
|
bc.ToBytes(block.Mapping[hash]),
|
|
},
|
|
[]byte{},
|
|
))
|
|
}
|
|
return bc.HashSum(bytes.Join(
|
|
[][]byte{
|
|
tempHash,
|
|
bc.ToBytes(uint64(block.Difficulty)),
|
|
block.PrevHash,
|
|
[]byte(block.Miner),
|
|
[]byte(block.TimeStamp),
|
|
},
|
|
[]byte{},
|
|
))
|
|
}
|
|
|
|
func copyFile(src, dst string) error {
|
|
in, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer in.Close()
|
|
out, err := os.Create(dst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer out.Close()
|
|
_, err = io.Copy(out, in)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return out.Close()
|
|
}
|
|
|