diff --git a/blockchain/storetopo.go b/blockchain/storetopo.go index 0ec2e285..0b0edebc 100644 --- a/blockchain/storetopo.go +++ b/blockchain/storetopo.go @@ -279,6 +279,10 @@ func (chain *Blockchain) Find_Blocks_Height_Range(startheight, stopheight int64) } _, topos_end := chain.Store.Topo_store.binarySearchHeight(stopheight) + if topos_start == nil || topos_end == nil { + return + } + lowest := topos_start[0] for _, t := range topos_start { if t < lowest { diff --git a/cmd/dero-wallet-cli/easymenu_post_open.go b/cmd/dero-wallet-cli/easymenu_post_open.go index 218e0560..ae7eec62 100644 --- a/cmd/dero-wallet-cli/easymenu_post_open.go +++ b/cmd/dero-wallet-cli/easymenu_post_open.go @@ -16,27 +16,26 @@ package main -import "io" -import "os" -import "time" -import "fmt" -import "errors" -import "runtime" -import "strings" - -import "path/filepath" -import "encoding/json" - -import "github.com/chzyer/readline" - -import "github.com/deroproject/derohe/rpc" -import "github.com/deroproject/derohe/globals" +import ( + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "runtime" + "strings" + "time" + + "github.com/chzyer/readline" + "github.com/deroproject/derohe/cryptography/crypto" + "github.com/deroproject/derohe/globals" + "github.com/deroproject/derohe/rpc" + "github.com/deroproject/derohe/transaction" +) //import "github.com/deroproject/derohe/address" -import "github.com/deroproject/derohe/cryptography/crypto" -import "github.com/deroproject/derohe/transaction" - // handle menu if a wallet is currently opened func display_easymenu_post_open_command(l *readline.Instance) { w := l.Stderr() @@ -65,6 +64,7 @@ func display_easymenu_post_open_command(l *readline.Instance) { io.WriteString(w, "\t\033[1m13\033[0m\tShow transaction history\n") io.WriteString(w, "\t\033[1m14\033[0m\tRescan transaction history\n") io.WriteString(w, "\t\033[1m15\033[0m\tExport all transaction history in json format\n") + io.WriteString(w, "\t\033[1m20\033[0m\tName registration\n") } io.WriteString(w, "\n\t\033[1m9\033[0m\tExit menu and start prompt\n") @@ -226,151 +226,186 @@ func handle_easymenu_post_open_command(l *readline.Instance, line string) (proce break } - // a , amount_to_transfer, err := collect_transfer_info(l,wallet) - a, err := ReadAddress(l, wallet) - if err != nil { - logger.Error(err, "error reading address") - break - } - - var amount_to_transfer uint64 + var tx *transaction.Transaction + var tx_data []rpc.Transfer + var done bool + var tx_amount uint64 + var amount_str string - var arguments = rpc.Arguments{ - // { rpc.RPC_DESTINATION_PORT, rpc.DataUint64,uint64(0x1234567812345678)}, - // { rpc.RPC_VALUE_TRANSFER, rpc.DataUint64,uint64(12345)}, - // { rpc.RPC_EXPIRY , rpc.DataTime, time.Now().Add(time.Hour).UTC()}, - // { rpc.RPC_COMMENT , rpc.DataString, "Purchase XYZ"}, - } - if a.IsIntegratedAddress() { // read everything from the address - - if a.Arguments.Validate_Arguments() != nil { - logger.Error(err, "Integrated Address arguments could not be validated.") - break + // Transfer loop + for !done { + // a , amount_to_transfer, err := collect_transfer_info(l,wallet) + a, err := ReadAddress(l, wallet) + if err != nil { + logger.Error(err, "error reading address") + return } - - if !a.Arguments.Has(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { // but only it is present - logger.Error(fmt.Errorf("Integrated Address does not contain destination port."), "") - break + if a_check := len(tx_data); a_check != 0 { + for i := 0; i < a_check; i++ { + if tx_data[i].Destination == a.String() { + logger.Error(nil, "Destination already used for this TX!") + return + } + } } - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)}) - // arguments = append(arguments, rpc.Argument{"Comment", rpc.DataString, "holygrail of all data is now working if you can see this"}) + var amount_to_transfer uint64 - if a.Arguments.Has(rpc.RPC_EXPIRY, rpc.DataTime) { // but only it is present + var arguments = rpc.Arguments{ + // { rpc.RPC_DESTINATION_PORT, rpc.DataUint64,uint64(0x1234567812345678)}, + // { rpc.RPC_VALUE_TRANSFER, rpc.DataUint64,uint64(12345)}, + // { rpc.RPC_EXPIRY , rpc.DataTime, time.Now().Add(time.Hour).UTC()}, + // { rpc.RPC_COMMENT , rpc.DataString, "Purchase XYZ"}, + } + if a.IsIntegratedAddress() { // read everything from the address - if a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime).(time.Time).Before(time.Now().UTC()) { - logger.Error(nil, "This address has expired.", "expiry time", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) - break - } else { - logger.Info("This address will expire ", "expiry time", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) + if a.Arguments.Validate_Arguments() != nil { + logger.Error(err, "Integrated Address arguments could not be validated.") + return } - } - logger.Info("Destination port is integrated in address.", "dst port", a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)) + if !a.Arguments.Has(rpc.RPC_DESTINATION_PORT, rpc.DataUint64) { // but only it is present + logger.Error(fmt.Errorf("Integrated Address does not contain destination port."), "") + return + } - if a.Arguments.Has(rpc.RPC_COMMENT, rpc.DataString) { // but only it is present - logger.Info("Integrated Message", "comment", a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)) - arguments = append(arguments, rpc.Argument{rpc.RPC_COMMENT, rpc.DataString, a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)}) - } - } + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)}) + // arguments = append(arguments, rpc.Argument{"Comment", rpc.DataString, "holygrail of all data is now working if you can see this"}) - // arguments have been already validated - for _, arg := range a.Arguments { - if !(arg.Name == rpc.RPC_COMMENT || arg.Name == rpc.RPC_EXPIRY || arg.Name == rpc.RPC_DESTINATION_PORT || arg.Name == rpc.RPC_SOURCE_PORT || arg.Name == rpc.RPC_VALUE_TRANSFER || arg.Name == rpc.RPC_NEEDS_REPLYBACK_ADDRESS) { - switch arg.DataType { - case rpc.DataString: - if v, err := ReadString(l, arg.Name, arg.Value.(string)); err == nil { - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: v}) - } else { - logger.Error(fmt.Errorf("%s could not be parsed (type %s),", arg.Name, arg.DataType), "") - return - } - case rpc.DataInt64: - if v, err := ReadInt64(l, arg.Name, arg.Value.(int64)); err == nil { - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: v}) - } else { - logger.Error(fmt.Errorf("%s could not be parsed (type %s),", arg.Name, arg.DataType), "") + if a.Arguments.Has(rpc.RPC_EXPIRY, rpc.DataTime) { // but only it is present + + if a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime).(time.Time).Before(time.Now().UTC()) { + logger.Error(nil, "This address has expired.", "expiry time", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) return - } - case rpc.DataUint64: - if v, err := ReadUint64(l, arg.Name, arg.Value.(uint64)); err == nil { - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: v}) } else { - logger.Error(fmt.Errorf("%s could not be parsed (type %s),", arg.Name, arg.DataType), "") - return + logger.Info("This address will expire ", "expiry time", a.Arguments.Value(rpc.RPC_EXPIRY, rpc.DataTime)) } - case rpc.DataFloat64: - if v, err := ReadFloat64(l, arg.Name, arg.Value.(float64)); err == nil { - arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: v}) - } else { - logger.Error(fmt.Errorf("%s could not be parsed (type %s),", arg.Name, arg.DataType), "") + } + + logger.Info("Destination port is integrated in address.", "dst port", a.Arguments.Value(rpc.RPC_DESTINATION_PORT, rpc.DataUint64).(uint64)) + + if a.Arguments.Has(rpc.RPC_COMMENT, rpc.DataString) { // but only it is present + logger.Info("Integrated Message", "comment", a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)) + arguments = append(arguments, rpc.Argument{rpc.RPC_COMMENT, rpc.DataString, a.Arguments.Value(rpc.RPC_COMMENT, rpc.DataString)}) + } + } + + // arguments have been already validated + for _, arg := range a.Arguments { + if !(arg.Name == rpc.RPC_COMMENT || arg.Name == rpc.RPC_EXPIRY || arg.Name == rpc.RPC_DESTINATION_PORT || arg.Name == rpc.RPC_SOURCE_PORT || arg.Name == rpc.RPC_VALUE_TRANSFER || arg.Name == rpc.RPC_NEEDS_REPLYBACK_ADDRESS) { + switch arg.DataType { + case rpc.DataString: + if v, err := ReadString(l, arg.Name, arg.Value.(string)); err == nil { + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: v}) + } else { + logger.Error(fmt.Errorf("%s could not be parsed (type %s),", arg.Name, arg.DataType), "") + return + } + case rpc.DataInt64: + if v, err := ReadInt64(l, arg.Name, arg.Value.(int64)); err == nil { + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: v}) + } else { + logger.Error(fmt.Errorf("%s could not be parsed (type %s),", arg.Name, arg.DataType), "") + return + } + case rpc.DataUint64: + if v, err := ReadUint64(l, arg.Name, arg.Value.(uint64)); err == nil { + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: v}) + } else { + logger.Error(fmt.Errorf("%s could not be parsed (type %s),", arg.Name, arg.DataType), "") + return + } + case rpc.DataFloat64: + if v, err := ReadFloat64(l, arg.Name, arg.Value.(float64)); err == nil { + arguments = append(arguments, rpc.Argument{Name: arg.Name, DataType: arg.DataType, Value: v}) + } else { + logger.Error(fmt.Errorf("%s could not be parsed (type %s),", arg.Name, arg.DataType), "") + return + } + case rpc.DataTime: + logger.Error(fmt.Errorf("time argument is currently not supported."), "") return - } - case rpc.DataTime: - logger.Error(fmt.Errorf("time argument is currently not supported."), "") - break + } } } - } - if a.Arguments.Has(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { // but only it is present - logger.Info("Transaction", "Value", globals.FormatMoney(a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64))) - amount_to_transfer = a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64) - } else { + if a.Arguments.Has(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64) { // but only it is present + logger.Info("Transaction", "Value", globals.FormatMoney(a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64))) + amount_to_transfer = a.Arguments.Value(rpc.RPC_VALUE_TRANSFER, rpc.DataUint64).(uint64) + } else { - mbal, _ := wallet.Get_Balance() - amount_str := read_line_with_prompt(l, fmt.Sprintf("Enter amount to transfer in DERO (current balance %s): ", globals.FormatMoney(mbal))) + mbal, _ := wallet.Get_Balance() + if tx_amount == 0 { + amount_str = read_line_with_prompt(l, fmt.Sprintf("Enter amount to transfer in DERO (current balance %s): ", globals.FormatMoney(mbal))) + } else { + amount_str = read_line_with_prompt(l, fmt.Sprintf("Enter amount to transfer in DERO (current balance %s (-%s needed for this TX): ", globals.FormatMoney(mbal), globals.FormatMoney(tx_amount))) + } - if amount_str == "" { - logger.Error(nil, "Cannot transfer 0") - break // invalid amount provided, bail out + if amount_str == "" { + logger.Error(nil, "Cannot transfer 0") + return // invalid amount provided, bail out + } + amount_to_transfer, err = globals.ParseAmount(amount_str) + if err != nil { + logger.Error(err, "Err parsing amount") + return // invalid amount provided, bail out + } } - amount_to_transfer, err = globals.ParseAmount(amount_str) - if err != nil { - logger.Error(err, "Err parsing amount") - break // invalid amount provided, bail out + + // check whether the service needs the address of sender + // this is required to enable services which are completely invisisble to external entities + // external entities means anyone except sender/receiver + if a.Arguments.Has(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataUint64) { + logger.Info("This RPC has requested your address.") + logger.Info("If you are expecting something back, it needs to be sent") + logger.Info("Your address will remain completely invisible to external entities(only sender/receiver can see your address)") + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_REPLYBACK_ADDRESS, DataType: rpc.DataAddress, Value: wallet.GetAddress()}) } - } - // check whether the service needs the address of sender - // this is required to enable services which are completely invisisble to external entities - // external entities means anyone except sender/receiver - if a.Arguments.Has(rpc.RPC_NEEDS_REPLYBACK_ADDRESS, rpc.DataUint64) { - logger.Info("This RPC has requested your address.") - logger.Info("If you are expecting something back, it needs to be sent") - logger.Info("Your address will remain completely invisible to external entities(only sender/receiver can see your address)") - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_REPLYBACK_ADDRESS, DataType: rpc.DataAddress, Value: wallet.GetAddress()}) - } + // if no arguments, use space by embedding a small comment + if len(arguments) == 0 { // allow user to enter Comment + if v, err := ReadUint64(l, "Please enter payment id (or destination port number)", uint64(0)); err == nil { + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: v}) + } else { + logger.Error(err, fmt.Sprintf("%s could not be parsed (type %s),", "Number", rpc.DataUint64)) + return + } - // if no arguments, use space by embedding a small comment - if len(arguments) == 0 { // allow user to enter Comment - if v, err := ReadUint64(l, "Please enter payment id (or destination port number)", uint64(0)); err == nil { - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_DESTINATION_PORT, DataType: rpc.DataUint64, Value: v}) - } else { - logger.Error(err, fmt.Sprintf("%s could not be parsed (type %s),", "Number", rpc.DataUint64)) - return + if v, err := ReadString(l, "Comment", ""); err == nil { + arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: v}) + } else { + logger.Error(fmt.Errorf("%s could not be parsed (type %s),", "Comment", rpc.DataString), "") + return + } } - if v, err := ReadString(l, "Comment", ""); err == nil { - arguments = append(arguments, rpc.Argument{Name: rpc.RPC_COMMENT, DataType: rpc.DataString, Value: v}) - } else { - logger.Error(fmt.Errorf("%s could not be parsed (type %s),", "Comment", rpc.DataString), "") + if _, err := arguments.CheckPack(transaction.PAYLOAD0_LIMIT); err != nil { + logger.Error(err, "Arguments packing err") return } + + // Add transaction + tx_data = append(tx_data, rpc.Transfer{Amount: amount_to_transfer, Destination: a.String(), Payload_RPC: arguments}) + tx_amount += amount_to_transfer + + if !ConfirmYesNoDefaultNo(l, "Add another transaction? (y/N)") { + done = true + } } - if _, err := arguments.CheckPack(transaction.PAYLOAD0_LIMIT); err != nil { - logger.Error(err, "Arguments packing err") - return + tx, err = wallet.TransferPayload0(tx_data, 0, false, rpc.Arguments{}, 0, false) // empty SCDATA + + // show transaction details + for i := range tx_data { + logger.Info(fmt.Sprintf("TX details: TX %d, Destination: %s, Amount: %s", i+1, tx_data[i].Destination, globals.FormatMoney(tx_data[i].Amount))) } + logger.Info("TX Summary", "Transactions", len(tx_data), "Amount", globals.FormatMoney(tx_amount), "Fees", globals.FormatMoney(tx.Fees()), "Total", globals.FormatMoney(tx_amount+tx.Fees()), "Ringsize", wallet.GetRingSize()) if ConfirmYesNoDefaultNo(l, "Confirm Transaction (y/N)") { //src_port := uint64(0xffffffffffffffff) - tx, err := wallet.TransferPayload0([]rpc.Transfer{rpc.Transfer{Amount: amount_to_transfer, Destination: a.String(), Payload_RPC: arguments}}, 0, false, rpc.Arguments{}, 0, false) // empty SCDATA - if err != nil { logger.Error(err, "Error while building Transaction") break @@ -382,6 +417,8 @@ func handle_easymenu_post_open_command(l *readline.Instance, line string) (proce } logger.Info("Dispatched tx", "txid", tx.GetHash().String()) //fmt.Printf("queued tx err %s\n") + } else { + logger.Error(nil, "Transfer aborted by user") } case "12": @@ -495,6 +532,51 @@ func handle_easymenu_post_open_command(l *readline.Instance, line string) (proce } } + case "20": // name registration + + if u, err := ReadString(l, "Name", ""); err == nil { + if len(u) < 6 || len(u) > 64 { + logger.Info("Invalid length. Name must be at least 6 and at most 64 characters long") + return + } else { + // check if name is aleady registered first + if _, err = wallet.NameToAddress(u); err != nil { + var zerohash crypto.Hash + var scid crypto.Hash + var transfer_args rpc.Transfer_Params + + scid[31] = 1 + // we need an address for a 0 amount transfer + random_member := wallet.Random_ring_members(zerohash) + + // Building the transfer and SC call + transfer_args.SC_RPC = append(transfer_args.SC_RPC, rpc.Argument{Name: rpc.SCACTION, DataType: rpc.DataUint64, Value: uint64(rpc.SC_CALL)}) + transfer_args.SC_RPC = append(transfer_args.SC_RPC, rpc.Argument{Name: rpc.SCID, DataType: rpc.DataHash, Value: scid}) + transfer_args.SC_RPC = append(transfer_args.SC_RPC, rpc.Argument{Name: "entrypoint", DataType: rpc.DataString, Value: "Register"}) + transfer_args.SC_RPC = append(transfer_args.SC_RPC, rpc.Argument{Name: "name", DataType: rpc.DataString, Value: u}) + transfer_args.Transfers = append(transfer_args.Transfers, rpc.Transfer{Amount: 0, Destination: random_member[0]}) + // ringsize 2 is neccessary, else the SC doesn't know who we are + transfer_args.Ringsize = 2 + + tx, err := wallet.TransferPayload0(transfer_args.Transfers, transfer_args.Ringsize, false, transfer_args.SC_RPC, 0, false) + if err != nil { + logger.Error(err, "Error building transaction") + return + } + + if err = wallet.SendTransaction(tx); err != nil { + logger.Error(err, "Error sending transaction") + return + } + + logger.Info("Name successfully associated") + } else { + logger.Info("Name is already registered or other error occured") + return + } + } + } + default: processed = false // just loop diff --git a/cmd/derod/rpc/websocket_getwork_server.go b/cmd/derod/rpc/websocket_getwork_server.go index d786b984..f692765e 100644 --- a/cmd/derod/rpc/websocket_getwork_server.go +++ b/cmd/derod/rpc/websocket_getwork_server.go @@ -8,40 +8,36 @@ import ( "sort" "time" + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/hex" + "encoding/json" + "encoding/pem" + "math/big" + "net" + "runtime" + "strings" + "sync" + "sync/atomic" + + "github.com/deroproject/derohe/config" + "github.com/deroproject/derohe/globals" + "github.com/deroproject/derohe/rpc" + "github.com/deroproject/graviton" + "github.com/go-logr/logr" "github.com/lesismal/llib/std/crypto/tls" + "github.com/lesismal/nbio" + "github.com/lesismal/nbio/logging" "github.com/lesismal/nbio/nbhttp" "github.com/lesismal/nbio/nbhttp/websocket" ) -import "github.com/lesismal/nbio" -import "github.com/lesismal/nbio/logging" - -import "net" -import "bytes" -import "encoding/hex" -import "encoding/json" -import "runtime" -import "strings" -import "math/big" -import "crypto/ecdsa" -import "crypto/elliptic" - -import "sync/atomic" -import "crypto/rand" -import "crypto/x509" -import "encoding/pem" - -import "github.com/deroproject/derohe/globals" -import "github.com/deroproject/derohe/config" -import "github.com/deroproject/derohe/rpc" -import "github.com/deroproject/graviton" -import "github.com/go-logr/logr" - // this file implements the non-blocking job streamer // only job is to stream jobs to thousands of workers, if any is successful,accept and report back -import "sync" - var memPool = sync.Pool{ New: func() interface{} { return make([]byte, 16*1024) @@ -64,8 +60,14 @@ type user_session struct { address_sum [32]byte } +type banned struct { + fail_count uint8 + timestamp time.Time +} + var client_list_mutex sync.Mutex var client_list = map[*websocket.Conn]*user_session{} +var ban_list = make(map[string]banned) var miners_count int @@ -218,6 +220,7 @@ func newUpgrader() *websocket.Upgrader { } sess := c.Session().(*user_session) + miner := ParseIPNoError(c.RemoteAddr().String()) client_list_mutex.Lock() defer client_list_mutex.Unlock() @@ -249,6 +252,14 @@ func newUpgrader() *websocket.Upgrader { rate_lock.Lock() defer rate_lock.Unlock() mini_found_time = append(mini_found_time, time.Now().Unix()) + + // Reset fail count in case of valid PoW + for i, t := range ban_list { + if miner == i { + t.fail_count = 0 + ban_list[miner] = t + } + } } else { sess.blocks++ atomic.AddInt64(&CountBlocks, 1) @@ -258,6 +269,17 @@ func newUpgrader() *websocket.Upgrader { if !sresult || err != nil { sess.rejected++ atomic.AddInt64(&CountMinisRejected, 1) + + // Increase fail count and ban miner in case of 3 invalid PoW's in a row + i := ban_list[miner] + i.fail_count++ + if i.fail_count >= 3 { + i.timestamp = time.Now() + c.Close() + delete(client_list, c) + logger_getwork.Info("Banned miner", "Address", miner, "Info", "Banned") + } + ban_list[miner] = i } }) @@ -291,6 +313,21 @@ func onWebsocket(w http.ResponseWriter, r *http.Request) { //panic(err) return } + + // Check incoming connections if ban still exists + // Ban is active for 15 minutes + miner := ParseIPNoError(r.RemoteAddr) + for i, t := range ban_list { + if miner == i { + if time.Now().Sub(t.timestamp) < time.Minute*15 { + logger_getwork.V(1).Info("Banned miner", "Address", i, "Info", "Ban still active") + conn.Close() + } else { + delete(ban_list, i) + } + } + } + wsConn := conn.(*websocket.Conn) session := user_session{address: *addr, address_sum: graviton.Sum(addr_raw)} @@ -454,3 +491,22 @@ func generate_random_tls_cert() tls.Certificate { } return tlsCert } + +func ParseIP(s string) (string, error) { + ip, _, err := net.SplitHostPort(s) + if err == nil { + return ip, nil + } + + ip2 := net.ParseIP(s) + if ip2 == nil { + return "", fmt.Errorf("invalid IP") + } + + return ip2.String(), nil +} + +func ParseIPNoError(s string) string { + ip, _ := ParseIP(s) + return ip +}