diff --git a/api/http.go b/api/http.go index 1987d9d..3524fa7 100644 --- a/api/http.go +++ b/api/http.go @@ -30,4 +30,7 @@ const ( // HTTPGetLogs is the path of the URL to get a list of all logs the // distributor is aware of. HTTPGetLogs = "/distributor/v0/logs" + // HTTPGetWitnesses is the path of the URL to get a list of all witnesses + // that the distributor is aware of. + HTTPGetWitnesses = "/distributor/v0/witnesses" ) diff --git a/client/client.go b/client/client.go index aa07e87..cd8d8df 100644 --- a/client/client.go +++ b/client/client.go @@ -61,6 +61,24 @@ func (d *RestDistributor) GetLogs() ([]LogID, error) { return r, nil } +// GetWitnesses returns the verifier keys for all witnesses that +// the distributor knows about. +func (d *RestDistributor) GetWitnesses() ([]string, error) { + u, err := url.Parse(d.baseURL + api.HTTPGetWitnesses) + if err != nil { + return nil, err + } + r := make([]string, 0) + bs, err := d.fetchData(u) + if err != nil { + return nil, err + } + if err := json.Unmarshal(bs, &r); err != nil { + return nil, err + } + return r, nil +} + // GetCheckpointN returns the freshest checkpoint for the log that at least N witnesses // have provided signatures for. func (d *RestDistributor) GetCheckpointN(l LogID, n uint) ([]byte, error) { diff --git a/cmd/client/client.go b/cmd/client/client.go index 29113c7..47c3e83 100644 --- a/cmd/client/client.go +++ b/cmd/client/client.go @@ -40,11 +40,11 @@ var ( func main() { flag.Parse() - ls := getLogsOrDie() - ws := getWitnessesOrDie() - d := client.NewRestDistributor(*baseURL, http.DefaultClient) + ls := getLogsOrDie() + ws := getWitnessesOrDie(d) + logs, err := d.GetLogs() if err != nil { glog.Exitf("❌ Failed to enumerate logs: %v", err) @@ -91,10 +91,19 @@ func getLogsOrDie() map[string]config.LogInfo { return r } -func getWitnessesOrDie() map[uint32]note.Verifier { - r, err := config.DefaultWitnesses() +func getWitnessesOrDie(c *client.RestDistributor) map[uint32]note.Verifier { + rawWs, err := c.GetWitnesses() if err != nil { - glog.Exitf("Failed to get witness config: %v", err) + glog.Exitf("Failed to GetWitnesses() from distributor: %v", err) } - return r + + ws := make(map[uint32]note.Verifier) + for _, w := range rawWs { + wSigV, err := f_note.NewVerifierForCosignatureV1(w) + if err != nil { + glog.Exitf("Invalid witness public key retrieved from distributor: %v: %v", w, err) + } + ws[wSigV.KeyHash()] = wSigV + } + return ws } diff --git a/cmd/internal/distributor/distributor.go b/cmd/internal/distributor/distributor.go index 55fd826..befbb1f 100644 --- a/cmd/internal/distributor/distributor.go +++ b/cmd/internal/distributor/distributor.go @@ -71,22 +71,31 @@ var ( // NewDistributor returns a distributor that will accept checkpoints from // the given witnesses, for the given logs, and persist its state in the // database provided. Callers must call Init() on the returned distributor. -// `ws` is a map from witness ID (verifier key name) to the note verifier. +// `ws` is a map from witness raw verifier string to the note verifier. // `ls` is a map from log ID (github.com/transparency-dev/formats/log.ID) to log info. func NewDistributor(ws map[string]note.Verifier, ls map[string]config.LogInfo, db *sql.DB) (*Distributor, error) { + witsByID := make(map[string]note.Verifier, len(ws)) + rawVKeys := make([]string, 0, len(ws)) + for k, v := range ws { + rawVKeys = append(rawVKeys, k) + witsByID[v.Name()] = v + } + sort.Strings(rawVKeys) d := &Distributor{ - ws: ws, - ls: ls, - db: db, + ws: ws, + witKeys: rawVKeys, + ls: ls, + db: db, } return d, d.init() } // Distributor persists witnessed checkpoints and allows querying of them. type Distributor struct { - ws map[string]note.Verifier - ls map[string]config.LogInfo - db *sql.DB + ws map[string]note.Verifier + witKeys []string + ls map[string]config.LogInfo + db *sql.DB } // GetLogs returns a list of all log IDs the distributor is aware of, sorted @@ -100,6 +109,12 @@ func (d *Distributor) GetLogs(ctx context.Context) ([]string, error) { return r, nil } +// GetLogs returns a list of all witness verifier keys that the distributor is +// aware of, sorted by the key. +func (d *Distributor) GetWitnesses(ctx context.Context) ([]string, error) { + return d.witKeys, nil +} + // GetCheckpointN gets the largest checkpoint for a given log that has at least `n` signatures. func (d *Distributor) GetCheckpointN(ctx context.Context, logID string, n uint32) ([]byte, error) { counterCheckpointGetNRequests.Inc() diff --git a/cmd/internal/http/mock_distributor_test.go b/cmd/internal/http/mock_distributor_test.go index e0e40c1..94b7c8c 100644 --- a/cmd/internal/http/mock_distributor_test.go +++ b/cmd/internal/http/mock_distributor_test.go @@ -91,3 +91,18 @@ func (mr *MockDistributorMockRecorder) GetLogs(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogs", reflect.TypeOf((*MockDistributor)(nil).GetLogs), arg0) } + +// GetWitnesses mocks base method. +func (m *MockDistributor) GetWitnesses(arg0 context.Context) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetWitnesses", arg0) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetWitnesses indicates an expected call of GetWitnesses. +func (mr *MockDistributorMockRecorder) GetWitnesses(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWitnesses", reflect.TypeOf((*MockDistributor)(nil).GetWitnesses), arg0) +} diff --git a/cmd/internal/http/server.go b/cmd/internal/http/server.go index 7a81193..355ed70 100644 --- a/cmd/internal/http/server.go +++ b/cmd/internal/http/server.go @@ -24,8 +24,8 @@ import ( "strconv" "github.com/golang/glog" - "github.com/transparency-dev/distributor/api" "github.com/gorilla/mux" + "github.com/transparency-dev/distributor/api" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -35,6 +35,9 @@ type Distributor interface { // GetLogs returns a list of all log IDs the distributor is aware of, sorted // by the ID. GetLogs(ctx context.Context) ([]string, error) + // GetWitnesses returns a list of all witness verifier keys the distributor is + // aware of, sorted by the ID. + GetWitnesses(ctx context.Context) ([]string, error) // GetCheckpointN gets the largest checkpoint for a given log that has at least `n` signatures. GetCheckpointN(ctx context.Context, logID string, n uint32) ([]byte, error) // GetCheckpointWitness gets the largest checkpoint for the log that was witnessed by the given witness. @@ -115,7 +118,7 @@ func (s *Server) getCheckpointWitness(w http.ResponseWriter, r *http.Request) { } } -// getLogs returns a list of all logs the witness is aware of. +// getLogs returns a list of all logs the distributor is aware of. func (s *Server) getLogs(w http.ResponseWriter, r *http.Request) { logs, err := s.d.GetLogs(r.Context()) if err != nil { @@ -133,6 +136,24 @@ func (s *Server) getLogs(w http.ResponseWriter, r *http.Request) { } } +// getWitnesses returns a list of all witnesses the distributor is aware of. +func (s *Server) getWitnesses(w http.ResponseWriter, r *http.Request) { + witnesses, err := s.d.GetWitnesses(r.Context()) + if err != nil { + http.Error(w, fmt.Sprintf("failed to get witness list: %v", err), http.StatusInternalServerError) + return + } + witnessList, err := json.Marshal(witnesses) + if err != nil { + http.Error(w, fmt.Sprintf("failed to convert witness list to JSON: %v", err), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "text/json") + if _, err := w.Write(witnessList); err != nil { + glog.Errorf("w.Write(): %v", err) + } +} + // RegisterHandlers registers HTTP handlers for witness endpoints. func (s *Server) RegisterHandlers(r *mux.Router) { logStr := "{logid:[a-zA-Z0-9-]+}" @@ -141,6 +162,7 @@ func (s *Server) RegisterHandlers(r *mux.Router) { r.HandleFunc(fmt.Sprintf(api.HTTPCheckpointByWitness, logStr, witStr), s.update).Methods("PUT") r.HandleFunc(fmt.Sprintf(api.HTTPCheckpointByWitness, logStr, witStr), s.getCheckpointWitness).Methods("GET") r.HandleFunc(api.HTTPGetLogs, s.getLogs).Methods("GET") + r.HandleFunc(api.HTTPGetWitnesses, s.getWitnesses).Methods("GET") } func httpForCode(c codes.Code) int { diff --git a/cmd/main.go b/cmd/main.go index 2dd7266..2cfcb6d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -193,12 +193,8 @@ func getWitnessesOrDie() map[string]note.Verifier { glog.Exitf("Failed to unmarshal witness config: %v", err) } - r := make(map[string]note.Verifier, len(w)) - for _, v := range w { - r[v.Name()] = v - } - glog.Infof("Configured with %d witness keys: %s", len(r), r) - return r + glog.Infof("Configured with %d witness keys: %s", len(w), w) + return w } type witFlags []string diff --git a/config/config.go b/config/config.go index 700d9ac..2423513 100644 --- a/config/config.go +++ b/config/config.go @@ -41,8 +41,8 @@ func DefaultLogs() (map[string]LogInfo, error) { } // DeafultWitnesses returns a parsed representation of the embedded WitnessesYAML config. -// The returned map is keyed by the witness verifier key hash. -func DefaultWitnesses() (map[uint32]note.Verifier, error) { +// The returned map is keyed by the raw verifier key string. +func DefaultWitnesses() (map[string]note.Verifier, error) { return ParseWitnessesConfig(WitnessesYAML) } @@ -80,22 +80,21 @@ func ParseLogConfig(y []byte) (map[string]LogInfo, error) { } // ParseWitnessesConfig parses the passed in witnesses config, and returns a map keyed -// by witness verified key hash. -func ParseWitnessesConfig(y []byte) (map[uint32]note.Verifier, error) { +// by the raw verifier key string. +func ParseWitnessesConfig(y []byte) (map[string]note.Verifier, error) { witCfg := struct { Witnesses []string `yaml:"Witnesses"` }{} if err := yaml.Unmarshal(y, &witCfg); err != nil { return nil, fmt.Errorf("failed to unmarshal witness config: %v", err) } - ws := make(map[uint32]note.Verifier) + ws := make(map[string]note.Verifier) for _, w := range witCfg.Witnesses { wSigV, err := f_note.NewVerifierForCosignatureV1(w) if err != nil { return nil, fmt.Errorf("invalid witness public key: %v", err) } - ws[wSigV.KeyHash()] = wSigV + ws[w] = wSigV } - return ws, nil }