summaryrefslogtreecommitdiff
path: root/srv/pow/store.go
blob: 0b5e7d0e49168a30bbf37f095c12551eab1dd43e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package pow

import (
	"errors"
	"sync"
	"time"

	"github.com/tilinna/clock"
)

// ErrSeedSolved is used to indicate a seed has already been solved.
var ErrSeedSolved = errors.New("seed already solved")

// Store is used to track information related to proof-of-work challenges and
// solutions.
type Store interface {

	// MarkSolved will return ErrSeedSolved if the seed was already marked. The
	// seed will be cleared from the Store once expiresAt is reached.
	MarkSolved(seed []byte, expiresAt time.Time) error

	Close() error
}

type inMemStore struct {
	clock clock.Clock

	m          map[string]time.Time
	l          sync.Mutex
	closeCh    chan struct{}
	spinLoopCh chan struct{} // only used by tests
}

const inMemStoreGCPeriod = 5 * time.Second

// NewMemoryStore initializes and returns an in-memory Store implementation.
func NewMemoryStore(clock clock.Clock) Store {
	s := &inMemStore{
		clock:      clock,
		m:          map[string]time.Time{},
		closeCh:    make(chan struct{}),
		spinLoopCh: make(chan struct{}, 1),
	}
	go s.spin(s.clock.NewTicker(inMemStoreGCPeriod))
	return s
}

func (s *inMemStore) spin(ticker *clock.Ticker) {
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			now := s.clock.Now()

			s.l.Lock()
			for seed, expiresAt := range s.m {
				if !now.Before(expiresAt) {
					delete(s.m, seed)
				}
			}
			s.l.Unlock()

		case <-s.closeCh:
			return
		}

		select {
		case s.spinLoopCh <- struct{}{}:
		default:
		}
	}
}

func (s *inMemStore) MarkSolved(seed []byte, expiresAt time.Time) error {
	seedStr := string(seed)

	s.l.Lock()
	defer s.l.Unlock()

	if _, ok := s.m[seedStr]; ok {
		return ErrSeedSolved
	}

	s.m[seedStr] = expiresAt
	return nil
}

func (s *inMemStore) Close() error {
	close(s.closeCh)
	return nil
}