From e80b91c1768be17899e2439d2bdc33d936ee46f0 Mon Sep 17 00:00:00 2001 From: number571 Date: Sun, 31 Mar 2024 19:22:20 +0700 Subject: [PATCH] fix --- .gitignore | 12 +- cmd/client/client.go | 240 +++++++++ cmd/client/values.go | 60 +++ cmd/client_eth/client_eth.go | 622 ++++++++++++++++++++++ cmd/client_eth/values_eth.go | 304 +++++++++++ cmd/deploy/deploy.go | 136 +++++ cmd/gclient/gclient.go | 285 +++++++++++ cmd/gclient/values.go | 60 +++ cmd/gclient_eth/gclient_eth.go | 905 +++++++++++++++++++++++++++++++++ cmd/gclient_eth/values_eth.go | 304 +++++++++++ cmd/node/node.go | 129 +++++ cmd/node/serve.go | 290 +++++++++++ cmd/node/values.go | 60 +++ 13 files changed, 3401 insertions(+), 6 deletions(-) create mode 100644 cmd/client/client.go create mode 100644 cmd/client/values.go create mode 100644 cmd/client_eth/client_eth.go create mode 100644 cmd/client_eth/values_eth.go create mode 100644 cmd/deploy/deploy.go create mode 100644 cmd/gclient/gclient.go create mode 100644 cmd/gclient/values.go create mode 100644 cmd/gclient_eth/gclient_eth.go create mode 100644 cmd/gclient_eth/values_eth.go create mode 100644 cmd/node/node.go create mode 100644 cmd/node/serve.go create mode 100644 cmd/node/values.go diff --git a/.gitignore b/.gitignore index f451997..1a4a00b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,9 +17,9 @@ contracts/Contract.go build/WorldSkills.abi build/WorldSkills.bin abigen -!./cmd/node -!./cmd/client -!./cmd/client_eth -!./cmd/gclient -!./cmd/gclient_eth -!./cmd/deploy +!cmd/node +!cmd/client +!cmd/client_eth +!cmd/gclient +!cmd/gclient_eth +!cmd/deploy diff --git a/cmd/client/client.go b/cmd/client/client.go new file mode 100644 index 0000000..c5a92d6 --- /dev/null +++ b/cmd/client/client.go @@ -0,0 +1,240 @@ +package main + +import ( + bc "blockchain-from-scratch/blockchain" + "bufio" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + + nt "blockchain-from-scratch/network" +) + +func init() { + if len(os.Args) < 2 { + panic("failed: len(os.Args) < 2") + } + var ( + addrStr = "" + userNewStr = "" + userLoadStr = "" + ) + var ( + addrExist = false + userNewExist = false + userLoadExist = false + ) + for i := 1; i < len(os.Args); i++ { + arg := os.Args[i] + switch { + 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 + } + } + + if !(userNewExist || userLoadExist) || !addrExist { + panic("failed: !(userNewExist || userLoadExist) || !addrExist") + } + + err := json.Unmarshal([]byte(readFile(addrStr)), &Addresses) + if err != nil { + panic("failed: load addresses") + } + if len(Addresses) == 0 { + panic("failed: len(Addresses) == 0") + } + + if userNewExist { + User = userNew(userNewStr) + } + if userLoadExist { + User = userLoad(userLoadStr) + } + if User == nil { + panic("failed: load user") + } +} + +func main() { + handleClient() +} + +func handleClient() { + var ( + message string + splited []string + ) + for { + message = inputString("> ") + splited = strings.Split(message, " ") + switch splited[0] { + case "/exit": + os.Exit(0) + case "/user": + if len(splited) < 2 { + fmt.Println("failed: len(user) < 2\n") + continue + } + switch splited[1] { + case "address": + userAddress() + case "purse": + userPurse() + case "balance": + userBalance() + default: + fmt.Println("command undefined\n") + } + case "/chain": + if len(splited) < 2 { + fmt.Println("failed: len(chain) < 2\n") + continue + } + switch splited[1] { + case "print": + chainPrint() + case "tx": + chainTX(splited[1:]) + case "balance": + chainBalance(splited[1:]) + case "block": + chainBlock(splited[1:]) + case "size": + chainSize() + default: + fmt.Println("command undefined\n") + } + default: + fmt.Println("command undefined\n") + } + } +} + +func chainSize() { + res := nt.Send(Addresses[0], &nt.Package{ + Option: GET_CSIZE, + }) + if res == nil || res.Data == "" { + fmt.Println("failed: getSize\n") + return + } + fmt.Printf("Size: %s blocks\n\n", res.Data) +} + +func chainBlock(splited []string) { + if len(splited) != 2 { + fmt.Println("failed: len(splited) != 2\n") + return + } + num, err := strconv.Atoi(splited[1]) + if err != nil { + fmt.Println("failed: strconv.Atoi(num)\n") + return + } + res := nt.Send(Addresses[0], &nt.Package{ + Option: GET_BLOCK, + Data: fmt.Sprintf("%d", num-1), + }) + if res == nil || res.Data == "" { + fmt.Println("failed: getBlock\n") + return + } + fmt.Printf("[%d] => %s\n", num, res.Data) +} + +func chainPrint() { + for i := 0; ; i++ { + res := nt.Send(Addresses[0], &nt.Package{ + Option: GET_BLOCK, + Data: fmt.Sprintf("%d", i), + }) + if res == nil || res.Data == "" { + break + } + fmt.Printf("[%d] => %s\n", i+1, res.Data) + } + fmt.Println() +} + +func chainTX(splited []string) { + if len(splited) != 3 { + fmt.Println("failed: len(splited) != 3\n") + return + } + num, err := strconv.Atoi(splited[2]) + if err != nil { + fmt.Println("failed: strconv.Atoi(num)\n") + return + } + for _, addr := range Addresses { + res := nt.Send(addr, &nt.Package{ + Option: GET_LHASH, + }) + if res == nil { + continue + } + tx := bc.NewTransaction(User, bc.Base64Decode(res.Data), splited[1], uint64(num)) + res = nt.Send(addr, &nt.Package{ + Option: ADD_TRNSX, + Data: bc.SerializeTX(tx), + }) + if res == nil { + continue + } + if res.Data == "ok" { + fmt.Printf("ok: (%s)\n", addr) + } else { + fmt.Printf("fail: (%s)\n", addr) + } + } + fmt.Println() +} + +func chainBalance(splited []string) { + if len(splited) != 2 { + fmt.Println("fail: len(splited) != 2\n") + return + } + printBalance(splited[1]) +} + +func printBalance(useraddr string) { + for _, addr := range Addresses { + res := nt.Send(addr, &nt.Package{ + Option: GET_BLNCE, + Data: useraddr, + }) + if res == nil { + continue + } + fmt.Printf("Balance (%s): %s coins\n", addr, res.Data) + } + fmt.Println() +} + +func userAddress() { + fmt.Println("Address:", User.Address(), "\n") +} + +func userPurse() { + fmt.Println("Purse:", User.Purse(), "\n") +} + +func userBalance() { + printBalance(User.Address()) +} + +func inputString(begin string) string { + fmt.Print(begin) + msg, _ := bufio.NewReader(os.Stdin).ReadString('\n') + return strings.Replace(msg, "\n", "", 1) +} diff --git a/cmd/client/values.go b/cmd/client/values.go new file mode 100644 index 0000000..c06a897 --- /dev/null +++ b/cmd/client/values.go @@ -0,0 +1,60 @@ +package main + +import ( + bc "blockchain-from-scratch/blockchain" + "io/ioutil" +) + +var ( + Addresses []string + User *bc.User +) + +const ( + SEPARATOR = "_SEPARATOR_" +) + +const ( + ADD_BLOCK = iota + 1 + ADD_TRNSX + GET_BLOCK + GET_LHASH + GET_BLNCE + GET_CSIZE +) + +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 +} + +func writeFile(filename string, data string) error { + return ioutil.WriteFile(filename, []byte(data), 0644) +} + +func readFile(filename string) string { + data, err := ioutil.ReadFile(filename) + if err != nil { + return "" + } + return string(data) +} diff --git a/cmd/client_eth/client_eth.go b/cmd/client_eth/client_eth.go new file mode 100644 index 0000000..59dd9de --- /dev/null +++ b/cmd/client_eth/client_eth.go @@ -0,0 +1,622 @@ +package main + +import ( + "os" + "fmt" + "bufio" + "context" + "strings" + "math/big" + "encoding/json" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/accounts/abi/bind" +) + +func init() { + if len(os.Args) < 2 { + panic("failed: len(os.Args) < 2") + } + var ( + userLoadStr = "" + userLoadExist = false + ) + for i := 1; i < len(os.Args); i++ { + arg := os.Args[i] + switch { + case strings.HasPrefix(arg, "-loaduser:"): + userLoadStr = strings.Replace(arg, "-loaduser:", "", 1) + userLoadExist = true + } + } + if !userLoadExist { + panic("failed: !userLoadExist") + } + if ClientETH == nil { + panic("failed: connect to ETH") + } + if Instance == nil { + panic("failed: instance is nil") + } + User = loadUser(userLoadStr) + if User == nil { + panic("failed: load user") + } +} + +func main() { + var ( + message string + splited []string + ) + for { + message = inputString("> ") + splited = strings.Split(message, " ") + switch splited[0] { + case "/exit": + os.Exit(0) + case "/user": + if len(splited) < 2 { + fmt.Println("failed: len(user) < 2\n") + continue + } + switch splited[1] { + case "address": + userAddress() + case "purse": + userPurse() + case "balance": + userBalance() + default: + fmt.Println("command undefined\n") + } + case "/chain": + if len(splited) < 3 { + fmt.Println("failed: len(chain) < 3\n") + continue + } + switch splited[1] { + case "get": + chainGet(splited[2], splited[2:]) + case "create": + switch splited[2] { + case "estate": + // chain create estate address info squere usefulSquere + chainCreateEstate(splited[2:]) + case "present": + // chain create present id_estate address + chainCreatePresent(splited[2:]) + case "sale": + // chain create sale id_estate money + chainCreateSale(splited[2:]) + case "rent": + // chain create rent id_estate hours price + chainCreateRent(splited[2:]) + default: + fmt.Println("command undefined\n") + } + case "cancel": + chainCancel(splited[2], splited[2:]) + case "confirm": + switch splited[2] { + case "present": + // chain confirm present id_present + chainConfirmPresent(splited[2:]) + case "sale": + // chain confirm sale id_sale id_customer + chainConfirmSale(splited[2:]) + case "rent": + // chain confirm rent id_rent + chainConfirmRent(splited[2:]) + default: + fmt.Println("command undefined\n") + } + case "try-buy": + // chain try-buy id_sale money + chainTryBuy(splited[1:]) + case "cancel-buy": + // chain cancel-buy id_sale + chainCancelBuy(splited[1:]) + case "finish-rent": + // chain finish-rent id_rent + finishRent(splited[1:]) + default: + fmt.Println("command undefined\n") + } + default: + fmt.Println("command undefined\n") + } + } +} + +func chainTryBuy(splited []string) { + if len(splited) != 3 { + fmt.Println("failed: len(splited) != 3\n") + return + } + var ( + saleNumber = new(big.Int) + value = new(big.Int) + ok bool + ) + saleNumber, ok = saleNumber.SetString(splited[1], 10) + if !ok { + fmt.Println("failed: conv(str1) to num\n") + return + } + value, ok = value.SetString(splited[2], 10) + if !ok { + fmt.Println("failed: conv(str2) to num\n") + return + } + auth := resetAuth(User) + if auth == nil { + fmt.Println("failed: auth is nil\n") + return + } + auth.Value = value + tx, err := Instance.CheckToBuy( + auth, + saleNumber, + ) + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println("Tx:", tx.Hash().Hex(), "\n") +} + +func chainCancelBuy(splited []string) { + if len(splited) != 2 { + fmt.Println("failed: len(splited) != 2\n") + return + } + var ( + saleNumber = new(big.Int) + ok bool + ) + saleNumber, ok = saleNumber.SetString(splited[1], 10) + if !ok { + fmt.Println("failed: conv(str1) to num\n") + return + } + tx, err := Instance.CancelToBuy( + resetAuth(User), + saleNumber, + ) + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println("Tx:", tx.Hash().Hex(), "\n") +} + +func finishRent(splited []string) { + if len(splited) != 2 { + fmt.Println("failed: len(splited) != 2\n") + return + } + var ( + rentNumber = new(big.Int) + ok bool + ) + rentNumber, ok = rentNumber.SetString(splited[1], 10) + if !ok { + fmt.Println("failed: conv(str1) to num\n") + return + } + tx, err := Instance.FinishRent( + resetAuth(User), + rentNumber, + ) + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println("Tx:", tx.Hash().Hex(), "\n") +} + +func chainCreateEstate(splited []string) { + if len(splited) != 5 { + fmt.Println("failed: len(splited) != 5\n") + return + } + var ( + squere = new(big.Int) + usefulSquere = new(big.Int) + ok bool + ) + squere, ok = squere.SetString(splited[3], 10) + if !ok { + fmt.Println("failed: conv(str1) to num\n") + return + } + usefulSquere, ok = usefulSquere.SetString(splited[4], 10) + if !ok { + fmt.Println("failed: conv(str2) to num\n") + return + } + var address common.Address + if splited[1] == "my" { + address = User.AddressEth + } else { + address = common.HexToAddress(splited[1]) + } + tx, err := Instance.CreateEstate( + resetAuth(User), + address, + splited[2], + squere, + usefulSquere, + ) + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println("Tx:", tx.Hash().Hex(), "\n") +} + +func chainCreatePresent(splited []string) { + if len(splited) != 3 { + fmt.Println("failed: len(splited) != 3\n") + return + } + var ( + estateId = new(big.Int) + ok bool + ) + estateId, ok = estateId.SetString(splited[1], 10) + if !ok { + fmt.Println("failed: conv(str1) to num\n") + return + } + tx, err := Instance.CreatePresent( + resetAuth(User), + estateId, + common.HexToAddress(splited[2]), + ) + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println("Tx:", tx.Hash().Hex(), "\n") +} + +func chainCreateSale(splited []string) { + if len(splited) != 3 { + fmt.Println("failed: len(splited) != 3\n") + return + } + var ( + estateId = new(big.Int) + price = new(big.Int) + ok bool + ) + estateId, ok = estateId.SetString(splited[1], 10) + if !ok { + fmt.Println("failed: conv(str1) to num\n") + return + } + price, ok = price.SetString(splited[2], 10) + if !ok { + fmt.Println("failed: conv(str2) to num\n") + return + } + tx, err := Instance.CreateSale( + resetAuth(User), + estateId, + price, + ) + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println("Tx:", tx.Hash().Hex(), "\n") +} + +func chainCreateRent(splited []string) { + if len(splited) != 4 { + fmt.Println("failed: len(splited) != 4\n") + return + } + var ( + estateId = new(big.Int) + hours = new(big.Int) + price = new(big.Int) + ok bool + ) + estateId, ok = estateId.SetString(splited[1], 10) + if !ok { + fmt.Println("failed: conv(str1) to num\n") + return + } + hours, ok = hours.SetString(splited[2], 10) + if !ok { + fmt.Println("failed: conv(str2) to num\n") + return + } + price, ok = price.SetString(splited[3], 10) + if !ok { + fmt.Println("failed: conv(str3) to num\n") + return + } + tx, err := Instance.CreateRent( + resetAuth(User), + estateId, + hours, + price, + ) + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println("Tx:", tx.Hash().Hex(), "\n") +} + +func chainCancel(category string, splited []string) { + if len(splited) != 2 { + fmt.Println("failed: len(splited) != 2\n") + return + } + var ( + tx *types.Transaction + err error + num = new(big.Int) + ok bool + ) + num, ok = num.SetString(splited[1], 10) + if !ok { + fmt.Println("failed: conv(str1) to num\n") + return + } + switch category { + case "present": + tx, err = Instance.CancelPresent( + resetAuth(User), + num, + ) + case "sale": + tx, err = Instance.CancelSale( + resetAuth(User), + num, + ) + case "rent": + tx, err = Instance.CancelRent( + resetAuth(User), + num, + ) + } + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println("Tx:", tx.Hash().Hex(), "\n") +} + +func chainConfirmPresent(splited []string) { + if len(splited) != 2 { + fmt.Println("failed: len(splited) != 2\n") + return + } + var ( + presentNumber = new(big.Int) + ok bool + ) + presentNumber, ok = presentNumber.SetString(splited[1], 10) + if !ok { + fmt.Println("failed: conv(str1) to num\n") + return + } + tx, err := Instance.ConfirmPresent( + resetAuth(User), + presentNumber, + ) + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println("Tx:", tx.Hash().Hex(), "\n") +} + +func chainConfirmSale(splited []string) { + if len(splited) != 3 { + fmt.Println("failed: len(splited) != 3\n") + return + } + var ( + saleNumber = new(big.Int) + saleTo = new(big.Int) + ok bool + ) + saleNumber, ok = saleNumber.SetString(splited[1], 10) + if !ok { + fmt.Println("failed: conv(str1) to num\n") + return + } + saleTo, ok = saleTo.SetString(splited[2], 10) + if !ok { + fmt.Println("failed: conv(str2) to num\n") + return + } + tx, err := Instance.ConfirmSale( + resetAuth(User), + saleNumber, + saleTo, + ) + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println("Tx:", tx.Hash().Hex(), "\n") +} + +func chainConfirmRent(splited []string) { + if len(splited) != 2 { + fmt.Println("failed: len(splited) != 2\n") + return + } + var ( + rentNumber = new(big.Int) + ok bool + ) + rentNumber, ok = rentNumber.SetString(splited[1], 10) + if !ok { + fmt.Println("failed: conv(str1) to num\n") + return + } + rent := getRents(rentNumber) + if rent == nil { + fmt.Println("failed: get rent\n") + return + } + auth := resetAuth(User) + if auth == nil { + fmt.Println("failed: auth is nil\n") + return + } + auth.Value = rent.Money + tx, err := Instance.ToRent( + auth, + rentNumber, + ) + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println("Tx:", tx.Hash().Hex(), "\n") +} + +func chainGet(category string, splited []string) { + if len(splited) != 2 { + fmt.Println("failed: len(splited) != 2\n") + return + } + var ( + inc = big.NewInt(1) + err error + num *big.Int + jsonData []byte + ) + switch category { + case "estates": + num, err = Instance.GetEstatesNumber(&bind.CallOpts{From: User.AddressEth}) + case "presents": + num, err = Instance.GetPresentsNumber(&bind.CallOpts{From: User.AddressEth}) + case "sales": + num, err = Instance.GetSalesNumber(&bind.CallOpts{From: User.AddressEth}) + case "rents": + num, err = Instance.GetRentsNumber(&bind.CallOpts{From: User.AddressEth}) + default: + fmt.Println("undefined category\n") + return + } + if err != nil { + fmt.Println(err, "\n") + return + } + for index := big.NewInt(0); index.Cmp(num) == -1; index.Add(index, inc) { + switch category { + case "estates": + data := getEstates(index) + if data == nil { + fmt.Println("data is nil\n") + return + } + if splited[1] == "my" && User.AddressHex != data.Owner.Hex() { + continue + } + if splited[1] != "all" && splited[1] != "my" && + strings.ToLower(splited[1]) != strings.ToLower(data.Owner.Hex()) { + continue + } + jsonData, err = json.MarshalIndent(data, "", "\t") + case "presents": + data := getPresents(index) + if data == nil { + fmt.Println("data is nil\n") + return + } + if data.Finished { + continue + } + if splited[1] == "my" && + (User.AddressHex != data.AddressFrom.Hex() && User.AddressHex != data.AddressTo.Hex()){ + continue + } + if splited[1] != "all" && splited[1] != "my" && + (strings.ToLower(splited[1]) != strings.ToLower(data.AddressFrom.Hex()) && + strings.ToLower(splited[1]) != strings.ToLower(data.AddressTo.Hex())) { + continue + } + jsonData, err = json.MarshalIndent(data, "", "\t") + case "sales": + data := getSales(index) + if data == nil { + fmt.Println("data is nil\n") + return + } + if data.Finished { + continue + } + if splited[1] == "my" && User.AddressHex != data.Owner.Hex() { + continue + } + if splited[1] != "all" && splited[1] != "my" && + strings.ToLower(splited[1]) != strings.ToLower(data.Owner.Hex()) { + continue + } + jsonData, err = json.MarshalIndent(data, "", "\t") + case "rents": + data := getRents(index) + if data == nil { + fmt.Println("data is nil\n") + return + } + if data.Finished { + continue + } + if splited[1] == "my" && + (User.AddressHex != data.Owner.Hex() && User.AddressHex != data.Renter.Hex()){ + continue + } + if splited[1] != "all" && splited[1] != "my" && + (strings.ToLower(splited[1]) != strings.ToLower(data.Owner.Hex()) && + strings.ToLower(splited[1]) != strings.ToLower(data.Renter.Hex())) { + continue + } + jsonData, err = json.MarshalIndent(data, "", "\t") + default: + fmt.Println("undefined category\n") + return + } + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println(string(jsonData)) + } + fmt.Println() +} + +func userAddress() { + fmt.Println("Address:", User.AddressHex, "\n") +} + +func userPurse() { + fmt.Println("Purse:", User.Purse, "\n") +} + +func userBalance() { + balance, err := ClientETH.BalanceAt(context.Background(), User.AddressEth, nil) + if err != nil { + fmt.Println(err, "\n") + return + } + fmt.Println("Balance:", balance, "\n") +} + +func inputString(begin string) string { + fmt.Print(begin) + msg, _ := bufio.NewReader(os.Stdin).ReadString('\n') + return strings.Replace(msg, "\n", "", 1) +} diff --git a/cmd/client_eth/values_eth.go b/cmd/client_eth/values_eth.go new file mode 100644 index 0000000..d66a33d --- /dev/null +++ b/cmd/client_eth/values_eth.go @@ -0,0 +1,304 @@ +package main + +import ( + contract "blockchain-from-scratch/contracts" + "context" + "crypto/ecdsa" + "io/ioutil" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" +) + +type UserType struct { + Purse string + AddressHex string + AddressEth common.Address + PublicKey *ecdsa.PublicKey + PrivateKey *ecdsa.PrivateKey +} + +type Estate struct { + Id *big.Int + Owner common.Address + Info string + Squere *big.Int + UsefulSquere *big.Int + RenterAddress common.Address + PresentStatus bool + SaleStatus bool + RentStatus bool +} + +type Present struct { + Id *big.Int + EstateId *big.Int + AddressFrom common.Address + AddressTo common.Address + Finished bool +} + +type Sale struct { + Id *big.Int + EstateId *big.Int + Owner common.Address + Price *big.Int + Customers []common.Address + Prices []*big.Int + Finished bool +} + +type Rent struct { + Id *big.Int + EstateId *big.Int + Owner common.Address + Renter common.Address + Time *big.Int + Money *big.Int + DeadLine *big.Int + Finished bool +} + +var ( + User *UserType + ClientETH = connectToETH("http://127.0.0.1:5555") + Instance = newContract( + common.HexToAddress(readFile("contract.address")), + ClientETH, + ) +) + +func loadUser(purse string) *UserType { + priv, err := crypto.HexToECDSA(purse) + if err != nil { + return nil + } + pub, ok := priv.Public().(*ecdsa.PublicKey) + if !ok { + return nil + } + addressHex := crypto.PubkeyToAddress(*pub).Hex() + addressEth := common.HexToAddress(addressHex) + return &UserType{ + Purse: purse, + AddressHex: addressHex, + AddressEth: addressEth, + PublicKey: pub, + PrivateKey: priv, + } +} + +func newContract(contractAddr common.Address, clientEth *ethclient.Client) *contract.Contract { + instance, err := contract.NewContract(contractAddr, clientEth) + if err != nil { + return nil + } + return instance +} + +func connectToETH(address string) *ethclient.Client { + client, err := ethclient.Dial(address) + if err != nil { + return nil + } + return client +} + +func readFile(filename string) string { + data, err := ioutil.ReadFile(filename) + if err != nil { + return "" + } + return string(data) +} + +func resetAuth(user *UserType) *bind.TransactOpts { + nonce, err := ClientETH.PendingNonceAt(context.Background(), crypto.PubkeyToAddress(*user.PublicKey)) + if err != nil { + return nil + } + + gasPrice, err := ClientETH.SuggestGasPrice(context.Background()) + if err != nil { + return nil + } + + auth := bind.NewKeyedTransactor(user.PrivateKey) + auth.Nonce = big.NewInt(int64(nonce)) + auth.Value = big.NewInt(0) + + auth.GasLimit = uint64(3000000) + auth.GasPrice = gasPrice + + return auth +} + +func getEstates(index *big.Int) *Estate { + // (*big.Int, common.Address, string, *big.Int, *big.Int, common.Address, error) + id, owner, info, squere, usefulsquere, renteraddress, err := Instance.GetEstates(&bind.CallOpts{From: User.AddressEth}, index) + if err != nil { + return nil + } + presentS, saleS, rentS, err := Instance.GetEstatesStatuses(&bind.CallOpts{From: User.AddressEth}, index) + if err != nil { + return nil + } + return &Estate{ + Id: id, + Owner: owner, + Info: info, + Squere: squere, + UsefulSquere: usefulsquere, + RenterAddress: renteraddress, + PresentStatus: presentS, + SaleStatus: saleS, + RentStatus: rentS, + } +} + +func getPresents(index *big.Int) *Present { + // (*big.Int, common.Address, common.Address, bool, error) + id, from, to, finished, err := Instance.GetPresents(&bind.CallOpts{From: User.AddressEth}, index) + if err != nil { + return nil + } + return &Present{ + Id: index, + EstateId: id, + AddressFrom: from, + AddressTo: to, + Finished: finished, + } +} + +func getSales(index *big.Int) *Sale { + // (*big.Int, common.Address, *big.Int, []common.Address, []*big.Int, bool, error) + id, owner, price, customers, prices, finished, err := Instance.GetSales(&bind.CallOpts{From: User.AddressEth}, index) + if err != nil { + return nil + } + return &Sale{ + Id: index, + EstateId: id, + Owner: owner, + Price: price, + Customers: customers, + Prices: prices, + Finished: finished, + } +} + +func getRents(index *big.Int) *Rent { + // (*big.Int, common.Address, common.Address, *big.Int, *big.Int, *big.Int, bool, error) + id, owner, renter, time, money, deadline, finished, err := Instance.GetRents(&bind.CallOpts{From: User.AddressEth}, index) + if err != nil { + return nil + } + return &Rent{ + Id: index, + EstateId: id, + Owner: owner, + Renter: renter, + Time: time, + Money: money, + DeadLine: deadline, + Finished: finished, + } +} + +type EstateStr struct { + Id *big.Int + Owner string + Info string + Squere *big.Int + UsefulSquere *big.Int + RenterAddress string + PresentStatus bool + SaleStatus bool + RentStatus bool +} + +func estatesToString(estate *Estate) *EstateStr { + return &EstateStr{ + Id: estate.Id, + Owner: estate.Owner.Hex(), + Info: estate.Info, + Squere: estate.Squere, + UsefulSquere: estate.UsefulSquere, + RenterAddress: estate.RenterAddress.Hex(), + PresentStatus: estate.PresentStatus, + SaleStatus: estate.SaleStatus, + RentStatus: estate.RentStatus, + } +} + +type PresentStr struct { + Id *big.Int + EstateId *big.Int + AddressFrom string + AddressTo string + Finished bool +} + +func presentsToString(present *Present) *PresentStr { + return &PresentStr{ + Id: present.Id, + EstateId: present.EstateId, + AddressFrom: present.AddressFrom.Hex(), + AddressTo: present.AddressTo.Hex(), + Finished: present.Finished, + } +} + +type SaleStr struct { + Id *big.Int + EstateId *big.Int + Owner string + Price *big.Int + Customers []string + Prices []*big.Int + Finished bool +} + +func salesToString(sale *Sale) *SaleStr { + var customers []string + for _, cust := range sale.Customers { + customers = append(customers, cust.Hex()) + } + return &SaleStr{ + Id: sale.Id, + EstateId: sale.EstateId, + Owner: sale.Owner.Hex(), + Price: sale.Price, + Customers: customers, + Prices: sale.Prices, + Finished: sale.Finished, + } +} + +type RentStr struct { + Id *big.Int + EstateId *big.Int + Owner string + Renter string + Time *big.Int + Money *big.Int + DeadLine *big.Int + Finished bool +} + +func rentsToString(rent *Rent) *RentStr { + return &RentStr{ + Id: rent.Id, + EstateId: rent.EstateId, + Owner: rent.Owner.Hex(), + Renter: rent.Renter.Hex(), + Time: rent.Time, + Money: rent.Money, + DeadLine: rent.DeadLine, + Finished: rent.Finished, + } +} diff --git a/cmd/deploy/deploy.go b/cmd/deploy/deploy.go new file mode 100644 index 0000000..37303de --- /dev/null +++ b/cmd/deploy/deploy.go @@ -0,0 +1,136 @@ +package main + +import ( + contract "blockchain-from-scratch/contracts" + "context" + "crypto/ecdsa" + "fmt" + "io/ioutil" + "log" + "math/big" + "os" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" +) + +type UserType struct { + Purse string + AddressHex string + AddressEth common.Address + PublicKey *ecdsa.PublicKey + PrivateKey *ecdsa.PrivateKey +} + +var ( + ClientETH = connectToETH("http://127.0.0.1:5555") + User *UserType +) + +func init() { + if len(os.Args) < 2 { + panic("failed: len(os.Args) < 2") + } + var ( + userLoadStr = "" + userLoadExist = false + ) + for i := 1; i < len(os.Args); i++ { + arg := os.Args[i] + switch { + case strings.HasPrefix(arg, "-loaduser:"): + userLoadStr = strings.Replace(arg, "-loaduser:", "", 1) + userLoadExist = true + } + } + if !userLoadExist { + panic("failed: !userLoadExist") + } + if ClientETH == nil { + panic("failed: connect to ETH") + } + User = userLoad(userLoadStr) + if User == nil { + panic("failed: load user") + } +} + +// Deploy contract and save address in file. +func main() { + auth := resetAuth(User) + address, tx, instance, err := contract.DeployContract(auth, ClientETH) + + if err != nil { + log.Fatal(err) + } + + fmt.Println(address.Hex()) + fmt.Println(tx.Hash().Hex()) + _ = instance + + contractFile := "contract.address" + writeFile(contractFile, address.Hex()) +} + +func writeFile(filename string, data string) error { + return ioutil.WriteFile(filename, []byte(data), 0644) +} + +func userLoad(purse string) *UserType { + priv, err := crypto.HexToECDSA(purse) + if err != nil { + return nil + } + pub, ok := priv.Public().(*ecdsa.PublicKey) + if !ok { + return nil + } + addressHex := crypto.PubkeyToAddress(*pub).Hex() + addressEth := common.HexToAddress(addressHex) + return &UserType{ + Purse: purse, + AddressHex: addressHex, + AddressEth: addressEth, + PublicKey: pub, + PrivateKey: priv, + } +} + +func connectToETH(address string) *ethclient.Client { + client, err := ethclient.Dial(address) + if err != nil { + return nil + } + return client +} + +func resetAuth(user *UserType) *bind.TransactOpts { + nonce, err := ClientETH.PendingNonceAt(context.Background(), User.AddressEth) + if err != nil { + return nil + } + + gasPrice, err := ClientETH.SuggestGasPrice(context.Background()) + if err != nil { + return nil + } + + auth := bind.NewKeyedTransactor(user.PrivateKey) + auth.Nonce = big.NewInt(int64(nonce)) + auth.Value = big.NewInt(0) + + // auth.GasLimit = uint64(3000000) + auth.GasPrice = gasPrice + + return auth +} + +func fileIsExist(name string) bool { + if _, err := os.Stat(name); os.IsNotExist(err) { + return false + } + return true +} diff --git a/cmd/gclient/gclient.go b/cmd/gclient/gclient.go new file mode 100644 index 0000000..35ef285 --- /dev/null +++ b/cmd/gclient/gclient.go @@ -0,0 +1,285 @@ +package main + +import ( + bc "blockchain-from-scratch/blockchain" + "encoding/json" + "fmt" + "html/template" + "net/http" + "os" + "strconv" + "strings" + + nt "blockchain-from-scratch/network" +) + +const ( + STTC_PATH = "static/" + TMPL_PATH = "templates/" + ADDR_FILE = "addr.json" +) + +func init() { + err := json.Unmarshal([]byte(readFile(ADDR_FILE)), &Addresses) + if err != nil { + panic("failed: load addresses") + } + if len(Addresses) == 0 { + panic("failed: len(Addresses) == 0") + } +} + +func main() { + fmt.Println("Server is running ...") + + http.Handle("/static/", http.StripPrefix( + "/static/", + handleFileServer(http.Dir(STTC_PATH))), + ) + + http.HandleFunc("/", indexPage) + http.HandleFunc("/login", loginPage) + http.HandleFunc("/signup", signupPage) + http.HandleFunc("/logout", logoutPage) + http.HandleFunc("/account", accountPage) + http.HandleFunc("/transaction", transactionPage) + http.HandleFunc("/blockchain", blockchainPage) + http.HandleFunc("/blockchain/", blockchainXPage) + + http.ListenAndServe(":7545", nil) +} + +func handleFileServer(fs http.FileSystem) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if _, err := fs.Open(r.URL.Path); os.IsNotExist(err) { + indexPage(w, r) + return + } + http.FileServer(fs).ServeHTTP(w, r) + }) +} + +func indexPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"index.html", + ) + if err != nil { + panic("can't load hmtl files") + } + var data struct { + User *bc.User + } + data.User = User + t.Execute(w, data) +} + +func accountPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"account.html", + ) + if err != nil { + panic("can't load hmtl files") + } + var data struct { + User *bc.User + Address string + Balance string + } + data.User = User + if data.User != nil { + data.Address = User.Address() + res := nt.Send(Addresses[0], &nt.Package{ + Option: GET_BLNCE, + Data: data.Address, + }) + if res != nil { + data.Balance = res.Data + } + } else { + http.Redirect(w, r, "/", 302) + return + } + t.Execute(w, data) +} + +func loginPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"login.html", + ) + if err != nil { + panic("can't load hmtl files") + } + var data struct { + User *bc.User + Error string + } + if r.Method == "POST" { + r.ParseForm() + User = bc.LoadUser(r.FormValue("private")) + if User == nil { + data.Error = "Load Private Key Error" + } else { + http.Redirect(w, r, "/", 302) + return + } + } + data.User = User + t.Execute(w, data) +} + +func logoutPage(w http.ResponseWriter, r *http.Request) { + User = nil + http.Redirect(w, r, "/", 302) +} + +func signupPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"signup.html", + ) + if err != nil { + panic("can't load hmtl files") + } + var data struct { + User *bc.User + PrivateKey string + } + data.User = User + if r.Method == "POST" { + data.PrivateKey = bc.NewUser().Purse() + } + t.Execute(w, data) +} + +func transactionPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"transaction.html", + ) + if err != nil { + panic("can't load hmtl files") + } + var data struct { + User *bc.User + Error string + } + data.User = User + if r.Method == "POST" { + r.ParseForm() + if data.User == nil { + data.Error = "User not authorizated" + t.Execute(w, data) + return + } + receiver := r.FormValue("receiver") + num, err := strconv.Atoi(r.FormValue("value")) + if err != nil { + data.Error = "strconv.Atoi error" + t.Execute(w, data) + return + } + flag := false + for _, addr := range Addresses { + res := nt.Send(addr, &nt.Package{ + Option: GET_LHASH, + }) + if res == nil { + continue + } + tx := bc.NewTransaction(User, bc.Base64Decode(res.Data), receiver, uint64(num)) + res = nt.Send(addr, &nt.Package{ + Option: ADD_TRNSX, + Data: bc.SerializeTX(tx), + }) + if res == nil || res.Data != "ok" { + continue + } + flag = true + } + if !flag { + data.Error = "TX failed" + } else { + data.Error = "TX success" + } + } + t.Execute(w, data) +} + +func blockchainPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"blockchain.html", + ) + if err != nil { + panic("can't load hmtl files") + } + var data struct { + Error string + Size []bool + Address string + Balance string + User *bc.User + } + data.User = User + if r.Method == "POST" { + data.Address = r.FormValue("address") + res := nt.Send(Addresses[0], &nt.Package{ + Option: GET_BLNCE, + Data: data.Address, + }) + if res != nil { + data.Balance = res.Data + } + } + res := nt.Send(Addresses[0], &nt.Package{ + Option: GET_CSIZE, + }) + if res == nil || res.Data == "" { + data.Error = "Receive error" + t.Execute(w, data) + return + } + num, err := strconv.Atoi(res.Data) + if err != nil { + data.Error = "strconv.Atoi error" + t.Execute(w, data) + return + } + data.Size = make([]bool, num) + t.Execute(w, data) +} + +func blockchainXPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"blockchainX.html", + ) + if err != nil { + panic("can't load hmtl files") + } + var data struct { + Error string + Block *bc.Block + User *bc.User + } + data.User = User + res := nt.Send(Addresses[0], &nt.Package{ + Option: GET_BLOCK, + Data: strings.Replace(r.URL.Path, "/blockchain/", "", 1), + }) + if res == nil || res.Data == "" { + data.Error = "Receive error" + t.Execute(w, data) + return + } + data.Block = bc.DeserializeBlock(res.Data) + if data.Block == nil { + data.Error = "Block is nil" + t.Execute(w, data) + return + } + t.Execute(w, data) +} diff --git a/cmd/gclient/values.go b/cmd/gclient/values.go new file mode 100644 index 0000000..c06a897 --- /dev/null +++ b/cmd/gclient/values.go @@ -0,0 +1,60 @@ +package main + +import ( + bc "blockchain-from-scratch/blockchain" + "io/ioutil" +) + +var ( + Addresses []string + User *bc.User +) + +const ( + SEPARATOR = "_SEPARATOR_" +) + +const ( + ADD_BLOCK = iota + 1 + ADD_TRNSX + GET_BLOCK + GET_LHASH + GET_BLNCE + GET_CSIZE +) + +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 +} + +func writeFile(filename string, data string) error { + return ioutil.WriteFile(filename, []byte(data), 0644) +} + +func readFile(filename string) string { + data, err := ioutil.ReadFile(filename) + if err != nil { + return "" + } + return string(data) +} diff --git a/cmd/gclient_eth/gclient_eth.go b/cmd/gclient_eth/gclient_eth.go new file mode 100644 index 0000000..6e91c17 --- /dev/null +++ b/cmd/gclient_eth/gclient_eth.go @@ -0,0 +1,905 @@ +package main + +import ( + "os" + "fmt" + "strings" + "context" + "net/http" + "math/big" + "html/template" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" +) + +const ( + STTC_PATH = "static/" + TMPL_PATH = "templates_eth/" +) + +func init() { + if ClientETH == nil { + panic("failed: connect to ETH") + } + if Instance == nil { + panic("failed: instance is nil") + } +} + +func main() { + fmt.Println("Server is running ...") + + http.Handle("/static/", http.StripPrefix( + "/static/", + handleFileServer(http.Dir(STTC_PATH))), + ) + + http.HandleFunc("/", indexPage) + http.HandleFunc("/login", loginPage) + http.HandleFunc("/logout", logoutPage) + http.HandleFunc("/account", accountPage) + + http.HandleFunc("/blockchain", blockchainPage) + http.HandleFunc("/blockchain/estates", blockchainEstatesPage) + http.HandleFunc("/blockchain/presents", blockchainPresentsPage) + http.HandleFunc("/blockchain/sales", blockchainSalesPage) + http.HandleFunc("/blockchain/rents", blockchainRentsPage) + + http.HandleFunc("/blockchain/estates/", blockchainEstatesXPage) + http.HandleFunc("/blockchain/presents/", blockchainPresentsXPage) + http.HandleFunc("/blockchain/sales/", blockchainSalesXPage) + http.HandleFunc("/blockchain/rents/", blockchainRentsXPage) + + http.HandleFunc("/blockchain/presents/do/", blockchainPresentsDoPage) + http.HandleFunc("/blockchain/sales/do/", blockchainSalesDoPage) + http.HandleFunc("/blockchain/rents/do/", blockchainRentsDoPage) + + http.ListenAndServe(":7545", nil) +} + +func handleFileServer(fs http.FileSystem) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if _, err := fs.Open(r.URL.Path); os.IsNotExist(err) { + indexPage(w, r) + return + } + http.FileServer(fs).ServeHTTP(w, r) + }) +} + +func indexPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"index.html", + ) + if err != nil { + panic("can't load hmtl files") + } + var data struct{ + User *UserType + } + data.User = User + t.Execute(w, data) +} + +func loginPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"login.html", + ) + if err != nil { + panic("can't load hmtl files") + } + var data struct{ + User *UserType + Error string + } + if r.Method == "POST" { + r.ParseForm() + User = loadUser(r.FormValue("private")) + if User == nil { + data.Error = "Load Private Key Error" + } else { + http.Redirect(w, r, "/", 302) + return + } + } + data.User = User + t.Execute(w, data) +} + +func logoutPage(w http.ResponseWriter, r *http.Request) { + User = nil + http.Redirect(w, r, "/", 302) +} + +func accountPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"account.html", + ) + if err != nil { + panic("can't load hmtl files") + } + var data struct{ + User *UserType + Address string + Balance string + } + data.User = User + if data.User != nil { + data.Address = User.AddressHex + balance, err := ClientETH.BalanceAt(context.Background(), User.AddressEth, nil) + if err == nil { + data.Balance = balance.String() + } + } else { + http.Redirect(w, r, "/", 302) + return + } + t.Execute(w, data) +} + +func blockchainPresentsDoPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"presentsDo.html", + ) + if err != nil { + panic("can't load hmtl files") + } + if User == nil { + http.Redirect(w, r, "/login", 302) + return + } + var data struct{ + User *UserType + Block *EstateStr + Error string + } + data.User = User + var ( + index = new(big.Int) + ok bool + ) + num := strings.Replace(r.URL.Path, "/blockchain/presents/do/", "", 1) + index, ok = index.SetString(num, 10) + if !ok { + data.Error = "strconv error" + t.Execute(w, data) + return + } + estate := getEstates(index) + if estate == nil { + data.Error = "estate is nil" + t.Execute(w, data) + return + } + data.Block = estatesToString(estate) + if r.Method == "POST" { + r.ParseForm() + _, err := Instance.CreatePresent( + resetAuth(User), + index, + common.HexToAddress(r.FormValue("address")), + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success created" + } + t.Execute(w, data) +} + +func blockchainSalesDoPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"salesDo.html", + ) + if err != nil { + panic("can't load hmtl files") + } + if User == nil { + http.Redirect(w, r, "/login", 302) + return + } + var data struct{ + User *UserType + Block *EstateStr + Error string + } + data.User = User + var ( + index = new(big.Int) + ok bool + ) + num := strings.Replace(r.URL.Path, "/blockchain/sales/do/", "", 1) + index, ok = index.SetString(num, 10) + if !ok { + data.Error = "strconv error" + t.Execute(w, data) + return + } + estate := getEstates(index) + if estate == nil { + data.Error = "estate is nil" + t.Execute(w, data) + return + } + data.Block = estatesToString(estate) + if r.Method == "POST" { + r.ParseForm() + var ( + price = new(big.Int) + ok bool + ) + price, ok = price.SetString(r.FormValue("price"), 10) + if !ok { + data.Error = "strconv error" + t.Execute(w, data) + return + } + _, err := Instance.CreateSale( + resetAuth(User), + index, + price, + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success created" + } + t.Execute(w, data) +} + +func blockchainRentsDoPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"rentsDo.html", + ) + if err != nil { + panic("can't load hmtl files") + } + if User == nil { + http.Redirect(w, r, "/login", 302) + return + } + var data struct{ + User *UserType + Block *EstateStr + Error string + } + data.User = User + var ( + index = new(big.Int) + ok bool + ) + num := strings.Replace(r.URL.Path, "/blockchain/rents/do/", "", 1) + index, ok = index.SetString(num, 10) + if !ok { + data.Error = "strconv error" + t.Execute(w, data) + return + } + rent := getEstates(index) + if rent == nil { + data.Error = "rent is nil" + t.Execute(w, data) + return + } + data.Block = estatesToString(rent) + if r.Method == "POST" { + r.ParseForm() + var ( + days = new(big.Int) + price = new(big.Int) + ok bool + ) + days, ok = days.SetString(r.FormValue("days"), 10) + if !ok { + data.Error = "strconv error" + t.Execute(w, data) + return + } + price, ok = price.SetString(r.FormValue("price"), 10) + if !ok { + data.Error = "strconv error" + t.Execute(w, data) + return + } + _, err := Instance.CreateRent( + resetAuth(User), + index, + days, + price, + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success created" + } + t.Execute(w, data) +} + +func blockchainEstatesXPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"estatesX.html", + ) + if err != nil { + panic("can't load hmtl files") + } + if User == nil { + http.Redirect(w, r, "/login", 302) + return + } + var data struct{ + User *UserType + Block *EstateStr + Error string + } + data.User = User + var ( + index = new(big.Int) + ok bool + ) + num := strings.Replace(r.URL.Path, "/blockchain/estates/", "", 1) + index, ok = index.SetString(num, 10) + if !ok { + data.Error = "strconv error" + t.Execute(w, data) + return + } + estate := getEstates(index) + if estate == nil { + data.Error = "estate is nil" + t.Execute(w, data) + return + } + data.Block = estatesToString(estate) + t.Execute(w, data) +} + +func blockchainPresentsXPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"presentsX.html", + ) + if err != nil { + panic("can't load hmtl files") + } + if User == nil { + http.Redirect(w, r, "/login", 302) + return + } + var data struct{ + User *UserType + Block *PresentStr + Error string + } + data.User = User + var ( + index = new(big.Int) + ok bool + ) + num := strings.Replace(r.URL.Path, "/blockchain/presents/", "", 1) + index, ok = index.SetString(num, 10) + if !ok { + data.Error = "strconv error" + t.Execute(w, data) + return + } + present := getPresents(index) + if present == nil { + data.Error = "present is nil" + t.Execute(w, data) + return + } + data.Block = presentsToString(present) + if r.Method == "POST" { + r.ParseForm() + if r.FormValue("cancel") != "" { + _, err := Instance.CancelPresent( + resetAuth(User), + index, + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success cancel" + } + if r.FormValue("confirm") != "" { + _, err := Instance.ConfirmPresent( + resetAuth(User), + index, + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success confirm" + } + } + t.Execute(w, data) +} + +func blockchainSalesXPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"salesX.html", + ) + if err != nil { + panic("can't load hmtl files") + } + if User == nil { + http.Redirect(w, r, "/login", 302) + return + } + var data struct{ + User *UserType + Block *SaleStr + Error string + InCustomers bool + } + data.User = User + var ( + index = new(big.Int) + ok bool + ) + num := strings.Replace(r.URL.Path, "/blockchain/sales/", "", 1) + index, ok = index.SetString(num, 10) + if !ok { + data.Error = "strconv error" + t.Execute(w, data) + return + } + sale := getSales(index) + if sale == nil { + data.Error = "sale is nil" + t.Execute(w, data) + return + } + data.Block = salesToString(sale) + for _, cust := range data.Block.Customers { + if cust == User.AddressHex { + data.InCustomers = true + break + } + } + if r.Method == "POST" { + r.ParseForm() + if r.FormValue("cancel") != "" { + _, err := Instance.CancelSale( + resetAuth(User), + index, + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success cancel" + } + if r.FormValue("confirm") != "" { + var ( + saleTo = new(big.Int) + ok bool + ) + saleTo, ok = saleTo.SetString(r.FormValue("addrnum"), 10) + if !ok { + data.Error = "strconv error" + t.Execute(w, data) + return + } + _, err := Instance.ConfirmSale( + resetAuth(User), + index, + saleTo, + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success confirm" + } + if r.FormValue("buy") != "" { + var ( + price = new(big.Int) + ok bool + ) + price, ok = price.SetString(r.FormValue("price"), 10) + if !ok { + data.Error = "strconv error" + t.Execute(w, data) + return + } + auth := resetAuth(User) + if auth == nil { + data.Error = "failed: auth is nil" + t.Execute(w, data) + return + } + auth.Value = price + _, err := Instance.CheckToBuy( + auth, + index, + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success buy request" + } + if r.FormValue("close") != "" { + _, err := Instance.CancelToBuy( + resetAuth(User), + index, + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success close buy request" + } + } + t.Execute(w, data) +} + +func blockchainRentsXPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"rentsX.html", + ) + if err != nil { + panic("can't load hmtl files") + } + if User == nil { + http.Redirect(w, r, "/login", 302) + return + } + var data struct{ + User *UserType + Block *RentStr + Error string + Confirmed bool + } + data.User = User + var ( + index = new(big.Int) + ok bool + ) + num := strings.Replace(r.URL.Path, "/blockchain/rents/", "", 1) + index, ok = index.SetString(num, 10) + if !ok { + data.Error = "strconv error" + t.Execute(w, data) + return + } + rent := getRents(index) + if rent == nil { + data.Error = "rent is nil" + t.Execute(w, data) + return + } + data.Block = rentsToString(rent) + if data.Block.Renter != "0x0000000000000000000000000000000000000000" { + data.Confirmed = true + } + if r.Method == "POST" { + r.ParseForm() + if r.FormValue("cancel") != "" { + _, err := Instance.CancelRent( + resetAuth(User), + index, + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success cancel" + } + if r.FormValue("confirm") != "" { + auth := resetAuth(User) + if auth == nil { + data.Error = "failed: auth is nil" + t.Execute(w, data) + return + } + auth.Value = rent.Money + _, err := Instance.ToRent( + auth, + index, + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success confirm" + } + if r.FormValue("finish") != "" { + _, err := Instance.FinishRent( + resetAuth(User), + index, + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success finish" + } + } + t.Execute(w, data) +} + +func blockchainPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"blockchain.html", + ) + if err != nil { + panic("can't load hmtl files") + } + if User == nil { + http.Redirect(w, r, "/login", 302) + return + } + var data struct{ + User *UserType + IsAdmin bool + Error string + } + data.User = User + iamAdmin, err := Instance.IamAdmin(&bind.CallOpts{From: User.AddressEth}) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + if iamAdmin { + data.IsAdmin = true + } + if r.Method == "POST" { + r.ParseForm() + var ( + squere = new(big.Int) + usefulSquere = new(big.Int) + ok bool + ) + squere, ok = squere.SetString(r.FormValue("squere"), 10) + if !ok { + data.Error = "strconv error 1" + t.Execute(w, data) + return + } + usefulSquere, ok = usefulSquere.SetString(r.FormValue("usefulsquere"), 10) + if !ok { + data.Error = "strconv error 2" + t.Execute(w, data) + return + } + _, err := Instance.CreateEstate( + resetAuth(User), + User.AddressEth, + r.FormValue("info"), + squere, + usefulSquere, + ) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + data.Error = "Success created" + } + t.Execute(w, data) +} + +func blockchainEstatesPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"estates.html", + ) + if err != nil { + panic("can't load hmtl files") + } + if User == nil { + http.Redirect(w, r, "/login", 302) + return + } + var data struct{ + Error string + Blocks []uint64 + Address string + User *UserType + } + data.User = User + data.Address = User.AddressHex + if r.Method == "POST" { + data.Address = r.FormValue("address") + } + var inc = big.NewInt(1) + num, err := Instance.GetEstatesNumber(&bind.CallOpts{From: User.AddressEth}) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + for index := big.NewInt(0); index.Cmp(num) == -1; index.Add(index, inc) { + block := getEstates(index) + if block == nil { + data.Error = "data is nil" + t.Execute(w, data) + return + } + if data.Address != "all" && strings.ToLower(data.Address) != strings.ToLower(block.Owner.Hex()) { + continue + } + data.Blocks = append(data.Blocks, index.Uint64()) + } + t.Execute(w, data) +} + +func blockchainPresentsPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"presents.html", + ) + if err != nil { + panic("can't load hmtl files") + } + if User == nil { + http.Redirect(w, r, "/login", 302) + return + } + var data struct{ + Error string + Blocks []uint64 + Address string + User *UserType + } + data.User = User + data.Address = User.AddressHex + if r.Method == "POST" { + data.Address = r.FormValue("address") + } + var inc = big.NewInt(1) + num, err := Instance.GetPresentsNumber(&bind.CallOpts{From: User.AddressEth}) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + for index := big.NewInt(0); index.Cmp(num) == -1; index.Add(index, inc) { + block := getPresents(index) + if block == nil { + data.Error = "data is nil" + t.Execute(w, data) + return + } + if block.Finished { + continue + } + if data.Address != "all" && + strings.ToLower(data.Address) != strings.ToLower(block.AddressFrom.Hex()) && + strings.ToLower(data.Address) != strings.ToLower(block.AddressTo.Hex()) { + continue + } + data.Blocks = append(data.Blocks, index.Uint64()) + } + t.Execute(w, data) +} + +func blockchainSalesPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"sales.html", + ) + if err != nil { + panic("can't load hmtl files") + } + if User == nil { + http.Redirect(w, r, "/login", 302) + return + } + var data struct{ + Error string + Blocks []uint64 + Address string + User *UserType + } + data.User = User + data.Address = User.AddressHex + if r.Method == "POST" { + data.Address = r.FormValue("address") + } + var inc = big.NewInt(1) + num, err := Instance.GetSalesNumber(&bind.CallOpts{From: User.AddressEth}) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + for index := big.NewInt(0); index.Cmp(num) == -1; index.Add(index, inc) { + block := getSales(index) + if block == nil { + data.Error = "data is nil" + t.Execute(w, data) + return + } + if block.Finished { + continue + } + if data.Address != "all" && + strings.ToLower(data.Address) != strings.ToLower(block.Owner.Hex()) { + continue + } + data.Blocks = append(data.Blocks, index.Uint64()) + } + t.Execute(w, data) +} + +func blockchainRentsPage(w http.ResponseWriter, r *http.Request) { + t, err := template.ParseFiles( + TMPL_PATH+"base.html", + TMPL_PATH+"rents.html", + ) + if err != nil { + panic("can't load hmtl files") + } + if User == nil { + http.Redirect(w, r, "/login", 302) + return + } + var data struct{ + Error string + Blocks []uint64 + Address string + User *UserType + } + data.User = User + data.Address = User.AddressHex + if r.Method == "POST" { + data.Address = r.FormValue("address") + } + var inc = big.NewInt(1) + num, err := Instance.GetRentsNumber(&bind.CallOpts{From: User.AddressEth}) + if err != nil { + data.Error = err.Error() + t.Execute(w, data) + return + } + for index := big.NewInt(0); index.Cmp(num) == -1; index.Add(index, inc) { + block := getRents(index) + if block == nil { + data.Error = "data is nil" + t.Execute(w, data) + return + } + if block.Finished { + continue + } + if data.Address != "all" && + strings.ToLower(data.Address) != strings.ToLower(block.Owner.Hex()) && + strings.ToLower(data.Address) != strings.ToLower(block.Renter.Hex()) { + continue + } + data.Blocks = append(data.Blocks, index.Uint64()) + } + + t.Execute(w, data) +} diff --git a/cmd/gclient_eth/values_eth.go b/cmd/gclient_eth/values_eth.go new file mode 100644 index 0000000..d66a33d --- /dev/null +++ b/cmd/gclient_eth/values_eth.go @@ -0,0 +1,304 @@ +package main + +import ( + contract "blockchain-from-scratch/contracts" + "context" + "crypto/ecdsa" + "io/ioutil" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" +) + +type UserType struct { + Purse string + AddressHex string + AddressEth common.Address + PublicKey *ecdsa.PublicKey + PrivateKey *ecdsa.PrivateKey +} + +type Estate struct { + Id *big.Int + Owner common.Address + Info string + Squere *big.Int + UsefulSquere *big.Int + RenterAddress common.Address + PresentStatus bool + SaleStatus bool + RentStatus bool +} + +type Present struct { + Id *big.Int + EstateId *big.Int + AddressFrom common.Address + AddressTo common.Address + Finished bool +} + +type Sale struct { + Id *big.Int + EstateId *big.Int + Owner common.Address + Price *big.Int + Customers []common.Address + Prices []*big.Int + Finished bool +} + +type Rent struct { + Id *big.Int + EstateId *big.Int + Owner common.Address + Renter common.Address + Time *big.Int + Money *big.Int + DeadLine *big.Int + Finished bool +} + +var ( + User *UserType + ClientETH = connectToETH("http://127.0.0.1:5555") + Instance = newContract( + common.HexToAddress(readFile("contract.address")), + ClientETH, + ) +) + +func loadUser(purse string) *UserType { + priv, err := crypto.HexToECDSA(purse) + if err != nil { + return nil + } + pub, ok := priv.Public().(*ecdsa.PublicKey) + if !ok { + return nil + } + addressHex := crypto.PubkeyToAddress(*pub).Hex() + addressEth := common.HexToAddress(addressHex) + return &UserType{ + Purse: purse, + AddressHex: addressHex, + AddressEth: addressEth, + PublicKey: pub, + PrivateKey: priv, + } +} + +func newContract(contractAddr common.Address, clientEth *ethclient.Client) *contract.Contract { + instance, err := contract.NewContract(contractAddr, clientEth) + if err != nil { + return nil + } + return instance +} + +func connectToETH(address string) *ethclient.Client { + client, err := ethclient.Dial(address) + if err != nil { + return nil + } + return client +} + +func readFile(filename string) string { + data, err := ioutil.ReadFile(filename) + if err != nil { + return "" + } + return string(data) +} + +func resetAuth(user *UserType) *bind.TransactOpts { + nonce, err := ClientETH.PendingNonceAt(context.Background(), crypto.PubkeyToAddress(*user.PublicKey)) + if err != nil { + return nil + } + + gasPrice, err := ClientETH.SuggestGasPrice(context.Background()) + if err != nil { + return nil + } + + auth := bind.NewKeyedTransactor(user.PrivateKey) + auth.Nonce = big.NewInt(int64(nonce)) + auth.Value = big.NewInt(0) + + auth.GasLimit = uint64(3000000) + auth.GasPrice = gasPrice + + return auth +} + +func getEstates(index *big.Int) *Estate { + // (*big.Int, common.Address, string, *big.Int, *big.Int, common.Address, error) + id, owner, info, squere, usefulsquere, renteraddress, err := Instance.GetEstates(&bind.CallOpts{From: User.AddressEth}, index) + if err != nil { + return nil + } + presentS, saleS, rentS, err := Instance.GetEstatesStatuses(&bind.CallOpts{From: User.AddressEth}, index) + if err != nil { + return nil + } + return &Estate{ + Id: id, + Owner: owner, + Info: info, + Squere: squere, + UsefulSquere: usefulsquere, + RenterAddress: renteraddress, + PresentStatus: presentS, + SaleStatus: saleS, + RentStatus: rentS, + } +} + +func getPresents(index *big.Int) *Present { + // (*big.Int, common.Address, common.Address, bool, error) + id, from, to, finished, err := Instance.GetPresents(&bind.CallOpts{From: User.AddressEth}, index) + if err != nil { + return nil + } + return &Present{ + Id: index, + EstateId: id, + AddressFrom: from, + AddressTo: to, + Finished: finished, + } +} + +func getSales(index *big.Int) *Sale { + // (*big.Int, common.Address, *big.Int, []common.Address, []*big.Int, bool, error) + id, owner, price, customers, prices, finished, err := Instance.GetSales(&bind.CallOpts{From: User.AddressEth}, index) + if err != nil { + return nil + } + return &Sale{ + Id: index, + EstateId: id, + Owner: owner, + Price: price, + Customers: customers, + Prices: prices, + Finished: finished, + } +} + +func getRents(index *big.Int) *Rent { + // (*big.Int, common.Address, common.Address, *big.Int, *big.Int, *big.Int, bool, error) + id, owner, renter, time, money, deadline, finished, err := Instance.GetRents(&bind.CallOpts{From: User.AddressEth}, index) + if err != nil { + return nil + } + return &Rent{ + Id: index, + EstateId: id, + Owner: owner, + Renter: renter, + Time: time, + Money: money, + DeadLine: deadline, + Finished: finished, + } +} + +type EstateStr struct { + Id *big.Int + Owner string + Info string + Squere *big.Int + UsefulSquere *big.Int + RenterAddress string + PresentStatus bool + SaleStatus bool + RentStatus bool +} + +func estatesToString(estate *Estate) *EstateStr { + return &EstateStr{ + Id: estate.Id, + Owner: estate.Owner.Hex(), + Info: estate.Info, + Squere: estate.Squere, + UsefulSquere: estate.UsefulSquere, + RenterAddress: estate.RenterAddress.Hex(), + PresentStatus: estate.PresentStatus, + SaleStatus: estate.SaleStatus, + RentStatus: estate.RentStatus, + } +} + +type PresentStr struct { + Id *big.Int + EstateId *big.Int + AddressFrom string + AddressTo string + Finished bool +} + +func presentsToString(present *Present) *PresentStr { + return &PresentStr{ + Id: present.Id, + EstateId: present.EstateId, + AddressFrom: present.AddressFrom.Hex(), + AddressTo: present.AddressTo.Hex(), + Finished: present.Finished, + } +} + +type SaleStr struct { + Id *big.Int + EstateId *big.Int + Owner string + Price *big.Int + Customers []string + Prices []*big.Int + Finished bool +} + +func salesToString(sale *Sale) *SaleStr { + var customers []string + for _, cust := range sale.Customers { + customers = append(customers, cust.Hex()) + } + return &SaleStr{ + Id: sale.Id, + EstateId: sale.EstateId, + Owner: sale.Owner.Hex(), + Price: sale.Price, + Customers: customers, + Prices: sale.Prices, + Finished: sale.Finished, + } +} + +type RentStr struct { + Id *big.Int + EstateId *big.Int + Owner string + Renter string + Time *big.Int + Money *big.Int + DeadLine *big.Int + Finished bool +} + +func rentsToString(rent *Rent) *RentStr { + return &RentStr{ + Id: rent.Id, + EstateId: rent.EstateId, + Owner: rent.Owner.Hex(), + Renter: rent.Renter.Hex(), + Time: rent.Time, + Money: rent.Money, + DeadLine: rent.DeadLine, + Finished: rent.Finished, + } +} diff --git a/cmd/node/node.go b/cmd/node/node.go new file mode 100644 index 0000000..440ef48 --- /dev/null +++ b/cmd/node/node.go @@ -0,0 +1,129 @@ +package main + +import ( + bc "blockchain-from-scratch/blockchain" + "encoding/json" + "fmt" + "os" + "strings" + + nt "blockchain-from-scratch/network" +) + +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()) +} + +func main() { + nt.Listen(Serve, handleServer) + for { + fmt.Scanln() + } +} + +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 +} diff --git a/cmd/node/serve.go b/cmd/node/serve.go new file mode 100644 index 0000000..679b22f --- /dev/null +++ b/cmd/node/serve.go @@ -0,0 +1,290 @@ +package main + +import ( + bc "blockchain-from-scratch/blockchain" + "bytes" + "database/sql" + "encoding/hex" + "fmt" + "io" + "os" + "sort" + "strconv" + "strings" + "sync" + + nt "blockchain-from-scratch/network" + + _ "github.com/mattn/go-sqlite3" +) + +var ( + Filename string + Serve string + Chain *bc.BlockChain + Block *bc.Block + Mutex sync.Mutex +) + +var ( + IsMining bool + BreakMining = make(chan bool) +) + +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 getChainSize(pack *nt.Package) string { + return fmt.Sprintf("%d", Chain.Size()) +} + +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 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) + + defer func() { + chain.DB.Close() + }() + + 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 + } + + return +} + +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() +} + +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 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 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 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, + }) + } +} diff --git a/cmd/node/values.go b/cmd/node/values.go new file mode 100644 index 0000000..c06a897 --- /dev/null +++ b/cmd/node/values.go @@ -0,0 +1,60 @@ +package main + +import ( + bc "blockchain-from-scratch/blockchain" + "io/ioutil" +) + +var ( + Addresses []string + User *bc.User +) + +const ( + SEPARATOR = "_SEPARATOR_" +) + +const ( + ADD_BLOCK = iota + 1 + ADD_TRNSX + GET_BLOCK + GET_LHASH + GET_BLNCE + GET_CSIZE +) + +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 +} + +func writeFile(filename string, data string) error { + return ioutil.WriteFile(filename, []byte(data), 0644) +} + +func readFile(filename string) string { + data, err := ioutil.ReadFile(filename) + if err != nil { + return "" + } + return string(data) +}