Skip to content

Commit

Permalink
Expose an endpoint with all of the witness keys (#114)
Browse files Browse the repository at this point in the history
Each of the environments will be configured differently, and thus using the single embedded config on both the client and the server to enumerate the witnesses was brittle/broken.

This change temporarily breaks the client until all distributors are updated to have the new endpoint, but we're still at v0 and I'll get this rolled out within a matter of hours so I'll avoid the tax of doing this the staged way. This is also a breaking change at the code level, but again, this is v0 and we can flex that licence.
  • Loading branch information
mhutchinson authored Feb 27, 2024
1 parent 67533a6 commit 50ddc6c
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 29 deletions.
3 changes: 3 additions & 0 deletions api/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
18 changes: 18 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
23 changes: 16 additions & 7 deletions cmd/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
29 changes: 22 additions & 7 deletions cmd/internal/distributor/distributor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down
15 changes: 15 additions & 0 deletions cmd/internal/http/mock_distributor_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 24 additions & 2 deletions cmd/internal/http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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.
Expand Down Expand Up @@ -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 {
Expand All @@ -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-]+}"
Expand All @@ -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 {
Expand Down
8 changes: 2 additions & 6 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 6 additions & 7 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
}

0 comments on commit 50ddc6c

Please sign in to comment.