aboutsummaryrefslogtreecommitdiff
path: root/pkg/state
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/state')
-rw-r--r--pkg/state/mocks/signer.go23
-rw-r--r--pkg/state/mocks/state_manager.go92
-rw-r--r--pkg/state/single.go165
-rw-r--r--pkg/state/single_test.go217
-rw-r--r--pkg/state/state_manager.go29
5 files changed, 0 insertions, 526 deletions
diff --git a/pkg/state/mocks/signer.go b/pkg/state/mocks/signer.go
deleted file mode 100644
index 7c699dd..0000000
--- a/pkg/state/mocks/signer.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package mocks
-
-import (
- "crypto"
- "crypto/ed25519"
- "io"
-)
-
-// TestSign implements the signer interface. It can be used to mock an Ed25519
-// signer that always return the same public key, signature, and error.
-type TestSigner struct {
- PublicKey [ed25519.PublicKeySize]byte
- Signature [ed25519.SignatureSize]byte
- Error error
-}
-
-func (ts *TestSigner) Public() crypto.PublicKey {
- return ed25519.PublicKey(ts.PublicKey[:])
-}
-
-func (ts *TestSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
- return ts.Signature[:], ts.Error
-}
diff --git a/pkg/state/mocks/state_manager.go b/pkg/state/mocks/state_manager.go
deleted file mode 100644
index 05831c6..0000000
--- a/pkg/state/mocks/state_manager.go
+++ /dev/null
@@ -1,92 +0,0 @@
-// Code generated by MockGen. DO NOT EDIT.
-// Source: git.sigsum.org/sigsum-log-go/pkg/state (interfaces: StateManager)
-
-// Package mocks is a generated GoMock package.
-package mocks
-
-import (
- context "context"
- reflect "reflect"
-
- types "git.sigsum.org/sigsum-go/pkg/types"
- gomock "github.com/golang/mock/gomock"
-)
-
-// MockStateManager is a mock of StateManager interface.
-type MockStateManager struct {
- ctrl *gomock.Controller
- recorder *MockStateManagerMockRecorder
-}
-
-// MockStateManagerMockRecorder is the mock recorder for MockStateManager.
-type MockStateManagerMockRecorder struct {
- mock *MockStateManager
-}
-
-// NewMockStateManager creates a new mock instance.
-func NewMockStateManager(ctrl *gomock.Controller) *MockStateManager {
- mock := &MockStateManager{ctrl: ctrl}
- mock.recorder = &MockStateManagerMockRecorder{mock}
- return mock
-}
-
-// EXPECT returns an object that allows the caller to indicate expected use.
-func (m *MockStateManager) EXPECT() *MockStateManagerMockRecorder {
- return m.recorder
-}
-
-// AddCosignature mocks base method.
-func (m *MockStateManager) AddCosignature(arg0 context.Context, arg1 *types.PublicKey, arg2 *types.Signature) error {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "AddCosignature", arg0, arg1, arg2)
- ret0, _ := ret[0].(error)
- return ret0
-}
-
-// AddCosignature indicates an expected call of AddCosignature.
-func (mr *MockStateManagerMockRecorder) AddCosignature(arg0, arg1, arg2 interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddCosignature", reflect.TypeOf((*MockStateManager)(nil).AddCosignature), arg0, arg1, arg2)
-}
-
-// CosignedTreeHead mocks base method.
-func (m *MockStateManager) CosignedTreeHead(arg0 context.Context) (*types.CosignedTreeHead, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "CosignedTreeHead", arg0)
- ret0, _ := ret[0].(*types.CosignedTreeHead)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// CosignedTreeHead indicates an expected call of CosignedTreeHead.
-func (mr *MockStateManagerMockRecorder) CosignedTreeHead(arg0 interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CosignedTreeHead", reflect.TypeOf((*MockStateManager)(nil).CosignedTreeHead), arg0)
-}
-
-// Run mocks base method.
-func (m *MockStateManager) Run(arg0 context.Context) {
- m.ctrl.T.Helper()
- m.ctrl.Call(m, "Run", arg0)
-}
-
-// Run indicates an expected call of Run.
-func (mr *MockStateManagerMockRecorder) Run(arg0 interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockStateManager)(nil).Run), arg0)
-}
-
-// ToCosignTreeHead mocks base method.
-func (m *MockStateManager) ToCosignTreeHead(arg0 context.Context) (*types.SignedTreeHead, error) {
- m.ctrl.T.Helper()
- ret := m.ctrl.Call(m, "ToCosignTreeHead", arg0)
- ret0, _ := ret[0].(*types.SignedTreeHead)
- ret1, _ := ret[1].(error)
- return ret0, ret1
-}
-
-// ToCosignTreeHead indicates an expected call of ToCosignTreeHead.
-func (mr *MockStateManagerMockRecorder) ToCosignTreeHead(arg0 interface{}) *gomock.Call {
- mr.mock.ctrl.T.Helper()
- return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ToCosignTreeHead", reflect.TypeOf((*MockStateManager)(nil).ToCosignTreeHead), arg0)
-}
diff --git a/pkg/state/single.go b/pkg/state/single.go
deleted file mode 100644
index 695f0e3..0000000
--- a/pkg/state/single.go
+++ /dev/null
@@ -1,165 +0,0 @@
-package state
-
-import (
- "context"
- "crypto"
- "crypto/ed25519"
- "fmt"
- "sync"
- "time"
-
- "git.sigsum.org/log-go/pkg/db"
- "git.sigsum.org/sigsum-go/pkg/log"
- "git.sigsum.org/sigsum-go/pkg/types"
-)
-
-// StateManagerSingle implements a single-instance StateManager
-type StateManagerSingle struct {
- client db.Client
- signer crypto.Signer
- namespace types.Hash
- interval time.Duration
- deadline time.Duration
-
- // Lock-protected access to pointers. A write lock is only obtained once
- // per interval when doing pointer rotation. All endpoints are readers.
- sync.RWMutex
- signedTreeHead *types.SignedTreeHead
- cosignedTreeHead *types.CosignedTreeHead
-
- // Syncronized and deduplicated witness cosignatures for signedTreeHead
- events chan *event
- cosignatures map[types.Hash]*types.Signature
-}
-
-func NewStateManagerSingle(client db.Client, signer crypto.Signer, interval, deadline time.Duration) (*StateManagerSingle, error) {
- sm := &StateManagerSingle{
- client: client,
- signer: signer,
- namespace: *types.HashFn(signer.Public().(ed25519.PublicKey)),
- interval: interval,
- deadline: deadline,
- }
- sth, err := sm.latestSTH(context.Background())
- sm.setCosignedTreeHead()
- sm.setToCosignTreeHead(sth)
- return sm, err
-}
-
-func (sm *StateManagerSingle) Run(ctx context.Context) {
- rotation := func() {
- nextSTH, err := sm.latestSTH(ctx)
- if err != nil {
- log.Warning("cannot rotate without tree head: %v", err)
- return
- }
- sm.rotate(nextSTH)
- }
- sm.events = make(chan *event, 4096)
- defer close(sm.events)
- ticker := time.NewTicker(sm.interval)
- defer ticker.Stop()
-
- rotation()
- for {
- select {
- case <-ticker.C:
- rotation()
- case ev := <-sm.events:
- sm.handleEvent(ev)
- case <-ctx.Done():
- return
- }
- }
-}
-
-func (sm *StateManagerSingle) ToCosignTreeHead(_ context.Context) (*types.SignedTreeHead, error) {
- sm.RLock()
- defer sm.RUnlock()
- return sm.signedTreeHead, nil
-}
-
-func (sm *StateManagerSingle) CosignedTreeHead(_ context.Context) (*types.CosignedTreeHead, error) {
- sm.RLock()
- defer sm.RUnlock()
- if sm.cosignedTreeHead == nil {
- return nil, fmt.Errorf("no cosignatures available")
- }
- return sm.cosignedTreeHead, nil
-}
-
-func (sm *StateManagerSingle) AddCosignature(ctx context.Context, pub *types.PublicKey, sig *types.Signature) error {
- sm.RLock()
- defer sm.RUnlock()
-
- msg := sm.signedTreeHead.TreeHead.ToBinary(&sm.namespace)
- if !ed25519.Verify(ed25519.PublicKey(pub[:]), msg, sig[:]) {
- return fmt.Errorf("invalid cosignature")
- }
- select {
- case sm.events <- &event{types.HashFn(pub[:]), sig}:
- return nil
- case <-ctx.Done():
- return fmt.Errorf("request timeout")
- }
-}
-
-func (sm *StateManagerSingle) rotate(nextSTH *types.SignedTreeHead) {
- sm.Lock()
- defer sm.Unlock()
-
- log.Debug("rotating tree heads")
- sm.handleEvents()
- sm.setCosignedTreeHead()
- sm.setToCosignTreeHead(nextSTH)
-}
-
-func (sm *StateManagerSingle) handleEvents() {
- log.Debug("handling any outstanding events")
- for i, n := 0, len(sm.events); i < n; i++ {
- sm.handleEvent(<-sm.events)
- }
-}
-
-func (sm *StateManagerSingle) handleEvent(ev *event) {
- log.Debug("handling event from witness %x", ev.keyHash[:])
- sm.cosignatures[*ev.keyHash] = ev.cosignature
-}
-
-func (sm *StateManagerSingle) setCosignedTreeHead() {
- n := len(sm.cosignatures)
- if n == 0 {
- sm.cosignedTreeHead = nil
- return
- }
-
- var cth types.CosignedTreeHead
- cth.SignedTreeHead = *sm.signedTreeHead
- cth.Cosignature = make([]types.Signature, 0, n)
- cth.KeyHash = make([]types.Hash, 0, n)
- for keyHash, cosignature := range sm.cosignatures {
- cth.KeyHash = append(cth.KeyHash, keyHash)
- cth.Cosignature = append(cth.Cosignature, *cosignature)
- }
- sm.cosignedTreeHead = &cth
-}
-
-func (sm *StateManagerSingle) setToCosignTreeHead(nextSTH *types.SignedTreeHead) {
- sm.cosignatures = make(map[types.Hash]*types.Signature)
- sm.signedTreeHead = nextSTH
-}
-
-func (sm *StateManagerSingle) latestSTH(ctx context.Context) (*types.SignedTreeHead, error) {
- ictx, cancel := context.WithTimeout(ctx, sm.deadline)
- defer cancel()
-
- th, err := sm.client.GetTreeHead(ictx)
- if err != nil {
- return nil, fmt.Errorf("failed fetching tree head: %v", err)
- }
- sth, err := th.Sign(sm.signer, &sm.namespace)
- if err != nil {
- return nil, fmt.Errorf("failed signing tree head: %v", err)
- }
- return sth, nil
-}
diff --git a/pkg/state/single_test.go b/pkg/state/single_test.go
deleted file mode 100644
index 8e89020..0000000
--- a/pkg/state/single_test.go
+++ /dev/null
@@ -1,217 +0,0 @@
-package state
-
-import (
- "context"
- "crypto"
- "crypto/ed25519"
- "crypto/rand"
- "fmt"
- "reflect"
- "testing"
- "time"
-
- db "git.sigsum.org/log-go/pkg/db/mocks"
- "git.sigsum.org/log-go/pkg/state/mocks"
- "git.sigsum.org/sigsum-go/pkg/types"
- "github.com/golang/mock/gomock"
-)
-
-func TestNewStateManagerSingle(t *testing.T) {
- signerOk := &mocks.TestSigner{types.PublicKey{}, types.Signature{}, nil}
- signerErr := &mocks.TestSigner{types.PublicKey{}, types.Signature{}, fmt.Errorf("err")}
- for _, table := range []struct {
- description string
- signer crypto.Signer
- rsp types.TreeHead
- err error
- wantErr bool
- wantSth types.SignedTreeHead
- }{
- {
- description: "invalid: backend failure",
- signer: signerOk,
- err: fmt.Errorf("something went wrong"),
- wantErr: true,
- },
- {
- description: "invalid: signer failure",
- signer: signerErr,
- rsp: types.TreeHead{},
- wantErr: true,
- },
- {
- description: "valid",
- signer: signerOk,
- rsp: types.TreeHead{},
- wantSth: types.SignedTreeHead{},
- },
- } {
- func() {
- ctrl := gomock.NewController(t)
- defer ctrl.Finish()
- client := db.NewMockClient(ctrl)
- client.EXPECT().GetTreeHead(gomock.Any()).Return(&table.rsp, table.err)
-
- sm, err := NewStateManagerSingle(client, table.signer, time.Duration(0), time.Duration(0))
- if got, want := err != nil, table.wantErr; got != want {
- t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.description, err)
- }
- if err != nil {
- return
- }
- if got, want := sm.signedTreeHead, &table.wantSth; !reflect.DeepEqual(got, want) {
- t.Errorf("got to-cosign tree head\n\t%v\nbut wanted\n\t%v\nin test %q", got, want, table.description)
- }
- if got := sm.cosignedTreeHead; got != nil {
- t.Errorf("got cosigned tree head but should have none in test %q", table.description)
- }
- }()
- }
-}
-
-func TestToCosignTreeHead(t *testing.T) {
- want := &types.SignedTreeHead{}
- sm := StateManagerSingle{
- signedTreeHead: want,
- }
- sth, err := sm.ToCosignTreeHead(context.Background())
- if err != nil {
- t.Errorf("should not fail with error: %v", err)
- return
- }
- if got := sth; !reflect.DeepEqual(got, want) {
- t.Errorf("got signed tree head\n\t%v\nbut wanted\n\t%v", got, want)
- }
-}
-
-func TestCosignedTreeHead(t *testing.T) {
- want := &types.CosignedTreeHead{
- Cosignature: make([]types.Signature, 1),
- KeyHash: make([]types.Hash, 1),
- }
- sm := StateManagerSingle{
- cosignedTreeHead: want,
- }
- cth, err := sm.CosignedTreeHead(context.Background())
- if err != nil {
- t.Errorf("should not fail with error: %v", err)
- return
- }
- if got := cth; !reflect.DeepEqual(got, want) {
- t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v", got, want)
- }
-
- sm.cosignedTreeHead = nil
- cth, err = sm.CosignedTreeHead(context.Background())
- if err == nil {
- t.Errorf("should fail without a cosigned tree head")
- return
- }
-}
-
-func TestAddCosignature(t *testing.T) {
- secret, public := mustKeyPair(t)
- for _, table := range []struct {
- desc string
- signer crypto.Signer
- vk types.PublicKey
- wantErr bool
- }{
- {
- desc: "invalid: wrong public key",
- signer: secret,
- vk: types.PublicKey{},
- wantErr: true,
- },
- {
- desc: "valid",
- signer: secret,
- vk: public,
- },
- } {
- sm := &StateManagerSingle{
- namespace: *types.HashFn(nil),
- signedTreeHead: &types.SignedTreeHead{},
- events: make(chan *event, 1),
- }
- defer close(sm.events)
-
- sth := mustSign(t, table.signer, &sm.signedTreeHead.TreeHead, &sm.namespace)
- ctx := context.Background()
- err := sm.AddCosignature(ctx, &table.vk, &sth.Signature)
- if got, want := err != nil, table.wantErr; got != want {
- t.Errorf("got error %v but wanted %v in test %q: %v", got, want, table.desc, err)
- }
- if err != nil {
- continue
- }
-
- ctx, cancel := context.WithTimeout(ctx, 50*time.Millisecond)
- defer cancel()
- if err := sm.AddCosignature(ctx, &table.vk, &sth.Signature); err == nil {
- t.Errorf("expected full channel in test %q", table.desc)
- }
- if got, want := len(sm.events), 1; got != want {
- t.Errorf("wanted %d cosignatures but got %d in test %q", want, got, table.desc)
- }
- }
-}
-
-func TestRotate(t *testing.T) {
- sth := &types.SignedTreeHead{}
- nextSTH := &types.SignedTreeHead{TreeHead: types.TreeHead{Timestamp: 1}}
- ev := &event{
- keyHash: &types.Hash{},
- cosignature: &types.Signature{},
- }
- wantCTH := &types.CosignedTreeHead{
- SignedTreeHead: *sth,
- KeyHash: []types.Hash{*ev.keyHash},
- Cosignature: []types.Signature{*ev.cosignature},
- }
- sm := &StateManagerSingle{
- signedTreeHead: sth,
- cosignatures: make(map[types.Hash]*types.Signature),
- events: make(chan *event, 1),
- }
- defer close(sm.events)
-
- sm.events <- ev
- sm.rotate(nextSTH)
- if got, want := sm.signedTreeHead, nextSTH; !reflect.DeepEqual(got, want) {
- t.Errorf("got to-cosign tree head\n\t%v\nbut wanted\n\t%v", got, want)
- }
- if got, want := sm.cosignedTreeHead, wantCTH; !reflect.DeepEqual(got, want) {
- t.Errorf("got cosigned tree head\n\t%v\nbut wanted\n\t%v", got, want)
- }
-
- sth = nextSTH
- nextSTH = &types.SignedTreeHead{TreeHead: types.TreeHead{Timestamp: 2}}
- sm.rotate(nextSTH)
- if got, want := sm.signedTreeHead, nextSTH; !reflect.DeepEqual(got, want) {
- t.Errorf("got to-cosign tree head\n\t%v\nbut wanted\n\t%v", got, want)
- }
- if got := sm.cosignedTreeHead; got != nil {
- t.Errorf("expected no cosignatures to be available")
- }
-}
-
-func mustKeyPair(t *testing.T) (crypto.Signer, types.PublicKey) {
- t.Helper()
- vk, sk, err := ed25519.GenerateKey(rand.Reader)
- if err != nil {
- t.Fatal(err)
- }
- var pub types.PublicKey
- copy(pub[:], vk[:])
- return sk, pub
-}
-
-func mustSign(t *testing.T, s crypto.Signer, th *types.TreeHead, kh *types.Hash) *types.SignedTreeHead {
- t.Helper()
- sth, err := th.Sign(s, kh)
- if err != nil {
- t.Fatal(err)
- }
- return sth
-}
diff --git a/pkg/state/state_manager.go b/pkg/state/state_manager.go
deleted file mode 100644
index 9533479..0000000
--- a/pkg/state/state_manager.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package state
-
-import (
- "context"
-
- "git.sigsum.org/sigsum-go/pkg/types"
-)
-
-// StateManager coordinates access to a log's tree heads and (co)signatures.
-type StateManager interface {
- // ToCosignTreeHead returns the log's to-cosign tree head
- ToCosignTreeHead(context.Context) (*types.SignedTreeHead, error)
-
- // CosignedTreeHead returns the log's cosigned tree head
- CosignedTreeHead(context.Context) (*types.CosignedTreeHead, error)
-
- // AddCosignature verifies that a cosignature is valid for the to-cosign
- // tree head before adding it
- AddCosignature(context.Context, *types.PublicKey, *types.Signature) error
-
- // Run peridically rotates the log's to-cosign and cosigned tree heads
- Run(context.Context)
-}
-
-// event is a verified cosignature request
-type event struct {
- keyHash *types.Hash
- cosignature *types.Signature
-}