Skip to content

Commit

Permalink
Reviewer comments
Browse files Browse the repository at this point in the history
  • Loading branch information
gdbelvin committed Feb 1, 2017
1 parent a7ceb14 commit 5e62c16
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 8 deletions.
92 changes: 88 additions & 4 deletions client/client.go
Expand Up @@ -18,15 +18,19 @@ package client
import (
"context"
"crypto/sha256"
"time"

"github.com/google/trillian"
"github.com/jpillora/Backoff"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)

// LogClient represents a client for a given Trillian log instance.
type LogClient struct {
LogID int64
client trillian.TrillianLogClient
STR trillian.SignedLogRoot
}

// New returns a new LogClient.
Expand All @@ -37,21 +41,101 @@ func New(logID int64, cc *grpc.ClientConn) *LogClient {
}
}

// AddLeaf adds leaf to the append only log. It blocks until a verifiable response is received.
// AddLeaf adds leaf to the append only log.
// Blocks until it gets a verifiable response.
func (c *LogClient) AddLeaf(ctx context.Context, data []byte) error {
if err := c.UpdateSTR(ctx); err != nil {
return err
}

leaf := buildLeaf(data)
if err := c.queueLeaf(ctx, leaf); err != nil {
return err
}

// Wait for TreeSize to update.
if err := c.waitForSTRUpdate(ctx, 2); err != nil {
return err
}

// Get proof by hash.
if err := c.getInclusionProof(ctx, leaf.MerkleLeafHash, c.STR.TreeSize); err != nil {
return err
// How do we know that just because the tree updated, that it included our leaf?
// TODO(gbelvin): retry with backoff and jitter
}
return nil
}

// waitForSTRUpdate repeatedly fetches the STR until the TreeSize changes
// or until a maximum number of attempts has been tried.
func (c *LogClient) waitForSTRUpdate(ctx context.Context, attempts int) error {
b := &backoff.Backoff{
Min: 100 * time.Millisecond,
Max: 10 * time.Second,
Factor: 2,
Jitter: true,
}
startTreeSize := c.STR.TreeSize
for i := 0; ; i++ {
if err := c.UpdateSTR(ctx); err != nil {
return err
}
if c.STR.TreeSize > startTreeSize {
return nil
}
if i >= (attempts - 1) {
return grpc.Errorf(codes.DeadlineExceeded,
"No STR update in %v tries", i)
}
time.Sleep(b.Duration())
}
}

// UpdateSTR retrieves the current SignedLogRoot and verifies it.
func (c *LogClient) UpdateSTR(ctx context.Context) error {
req := &trillian.GetLatestSignedLogRootRequest{
LogId: c.LogID,
}
resp, err := c.client.GetLatestSignedLogRoot(ctx, req)
if err != nil {
return err
}
// TODO(gdbelvin): Verify SignedLogRoot

c.STR = *resp.SignedLogRoot
return nil
}

func (c *LogClient) getInclusionProof(ctx context.Context, hash []byte, treeSize int64) error {
req := &trillian.GetInclusionProofByHashRequest{
LogId: c.LogID,
LeafHash: hash,
TreeSize: treeSize,
}
if _, err := c.client.GetInclusionProofByHash(ctx, req); err != nil {
return err
}
// TODO(gbelvin): verify inclusion proof
return nil
}

func buildLeaf(data []byte) *trillian.LogLeaf {
hash := sha256.Sum256(data)
leaf := &trillian.LogLeaf{
LeafValue: data,
MerkleLeafHash: hash[:],
LeafIdentityHash: hash[:],
}
return leaf
}

func (c *LogClient) queueLeaf(ctx context.Context, leaf *trillian.LogLeaf) error {
// Queue Leaf
req := trillian.QueueLeafRequest{
LogId: c.LogID,
Leaf: leaf,
}
_, err := c.client.QueueLeaf(ctx, &req)
// TODO(gdbelvin): Get proof by hash
// TODO(gdbelvin): backoff with jitter
// TODO(gdbelvin): verify proof
return err
}
29 changes: 25 additions & 4 deletions client/client_test.go
Expand Up @@ -22,9 +22,10 @@ import (
"github.com/google/trillian/testonly/integration"
)

const logID = int64(1234)

func TestAddLeaf(t *testing.T) {
ctx := context.Background()
logID := int64(1234)
env, err := integration.NewLogEnv("client")
if err != nil {
t.Fatal(err)
Expand All @@ -33,18 +34,38 @@ func TestAddLeaf(t *testing.T) {
if err := mysql.CreateTree(logID, env.DB); err != nil {
t.Errorf("Failed to create log: %v", err)
}

client := New(logID, env.ClientConn)

if err := client.AddLeaf(ctx, []byte("foo")); err != nil {
t.Errorf("Failed to add Leaf: %v", err)
}
}

func TestAddSameLeaf(t *testing.T) {
func TestUpdateSTR(t *testing.T) {
t.Skip("Need to add a leaf first")
ctx := context.Background()
logID := int64(1234)
env, err := integration.NewLogEnv("TestUpdateSTR")
if err != nil {
t.Fatal(err)
}
defer env.Close()
if err := mysql.CreateTree(logID, env.DB); err != nil {
t.Errorf("Failed to create log: %v", err)
}
client := New(logID, env.ClientConn)

before := client.STR.TreeSize
if err := client.UpdateSTR(ctx); err != nil {
t.Error(err)
}
if got, want := client.STR.TreeSize, before; got <= want {
t.Errorf("Tree size after add Leaf: %v, want > %v", got, want)
}
}

func TestAddSameLeaf(t *testing.T) {
t.Skip("Submitting two leaves currently breaks")
ctx := context.Background()
env, err := integration.NewLogEnv("client")
if err != nil {
t.Fatal(err)
Expand Down

0 comments on commit 5e62c16

Please sign in to comment.