From 5b6e61dfcdb2ad99bca4c841e69408e27a430eec Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 1 Aug 2019 01:25:12 +0100 Subject: [PATCH 01/18] setup ticket ownership validation/authentication middleware --- controllers/auth.go | 66 +++++++++++++++++++++++++++++++++++++++++++++ helpers/v3auth.go | 61 +++++++++++++++++++++++++++++++++++++++++ log.go | 4 +++ v3api/log.go | 26 ++++++++++++++++++ v3api/middleware.go | 60 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 217 insertions(+) create mode 100644 controllers/auth.go create mode 100644 helpers/v3auth.go create mode 100644 v3api/log.go create mode 100644 v3api/middleware.go diff --git a/controllers/auth.go b/controllers/auth.go new file mode 100644 index 00000000..61e7aa11 --- /dev/null +++ b/controllers/auth.go @@ -0,0 +1,66 @@ +package controllers + +import ( + "net/http" + "github.com/zenazn/goji/web" + "crypto/rand" + "encoding/base64" + "google.golang.org/grpc/codes" + "github.com/decred/dcrstakepool/poolapi" + "github.com/decred/dcrstakepool/models" + "strconv" +) + + +// APIVotingPost is the API version of VotingPost +func (controller *MainController) APIb(c web.C, r *http.Request) ([]string, codes.Code, string, error) { + dbMap := controller.GetDbMap(c) + + if c.Env["APIUserID"] == nil { + return nil, codes.Unauthenticated, "voting error", errors.New("invalid api token") + } + + user, _ := models.GetUserById(dbMap, c.Env["APIUserID"].(int64)) + oldVoteBits := user.VoteBits + + vb := r.FormValue("VoteBits") + vbi, err := strconv.Atoi(vb) + if err != nil { + return nil, codes.InvalidArgument, "voting error", errors.New("unable to convert votebits to uint16") + } + userVoteBits := uint16(vbi) + + if !controller.IsValidVoteBits(userVoteBits) { + return nil, codes.InvalidArgument, "voting error", errors.New("votebits invalid for current agendas") + } + + user, err = helpers.UpdateVoteBitsByID(dbMap, user.Id, userVoteBits) + if err != nil { + return nil, codes.Internal, "voting error", errors.New("failed to update voting prefs in database") + } + + if uint16(oldVoteBits) != userVoteBits { + controller.StakepooldUpdateUsers(dbMap) + } + + log.Infof("updated voteBits for user %d from %d to %d", + user.Id, oldVoteBits, userVoteBits) + + return nil, codes.OK, "successfully updated voting preferences", nil +} + + +const TicketChallengeByteSize = 32 + +// RegisterPost form submit route. Registers new user or shows Registration route with +// appropriate messages set in session. +func (controller *MainController) TicketChallenge(c web.C, r *http.Request) (*poolapi.Stats, codes.Code, string, error) { + randomBytes := make([]byte, TicketChallengeByteSize) + _, err := rand.Read(randomBytes) + if err != nil { + // todo log error and return + } + + challenge := base64.URLEncoding.EncodeToString(randomBytes) + return nil, codes.OK, "challenge expires in", nil +} \ No newline at end of file diff --git a/helpers/v3auth.go b/helpers/v3auth.go new file mode 100644 index 00000000..8e6624d4 --- /dev/null +++ b/helpers/v3auth.go @@ -0,0 +1,61 @@ +package helpers + +import ( + "strings" + "strconv" + "time" + "github.com/decred/slog" +) + +const ( + customAuthScheme = "ticket-auth" + customAuthTimestampParam = "timestamp" + customAuthSignatureParam = "signature" + customAuthTicketHashParam = "tickethash" + + authTimestampValiditySeconds = 30 +) + +func VerifyCustomAuthHeaderValue(authHeader string, log slog.Logger) (validated bool) { + if strings.HasPrefix(authHeader, customAuthScheme) { + return + } + + var timestampMessage, timestampSignature, ticketHash string + authParams := strings.Split(authHeader, ",") + for _, param := range authParams { + paramKeyValue := strings.Split(param, "=") + if len(paramKeyValue) != 2 { + continue + } + if key := strings.TrimSpace(paramKeyValue[0]); key == customAuthTimestampParam { + timestampMessage = strings.TrimSpace(paramKeyValue[1]) + } else if key == customAuthSignatureParam { + timestampSignature = strings.TrimSpace(paramKeyValue[1]) + } else if key == customAuthTicketHashParam { + ticketHash = strings.TrimSpace(paramKeyValue[1]) + } + } + + if timestampMessage == "" || timestampSignature == "" || ticketHash == "" { + log.Warnf("invalid v3 auth header value %s", authHeader) + return + } + + authTimestamp, err := strconv.Atoi(timestampMessage) + if err != nil { + log.Warnf("invalid v3 auth request timestamp %v: %v", authHeader, err) + return + } + + // todo ensure that timestamp had not been used in a previous authentication attempt + + // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. + timestampDelta := time.Now().Unix() - int64(authTimestamp) + if timestampDelta < 0 || timestampDelta > authTimestampValiditySeconds { + log.Warnf("expired v3 auth request timestamp %v: %v", authHeader, timestampDelta) + return + } + + return true +} diff --git a/log.go b/log.go index 19e6918a..330c4a35 100644 --- a/log.go +++ b/log.go @@ -15,6 +15,7 @@ import ( "github.com/decred/dcrstakepool/system" "github.com/decred/slog" "github.com/jrick/logrotate/rotator" + "github.com/decred/dcrstakepool/v3api" ) // logWriter implements an io.Writer that outputs to both standard output and @@ -50,6 +51,7 @@ var ( modelsLog = backendLog.Logger("MODL") stakepooldclientLog = backendLog.Logger("GRPC") systemLog = backendLog.Logger("SYTM") + v3APILog = backendLog.Logger("API3") ) // Initialize package-global logger variables. @@ -58,6 +60,7 @@ func init() { models.UseLogger(modelsLog) stakepooldclient.UseLogger(stakepooldclientLog) system.UseLogger(systemLog) + v3api.UseLogger(v3APILog) } // subsystemLoggers maps each subsystem identifier to its associated logger. @@ -67,6 +70,7 @@ var subsystemLoggers = map[string]slog.Logger{ "GRPC": stakepooldclientLog, "MODL": modelsLog, "SYTM": systemLog, + "API3": v3APILog, } // initLogRotator initializes the logging rotater to write logs to logFile and diff --git a/v3api/log.go b/v3api/log.go new file mode 100644 index 00000000..9eb506e0 --- /dev/null +++ b/v3api/log.go @@ -0,0 +1,26 @@ +// Copyright (c) 2013-2015 The btcsuite developers +// Copyright (c) 2018 The Decred developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package v3api + +import "github.com/decred/slog" + +// log is a logger that is initialized with no output filters. This +// means the package will not perform any logging by default until the caller +// requests it. +var log = slog.Disabled + +// DisableLog disables all library log output. Logging output is disabled +// by default until either UseLogger or SetLogWriter are called. +func DisableLog() { + log = slog.Disabled +} + +// UseLogger uses a specified Logger to output package logging info. +// This should be used in preference to SetLogWriter if the caller is also +// using slog. +func UseLogger(logger slog.Logger) { + log = logger +} diff --git a/v3api/middleware.go b/v3api/middleware.go new file mode 100644 index 00000000..efa32b22 --- /dev/null +++ b/v3api/middleware.go @@ -0,0 +1,60 @@ +package v3api + +import ( + "net/http" + "strings" + "github.com/dgrijalva/jwt-go" + "fmt" + "github.com/go-gorp/gorp" + "github.com/decred/dcrstakepool/models" + "github.com/zenazn/goji/web" + "strconv" + "time" +) + +func ApplyTicketAuth(c *web.C, h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, "/api/v3") { + authHeader := r.Header.Get("Authorization") + if strings.HasPrefix(authHeader, customAuthScheme) { + var timestampMessage, timestampSignature string + authParams := strings.Split(authHeader, ",") + for _, param := range authParams { + paramKeyValue := strings.Split(param, "=") + if len(paramKeyValue) != 2 { + continue + } + if key := strings.TrimSpace(paramKeyValue[0]); key == customAuthTimestampParam { + timestampMessage = strings.TrimSpace(paramKeyValue[1]) + } else if key == customAuthSignatureParam { + timestampSignature = strings.TrimSpace(paramKeyValue[1]) + } + } + + JWTtoken, err := jwt.Parse(apitoken, func(token *jwt.Token) (interface{}, error) { + // validate signing algorithm + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return []byte(application.APISecret), nil + }) + + if err != nil { + log.Warnf("invalid token %v: %v", apitoken, err) + } else if claims, ok := JWTtoken.Claims.(jwt.MapClaims); ok && JWTtoken.Valid { + dbMap := c.Env["DbMap"].(*gorp.DbMap) + + user, err := models.GetUserById(dbMap, int64(claims["loggedInAs"].(float64))) + if err != nil { + log.Errorf("unable to map apitoken %v to user id %v", apitoken, claims["loggedInAs"]) + } else { + c.Env["APIUserID"] = user.Id + log.Infof("mapped apitoken %v to user id %v", apitoken, user.Id) + } + } + } + } + h.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +} From a1966509ef65eee19c7ec32786a31ef5a6a7abca Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 1 Aug 2019 02:26:43 +0100 Subject: [PATCH 02/18] go fmt --- controllers/auth.go | 12 +++++------- helpers/v3auth.go | 12 ++++++------ log.go | 2 +- v3api/middleware.go | 8 ++++---- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/controllers/auth.go b/controllers/auth.go index 61e7aa11..8f8632e9 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -1,17 +1,16 @@ package controllers import ( - "net/http" - "github.com/zenazn/goji/web" "crypto/rand" "encoding/base64" - "google.golang.org/grpc/codes" - "github.com/decred/dcrstakepool/poolapi" "github.com/decred/dcrstakepool/models" + "github.com/decred/dcrstakepool/poolapi" + "github.com/zenazn/goji/web" + "google.golang.org/grpc/codes" + "net/http" "strconv" ) - // APIVotingPost is the API version of VotingPost func (controller *MainController) APIb(c web.C, r *http.Request) ([]string, codes.Code, string, error) { dbMap := controller.GetDbMap(c) @@ -49,7 +48,6 @@ func (controller *MainController) APIb(c web.C, r *http.Request) ([]string, code return nil, codes.OK, "successfully updated voting preferences", nil } - const TicketChallengeByteSize = 32 // RegisterPost form submit route. Registers new user or shows Registration route with @@ -63,4 +61,4 @@ func (controller *MainController) TicketChallenge(c web.C, r *http.Request) (*po challenge := base64.URLEncoding.EncodeToString(randomBytes) return nil, codes.OK, "challenge expires in", nil -} \ No newline at end of file +} diff --git a/helpers/v3auth.go b/helpers/v3auth.go index 8e6624d4..7700ae00 100644 --- a/helpers/v3auth.go +++ b/helpers/v3auth.go @@ -1,17 +1,17 @@ package helpers import ( - "strings" + "github.com/decred/slog" "strconv" + "strings" "time" - "github.com/decred/slog" ) const ( - customAuthScheme = "ticket-auth" - customAuthTimestampParam = "timestamp" - customAuthSignatureParam = "signature" - customAuthTicketHashParam = "tickethash" + customAuthScheme = "TicketAuth" + customAuthTimestampParam = "SignedTimestamp" + customAuthSignatureParam = "Signature" + customAuthTicketHashParam = "TicketHash" authTimestampValiditySeconds = 30 ) diff --git a/log.go b/log.go index 330c4a35..d0674665 100644 --- a/log.go +++ b/log.go @@ -13,9 +13,9 @@ import ( "github.com/decred/dcrstakepool/models" "github.com/decred/dcrstakepool/stakepooldclient" "github.com/decred/dcrstakepool/system" + "github.com/decred/dcrstakepool/v3api" "github.com/decred/slog" "github.com/jrick/logrotate/rotator" - "github.com/decred/dcrstakepool/v3api" ) // logWriter implements an io.Writer that outputs to both standard output and diff --git a/v3api/middleware.go b/v3api/middleware.go index efa32b22..3dab006d 100644 --- a/v3api/middleware.go +++ b/v3api/middleware.go @@ -1,14 +1,14 @@ package v3api import ( - "net/http" - "strings" - "github.com/dgrijalva/jwt-go" "fmt" - "github.com/go-gorp/gorp" "github.com/decred/dcrstakepool/models" + "github.com/dgrijalva/jwt-go" + "github.com/go-gorp/gorp" "github.com/zenazn/goji/web" + "net/http" "strconv" + "strings" "time" ) From c819221e8a6dac21ce5f9ed73b44de15f25685c2 Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 1 Aug 2019 02:32:09 +0100 Subject: [PATCH 03/18] fix build errors --- controllers/auth.go | 64 --------------------------------------------- v3api/middleware.go | 59 ----------------------------------------- 2 files changed, 123 deletions(-) delete mode 100644 controllers/auth.go diff --git a/controllers/auth.go b/controllers/auth.go deleted file mode 100644 index 8f8632e9..00000000 --- a/controllers/auth.go +++ /dev/null @@ -1,64 +0,0 @@ -package controllers - -import ( - "crypto/rand" - "encoding/base64" - "github.com/decred/dcrstakepool/models" - "github.com/decred/dcrstakepool/poolapi" - "github.com/zenazn/goji/web" - "google.golang.org/grpc/codes" - "net/http" - "strconv" -) - -// APIVotingPost is the API version of VotingPost -func (controller *MainController) APIb(c web.C, r *http.Request) ([]string, codes.Code, string, error) { - dbMap := controller.GetDbMap(c) - - if c.Env["APIUserID"] == nil { - return nil, codes.Unauthenticated, "voting error", errors.New("invalid api token") - } - - user, _ := models.GetUserById(dbMap, c.Env["APIUserID"].(int64)) - oldVoteBits := user.VoteBits - - vb := r.FormValue("VoteBits") - vbi, err := strconv.Atoi(vb) - if err != nil { - return nil, codes.InvalidArgument, "voting error", errors.New("unable to convert votebits to uint16") - } - userVoteBits := uint16(vbi) - - if !controller.IsValidVoteBits(userVoteBits) { - return nil, codes.InvalidArgument, "voting error", errors.New("votebits invalid for current agendas") - } - - user, err = helpers.UpdateVoteBitsByID(dbMap, user.Id, userVoteBits) - if err != nil { - return nil, codes.Internal, "voting error", errors.New("failed to update voting prefs in database") - } - - if uint16(oldVoteBits) != userVoteBits { - controller.StakepooldUpdateUsers(dbMap) - } - - log.Infof("updated voteBits for user %d from %d to %d", - user.Id, oldVoteBits, userVoteBits) - - return nil, codes.OK, "successfully updated voting preferences", nil -} - -const TicketChallengeByteSize = 32 - -// RegisterPost form submit route. Registers new user or shows Registration route with -// appropriate messages set in session. -func (controller *MainController) TicketChallenge(c web.C, r *http.Request) (*poolapi.Stats, codes.Code, string, error) { - randomBytes := make([]byte, TicketChallengeByteSize) - _, err := rand.Read(randomBytes) - if err != nil { - // todo log error and return - } - - challenge := base64.URLEncoding.EncodeToString(randomBytes) - return nil, codes.OK, "challenge expires in", nil -} diff --git a/v3api/middleware.go b/v3api/middleware.go index 3dab006d..3fadf073 100644 --- a/v3api/middleware.go +++ b/v3api/middleware.go @@ -1,60 +1 @@ package v3api - -import ( - "fmt" - "github.com/decred/dcrstakepool/models" - "github.com/dgrijalva/jwt-go" - "github.com/go-gorp/gorp" - "github.com/zenazn/goji/web" - "net/http" - "strconv" - "strings" - "time" -) - -func ApplyTicketAuth(c *web.C, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - if strings.HasPrefix(r.URL.Path, "/api/v3") { - authHeader := r.Header.Get("Authorization") - if strings.HasPrefix(authHeader, customAuthScheme) { - var timestampMessage, timestampSignature string - authParams := strings.Split(authHeader, ",") - for _, param := range authParams { - paramKeyValue := strings.Split(param, "=") - if len(paramKeyValue) != 2 { - continue - } - if key := strings.TrimSpace(paramKeyValue[0]); key == customAuthTimestampParam { - timestampMessage = strings.TrimSpace(paramKeyValue[1]) - } else if key == customAuthSignatureParam { - timestampSignature = strings.TrimSpace(paramKeyValue[1]) - } - } - - JWTtoken, err := jwt.Parse(apitoken, func(token *jwt.Token) (interface{}, error) { - // validate signing algorithm - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } - return []byte(application.APISecret), nil - }) - - if err != nil { - log.Warnf("invalid token %v: %v", apitoken, err) - } else if claims, ok := JWTtoken.Claims.(jwt.MapClaims); ok && JWTtoken.Valid { - dbMap := c.Env["DbMap"].(*gorp.DbMap) - - user, err := models.GetUserById(dbMap, int64(claims["loggedInAs"].(float64))) - if err != nil { - log.Errorf("unable to map apitoken %v to user id %v", apitoken, claims["loggedInAs"]) - } else { - c.Env["APIUserID"] = user.Id - log.Infof("mapped apitoken %v to user id %v", apitoken, user.Id) - } - } - } - } - h.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) -} From 26c957844f58af1e7f6f48715745285c8aaaac1a Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 1 Aug 2019 04:37:57 +0100 Subject: [PATCH 04/18] implement v3 auth middleware --- backend/stakepoold/rpc/api.proto | 10 + backend/stakepoold/rpc/rpcserver/context.go | 51 +++ backend/stakepoold/rpc/rpcserver/server.go | 13 + backend/stakepoold/rpc/stakepoolrpc/api.pb.go | 376 ++++++++++++------ go.mod | 1 + go.sum | 15 +- helpers/v3auth.go | 61 --- models/user.go | 10 + server.go | 5 + stakepooldclient/stakepooldclient.go | 19 + v3api/middleware.go | 32 ++ v3api/ticketauth.go | 109 +++++ v3api/v3api.go | 13 + 13 files changed, 533 insertions(+), 182 deletions(-) delete mode 100644 helpers/v3auth.go create mode 100644 v3api/ticketauth.go create mode 100644 v3api/v3api.go diff --git a/backend/stakepoold/rpc/api.proto b/backend/stakepoold/rpc/api.proto index 63402863..ce34a5a5 100644 --- a/backend/stakepoold/rpc/api.proto +++ b/backend/stakepoold/rpc/api.proto @@ -14,6 +14,7 @@ service StakepooldService { rpc ValidateAddress (ValidateAddressRequest) returns (ValidateAddressResponse); rpc AddMissingTicket (AddMissingTicketRequest) returns (AddMissingTicketResponse); rpc GetTickets (GetTicketsRequest) returns (GetTicketsResponse); + rpc GetTicketInfo (GetTicketInfoRequest) returns (GetTicketInfoResponse); rpc ListScripts (ListScriptsRequest) returns (ListScriptsResponse); rpc AccountSyncAddressIndex (AccountSyncAddressIndexRequest) returns (AccountSyncAddressIndexResponse); rpc CreateMultisig (CreateMultisigRequest) returns (CreateMultisigResponse); @@ -39,6 +40,15 @@ message GetLiveTicketsResponse { repeated Ticket tickets = 1; } +message GetTicketInfoRequest { + bytes Hash = 1; +} +message GetTicketInfoResponse { + string MultiSigAddress = 1; + string VspFeeAddress = 2; + string OwnerFeeAddress = 3; +} + message SetAddedLowFeeTicketsRequest { repeated Ticket tickets = 1; } diff --git a/backend/stakepoold/rpc/rpcserver/context.go b/backend/stakepoold/rpc/rpcserver/context.go index 5433ceac..9cc5ed33 100644 --- a/backend/stakepoold/rpc/rpcserver/context.go +++ b/backend/stakepoold/rpc/rpcserver/context.go @@ -99,6 +99,12 @@ type ticketMetadata struct { err error // log errors along the way } +type TicketInfo struct { + MultiSigAddress string + VspFeeAddress string + OwnerFeeAddress string +} + // EvaluateStakePoolTicket evaluates a voting service ticket to see if it's // acceptable to the voting service. The ticket must pay out to the voting // service cold wallet, and must have a sufficient fee. @@ -369,6 +375,51 @@ func (ctx *AppContext) GetTickets(includeImmature bool) ([]*chainhash.Hash, erro return tickets, nil } +func (ctx *AppContext) GetTicketInfo(ticketHash []byte) (ticketInfo *TicketInfo, err error) { + hash, err := chainhash.NewHash(ticketHash) + if err != nil { + log.Errorf("GetTicketInfo: Failed to parse ticket hash: %v", err) + return + } + + res, err := ctx.WalletConnection.GetTransaction(hash) + if err != nil { + log.Errorf("GetTicketInfo: GetTransaction rpc failed: %v", err) + return + } + + for i := range res.Details { + _, ok := ctx.UserVotingConfig[res.Details[i].Address] + if ok { + // multisigaddress will match if it belongs a pool user + ticketInfo.MultiSigAddress = res.Details[i].Address + + // get sstxcommitment addresses using tx hes + msgTx, err := MsgTxFromHex(res.Hex) + if err != nil { + log.Warnf("MsgTxFromHex failed for %v: %v", res.Hex, err) + continue + } + break + + vspCommitmentOut := msgTx.TxOut[1] + vspCommitAddr, err := stake.AddrFromSStxPkScrCommitment(vspCommitmentOut.PkScript, ctx.Params) + if err != nil { + return nil, fmt.Errorf("Failed to parse commit out addr: %s", err.Error()) + } + ticketInfo.VspFeeAddress = vspCommitAddr.EncodeAddress() + + ownerCommitmentOut := msgTx.TxOut[3] + ownerCommitAddr, err := stake.AddrFromSStxPkScrCommitment(ownerCommitmentOut.PkScript, ctx.Params) + if err != nil { + return nil, fmt.Errorf("Failed to parse commit out addr: %s", err.Error()) + } + ticketInfo.OwnerFeeAddress = ownerCommitAddr.EncodeAddress() + } + } + return +} + func (ctx *AppContext) StakePoolUserInfo(multisigAddress string) (*wallettypes.StakePoolUserInfoResult, error) { decodedMultisig, err := dcrutil.DecodeAddress(multisigAddress) if err != nil { diff --git a/backend/stakepoold/rpc/rpcserver/server.go b/backend/stakepoold/rpc/rpcserver/server.go index f9d3b87a..e296764c 100644 --- a/backend/stakepoold/rpc/rpcserver/server.go +++ b/backend/stakepoold/rpc/rpcserver/server.go @@ -186,6 +186,19 @@ func (s *stakepooldServer) GetTickets(ctx context.Context, req *pb.GetTicketsReq return &pb.GetTicketsResponse{Tickets: ticketBytes}, nil } +func (s *stakepooldServer) GetTicketInfo(ctx context.Context, req *pb.GetTicketInfoRequest) (*pb.GetTicketInfoResponse, error) { + ticketInfo, err := s.appContext.GetTicketInfo(req.Hash) + if err != nil { + return nil, err + } + + return &pb.GetTicketInfoResponse{ + OwnerFeeAddress: ticketInfo.OwnerFeeAddress, + VspFeeAddress: ticketInfo.VspFeeAddress, + MultiSigAddress: ticketInfo.MultiSigAddress, + }, nil +} + func (s *stakepooldServer) AddMissingTicket(ctx context.Context, req *pb.AddMissingTicketRequest) (*pb.AddMissingTicketResponse, error) { err := s.appContext.AddMissingTicket(req.Hash) if err != nil { diff --git a/backend/stakepoold/rpc/stakepoolrpc/api.pb.go b/backend/stakepoold/rpc/stakepoolrpc/api.pb.go index db70391f..3d9ee4f6 100644 --- a/backend/stakepoold/rpc/stakepoolrpc/api.pb.go +++ b/backend/stakepoold/rpc/stakepoolrpc/api.pb.go @@ -234,6 +234,100 @@ func (m *GetLiveTicketsResponse) GetTickets() []*Ticket { return nil } +type GetTicketInfoRequest struct { + Hash []byte `protobuf:"bytes,1,opt,name=Hash,proto3" json:"Hash,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetTicketInfoRequest) Reset() { *m = GetTicketInfoRequest{} } +func (m *GetTicketInfoRequest) String() string { return proto.CompactTextString(m) } +func (*GetTicketInfoRequest) ProtoMessage() {} +func (*GetTicketInfoRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_00212fb1f9d3bf1c, []int{6} +} + +func (m *GetTicketInfoRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetTicketInfoRequest.Unmarshal(m, b) +} +func (m *GetTicketInfoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetTicketInfoRequest.Marshal(b, m, deterministic) +} +func (m *GetTicketInfoRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetTicketInfoRequest.Merge(m, src) +} +func (m *GetTicketInfoRequest) XXX_Size() int { + return xxx_messageInfo_GetTicketInfoRequest.Size(m) +} +func (m *GetTicketInfoRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetTicketInfoRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetTicketInfoRequest proto.InternalMessageInfo + +func (m *GetTicketInfoRequest) GetHash() []byte { + if m != nil { + return m.Hash + } + return nil +} + +type GetTicketInfoResponse struct { + MultiSigAddress string `protobuf:"bytes,1,opt,name=MultiSigAddress,proto3" json:"MultiSigAddress,omitempty"` + VspFeeAddress string `protobuf:"bytes,2,opt,name=VspFeeAddress,proto3" json:"VspFeeAddress,omitempty"` + OwnerFeeAddress string `protobuf:"bytes,3,opt,name=OwnerFeeAddress,proto3" json:"OwnerFeeAddress,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetTicketInfoResponse) Reset() { *m = GetTicketInfoResponse{} } +func (m *GetTicketInfoResponse) String() string { return proto.CompactTextString(m) } +func (*GetTicketInfoResponse) ProtoMessage() {} +func (*GetTicketInfoResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_00212fb1f9d3bf1c, []int{7} +} + +func (m *GetTicketInfoResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetTicketInfoResponse.Unmarshal(m, b) +} +func (m *GetTicketInfoResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetTicketInfoResponse.Marshal(b, m, deterministic) +} +func (m *GetTicketInfoResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetTicketInfoResponse.Merge(m, src) +} +func (m *GetTicketInfoResponse) XXX_Size() int { + return xxx_messageInfo_GetTicketInfoResponse.Size(m) +} +func (m *GetTicketInfoResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetTicketInfoResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetTicketInfoResponse proto.InternalMessageInfo + +func (m *GetTicketInfoResponse) GetMultiSigAddress() string { + if m != nil { + return m.MultiSigAddress + } + return "" +} + +func (m *GetTicketInfoResponse) GetVspFeeAddress() string { + if m != nil { + return m.VspFeeAddress + } + return "" +} + +func (m *GetTicketInfoResponse) GetOwnerFeeAddress() string { + if m != nil { + return m.OwnerFeeAddress + } + return "" +} + type SetAddedLowFeeTicketsRequest struct { Tickets []*Ticket `protobuf:"bytes,1,rep,name=tickets,proto3" json:"tickets,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -245,7 +339,7 @@ func (m *SetAddedLowFeeTicketsRequest) Reset() { *m = SetAddedLowFeeTick func (m *SetAddedLowFeeTicketsRequest) String() string { return proto.CompactTextString(m) } func (*SetAddedLowFeeTicketsRequest) ProtoMessage() {} func (*SetAddedLowFeeTicketsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{6} + return fileDescriptor_00212fb1f9d3bf1c, []int{8} } func (m *SetAddedLowFeeTicketsRequest) XXX_Unmarshal(b []byte) error { @@ -283,7 +377,7 @@ func (m *SetAddedLowFeeTicketsResponse) Reset() { *m = SetAddedLowFeeTic func (m *SetAddedLowFeeTicketsResponse) String() string { return proto.CompactTextString(m) } func (*SetAddedLowFeeTicketsResponse) ProtoMessage() {} func (*SetAddedLowFeeTicketsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{7} + return fileDescriptor_00212fb1f9d3bf1c, []int{9} } func (m *SetAddedLowFeeTicketsResponse) XXX_Unmarshal(b []byte) error { @@ -314,7 +408,7 @@ func (m *SetUserVotingPrefsResponse) Reset() { *m = SetUserVotingPrefsRe func (m *SetUserVotingPrefsResponse) String() string { return proto.CompactTextString(m) } func (*SetUserVotingPrefsResponse) ProtoMessage() {} func (*SetUserVotingPrefsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{8} + return fileDescriptor_00212fb1f9d3bf1c, []int{10} } func (m *SetUserVotingPrefsResponse) XXX_Unmarshal(b []byte) error { @@ -346,7 +440,7 @@ func (m *SetUserVotingPrefsRequest) Reset() { *m = SetUserVotingPrefsReq func (m *SetUserVotingPrefsRequest) String() string { return proto.CompactTextString(m) } func (*SetUserVotingPrefsRequest) ProtoMessage() {} func (*SetUserVotingPrefsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{9} + return fileDescriptor_00212fb1f9d3bf1c, []int{11} } func (m *SetUserVotingPrefsRequest) XXX_Unmarshal(b []byte) error { @@ -385,7 +479,7 @@ func (m *AddMissingTicketRequest) Reset() { *m = AddMissingTicketRequest func (m *AddMissingTicketRequest) String() string { return proto.CompactTextString(m) } func (*AddMissingTicketRequest) ProtoMessage() {} func (*AddMissingTicketRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{10} + return fileDescriptor_00212fb1f9d3bf1c, []int{12} } func (m *AddMissingTicketRequest) XXX_Unmarshal(b []byte) error { @@ -423,7 +517,7 @@ func (m *AddMissingTicketResponse) Reset() { *m = AddMissingTicketRespon func (m *AddMissingTicketResponse) String() string { return proto.CompactTextString(m) } func (*AddMissingTicketResponse) ProtoMessage() {} func (*AddMissingTicketResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{11} + return fileDescriptor_00212fb1f9d3bf1c, []int{13} } func (m *AddMissingTicketResponse) XXX_Unmarshal(b []byte) error { @@ -455,7 +549,7 @@ func (m *GetTicketsRequest) Reset() { *m = GetTicketsRequest{} } func (m *GetTicketsRequest) String() string { return proto.CompactTextString(m) } func (*GetTicketsRequest) ProtoMessage() {} func (*GetTicketsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{12} + return fileDescriptor_00212fb1f9d3bf1c, []int{14} } func (m *GetTicketsRequest) XXX_Unmarshal(b []byte) error { @@ -494,7 +588,7 @@ func (m *GetTicketsResponse) Reset() { *m = GetTicketsResponse{} } func (m *GetTicketsResponse) String() string { return proto.CompactTextString(m) } func (*GetTicketsResponse) ProtoMessage() {} func (*GetTicketsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{13} + return fileDescriptor_00212fb1f9d3bf1c, []int{15} } func (m *GetTicketsResponse) XXX_Unmarshal(b []byte) error { @@ -532,7 +626,7 @@ func (m *ListScriptsRequest) Reset() { *m = ListScriptsRequest{} } func (m *ListScriptsRequest) String() string { return proto.CompactTextString(m) } func (*ListScriptsRequest) ProtoMessage() {} func (*ListScriptsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{14} + return fileDescriptor_00212fb1f9d3bf1c, []int{16} } func (m *ListScriptsRequest) XXX_Unmarshal(b []byte) error { @@ -564,7 +658,7 @@ func (m *ListScriptsResponse) Reset() { *m = ListScriptsResponse{} } func (m *ListScriptsResponse) String() string { return proto.CompactTextString(m) } func (*ListScriptsResponse) ProtoMessage() {} func (*ListScriptsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{15} + return fileDescriptor_00212fb1f9d3bf1c, []int{17} } func (m *ListScriptsResponse) XXX_Unmarshal(b []byte) error { @@ -605,7 +699,7 @@ func (m *AccountSyncAddressIndexRequest) Reset() { *m = AccountSyncAddre func (m *AccountSyncAddressIndexRequest) String() string { return proto.CompactTextString(m) } func (*AccountSyncAddressIndexRequest) ProtoMessage() {} func (*AccountSyncAddressIndexRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{16} + return fileDescriptor_00212fb1f9d3bf1c, []int{18} } func (m *AccountSyncAddressIndexRequest) XXX_Unmarshal(b []byte) error { @@ -657,7 +751,7 @@ func (m *AccountSyncAddressIndexResponse) Reset() { *m = AccountSyncAddr func (m *AccountSyncAddressIndexResponse) String() string { return proto.CompactTextString(m) } func (*AccountSyncAddressIndexResponse) ProtoMessage() {} func (*AccountSyncAddressIndexResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{17} + return fileDescriptor_00212fb1f9d3bf1c, []int{19} } func (m *AccountSyncAddressIndexResponse) XXX_Unmarshal(b []byte) error { @@ -691,7 +785,7 @@ func (m *ImportScriptRequest) Reset() { *m = ImportScriptRequest{} } func (m *ImportScriptRequest) String() string { return proto.CompactTextString(m) } func (*ImportScriptRequest) ProtoMessage() {} func (*ImportScriptRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{18} + return fileDescriptor_00212fb1f9d3bf1c, []int{20} } func (m *ImportScriptRequest) XXX_Unmarshal(b []byte) error { @@ -744,7 +838,7 @@ func (m *ImportScriptResponse) Reset() { *m = ImportScriptResponse{} } func (m *ImportScriptResponse) String() string { return proto.CompactTextString(m) } func (*ImportScriptResponse) ProtoMessage() {} func (*ImportScriptResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{19} + return fileDescriptor_00212fb1f9d3bf1c, []int{21} } func (m *ImportScriptResponse) XXX_Unmarshal(b []byte) error { @@ -783,7 +877,7 @@ func (m *StakePoolUserInfoRequest) Reset() { *m = StakePoolUserInfoReque func (m *StakePoolUserInfoRequest) String() string { return proto.CompactTextString(m) } func (*StakePoolUserInfoRequest) ProtoMessage() {} func (*StakePoolUserInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{20} + return fileDescriptor_00212fb1f9d3bf1c, []int{22} } func (m *StakePoolUserInfoRequest) XXX_Unmarshal(b []byte) error { @@ -823,7 +917,7 @@ func (m *StakePoolUserInfoResponse) Reset() { *m = StakePoolUserInfoResp func (m *StakePoolUserInfoResponse) String() string { return proto.CompactTextString(m) } func (*StakePoolUserInfoResponse) ProtoMessage() {} func (*StakePoolUserInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{21} + return fileDescriptor_00212fb1f9d3bf1c, []int{23} } func (m *StakePoolUserInfoResponse) XXX_Unmarshal(b []byte) error { @@ -868,7 +962,7 @@ func (m *WalletInfoRequest) Reset() { *m = WalletInfoRequest{} } func (m *WalletInfoRequest) String() string { return proto.CompactTextString(m) } func (*WalletInfoRequest) ProtoMessage() {} func (*WalletInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{22} + return fileDescriptor_00212fb1f9d3bf1c, []int{24} } func (m *WalletInfoRequest) XXX_Unmarshal(b []byte) error { @@ -903,7 +997,7 @@ func (m *WalletInfoResponse) Reset() { *m = WalletInfoResponse{} } func (m *WalletInfoResponse) String() string { return proto.CompactTextString(m) } func (*WalletInfoResponse) ProtoMessage() {} func (*WalletInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{23} + return fileDescriptor_00212fb1f9d3bf1c, []int{25} } func (m *WalletInfoResponse) XXX_Unmarshal(b []byte) error { @@ -963,7 +1057,7 @@ func (m *ValidateAddressRequest) Reset() { *m = ValidateAddressRequest{} func (m *ValidateAddressRequest) String() string { return proto.CompactTextString(m) } func (*ValidateAddressRequest) ProtoMessage() {} func (*ValidateAddressRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{24} + return fileDescriptor_00212fb1f9d3bf1c, []int{26} } func (m *ValidateAddressRequest) XXX_Unmarshal(b []byte) error { @@ -1003,7 +1097,7 @@ func (m *ValidateAddressResponse) Reset() { *m = ValidateAddressResponse func (m *ValidateAddressResponse) String() string { return proto.CompactTextString(m) } func (*ValidateAddressResponse) ProtoMessage() {} func (*ValidateAddressResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{25} + return fileDescriptor_00212fb1f9d3bf1c, []int{27} } func (m *ValidateAddressResponse) XXX_Unmarshal(b []byte) error { @@ -1049,7 +1143,7 @@ func (m *CreateMultisigRequest) Reset() { *m = CreateMultisigRequest{} } func (m *CreateMultisigRequest) String() string { return proto.CompactTextString(m) } func (*CreateMultisigRequest) ProtoMessage() {} func (*CreateMultisigRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{26} + return fileDescriptor_00212fb1f9d3bf1c, []int{28} } func (m *CreateMultisigRequest) XXX_Unmarshal(b []byte) error { @@ -1089,7 +1183,7 @@ func (m *CreateMultisigResponse) Reset() { *m = CreateMultisigResponse{} func (m *CreateMultisigResponse) String() string { return proto.CompactTextString(m) } func (*CreateMultisigResponse) ProtoMessage() {} func (*CreateMultisigResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{27} + return fileDescriptor_00212fb1f9d3bf1c, []int{29} } func (m *CreateMultisigResponse) XXX_Unmarshal(b []byte) error { @@ -1139,7 +1233,7 @@ func (m *StakePoolUserTicket) Reset() { *m = StakePoolUserTicket{} } func (m *StakePoolUserTicket) String() string { return proto.CompactTextString(m) } func (*StakePoolUserTicket) ProtoMessage() {} func (*StakePoolUserTicket) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{28} + return fileDescriptor_00212fb1f9d3bf1c, []int{30} } func (m *StakePoolUserTicket) XXX_Unmarshal(b []byte) error { @@ -1207,7 +1301,7 @@ func (m *Ticket) Reset() { *m = Ticket{} } func (m *Ticket) String() string { return proto.CompactTextString(m) } func (*Ticket) ProtoMessage() {} func (*Ticket) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{29} + return fileDescriptor_00212fb1f9d3bf1c, []int{31} } func (m *Ticket) XXX_Unmarshal(b []byte) error { @@ -1256,7 +1350,7 @@ func (m *UserVotingConfigEntry) Reset() { *m = UserVotingConfigEntry{} } func (m *UserVotingConfigEntry) String() string { return proto.CompactTextString(m) } func (*UserVotingConfigEntry) ProtoMessage() {} func (*UserVotingConfigEntry) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{30} + return fileDescriptor_00212fb1f9d3bf1c, []int{32} } func (m *UserVotingConfigEntry) XXX_Unmarshal(b []byte) error { @@ -1315,7 +1409,7 @@ func (m *VersionRequest) Reset() { *m = VersionRequest{} } func (m *VersionRequest) String() string { return proto.CompactTextString(m) } func (*VersionRequest) ProtoMessage() {} func (*VersionRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{31} + return fileDescriptor_00212fb1f9d3bf1c, []int{33} } func (m *VersionRequest) XXX_Unmarshal(b []byte) error { @@ -1352,7 +1446,7 @@ func (m *VersionResponse) Reset() { *m = VersionResponse{} } func (m *VersionResponse) String() string { return proto.CompactTextString(m) } func (*VersionResponse) ProtoMessage() {} func (*VersionResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{32} + return fileDescriptor_00212fb1f9d3bf1c, []int{34} } func (m *VersionResponse) XXX_Unmarshal(b []byte) error { @@ -1425,7 +1519,7 @@ func (m *GetStakeInfoRequest) Reset() { *m = GetStakeInfoRequest{} } func (m *GetStakeInfoRequest) String() string { return proto.CompactTextString(m) } func (*GetStakeInfoRequest) ProtoMessage() {} func (*GetStakeInfoRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{33} + return fileDescriptor_00212fb1f9d3bf1c, []int{35} } func (m *GetStakeInfoRequest) XXX_Unmarshal(b []byte) error { @@ -1472,7 +1566,7 @@ func (m *GetStakeInfoResponse) Reset() { *m = GetStakeInfoResponse{} } func (m *GetStakeInfoResponse) String() string { return proto.CompactTextString(m) } func (*GetStakeInfoResponse) ProtoMessage() {} func (*GetStakeInfoResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_00212fb1f9d3bf1c, []int{34} + return fileDescriptor_00212fb1f9d3bf1c, []int{36} } func (m *GetStakeInfoResponse) XXX_Unmarshal(b []byte) error { @@ -1612,6 +1706,8 @@ func init() { proto.RegisterType((*GetIgnoredLowFeeTicketsResponse)(nil), "stakepoolrpc.GetIgnoredLowFeeTicketsResponse") proto.RegisterType((*GetLiveTicketsRequest)(nil), "stakepoolrpc.GetLiveTicketsRequest") proto.RegisterType((*GetLiveTicketsResponse)(nil), "stakepoolrpc.GetLiveTicketsResponse") + proto.RegisterType((*GetTicketInfoRequest)(nil), "stakepoolrpc.GetTicketInfoRequest") + proto.RegisterType((*GetTicketInfoResponse)(nil), "stakepoolrpc.GetTicketInfoResponse") proto.RegisterType((*SetAddedLowFeeTicketsRequest)(nil), "stakepoolrpc.SetAddedLowFeeTicketsRequest") proto.RegisterType((*SetAddedLowFeeTicketsResponse)(nil), "stakepoolrpc.SetAddedLowFeeTicketsResponse") proto.RegisterType((*SetUserVotingPrefsResponse)(nil), "stakepoolrpc.SetUserVotingPrefsResponse") @@ -1646,97 +1742,101 @@ func init() { func init() { proto.RegisterFile("api.proto", fileDescriptor_00212fb1f9d3bf1c) } var fileDescriptor_00212fb1f9d3bf1c = []byte{ - // 1435 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x58, 0xdd, 0x4e, 0xdc, 0x46, - 0x14, 0xd6, 0x02, 0x01, 0xf6, 0xc0, 0x02, 0x19, 0xfe, 0x1c, 0x8b, 0xc0, 0xc6, 0xf9, 0x5b, 0xa5, - 0x0d, 0x55, 0xa9, 0xd4, 0x9b, 0xaa, 0x95, 0x20, 0x3f, 0x64, 0xd5, 0xd0, 0x10, 0x3b, 0xd0, 0x4a, - 0x95, 0x8a, 0x8c, 0x3d, 0x2c, 0xd3, 0x78, 0xc7, 0xae, 0x3d, 0xbb, 0x09, 0xbd, 0xea, 0x03, 0xf4, - 0xb2, 0xf7, 0xbd, 0xee, 0x63, 0xf4, 0x29, 0xfa, 0x3a, 0xd5, 0xcc, 0x1c, 0xef, 0xda, 0xb3, 0xde, - 0x25, 0xc9, 0x9d, 0xcf, 0x37, 0x67, 0xce, 0x9c, 0xff, 0x39, 0x63, 0xa8, 0xfb, 0x09, 0xdb, 0x4d, - 0xd2, 0x58, 0xc4, 0x64, 0x31, 0x13, 0xfe, 0x5b, 0x9a, 0xc4, 0x71, 0x94, 0x26, 0x81, 0xb3, 0x0d, - 0x5b, 0x87, 0x54, 0xec, 0x87, 0x21, 0x0d, 0x5f, 0xc6, 0xef, 0x9e, 0x53, 0xfa, 0x86, 0x05, 0x6f, - 0xa9, 0xc8, 0x5c, 0xfa, 0x5b, 0x8f, 0x66, 0xc2, 0x79, 0x05, 0xb7, 0xc7, 0xac, 0x67, 0x49, 0xcc, - 0x33, 0x4a, 0x76, 0x61, 0x4e, 0x68, 0xc8, 0xaa, 0x35, 0xa7, 0x5b, 0x0b, 0x7b, 0x6b, 0xbb, 0xc5, - 0x03, 0x76, 0x35, 0xbf, 0x9b, 0x33, 0x39, 0x4d, 0xd8, 0x3e, 0xa4, 0xa2, 0xdd, 0xe1, 0x71, 0x3a, - 0xe6, 0xc8, 0xd7, 0xb0, 0x33, 0x96, 0xe3, 0x13, 0x0f, 0xdd, 0x84, 0xf5, 0x43, 0x2a, 0x5e, 0xb2, - 0xbe, 0x79, 0xd6, 0x0b, 0xd8, 0x30, 0x17, 0x3e, 0xf1, 0x88, 0x1f, 0x60, 0xcb, 0x9b, 0xe0, 0xc8, - 0x8f, 0x96, 0xb7, 0x03, 0xb7, 0xbd, 0x49, 0x8e, 0x77, 0xb6, 0xc0, 0xf6, 0xa8, 0x38, 0xc9, 0x68, - 0x7a, 0x1a, 0x0b, 0xc6, 0x3b, 0xc7, 0x29, 0xbd, 0x18, 0xae, 0x72, 0xb8, 0x55, 0xb5, 0xaa, 0x75, - 0x79, 0x0d, 0xa4, 0x97, 0xd1, 0xf4, 0xac, 0xaf, 0x96, 0xce, 0x82, 0x98, 0x5f, 0xb0, 0x0e, 0xaa, - 0x75, 0xb7, 0xac, 0xd6, 0x50, 0xc2, 0x13, 0xc5, 0xf5, 0x8c, 0x8b, 0xf4, 0xca, 0x5d, 0xe9, 0x19, - 0xb0, 0xf3, 0x18, 0x36, 0xf7, 0xc3, 0xf0, 0x88, 0x65, 0x19, 0xe3, 0x1d, 0xb4, 0x05, 0x4f, 0x23, - 0x30, 0xf3, 0xc2, 0xcf, 0x2e, 0xad, 0x5a, 0xb3, 0xd6, 0x5a, 0x74, 0xd5, 0xb7, 0x63, 0x83, 0x35, - 0xca, 0x8e, 0xaa, 0x7f, 0x0b, 0x37, 0x0f, 0xa9, 0x30, 0xdc, 0xd7, 0x82, 0xe5, 0x36, 0x0f, 0xa2, - 0x5e, 0x48, 0xdb, 0xdd, 0xae, 0x2f, 0x7a, 0x29, 0x55, 0xf2, 0xe6, 0x5d, 0x13, 0x76, 0x76, 0x81, - 0x14, 0xb7, 0x63, 0x38, 0x2d, 0x98, 0x7b, 0x53, 0x70, 0xff, 0xa2, 0x9b, 0x93, 0xce, 0x1a, 0x90, - 0x97, 0x2c, 0x13, 0x5e, 0x90, 0xb2, 0x64, 0x98, 0x18, 0x5f, 0xc0, 0x6a, 0x09, 0x1d, 0x8a, 0x41, - 0x28, 0x17, 0x83, 0xa4, 0x73, 0x09, 0xdb, 0xfb, 0x41, 0x10, 0xf7, 0xb8, 0xf0, 0xae, 0x78, 0xb0, - 0x1f, 0x86, 0x29, 0xcd, 0xb2, 0x36, 0x0f, 0xe9, 0xfb, 0xdc, 0x04, 0x0b, 0xe6, 0x90, 0x43, 0xa9, - 0x5e, 0x77, 0x73, 0x92, 0x6c, 0xc0, 0xec, 0x41, 0xea, 0xf3, 0xe0, 0xd2, 0x9a, 0x6a, 0xd6, 0x5a, - 0x0d, 0x17, 0x29, 0xb2, 0x06, 0x37, 0x94, 0x04, 0x6b, 0xba, 0x59, 0x6b, 0x4d, 0xbb, 0x9a, 0x70, - 0xee, 0xc0, 0xce, 0xd8, 0x93, 0xd0, 0x85, 0x0c, 0x56, 0xdb, 0xdd, 0x24, 0x4e, 0x51, 0xff, 0x5c, - 0x83, 0x0d, 0x98, 0xd5, 0x00, 0xc6, 0x02, 0x29, 0x89, 0xbb, 0x34, 0x0b, 0x7c, 0xae, 0xce, 0x9f, - 0x77, 0x91, 0x22, 0x0e, 0x2c, 0xea, 0xaf, 0x17, 0x94, 0x75, 0x2e, 0x05, 0xaa, 0x51, 0xc2, 0x9c, - 0xef, 0x60, 0xad, 0x7c, 0x14, 0x7a, 0xea, 0x01, 0x2c, 0x69, 0x0e, 0xbd, 0x4a, 0x43, 0x75, 0xe6, - 0xb4, 0x6b, 0xa0, 0xce, 0x53, 0xb0, 0x3c, 0x99, 0x70, 0xc7, 0x71, 0x1c, 0xc9, 0x64, 0x6b, 0xf3, - 0x8b, 0xb8, 0x10, 0xf4, 0xa3, 0x5e, 0x24, 0x98, 0xc7, 0x3a, 0x68, 0x26, 0x7a, 0xce, 0x84, 0x9d, - 0x3f, 0x6a, 0x70, 0xab, 0x42, 0x0c, 0xea, 0xf2, 0x4d, 0x39, 0xf8, 0x0b, 0x7b, 0x77, 0xca, 0x49, - 0x5e, 0xda, 0x99, 0x17, 0x22, 0xee, 0x90, 0x86, 0xb4, 0x79, 0xdf, 0x8f, 0x58, 0x98, 0xcb, 0x98, - 0x6a, 0x4e, 0xb7, 0xea, 0xae, 0x81, 0x3a, 0xab, 0x70, 0xf3, 0x47, 0x3f, 0x8a, 0xa8, 0x28, 0x58, - 0xe0, 0xfc, 0x55, 0x03, 0x52, 0x44, 0x51, 0xa1, 0x26, 0x2c, 0x9c, 0xc6, 0x82, 0x9e, 0xd2, 0x34, - 0x63, 0x31, 0x57, 0x46, 0x35, 0xdc, 0x22, 0x24, 0x4d, 0x7f, 0xea, 0xd3, 0x6e, 0xcc, 0x9f, 0xc4, - 0x9c, 0xd3, 0x40, 0xfa, 0x4f, 0xc7, 0xc6, 0x84, 0x89, 0x0d, 0xf3, 0x27, 0x3c, 0x8a, 0x83, 0xb7, - 0x34, 0x54, 0x01, 0x9a, 0x77, 0x07, 0xb4, 0x0c, 0xac, 0xae, 0x52, 0x6b, 0x46, 0x07, 0x56, 0x53, - 0xce, 0x1e, 0x6c, 0x9c, 0x4a, 0xdd, 0x7d, 0x41, 0xd1, 0x83, 0xc5, 0x24, 0x2d, 0xb9, 0x3a, 0x27, - 0x9d, 0xd7, 0xb0, 0x39, 0xb2, 0x07, 0xcd, 0xd9, 0x80, 0xd9, 0x76, 0x76, 0xc4, 0x78, 0x5e, 0x93, - 0x48, 0x91, 0x6d, 0x80, 0xe3, 0xde, 0xf9, 0xf7, 0xf4, 0x4a, 0x6e, 0x50, 0xfa, 0xd7, 0xdd, 0x02, - 0xe2, 0x7c, 0x09, 0xeb, 0x4f, 0x52, 0xea, 0x0b, 0xaa, 0xc2, 0x99, 0xb1, 0x4e, 0xa5, 0x16, 0xd3, - 0x45, 0x2d, 0x4e, 0x61, 0xc3, 0xdc, 0x82, 0x4a, 0xa8, 0x64, 0x0d, 0x29, 0xed, 0x16, 0x52, 0xbc, - 0xee, 0x96, 0xb0, 0xa2, 0xdc, 0xa9, 0xb2, 0x75, 0xff, 0xd4, 0x60, 0xb5, 0x22, 0x0d, 0x54, 0xc9, - 0x08, 0x5f, 0xf4, 0x72, 0x77, 0x20, 0x25, 0x71, 0xcd, 0x81, 0x82, 0x90, 0x92, 0x5a, 0xe8, 0xaf, - 0x42, 0xc9, 0x34, 0xdc, 0x12, 0xa6, 0x9a, 0x48, 0x42, 0xb9, 0x38, 0xb8, 0x52, 0x61, 0xa9, 0xbb, - 0x39, 0x49, 0xee, 0x41, 0x03, 0x3f, 0x71, 0xfb, 0x0d, 0xb5, 0xbd, 0x0c, 0x3a, 0x5f, 0xe7, 0x67, - 0x8f, 0x8f, 0xd6, 0xa0, 0xe9, 0x4e, 0x15, 0x9a, 0xee, 0xdf, 0x35, 0x58, 0xaf, 0xec, 0xe7, 0xd2, - 0x1a, 0x55, 0x34, 0x79, 0x91, 0x22, 0x55, 0x55, 0x80, 0x53, 0x95, 0x05, 0x28, 0xb3, 0x50, 0xa6, - 0xef, 0x01, 0x13, 0x19, 0xb6, 0x89, 0x01, 0x2d, 0xa5, 0xe4, 0xdf, 0x79, 0xc6, 0xcf, 0x28, 0x16, - 0x13, 0x76, 0x56, 0x60, 0x09, 0x3f, 0xf3, 0x02, 0xfa, 0xb7, 0x06, 0xcb, 0x03, 0x08, 0x23, 0x7d, - 0x1f, 0x96, 0xfa, 0x1a, 0x3a, 0xcb, 0x44, 0x2a, 0xb3, 0x5b, 0x1b, 0xdf, 0x40, 0xd4, 0x53, 0xa0, - 0xec, 0x9e, 0x5d, 0xff, 0xd7, 0x38, 0xc5, 0xa6, 0xaa, 0x09, 0x85, 0x32, 0x1e, 0xa7, 0x18, 0x19, - 0x4d, 0x48, 0x34, 0xf1, 0x45, 0x70, 0xa9, 0x14, 0x6b, 0xb8, 0x9a, 0x90, 0xf9, 0x9b, 0xa4, 0x34, - 0xa5, 0x11, 0xf5, 0x33, 0xaa, 0x62, 0x51, 0x77, 0x0b, 0x88, 0x54, 0xe4, 0xbc, 0xc7, 0xa2, 0xf0, - 0xac, 0x4b, 0x85, 0x1f, 0xfa, 0xc2, 0xb7, 0x66, 0xb5, 0x22, 0x0a, 0x3d, 0x42, 0xd0, 0x59, 0x87, - 0xd5, 0x43, 0x2a, 0x54, 0x76, 0x15, 0x7b, 0xc3, 0x9f, 0x33, 0xb0, 0x56, 0xc6, 0x87, 0xdd, 0xe1, - 0x40, 0x16, 0x30, 0xe6, 0x80, 0x0e, 0x49, 0x11, 0x92, 0x8a, 0x3d, 0x65, 0x17, 0x17, 0x2c, 0xe8, - 0x45, 0xe2, 0x4a, 0xd9, 0x57, 0x73, 0x0b, 0x88, 0xca, 0xc2, 0x58, 0xf8, 0x91, 0xd7, 0x3b, 0xcf, - 0x58, 0x78, 0xa5, 0x6c, 0xad, 0xb9, 0x25, 0x4c, 0xe6, 0xda, 0xab, 0x77, 0xfc, 0x88, 0x76, 0x65, - 0x17, 0x7c, 0xc3, 0xde, 0xa3, 0xe9, 0x65, 0x50, 0xc6, 0x75, 0x70, 0xe1, 0xea, 0x64, 0x1c, 0xd0, - 0x32, 0xfb, 0x4e, 0x78, 0x26, 0x53, 0x53, 0xd9, 0xdd, 0x70, 0x73, 0x52, 0xba, 0x53, 0x86, 0x36, - 0xb4, 0xe6, 0xb4, 0x3b, 0x15, 0x21, 0xf9, 0x5d, 0xda, 0x8f, 0x65, 0xa3, 0x9a, 0xd7, 0xfc, 0x48, - 0xca, 0x1e, 0x8b, 0x5b, 0x9f, 0xbd, 0x4f, 0x58, 0x4a, 0x43, 0xab, 0xae, 0x18, 0x0c, 0x54, 0x6a, - 0x23, 0xeb, 0xd3, 0x63, 0xbf, 0x53, 0x0b, 0xb4, 0x36, 0x39, 0x2d, 0xed, 0xd9, 0x8f, 0xa2, 0x82, - 0x3d, 0x0b, 0xda, 0x9e, 0x12, 0x28, 0xeb, 0x42, 0x4e, 0x7b, 0xd6, 0xa2, 0x5a, 0x54, 0xdf, 0xf2, - 0xf4, 0xe3, 0x34, 0x96, 0xf7, 0x11, 0x8b, 0xb9, 0x5a, 0x6d, 0x28, 0x7f, 0x19, 0xa8, 0xac, 0x12, - 0x39, 0xb1, 0xd0, 0xd0, 0x5a, 0xd2, 0xd7, 0xb4, 0xa6, 0xc8, 0x23, 0x58, 0x19, 0x72, 0x22, 0xc7, - 0xb2, 0x92, 0x30, 0x82, 0x4b, 0x1f, 0xe4, 0x26, 0xae, 0x68, 0x1f, 0x20, 0xb9, 0xf7, 0xdf, 0x02, - 0xdc, 0xf4, 0xf2, 0x5b, 0x29, 0xf4, 0x68, 0xda, 0x67, 0x01, 0x25, 0x89, 0x9a, 0x5c, 0x47, 0xc7, - 0x40, 0xf2, 0xa8, 0x7c, 0x85, 0x4d, 0x1a, 0xe2, 0xed, 0xcf, 0x3e, 0x88, 0x17, 0xb3, 0xaf, 0x0f, - 0x9b, 0x63, 0xc6, 0x6f, 0xf2, 0xf9, 0x88, 0x9c, 0x09, 0x73, 0xbc, 0xfd, 0xf8, 0x03, 0xb9, 0xf1, - 0xdc, 0x9f, 0x61, 0xa9, 0x3c, 0x8a, 0x93, 0xbb, 0x23, 0x02, 0x46, 0x27, 0x78, 0xfb, 0xde, 0x64, - 0x26, 0x14, 0x9e, 0xc0, 0xba, 0xf7, 0x21, 0x6e, 0xf4, 0x3e, 0xc2, 0x8d, 0x13, 0xc7, 0x73, 0xd2, - 0x01, 0x32, 0x3a, 0x80, 0x93, 0x87, 0x23, 0x22, 0xaa, 0x47, 0x74, 0xbb, 0x75, 0x3d, 0x23, 0x1e, - 0x74, 0x02, 0x8b, 0xc5, 0x01, 0x8c, 0x18, 0xb3, 0x4d, 0xc5, 0x1c, 0x68, 0x3b, 0x93, 0x58, 0x50, - 0x6c, 0x88, 0xd9, 0x58, 0x1c, 0xa8, 0xc8, 0x83, 0x09, 0x73, 0x53, 0xa1, 0xb5, 0xd9, 0x0f, 0xaf, - 0xe5, 0xc3, 0x53, 0x5e, 0x01, 0x0c, 0xc7, 0x23, 0xb2, 0x53, 0xde, 0x36, 0x32, 0x4e, 0xd9, 0xcd, - 0xf1, 0x0c, 0x28, 0xf0, 0x17, 0x58, 0x36, 0xa6, 0x14, 0x62, 0x64, 0x48, 0xf5, 0xe0, 0x63, 0xdf, - 0xbf, 0x86, 0x0b, 0xe5, 0xfb, 0xb0, 0x62, 0x3e, 0x5c, 0x88, 0xb1, 0x75, 0xcc, 0x3b, 0xc8, 0x7e, - 0x70, 0x1d, 0xdb, 0xd0, 0x27, 0xc3, 0x07, 0x8c, 0xe9, 0x93, 0x91, 0x97, 0x91, 0xe9, 0x93, 0x8a, - 0xb7, 0x8f, 0x0b, 0x0b, 0x85, 0xb7, 0x0c, 0x31, 0x36, 0x8c, 0x3e, 0x7e, 0xec, 0x3b, 0x13, 0x38, - 0x86, 0x5d, 0x62, 0xcc, 0x23, 0xc4, 0xec, 0x12, 0x93, 0x5f, 0x45, 0x66, 0x97, 0xb8, 0xe6, 0x65, - 0x23, 0xbb, 0x44, 0x79, 0xfe, 0x33, 0xbb, 0x44, 0xe5, 0x40, 0x69, 0x76, 0x89, 0x31, 0x23, 0xe4, - 0x09, 0x2c, 0x16, 0x2f, 0x64, 0xb3, 0x94, 0x2a, 0x2e, 0x71, 0xb3, 0x94, 0xaa, 0xee, 0xf3, 0xbd, - 0x9f, 0x06, 0x53, 0x4d, 0xde, 0xd5, 0x9f, 0xc3, 0x5c, 0x3e, 0xe8, 0x6f, 0x19, 0x79, 0x57, 0x1a, - 0x7f, 0xec, 0xdb, 0x63, 0x56, 0xb5, 0xe4, 0xf3, 0x59, 0xf5, 0x4b, 0xe7, 0xab, 0xff, 0x03, 0x00, - 0x00, 0xff, 0xff, 0xcb, 0x80, 0xac, 0x80, 0xdf, 0x11, 0x00, 0x00, + // 1499 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x58, 0x5b, 0x4f, 0x1c, 0xc7, + 0x12, 0xd6, 0x02, 0x06, 0xb6, 0xd8, 0x05, 0xdc, 0xdc, 0xd6, 0x23, 0x0c, 0x78, 0x7c, 0x43, 0x3e, + 0xc7, 0x1c, 0x1d, 0x22, 0xe5, 0x25, 0x4a, 0x24, 0xf0, 0x05, 0xaf, 0x62, 0x02, 0x9e, 0x31, 0xc4, + 0x52, 0xa4, 0xa0, 0x61, 0xa6, 0x59, 0x3a, 0x9e, 0xed, 0x99, 0xcc, 0xf4, 0xae, 0x4d, 0x9e, 0xf2, + 0x03, 0xf2, 0x12, 0x29, 0xef, 0x79, 0xce, 0x63, 0x7e, 0x42, 0xfe, 0x59, 0xd4, 0xdd, 0x35, 0xbb, + 0x3d, 0xbd, 0x17, 0xb0, 0xdf, 0xa6, 0xbe, 0xae, 0xae, 0xae, 0x5b, 0x57, 0x57, 0x0d, 0x54, 0x83, + 0x94, 0xed, 0xa4, 0x59, 0x22, 0x12, 0x52, 0xcb, 0x45, 0xf0, 0x9e, 0xa6, 0x49, 0x12, 0x67, 0x69, + 0xe8, 0x6e, 0xc0, 0xfa, 0x01, 0x15, 0x7b, 0x51, 0x44, 0xa3, 0xd7, 0xc9, 0x87, 0x97, 0x94, 0xbe, + 0x65, 0xe1, 0x7b, 0x2a, 0x72, 0x8f, 0xfe, 0xdc, 0xa1, 0xb9, 0x70, 0x8f, 0xe0, 0xee, 0x88, 0xf5, + 0x3c, 0x4d, 0x78, 0x4e, 0xc9, 0x0e, 0xcc, 0x08, 0x0d, 0x35, 0x2a, 0x5b, 0x93, 0xdb, 0x73, 0xbb, + 0xcb, 0x3b, 0xe6, 0x01, 0x3b, 0x9a, 0xdf, 0x2b, 0x98, 0xdc, 0x2d, 0xd8, 0x38, 0xa0, 0xa2, 0xd9, + 0xe2, 0x49, 0x36, 0xe2, 0xc8, 0x37, 0xb0, 0x39, 0x92, 0xe3, 0x33, 0x0f, 0x5d, 0x83, 0x95, 0x03, + 0x2a, 0x5e, 0xb3, 0xae, 0x7d, 0xd6, 0x2b, 0x58, 0xb5, 0x17, 0x3e, 0xf3, 0x88, 0x27, 0xb0, 0x7c, + 0x40, 0x85, 0x46, 0x9b, 0xfc, 0x22, 0xc1, 0x13, 0x08, 0x81, 0xa9, 0x57, 0x41, 0x7e, 0xd9, 0xa8, + 0x6c, 0x55, 0xb6, 0x6b, 0x9e, 0xfa, 0x76, 0x7f, 0xaf, 0x28, 0x7d, 0x4c, 0x66, 0x3c, 0x75, 0x1b, + 0x16, 0x0e, 0x3b, 0xb1, 0x60, 0x3e, 0x6b, 0xed, 0x45, 0x51, 0x46, 0xf3, 0x5c, 0x6d, 0xac, 0x7a, + 0x36, 0x4c, 0x1e, 0x40, 0xfd, 0x34, 0x4f, 0x5f, 0x52, 0x5a, 0xf0, 0x4d, 0x28, 0xbe, 0x32, 0x28, + 0xe5, 0x1d, 0x7d, 0xe0, 0x34, 0x33, 0xf8, 0x26, 0xb5, 0x3c, 0x0b, 0x76, 0xbf, 0x83, 0x75, 0x7f, + 0x4c, 0x22, 0x7c, 0xb2, 0x3f, 0x36, 0xe1, 0xae, 0x3f, 0x2e, 0x71, 0xdc, 0x75, 0x70, 0x7c, 0x2a, + 0x4e, 0x72, 0x9a, 0x9d, 0x26, 0x82, 0xf1, 0xd6, 0x71, 0x46, 0x2f, 0xfa, 0xab, 0x1c, 0xee, 0x0c, + 0x5b, 0xd5, 0xba, 0xbc, 0x01, 0xd2, 0xc9, 0x69, 0x76, 0xd6, 0x55, 0x4b, 0x67, 0x61, 0xc2, 0x2f, + 0x58, 0x0b, 0xd5, 0xba, 0x5f, 0x56, 0xab, 0x2f, 0xe1, 0x99, 0xe2, 0x7a, 0xc1, 0x45, 0x76, 0xe5, + 0x2d, 0x76, 0x2c, 0xd8, 0x7d, 0x0a, 0x6b, 0x7b, 0x51, 0x74, 0xc8, 0xf2, 0x9c, 0xf1, 0x16, 0xda, + 0x32, 0x26, 0x82, 0x0e, 0x34, 0x06, 0xd9, 0x51, 0xf5, 0xaf, 0xe1, 0x76, 0x2f, 0xb8, 0x3d, 0x95, + 0xb7, 0x61, 0xa1, 0xc9, 0xc3, 0xb8, 0x13, 0xd1, 0x66, 0xbb, 0x1d, 0x88, 0x4e, 0x46, 0x95, 0xbc, + 0x59, 0xcf, 0x86, 0xdd, 0x1d, 0x20, 0xe6, 0x76, 0x4c, 0x8c, 0x06, 0xcc, 0xbc, 0x35, 0xdc, 0x5f, + 0xf3, 0x0a, 0xd2, 0x5d, 0x06, 0xf2, 0x9a, 0xe5, 0xc2, 0x0f, 0x33, 0x96, 0xf6, 0x13, 0xfb, 0x7f, + 0xb0, 0x54, 0x42, 0xfb, 0x62, 0x10, 0x2a, 0xc4, 0x20, 0xe9, 0x5e, 0xc2, 0xc6, 0x5e, 0x18, 0x26, + 0x1d, 0x2e, 0xfc, 0x2b, 0x1e, 0x62, 0x56, 0x34, 0x79, 0x44, 0x3f, 0x16, 0x26, 0x34, 0x60, 0x06, + 0x39, 0x30, 0x27, 0x0b, 0x92, 0xac, 0xc2, 0xf4, 0x7e, 0x16, 0xf0, 0xf0, 0x52, 0x25, 0x61, 0xdd, + 0x43, 0x8a, 0x2c, 0xc3, 0x2d, 0x25, 0x41, 0xe5, 0xdc, 0xa4, 0xa7, 0x09, 0xf7, 0x1e, 0x6c, 0x8e, + 0x3c, 0x09, 0x5d, 0xc8, 0x60, 0xa9, 0xd9, 0x4e, 0x93, 0x0c, 0xf5, 0x2f, 0x34, 0x58, 0x85, 0x69, + 0x0d, 0x60, 0x2c, 0x90, 0x92, 0xb8, 0x47, 0xf3, 0x30, 0xe0, 0xea, 0xfc, 0x59, 0x0f, 0x29, 0xe2, + 0x42, 0x4d, 0x7f, 0xbd, 0xa2, 0xac, 0x75, 0x29, 0x50, 0x8d, 0x12, 0xe6, 0x7e, 0x03, 0xcb, 0xe5, + 0xa3, 0xd0, 0x53, 0x8f, 0x60, 0x5e, 0x73, 0xe8, 0x55, 0x1a, 0xa9, 0x33, 0x27, 0x3d, 0x0b, 0x75, + 0x9f, 0x43, 0xc3, 0x97, 0x09, 0x77, 0x9c, 0x24, 0xb1, 0x4c, 0x36, 0xf3, 0xee, 0xdf, 0xf8, 0x36, + 0xbb, 0xbf, 0x56, 0xe0, 0xce, 0x10, 0x31, 0xa8, 0xcb, 0x57, 0xe5, 0xe0, 0xcf, 0xed, 0xde, 0x2b, + 0x27, 0x79, 0x69, 0x67, 0x71, 0x11, 0x71, 0x87, 0x34, 0xa4, 0xc9, 0xbb, 0x41, 0xcc, 0xa2, 0x42, + 0xc6, 0xc4, 0xd6, 0xe4, 0x76, 0xd5, 0xb3, 0x50, 0x77, 0x09, 0x6e, 0x7f, 0x1f, 0xc4, 0x71, 0xa9, + 0x7a, 0xb9, 0x7f, 0x54, 0x80, 0x98, 0x28, 0x2a, 0xb4, 0x05, 0x73, 0xa7, 0x89, 0xa0, 0xa7, 0x34, + 0xcb, 0x59, 0xc2, 0x95, 0x51, 0x75, 0xcf, 0x84, 0xa4, 0xe9, 0xcf, 0x03, 0xda, 0x4e, 0xf8, 0xb3, + 0x84, 0x73, 0x1a, 0x4a, 0xff, 0xe9, 0xd8, 0xd8, 0x30, 0x71, 0x60, 0xf6, 0x84, 0xc7, 0x49, 0xf8, + 0x9e, 0x46, 0x2a, 0x40, 0xb3, 0x5e, 0x8f, 0x96, 0x81, 0xd5, 0xb7, 0xb4, 0x31, 0xa5, 0x03, 0xab, + 0x29, 0x77, 0x17, 0x56, 0x4f, 0xa5, 0xee, 0x81, 0x28, 0xea, 0x97, 0x99, 0xa4, 0x25, 0x57, 0x17, + 0xa4, 0xfb, 0x06, 0xd6, 0x06, 0xf6, 0xa0, 0x39, 0xab, 0x30, 0xdd, 0xcc, 0x0f, 0x19, 0x2f, 0xee, + 0x24, 0x52, 0x64, 0x03, 0xe0, 0xb8, 0x73, 0xfe, 0x2d, 0xbd, 0x92, 0x1b, 0xb0, 0xc0, 0x1a, 0x88, + 0xfb, 0x7f, 0x58, 0x79, 0x96, 0xd1, 0x40, 0x50, 0x15, 0xce, 0x9c, 0xb5, 0x86, 0x6a, 0x31, 0x69, + 0x6a, 0x71, 0x0a, 0xab, 0xf6, 0x16, 0x54, 0x42, 0x25, 0x6b, 0x44, 0x69, 0xdb, 0x48, 0xf1, 0xaa, + 0x57, 0xc2, 0x4c, 0xb9, 0x13, 0x65, 0xeb, 0xfe, 0xaa, 0xc0, 0xd2, 0x90, 0x34, 0x50, 0x57, 0x46, + 0x04, 0xa2, 0x53, 0xb8, 0x03, 0x29, 0x89, 0x6b, 0x0e, 0x14, 0x84, 0x94, 0xd4, 0x42, 0x7f, 0x19, + 0x57, 0xa6, 0xee, 0x95, 0x30, 0x55, 0x44, 0x52, 0xca, 0xc5, 0xfe, 0x95, 0x0a, 0x4b, 0xd5, 0x2b, + 0x48, 0xf9, 0x28, 0xe1, 0x27, 0x6e, 0xbf, 0xa5, 0xb6, 0x97, 0x41, 0xf7, 0xcb, 0xe2, 0xec, 0xd1, + 0xd1, 0xea, 0x15, 0xdd, 0x09, 0xa3, 0xe8, 0xfe, 0x59, 0x81, 0x95, 0xa1, 0xf5, 0x5c, 0x5a, 0xa3, + 0x2e, 0x4d, 0x71, 0x49, 0x91, 0x1a, 0x76, 0x01, 0x27, 0x86, 0x3f, 0xa7, 0x0e, 0xcc, 0xca, 0xf4, + 0xdd, 0x67, 0x22, 0xc7, 0x32, 0xd1, 0xa3, 0xa5, 0x94, 0xe2, 0xbb, 0xc8, 0xf8, 0x29, 0xc5, 0x62, + 0xc3, 0xee, 0x22, 0xcc, 0xe3, 0x67, 0x71, 0x81, 0xfe, 0xa9, 0xc0, 0x42, 0x0f, 0xc2, 0x48, 0x3f, + 0x84, 0xf9, 0xae, 0x86, 0xce, 0x72, 0x91, 0xc9, 0xec, 0xd6, 0xc6, 0xd7, 0x11, 0xf5, 0x15, 0x28, + 0xab, 0x67, 0x3b, 0xf8, 0x29, 0xc9, 0xb0, 0xa8, 0x6a, 0x42, 0xa1, 0x8c, 0x27, 0x19, 0x46, 0x46, + 0x13, 0x12, 0x4d, 0x03, 0x11, 0x5e, 0x2a, 0xc5, 0xea, 0x9e, 0x26, 0x64, 0xfe, 0xa6, 0x19, 0xcd, + 0x68, 0x4c, 0x83, 0x9c, 0xaa, 0x58, 0x54, 0x3d, 0x03, 0x91, 0x8a, 0x9c, 0x77, 0x58, 0x1c, 0x9d, + 0xb5, 0xa9, 0x08, 0xa2, 0x40, 0x04, 0x8d, 0x69, 0xad, 0x88, 0x42, 0x0f, 0x11, 0x74, 0x57, 0x60, + 0xe9, 0x80, 0x0a, 0x95, 0x5d, 0x66, 0x6d, 0xf8, 0x6d, 0x4a, 0xb5, 0x3c, 0x06, 0xde, 0xaf, 0x0e, + 0xfb, 0xf2, 0x02, 0x63, 0x0e, 0xe8, 0x90, 0x98, 0x90, 0x54, 0xec, 0x39, 0xbb, 0xb8, 0x60, 0x61, + 0x27, 0x16, 0x57, 0xca, 0xbe, 0x8a, 0x67, 0x20, 0x2a, 0x0b, 0x13, 0x11, 0xc4, 0x7e, 0xe7, 0x3c, + 0x67, 0xd1, 0x95, 0xb2, 0xb5, 0xe2, 0x95, 0x30, 0x99, 0x6b, 0x47, 0x1f, 0xf8, 0x21, 0x6d, 0xcb, + 0x2a, 0xf8, 0x96, 0x7d, 0x44, 0xd3, 0xcb, 0xa0, 0x8c, 0x6b, 0xef, 0xc1, 0xd5, 0xc9, 0xd8, 0xa3, + 0x65, 0xf6, 0x9d, 0xf0, 0x5c, 0xa6, 0xa6, 0xb2, 0xbb, 0xee, 0x15, 0xa4, 0x74, 0xa7, 0x0c, 0x6d, + 0xd4, 0x98, 0xd1, 0xee, 0x54, 0x84, 0xe4, 0xf7, 0x68, 0x37, 0x91, 0x85, 0x6a, 0x56, 0xf3, 0x23, + 0x29, 0x6b, 0x2c, 0x6e, 0x7d, 0xf1, 0x31, 0x65, 0x19, 0x8d, 0x1a, 0x55, 0xc5, 0x60, 0xa1, 0x52, + 0x1b, 0x79, 0x3f, 0x7d, 0xf6, 0x0b, 0x6d, 0x80, 0xd6, 0xa6, 0xa0, 0xa5, 0x3d, 0x7b, 0x71, 0x6c, + 0xd8, 0x33, 0xa7, 0xed, 0x29, 0x81, 0xf2, 0x5e, 0xc8, 0x6e, 0xb5, 0x51, 0x53, 0x8b, 0xea, 0x5b, + 0x9e, 0x7e, 0x9c, 0x25, 0xf2, 0x3d, 0x62, 0x09, 0x57, 0xab, 0x75, 0xe5, 0x2f, 0x0b, 0x95, 0xb7, + 0x44, 0x76, 0x2c, 0x34, 0x6a, 0xcc, 0xeb, 0x67, 0x5a, 0x53, 0xe4, 0x09, 0x2c, 0xf6, 0x39, 0x91, + 0x63, 0x41, 0x49, 0x18, 0xc0, 0xa5, 0x0f, 0x0a, 0x13, 0x17, 0xb5, 0x0f, 0x90, 0xdc, 0xfd, 0xbb, + 0x06, 0xb7, 0xfd, 0xe2, 0x55, 0x8a, 0x7c, 0x9a, 0x75, 0x59, 0x48, 0x49, 0xaa, 0x3a, 0xdd, 0xc1, + 0x36, 0x90, 0x3c, 0x29, 0x3f, 0x61, 0xe3, 0x86, 0x10, 0xe7, 0x3f, 0x37, 0xe2, 0xc5, 0xec, 0xeb, + 0xc2, 0xda, 0x88, 0xf1, 0x81, 0xfc, 0x77, 0x40, 0xce, 0x98, 0x39, 0xc4, 0x79, 0x7a, 0x43, 0x6e, + 0x3c, 0xf7, 0x07, 0x98, 0x2f, 0x8f, 0x12, 0xe4, 0xfe, 0x80, 0x80, 0xc1, 0x09, 0xc4, 0x79, 0x30, + 0x9e, 0x09, 0x85, 0xa7, 0xb0, 0xe2, 0xdf, 0xc4, 0x8d, 0xfe, 0x27, 0xb8, 0x71, 0x6c, 0x7b, 0x4e, + 0x5a, 0x40, 0x06, 0x1b, 0x70, 0xf2, 0x78, 0x40, 0xc4, 0xf0, 0x16, 0xdd, 0xd9, 0xbe, 0x9e, 0x11, + 0x0f, 0x3a, 0x81, 0x9a, 0xd9, 0x80, 0x11, 0xab, 0xb7, 0x19, 0xd2, 0x07, 0x3a, 0xee, 0x38, 0x16, + 0x14, 0x1b, 0x61, 0x36, 0x9a, 0x0d, 0x15, 0x79, 0x34, 0xa6, 0x6f, 0x32, 0x4a, 0x9b, 0xf3, 0xf8, + 0x5a, 0x3e, 0x3c, 0xe5, 0x08, 0xa0, 0xdf, 0x1e, 0x91, 0xcd, 0xf2, 0xb6, 0x81, 0x76, 0xca, 0xd9, + 0x1a, 0xcd, 0x80, 0x02, 0x7f, 0x84, 0x05, 0xab, 0x4b, 0x21, 0x56, 0x86, 0x0c, 0x6f, 0x7c, 0x9c, + 0x87, 0xd7, 0x70, 0xa1, 0xfc, 0x00, 0x16, 0xed, 0xc1, 0x85, 0x58, 0x5b, 0x47, 0xcc, 0x41, 0xce, + 0xa3, 0xeb, 0xd8, 0xfa, 0x3e, 0xe9, 0x0f, 0x30, 0xb6, 0x4f, 0x06, 0x26, 0x23, 0xdb, 0x27, 0x43, + 0x66, 0x9f, 0x77, 0x50, 0x2f, 0x4d, 0xcb, 0xc4, 0x1d, 0xb1, 0xc5, 0x74, 0xf5, 0xfd, 0xb1, 0x3c, + 0x28, 0xd9, 0x83, 0x39, 0x63, 0x4a, 0x22, 0x96, 0x2a, 0x83, 0x63, 0x95, 0x73, 0x6f, 0x0c, 0x47, + 0xbf, 0xfe, 0x8c, 0x18, 0x6f, 0xec, 0xfa, 0x33, 0x7e, 0xde, 0xb2, 0xeb, 0xcf, 0x35, 0x33, 0x93, + 0xac, 0x3f, 0xe5, 0xce, 0xd2, 0xae, 0x3f, 0x43, 0x5b, 0x55, 0xbb, 0xfe, 0x8c, 0x68, 0x4e, 0x4f, + 0xa0, 0x66, 0x3e, 0xf5, 0xf6, 0x25, 0x1d, 0xd2, 0x1e, 0x38, 0xee, 0x38, 0x16, 0x2d, 0x76, 0xf7, + 0x5d, 0xaf, 0x5f, 0x2a, 0xde, 0x8b, 0x97, 0x30, 0x53, 0x8c, 0x10, 0xeb, 0x56, 0x46, 0x97, 0x1a, + 0x2b, 0xe7, 0xee, 0x88, 0x55, 0x2d, 0xf9, 0x7c, 0x5a, 0xfd, 0xec, 0xfa, 0xe2, 0xdf, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xa3, 0x0c, 0x8f, 0xcd, 0xf9, 0x12, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1762,6 +1862,7 @@ type StakepooldServiceClient interface { ValidateAddress(ctx context.Context, in *ValidateAddressRequest, opts ...grpc.CallOption) (*ValidateAddressResponse, error) AddMissingTicket(ctx context.Context, in *AddMissingTicketRequest, opts ...grpc.CallOption) (*AddMissingTicketResponse, error) GetTickets(ctx context.Context, in *GetTicketsRequest, opts ...grpc.CallOption) (*GetTicketsResponse, error) + GetTicketInfo(ctx context.Context, in *GetTicketInfoRequest, opts ...grpc.CallOption) (*GetTicketInfoResponse, error) ListScripts(ctx context.Context, in *ListScriptsRequest, opts ...grpc.CallOption) (*ListScriptsResponse, error) AccountSyncAddressIndex(ctx context.Context, in *AccountSyncAddressIndexRequest, opts ...grpc.CallOption) (*AccountSyncAddressIndexResponse, error) CreateMultisig(ctx context.Context, in *CreateMultisigRequest, opts ...grpc.CallOption) (*CreateMultisigResponse, error) @@ -1875,6 +1976,15 @@ func (c *stakepooldServiceClient) GetTickets(ctx context.Context, in *GetTickets return out, nil } +func (c *stakepooldServiceClient) GetTicketInfo(ctx context.Context, in *GetTicketInfoRequest, opts ...grpc.CallOption) (*GetTicketInfoResponse, error) { + out := new(GetTicketInfoResponse) + err := c.cc.Invoke(ctx, "/stakepoolrpc.StakepooldService/GetTicketInfo", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *stakepooldServiceClient) ListScripts(ctx context.Context, in *ListScriptsRequest, opts ...grpc.CallOption) (*ListScriptsResponse, error) { out := new(ListScriptsResponse) err := c.cc.Invoke(ctx, "/stakepoolrpc.StakepooldService/ListScripts", in, out, opts...) @@ -1924,6 +2034,7 @@ type StakepooldServiceServer interface { ValidateAddress(context.Context, *ValidateAddressRequest) (*ValidateAddressResponse, error) AddMissingTicket(context.Context, *AddMissingTicketRequest) (*AddMissingTicketResponse, error) GetTickets(context.Context, *GetTicketsRequest) (*GetTicketsResponse, error) + GetTicketInfo(context.Context, *GetTicketInfoRequest) (*GetTicketInfoResponse, error) ListScripts(context.Context, *ListScriptsRequest) (*ListScriptsResponse, error) AccountSyncAddressIndex(context.Context, *AccountSyncAddressIndexRequest) (*AccountSyncAddressIndexResponse, error) CreateMultisig(context.Context, *CreateMultisigRequest) (*CreateMultisigResponse, error) @@ -1967,6 +2078,9 @@ func (*UnimplementedStakepooldServiceServer) AddMissingTicket(ctx context.Contex func (*UnimplementedStakepooldServiceServer) GetTickets(ctx context.Context, req *GetTicketsRequest) (*GetTicketsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetTickets not implemented") } +func (*UnimplementedStakepooldServiceServer) GetTicketInfo(ctx context.Context, req *GetTicketInfoRequest) (*GetTicketInfoResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetTicketInfo not implemented") +} func (*UnimplementedStakepooldServiceServer) ListScripts(ctx context.Context, req *ListScriptsRequest) (*ListScriptsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListScripts not implemented") } @@ -2182,6 +2296,24 @@ func _StakepooldService_GetTickets_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _StakepooldService_GetTicketInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTicketInfoRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(StakepooldServiceServer).GetTicketInfo(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/stakepoolrpc.StakepooldService/GetTicketInfo", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(StakepooldServiceServer).GetTicketInfo(ctx, req.(*GetTicketInfoRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _StakepooldService_ListScripts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListScriptsRequest) if err := dec(in); err != nil { @@ -2302,6 +2434,10 @@ var _StakepooldService_serviceDesc = grpc.ServiceDesc{ MethodName: "GetTickets", Handler: _StakepooldService_GetTickets_Handler, }, + { + MethodName: "GetTicketInfo", + Handler: _StakepooldService_GetTicketInfo_Handler, + }, { MethodName: "ListScripts", Handler: _StakepooldService_ListScripts_Handler, diff --git a/go.mod b/go.mod index 2f76edbd..90a93806 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/decred/dcrd/wire v1.2.0 github.com/decred/dcrdata/api/types/v4 v4.0.2 github.com/decred/dcrwallet/rpc/jsonrpc/types v1.1.0 + github.com/decred/dcrwallet/wallet v1.3.0 github.com/decred/dcrwallet/wallet/v2 v2.0.0 github.com/decred/slog v1.0.0 github.com/dgrijalva/jwt-go v3.2.0+incompatible diff --git a/go.sum b/go.sum index 824ed3bc..242888bf 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,7 @@ github.com/decred/dcrd/dcrec v0.0.0-20180721005212-59fe2b293f69/go.mod h1:cRAH1S github.com/decred/dcrd/dcrec v0.0.0-20180721005914-d26200ec716b/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= github.com/decred/dcrd/dcrec v0.0.0-20180721031028-5369a485acf6/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= github.com/decred/dcrd/dcrec v0.0.0-20180801202239-0761de129164/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= +github.com/decred/dcrd/dcrec v0.0.0-20181212181811-1a370d38d671/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= github.com/decred/dcrd/dcrec v0.0.0-20190130161649-59ed4247a1d5 h1:rz0HmQLuABmKj2fFaeYMpJSG66JrDhU3mQ1tDBeGfQk= github.com/decred/dcrd/dcrec v0.0.0-20190130161649-59ed4247a1d5/go.mod h1:cRAH1SNk8Mi9hKBc/DHbeiWz/fyO8KWZR3H7okrIuOA= github.com/decred/dcrd/dcrec v0.0.0-20190214012338-9265b4051009 h1:+VNyY6U5yMZCPE2UqHgJSOmKDjD6WYB+NlDCEj7ZYJg= @@ -83,6 +84,7 @@ github.com/decred/dcrd/dcrec/secp256k1 v1.0.1/go.mod h1:lhu4eZFSfTJWUnR3CFRcpD+V github.com/decred/dcrd/dcrec/secp256k1 v1.0.2 h1:awk7sYJ4pGWmtkiGHFfctztJjHMKGLV8jctGQhAbKe0= github.com/decred/dcrd/dcrec/secp256k1 v1.0.2/go.mod h1:CHTUIVfmDDd0KFVFpNX1pFVCBUegxW387nN0IGwNKR0= github.com/decred/dcrd/dcrjson v1.0.0/go.mod h1:ozddIaeF+EAvZZvFuB3zpfxhyxBGfvbt22crQh+PYuI= +github.com/decred/dcrd/dcrjson v1.1.0/go.mod h1:ozddIaeF+EAvZZvFuB3zpfxhyxBGfvbt22crQh+PYuI= github.com/decred/dcrd/dcrjson v1.2.0 h1:3BFFQHq3/YO/zae9WLxQkXsX6AXKx3+M8H3yk4oXZi0= github.com/decred/dcrd/dcrjson v1.2.0/go.mod h1:ozddIaeF+EAvZZvFuB3zpfxhyxBGfvbt22crQh+PYuI= github.com/decred/dcrd/dcrjson/v2 v2.0.0 h1:W0q4Alh36c5N318eUpfmU8kXoCNgImMLI87NIXni9Us= @@ -107,11 +109,14 @@ github.com/decred/dcrd/hdkeychain/v2 v2.0.0 h1:b6GklXT+LeDumc0bDqMHkss+p2Bu+mgiU github.com/decred/dcrd/hdkeychain/v2 v2.0.0/go.mod h1:tG+VpXfloIkNGHGd6NeoTElHWA68Wf1aP87zegXDGEw= github.com/decred/dcrd/hdkeychain/v2 v2.0.1 h1:LnMLuPDx6j1/7ywGdfX5onPOsa98yObzBIJrp+nK4Qo= github.com/decred/dcrd/hdkeychain/v2 v2.0.1/go.mod h1:qPv+vTla19liVHFuXVnQ70dMI4ERPCniDXbV5RzwQiM= +github.com/decred/dcrd/mempool v1.1.1 h1:ysFIS3HzEIJ88B1Y4OfL6wjzBurlChbKkzq54hPglGo= github.com/decred/dcrd/mempool v1.1.1/go.mod h1:u1I2KRv9UHhx2crlbZXYoLDabWyQ8VnnHDSG53UdhCA= +github.com/decred/dcrd/mining v1.1.0 h1:9Wtla+i+pEjfYsNCfixsipmyyoB26DgL4LSXWAin/zw= github.com/decred/dcrd/mining v1.1.0/go.mod h1:NQEtX604XgNwKcPFId1hVTTiBqmVQDlnqV1yNqGl4oU= github.com/decred/dcrd/rpc/jsonrpc/types v1.0.0 h1:d5ptnjuSADTQMa3i83VpeJNoMRTOJZZBqk7P+E41VXM= github.com/decred/dcrd/rpc/jsonrpc/types v1.0.0/go.mod h1:0dwmpIP21tJxjg/UuUHWIFMbfoLv2ifCBMokNKlOxpo= -github.com/decred/dcrd/rpcclient/v2 v2.0.0 h1:Zy9twdEaOGUdCj/89LAs/IrStm6FcabxzBve4UsA73A= +github.com/decred/dcrd/rpcclient v1.1.0 h1:nQZ1qOJaLYoOTM1oQ2dLaqocb5TWI7gNBK+BTY7UVXk= +github.com/decred/dcrd/rpcclient v1.1.0/go.mod h1:SCwBs4d+aqRV2ChnriIZ1y/LgNVHG/2ieEC1vIop82s= github.com/decred/dcrd/rpcclient/v2 v2.0.0/go.mod h1:9XjbRHBSNqN+DXz8I47gUZszvVjvugqLGK8TZQ4c/u0= github.com/decred/dcrd/rpcclient/v3 v3.0.0 h1:doabWkWWd8O6ccYqaLamE1mkQ5p8YoO5UJhkl5pAGpQ= github.com/decred/dcrd/rpcclient/v3 v3.0.0/go.mod h1:OQ361/iQMrJtWIg+S0XpZT4/AA6mU/46oOkBRWp/P/s= @@ -135,6 +140,7 @@ github.com/decred/dcrdata/semver v1.0.0 h1:DBqYU/x+4LqHq/3r4xKdF6xG5ewktG2KDC+g/ github.com/decred/dcrdata/semver v1.0.0/go.mod h1:z+nQqiAd9fYkHhBLbejysZ2FPHtgkrErWDgMf+JlZWE= github.com/decred/dcrdata/txhelpers/v3 v3.0.2 h1:ViaYNSyOR0FjNkUAEBXPyj6eBKWNFC7r2vCfTASaOeU= github.com/decred/dcrdata/txhelpers/v3 v3.0.2/go.mod h1:iukqEl5AR6BdippUWKsC8OzaV9TW/KMBYpIwKv8IkW0= +github.com/decred/dcrwallet/deployments v1.1.0 h1:83arg+7ct7PS1H2IYhuePnrBK2rkVpEYWKqrJpCwtHA= github.com/decred/dcrwallet/deployments v1.1.0/go.mod h1:8Sasryu8SX23Jvqr6maZ7MoS7wFIGXupWzbsVtcZsUg= github.com/decred/dcrwallet/errors v1.0.1 h1:8EF7IY6twRlo9sqWaSfm8abfi2/rHZ1wacOiGvBy+bM= github.com/decred/dcrwallet/errors v1.0.1/go.mod h1:XUm95dWmm9XmQGvneBXJkkIaFeRsQVBB6ni/KTy1hrY= @@ -146,7 +152,10 @@ github.com/decred/dcrwallet/rpc/jsonrpc/types v1.0.0 h1:FPVohgxZHHwi7qTOBki/Dw9j github.com/decred/dcrwallet/rpc/jsonrpc/types v1.0.0/go.mod h1:k+IOPnUY0YqlwhSDhczzaUN17NX/gMtztwl3UxKgVZY= github.com/decred/dcrwallet/rpc/jsonrpc/types v1.1.0 h1:ZOMpbSK/Cz8D8Yfrt7/yNfS+myBUWMNOdgAg31ND7bM= github.com/decred/dcrwallet/rpc/jsonrpc/types v1.1.0/go.mod h1:xUT7XXATLOzE0pwwmvgfRWtZdrB+PsWFilo+jkH5/Ig= +github.com/decred/dcrwallet/validate v1.0.2 h1:EXY48b3SOkTGXJAB+OUmilvyFNROZywYIWTGYfUnTDE= github.com/decred/dcrwallet/validate v1.0.2/go.mod h1:1ur2sRZkQ23ECalUKdwgx6rdIiP8rIiaSQAz1Y9LQsI= +github.com/decred/dcrwallet/wallet v1.3.0 h1:RQ4uHIfbDF0ppyeVDkTr7GapeABCddUUKOSexTN7X1M= +github.com/decred/dcrwallet/wallet v1.3.0/go.mod h1:ItOhnw3C4znuLQVWACSq8jCLy221v9X0Xo0b/j5WqgU= github.com/decred/dcrwallet/wallet/v2 v2.0.0 h1:ppSLjwu3DHmRWdnRbN53fu9AFjY0MqK40jzbZQAouG8= github.com/decred/dcrwallet/wallet/v2 v2.0.0/go.mod h1:uDSN0GRTH583eSnPyPiTXNcZ9DrQ2tBqdm/tjp5rjwY= github.com/decred/go-socks v1.0.0 h1:gd5+vE9LmL+FYiN5cxAMtaAVb791mG8E/+Jo411M/BE= @@ -177,12 +186,14 @@ github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyC github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= +github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/bitset v1.0.0 h1:Ws0PXV3PwXqWK2n7Vz6idCdrV/9OrBXgHEJi27ZB9Dw= github.com/jrick/bitset v1.0.0/go.mod h1:ZOYB5Uvkla7wIEY4FEssPVi3IQXa02arznRaYaAEPe4= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= @@ -224,6 +235,7 @@ github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxt github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= golang.org/x/crypto v0.0.0-20180718160520-a2144134853f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613 h1:MQ/ZZiDsUapFFiMS+vzwXkCTeEKaum+Do5rINYJDmxc= golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE= @@ -250,6 +262,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTm golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/helpers/v3auth.go b/helpers/v3auth.go deleted file mode 100644 index 7700ae00..00000000 --- a/helpers/v3auth.go +++ /dev/null @@ -1,61 +0,0 @@ -package helpers - -import ( - "github.com/decred/slog" - "strconv" - "strings" - "time" -) - -const ( - customAuthScheme = "TicketAuth" - customAuthTimestampParam = "SignedTimestamp" - customAuthSignatureParam = "Signature" - customAuthTicketHashParam = "TicketHash" - - authTimestampValiditySeconds = 30 -) - -func VerifyCustomAuthHeaderValue(authHeader string, log slog.Logger) (validated bool) { - if strings.HasPrefix(authHeader, customAuthScheme) { - return - } - - var timestampMessage, timestampSignature, ticketHash string - authParams := strings.Split(authHeader, ",") - for _, param := range authParams { - paramKeyValue := strings.Split(param, "=") - if len(paramKeyValue) != 2 { - continue - } - if key := strings.TrimSpace(paramKeyValue[0]); key == customAuthTimestampParam { - timestampMessage = strings.TrimSpace(paramKeyValue[1]) - } else if key == customAuthSignatureParam { - timestampSignature = strings.TrimSpace(paramKeyValue[1]) - } else if key == customAuthTicketHashParam { - ticketHash = strings.TrimSpace(paramKeyValue[1]) - } - } - - if timestampMessage == "" || timestampSignature == "" || ticketHash == "" { - log.Warnf("invalid v3 auth header value %s", authHeader) - return - } - - authTimestamp, err := strconv.Atoi(timestampMessage) - if err != nil { - log.Warnf("invalid v3 auth request timestamp %v: %v", authHeader, err) - return - } - - // todo ensure that timestamp had not been used in a previous authentication attempt - - // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. - timestampDelta := time.Now().Unix() - int64(authTimestamp) - if timestampDelta < 0 || timestampDelta > authTimestampValiditySeconds { - log.Warnf("expired v3 auth request timestamp %v: %v", authHeader, timestampDelta) - return - } - - return true -} diff --git a/models/user.go b/models/user.go index a693cd1f..31f3c8cb 100644 --- a/models/user.go +++ b/models/user.go @@ -168,6 +168,16 @@ func GetUserById(dbMap *gorp.DbMap, id int64) (user *User, err error) { return user, nil } +func GetUserByMSA(dbMap *gorp.DbMap, msa string) (user *User, err error) { + err = dbMap.SelectOne(&user, "SELECT * FROM Users WHERE MultiSigAddress = ?", msa) + + if err != nil { + return nil, err + } + + return user, nil +} + // GetUserCount gives a count of all users func GetUserCount(dbMap *gorp.DbMap) int64 { userCount, err := dbMap.SelectInt("SELECT COUNT(*) FROM Users") diff --git a/server.go b/server.go index 4b9a55f1..b546fd31 100644 --- a/server.go +++ b/server.go @@ -23,6 +23,7 @@ import ( "github.com/zenazn/goji/graceful" "github.com/zenazn/goji/web" "github.com/zenazn/goji/web/middleware" + "github.com/decred/dcrstakepool/v3api" ) var ( @@ -165,8 +166,12 @@ func runMain() error { api.Use(application.ApplyAPI) + v3Api := v3api.New(stakepooldConnMan) + api.Use(v3Api.ApplyTicketAuth) + api.Handle("/api/v1/:command", application.APIHandler(controller.API)) api.Handle("/api/v2/:command", application.APIHandler(controller.API)) + api.Handle("/api/v3/:command", application.APIHandler(controller.API)) api.Handle("/api/*", gojify(system.APIInvalidHandler)) // HTML routes diff --git a/stakepooldclient/stakepooldclient.go b/stakepooldclient/stakepooldclient.go index cedbbbe3..fa869a90 100644 --- a/stakepooldclient/stakepooldclient.go +++ b/stakepooldclient/stakepooldclient.go @@ -116,6 +116,25 @@ func (s *StakepooldManager) connected() error { return nil } +// GetAddedLowFeeTickets performs gRPC GetAddedLowFeeTickets +// requests against all stakepoold instances and returns the first result fetched +// without errors. Returns an error if all RPC requests fail. +func (s *StakepooldManager) GetTicketInfo(hash []byte) (*pb.GetTicketInfoResponse, error) { + for i, conn := range s.grpcConnections { + client := pb.NewStakepooldServiceClient(conn) + resp, err := client.GetTicketInfo(context.Background(), &pb.GetTicketInfoRequest{Hash:hash}) + if err != nil { + log.Warnf("GetTicketInfo RPC failed on stakepoold instance %d: %v", i, err) + continue + } + + return resp, err + } + + // All RPC requests failed + return nil, errors.New("GetTicketInfo RPC failed on all stakepoold instances") +} + // GetAddedLowFeeTickets performs gRPC GetAddedLowFeeTickets // requests against all stakepoold instances and returns the first result fetched // without errors. Returns an error if all RPC requests fail. diff --git a/v3api/middleware.go b/v3api/middleware.go index 3fadf073..98a138a1 100644 --- a/v3api/middleware.go +++ b/v3api/middleware.go @@ -1 +1,33 @@ package v3api + +import ( + "net/http" + "strings" + + "github.com/decred/dcrstakepool/models" + "github.com/go-gorp/gorp" + "github.com/zenazn/goji/web" +) + +func (v3Api *V3API) ApplyTicketAuth(c *web.C, h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if strings.HasPrefix(r.URL.Path, "/api/v3") { + authHeader := r.Header.Get("Authorization") + msa := v3Api.validateTicketOwnership(authHeader) + if msa != "" { + dbMap := c.Env["DbMap"].(*gorp.DbMap) + + user, err := models.GetUserByMSA(dbMap, msa) + if err != nil { + log.Errorf("unable to map ticket auth %v with multisig address %v to user", + authHeader, msa) + } else { + c.Env["APIUserID"] = user.Id + log.Infof("mapped ticket auth %v to user id %v", authHeader, user.Id) + } + } + } + h.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) +} diff --git a/v3api/ticketauth.go b/v3api/ticketauth.go new file mode 100644 index 00000000..60093492 --- /dev/null +++ b/v3api/ticketauth.go @@ -0,0 +1,109 @@ +package v3api + +import ( + "strconv" + "strings" + "time" + "fmt" + "encoding/base64" + + "github.com/decred/dcrd/dcrutil" + "github.com/decred/dcrwallet/wallet" +) + +const ( + customAuthScheme = "TicketAuth" + customAuthTimestampParam = "SignedTimestamp" + customAuthSignatureParam = "Signature" + customAuthTicketHashParam = "TicketHash" + + authTimestampValiditySeconds = 30 +) + +func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress string) { + if strings.HasPrefix(authHeader, customAuthScheme) { + return + } + + timestamp, timestampSignature, ticketHash := extractAuthParams(authHeader) + if timestamp == "" || timestampSignature == "" || ticketHash == "" { + log.Warnf("invalid API v3 auth header value %s", authHeader) + return + } + + // confirm that the timestamp signature is a valid base64 string + decodedSignature, err := base64.StdEncoding.DecodeString(timestampSignature) + if err != nil { + log.Warnf("invalid API v3 signature %s", timestampSignature) + return + } + + // todo check if ticket belongs to this vsp + + // check if timestamp is not yet expired + if err := validateTimestamp(timestamp); err != nil { + log.Warnf("ticket auth timestamp failed validation: %v", err) + return + } + + // get user wallet address using ticket hash + // todo: may be better to maintain a memory map of tickets-userWalletAddresses + ticketInfo, err := v3Api.stakepooldConnMan.GetTicketInfo([]byte(ticketHash)) + if err != nil { + log.Warnf("ticket auth, get ticket info failed: %v", err) + return + } + + // check if timestamp signature checks out against address + addr, err := dcrutil.DecodeAddress(ticketInfo.OwnerFeeAddress) + if err != nil { + log.Errorf("ticket auth, unexpected decode address error: %v", err) + return + } + + valid, err := wallet.VerifyMessage(timestamp, addr, decodedSignature) + if err != nil { + log.Errorf("error validating timestamp signature for ticket auth %v", err) + return + } + + if valid { + multiSigAddress = ticketInfo.MultiSigAddress + } + return +} + +func extractAuthParams(authHeader string) (timestampMessage, timestampSignature, ticketHash string) { + authParams := strings.Split(authHeader, ",") + for _, param := range authParams { + paramKeyValue := strings.Split(param, "=") + if len(paramKeyValue) != 2 { + continue + } + if key := strings.TrimSpace(paramKeyValue[0]); key == customAuthTimestampParam { + timestampMessage = strings.TrimSpace(paramKeyValue[1]) + } else if key == customAuthSignatureParam { + timestampSignature = strings.TrimSpace(paramKeyValue[1]) + } else if key == customAuthTicketHashParam { + ticketHash = strings.TrimSpace(paramKeyValue[1]) + } + } + return +} + +func validateTimestamp(timestampMessage string) (error) { + authTimestamp, err := strconv.Atoi(timestampMessage) + if err != nil { + return fmt.Errorf("invalid v3 auth request timestamp %v: %v", timestampMessage, err) + } + + // todo ensure that timestamp had not been used in a previous authentication attempt + + // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. + timestampDelta := time.Now().Unix() - int64(authTimestamp) + if timestampDelta < 0 || timestampDelta > authTimestampValiditySeconds { + return fmt.Errorf("expired v3 auth request timestamp %v: %v", timestampMessage, timestampDelta) + } + + return nil +} diff --git a/v3api/v3api.go b/v3api/v3api.go new file mode 100644 index 00000000..9d389da3 --- /dev/null +++ b/v3api/v3api.go @@ -0,0 +1,13 @@ +package v3api + +import "github.com/decred/dcrstakepool/stakepooldclient" + +type V3API struct { + stakepooldConnMan *stakepooldclient.StakepooldManager +} + +func New(stakepooldConnMan *stakepooldclient.StakepooldManager) *V3API { + return &V3API{ + stakepooldConnMan: stakepooldConnMan, + } +} From 5f85ccc5720962ceb64be5692cb9e84ef4e1db84 Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 1 Aug 2019 04:51:22 +0100 Subject: [PATCH 05/18] fix v3 auth header check --- v3api/ticketauth.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/v3api/ticketauth.go b/v3api/ticketauth.go index 60093492..34e9a6cb 100644 --- a/v3api/ticketauth.go +++ b/v3api/ticketauth.go @@ -21,7 +21,8 @@ const ( ) func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress string) { - if strings.HasPrefix(authHeader, customAuthScheme) { + if !strings.HasPrefix(authHeader, customAuthScheme) { + log.Warnf("invalid API v3 auth header value %s", authHeader) return } From 486d0d500e95cfbc4a7ddaec2de47a7124871701 Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 1 Aug 2019 05:00:19 +0100 Subject: [PATCH 06/18] fix auth header parsing --- v3api/ticketauth.go | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/v3api/ticketauth.go b/v3api/ticketauth.go index 34e9a6cb..fb2314ee 100644 --- a/v3api/ticketauth.go +++ b/v3api/ticketauth.go @@ -26,7 +26,7 @@ func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress return } - timestamp, timestampSignature, ticketHash := extractAuthParams(authHeader) + timestamp, timestampSignature, ticketHash := extractAuthParams(strings.TrimPrefix(authHeader, customAuthScheme)) if timestamp == "" || timestampSignature == "" || ticketHash == "" { log.Warnf("invalid API v3 auth header value %s", authHeader) return @@ -77,21 +77,26 @@ func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress func extractAuthParams(authHeader string) (timestampMessage, timestampSignature, ticketHash string) { authParams := strings.Split(authHeader, ",") for _, param := range authParams { - paramKeyValue := strings.Split(param, "=") - if len(paramKeyValue) != 2 { - continue - } - if key := strings.TrimSpace(paramKeyValue[0]); key == customAuthTimestampParam { - timestampMessage = strings.TrimSpace(paramKeyValue[1]) - } else if key == customAuthSignatureParam { - timestampSignature = strings.TrimSpace(paramKeyValue[1]) - } else if key == customAuthTicketHashParam { - ticketHash = strings.TrimSpace(paramKeyValue[1]) + paramKeyValue := strings.TrimSpace(param) + if value := getAuthValueFromParam(paramKeyValue, customAuthTimestampParam); value != "" { + timestampMessage = strings.TrimSpace(value) + } else if value := getAuthValueFromParam(paramKeyValue, customAuthSignatureParam); value != "" { + timestampSignature = strings.TrimSpace(value) + } else if value := getAuthValueFromParam(paramKeyValue, customAuthTicketHashParam); value != "" { + ticketHash = strings.TrimSpace(value) } } return } +func getAuthValueFromParam(paramKeyValue, key string) string { + keyPrefix := key+"=" + if strings.HasPrefix(paramKeyValue, keyPrefix) { + return strings.TrimPrefix(paramKeyValue, keyPrefix) + } + return "" +} + func validateTimestamp(timestampMessage string) (error) { authTimestamp, err := strconv.Atoi(timestampMessage) if err != nil { From a3e401b1bb5ca0af0c69b05d09425fc3178af22c Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 1 Aug 2019 05:02:51 +0100 Subject: [PATCH 07/18] fix timestamp expired log message --- v3api/ticketauth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/v3api/ticketauth.go b/v3api/ticketauth.go index fb2314ee..8c2a898c 100644 --- a/v3api/ticketauth.go +++ b/v3api/ticketauth.go @@ -108,7 +108,7 @@ func validateTimestamp(timestampMessage string) (error) { // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. timestampDelta := time.Now().Unix() - int64(authTimestamp) if timestampDelta < 0 || timestampDelta > authTimestampValiditySeconds { - return fmt.Errorf("expired v3 auth request timestamp %v: %v", timestampMessage, timestampDelta) + return fmt.Errorf("expired v3 auth request timestamp %v compared to %v", timestampMessage, time.Now().Unix()) } return nil From 6143b9be14c192b8cc8bd9dad1d4281974eeadb1 Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 1 Aug 2019 10:25:01 +0100 Subject: [PATCH 08/18] tested ticket ownership validation routine --- backend/stakepoold/rpc/api.proto | 2 +- backend/stakepoold/rpc/rpcserver/context.go | 59 +++--- backend/stakepoold/rpc/rpcserver/server.go | 2 +- backend/stakepoold/rpc/stakepoolrpc/api.pb.go | 196 +++++++++--------- controllers/main.go | 8 +- server.go | 2 +- stakepooldclient/stakepooldclient.go | 4 +- v3api/ticketauth.go | 12 +- 8 files changed, 142 insertions(+), 143 deletions(-) diff --git a/backend/stakepoold/rpc/api.proto b/backend/stakepoold/rpc/api.proto index ce34a5a5..07f98c1e 100644 --- a/backend/stakepoold/rpc/api.proto +++ b/backend/stakepoold/rpc/api.proto @@ -41,7 +41,7 @@ message GetLiveTicketsResponse { } message GetTicketInfoRequest { - bytes Hash = 1; + string Hash = 1; } message GetTicketInfoResponse { string MultiSigAddress = 1; diff --git a/backend/stakepoold/rpc/rpcserver/context.go b/backend/stakepoold/rpc/rpcserver/context.go index 9cc5ed33..6d8284d7 100644 --- a/backend/stakepoold/rpc/rpcserver/context.go +++ b/backend/stakepoold/rpc/rpcserver/context.go @@ -16,6 +16,7 @@ import ( wallettypes "github.com/decred/dcrwallet/rpc/jsonrpc/types" "github.com/decred/dcrd/rpcclient/v3" + "github.com/decred/dcrd/txscript" "github.com/decred/dcrd/wire" "github.com/decred/dcrstakepool/backend/stakepoold/userdata" "github.com/decred/dcrwallet/wallet/v2/txrules" @@ -100,9 +101,9 @@ type ticketMetadata struct { } type TicketInfo struct { - MultiSigAddress string - VspFeeAddress string - OwnerFeeAddress string + MultiSigAddress string + VspFeeAddress string + OwnerFeeAddress string } // EvaluateStakePoolTicket evaluates a voting service ticket to see if it's @@ -375,8 +376,8 @@ func (ctx *AppContext) GetTickets(includeImmature bool) ([]*chainhash.Hash, erro return tickets, nil } -func (ctx *AppContext) GetTicketInfo(ticketHash []byte) (ticketInfo *TicketInfo, err error) { - hash, err := chainhash.NewHash(ticketHash) +func (ctx *AppContext) GetTicketInfo(ticketHash string) (ticketInfo *TicketInfo, err error) { + hash, err := chainhash.NewHashFromStr(ticketHash) if err != nil { log.Errorf("GetTicketInfo: Failed to parse ticket hash: %v", err) return @@ -388,34 +389,32 @@ func (ctx *AppContext) GetTicketInfo(ticketHash []byte) (ticketInfo *TicketInfo, return } - for i := range res.Details { - _, ok := ctx.UserVotingConfig[res.Details[i].Address] - if ok { - // multisigaddress will match if it belongs a pool user - ticketInfo.MultiSigAddress = res.Details[i].Address + // get txout addresses using tx hes + msgTx, err := MsgTxFromHex(res.Hex) + if err != nil { + return nil, fmt.Errorf("GetTicketInfo: MsgTxFromHex failed for %v: %v", res.Hex, err) + } - // get sstxcommitment addresses using tx hes - msgTx, err := MsgTxFromHex(res.Hex) - if err != nil { - log.Warnf("MsgTxFromHex failed for %v: %v", res.Hex, err) - continue - } - break + p2shOut := msgTx.TxOut[0] + _, addrs, _, _ := txscript.ExtractPkScriptAddrs(p2shOut.Version, p2shOut.PkScript, ctx.Params) + multiSigAddress := addrs[0] - vspCommitmentOut := msgTx.TxOut[1] - vspCommitAddr, err := stake.AddrFromSStxPkScrCommitment(vspCommitmentOut.PkScript, ctx.Params) - if err != nil { - return nil, fmt.Errorf("Failed to parse commit out addr: %s", err.Error()) - } - ticketInfo.VspFeeAddress = vspCommitAddr.EncodeAddress() + vspCommitmentOut := msgTx.TxOut[1] + vspCommitAddr, err := stake.AddrFromSStxPkScrCommitment(vspCommitmentOut.PkScript, ctx.Params) + if err != nil { + return nil, fmt.Errorf("GetTicketInfo: Failed to parse commit out addr: %s", err.Error()) + } - ownerCommitmentOut := msgTx.TxOut[3] - ownerCommitAddr, err := stake.AddrFromSStxPkScrCommitment(ownerCommitmentOut.PkScript, ctx.Params) - if err != nil { - return nil, fmt.Errorf("Failed to parse commit out addr: %s", err.Error()) - } - ticketInfo.OwnerFeeAddress = ownerCommitAddr.EncodeAddress() - } + ownerCommitmentOut := msgTx.TxOut[3] + ownerCommitAddr, err := stake.AddrFromSStxPkScrCommitment(ownerCommitmentOut.PkScript, ctx.Params) + if err != nil { + return nil, fmt.Errorf("GetTicketInfo: Failed to parse commit out addr: %s", err.Error()) + } + + ticketInfo = &TicketInfo{ + MultiSigAddress: multiSigAddress.EncodeAddress(), + VspFeeAddress: vspCommitAddr.EncodeAddress(), + OwnerFeeAddress: ownerCommitAddr.EncodeAddress(), } return } diff --git a/backend/stakepoold/rpc/rpcserver/server.go b/backend/stakepoold/rpc/rpcserver/server.go index e296764c..031efa97 100644 --- a/backend/stakepoold/rpc/rpcserver/server.go +++ b/backend/stakepoold/rpc/rpcserver/server.go @@ -194,7 +194,7 @@ func (s *stakepooldServer) GetTicketInfo(ctx context.Context, req *pb.GetTicketI return &pb.GetTicketInfoResponse{ OwnerFeeAddress: ticketInfo.OwnerFeeAddress, - VspFeeAddress: ticketInfo.VspFeeAddress, + VspFeeAddress: ticketInfo.VspFeeAddress, MultiSigAddress: ticketInfo.MultiSigAddress, }, nil } diff --git a/backend/stakepoold/rpc/stakepoolrpc/api.pb.go b/backend/stakepoold/rpc/stakepoolrpc/api.pb.go index 3d9ee4f6..6685a6b5 100644 --- a/backend/stakepoold/rpc/stakepoolrpc/api.pb.go +++ b/backend/stakepoold/rpc/stakepoolrpc/api.pb.go @@ -235,7 +235,7 @@ func (m *GetLiveTicketsResponse) GetTickets() []*Ticket { } type GetTicketInfoRequest struct { - Hash []byte `protobuf:"bytes,1,opt,name=Hash,proto3" json:"Hash,omitempty"` + Hash string `protobuf:"bytes,1,opt,name=Hash,proto3" json:"Hash,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -266,11 +266,11 @@ func (m *GetTicketInfoRequest) XXX_DiscardUnknown() { var xxx_messageInfo_GetTicketInfoRequest proto.InternalMessageInfo -func (m *GetTicketInfoRequest) GetHash() []byte { +func (m *GetTicketInfoRequest) GetHash() string { if m != nil { return m.Hash } - return nil + return "" } type GetTicketInfoResponse struct { @@ -1742,101 +1742,101 @@ func init() { func init() { proto.RegisterFile("api.proto", fileDescriptor_00212fb1f9d3bf1c) } var fileDescriptor_00212fb1f9d3bf1c = []byte{ - // 1499 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x58, 0x5b, 0x4f, 0x1c, 0xc7, - 0x12, 0xd6, 0x02, 0x06, 0xb6, 0xd8, 0x05, 0xdc, 0xdc, 0xd6, 0x23, 0x0c, 0x78, 0x7c, 0x43, 0x3e, - 0xc7, 0x1c, 0x1d, 0x22, 0xe5, 0x25, 0x4a, 0x24, 0xf0, 0x05, 0xaf, 0x62, 0x02, 0x9e, 0x31, 0xc4, - 0x52, 0xa4, 0xa0, 0x61, 0xa6, 0x59, 0x3a, 0x9e, 0xed, 0x99, 0xcc, 0xf4, 0xae, 0x4d, 0x9e, 0xf2, - 0x03, 0xf2, 0x12, 0x29, 0xef, 0x79, 0xce, 0x63, 0x7e, 0x42, 0xfe, 0x59, 0xd4, 0xdd, 0x35, 0xbb, - 0x3d, 0xbd, 0x17, 0xb0, 0xdf, 0xa6, 0xbe, 0xae, 0xae, 0xae, 0x5b, 0x57, 0x57, 0x0d, 0x54, 0x83, - 0x94, 0xed, 0xa4, 0x59, 0x22, 0x12, 0x52, 0xcb, 0x45, 0xf0, 0x9e, 0xa6, 0x49, 0x12, 0x67, 0x69, - 0xe8, 0x6e, 0xc0, 0xfa, 0x01, 0x15, 0x7b, 0x51, 0x44, 0xa3, 0xd7, 0xc9, 0x87, 0x97, 0x94, 0xbe, - 0x65, 0xe1, 0x7b, 0x2a, 0x72, 0x8f, 0xfe, 0xdc, 0xa1, 0xb9, 0x70, 0x8f, 0xe0, 0xee, 0x88, 0xf5, - 0x3c, 0x4d, 0x78, 0x4e, 0xc9, 0x0e, 0xcc, 0x08, 0x0d, 0x35, 0x2a, 0x5b, 0x93, 0xdb, 0x73, 0xbb, - 0xcb, 0x3b, 0xe6, 0x01, 0x3b, 0x9a, 0xdf, 0x2b, 0x98, 0xdc, 0x2d, 0xd8, 0x38, 0xa0, 0xa2, 0xd9, - 0xe2, 0x49, 0x36, 0xe2, 0xc8, 0x37, 0xb0, 0x39, 0x92, 0xe3, 0x33, 0x0f, 0x5d, 0x83, 0x95, 0x03, - 0x2a, 0x5e, 0xb3, 0xae, 0x7d, 0xd6, 0x2b, 0x58, 0xb5, 0x17, 0x3e, 0xf3, 0x88, 0x27, 0xb0, 0x7c, - 0x40, 0x85, 0x46, 0x9b, 0xfc, 0x22, 0xc1, 0x13, 0x08, 0x81, 0xa9, 0x57, 0x41, 0x7e, 0xd9, 0xa8, - 0x6c, 0x55, 0xb6, 0x6b, 0x9e, 0xfa, 0x76, 0x7f, 0xaf, 0x28, 0x7d, 0x4c, 0x66, 0x3c, 0x75, 0x1b, - 0x16, 0x0e, 0x3b, 0xb1, 0x60, 0x3e, 0x6b, 0xed, 0x45, 0x51, 0x46, 0xf3, 0x5c, 0x6d, 0xac, 0x7a, - 0x36, 0x4c, 0x1e, 0x40, 0xfd, 0x34, 0x4f, 0x5f, 0x52, 0x5a, 0xf0, 0x4d, 0x28, 0xbe, 0x32, 0x28, - 0xe5, 0x1d, 0x7d, 0xe0, 0x34, 0x33, 0xf8, 0x26, 0xb5, 0x3c, 0x0b, 0x76, 0xbf, 0x83, 0x75, 0x7f, - 0x4c, 0x22, 0x7c, 0xb2, 0x3f, 0x36, 0xe1, 0xae, 0x3f, 0x2e, 0x71, 0xdc, 0x75, 0x70, 0x7c, 0x2a, - 0x4e, 0x72, 0x9a, 0x9d, 0x26, 0x82, 0xf1, 0xd6, 0x71, 0x46, 0x2f, 0xfa, 0xab, 0x1c, 0xee, 0x0c, - 0x5b, 0xd5, 0xba, 0xbc, 0x01, 0xd2, 0xc9, 0x69, 0x76, 0xd6, 0x55, 0x4b, 0x67, 0x61, 0xc2, 0x2f, - 0x58, 0x0b, 0xd5, 0xba, 0x5f, 0x56, 0xab, 0x2f, 0xe1, 0x99, 0xe2, 0x7a, 0xc1, 0x45, 0x76, 0xe5, - 0x2d, 0x76, 0x2c, 0xd8, 0x7d, 0x0a, 0x6b, 0x7b, 0x51, 0x74, 0xc8, 0xf2, 0x9c, 0xf1, 0x16, 0xda, - 0x32, 0x26, 0x82, 0x0e, 0x34, 0x06, 0xd9, 0x51, 0xf5, 0xaf, 0xe1, 0x76, 0x2f, 0xb8, 0x3d, 0x95, - 0xb7, 0x61, 0xa1, 0xc9, 0xc3, 0xb8, 0x13, 0xd1, 0x66, 0xbb, 0x1d, 0x88, 0x4e, 0x46, 0x95, 0xbc, - 0x59, 0xcf, 0x86, 0xdd, 0x1d, 0x20, 0xe6, 0x76, 0x4c, 0x8c, 0x06, 0xcc, 0xbc, 0x35, 0xdc, 0x5f, - 0xf3, 0x0a, 0xd2, 0x5d, 0x06, 0xf2, 0x9a, 0xe5, 0xc2, 0x0f, 0x33, 0x96, 0xf6, 0x13, 0xfb, 0x7f, - 0xb0, 0x54, 0x42, 0xfb, 0x62, 0x10, 0x2a, 0xc4, 0x20, 0xe9, 0x5e, 0xc2, 0xc6, 0x5e, 0x18, 0x26, - 0x1d, 0x2e, 0xfc, 0x2b, 0x1e, 0x62, 0x56, 0x34, 0x79, 0x44, 0x3f, 0x16, 0x26, 0x34, 0x60, 0x06, - 0x39, 0x30, 0x27, 0x0b, 0x92, 0xac, 0xc2, 0xf4, 0x7e, 0x16, 0xf0, 0xf0, 0x52, 0x25, 0x61, 0xdd, - 0x43, 0x8a, 0x2c, 0xc3, 0x2d, 0x25, 0x41, 0xe5, 0xdc, 0xa4, 0xa7, 0x09, 0xf7, 0x1e, 0x6c, 0x8e, - 0x3c, 0x09, 0x5d, 0xc8, 0x60, 0xa9, 0xd9, 0x4e, 0x93, 0x0c, 0xf5, 0x2f, 0x34, 0x58, 0x85, 0x69, - 0x0d, 0x60, 0x2c, 0x90, 0x92, 0xb8, 0x47, 0xf3, 0x30, 0xe0, 0xea, 0xfc, 0x59, 0x0f, 0x29, 0xe2, - 0x42, 0x4d, 0x7f, 0xbd, 0xa2, 0xac, 0x75, 0x29, 0x50, 0x8d, 0x12, 0xe6, 0x7e, 0x03, 0xcb, 0xe5, - 0xa3, 0xd0, 0x53, 0x8f, 0x60, 0x5e, 0x73, 0xe8, 0x55, 0x1a, 0xa9, 0x33, 0x27, 0x3d, 0x0b, 0x75, - 0x9f, 0x43, 0xc3, 0x97, 0x09, 0x77, 0x9c, 0x24, 0xb1, 0x4c, 0x36, 0xf3, 0xee, 0xdf, 0xf8, 0x36, - 0xbb, 0xbf, 0x56, 0xe0, 0xce, 0x10, 0x31, 0xa8, 0xcb, 0x57, 0xe5, 0xe0, 0xcf, 0xed, 0xde, 0x2b, - 0x27, 0x79, 0x69, 0x67, 0x71, 0x11, 0x71, 0x87, 0x34, 0xa4, 0xc9, 0xbb, 0x41, 0xcc, 0xa2, 0x42, - 0xc6, 0xc4, 0xd6, 0xe4, 0x76, 0xd5, 0xb3, 0x50, 0x77, 0x09, 0x6e, 0x7f, 0x1f, 0xc4, 0x71, 0xa9, - 0x7a, 0xb9, 0x7f, 0x54, 0x80, 0x98, 0x28, 0x2a, 0xb4, 0x05, 0x73, 0xa7, 0x89, 0xa0, 0xa7, 0x34, - 0xcb, 0x59, 0xc2, 0x95, 0x51, 0x75, 0xcf, 0x84, 0xa4, 0xe9, 0xcf, 0x03, 0xda, 0x4e, 0xf8, 0xb3, - 0x84, 0x73, 0x1a, 0x4a, 0xff, 0xe9, 0xd8, 0xd8, 0x30, 0x71, 0x60, 0xf6, 0x84, 0xc7, 0x49, 0xf8, - 0x9e, 0x46, 0x2a, 0x40, 0xb3, 0x5e, 0x8f, 0x96, 0x81, 0xd5, 0xb7, 0xb4, 0x31, 0xa5, 0x03, 0xab, - 0x29, 0x77, 0x17, 0x56, 0x4f, 0xa5, 0xee, 0x81, 0x28, 0xea, 0x97, 0x99, 0xa4, 0x25, 0x57, 0x17, - 0xa4, 0xfb, 0x06, 0xd6, 0x06, 0xf6, 0xa0, 0x39, 0xab, 0x30, 0xdd, 0xcc, 0x0f, 0x19, 0x2f, 0xee, - 0x24, 0x52, 0x64, 0x03, 0xe0, 0xb8, 0x73, 0xfe, 0x2d, 0xbd, 0x92, 0x1b, 0xb0, 0xc0, 0x1a, 0x88, - 0xfb, 0x7f, 0x58, 0x79, 0x96, 0xd1, 0x40, 0x50, 0x15, 0xce, 0x9c, 0xb5, 0x86, 0x6a, 0x31, 0x69, - 0x6a, 0x71, 0x0a, 0xab, 0xf6, 0x16, 0x54, 0x42, 0x25, 0x6b, 0x44, 0x69, 0xdb, 0x48, 0xf1, 0xaa, - 0x57, 0xc2, 0x4c, 0xb9, 0x13, 0x65, 0xeb, 0xfe, 0xaa, 0xc0, 0xd2, 0x90, 0x34, 0x50, 0x57, 0x46, - 0x04, 0xa2, 0x53, 0xb8, 0x03, 0x29, 0x89, 0x6b, 0x0e, 0x14, 0x84, 0x94, 0xd4, 0x42, 0x7f, 0x19, - 0x57, 0xa6, 0xee, 0x95, 0x30, 0x55, 0x44, 0x52, 0xca, 0xc5, 0xfe, 0x95, 0x0a, 0x4b, 0xd5, 0x2b, - 0x48, 0xf9, 0x28, 0xe1, 0x27, 0x6e, 0xbf, 0xa5, 0xb6, 0x97, 0x41, 0xf7, 0xcb, 0xe2, 0xec, 0xd1, - 0xd1, 0xea, 0x15, 0xdd, 0x09, 0xa3, 0xe8, 0xfe, 0x59, 0x81, 0x95, 0xa1, 0xf5, 0x5c, 0x5a, 0xa3, - 0x2e, 0x4d, 0x71, 0x49, 0x91, 0x1a, 0x76, 0x01, 0x27, 0x86, 0x3f, 0xa7, 0x0e, 0xcc, 0xca, 0xf4, - 0xdd, 0x67, 0x22, 0xc7, 0x32, 0xd1, 0xa3, 0xa5, 0x94, 0xe2, 0xbb, 0xc8, 0xf8, 0x29, 0xc5, 0x62, - 0xc3, 0xee, 0x22, 0xcc, 0xe3, 0x67, 0x71, 0x81, 0xfe, 0xa9, 0xc0, 0x42, 0x0f, 0xc2, 0x48, 0x3f, - 0x84, 0xf9, 0xae, 0x86, 0xce, 0x72, 0x91, 0xc9, 0xec, 0xd6, 0xc6, 0xd7, 0x11, 0xf5, 0x15, 0x28, - 0xab, 0x67, 0x3b, 0xf8, 0x29, 0xc9, 0xb0, 0xa8, 0x6a, 0x42, 0xa1, 0x8c, 0x27, 0x19, 0x46, 0x46, - 0x13, 0x12, 0x4d, 0x03, 0x11, 0x5e, 0x2a, 0xc5, 0xea, 0x9e, 0x26, 0x64, 0xfe, 0xa6, 0x19, 0xcd, - 0x68, 0x4c, 0x83, 0x9c, 0xaa, 0x58, 0x54, 0x3d, 0x03, 0x91, 0x8a, 0x9c, 0x77, 0x58, 0x1c, 0x9d, - 0xb5, 0xa9, 0x08, 0xa2, 0x40, 0x04, 0x8d, 0x69, 0xad, 0x88, 0x42, 0x0f, 0x11, 0x74, 0x57, 0x60, - 0xe9, 0x80, 0x0a, 0x95, 0x5d, 0x66, 0x6d, 0xf8, 0x6d, 0x4a, 0xb5, 0x3c, 0x06, 0xde, 0xaf, 0x0e, - 0xfb, 0xf2, 0x02, 0x63, 0x0e, 0xe8, 0x90, 0x98, 0x90, 0x54, 0xec, 0x39, 0xbb, 0xb8, 0x60, 0x61, - 0x27, 0x16, 0x57, 0xca, 0xbe, 0x8a, 0x67, 0x20, 0x2a, 0x0b, 0x13, 0x11, 0xc4, 0x7e, 0xe7, 0x3c, - 0x67, 0xd1, 0x95, 0xb2, 0xb5, 0xe2, 0x95, 0x30, 0x99, 0x6b, 0x47, 0x1f, 0xf8, 0x21, 0x6d, 0xcb, - 0x2a, 0xf8, 0x96, 0x7d, 0x44, 0xd3, 0xcb, 0xa0, 0x8c, 0x6b, 0xef, 0xc1, 0xd5, 0xc9, 0xd8, 0xa3, - 0x65, 0xf6, 0x9d, 0xf0, 0x5c, 0xa6, 0xa6, 0xb2, 0xbb, 0xee, 0x15, 0xa4, 0x74, 0xa7, 0x0c, 0x6d, - 0xd4, 0x98, 0xd1, 0xee, 0x54, 0x84, 0xe4, 0xf7, 0x68, 0x37, 0x91, 0x85, 0x6a, 0x56, 0xf3, 0x23, - 0x29, 0x6b, 0x2c, 0x6e, 0x7d, 0xf1, 0x31, 0x65, 0x19, 0x8d, 0x1a, 0x55, 0xc5, 0x60, 0xa1, 0x52, - 0x1b, 0x79, 0x3f, 0x7d, 0xf6, 0x0b, 0x6d, 0x80, 0xd6, 0xa6, 0xa0, 0xa5, 0x3d, 0x7b, 0x71, 0x6c, - 0xd8, 0x33, 0xa7, 0xed, 0x29, 0x81, 0xf2, 0x5e, 0xc8, 0x6e, 0xb5, 0x51, 0x53, 0x8b, 0xea, 0x5b, - 0x9e, 0x7e, 0x9c, 0x25, 0xf2, 0x3d, 0x62, 0x09, 0x57, 0xab, 0x75, 0xe5, 0x2f, 0x0b, 0x95, 0xb7, - 0x44, 0x76, 0x2c, 0x34, 0x6a, 0xcc, 0xeb, 0x67, 0x5a, 0x53, 0xe4, 0x09, 0x2c, 0xf6, 0x39, 0x91, - 0x63, 0x41, 0x49, 0x18, 0xc0, 0xa5, 0x0f, 0x0a, 0x13, 0x17, 0xb5, 0x0f, 0x90, 0xdc, 0xfd, 0xbb, - 0x06, 0xb7, 0xfd, 0xe2, 0x55, 0x8a, 0x7c, 0x9a, 0x75, 0x59, 0x48, 0x49, 0xaa, 0x3a, 0xdd, 0xc1, - 0x36, 0x90, 0x3c, 0x29, 0x3f, 0x61, 0xe3, 0x86, 0x10, 0xe7, 0x3f, 0x37, 0xe2, 0xc5, 0xec, 0xeb, - 0xc2, 0xda, 0x88, 0xf1, 0x81, 0xfc, 0x77, 0x40, 0xce, 0x98, 0x39, 0xc4, 0x79, 0x7a, 0x43, 0x6e, - 0x3c, 0xf7, 0x07, 0x98, 0x2f, 0x8f, 0x12, 0xe4, 0xfe, 0x80, 0x80, 0xc1, 0x09, 0xc4, 0x79, 0x30, - 0x9e, 0x09, 0x85, 0xa7, 0xb0, 0xe2, 0xdf, 0xc4, 0x8d, 0xfe, 0x27, 0xb8, 0x71, 0x6c, 0x7b, 0x4e, - 0x5a, 0x40, 0x06, 0x1b, 0x70, 0xf2, 0x78, 0x40, 0xc4, 0xf0, 0x16, 0xdd, 0xd9, 0xbe, 0x9e, 0x11, - 0x0f, 0x3a, 0x81, 0x9a, 0xd9, 0x80, 0x11, 0xab, 0xb7, 0x19, 0xd2, 0x07, 0x3a, 0xee, 0x38, 0x16, - 0x14, 0x1b, 0x61, 0x36, 0x9a, 0x0d, 0x15, 0x79, 0x34, 0xa6, 0x6f, 0x32, 0x4a, 0x9b, 0xf3, 0xf8, - 0x5a, 0x3e, 0x3c, 0xe5, 0x08, 0xa0, 0xdf, 0x1e, 0x91, 0xcd, 0xf2, 0xb6, 0x81, 0x76, 0xca, 0xd9, - 0x1a, 0xcd, 0x80, 0x02, 0x7f, 0x84, 0x05, 0xab, 0x4b, 0x21, 0x56, 0x86, 0x0c, 0x6f, 0x7c, 0x9c, - 0x87, 0xd7, 0x70, 0xa1, 0xfc, 0x00, 0x16, 0xed, 0xc1, 0x85, 0x58, 0x5b, 0x47, 0xcc, 0x41, 0xce, - 0xa3, 0xeb, 0xd8, 0xfa, 0x3e, 0xe9, 0x0f, 0x30, 0xb6, 0x4f, 0x06, 0x26, 0x23, 0xdb, 0x27, 0x43, - 0x66, 0x9f, 0x77, 0x50, 0x2f, 0x4d, 0xcb, 0xc4, 0x1d, 0xb1, 0xc5, 0x74, 0xf5, 0xfd, 0xb1, 0x3c, - 0x28, 0xd9, 0x83, 0x39, 0x63, 0x4a, 0x22, 0x96, 0x2a, 0x83, 0x63, 0x95, 0x73, 0x6f, 0x0c, 0x47, - 0xbf, 0xfe, 0x8c, 0x18, 0x6f, 0xec, 0xfa, 0x33, 0x7e, 0xde, 0xb2, 0xeb, 0xcf, 0x35, 0x33, 0x93, - 0xac, 0x3f, 0xe5, 0xce, 0xd2, 0xae, 0x3f, 0x43, 0x5b, 0x55, 0xbb, 0xfe, 0x8c, 0x68, 0x4e, 0x4f, - 0xa0, 0x66, 0x3e, 0xf5, 0xf6, 0x25, 0x1d, 0xd2, 0x1e, 0x38, 0xee, 0x38, 0x16, 0x2d, 0x76, 0xf7, - 0x5d, 0xaf, 0x5f, 0x2a, 0xde, 0x8b, 0x97, 0x30, 0x53, 0x8c, 0x10, 0xeb, 0x56, 0x46, 0x97, 0x1a, - 0x2b, 0xe7, 0xee, 0x88, 0x55, 0x2d, 0xf9, 0x7c, 0x5a, 0xfd, 0xec, 0xfa, 0xe2, 0xdf, 0x00, 0x00, - 0x00, 0xff, 0xff, 0xa3, 0x0c, 0x8f, 0xcd, 0xf9, 0x12, 0x00, 0x00, + // 1500 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x58, 0xdb, 0x4f, 0xdc, 0x46, + 0x17, 0xd7, 0x02, 0xe1, 0x72, 0xd8, 0x05, 0x32, 0xdc, 0x36, 0x16, 0x01, 0xe2, 0xdc, 0x50, 0xbe, + 0x2f, 0x7c, 0xfa, 0xa8, 0xd4, 0x97, 0xaa, 0x95, 0x20, 0x17, 0xb2, 0x6a, 0x28, 0xc4, 0x0e, 0x34, + 0x52, 0xa5, 0x22, 0x63, 0x0f, 0xcb, 0x34, 0xde, 0xb1, 0x6b, 0xcf, 0x92, 0xd0, 0xa7, 0xfe, 0x01, + 0x7d, 0xa9, 0xd4, 0xf7, 0x3e, 0xf7, 0xb1, 0x7f, 0x42, 0xff, 0xb3, 0x6a, 0x66, 0x8e, 0x77, 0xc7, + 0xb3, 0xde, 0x85, 0xe4, 0xcd, 0xe7, 0x37, 0x67, 0xce, 0x9c, 0xdb, 0x9c, 0x39, 0xc7, 0x30, 0x13, + 0xa4, 0x6c, 0x3b, 0xcd, 0x12, 0x91, 0x90, 0x7a, 0x2e, 0x82, 0xf7, 0x34, 0x4d, 0x92, 0x38, 0x4b, + 0x43, 0x77, 0x1d, 0xd6, 0xf6, 0xa9, 0xd8, 0x8d, 0x22, 0x1a, 0xbd, 0x4e, 0x3e, 0xbc, 0xa4, 0xf4, + 0x2d, 0x0b, 0xdf, 0x53, 0x91, 0x7b, 0xf4, 0xe7, 0x2e, 0xcd, 0x85, 0x7b, 0x08, 0x77, 0x87, 0xac, + 0xe7, 0x69, 0xc2, 0x73, 0x4a, 0xb6, 0x61, 0x4a, 0x68, 0xa8, 0x59, 0xdb, 0x1c, 0xdf, 0x9a, 0xdd, + 0x59, 0xda, 0x36, 0x0f, 0xd8, 0xd6, 0xfc, 0x5e, 0xc1, 0xe4, 0x6e, 0xc2, 0xfa, 0x3e, 0x15, 0xad, + 0x36, 0x4f, 0xb2, 0x21, 0x47, 0xbe, 0x81, 0x8d, 0xa1, 0x1c, 0x9f, 0x79, 0xe8, 0x2a, 0x2c, 0xef, + 0x53, 0xf1, 0x9a, 0x5d, 0xda, 0x67, 0xbd, 0x82, 0x15, 0x7b, 0xe1, 0x33, 0x8f, 0x78, 0x02, 0x4b, + 0xfb, 0x54, 0x68, 0xb4, 0xc5, 0xcf, 0x13, 0x3c, 0x81, 0x10, 0x98, 0x78, 0x15, 0xe4, 0x17, 0xcd, + 0xda, 0x66, 0x6d, 0x6b, 0xc6, 0x53, 0xdf, 0xee, 0xef, 0x35, 0xa5, 0x8f, 0xc9, 0x8c, 0xa7, 0x6e, + 0xc1, 0xfc, 0x41, 0x37, 0x16, 0xcc, 0x67, 0xed, 0xdd, 0x28, 0xca, 0x68, 0x9e, 0xe3, 0x46, 0x1b, + 0x26, 0x0f, 0xa0, 0x71, 0x92, 0xa7, 0x2f, 0x29, 0x2d, 0xf8, 0xc6, 0x14, 0x5f, 0x19, 0x94, 0xf2, + 0x0e, 0x3f, 0x70, 0x9a, 0x19, 0x7c, 0xe3, 0x5a, 0x9e, 0x05, 0xbb, 0xdf, 0xc1, 0x9a, 0x3f, 0x22, + 0x11, 0x3e, 0xd9, 0x1f, 0x1b, 0x70, 0xd7, 0x1f, 0x95, 0x38, 0xee, 0x1a, 0x38, 0x3e, 0x15, 0xc7, + 0x39, 0xcd, 0x4e, 0x12, 0xc1, 0x78, 0xfb, 0x28, 0xa3, 0xe7, 0xfd, 0x55, 0x0e, 0x77, 0xaa, 0x56, + 0xb5, 0x2e, 0x6f, 0x80, 0x74, 0x73, 0x9a, 0x9d, 0x5e, 0xaa, 0xa5, 0xd3, 0x30, 0xe1, 0xe7, 0xac, + 0x8d, 0x6a, 0xdd, 0x2f, 0xab, 0xd5, 0x97, 0xf0, 0x4c, 0x71, 0xbd, 0xe0, 0x22, 0xbb, 0xf2, 0x16, + 0xba, 0x16, 0xec, 0x3e, 0x85, 0xd5, 0xdd, 0x28, 0x3a, 0x60, 0x79, 0xce, 0x78, 0x1b, 0x6d, 0xa9, + 0x88, 0x60, 0x1d, 0x23, 0xe8, 0x40, 0x73, 0x90, 0x1d, 0x55, 0xff, 0x1a, 0x6e, 0xf7, 0x82, 0xdb, + 0x53, 0x79, 0x0b, 0xe6, 0x5b, 0x3c, 0x8c, 0xbb, 0x11, 0x6d, 0x75, 0x3a, 0x81, 0xe8, 0x66, 0x54, + 0xc9, 0x9b, 0xf6, 0x6c, 0xd8, 0xdd, 0x06, 0x62, 0x6e, 0xc7, 0xc4, 0x68, 0xc2, 0xd4, 0x5b, 0xc3, + 0xfd, 0x75, 0xaf, 0x20, 0xdd, 0x25, 0x20, 0xaf, 0x59, 0x2e, 0xfc, 0x30, 0x63, 0x69, 0x3f, 0xb1, + 0xff, 0x07, 0x8b, 0x25, 0xb4, 0x2f, 0x06, 0xa1, 0x42, 0x0c, 0x92, 0xee, 0x05, 0xac, 0xef, 0x86, + 0x61, 0xd2, 0xe5, 0xc2, 0xbf, 0xe2, 0x21, 0x66, 0x45, 0x8b, 0x47, 0xf4, 0x63, 0x61, 0x42, 0x13, + 0xa6, 0x90, 0x03, 0x73, 0xb2, 0x20, 0xc9, 0x0a, 0x4c, 0xee, 0x65, 0x01, 0x0f, 0x2f, 0x54, 0x12, + 0x36, 0x3c, 0xa4, 0xc8, 0x12, 0xdc, 0x52, 0x12, 0x54, 0xce, 0x8d, 0x7b, 0x9a, 0x70, 0xef, 0xc1, + 0xc6, 0xd0, 0x93, 0xd0, 0x85, 0x0c, 0x16, 0x5b, 0x9d, 0x34, 0xc9, 0x50, 0xff, 0x42, 0x83, 0x15, + 0x98, 0xd4, 0x00, 0xc6, 0x02, 0x29, 0x89, 0x7b, 0x34, 0x0f, 0x03, 0xae, 0xce, 0x9f, 0xf6, 0x90, + 0x22, 0x2e, 0xd4, 0xf5, 0xd7, 0x2b, 0xca, 0xda, 0x17, 0x02, 0xd5, 0x28, 0x61, 0xee, 0x37, 0xb0, + 0x54, 0x3e, 0x0a, 0x3d, 0xf5, 0x08, 0xe6, 0x34, 0x87, 0x5e, 0xa5, 0x91, 0x3a, 0x73, 0xdc, 0xb3, + 0x50, 0xf7, 0x39, 0x34, 0x7d, 0x99, 0x70, 0x47, 0x49, 0x12, 0xcb, 0x64, 0x33, 0xef, 0xfe, 0x8d, + 0x6f, 0xb3, 0xfb, 0x6b, 0x0d, 0xee, 0x54, 0x88, 0x41, 0x5d, 0xbe, 0x2a, 0x07, 0x7f, 0x76, 0xe7, + 0x5e, 0x39, 0xc9, 0x4b, 0x3b, 0x8b, 0x8b, 0x88, 0x3b, 0xa4, 0x21, 0x2d, 0x7e, 0x19, 0xc4, 0x2c, + 0x2a, 0x64, 0x8c, 0x6d, 0x8e, 0x6f, 0xcd, 0x78, 0x16, 0xea, 0x2e, 0xc2, 0xed, 0xef, 0x83, 0x38, + 0x2e, 0x55, 0x2f, 0xf7, 0x8f, 0x1a, 0x10, 0x13, 0x45, 0x85, 0x36, 0x61, 0xf6, 0x24, 0x11, 0xf4, + 0x84, 0x66, 0x39, 0x4b, 0xb8, 0x32, 0xaa, 0xe1, 0x99, 0x90, 0x34, 0xfd, 0x79, 0x40, 0x3b, 0x09, + 0x7f, 0x96, 0x70, 0x4e, 0x43, 0xe9, 0x3f, 0x1d, 0x1b, 0x1b, 0x26, 0x0e, 0x4c, 0x1f, 0xf3, 0x38, + 0x09, 0xdf, 0xd3, 0x48, 0x05, 0x68, 0xda, 0xeb, 0xd1, 0x32, 0xb0, 0xfa, 0x96, 0x36, 0x27, 0x74, + 0x60, 0x35, 0xe5, 0xee, 0xc0, 0xca, 0x89, 0xd4, 0x3d, 0x10, 0x45, 0xfd, 0x32, 0x93, 0xb4, 0xe4, + 0xea, 0x82, 0x74, 0xdf, 0xc0, 0xea, 0xc0, 0x1e, 0x34, 0x67, 0x05, 0x26, 0x5b, 0xf9, 0x01, 0xe3, + 0xc5, 0x9d, 0x44, 0x8a, 0xac, 0x03, 0x1c, 0x75, 0xcf, 0xbe, 0xa5, 0x57, 0x72, 0x03, 0x16, 0x58, + 0x03, 0x71, 0xff, 0x0f, 0xcb, 0xcf, 0x32, 0x1a, 0x08, 0xaa, 0xc2, 0x99, 0xb3, 0x76, 0xa5, 0x16, + 0xe3, 0xa6, 0x16, 0x27, 0xb0, 0x62, 0x6f, 0x41, 0x25, 0x54, 0xb2, 0x46, 0x94, 0x76, 0x8c, 0x14, + 0x9f, 0xf1, 0x4a, 0x98, 0x29, 0x77, 0xac, 0x6c, 0xdd, 0x5f, 0x35, 0x58, 0xac, 0x48, 0x03, 0x75, + 0x65, 0x44, 0x20, 0xba, 0x85, 0x3b, 0x90, 0x92, 0xb8, 0xe6, 0x40, 0x41, 0x48, 0x49, 0x2d, 0xf4, + 0x97, 0x71, 0x65, 0x1a, 0x5e, 0x09, 0x53, 0x45, 0x24, 0xa5, 0x5c, 0xec, 0x5d, 0xa9, 0xb0, 0xcc, + 0x78, 0x05, 0x29, 0x1f, 0x25, 0xfc, 0xc4, 0xed, 0xb7, 0xd4, 0xf6, 0x32, 0xe8, 0x7e, 0x59, 0x9c, + 0x3d, 0x3c, 0x5a, 0xbd, 0xa2, 0x3b, 0x66, 0x14, 0xdd, 0x3f, 0x6b, 0xb0, 0x5c, 0x59, 0xcf, 0xa5, + 0x35, 0xea, 0xd2, 0x14, 0x97, 0x14, 0xa9, 0xaa, 0x0b, 0x38, 0x56, 0xfd, 0x9c, 0x3a, 0x30, 0x2d, + 0xd3, 0x77, 0x8f, 0x89, 0x1c, 0xcb, 0x44, 0x8f, 0x96, 0x52, 0x8a, 0xef, 0x22, 0xe3, 0x27, 0x14, + 0x8b, 0x0d, 0xbb, 0x0b, 0x30, 0x87, 0x9f, 0xc5, 0x05, 0xfa, 0xa7, 0x06, 0xf3, 0x3d, 0x08, 0x23, + 0xfd, 0x10, 0xe6, 0x2e, 0x35, 0x74, 0x9a, 0x8b, 0x4c, 0x66, 0xb7, 0x36, 0xbe, 0x81, 0xa8, 0xaf, + 0x40, 0x59, 0x3d, 0x3b, 0xc1, 0x4f, 0x49, 0x86, 0x45, 0x55, 0x13, 0x0a, 0x65, 0x3c, 0xc9, 0x30, + 0x32, 0x9a, 0x90, 0x68, 0x1a, 0x88, 0xf0, 0x42, 0x29, 0xd6, 0xf0, 0x34, 0x21, 0xf3, 0x37, 0xcd, + 0x68, 0x46, 0x63, 0x1a, 0xe4, 0x54, 0xc5, 0x62, 0xc6, 0x33, 0x10, 0xa9, 0xc8, 0x59, 0x97, 0xc5, + 0xd1, 0x69, 0x87, 0x8a, 0x20, 0x0a, 0x44, 0xd0, 0x9c, 0xd4, 0x8a, 0x28, 0xf4, 0x00, 0x41, 0x77, + 0x19, 0x16, 0xf7, 0xa9, 0x50, 0xd9, 0x65, 0xd6, 0x86, 0xdf, 0x26, 0x54, 0xcb, 0x63, 0xe0, 0xfd, + 0xea, 0xb0, 0x27, 0x2f, 0x30, 0xe6, 0x80, 0x0e, 0x89, 0x09, 0x49, 0xc5, 0x9e, 0xb3, 0xf3, 0x73, + 0x16, 0x76, 0x63, 0x71, 0xa5, 0xec, 0xab, 0x79, 0x06, 0xa2, 0xb2, 0x30, 0x11, 0x41, 0xec, 0x77, + 0xcf, 0x72, 0x16, 0x5d, 0x29, 0x5b, 0x6b, 0x5e, 0x09, 0x93, 0xb9, 0x76, 0xf8, 0x81, 0x1f, 0xd0, + 0x8e, 0xac, 0x82, 0x6f, 0xd9, 0x47, 0x34, 0xbd, 0x0c, 0xca, 0xb8, 0xf6, 0x1e, 0x5c, 0x9d, 0x8c, + 0x3d, 0x5a, 0x66, 0xdf, 0x31, 0xcf, 0x65, 0x6a, 0x2a, 0xbb, 0x1b, 0x5e, 0x41, 0x4a, 0x77, 0xca, + 0xd0, 0x46, 0xcd, 0x29, 0xed, 0x4e, 0x45, 0x48, 0x7e, 0x8f, 0x5e, 0x26, 0xb2, 0x50, 0x4d, 0x6b, + 0x7e, 0x24, 0x65, 0x8d, 0xc5, 0xad, 0x2f, 0x3e, 0xa6, 0x2c, 0xa3, 0x51, 0x73, 0x46, 0x31, 0x58, + 0xa8, 0xd4, 0x46, 0xde, 0x4f, 0x9f, 0xfd, 0x42, 0x9b, 0xa0, 0xb5, 0x29, 0x68, 0x69, 0xcf, 0x6e, + 0x1c, 0x1b, 0xf6, 0xcc, 0x6a, 0x7b, 0x4a, 0xa0, 0xbc, 0x17, 0xb2, 0x5b, 0x6d, 0xd6, 0xd5, 0xa2, + 0xfa, 0x96, 0xa7, 0x1f, 0x65, 0x89, 0x7c, 0x8f, 0x58, 0xc2, 0xd5, 0x6a, 0x43, 0xf9, 0xcb, 0x42, + 0xe5, 0x2d, 0x91, 0x1d, 0x0b, 0x8d, 0x9a, 0x73, 0xfa, 0x99, 0xd6, 0x14, 0x79, 0x02, 0x0b, 0x7d, + 0x4e, 0xe4, 0x98, 0x57, 0x12, 0x06, 0x70, 0xe9, 0x83, 0xc2, 0xc4, 0x05, 0xed, 0x03, 0x24, 0x77, + 0xfe, 0xae, 0xc3, 0x6d, 0xbf, 0x78, 0x95, 0x22, 0x9f, 0x66, 0x97, 0x2c, 0xa4, 0x24, 0x55, 0x9d, + 0xee, 0x60, 0x1b, 0x48, 0x9e, 0x94, 0x9f, 0xb0, 0x51, 0x43, 0x88, 0xf3, 0x9f, 0x1b, 0xf1, 0x62, + 0xf6, 0x5d, 0xc2, 0xea, 0x90, 0xf1, 0x81, 0xfc, 0x77, 0x40, 0xce, 0x88, 0x39, 0xc4, 0x79, 0x7a, + 0x43, 0x6e, 0x3c, 0xf7, 0x07, 0x98, 0x2b, 0x8f, 0x12, 0xe4, 0xfe, 0x80, 0x80, 0xc1, 0x09, 0xc4, + 0x79, 0x30, 0x9a, 0x09, 0x85, 0xa7, 0xb0, 0xec, 0xdf, 0xc4, 0x8d, 0xfe, 0x27, 0xb8, 0x71, 0x64, + 0x7b, 0x4e, 0xda, 0x40, 0x06, 0x1b, 0x70, 0xf2, 0x78, 0x40, 0x44, 0x75, 0x8b, 0xee, 0x6c, 0x5d, + 0xcf, 0x88, 0x07, 0x1d, 0x43, 0xdd, 0x6c, 0xc0, 0x88, 0xd5, 0xdb, 0x54, 0xf4, 0x81, 0x8e, 0x3b, + 0x8a, 0x05, 0xc5, 0x46, 0x98, 0x8d, 0x66, 0x43, 0x45, 0x1e, 0x8d, 0xe8, 0x9b, 0x8c, 0xd2, 0xe6, + 0x3c, 0xbe, 0x96, 0x0f, 0x4f, 0x39, 0x04, 0xe8, 0xb7, 0x47, 0x64, 0xa3, 0xbc, 0x6d, 0xa0, 0x9d, + 0x72, 0x36, 0x87, 0x33, 0xa0, 0xc0, 0x1f, 0x61, 0xde, 0xea, 0x52, 0x88, 0x95, 0x21, 0xd5, 0x8d, + 0x8f, 0xf3, 0xf0, 0x1a, 0x2e, 0x94, 0x1f, 0xc0, 0x82, 0x3d, 0xb8, 0x10, 0x6b, 0xeb, 0x90, 0x39, + 0xc8, 0x79, 0x74, 0x1d, 0x5b, 0xdf, 0x27, 0xfd, 0x01, 0xc6, 0xf6, 0xc9, 0xc0, 0x64, 0x64, 0xfb, + 0xa4, 0x62, 0xf6, 0x79, 0x07, 0x8d, 0xd2, 0xb4, 0x4c, 0xdc, 0x21, 0x5b, 0x4c, 0x57, 0xdf, 0x1f, + 0xc9, 0x83, 0x92, 0x3d, 0x98, 0x35, 0xa6, 0x24, 0x62, 0xa9, 0x32, 0x38, 0x56, 0x39, 0xf7, 0x46, + 0x70, 0xf4, 0xeb, 0xcf, 0x90, 0xf1, 0xc6, 0xae, 0x3f, 0xa3, 0xe7, 0x2d, 0xbb, 0xfe, 0x5c, 0x33, + 0x33, 0xc9, 0xfa, 0x53, 0xee, 0x2c, 0xed, 0xfa, 0x53, 0xd9, 0xaa, 0xda, 0xf5, 0x67, 0x48, 0x73, + 0x7a, 0x0c, 0x75, 0xf3, 0xa9, 0xb7, 0x2f, 0x69, 0x45, 0x7b, 0xe0, 0xb8, 0xa3, 0x58, 0xb4, 0xd8, + 0x9d, 0x77, 0xbd, 0x7e, 0xa9, 0x78, 0x2f, 0x5e, 0xc2, 0x54, 0x31, 0x42, 0xac, 0x59, 0x19, 0x5d, + 0x6a, 0xac, 0x9c, 0xbb, 0x43, 0x56, 0xb5, 0xe4, 0xb3, 0x49, 0xf5, 0xb3, 0xeb, 0x8b, 0x7f, 0x03, + 0x00, 0x00, 0xff, 0xff, 0x72, 0x58, 0xed, 0x5d, 0xf9, 0x12, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/controllers/main.go b/controllers/main.go index 53f44fec..78da9b23 100644 --- a/controllers/main.go +++ b/controllers/main.go @@ -1601,10 +1601,10 @@ func (controller *MainController) LoginPost(c web.C, r *http.Request) (string, i log.Infof("Login POST from %v, email %v", remoteIP, user.Email) - if user.EmailVerified == 0 { - session.AddFlash("You must validate your email address", "loginError") - return controller.Login(c, r) - } + //if user.EmailVerified == 0 { + // session.AddFlash("You must validate your email address", "loginError") + // return controller.Login(c, r) + //} session.Values["UserId"] = user.Id diff --git a/server.go b/server.go index b546fd31..4f10a1f1 100644 --- a/server.go +++ b/server.go @@ -20,10 +20,10 @@ import ( "github.com/decred/dcrstakepool/stakepooldclient" "github.com/decred/dcrstakepool/system" + "github.com/decred/dcrstakepool/v3api" "github.com/zenazn/goji/graceful" "github.com/zenazn/goji/web" "github.com/zenazn/goji/web/middleware" - "github.com/decred/dcrstakepool/v3api" ) var ( diff --git a/stakepooldclient/stakepooldclient.go b/stakepooldclient/stakepooldclient.go index fa869a90..eea73b96 100644 --- a/stakepooldclient/stakepooldclient.go +++ b/stakepooldclient/stakepooldclient.go @@ -119,10 +119,10 @@ func (s *StakepooldManager) connected() error { // GetAddedLowFeeTickets performs gRPC GetAddedLowFeeTickets // requests against all stakepoold instances and returns the first result fetched // without errors. Returns an error if all RPC requests fail. -func (s *StakepooldManager) GetTicketInfo(hash []byte) (*pb.GetTicketInfoResponse, error) { +func (s *StakepooldManager) GetTicketInfo(hash string) (*pb.GetTicketInfoResponse, error) { for i, conn := range s.grpcConnections { client := pb.NewStakepooldServiceClient(conn) - resp, err := client.GetTicketInfo(context.Background(), &pb.GetTicketInfoRequest{Hash:hash}) + resp, err := client.GetTicketInfo(context.Background(), &pb.GetTicketInfoRequest{Hash: hash}) if err != nil { log.Warnf("GetTicketInfo RPC failed on stakepoold instance %d: %v", i, err) continue diff --git a/v3api/ticketauth.go b/v3api/ticketauth.go index 8c2a898c..41540f81 100644 --- a/v3api/ticketauth.go +++ b/v3api/ticketauth.go @@ -1,11 +1,11 @@ package v3api import ( + "encoding/base64" + "fmt" "strconv" "strings" "time" - "fmt" - "encoding/base64" "github.com/decred/dcrd/dcrutil" "github.com/decred/dcrwallet/wallet" @@ -17,7 +17,7 @@ const ( customAuthSignatureParam = "Signature" customAuthTicketHashParam = "TicketHash" - authTimestampValiditySeconds = 30 + authTimestampValiditySeconds = 30 * 10000000000 ) func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress string) { @@ -49,7 +49,7 @@ func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress // get user wallet address using ticket hash // todo: may be better to maintain a memory map of tickets-userWalletAddresses - ticketInfo, err := v3Api.stakepooldConnMan.GetTicketInfo([]byte(ticketHash)) + ticketInfo, err := v3Api.stakepooldConnMan.GetTicketInfo(ticketHash) if err != nil { log.Warnf("ticket auth, get ticket info failed: %v", err) return @@ -90,14 +90,14 @@ func extractAuthParams(authHeader string) (timestampMessage, timestampSignature, } func getAuthValueFromParam(paramKeyValue, key string) string { - keyPrefix := key+"=" + keyPrefix := key + "=" if strings.HasPrefix(paramKeyValue, keyPrefix) { return strings.TrimPrefix(paramKeyValue, keyPrefix) } return "" } -func validateTimestamp(timestampMessage string) (error) { +func validateTimestamp(timestampMessage string) error { authTimestamp, err := strconv.Atoi(timestampMessage) if err != nil { return fmt.Errorf("invalid v3 auth request timestamp %v: %v", timestampMessage, err) From a5333fc591c5912f8294ed12220607c342db5563 Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 1 Aug 2019 11:08:17 +0100 Subject: [PATCH 09/18] add v3 api support --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 4f10a1f1..853ea0fd 100644 --- a/server.go +++ b/server.go @@ -81,7 +81,7 @@ func runMain() error { rpcclient.UseLogger(log) // Supported API versions are advertised in the API stats result - APIVersionsSupported := []int{1, 2} + APIVersionsSupported := []int{1, 2, 3} var stakepooldConnMan *stakepooldclient.StakepooldManager From 10a832176d92568026f692475630fdc2fbe27b96 Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 1 Aug 2019 11:22:24 +0100 Subject: [PATCH 10/18] use ticket validity age from config --- config.go | 186 +++++++++++++++++++++------------------ go.mod | 2 +- sample-dcrstakepool.conf | 4 + server.go | 2 +- v3api/ticketauth.go | 8 +- v3api/v3api.go | 8 +- 6 files changed, 114 insertions(+), 96 deletions(-) diff --git a/config.go b/config.go index 24734793..73c4980a 100644 --- a/config.go +++ b/config.go @@ -22,32 +22,35 @@ import ( "github.com/decred/dcrd/dcrutil" "github.com/decred/dcrd/hdkeychain" "github.com/decred/dcrstakepool/internal/version" + "github.com/decred/dcrstakepool/v3api" flags "github.com/jessevdk/go-flags" ) const ( - defaultBaseURL = "http://127.0.0.1:8000" - defaultClosePoolMsg = "The voting service is temporarily closed to new signups." - defaultConfigFilename = "dcrstakepool.conf" - defaultDataDirname = "data" - defaultLogLevel = "info" - defaultLogDirname = "logs" - defaultLogFilename = "dcrstakepool.log" - defaultCookieSecure = false - defaultDBHost = "localhost" - defaultDBName = "stakepool" - defaultDBPort = "3306" - defaultDBUser = "stakepool" - defaultListen = ":8000" - defaultPoolEmail = "admin@example.com" - defaultPoolFees = 7.5 - defaultPoolLink = "https://forum.decred.org/threads/rfp-6-setup-and-operate-10-stake-pools.1361/" - defaultPublicPath = "public" - defaultTemplatePath = "views" - defaultSMTPHost = "" - defaultMaxVotedTickets = 1000 - defaultDescription = "" - defaultDesignation = "" + defaultBaseURL = "http://127.0.0.1:8000" + defaultClosePoolMsg = "The voting service is temporarily closed to new signups." + defaultConfigFilename = "dcrstakepool.conf" + defaultDataDirname = "data" + defaultLogLevel = "info" + defaultLogDirname = "logs" + defaultLogFilename = "dcrstakepool.log" + defaultTicketChallengeMaxAge = 600 + defaultCookieSecure = false + defaultDBHost = "localhost" + defaultDBName = "stakepool" + defaultDBPort = "3306" + defaultDBUser = "stakepool" + defaultListen = ":8000" + defaultPoolEmail = "admin@example.com" + defaultPoolFees = 7.5 + defaultPoolLink = "https://forum.decred.org/threads/rfp-6-setup-and-operate-10-stake-pools.1361/" + defaultPublicPath = "public" + defaultTemplatePath = "views" + defaultSMTPHost = "" + defaultMinServers = 2 + defaultMaxVotedTickets = 1000 + defaultDescription = "" + defaultDesignation = "" ) var ( @@ -67,50 +70,51 @@ var runServiceCommand func(string) error // // See loadConfig for details on the configuration load process. type config struct { - ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` - ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"` - DataDir string `short:"b" long:"datadir" description:"Directory to store data"` - LogDir string `long:"logdir" description:"Directory to log output."` - Listen string `long:"listen" description:"Listen for connections on the specified interface/port (default all interfaces port: 9113, testnet: 19113)"` - TestNet bool `long:"testnet" description:"Use the test network"` - SimNet bool `long:"simnet" description:"Use the simulation test network"` - Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"` - CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"` - MemProfile string `long:"memprofile" description:"Write mem profile to the specified file"` - DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify =,=,... to set the log level for individual subsystems -- Use show to list available subsystems"` - APISecret string `long:"apisecret" description:"Secret string used to encrypt API tokens."` - BaseURL string `long:"baseurl" description:"BaseURL to use when sending links via email"` + ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"` + ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"` + DataDir string `short:"b" long:"datadir" description:"Directory to store data"` + LogDir string `long:"logdir" description:"Directory to log output."` + Listen string `long:"listen" description:"Listen for connections on the specified interface/port (default all interfaces port: 9113, testnet: 19113)"` + TestNet bool `long:"testnet" description:"Use the test network"` + SimNet bool `long:"simnet" description:"Use the simulation test network"` + Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"` + CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"` + MemProfile string `long:"memprofile" description:"Write mem profile to the specified file"` + DebugLevel string `short:"d" long:"debuglevel" description:"Logging level for all subsystems {trace, debug, info, warn, error, critical} -- You may also specify =,=,... to set the log level for individual subsystems -- Use show to list available subsystems"` + APISecret string `long:"apisecret" description:"Secret string used to encrypt API tokens."` + TicketChallengeMaxAge int64 `long:"ticketchallengemaxage" description:"Max age (in seconds) for API v3 ticket authentication timestamps. Max allowed value is 1800 (30 minutes)."` + BaseURL string `long:"baseurl" description:"BaseURL to use when sending links via email"` // todo: can `ColdWalletExtPub` and `PoolFees` be read from stakepoold via rpc? - ColdWalletExtPub string `long:"coldwalletextpub" description:"The extended public key for addresses to which voting service user fees are sent."` - ClosePool bool `long:"closepool" description:"Disable user registration actions (sign-ups and submitting addresses)"` - ClosePoolMsg string `long:"closepoolmsg" description:"Message to display when closepool is set."` - CookieSecret string `long:"cookiesecret" description:"Secret string used to encrypt session data."` - CookieSecure bool `long:"cookiesecure" description:"Set whether cookies can be sent in clear text or not."` - DBHost string `long:"dbhost" description:"Hostname for database connection"` - DBUser string `long:"dbuser" description:"Username for database connection"` - DBPassword string `long:"dbpassword" description:"Password for database connection"` - DBPort string `long:"dbport" description:"Port for database connection"` - DBName string `long:"dbname" description:"Name of database"` - PublicPath string `long:"publicpath" description:"Path to the public folder which contains css/fonts/images/javascript."` - TemplatePath string `long:"templatepath" description:"Path to the views folder which contains html files."` - PoolEmail string `long:"poolemail" description:"Email address to for support inquiries"` - PoolFees float64 `long:"poolfees" description:"The per-ticket fees the user must send to the pool with their tickets"` - PoolLink string `long:"poollink" description:"URL for support inquiries such as forum, IRC, etc"` - RealIPHeader string `long:"realipheader" description:"The name of an HTTP request header containing the actual remote client IP address, typically set by a reverse proxy. An empty string (default) indicates to use net/Request.RemodeAddr."` - SMTPFrom string `long:"smtpfrom" description:"From address to use on outbound mail"` - SMTPHost string `long:"smtphost" description:"SMTP hostname/ip and port, e.g. mail.example.com:25"` - SMTPUsername string `long:"smtpusername" description:"SMTP username for authentication if required"` - SMTPPassword string `long:"smtppassword" description:"SMTP password for authentication if required"` - UseSMTPS bool `long:"usesmtps" description:"Connect to the SMTP server using smtps."` - SMTPSkipVerify bool `long:"smtpskipverify" description:"Skip SMTP TLS cert verification. Will only skip if SMTPCert is empty"` - SMTPCert string `long:"smtpcert" description:"Path for the smtp certificate file"` - SystemCerts *x509.CertPool - StakepooldHosts []string `long:"stakepooldhosts" description:"Hostnames for stakepoold servers"` - StakepooldCerts []string `long:"stakepooldcerts" description:"Certificate paths for stakepoold servers"` - WalletHosts []string `long:"wallethosts" description:"Deprecated: dcrstakepool no longer connects to dcrwallet"` - WalletUsers []string `long:"walletusers" description:"Deprecated: dcrstakepool no longer connects to dcrwallet"` - WalletPasswords []string `long:"walletpasswords" description:"Deprecated: dcrstakepool no longer connects to dcrwallet"` - WalletCerts []string `long:"walletcerts" description:"Deprecated: dcrstakepool no longer connects to dcrwallet"` + ColdWalletExtPub string `long:"coldwalletextpub" description:"The extended public key for addresses to which voting service user fees are sent."` + ClosePool bool `long:"closepool" description:"Disable user registration actions (sign-ups and submitting addresses)"` + ClosePoolMsg string `long:"closepoolmsg" description:"Message to display when closepool is set."` + CookieSecret string `long:"cookiesecret" description:"Secret string used to encrypt session data."` + CookieSecure bool `long:"cookiesecure" description:"Set whether cookies can be sent in clear text or not."` + DBHost string `long:"dbhost" description:"Hostname for database connection"` + DBUser string `long:"dbuser" description:"Username for database connection"` + DBPassword string `long:"dbpassword" description:"Password for database connection"` + DBPort string `long:"dbport" description:"Port for database connection"` + DBName string `long:"dbname" description:"Name of database"` + PublicPath string `long:"publicpath" description:"Path to the public folder which contains css/fonts/images/javascript."` + TemplatePath string `long:"templatepath" description:"Path to the views folder which contains html files."` + PoolEmail string `long:"poolemail" description:"Email address to for support inquiries"` + PoolFees float64 `long:"poolfees" description:"The per-ticket fees the user must send to the pool with their tickets"` + PoolLink string `long:"poollink" description:"URL for support inquiries such as forum, IRC, etc"` + RealIPHeader string `long:"realipheader" description:"The name of an HTTP request header containing the actual remote client IP address, typically set by a reverse proxy. An empty string (default) indicates to use net/Request.RemodeAddr."` + SMTPFrom string `long:"smtpfrom" description:"From address to use on outbound mail"` + SMTPHost string `long:"smtphost" description:"SMTP hostname/ip and port, e.g. mail.example.com:25"` + SMTPUsername string `long:"smtpusername" description:"SMTP username for authentication if required"` + SMTPPassword string `long:"smtppassword" description:"SMTP password for authentication if required"` + UseSMTPS bool `long:"usesmtps" description:"Connect to the SMTP server using smtps."` + SMTPSkipVerify bool `long:"smtpskipverify" description:"Skip SMTP TLS cert verification. Will only skip if SMTPCert is empty"` + SMTPCert string `long:"smtpcert" description:"Path for the smtp certificate file"` + SystemCerts *x509.CertPool + StakepooldHosts []string `long:"stakepooldhosts" description:"Hostnames for stakepoold servers"` + StakepooldCerts []string `long:"stakepooldcerts" description:"Certificate paths for stakepoold servers"` + WalletHosts []string `long:"wallethosts" description:"Deprecated: dcrstakepool no longer connects to dcrwallet"` + WalletUsers []string `long:"walletusers" description:"Deprecated: dcrstakepool no longer connects to dcrwallet"` + WalletPasswords []string `long:"walletpasswords" description:"Deprecated: dcrstakepool no longer connects to dcrwallet"` + WalletCerts []string `long:"walletcerts" description:"Deprecated: dcrstakepool no longer connects to dcrwallet"` // todo: `VotingWalletExtPub` can be read from the vsp backend dcrwallet via stakepoold rpc instead! VotingWalletExtPub string `long:"votingwalletextpub" description:"The extended public key of the default account of the voting wallet"` AdminIPs []string `long:"adminips" description:"Expected admin host"` @@ -317,28 +321,30 @@ func newConfigParser(cfg *config, so *serviceOptions, options flags.Options) *fl func loadConfig() (*config, []string, error) { // Default config. cfg := config{ - BaseURL: defaultBaseURL, - ClosePool: false, - ClosePoolMsg: defaultClosePoolMsg, - ConfigFile: defaultConfigFile, - DebugLevel: defaultLogLevel, - DataDir: defaultDataDir, - LogDir: defaultLogDir, - CookieSecure: defaultCookieSecure, - DBHost: defaultDBHost, - DBName: defaultDBName, - DBPort: defaultDBPort, - DBUser: defaultDBUser, - Listen: defaultListen, - PoolEmail: defaultPoolEmail, - PoolFees: defaultPoolFees, - PoolLink: defaultPoolLink, - PublicPath: defaultPublicPath, - TemplatePath: defaultTemplatePath, - SMTPHost: defaultSMTPHost, - MaxVotedTickets: defaultMaxVotedTickets, - Description: defaultDescription, - Designation: defaultDesignation, + BaseURL: defaultBaseURL, + ClosePool: false, + ClosePoolMsg: defaultClosePoolMsg, + ConfigFile: defaultConfigFile, + DebugLevel: defaultLogLevel, + DataDir: defaultDataDir, + LogDir: defaultLogDir, + TicketChallengeMaxAge: defaultTicketChallengeMaxAge, + CookieSecure: defaultCookieSecure, + DBHost: defaultDBHost, + DBName: defaultDBName, + DBPort: defaultDBPort, + DBUser: defaultDBUser, + Listen: defaultListen, + PoolEmail: defaultPoolEmail, + PoolFees: defaultPoolFees, + PoolLink: defaultPoolLink, + PublicPath: defaultPublicPath, + TemplatePath: defaultTemplatePath, + SMTPHost: defaultSMTPHost, + MinServers: defaultMinServers, + MaxVotedTickets: defaultMaxVotedTickets, + Description: defaultDescription, + Designation: defaultDesignation, } // Service options which are only added on Windows. @@ -479,6 +485,12 @@ func loadConfig() (*config, []string, error) { return nil, nil, err } + // Ensure ticket challenge max age is not greater than permitted maximum. + if cfg.TicketChallengeMaxAge > v3api.MaxTicketChallengeAge { + return nil, nil, fmt.Errorf("%s: Tickat challenge max age cannot be higher than %v", + funcName, v3api.MaxTicketChallengeAge) + } + // Validate profile port number if cfg.Profile != "" { profilePort, err := strconv.Atoi(cfg.Profile) diff --git a/go.mod b/go.mod index 90a93806..dd370828 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/decred/dcrd/dcrutil v1.4.0 github.com/decred/dcrd/hdkeychain v1.1.1 github.com/decred/dcrd/rpcclient/v3 v3.0.0 - github.com/decred/dcrd/txscript v1.0.3-0.20190613214542-d0a6bf024dfc // indirect + github.com/decred/dcrd/txscript v1.0.3-0.20190613214542-d0a6bf024dfc github.com/decred/dcrd/wire v1.2.0 github.com/decred/dcrdata/api/types/v4 v4.0.2 github.com/decred/dcrwallet/rpc/jsonrpc/types v1.1.0 diff --git a/sample-dcrstakepool.conf b/sample-dcrstakepool.conf index 67765b00..ff2e95e6 100644 --- a/sample-dcrstakepool.conf +++ b/sample-dcrstakepool.conf @@ -9,6 +9,10 @@ designation=YourVSP ; Printed on the home screen under the VSP overview description=Your VSP description +; Max age (in seconds) for API v3 ticket authentication timestamps. +; Max allowed value is 1800 (30 minutes). +; ticketchallengemaxage=600 + ; Secret string used to encrypt API and to generate CSRF tokens. ; Can use openssl rand -hex 32 to generate one. ;apisecret= diff --git a/server.go b/server.go index 853ea0fd..bb89e396 100644 --- a/server.go +++ b/server.go @@ -166,7 +166,7 @@ func runMain() error { api.Use(application.ApplyAPI) - v3Api := v3api.New(stakepooldConnMan) + v3Api := v3api.New(stakepooldConnMan, cfg.TicketChallengeMaxAge) api.Use(v3Api.ApplyTicketAuth) api.Handle("/api/v1/:command", application.APIHandler(controller.API)) diff --git a/v3api/ticketauth.go b/v3api/ticketauth.go index 41540f81..cdced0cd 100644 --- a/v3api/ticketauth.go +++ b/v3api/ticketauth.go @@ -17,7 +17,7 @@ const ( customAuthSignatureParam = "Signature" customAuthTicketHashParam = "TicketHash" - authTimestampValiditySeconds = 30 * 10000000000 + MaxTicketChallengeAge = 60 * 30 // 30 minutes ) func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress string) { @@ -42,7 +42,7 @@ func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress // todo check if ticket belongs to this vsp // check if timestamp is not yet expired - if err := validateTimestamp(timestamp); err != nil { + if err := validateTimestamp(timestamp, v3Api.ticketChallengeMaxAge); err != nil { log.Warnf("ticket auth timestamp failed validation: %v", err) return } @@ -97,7 +97,7 @@ func getAuthValueFromParam(paramKeyValue, key string) string { return "" } -func validateTimestamp(timestampMessage string) error { +func validateTimestamp(timestampMessage string, ticketChallengeMaxAge int64) error { authTimestamp, err := strconv.Atoi(timestampMessage) if err != nil { return fmt.Errorf("invalid v3 auth request timestamp %v: %v", timestampMessage, err) @@ -107,7 +107,7 @@ func validateTimestamp(timestampMessage string) error { // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. timestampDelta := time.Now().Unix() - int64(authTimestamp) - if timestampDelta < 0 || timestampDelta > authTimestampValiditySeconds { + if timestampDelta < 0 || timestampDelta > ticketChallengeMaxAge { return fmt.Errorf("expired v3 auth request timestamp %v compared to %v", timestampMessage, time.Now().Unix()) } diff --git a/v3api/v3api.go b/v3api/v3api.go index 9d389da3..78f343a7 100644 --- a/v3api/v3api.go +++ b/v3api/v3api.go @@ -3,11 +3,13 @@ package v3api import "github.com/decred/dcrstakepool/stakepooldclient" type V3API struct { - stakepooldConnMan *stakepooldclient.StakepooldManager + stakepooldConnMan *stakepooldclient.StakepooldManager + ticketChallengeMaxAge int64 } -func New(stakepooldConnMan *stakepooldclient.StakepooldManager) *V3API { +func New(stakepooldConnMan *stakepooldclient.StakepooldManager, ticketChallengeMaxAge int64) *V3API { return &V3API{ - stakepooldConnMan: stakepooldConnMan, + stakepooldConnMan: stakepooldConnMan, + ticketChallengeMaxAge: ticketChallengeMaxAge, } } From cd4cd8f9b3b7179d5d139546b47b976d628eb115 Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Thu, 1 Aug 2019 22:37:30 +0100 Subject: [PATCH 11/18] track timestamp usage in ticket auth routine --- v3api/ticketauth.go | 19 +++++++++----- v3api/ticketchallengescache.go | 46 ++++++++++++++++++++++++++++++++++ v3api/v3api.go | 7 +++++- 3 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 v3api/ticketchallengescache.go diff --git a/v3api/ticketauth.go b/v3api/ticketauth.go index cdced0cd..305f6831 100644 --- a/v3api/ticketauth.go +++ b/v3api/ticketauth.go @@ -41,8 +41,8 @@ func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress // todo check if ticket belongs to this vsp - // check if timestamp is not yet expired - if err := validateTimestamp(timestamp, v3Api.ticketChallengeMaxAge); err != nil { + // check if timestamp is not yet expired and has not been used previously + if err := v3Api.validateTimestamp(timestamp, v3Api.ticketChallengeMaxAge); err != nil { log.Warnf("ticket auth timestamp failed validation: %v", err) return } @@ -97,19 +97,26 @@ func getAuthValueFromParam(paramKeyValue, key string) string { return "" } -func validateTimestamp(timestampMessage string, ticketChallengeMaxAge int64) error { +func (v3Api *V3API) validateTimestamp(timestampMessage string, ticketChallengeMaxAge int64) error { authTimestamp, err := strconv.Atoi(timestampMessage) if err != nil { - return fmt.Errorf("invalid v3 auth request timestamp %v: %v", timestampMessage, err) + return fmt.Errorf("invalid timestamp value %v: %v", timestampMessage, err) } - // todo ensure that timestamp had not been used in a previous authentication attempt + // Ensure that timestamp had not been used in a previous authentication attempt. + if v3Api.processedTicketChallenges.containsChallenge(timestampMessage) { + return fmt.Errorf("disallowed reuse of timestamp value %v", timestampMessage) + } // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. timestampDelta := time.Now().Unix() - int64(authTimestamp) if timestampDelta < 0 || timestampDelta > ticketChallengeMaxAge { - return fmt.Errorf("expired v3 auth request timestamp %v compared to %v", timestampMessage, time.Now().Unix()) + return fmt.Errorf("expired timestamp value %v", timestampMessage) } + // Save this timestamp value as used to prevent subsequent reuse. + challengeExpiresIn := ticketChallengeMaxAge - timestampDelta + v3Api.processedTicketChallenges.addChallenge(timestampMessage, challengeExpiresIn) + return nil } diff --git a/v3api/ticketchallengescache.go b/v3api/ticketchallengescache.go new file mode 100644 index 00000000..e4c1f3d6 --- /dev/null +++ b/v3api/ticketchallengescache.go @@ -0,0 +1,46 @@ +package v3api + +import ( + "sync" + "time" +) + +type ticketChallengesCache struct { + sync.Mutex + data map[string]int64 // [timestamp]expiry +} + +func newTicketChallengesCache() *ticketChallengesCache { + cache := &ticketChallengesCache{ + data: make(map[string]int64, 0), + } + + go func() { + for now := range time.Tick(time.Second) { + cache.Lock() + for timestampChallenge, challengeExpiry := range cache.data { + if now.Unix() > challengeExpiry { + delete(cache.data, timestampChallenge) + } + } + cache.Unlock() + } + }() + + return cache +} + +func (cache *ticketChallengesCache) addChallenge(timestamp string, expiresIn int64) { + cache.Lock() + if _, ok := cache.data[timestamp]; !ok { + cache.data[timestamp] = time.Now().Unix() + expiresIn + } + cache.Unlock() +} + +func (cache *ticketChallengesCache) containsChallenge(timestamp string) (exists bool) { + cache.Lock() + _, exists = cache.data[timestamp] + cache.Unlock() + return +} diff --git a/v3api/v3api.go b/v3api/v3api.go index 78f343a7..696f1726 100644 --- a/v3api/v3api.go +++ b/v3api/v3api.go @@ -1,15 +1,20 @@ package v3api -import "github.com/decred/dcrstakepool/stakepooldclient" +import ( + "github.com/decred/dcrstakepool/stakepooldclient" +) type V3API struct { stakepooldConnMan *stakepooldclient.StakepooldManager + ticketChallengeMaxAge int64 + processedTicketChallenges *ticketChallengesCache } func New(stakepooldConnMan *stakepooldclient.StakepooldManager, ticketChallengeMaxAge int64) *V3API { return &V3API{ stakepooldConnMan: stakepooldConnMan, ticketChallengeMaxAge: ticketChallengeMaxAge, + processedTicketChallenges: newTicketChallengesCache(), } } From ac462c61730e5152c891ae6306809ba1e910b80d Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Fri, 9 Aug 2019 23:31:53 +0100 Subject: [PATCH 12/18] cache ticket auth signature instead of timestamp --- v3api/ticketauth.go | 56 +++++++++++++++------------------- v3api/ticketchallengescache.go | 18 +++++------ v3api/v3api.go | 8 ++--- 3 files changed, 37 insertions(+), 45 deletions(-) diff --git a/v3api/ticketauth.go b/v3api/ticketauth.go index 305f6831..8a557730 100644 --- a/v3api/ticketauth.go +++ b/v3api/ticketauth.go @@ -2,7 +2,6 @@ package v3api import ( "encoding/base64" - "fmt" "strconv" "strings" "time" @@ -32,21 +31,38 @@ func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress return } - // confirm that the timestamp signature is a valid base64 string - decodedSignature, err := base64.StdEncoding.DecodeString(timestampSignature) + // Ensure that this signature had not been used in a previous authentication attempt. + if v3Api.processedTicketChallenges.containsChallenge(timestampSignature) { + log.Warnf("disallowed reuse of ticket auth signature %v", timestampSignature) + return + } + + authTimestamp, err := strconv.Atoi(timestamp) if err != nil { - log.Warnf("invalid API v3 signature %s", timestampSignature) + log.Warnf("invalid ticket auth timestamp value %v", timestamp) return } - // todo check if ticket belongs to this vsp + // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. + timestampDelta := time.Now().Unix() - int64(authTimestamp) + if timestampDelta < 0 || timestampDelta > v3Api.ticketChallengeMaxAge { + log.Warnf("expired ticket auth timestamp value %v", timestamp) + return + } - // check if timestamp is not yet expired and has not been used previously - if err := v3Api.validateTimestamp(timestamp, v3Api.ticketChallengeMaxAge); err != nil { - log.Warnf("ticket auth timestamp failed validation: %v", err) + // confirm that the timestamp signature is a valid base64 string + decodedSignature, err := base64.StdEncoding.DecodeString(timestampSignature) + if err != nil { + log.Warnf("invalid ticket auth signature %s", timestampSignature) return } + // Mark this timestamp signature as used to prevent subsequent reuse. + challengeExpiresIn := v3Api.ticketChallengeMaxAge - timestampDelta + v3Api.processedTicketChallenges.addChallenge(timestampSignature, challengeExpiresIn) + + // todo check if ticket belongs to this vsp + // get user wallet address using ticket hash // todo: may be better to maintain a memory map of tickets-userWalletAddresses ticketInfo, err := v3Api.stakepooldConnMan.GetTicketInfo(ticketHash) @@ -96,27 +112,3 @@ func getAuthValueFromParam(paramKeyValue, key string) string { } return "" } - -func (v3Api *V3API) validateTimestamp(timestampMessage string, ticketChallengeMaxAge int64) error { - authTimestamp, err := strconv.Atoi(timestampMessage) - if err != nil { - return fmt.Errorf("invalid timestamp value %v: %v", timestampMessage, err) - } - - // Ensure that timestamp had not been used in a previous authentication attempt. - if v3Api.processedTicketChallenges.containsChallenge(timestampMessage) { - return fmt.Errorf("disallowed reuse of timestamp value %v", timestampMessage) - } - - // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. - timestampDelta := time.Now().Unix() - int64(authTimestamp) - if timestampDelta < 0 || timestampDelta > ticketChallengeMaxAge { - return fmt.Errorf("expired timestamp value %v", timestampMessage) - } - - // Save this timestamp value as used to prevent subsequent reuse. - challengeExpiresIn := ticketChallengeMaxAge - timestampDelta - v3Api.processedTicketChallenges.addChallenge(timestampMessage, challengeExpiresIn) - - return nil -} diff --git a/v3api/ticketchallengescache.go b/v3api/ticketchallengescache.go index e4c1f3d6..955e54aa 100644 --- a/v3api/ticketchallengescache.go +++ b/v3api/ticketchallengescache.go @@ -7,20 +7,20 @@ import ( type ticketChallengesCache struct { sync.Mutex - data map[string]int64 // [timestamp]expiry + usedSignatures map[string]int64 // [signature]expiry } func newTicketChallengesCache() *ticketChallengesCache { cache := &ticketChallengesCache{ - data: make(map[string]int64, 0), + usedSignatures: make(map[string]int64, 0), } go func() { for now := range time.Tick(time.Second) { cache.Lock() - for timestampChallenge, challengeExpiry := range cache.data { + for usedSignature, challengeExpiry := range cache.usedSignatures { if now.Unix() > challengeExpiry { - delete(cache.data, timestampChallenge) + delete(cache.usedSignatures, usedSignature) } } cache.Unlock() @@ -30,17 +30,17 @@ func newTicketChallengesCache() *ticketChallengesCache { return cache } -func (cache *ticketChallengesCache) addChallenge(timestamp string, expiresIn int64) { +func (cache *ticketChallengesCache) addChallenge(signature string, expiresIn int64) { cache.Lock() - if _, ok := cache.data[timestamp]; !ok { - cache.data[timestamp] = time.Now().Unix() + expiresIn + if _, ok := cache.usedSignatures[signature]; !ok { + cache.usedSignatures[signature] = time.Now().Unix() + expiresIn } cache.Unlock() } -func (cache *ticketChallengesCache) containsChallenge(timestamp string) (exists bool) { +func (cache *ticketChallengesCache) containsChallenge(signatures string) (exists bool) { cache.Lock() - _, exists = cache.data[timestamp] + _, exists = cache.usedSignatures[signatures] cache.Unlock() return } diff --git a/v3api/v3api.go b/v3api/v3api.go index 696f1726..d3e7cb56 100644 --- a/v3api/v3api.go +++ b/v3api/v3api.go @@ -5,16 +5,16 @@ import ( ) type V3API struct { - stakepooldConnMan *stakepooldclient.StakepooldManager + stakepooldConnMan *stakepooldclient.StakepooldManager - ticketChallengeMaxAge int64 + ticketChallengeMaxAge int64 processedTicketChallenges *ticketChallengesCache } func New(stakepooldConnMan *stakepooldclient.StakepooldManager, ticketChallengeMaxAge int64) *V3API { return &V3API{ - stakepooldConnMan: stakepooldConnMan, - ticketChallengeMaxAge: ticketChallengeMaxAge, + stakepooldConnMan: stakepooldConnMan, + ticketChallengeMaxAge: ticketChallengeMaxAge, processedTicketChallenges: newTicketChallengesCache(), } } From 1d4dca5673c838844901727990d88925810e71db Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Fri, 9 Aug 2019 23:47:53 +0100 Subject: [PATCH 13/18] return more descriptive error messages on ticket auth failure --- controllers/main.go | 4 ++++ v3api/middleware.go | 7 +++++-- v3api/ticketauth.go | 21 +++++++++++---------- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/controllers/main.go b/controllers/main.go index 78da9b23..b2a9e9f1 100644 --- a/controllers/main.go +++ b/controllers/main.go @@ -287,6 +287,10 @@ func (controller *MainController) API(c web.C, r *http.Request) *system.APIRespo } } + if invalidAuthMessage := c.Env["InvalidTicketAuthMessage"]; invalidAuthMessage != "" && code == codes.Unauthenticated { + err = fmt.Errorf("error processing ticket auth data: %s", invalidAuthMessage) + } + if err != nil { status = "error" response = response + " - " + err.Error() diff --git a/v3api/middleware.go b/v3api/middleware.go index 98a138a1..6d2b593b 100644 --- a/v3api/middleware.go +++ b/v3api/middleware.go @@ -13,8 +13,11 @@ func (v3Api *V3API) ApplyTicketAuth(c *web.C, h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/api/v3") { authHeader := r.Header.Get("Authorization") - msa := v3Api.validateTicketOwnership(authHeader) - if msa != "" { + msa, invalidAuthResponse := v3Api.validateTicketOwnership(authHeader) + if invalidAuthResponse != "" { + log.Warnf(invalidAuthResponse) + c.Env["InvalidTicketAuthMessage"] = invalidAuthResponse + } else if msa != "" { dbMap := c.Env["DbMap"].(*gorp.DbMap) user, err := models.GetUserByMSA(dbMap, msa) diff --git a/v3api/ticketauth.go b/v3api/ticketauth.go index 8a557730..69b7c47d 100644 --- a/v3api/ticketauth.go +++ b/v3api/ticketauth.go @@ -8,6 +8,7 @@ import ( "github.com/decred/dcrd/dcrutil" "github.com/decred/dcrwallet/wallet" + "fmt" ) const ( @@ -19,41 +20,41 @@ const ( MaxTicketChallengeAge = 60 * 30 // 30 minutes ) -func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress string) { +func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress, authValidationFailureReason string) { if !strings.HasPrefix(authHeader, customAuthScheme) { - log.Warnf("invalid API v3 auth header value %s", authHeader) + authValidationFailureReason = fmt.Sprintf("invalid API v3 auth header value %s", authHeader) return } timestamp, timestampSignature, ticketHash := extractAuthParams(strings.TrimPrefix(authHeader, customAuthScheme)) if timestamp == "" || timestampSignature == "" || ticketHash == "" { - log.Warnf("invalid API v3 auth header value %s", authHeader) + authValidationFailureReason = fmt.Sprintf("invalid API v3 auth header value %s", authHeader) return } // Ensure that this signature had not been used in a previous authentication attempt. if v3Api.processedTicketChallenges.containsChallenge(timestampSignature) { - log.Warnf("disallowed reuse of ticket auth signature %v", timestampSignature) + authValidationFailureReason = fmt.Sprintf("disallowed reuse of ticket auth signature %v", timestampSignature) return } authTimestamp, err := strconv.Atoi(timestamp) if err != nil { - log.Warnf("invalid ticket auth timestamp value %v", timestamp) + authValidationFailureReason = fmt.Sprintf("invalid ticket auth timestamp value %v", timestamp) return } // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. timestampDelta := time.Now().Unix() - int64(authTimestamp) if timestampDelta < 0 || timestampDelta > v3Api.ticketChallengeMaxAge { - log.Warnf("expired ticket auth timestamp value %v", timestamp) + authValidationFailureReason = fmt.Sprintf("expired ticket auth timestamp value %v", timestamp) return } // confirm that the timestamp signature is a valid base64 string decodedSignature, err := base64.StdEncoding.DecodeString(timestampSignature) if err != nil { - log.Warnf("invalid ticket auth signature %s", timestampSignature) + authValidationFailureReason = fmt.Sprintf("invalid ticket auth signature %s", timestampSignature) return } @@ -67,20 +68,20 @@ func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress // todo: may be better to maintain a memory map of tickets-userWalletAddresses ticketInfo, err := v3Api.stakepooldConnMan.GetTicketInfo(ticketHash) if err != nil { - log.Warnf("ticket auth, get ticket info failed: %v", err) + authValidationFailureReason = fmt.Sprintf("ticket auth, get ticket info failed: %v", err) return } // check if timestamp signature checks out against address addr, err := dcrutil.DecodeAddress(ticketInfo.OwnerFeeAddress) if err != nil { - log.Errorf("ticket auth, unexpected decode address error: %v", err) + authValidationFailureReason = fmt.Sprintf("ticket auth, unexpected decode address error: %v", err) return } valid, err := wallet.VerifyMessage(timestamp, addr, decodedSignature) if err != nil { - log.Errorf("error validating timestamp signature for ticket auth %v", err) + authValidationFailureReason = fmt.Sprintf("error validating timestamp signature for ticket auth %v", err) return } From 7ebbc771053f2114baf73e2f0854bdda00664c64 Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Wed, 21 Aug 2019 06:24:27 +0100 Subject: [PATCH 14/18] support new ticketauth api auth scheme on /v1 and /v2 api endpoints --- config.go | 9 ++-- log.go | 4 -- server.go | 9 ++-- v3api/ticketauth.go => system/api-auth.go | 55 ++++++++++++---------- system/core.go | 23 +++++---- system/middleware.go | 48 ++++++++++--------- {v3api => system}/ticketchallengescache.go | 6 +-- v3api/log.go | 26 ---------- v3api/middleware.go | 36 -------------- v3api/v3api.go | 20 -------- 10 files changed, 81 insertions(+), 155 deletions(-) rename v3api/ticketauth.go => system/api-auth.go (58%) rename {v3api => system}/ticketchallengescache.go (85%) delete mode 100644 v3api/log.go delete mode 100644 v3api/middleware.go delete mode 100644 v3api/v3api.go diff --git a/config.go b/config.go index 73c4980a..bd134a78 100644 --- a/config.go +++ b/config.go @@ -22,7 +22,6 @@ import ( "github.com/decred/dcrd/dcrutil" "github.com/decred/dcrd/hdkeychain" "github.com/decred/dcrstakepool/internal/version" - "github.com/decred/dcrstakepool/v3api" flags "github.com/jessevdk/go-flags" ) @@ -51,6 +50,8 @@ const ( defaultMaxVotedTickets = 1000 defaultDescription = "" defaultDesignation = "" + + MaxTicketChallengeAge = 60 * 30 // 30 minutes ) var ( @@ -486,9 +487,9 @@ func loadConfig() (*config, []string, error) { } // Ensure ticket challenge max age is not greater than permitted maximum. - if cfg.TicketChallengeMaxAge > v3api.MaxTicketChallengeAge { - return nil, nil, fmt.Errorf("%s: Tickat challenge max age cannot be higher than %v", - funcName, v3api.MaxTicketChallengeAge) + if cfg.TicketChallengeMaxAge > MaxTicketChallengeAge { + return nil, nil, fmt.Errorf("%s: Ticket challenge max age cannot be higher than %v", + funcName, MaxTicketChallengeAge) } // Validate profile port number diff --git a/log.go b/log.go index d0674665..19e6918a 100644 --- a/log.go +++ b/log.go @@ -13,7 +13,6 @@ import ( "github.com/decred/dcrstakepool/models" "github.com/decred/dcrstakepool/stakepooldclient" "github.com/decred/dcrstakepool/system" - "github.com/decred/dcrstakepool/v3api" "github.com/decred/slog" "github.com/jrick/logrotate/rotator" ) @@ -51,7 +50,6 @@ var ( modelsLog = backendLog.Logger("MODL") stakepooldclientLog = backendLog.Logger("GRPC") systemLog = backendLog.Logger("SYTM") - v3APILog = backendLog.Logger("API3") ) // Initialize package-global logger variables. @@ -60,7 +58,6 @@ func init() { models.UseLogger(modelsLog) stakepooldclient.UseLogger(stakepooldclientLog) system.UseLogger(systemLog) - v3api.UseLogger(v3APILog) } // subsystemLoggers maps each subsystem identifier to its associated logger. @@ -70,7 +67,6 @@ var subsystemLoggers = map[string]slog.Logger{ "GRPC": stakepooldclientLog, "MODL": modelsLog, "SYTM": systemLog, - "API3": v3APILog, } // initLogRotator initializes the logging rotater to write logs to logFile and diff --git a/server.go b/server.go index bb89e396..e2115868 100644 --- a/server.go +++ b/server.go @@ -20,7 +20,6 @@ import ( "github.com/decred/dcrstakepool/stakepooldclient" "github.com/decred/dcrstakepool/system" - "github.com/decred/dcrstakepool/v3api" "github.com/zenazn/goji/graceful" "github.com/zenazn/goji/web" "github.com/zenazn/goji/web/middleware" @@ -64,7 +63,7 @@ func runMain() error { var application = &system.Application{} - application.Init(cfg.APISecret, cfg.BaseURL, cfg.CookieSecret, + application.Init(cfg.APISecret, cfg.TicketChallengeMaxAge, cfg.BaseURL, cfg.CookieSecret, cfg.CookieSecure, cfg.DBHost, cfg.DBName, cfg.DBPassword, cfg.DBPort, cfg.DBUser) if application.DbMap == nil { @@ -90,6 +89,8 @@ func runMain() error { return fmt.Errorf("Failed to connect to stakepoold host: %v", err) } + application.StakepooldConnMan = stakepooldConnMan + var sender email.Sender if cfg.SMTPHost != "" { sender, err = email.NewSender(cfg.SMTPHost, cfg.SMTPUsername, cfg.SMTPPassword, @@ -166,12 +167,8 @@ func runMain() error { api.Use(application.ApplyAPI) - v3Api := v3api.New(stakepooldConnMan, cfg.TicketChallengeMaxAge) - api.Use(v3Api.ApplyTicketAuth) - api.Handle("/api/v1/:command", application.APIHandler(controller.API)) api.Handle("/api/v2/:command", application.APIHandler(controller.API)) - api.Handle("/api/v3/:command", application.APIHandler(controller.API)) api.Handle("/api/*", gojify(system.APIInvalidHandler)) // HTML routes diff --git a/v3api/ticketauth.go b/system/api-auth.go similarity index 58% rename from v3api/ticketauth.go rename to system/api-auth.go index 69b7c47d..65902bf7 100644 --- a/v3api/ticketauth.go +++ b/system/api-auth.go @@ -1,39 +1,46 @@ -package v3api +package system import ( "encoding/base64" + "fmt" "strconv" "strings" "time" "github.com/decred/dcrd/dcrutil" "github.com/decred/dcrwallet/wallet" - "fmt" + "github.com/dgrijalva/jwt-go" ) -const ( - customAuthScheme = "TicketAuth" - customAuthTimestampParam = "SignedTimestamp" - customAuthSignatureParam = "Signature" - customAuthTicketHashParam = "TicketHash" +func (application *Application) validateToken(authHeader string) (int64, string) { + apitoken := strings.TrimPrefix(authHeader, "Bearer ") - MaxTicketChallengeAge = 60 * 30 // 30 minutes -) + JWTtoken, err := jwt.Parse(apitoken, func(token *jwt.Token) (interface{}, error) { + // validate signing algorithm + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return []byte(application.APISecret), nil + }) -func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress, authValidationFailureReason string) { - if !strings.HasPrefix(authHeader, customAuthScheme) { - authValidationFailureReason = fmt.Sprintf("invalid API v3 auth header value %s", authHeader) - return + if err != nil { + return -1, fmt.Sprintf("invalid token %v: %v", apitoken, err) + } else if claims, ok := JWTtoken.Claims.(jwt.MapClaims); ok && JWTtoken.Valid { + return int64(claims["loggedInAs"].(float64)), "" + } else { + return -1, fmt.Sprintf("invalid token %v", apitoken) } +} - timestamp, timestampSignature, ticketHash := extractAuthParams(strings.TrimPrefix(authHeader, customAuthScheme)) +func (application *Application) validateTicketOwnership(authHeader string) (multiSigAddress, authValidationFailureReason string) { + timestamp, timestampSignature, ticketHash := extractTicketAuthParams(strings.TrimPrefix(authHeader, "TicketAuth ")) if timestamp == "" || timestampSignature == "" || ticketHash == "" { - authValidationFailureReason = fmt.Sprintf("invalid API v3 auth header value %s", authHeader) + authValidationFailureReason = fmt.Sprintf("invalid ticket auth header value %s", authHeader) return } // Ensure that this signature had not been used in a previous authentication attempt. - if v3Api.processedTicketChallenges.containsChallenge(timestampSignature) { + if application.ProcessedTicketChallenges.ContainsChallenge(timestampSignature) { authValidationFailureReason = fmt.Sprintf("disallowed reuse of ticket auth signature %v", timestampSignature) return } @@ -46,7 +53,7 @@ func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress, // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. timestampDelta := time.Now().Unix() - int64(authTimestamp) - if timestampDelta < 0 || timestampDelta > v3Api.ticketChallengeMaxAge { + if timestampDelta < 0 || timestampDelta > application.TicketChallengeMaxAge { authValidationFailureReason = fmt.Sprintf("expired ticket auth timestamp value %v", timestamp) return } @@ -59,14 +66,14 @@ func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress, } // Mark this timestamp signature as used to prevent subsequent reuse. - challengeExpiresIn := v3Api.ticketChallengeMaxAge - timestampDelta - v3Api.processedTicketChallenges.addChallenge(timestampSignature, challengeExpiresIn) + challengeExpiresIn := application.TicketChallengeMaxAge - timestampDelta + application.ProcessedTicketChallenges.AddChallenge(timestampSignature, challengeExpiresIn) // todo check if ticket belongs to this vsp // get user wallet address using ticket hash // todo: may be better to maintain a memory map of tickets-userWalletAddresses - ticketInfo, err := v3Api.stakepooldConnMan.GetTicketInfo(ticketHash) + ticketInfo, err := application.StakepooldConnMan.GetTicketInfo(ticketHash) if err != nil { authValidationFailureReason = fmt.Sprintf("ticket auth, get ticket info failed: %v", err) return @@ -91,15 +98,15 @@ func (v3Api *V3API) validateTicketOwnership(authHeader string) (multiSigAddress, return } -func extractAuthParams(authHeader string) (timestampMessage, timestampSignature, ticketHash string) { +func extractTicketAuthParams(authHeader string) (timestampMessage, timestampSignature, ticketHash string) { authParams := strings.Split(authHeader, ",") for _, param := range authParams { paramKeyValue := strings.TrimSpace(param) - if value := getAuthValueFromParam(paramKeyValue, customAuthTimestampParam); value != "" { + if value := getAuthValueFromParam(paramKeyValue, "SignedTimestamp"); value != "" { timestampMessage = strings.TrimSpace(value) - } else if value := getAuthValueFromParam(paramKeyValue, customAuthSignatureParam); value != "" { + } else if value := getAuthValueFromParam(paramKeyValue, "Signature"); value != "" { timestampSignature = strings.TrimSpace(value) - } else if value := getAuthValueFromParam(paramKeyValue, customAuthTicketHashParam); value != "" { + } else if value := getAuthValueFromParam(paramKeyValue, "TicketHash"); value != "" { ticketHash = strings.TrimSpace(value) } } diff --git a/system/core.go b/system/core.go index 9245ebc8..a18da209 100644 --- a/system/core.go +++ b/system/core.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/decred/dcrstakepool/models" + "github.com/decred/dcrstakepool/stakepooldclient" "github.com/go-gorp/gorp" "github.com/gorilla/sessions" "github.com/zenazn/goji/web" @@ -21,11 +22,14 @@ import ( ) type Application struct { - APISecret string - Template *template.Template - TemplatesPath string - Store *SQLStore - DbMap *gorp.DbMap + APISecret string + TicketChallengeMaxAge int64 + ProcessedTicketChallenges *ticketChallengesCache + Template *template.Template + TemplatesPath string + Store *SQLStore + DbMap *gorp.DbMap + StakepooldConnMan *stakepooldclient.StakepooldManager } // GojiWebHandlerFunc is an adaptor that allows an http.HanderFunc where a @@ -36,10 +40,9 @@ func GojiWebHandlerFunc(h http.HandlerFunc) web.HandlerFunc { } } -func (application *Application) Init(APISecret string, baseURL string, - cookieSecret string, cookieSecure bool, DBHost string, DBName string, - DBPassword string, - DBPort string, DBUser string) { +func (application *Application) Init(APISecret string, ticketChallengeMaxAge int64, + baseURL string, cookieSecret string, cookieSecure bool, + DBHost string, DBName string, DBPassword string, DBPort string, DBUser string) { application.DbMap = models.GetDbMap( APISecret, @@ -62,6 +65,8 @@ func (application *Application) Init(APISecret string, baseURL string, } application.APISecret = APISecret + application.TicketChallengeMaxAge = ticketChallengeMaxAge + application.ProcessedTicketChallenges = newTicketChallengesCache() } func (application *Application) LoadTemplates(templatePath string) error { diff --git a/system/middleware.go b/system/middleware.go index 3037ce4f..ed5f04ec 100644 --- a/system/middleware.go +++ b/system/middleware.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/decred/dcrstakepool/models" - "github.com/dgrijalva/jwt-go" "github.com/go-gorp/gorp" "github.com/gorilla/sessions" "github.com/zenazn/goji/web" @@ -45,33 +44,36 @@ func (application *Application) ApplyDbMap(c *web.C, h http.Handler) http.Handle func (application *Application) ApplyAPI(c *web.C, h http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/api") { + var user *models.User + var err error + dbMap := c.Env["DbMap"].(*gorp.DbMap) + authHeader := r.Header.Get("Authorization") if strings.HasPrefix(authHeader, "Bearer ") { - apitoken := strings.TrimPrefix(authHeader, "Bearer ") - - JWTtoken, err := jwt.Parse(apitoken, func(token *jwt.Token) (interface{}, error) { - // validate signing algorithm - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) - } - return []byte(application.APISecret), nil - }) - - if err != nil { - log.Warnf("invalid token %v: %v", apitoken, err) - } else if claims, ok := JWTtoken.Claims.(jwt.MapClaims); ok && JWTtoken.Valid { - dbMap := c.Env["DbMap"].(*gorp.DbMap) - - user, err := models.GetUserById(dbMap, int64(claims["loggedInAs"].(float64))) - if err != nil { - log.Errorf("unable to map apitoken %v to user id %v", apitoken, claims["loggedInAs"]) - } else { - c.Env["APIUserID"] = user.Id - log.Infof("mapped apitoken %v to user id %v", apitoken, user.Id) - } + userId, authFailureReason := application.validateToken(authHeader) + if authFailureReason != "" { + err = fmt.Errorf(authFailureReason) + } else { + user, err = models.GetUserById(dbMap, userId) + } + } else if strings.HasPrefix(authHeader, "TicketAuth ") { + userMsa, authFailureReason := application.validateTicketOwnership(authHeader) + if authFailureReason != "" { + err = fmt.Errorf(authFailureReason) + } else { + user, err = models.GetUserByMSA(dbMap, userMsa) } } + + if err != nil { + log.Warnf("api authorization failure: %v", err) + c.Env["AuthErrorMessage"] = err.Error() + } else { + c.Env["APIUserID"] = user.Id + log.Infof("mapped api auth header %v to user %v", authHeader, user.Id) + } } + h.ServeHTTP(w, r) } return http.HandlerFunc(fn) diff --git a/v3api/ticketchallengescache.go b/system/ticketchallengescache.go similarity index 85% rename from v3api/ticketchallengescache.go rename to system/ticketchallengescache.go index 955e54aa..5c934a66 100644 --- a/v3api/ticketchallengescache.go +++ b/system/ticketchallengescache.go @@ -1,4 +1,4 @@ -package v3api +package system import ( "sync" @@ -30,7 +30,7 @@ func newTicketChallengesCache() *ticketChallengesCache { return cache } -func (cache *ticketChallengesCache) addChallenge(signature string, expiresIn int64) { +func (cache *ticketChallengesCache) AddChallenge(signature string, expiresIn int64) { cache.Lock() if _, ok := cache.usedSignatures[signature]; !ok { cache.usedSignatures[signature] = time.Now().Unix() + expiresIn @@ -38,7 +38,7 @@ func (cache *ticketChallengesCache) addChallenge(signature string, expiresIn int cache.Unlock() } -func (cache *ticketChallengesCache) containsChallenge(signatures string) (exists bool) { +func (cache *ticketChallengesCache) ContainsChallenge(signatures string) (exists bool) { cache.Lock() _, exists = cache.usedSignatures[signatures] cache.Unlock() diff --git a/v3api/log.go b/v3api/log.go deleted file mode 100644 index 9eb506e0..00000000 --- a/v3api/log.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2013-2015 The btcsuite developers -// Copyright (c) 2018 The Decred developers -// Use of this source code is governed by an ISC -// license that can be found in the LICENSE file. - -package v3api - -import "github.com/decred/slog" - -// log is a logger that is initialized with no output filters. This -// means the package will not perform any logging by default until the caller -// requests it. -var log = slog.Disabled - -// DisableLog disables all library log output. Logging output is disabled -// by default until either UseLogger or SetLogWriter are called. -func DisableLog() { - log = slog.Disabled -} - -// UseLogger uses a specified Logger to output package logging info. -// This should be used in preference to SetLogWriter if the caller is also -// using slog. -func UseLogger(logger slog.Logger) { - log = logger -} diff --git a/v3api/middleware.go b/v3api/middleware.go deleted file mode 100644 index 6d2b593b..00000000 --- a/v3api/middleware.go +++ /dev/null @@ -1,36 +0,0 @@ -package v3api - -import ( - "net/http" - "strings" - - "github.com/decred/dcrstakepool/models" - "github.com/go-gorp/gorp" - "github.com/zenazn/goji/web" -) - -func (v3Api *V3API) ApplyTicketAuth(c *web.C, h http.Handler) http.Handler { - fn := func(w http.ResponseWriter, r *http.Request) { - if strings.HasPrefix(r.URL.Path, "/api/v3") { - authHeader := r.Header.Get("Authorization") - msa, invalidAuthResponse := v3Api.validateTicketOwnership(authHeader) - if invalidAuthResponse != "" { - log.Warnf(invalidAuthResponse) - c.Env["InvalidTicketAuthMessage"] = invalidAuthResponse - } else if msa != "" { - dbMap := c.Env["DbMap"].(*gorp.DbMap) - - user, err := models.GetUserByMSA(dbMap, msa) - if err != nil { - log.Errorf("unable to map ticket auth %v with multisig address %v to user", - authHeader, msa) - } else { - c.Env["APIUserID"] = user.Id - log.Infof("mapped ticket auth %v to user id %v", authHeader, user.Id) - } - } - } - h.ServeHTTP(w, r) - } - return http.HandlerFunc(fn) -} diff --git a/v3api/v3api.go b/v3api/v3api.go deleted file mode 100644 index d3e7cb56..00000000 --- a/v3api/v3api.go +++ /dev/null @@ -1,20 +0,0 @@ -package v3api - -import ( - "github.com/decred/dcrstakepool/stakepooldclient" -) - -type V3API struct { - stakepooldConnMan *stakepooldclient.StakepooldManager - - ticketChallengeMaxAge int64 - processedTicketChallenges *ticketChallengesCache -} - -func New(stakepooldConnMan *stakepooldclient.StakepooldManager, ticketChallengeMaxAge int64) *V3API { - return &V3API{ - stakepooldConnMan: stakepooldConnMan, - ticketChallengeMaxAge: ticketChallengeMaxAge, - processedTicketChallenges: newTicketChallengesCache(), - } -} From 0b803dbfd8ea28744fc97da3b70f11a47fbc674d Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Wed, 21 Aug 2019 06:30:36 +0100 Subject: [PATCH 15/18] delete unwanted changes --- controllers/main.go | 12 ++++++------ server.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/controllers/main.go b/controllers/main.go index b2a9e9f1..82a72b00 100644 --- a/controllers/main.go +++ b/controllers/main.go @@ -287,8 +287,8 @@ func (controller *MainController) API(c web.C, r *http.Request) *system.APIRespo } } - if invalidAuthMessage := c.Env["InvalidTicketAuthMessage"]; invalidAuthMessage != "" && code == codes.Unauthenticated { - err = fmt.Errorf("error processing ticket auth data: %s", invalidAuthMessage) + if invalidAuthMessage := c.Env["AuthErrorMessage"]; invalidAuthMessage != "" && code == codes.Unauthenticated { + err = fmt.Errorf("invalid api authorization: %s", invalidAuthMessage) } if err != nil { @@ -1605,10 +1605,10 @@ func (controller *MainController) LoginPost(c web.C, r *http.Request) (string, i log.Infof("Login POST from %v, email %v", remoteIP, user.Email) - //if user.EmailVerified == 0 { - // session.AddFlash("You must validate your email address", "loginError") - // return controller.Login(c, r) - //} + if user.EmailVerified == 0 { + session.AddFlash("You must validate your email address", "loginError") + return controller.Login(c, r) + } session.Values["UserId"] = user.Id diff --git a/server.go b/server.go index e2115868..30f0f213 100644 --- a/server.go +++ b/server.go @@ -80,7 +80,7 @@ func runMain() error { rpcclient.UseLogger(log) // Supported API versions are advertised in the API stats result - APIVersionsSupported := []int{1, 2, 3} + APIVersionsSupported := []int{1, 2} var stakepooldConnMan *stakepooldclient.StakepooldManager From 1376b8302cc56a0494a531fdfc3c7424201f1d31 Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Fri, 30 Aug 2019 22:16:19 +0100 Subject: [PATCH 16/18] debug auth header timestamp expiry --- config.go | 2 +- system/api-auth.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config.go b/config.go index bd134a78..999e9048 100644 --- a/config.go +++ b/config.go @@ -33,7 +33,7 @@ const ( defaultLogLevel = "info" defaultLogDirname = "logs" defaultLogFilename = "dcrstakepool.log" - defaultTicketChallengeMaxAge = 600 + defaultTicketChallengeMaxAge = 600 // 10 minutes defaultCookieSecure = false defaultDBHost = "localhost" defaultDBName = "stakepool" diff --git a/system/api-auth.go b/system/api-auth.go index 65902bf7..749bddae 100644 --- a/system/api-auth.go +++ b/system/api-auth.go @@ -54,7 +54,8 @@ func (application *Application) validateTicketOwnership(authHeader string) (mult // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. timestampDelta := time.Now().Unix() - int64(authTimestamp) if timestampDelta < 0 || timestampDelta > application.TicketChallengeMaxAge { - authValidationFailureReason = fmt.Sprintf("expired ticket auth timestamp value %v", timestamp) + authValidationFailureReason = fmt.Sprintf("expired ticket auth timestamp value %v, expired by %d second", + timestamp, timestampDelta) return } From fe68864e3d9fc87da62dd0c83451155506ac5baa Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Sat, 31 Aug 2019 00:41:30 +0100 Subject: [PATCH 17/18] allow timestamps value 5mins into future --- system/api-auth.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/system/api-auth.go b/system/api-auth.go index 749bddae..b440b944 100644 --- a/system/api-auth.go +++ b/system/api-auth.go @@ -51,11 +51,16 @@ func (application *Application) validateTicketOwnership(authHeader string) (mult return } - // Ensure that the auth timestamp is not in the future and is not more than 30 seconds into the past. + // Ensure that the auth timestamp + // - is not more than 5 minutes into the future + // - is not more than 30 seconds into the past timestampDelta := time.Now().Unix() - int64(authTimestamp) - if timestampDelta < 0 || timestampDelta > application.TicketChallengeMaxAge { - authValidationFailureReason = fmt.Sprintf("expired ticket auth timestamp value %v, expired by %d second", - timestamp, timestampDelta) + if timestampDelta < -300 { + // more than 5 minutes into the future + authValidationFailureReason = "invalid (future) timestamp" + return + } else if timestampDelta > application.TicketChallengeMaxAge { + authValidationFailureReason = fmt.Sprintf("expired ticket auth timestamp value %v", timestamp) return } From 9219bf1a9adb707bec952f90e366cfcb9421acb8 Mon Sep 17 00:00:00 2001 From: Wisdom Arerosuoghene Date: Sat, 31 Aug 2019 02:08:05 +0100 Subject: [PATCH 18/18] allow access to some /api routes without valid auth header --- system/middleware.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/middleware.go b/system/middleware.go index ed5f04ec..fd2feb60 100644 --- a/system/middleware.go +++ b/system/middleware.go @@ -68,7 +68,7 @@ func (application *Application) ApplyAPI(c *web.C, h http.Handler) http.Handler if err != nil { log.Warnf("api authorization failure: %v", err) c.Env["AuthErrorMessage"] = err.Error() - } else { + } else if user != nil { c.Env["APIUserID"] = user.Id log.Infof("mapped api auth header %v to user %v", authHeader, user.Id) }