From 1f4e0b7fedb62730d1b5ddbc43c8ec19eb1562eb Mon Sep 17 00:00:00 2001 From: Ziyan Zhou Date: Thu, 9 Mar 2017 18:46:26 +0800 Subject: [PATCH] Add a command line tool nodectl 1. provide a command 'nodectl' 2. provide a local RPC server Signed-off-by: Ziyan Zhou --- Makefile | 2 + common/common.go | 5 +- common/debug.go | 19 +- common/log/log.go | 22 ++- config/config.go | 10 +- config/protocol.json | 4 +- main.go | 10 +- net/httpjsonrpc/RPCserver.go | 28 +++ net/httpjsonrpc/client.go | 64 ------- net/httpjsonrpc/common.go | 203 ++++++++++++++++++++ net/httpjsonrpc/interfaces.go | 190 +++++++++++++++++++ net/httpjsonrpc/localServer.go | 34 ++++ net/httpjsonrpc/server.go | 332 --------------------------------- net/net.go | 7 +- net/node/node.go | 33 ++-- net/protocol/protocol.go | 20 +- nodectl.go | 22 +++ utility/common.go | 123 ++++++++++++ utility/consensus/consensus.go | 53 ++++++ utility/info/info.go | 95 ++++++++++ utility/test/test.go | 17 ++ 21 files changed, 838 insertions(+), 455 deletions(-) create mode 100644 net/httpjsonrpc/RPCserver.go delete mode 100644 net/httpjsonrpc/client.go create mode 100644 net/httpjsonrpc/common.go create mode 100644 net/httpjsonrpc/interfaces.go create mode 100644 net/httpjsonrpc/localServer.go delete mode 100644 net/httpjsonrpc/server.go create mode 100644 nodectl.go create mode 100644 utility/common.go create mode 100644 utility/consensus/consensus.go create mode 100644 utility/info/info.go create mode 100644 utility/test/test.go diff --git a/Makefile b/Makefile index a6b47ced..44f25191 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ BUILD_PAR = -ldflags "-X main.Version=$(VERSION)" all: $(GC) $(BUILD_PAR) main.go + $(GC) $(BUILD_PAR) nodectl.go + format: $(GOFMT) -w main.go diff --git a/common/common.go b/common/common.go index d7ef1f42..61d698f7 100644 --- a/common/common.go +++ b/common/common.go @@ -9,7 +9,7 @@ import ( "golang.org/x/crypto/ripemd160" "crypto/sha256" . "GoOnchain/errors" - "GoOnchain/common/log" + //"GoOnchain/common/log" "errors" "io" ) @@ -32,7 +32,8 @@ func GetNonce() uint64 { Trace() // Fixme replace with the real random number generator nonce := uint64(rand.Uint32())<<32 + uint64(rand.Uint32()) - log.Debug(fmt.Sprintf("The new nonce is: 0x%x", nonce)) + Trace() + fmt.Println(fmt.Sprintf("The new nonce is: 0x%x", nonce)) return nonce } diff --git a/common/debug.go b/common/debug.go index 02dc903c..7eab7d7e 100644 --- a/common/debug.go +++ b/common/debug.go @@ -3,25 +3,24 @@ package common import ( "bytes" "fmt" + "path/filepath" "runtime" "strconv" "time" - "path/filepath" ) -func getGID() uint64 { - b := make([]byte, 64) - b = b[:runtime.Stack(b, false)] - b = bytes.TrimPrefix(b, []byte("goroutine ")) - b = b[:bytes.IndexByte(b, ' ')] - n, _ := strconv.ParseUint(string(b), 10, 64) - return n +func GetGID() uint64 { + b := make([]byte, 64) + b = b[:runtime.Stack(b, false)] + b = bytes.TrimPrefix(b, []byte("goroutine ")) + b = b[:bytes.IndexByte(b, ' ')] + n, _ := strconv.ParseUint(string(b), 10, 64) + return n } - func Trace() { t := time.Now().Format("15:04:05.000000") - id := getGID() + id := GetGID() pc := make([]uintptr, 10) runtime.Callers(2, pc) f := runtime.FuncForPC(pc[0]) diff --git a/common/log/log.go b/common/log/log.go index 9a8ba8ed..8cfbf484 100755 --- a/common/log/log.go +++ b/common/log/log.go @@ -1,6 +1,8 @@ package log import ( + "GoOnchain/common" + "GoOnchain/config" "bytes" "fmt" "io" @@ -27,11 +29,11 @@ const ( var ( levels = map[int]string{ - debugLog: "DEBUG", - infoLog: "INFO", - warnLog: "WARN", - errorLog: "ERROR", - fatalLog: "FATAL", + debugLog: "DEBUG", + infoLog: "INFO", + warnLog: "WARN", + errorLog: "ERROR", + fatalLog: "FATAL", } ) @@ -84,7 +86,13 @@ func New(out io.Writer, prefix string, flag, level int) *Logger { } func (l *Logger) output(level int, s string) error { - return l.logger.Output(callDepth, AddBracket(LevelName(level))+" "+s) + if (level == 0) || (level == 3) { + gid := common.GetGID() + gidStr := strconv.FormatUint(gid, 10) + return l.logger.Output(callDepth, AddBracket(LevelName(level))+" "+"GID"+" "+gidStr+", "+s) + } else { + return l.logger.Output(callDepth, AddBracket(LevelName(level))+" "+s) + } } func (l *Logger) Output(level int, a ...interface{}) error { @@ -175,7 +183,7 @@ func CreatePrintLog(path string) { if err != nil { fmt.Printf("%s\n", err.Error) } - var printlevel int = PRINTLEVEL + var printlevel int = config.Parameters.PrintLevel writers := []io.Writer{ logfile, os.Stdout, diff --git a/config/config.go b/config/config.go index f1f2c813..eafbea11 100644 --- a/config/config.go +++ b/config/config.go @@ -17,10 +17,12 @@ type ProtocolConfiguration struct { CoinVersion int `json:"CoinVersion"` StandbyMiners []string `json:"StandbyMiners"` SeedList []string `json:"SeedList"` - HttpJsonPort int `json:"HttpJsonPort"` - NodePort int `json:"NodePort"` - WebSocketPort int `json:"WebSocketPort"` - MinerName string `json:"MinerName"` + HttpJsonPort int `json:"HttpJsonPort"` + HttpLocalPort int `json:"HttpLocalPort"` + NodePort int `json:"NodePort"` + WebSocketPort int `json:"WebSocketPort"` + MinerName string `json:"MinerName"` + PrintLevel int `json:"PrintLevel"` } type ProtocolFile struct { diff --git a/config/protocol.json b/config/protocol.json index 925b6635..5d591be5 100644 --- a/config/protocol.json +++ b/config/protocol.json @@ -15,7 +15,9 @@ "192.168.33.11:20338" ], "HttpJsonPort": 20337, + "HttpLocalPort": 20336, "NodePort": 20338, - "MinerName": "c4" + "MinerName": "c4", + "PrintLevel": 0 } } diff --git a/main.go b/main.go index ce76349f..1d4fba50 100644 --- a/main.go +++ b/main.go @@ -57,6 +57,7 @@ func main() { fmt.Println("Can't get local client.") os.Exit(1) } + issuer, err := localclient.GetDefaultAccount() if err != nil { fmt.Println(err) @@ -81,22 +82,23 @@ func main() { ledger.DefaultLedger.Blockchain = &sampleBlockchain time.Sleep(2 * time.Second) - neter := net.StartProtocol() - - + neter, noder := net.StartProtocol() + httpjsonrpc.RegistRpcNode(noder) time.Sleep(20 * time.Second) fmt.Println("//**************************************************************************") fmt.Println("//*** 5. Start DBFT Services ***") fmt.Println("//**************************************************************************") dbftServices := dbft.NewDbftService(localclient, "logdbft", neter) + httpjsonrpc.RegistDbftService(dbftServices) go dbftServices.Start() time.Sleep(5 * time.Second) fmt.Println("DBFT Services start completed.") fmt.Println("//**************************************************************************") fmt.Println("//*** Init Complete ***") fmt.Println("//**************************************************************************") - go httpjsonrpc.StartServer() + go httpjsonrpc.StartRPCServer() + go httpjsonrpc.StartLocalServer() time.Sleep(2 * time.Second) // if config.Parameters.MinerName == "c4" { diff --git a/net/httpjsonrpc/RPCserver.go b/net/httpjsonrpc/RPCserver.go new file mode 100644 index 00000000..c62c19b9 --- /dev/null +++ b/net/httpjsonrpc/RPCserver.go @@ -0,0 +1,28 @@ +package httpjsonrpc + +import ( + . "GoOnchain/common" + . "GoOnchain/config" + "log" + "net/http" + "strconv" +) + +func StartRPCServer() { + Trace() + http.HandleFunc("/", Handle) + + HandleFunc("getbestblockhash", getBestBlockHash) + HandleFunc("getblock", getBlock) + HandleFunc("getblockcount", getBlockCount) + HandleFunc("getblockhash", getBlockHash) + HandleFunc("getconnectioncount", getConnectionCount) + HandleFunc("getrawmempool", getRawMemPool) + HandleFunc("getrawtransaction", getRawTransaction) + HandleFunc("submitblock", submitBlock) + + err := http.ListenAndServe(":"+strconv.Itoa(Parameters.HttpJsonPort), nil) + if err != nil { + log.Fatal("ListenAndServe: ", err.Error()) + } +} diff --git a/net/httpjsonrpc/client.go b/net/httpjsonrpc/client.go deleted file mode 100644 index 44876bba..00000000 --- a/net/httpjsonrpc/client.go +++ /dev/null @@ -1,64 +0,0 @@ -package httpjsonrpc - -import ( - "encoding/json" - "io/ioutil" - "log" - "net/http" - "strings" - "GoOnchain/common" -) - -func Call(address string, method string, id interface{}, params []interface{}) (map[string]interface{}, error) { - data, err := json.Marshal(map[string]interface{}{ - "method": method, - "id": id, - "params": params, - }) - if err != nil { - log.Fatalf("Marshal: %v", err) - return nil, err - } - resp, err := http.Post(address, "application/json", strings.NewReader(string(data))) - - if err != nil { - log.Fatalf("Post: %v", err) - return nil, err - } - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Fatalf("ReadAll: %v", err) - return nil, err - } - result := make(map[string]interface{}) - err = json.Unmarshal(body, &result) - if err != nil { - log.Fatalf("Unmarshal: %v", err) - return nil, err - } - //log.Println(result) - return result, nil -} - -func StartClient() { - var res map[string]interface{} - var err error - - common.Trace() - - // Call the get info - res, err = Call("http://127.0.0.1:20337", "getinfo", 1, []interface{}{}) - if err != nil { - log.Fatalf("Err: %v", err) - } - log.Println(res) - - // call send to address - params := []interface{}{"asset_id", "address", 56} - res, err = Call("http://127.0.0.1:20337", "sendtoaddress", 2, params) - if err != nil { - log.Fatalf("Err: %v", err) - } - log.Println(res) -} diff --git a/net/httpjsonrpc/common.go b/net/httpjsonrpc/common.go new file mode 100644 index 00000000..5b34ddc6 --- /dev/null +++ b/net/httpjsonrpc/common.go @@ -0,0 +1,203 @@ +package httpjsonrpc + +import ( + "GoOnchain/consensus/dbft" + "GoOnchain/core/ledger" + tx "GoOnchain/core/transaction" + . "GoOnchain/net/protocol" + "encoding/json" + "io/ioutil" + "log" + "net/http" + "strings" +) + +func init() { + mainMux.m = make(map[string]func(*http.Request, map[string]interface{}) map[string]interface{}) +} + +//an instance of the multiplexer +var mainMux ServeMux +var node Noder +var dBFT *dbft.DbftService + +//multiplexer that keeps track of every function to be called on specific rpc call +type ServeMux struct { + m map[string]func(*http.Request, map[string]interface{}) map[string]interface{} + defaultFunction func(http.ResponseWriter, *http.Request) +} + +type BlockInfo struct { + Hash string + Block *ledger.Block +} + +type TxInfo struct { + Hash string + Hex string + Tx *tx.Transaction +} + +type TxoutInfo struct { + High uint32 + Low uint32 + Txout tx.TxOutput +} + +type NodeInfo struct { + State uint // node status + Port uint16 // The nodes's port + ID uint64 // The nodes's id + Time int64 + Version uint32 // The network protocol the node used + Services uint64 // The services the node supplied + Relay bool // The relay capability of the node (merge into capbility flag) + Height uint64 // The node latest block height +} + +type ConsensusInfo struct { + // TODO +} + +func RegistRpcNode(n Noder) { + if node == nil { + node = n + } +} + +func RegistDbftService(d *dbft.DbftService) { + if dBFT == nil { + dBFT = d + } +} + +//a function to register functions to be called for specific rpc calls +func HandleFunc(pattern string, handler func(*http.Request, map[string]interface{}) map[string]interface{}) { + mainMux.m[pattern] = handler +} + +//a function to be called if the request is not a HTTP JSON RPC call +func SetDefaultFunc(def func(http.ResponseWriter, *http.Request)) { + mainMux.defaultFunction = def +} + +//this is the funciton that should be called in order to answer an rpc call +//should be registered like "http.HandleFunc("/", httpjsonrpc.Handle)" +func Handle(w http.ResponseWriter, r *http.Request) { + //JSON RPC commands should be POSTs + if r.Method != "POST" { + if mainMux.defaultFunction != nil { + log.Printf("HTTP JSON RPC Handle - Method!=\"POST\"") + mainMux.defaultFunction(w, r) + return + } else { + log.Panicf("HTTP JSON RPC Handle - Method!=\"POST\"") + return + } + } + + //We must check if there is Request Body to read + if r.Body == nil { + if mainMux.defaultFunction != nil { + log.Printf("HTTP JSON RPC Handle - Request body is nil") + mainMux.defaultFunction(w, r) + return + } else { + log.Panicf("HTTP JSON RPC Handle - Request body is nil") + return + } + } + + //read the body of the request + body, err := ioutil.ReadAll(r.Body) + //log.Println(r) + //log.Println(body) + if err != nil { + log.Fatalf("HTTP JSON RPC Handle - ioutil.ReadAll: %v", err) + return + } + request := make(map[string]interface{}) + //unmarshal the request + err = json.Unmarshal(body, &request) + if err != nil { + log.Fatalf("HTTP JSON RPC Handle - json.Unmarshal: %v", err) + return + } + //log.Println(request["method"]) + + //get the corresponding function + function, ok := mainMux.m[request["method"].(string)] + + if ok { + response := function(r, request) + //response from the program is encoded + data, err := json.Marshal(response) + if err != nil { + log.Fatalf("HTTP JSON RPC Handle - json.Marshal: %v", err) + return + } + //result is printed to the output + w.Write(data) + } else { //if the function does not exist + log.Println("HTTP JSON RPC Handle - No function to call for", request["method"]) + + //if you don't want to send an error, send something else: + // data, err := json.Marshal(map[string]interface{}{ + // "result": "OK!", + // "error": nil, + // "id": request["id"], + // }) + + //an error json is created + data, err := json.Marshal(map[string]interface{}{ + "result": nil, + "error": map[string]interface{}{ + "code": -32601, + "message": "Method not found", + "data": "The called method was not found on the server", + }, + "id": request["id"], + }) + if err != nil { + log.Fatalf("HTTP JSON RPC Handle - json.Marshal: %v", err) + return + } + w.Write(data) + } +} + +func responsePacking(result interface{}, id interface{}) map[string]interface{} { + resp := map[string]interface{}{ + "Jsonrpc": "2.0", + "Result": result, + "Id": id, + } + return resp +} + +// Call sends RPC request to server +func Call(address string, method string, id interface{}, params []interface{}) ([]byte, error) { + data, err := json.Marshal(map[string]interface{}{ + "method": method, + "id": id, + "params": params, + }) + if err != nil { + log.Fatalf("Marshal: %v", err) + return nil, err + } + resp, err := http.Post(address, "application/json", strings.NewReader(string(data))) + if err != nil { + log.Fatalf("Post: %v", err) + return nil, err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatalf("ReadAll: %v", err) + return nil, err + } + + return body, nil +} diff --git a/net/httpjsonrpc/interfaces.go b/net/httpjsonrpc/interfaces.go new file mode 100644 index 00000000..4fef465e --- /dev/null +++ b/net/httpjsonrpc/interfaces.go @@ -0,0 +1,190 @@ +package httpjsonrpc + +import ( + . "GoOnchain/common" + "GoOnchain/core/ledger" + tx "GoOnchain/core/transaction" + "bytes" + "encoding/hex" + "fmt" + "net/http" +) + +func getBestBlockHash(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + id := cmd["id"] + hash := ledger.DefaultLedger.Blockchain.CurrentBlockHash() + response := responsePacking(ToHexString(hash.ToArray()), id) + return response +} + +func getBlock(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + id := cmd["id"] + params := cmd["params"] + var block *ledger.Block + var err error + var b BlockInfo + switch (params.([]interface{})[0]).(type) { + case int: + index := params.([]interface{})[0].(uint32) + hash, _ := ledger.DefaultLedger.Store.GetBlockHash(index) + block, err = ledger.DefaultLedger.Store.GetBlock(hash) + b = BlockInfo{ + Hash: ToHexString(hash.ToArray()), + Block: block, + } + case string: + hash := params.([]interface{})[0].(string) + hashslice, _ := hex.DecodeString(hash) + var hasharr Uint256 + hasharr.Deserialize(bytes.NewReader(hashslice[0:32])) + block, err = ledger.DefaultLedger.Store.GetBlock(hasharr) + b = BlockInfo{ + Hash: hash, + Block: block, + } + } + + if err != nil { + var erro []interface{} = []interface{}{-100, "Unknown block"} + response := responsePacking(erro, id) + return response + } + + return responsePacking(b, id) +} + +func getBlockCount(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + id := cmd["id"] + count := ledger.DefaultLedger.Blockchain.BlockHeight + 1 + return responsePacking(count, id) +} + +func getBlockHash(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + id := cmd["id"] + index := cmd["params"] + var hash Uint256 + height, ok := index.(uint32) + if ok == true { + hash, _ = ledger.DefaultLedger.Store.GetBlockHash(height) + } + hashhex := fmt.Sprintf("%016x", hash) + return responsePacking(hashhex, id) +} + +func getConnectionCount(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + id := cmd["id"] + count := node.GetConnectionCnt() + return responsePacking(count, id) +} + +func getRawMemPool(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + id := cmd["id"] + mempoollist := node.GetTxnPool() + return responsePacking(mempoollist, id) +} + +func getRawTransaction(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + id := cmd["id"] + params := cmd["params"] + txid := params.([]interface{})[0].(string) + txidSlice, _ := hex.DecodeString(txid) + var txidArr Uint256 + txidArr.Deserialize(bytes.NewReader(txidSlice[0:32])) + verbose := params.([]interface{})[1].(bool) + tx := node.GetTransaction(txidArr) + txBuffer := bytes.NewBuffer([]byte{}) + tx.Serialize(txBuffer) + if verbose == true { + t := TxInfo{ + Hash: txid, + Hex: hex.EncodeToString(txBuffer.Bytes()), + Tx: tx, + } + response := responsePacking(t, id) + return response + } + + return responsePacking(txBuffer.Bytes(), id) +} + +func getTxout(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + id := cmd["id"] + //params := cmd["params"] + //txid := params.([]interface{})[0].(string) + //var n int = params.([]interface{})[1].(int) + var txout tx.TxOutput // := tx.GetTxOut() //TODO + high := uint32(txout.Value >> 32) + low := uint32(txout.Value) + to := TxoutInfo{ + High: high, + Low: low, + Txout: txout, + } + return responsePacking(to, id) +} + +func sendRawTransaction(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + id := cmd["id"] + hexValue := cmd["params"].(string) + hexSlice, _ := hex.DecodeString(hexValue) + var txTransaction tx.Transaction + txTransaction.Deserialize(bytes.NewReader(hexSlice[:])) + err := node.Xmit(&txTransaction) + return responsePacking(err, id) +} + +func submitBlock(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + id := cmd["id"] + hexValue := cmd["params"].(string) + hexSlice, _ := hex.DecodeString(hexValue) + var txTransaction tx.Transaction + txTransaction.Deserialize(bytes.NewReader(hexSlice[:])) + err := node.Xmit(&txTransaction) + response := responsePacking(err, id) + return response +} + +func getNeighbor(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + id := cmd["id"] + addr, _ := node.GetNeighborAddrs() + return responsePacking(addr, id) +} + +func getNodeState(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + id := cmd["id"] + n := NodeInfo{ + State: node.GetState(), + Time: node.GetTime(), + Port: node.GetPort(), + ID: node.GetID(), + Version: node.Version(), + Services: node.Services(), + Relay: node.GetRelay(), + Height: node.GetHeight(), + } + return responsePacking(n, id) +} + +func startConsensus(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + var response map[string]interface{} + id := cmd["id"] + err := dBFT.Start() + if err != nil { + response = responsePacking("Failed to start", id) + } else { + response = responsePacking("Consensus Started", id) + } + return response +} + +func stopConsensus(req *http.Request, cmd map[string]interface{}) map[string]interface{} { + var response map[string]interface{} + id := cmd["id"] + err := dBFT.Halt() + if err != nil { + response = responsePacking("Failed to stop", id) + } else { + response = responsePacking("Consensus Stopped", id) + } + return response +} diff --git a/net/httpjsonrpc/localServer.go b/net/httpjsonrpc/localServer.go new file mode 100644 index 00000000..c0520063 --- /dev/null +++ b/net/httpjsonrpc/localServer.go @@ -0,0 +1,34 @@ +package httpjsonrpc + +import ( + . "GoOnchain/common" + . "GoOnchain/config" + "log" + "net/http" + "strconv" +) + +const ( + localHost string = "127.0.0.1" +) + +func StartLocalServer() { + Trace() + http.HandleFunc("/local", Handle) + + HandleFunc("getbestblockhash", getBestBlockHash) + HandleFunc("getblock", getBlock) + HandleFunc("getblockcount", getBlockCount) + HandleFunc("getblockhash", getBlockHash) + HandleFunc("getconnectioncount", getConnectionCount) + HandleFunc("getneighbor", getNeighbor) + HandleFunc("getnodestate", getNodeState) + HandleFunc("startconsensus", startConsensus) + HandleFunc("stopconsensus", stopConsensus) + + // TODO: only listen to local host + err := http.ListenAndServe(":"+strconv.Itoa(Parameters.HttpLocalPort), nil) + if err != nil { + log.Fatal("ListenAndServe: ", err.Error()) + } +} diff --git a/net/httpjsonrpc/server.go b/net/httpjsonrpc/server.go deleted file mode 100644 index 0db00f5a..00000000 --- a/net/httpjsonrpc/server.go +++ /dev/null @@ -1,332 +0,0 @@ -package httpjsonrpc - -import ( - . "GoOnchain/common" - . "GoOnchain/config" - "GoOnchain/core/ledger" - tx "GoOnchain/core/transaction" - . "GoOnchain/net/protocol" - "bytes" - "encoding/hex" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net/http" - "strconv" -) - -//multiplexer that keeps track of every function to be called on specific rpc call -type ServeMux struct { - m map[string]func(*http.Request, map[string]interface{}) map[string]interface{} - defaultFunction func(http.ResponseWriter, *http.Request) -} - -type BlockInfo struct { - Hash string - Block *ledger.Block -} - -type NoderInfo struct { - Noder JsonNoder -} - -type TxInfo struct { - Hash string - Hex string - Tx *tx.Transaction -} - -var nodeInfo NoderInfo - -//an instance of the multiplexer -var mainMux ServeMux - -func InitServeMux() { - mainMux.m = make(map[string]func(*http.Request, map[string]interface{}) map[string]interface{}) -} - -//a function to register functions to be called for specific rpc calls -func HandleFunc(pattern string, handler func(*http.Request, map[string]interface{}) map[string]interface{}) { - mainMux.m[pattern] = handler -} - -//a function to be called if the request is not a HTTP JSON RPC call -func SetDefaultFunc(def func(http.ResponseWriter, *http.Request)) { - mainMux.defaultFunction = def -} - -func InitNoderInfo(jsonNode JsonNoder) { - //TODO - //return NodeInfo - if nodeInfo.Noder == nil { - nodeInfo.Noder = jsonNode - } -} - -//this is the funciton that should be called in order to answer an rpc call -//should be registered like "http.HandleFunc("/", httpjsonrpc.Handle)" -func Handle(w http.ResponseWriter, r *http.Request) { - //JSON RPC commands should be POSTs - if r.Method != "POST" { - if mainMux.defaultFunction != nil { - log.Printf("HTTP JSON RPC Handle - Method!=\"POST\"") - mainMux.defaultFunction(w, r) - return - } else { - log.Panicf("HTTP JSON RPC Handle - Method!=\"POST\"") - return - } - } - - //We must check if there is Request Body to read - if r.Body == nil { - if mainMux.defaultFunction != nil { - log.Printf("HTTP JSON RPC Handle - Request body is nil") - mainMux.defaultFunction(w, r) - return - } else { - log.Panicf("HTTP JSON RPC Handle - Request body is nil") - return - } - } - - //read the body of the request - body, err := ioutil.ReadAll(r.Body) - //log.Println(r) - //log.Println(body) - if err != nil { - log.Fatalf("HTTP JSON RPC Handle - ioutil.ReadAll: %v", err) - return - } - request := make(map[string]interface{}) - //unmarshal the request - err = json.Unmarshal(body, &request) - if err != nil { - log.Fatalf("HTTP JSON RPC Handle - json.Unmarshal: %v", err) - return - } - //log.Println(request["method"]) - - //get the corresponding function - function, ok := mainMux.m[request["method"].(string)] - - if ok { - response := function(r, request) - //response from the program is encoded - data, err := json.Marshal(response) - if err != nil { - log.Fatalf("HTTP JSON RPC Handle - json.Marshal: %v", err) - return - } - //result is printed to the output - w.Write(data) - } else { //if the function does not exist - log.Println("HTTP JSON RPC Handle - No function to call for", request["method"]) - - //if you don't want to send an error, send something else: - // data, err := json.Marshal(map[string]interface{}{ - // "result": "OK!", - // "error": nil, - // "id": request["id"], - // }) - - //an error json is created - data, err := json.Marshal(map[string]interface{}{ - "result": nil, - "error": map[string]interface{}{ - "code": -32601, - "message": "Method not found", - "data": "The called method was not found on the server", - }, - "id": request["id"], - }) - if err != nil { - log.Fatalf("HTTP JSON RPC Handle - json.Marshal: %v", err) - return - } - w.Write(data) - } -} - -func responsePacking(result interface{}, id interface{}) map[string]interface{} { - resp := map[string]interface{}{ - "Jsonrpc": "2.0", - "Result": result, - "Id": id, - } - return resp -} - -func getBestBlock(req *http.Request, cmd map[string]interface{}) map[string]interface{} { - id := cmd["id"] - hash := ledger.DefaultLedger.Blockchain.CurrentBlockHash() - response := responsePacking(ToHexString(hash.ToArray()), id) - return response -} - -func getBlock(req *http.Request, cmd map[string]interface{}) map[string]interface{} { - id := cmd["id"] - params := cmd["params"] - var block *ledger.Block - var err error - var b BlockInfo - switch (params.([]interface{})[0]).(type) { - case int: - index := params.([]interface{})[0].(uint32) - hash, _ := ledger.DefaultLedger.Store.GetBlockHash(index) - block, err = ledger.DefaultLedger.Store.GetBlock(hash) - b = BlockInfo{ - Hash: ToHexString(hash.ToArray()), - Block: block, - } - case string: - hash := params.([]interface{})[0].(string) - hashslice, _ := hex.DecodeString(hash) - var hasharr Uint256 - hasharr.Deserialize(bytes.NewReader(hashslice[0:32])) - block, err = ledger.DefaultLedger.Store.GetBlock(hasharr) - b = BlockInfo{ - Hash: hash, - Block: block, - } - } - - if err != nil { - var erro []interface{} = []interface{}{-100, "Unknown block"} - response := responsePacking(erro, id) - return response - } - - raw, _ := json.Marshal(&b) - response := responsePacking(string(raw), id) - return response -} - -func getBlockCount(req *http.Request, cmd map[string]interface{}) map[string]interface{} { - id := cmd["id"] - count := ledger.DefaultLedger.Blockchain.BlockHeight + 1 - response := responsePacking(count, id) - return response -} - -func getBlockHash(req *http.Request, cmd map[string]interface{}) map[string]interface{} { - id := cmd["id"] - index := cmd["params"] - var hash Uint256 - height, ok := index.(uint32) - if ok == true { - hash, _ = ledger.DefaultLedger.Store.GetBlockHash(height) - } - hashhex := fmt.Sprintf("%016x", hash) - response := responsePacking(hashhex, id) - return response -} - -func getConnectionCount(req *http.Request, cmd map[string]interface{}) map[string]interface{} { - id := cmd["id"] - count := nodeInfo.Noder.GetConnectionCnt() - response := responsePacking(count, id) - return response -} - -func getRawMemPool(req *http.Request, cmd map[string]interface{}) map[string]interface{} { - id := cmd["id"] - mempoollist := nodeInfo.Noder.GetTxnPool() - raw, _ := json.Marshal(mempoollist) - response := responsePacking(string(raw), id) - return response -} - -func getRawTransaction(req *http.Request, cmd map[string]interface{}) map[string]interface{} { - id := cmd["id"] - params := cmd["params"] - txid := params.([]interface{})[0].(string) - txidSlice, _ := hex.DecodeString(txid) - var txidArr Uint256 - txidArr.Deserialize(bytes.NewReader(txidSlice[0:32])) - verbose := params.([]interface{})[1].(bool) - tx := nodeInfo.Noder.GetTransaction(txidArr) - txBuffer := bytes.NewBuffer([]byte{}) - tx.Serialize(txBuffer) - if verbose == true { - t := TxInfo{ - Hash: txid, - Hex: hex.EncodeToString(txBuffer.Bytes()), - Tx: tx, - } - raw, _ := json.Marshal(&t) - response := responsePacking(string(raw), id) - return response - } - - response := responsePacking(txBuffer.Bytes(), id) - return response -} - -type TxoutInfo struct { - High uint32 - Low uint32 - Txout tx.TxOutput -} - -func getTxout(req *http.Request, cmd map[string]interface{}) map[string]interface{} { - id := cmd["id"] - //params := cmd["params"] - //txid := params.([]interface{})[0].(string) - //var n int = params.([]interface{})[1].(int) - var txout tx.TxOutput // := tx.GetTxOut() //TODO - high := uint32(txout.Value >> 32) - low := uint32(txout.Value) - to := TxoutInfo{ - High: high, - Low: low, - Txout: txout, - } - raw, _ := json.Marshal(&to) - response := responsePacking(string(raw), id) - return response - -} - -func sendRawTransaction(req *http.Request, cmd map[string]interface{}) map[string]interface{} { - id := cmd["id"] - hexValue := cmd["params"].(string) - hexSlice, _ := hex.DecodeString(hexValue) - var txTransaction tx.Transaction - txTransaction.Deserialize(bytes.NewReader(hexSlice[:])) - err := nodeInfo.Noder.Xmit(&txTransaction) - response := responsePacking(err, id) - return response -} - -func submitBlock(req *http.Request, cmd map[string]interface{}) map[string]interface{} { - id := cmd["id"] - hexValue := cmd["params"].(string) - hexSlice, _ := hex.DecodeString(hexValue) - var txTransaction tx.Transaction - txTransaction.Deserialize(bytes.NewReader(hexSlice[:])) - err := nodeInfo.Noder.Xmit(&txTransaction) - response := responsePacking(err, id) - return response -} - -func StartServer() { - Trace() - InitServeMux() - http.HandleFunc("/", Handle) - - HandleFunc("getbestblock", getBestBlock) - HandleFunc("getblock", getBlock) - HandleFunc("getblockcount", getBlockCount) - HandleFunc("getblockhash", getBlockHash) - HandleFunc("getconnectioncount", getConnectionCount) - HandleFunc("getrawmempool", getRawMemPool) - HandleFunc("getrawtransaction", getRawTransaction) - HandleFunc("submitblock", submitBlock) - - err := http.ListenAndServe(":"+strconv.Itoa(Parameters.HttpJsonPort), nil) - if err != nil { - log.Fatal("ListenAndServe: ", err.Error()) - } -} diff --git a/net/net.go b/net/net.go index d7828085..bc6258a0 100644 --- a/net/net.go +++ b/net/net.go @@ -3,9 +3,10 @@ package net import ( "GoOnchain/common" "GoOnchain/config" - "GoOnchain/events" "GoOnchain/core/transaction" + "GoOnchain/events" "GoOnchain/net/node" + "GoOnchain/net/protocol" ) type Neter interface { @@ -15,12 +16,12 @@ type Neter interface { GetEvent(eventName string) *events.Event } -func StartProtocol() Neter { +func StartProtocol() (Neter, protocol.Noder) { seedNodes := config.Parameters.SeedList net := node.InitNode() for _, nodeAddr := range seedNodes { net.Connect(nodeAddr) } - return net + return net, net } diff --git a/net/node/node.go b/net/node/node.go index e05dfee0..47fed48a 100644 --- a/net/node/node.go +++ b/net/node/node.go @@ -24,22 +24,22 @@ const ( ) type node struct { - state uint // node status - id uint64 // The nodes's id - cap uint32 // The node capability set - version uint32 // The network protocol the node used - services uint64 // The services the node supplied - relay bool // The relay capability of the node (merge into capbility flag) - height uint64 // The node latest block height + state uint // node status + id uint64 // The nodes's id + cap uint32 // The node capability set + version uint32 // The network protocol the node used + services uint64 // The services the node supplied + relay bool // The relay capability of the node (merge into capbility flag) + height uint64 // The node latest block height // TODO does this channel should be a buffer channel - chF chan func() error // Channel used to operate the node without lock - link // The link status and infomation - local *node // The pointer to local node - nbrNodes // The neighbor node connect with currently node except itself - eventQueue // The event queue to notice notice other modules - TXNPool // Unconfirmed transaction pool - idCache // The buffer to store the id of the items which already be processed - ledger *ledger.Ledger // The Local ledger + chF chan func() error // Channel used to operate the node without lock + link // The link status and infomation + local *node // The pointer to local node + nbrNodes // The neighbor node connect with currently node except itself + eventQueue // The event queue to notice notice other modules + TXNPool // Unconfirmed transaction pool + idCache // The buffer to store the id of the items which already be processed + ledger *ledger.Ledger // The Local ledger } func (node node) DumpInfo() { @@ -84,7 +84,7 @@ func NewNode() *node { return &n } -func InitNode() Tmper { +func InitNode() Noder { var err error n := NewNode() @@ -108,6 +108,7 @@ func InitNode() Tmper { go n.initConnection() go n.updateNodeInfo() + return n } diff --git a/net/protocol/protocol.go b/net/protocol/protocol.go index 8f2578f8..3b4aafc4 100644 --- a/net/protocol/protocol.go +++ b/net/protocol/protocol.go @@ -17,7 +17,7 @@ type NodeAddr struct { Services uint64 IpAddr [16]byte Port uint16 - ID uint64 // Unique ID + ID uint64 // Unique ID } const ( @@ -41,15 +41,15 @@ const ( // The node state const ( - INIT = 0 - HANDSHAKE = 1 - ESTABLISH = 2 - INACTIVITY = 3 + INIT = 0 + HANDSHAKE = 1 + ESTABLISH = 2 + INACTIVITY = 3 ) type Noder interface { Version() uint32 - GetID() uint64 + GetID() uint64 Services() uint64 GetPort() uint16 GetState() uint @@ -76,14 +76,10 @@ type Noder interface { NodeEstablished(uid uint64) bool GetEvent(eventName string) *events.Event GetNeighborAddrs() ([]NodeAddr, uint64) -} - -type Tmper interface { + GetTransaction(hash common.Uint256) *transaction.Transaction + Xmit(common.Inventory) error GetMemoryPool() map[common.Uint256]*transaction.Transaction SynchronizeMemoryPool() - Xmit(common.Inventory) error // The transmit interface - GetEvent(eventName string) *events.Event - Connect(nodeAddr string) } type JsonNoder interface { diff --git a/nodectl.go b/nodectl.go new file mode 100644 index 00000000..aec43101 --- /dev/null +++ b/nodectl.go @@ -0,0 +1,22 @@ +package main + +import ( + "GoOnchain/utility" + "GoOnchain/utility/consensus" + "GoOnchain/utility/info" + "GoOnchain/utility/test" + "os" +) + +func main() { + cmds := map[string]*utility.Command{ + "info": info.Command, + "consensus": consensus.Command, + "test": test.Command, + } + + err := utility.Start(cmds) + if err != nil { + os.Exit(1) + } +} diff --git a/utility/common.go b/utility/common.go new file mode 100644 index 00000000..4c2e745a --- /dev/null +++ b/utility/common.go @@ -0,0 +1,123 @@ +package utility + +import ( + "bytes" + "encoding/json" + "errors" + "flag" + "fmt" + "os" +) + +var p Param +var flagSet = flag.NewFlagSet(os.Args[0], flag.ExitOnError) + +func init() { + registerFlags(flagSet) +} + +type Command struct { + UsageText string + Flags []string + Main func(args []string, p Param) error +} + +type Param struct { + Ip string // IP address + Port string // port number + BestBlockHash bool // hash of current block + Height int64 // block height + BlockHash string // block hash + BlockCount bool // block count + ConnectionCount bool // connected node number + Neighbor bool // neighbor nodes + Start bool // start service + Stop bool // stop service + NodeState bool // node state + RPCID int64 // RPC ID, use int64 by default +} + +func registerFlags(f *flag.FlagSet) { + f.StringVar(&p.Ip, "ip", "", "IP address") + f.StringVar(&p.Port, "port", "", "port number") + f.Int64Var(&p.RPCID, "rpcid", 0, "JSON-RPC ID") + f.BoolVar(&p.BestBlockHash, "bestblockhash", false, "hash of current block") + f.Int64Var(&p.Height, "height", -1, "height of blockchain") + f.StringVar(&p.BlockHash, "blockhash", "", "block hash") + f.BoolVar(&p.BlockCount, "blockcount", false, "block numbers") + f.BoolVar(&p.ConnectionCount, "connections", false, "connection counnt") + f.BoolVar(&p.Neighbor, "neighbor", false, "neighbor nodes information") + f.BoolVar(&p.NodeState, "state", false, "node state") + f.BoolVar(&p.Start, "start", false, "start service") + f.BoolVar(&p.Stop, "stop", false, "stop service") +} + +func Start(cmds map[string]*Command) error { + // Automatically called when -h present + flag.Usage = func() { + fmt.Fprintln(os.Stderr, "Available Commands:") + for name := range cmds { + fmt.Fprintf(os.Stderr, " %-10s\t- %s\n", name, cmds[name].UsageText) + } + } + flag.Parse() + if flag.NArg() < 1 { + fmt.Fprintln(os.Stderr, "no subcommand is given\n") + flag.Usage() + return errors.New("no subcommand was given") + } + + subCmdName := flag.Arg(0) + subCmdArgs := flag.Args()[1:] + subCmd, found := cmds[subCmdName] + if !found { + fmt.Fprintf(os.Stderr, "subcommand %s is not defined\n\n", subCmdName) + flag.Usage() + return errors.New("undefined subcommand") + } + + // Automatically called when flagSet.Parse failed + flagSet.Usage = func() { + fmt.Fprintln(os.Stderr, "\nAvailable Flags:") + for _, name := range subCmd.Flags { + if f := flagSet.Lookup(name); f != nil { + fmt.Fprintf(os.Stderr, " -%-15s\t%s\n", f.Name, f.Usage) + } + } + } + flagSet.Parse(subCmdArgs) + if len(subCmdArgs) == 0 { + fmt.Fprintf(os.Stderr, "no flag is given\n") + flagSet.Usage() + return errors.New("no flag for subcommand is given") + } + + if err := subCmd.Main(subCmdArgs, p); err != nil { + return err + } + + return nil +} + +func Address(ip, port string) (string, error) { + if ip == "" || port == "" { + fmt.Fprintln(os.Stderr, "IP address or port number can't be blank") + return "", errors.New("Invaild IP address or port number") + } + + addr := "http://" + ip + ":" + port + + return addr, nil +} + +func FormatOutput(o []byte) error { + var out bytes.Buffer + err := json.Indent(&out, o, "", "\t") + if err != nil { + return err + } + out.Write([]byte("\n")) + _, err = out.WriteTo(os.Stdout) + + return err +} diff --git a/utility/consensus/consensus.go b/utility/consensus/consensus.go new file mode 100644 index 00000000..f076b27d --- /dev/null +++ b/utility/consensus/consensus.go @@ -0,0 +1,53 @@ +package consensus + +import ( + "GoOnchain/net/httpjsonrpc" + "GoOnchain/utility" + "errors" + "fmt" + "os" +) + +var usage = `switch of consensus function` + +var flags = []string{"ip", "port", "rpcid", "start", "stop"} + +func main(args []string, p utility.Param) (err error) { + var resp []byte + addr, err := utility.Address(p.Ip, p.Port) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return err + } + + id := p.RPCID + if p.Start && p.Stop { + fmt.Fprintln(os.Stdout, "Which option do you want? (start or stop)") + return errors.New("ambiguous flag") + + } else if p.Start { + resp, err = httpjsonrpc.Call(addr, "startconsensus", id, []interface{}{}) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return err + } + fmt.Fprintln(os.Stdout, resp) + + } else if p.Stop { + resp, err = httpjsonrpc.Call(addr, "stopconsensus", id, []interface{}{}) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return err + } + fmt.Fprintln(os.Stdout, resp) + } else { + fmt.Fprintln(os.Stdout, "Do you miss option? (start or stop)") + return errors.New("missing flag") + } + + utility.FormatOutput(resp) + + return nil +} + +var Command = &utility.Command{UsageText: usage, Flags: flags, Main: main} diff --git a/utility/info/info.go b/utility/info/info.go new file mode 100644 index 00000000..29ee57a2 --- /dev/null +++ b/utility/info/info.go @@ -0,0 +1,95 @@ +package info + +import ( + "GoOnchain/net/httpjsonrpc" + "GoOnchain/utility" + "fmt" + "os" +) + +var usage = `show info about blockchain` + +var flags = []string{"ip", "port", "rpcid", "height", "bestblockhash", "blockhash", + "blockcount", "connections", "neighbor", "state"} + +func main(args []string, p utility.Param) (err error) { + var resp []byte + var output [][]byte + addr, err := utility.Address(p.Ip, p.Port) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return err + } + + id := p.RPCID + if height := p.Height; height >= 0 { + resp, err = httpjsonrpc.Call(addr, "getblock", id, []interface{}{height, 1}) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return err + } + output = append(output, resp) + } + + if hash := p.BlockHash; hash != "" { + resp, err = httpjsonrpc.Call(addr, "getblock", id, []interface{}{hash, 1}) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return err + } + output = append(output, resp) + } + + if p.BestBlockHash { + resp, err = httpjsonrpc.Call(addr, "getbestblockhash", id, []interface{}{}) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return err + } + output = append(output, resp) + } + + if p.BlockCount { + resp, err = httpjsonrpc.Call(addr, "getblockcount", id, []interface{}{}) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return err + } + output = append(output, resp) + } + + if p.ConnectionCount { + resp, err = httpjsonrpc.Call(addr, "getconnectioncount", id, []interface{}{}) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return err + } + output = append(output, resp) + } + + if p.Neighbor { + resp, err := httpjsonrpc.Call(addr, "getneighbor", id, []interface{}{}) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return err + } + output = append(output, resp) + } + + if p.NodeState { + resp, err := httpjsonrpc.Call(addr, "getnodestate", id, []interface{}{}) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return err + } + output = append(output, resp) + } + + for _, v := range output { + utility.FormatOutput(v) + } + + return nil +} + +var Command = &utility.Command{UsageText: usage, Flags: flags, Main: main} diff --git a/utility/test/test.go b/utility/test/test.go new file mode 100644 index 00000000..b7c9da6b --- /dev/null +++ b/utility/test/test.go @@ -0,0 +1,17 @@ +package test + +import ( + "GoOnchain/utility" +) + +var usage = `run specific test case` + +var flags = []string{} + +func main(args []string, p utility.Param) (err error) { + + //TODO + return nil +} + +var Command = &utility.Command{UsageText: usage, Flags: flags, Main: main}