diff --git a/Gopkg.lock b/Gopkg.lock index 1085b42..6eaa3b2 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -33,8 +33,8 @@ "sortkeys", "types" ] - revision = "1adfc126b41513cc696b209667c8656ea7aac67c" - version = "v1.0.0" + revision = "636bf0302bc95575d69441b25a2603156ffdddf1" + version = "v1.1.1" [[projects]] name = "github.com/golang/protobuf" @@ -61,23 +61,15 @@ version = "v0.8.0" [[projects]] - name = "github.com/tendermint/abci" + name = "github.com/tendermint/tendermint" packages = [ - "example/code", - "server", - "types" - ] - revision = "78a8905690ef54f9d57e3b2b0ee7ad3a04ef3f1f" - version = "v0.10.3" - -[[projects]] - name = "github.com/tendermint/tmlibs" - packages = [ - "common", - "log" + "abci/server", + "abci/types", + "libs/common", + "libs/log" ] - revision = "d970af87248a4e162590300dbb74e102183a417d" - version = "v0.8.3" + revision = "013b9cef642f875634c614019ab13b17570778ad" + version = "v0.23.0" [[projects]] branch = "master" @@ -86,10 +78,9 @@ "ed25519", "ed25519/internal/edwards25519" ] - revision = "1a580b3eff7814fc9b40602fd35256c63b50f491" + revision = "f027049dab0ad238e394a753dba2d14753473a04" [[projects]] - branch = "master" name = "golang.org/x/net" packages = [ "context", @@ -100,7 +91,7 @@ "internal/timeseries", "trace" ] - revision = "8e0cdda24ed423affc8f35c241e5e9b16180338e" + revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f" [[projects]] name = "golang.org/x/text" @@ -127,31 +118,39 @@ branch = "master" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] - revision = "11a468237815f3a3ddf9f7c6e8b6b3b382a24d15" + revision = "daca94659cb50e9f37c1b834680f2e46358f10b0" [[projects]] name = "google.golang.org/grpc" packages = [ ".", "balancer", + "balancer/base", + "balancer/roundrobin", "codes", "connectivity", "credentials", - "grpclb/grpc_lb_v1/messages", + "encoding", + "encoding/proto", "grpclog", "internal", + "internal/backoff", + "internal/channelz", + "internal/grpcrand", "keepalive", "metadata", "naming", "peer", "resolver", + "resolver/dns", + "resolver/passthrough", "stats", "status", "tap", "transport" ] - revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e" - version = "v1.7.5" + revision = "168a6198bcb0ef175f7dacec0b8691fc141dc9b8" + version = "v1.13.0" [[projects]] branch = "v2" @@ -163,11 +162,11 @@ "internal/sasl", "internal/scram" ] - revision = "3f83fa5005286a7fe593b055f0d7771a7dce4655" + revision = "9856a29383ce1c59f308dd1cf0363a79b5bef6b5" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "894cdc23200fd590b6580355e5ca4cd4ad2b29924bb73d8be704f7c509fe3a41" + inputs-digest = "6fb21f6f6b7b6ac14487967921d9e94da72b2a89b4fcdc4765b9f09b13cf53fb" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 4b7a5da..b1ce610 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -1,6 +1,6 @@ # Gopkg.toml example # -# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md # for detailed Gopkg.toml documentation. # # required = ["github.com/user/thing/cmd/thing"] @@ -26,12 +26,8 @@ [[constraint]] - name = "github.com/tendermint/abci" - version = "0.10.3" - -[[constraint]] - name = "github.com/tendermint/tmlibs" - version = "0.8.3" + name = "github.com/tendermint/tendermint" + version = "0.23.0" [[constraint]] branch = "master" diff --git a/jsonstore/jsonstore.go b/jsonstore/jsonstore.go index 3ce7baa..99b377c 100644 --- a/jsonstore/jsonstore.go +++ b/jsonstore/jsonstore.go @@ -15,12 +15,17 @@ import ( "mint/code" - "github.com/tendermint/abci/types" + "github.com/tendermint/tendermint/abci/types" "golang.org/x/crypto/ed25519" mgo "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson" ) +const ( + // TODO should come from config file + numActiveValidators = 4 +) + var _ types.Application = (*JSONStoreApplication)(nil) var db *mgo.Database @@ -74,6 +79,31 @@ type UserCommentVote struct { CommentID bson.ObjectId `bson:"commentID" json:"commentID"` } +// Validator +type Validator struct { + ID []byte `bson:"_id" json:"_id"` + Name string `bson:"name" json:"name"` + PubKey types.PubKey `bson:"pubKey" json:"pubKey"` + Upvotes int `bson:"upvotes" json:"upvotes"` + Power int64 `bson:"power" json:"power"` +} + +func (v Validator) ToTDValidator() types.Validator { + return types.Validator{ + PubKey: v.PubKey, + Power: v.Power, + } +} + +// ValidatorsVotes +// TODO there should be an index ensured on ValidatorID, UserID and +// probs multikey index on ValidatorID and UserID +type UserValidatorVote struct { + ID bson.ObjectId `bson:"_id" json:"_id"` + ValidatorID bson.ObjectId `bson:"validatorID" json:"validatorID"` + UserID bson.ObjectId `bson:"userID" json:"userID"` +} + // JSONStoreApplication ... type JSONStoreApplication struct { types.BaseApplication @@ -88,7 +118,7 @@ func byteToHex(input []byte) string { } func findTotalDocuments(db *mgo.Database) int64 { - collections := [5]string{"posts", "comments", "users", "userpostvotes", "usercommentvotes"} + collections := [6]string{"posts", "comments", "users", "userpostvotes", "usercommentvotes", "validators"} var sum int64 for _, collection := range collections { @@ -202,6 +232,7 @@ func (app *JSONStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx { panic(dbErr) } + // TODO is that really needed? it's being created on upvotePost too. var document UserPostVote document.ID = bson.NewObjectId() document.UserID = user.ID @@ -235,6 +266,61 @@ func (app *JSONStoreApplication) DeliverTx(tx []byte) types.ResponseDeliverTx { } break + + case "upvoteValidator": + entity := body["entity"].(map[string]interface{}) + + // validate user exists + pubKeyBytes, errDecode := base64.StdEncoding.DecodeString(message["publicKey"].(string)) + + if errDecode != nil { + panic(errDecode) + } + + publicKey := strings.ToUpper(byteToHex(pubKeyBytes)) + + var user User + err := db.C("users").Find(bson.M{"publicKey": publicKey}).One(&user) + if err != nil { + panic(err) + } + fmt.Println("user validated!") + + userID := user.ID + validatorID := bson.ObjectIdHex(entity["validator"].(string)) + + // validate validator exists + _, err = db.C("validators").Find(bson.M{"_id": validatorID}).Limit(1).Count() + if err != nil { + panic(err) + } + + userValidatorVote := UserValidatorVote{} + var upvote int8 + err = db.C("uservalidatorvotes").Find(bson.M{"userID": userID, "validatorID": validatorID}).One(&userValidatorVote) + if err == nil { + errRemoval := db.C("uservalidatorvotes").Remove(bson.M{"userID": userID, "validatorID": validatorID}) + if errRemoval == nil { + upvote = -1 + } + } else { + var newUserValidatorVote UserValidatorVote + newUserValidatorVote.ID = bson.NewObjectId() + newUserValidatorVote.UserID = userID + newUserValidatorVote.ValidatorID = validatorID + + insertErr := db.C("uservalidatorvotes").Insert(newUserValidatorVote) + if insertErr == nil { + upvote = 1 + } + } + err = db.C("validators").Update(bson.M{"_id": validatorID}, bson.M{"$inc": bson.M{"upvotes": upvote}}) + if err != nil { + // TODO should be properly logged + fmt.Sprintf("Failed to update votes for validator %s by user %s", validatorID, userID) + } + fmt.Println("validator validated!") + case "upvotePost": entity := body["entity"].(map[string]interface{}) @@ -446,6 +532,24 @@ func (app *JSONStoreApplication) CheckTx(tx []byte) types.ResponseCheckTx { // ===== Data Validation ======= switch body["type"] { + // TODO consider doing more sophisticated validations here like + // checking existance of users and validators here + // assuming tx cannot be tempered with beteen CheckTx and DeliverTx + case "upvoteValidator": + fmt.Println("received upvote start") + entity := body["entity"].(map[string]interface{}) + + if (entity["validator"] == nil) || (bson.IsObjectIdHex(entity["validator"].(string)) != true) { + codeType = code.CodeTypeBadData + break + } + fmt.Println("received upvote here") + if (entity["user"] == nil) || (bson.IsObjectIdHex(entity["user"].(string)) != true) { + codeType = code.CodeTypeBadData + break + } + fmt.Println("received upvote end") + case "createPost": entity := body["entity"].(map[string]interface{}) @@ -524,3 +628,55 @@ func (app *JSONStoreApplication) Commit() types.ResponseCommit { func (app *JSONStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) { return } + +func (app *JSONStoreApplication) InitChain(req types.RequestInitChain) types.ResponseInitChain { + fmt.Println("calling InitChain") + // TODO only used for testing atm!!! + addValidators(req.GetValidators()) + return types.ResponseInitChain{} +} + +func addValidators(validators []types.Validator) { + var mintValidators []interface{} + if validators != nil { + for _, element := range validators { + validator := Validator{ + ID: element.GetAddress(), + Name: "mintValidator", + PubKey: element.GetPubKey(), + Power: element.GetPower(), + Upvotes: 0, + } + mintValidators = append(mintValidators, validator) + } + dbErr := db.C("validators").Insert(mintValidators...) + if dbErr != nil { + panic(dbErr) + } + } +} + +func (app *JSONStoreApplication) EndBlock(req types.RequestEndBlock) types.ResponseEndBlock { + // TODO below solution needs confirmation through testing on multiple nodes. + // according to documentation "To add a new validator or update an existing one, + // simply include them in the list returned in the EndBlock response. + // To remove one, include it in the list with a power equal to 0." + // That means below solution may not be removing any validators atm! + + var validators []Validator + + err := db.C("validators").Find(nil).Sort("-upvotes").Limit(numActiveValidators).All(&validators) + if err != nil { + panic(err) + } + + var tdValidators []types.Validator + + for _, validator := range validators { + tdValidator := validator.ToTDValidator() + tdValidators = append(tdValidators, tdValidator) + } + fmt.Println(tdValidators) + + return types.ResponseEndBlock{ValidatorUpdates: tdValidators} +} diff --git a/mint.go b/mint.go index 115fdf8..1c91531 100644 --- a/mint.go +++ b/mint.go @@ -1,15 +1,24 @@ package main import ( + "fmt" "mint/jsonstore" "os" - "github.com/tendermint/abci/server" - "github.com/tendermint/abci/types" + "github.com/tendermint/tendermint/abci/server" + "github.com/tendermint/tendermint/abci/types" mgo "gopkg.in/mgo.v2" - cmn "github.com/tendermint/tmlibs/common" - "github.com/tendermint/tmlibs/log" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" +) + +const ( + defaultMongoHost = "localhost" +) + +const ( + defaultMongoHost = "localhost" ) func main() { @@ -22,14 +31,18 @@ func initJSONStore() error { // Create the application var app types.Application - session, err := mgo.Dial("localhost") + mongoHost := os.Args[2] + if mongoHost == "" { + mongoHost = defaultMongoHost + } + session, err := mgo.Dial(mongoHost) if err != nil { panic(err) } db := session.DB("tendermintdb") // Clean the DB on each reboot - collections := [5]string{"posts", "comments", "users", "userpostvotes", "usercommentvotes"} + collections := [7]string{"posts", "comments", "users", "userpostvotes", "usercommentvotes", "validators", "uservalidatorvotes"} for _, collection := range collections { db.C(collection).RemoveAll(nil) @@ -38,7 +51,8 @@ func initJSONStore() error { app = jsonstore.NewJSONStoreApplication(db) // Start the listener - srv, err := server.NewServer("tcp://0.0.0.0:46658", "socket", app) + proxyAppPort := os.Args[1] + srv, err := server.NewServer(fmt.Sprintf("tcp://0.0.0.0:%s", proxyAppPort), "socket", app) if err != nil { return err } diff --git a/testing/README.md b/testing/README.md new file mode 100644 index 0000000..5babdce --- /dev/null +++ b/testing/README.md @@ -0,0 +1,25 @@ + +Run: + +docker run -d -p 27016:27017 -v ~/temp/mongo1:/data/db mongo +docker run -d -p 27015:27017 -v ~/temp/mongo2:/data/db mongo +docker run -d -p 27014:27017 -v ~/temp/mongo3:/data/db mongo +docker run -d -p 27013:27017 -v ~/temp/mongo4:/data/db mongo + +Run: +- tendermint testnet --starting-ip-address 192.168.0.1 +- get node_ids for each node: + example: tendermint --home mytestnet/node3/ show_node_id +- update SEEDS variable in launch_testnet_nodes.sh with appropriate node_ids +- run ./launch_testnet_nodes.sh + +Reset: +1. either wipe mytestnet folder and redo above steps +2. or do the following: +- go to each node config folder (example /mytestnet/node0) +- remove data folder, addressbook.json +- update priv_validator.json by replaing removing all fields starting with "last_*" and +adding below instead: +"last_height": 0, +"last_round": 0, +"last_step": 0, diff --git a/testing/launch_testnet_nodes.sh b/testing/launch_testnet_nodes.sh new file mode 100755 index 0000000..5076049 --- /dev/null +++ b/testing/launch_testnet_nodes.sh @@ -0,0 +1,69 @@ +# Launch multiple bash terminal tab windows by running the command `bash launch.sh` + +#!/bin/bash +# File: ~/launch.sh + +# Original Code Reference: http://dan.doezema.com/2013/04/programmatically-create-title-tabs-within-the-mac-os-x-terminal-app/ +# New-BSD License by Original Author Daniel Doezema http://dan.doezema.com/licenses/new-bsd/ + +# Modified by Luke Schoen in 2017 to include loading new tabs such as client and server and automatically open webpage in browser. +# Modified by Luke Schoen in 2018 to include launching multiple tabs for Tendermint Testnet Nodes + +function new_tab() { + TAB_NAME=$1 + DELAY=$2 + COMMAND=$3 + osascript \ + -e "tell application \"Terminal\"" \ + -e "tell application \"System Events\" to keystroke \"t\" using {command down}" \ + -e "do script \"$DELAY; printf '\\\e]1;$TAB_NAME\\\a'; $COMMAND\" in front window" \ + -e "end tell" > /dev/null +} + +IP=0.0.0.0 +AA=tcp://$IP +# SEEDS="" +SEEDS="72f768fd9e76271c43d05ce7fb32c367ac645f68@$IP:46656,5afaaa9876b8dc0628e244cb59163472ea8e0123@$IP:46666,1e467deae3d559bfc747dc9acd14a40dc00ef933@$IP:46676,47cf808a11961ac3797958042d81874d3ede9892@$IP:46686" +TESTNET_ROOT_DIR="~/go-workspace/src/mint/testing/" +TESTNET_FOLDER="mytestnet" +NODE_0_NAME="node0" +NODE_1_NAME="node1" +NODE_2_NAME="node2" +NODE_3_NAME="node3" +echo "Removing Previous Tendermint Testnet Files: $TESTNET_ROOT_DIR/$TESTNET_FOLDER" +rm -rf "$TESTNET_ROOT_DIR/$TESTNET_FOLDER" +echo "Tendermint Testnet Location: $TESTNET_ROOT_DIR/$TESTNET_FOLDER" +echo "Loading Nodes: $NODE_0_NAME, $NODE_1_NAME, $NODE_2_NAME, $NODE_3_NAME" +echo "Loading Seeds: $SEEDS" + +new_tab "node_0" \ + "echo 'Loading node_0...'" \ + "bash -c 'echo node_0; tendermint node --home "$TESTNET_ROOT_DIR/$TESTNET_FOLDER/$NODE_0_NAME" --rpc.laddr="$AA:46657" --p2p.laddr="$AA:46656" --p2p.seeds=$SEEDS --proxy_app="tcp://127.0.0.1:46658" --p2p.persistent_peers=""; exec $SHELL'" + +new_tab "mint_0" \ + "echo 'Loading mint_0...'" \ + "bash -c 'echo mint_0; cd ~/go-workspace/src/mint; ./mint 46658 'localhost:27013'; exec $SHELL'" + +new_tab "node_1" \ + "echo 'Loading node_1...'" \ + "bash -c 'echo node_1; tendermint node --home "$TESTNET_ROOT_DIR/$TESTNET_FOLDER/$NODE_1_NAME" --rpc.laddr="$AA:46667" --p2p.laddr="$AA:46666" --p2p.seeds=$SEEDS --proxy_app="tcp://127.0.0.1:46668" --p2p.persistent_peers=""; exec $SHELL'" + +new_tab "mint_1" \ + "echo 'Loading mint_1...'" \ + "bash -c 'echo mint_1; cd ~/go-workspace/src/mint; ./mint 46668 'localhost:27014'; exec $SHELL'" + +new_tab "node_2" \ + "echo 'Loading node_2...'" \ + "bash -c 'echo node_2; tendermint node --home "$TESTNET_ROOT_DIR/$TESTNET_FOLDER/$NODE_2_NAME" --rpc.laddr="$AA:46677" --p2p.laddr="$AA:46676" --p2p.seeds=$SEEDS --proxy_app="tcp://127.0.0.1:46678" --p2p.persistent_peers=""; exec $SHELL'" + +new_tab "mint_2" \ + "echo 'Loading mint_2...'" \ + "bash -c 'echo mint_2; cd ~/go-workspace/src/mint; ./mint 46678 'localhost:27015'; exec $SHELL'" + +new_tab "node_3" \ + "echo 'Loading node_3...'" \ + "bash -c 'echo node_3; tendermint node --home "$TESTNET_ROOT_DIR/$TESTNET_FOLDER/$NODE_3_NAME" --rpc.laddr="$AA:46687" --p2p.laddr="$AA:46686" --p2p.seeds=$SEEDS --proxy_app="tcp://127.0.0.1:46688" --p2p.persistent_peers=""; exec $SHELL'" + +new_tab "mint_3" \ + "echo 'Loading mint_3...'" \ + "bash -c 'echo mint_3; cd ~/go-workspace/src/mint; ./mint 46688 'localhost:27016'; exec $SHELL'"