Go implementation

peoplecoin

Standard library only. Primitives, the state machine, and the nine operations — no networking, no video, no wallet yet.

period/period_test.go

92 lines

1package period
2
3import (
4 "crypto/sha256"
5 "encoding/binary"
6 "math/big"
7 "testing"
8 "time"
9
10 "peoplecoin/params"
11)
12
13func TestPeriodAndPhase(t *testing.T) {
14 sp := params.SlotsPerPeriod
15 cases := []struct{ slot, period, phase uint64 }{
16 {0, 0, 0}, {10079, 0, 0}, {10080, 0, 1}, {20159, 0, 1}, {20160, 0, 2},
17 {30239, 0, 2}, {30240, 0, 3}, {sp - 1, 0, 3}, {sp, 1, 0}, {7*sp + 25000, 7, 2},
18 }
19 for _, c := range cases {
20 if Of(c.slot) != c.period || Phase(c.slot) != c.phase {
21 t.Errorf("slot %d: period %d phase %d, want %d %d", c.slot, Of(c.slot), Phase(c.slot), c.period, c.phase)
22 }
23 }
24 if Start(3) != 3*sp || End(3) != 4*sp || !IsBoundary(4*sp) || IsBoundary(4*sp+1) {
25 t.Fatal("start/end/boundary")
26 }
27 if Number(5*sp+9) != 5 {
28 t.Fatal("number")
29 }
30 if params.SlotsPerPhase*params.NumPhases != sp {
31 t.Fatal("phases do not tile the period")
32 }
33}
34
35func TestEventOffset(t *testing.T) {
36 seen := map[uint64]int{}
37 for p := uint32(0); p < 500; p++ {
38 o := EventOffset(p)
39 if o >= 24 {
40 t.Fatalf("offset %d out of range", o)
41 }
42 if o != EventOffset(p) {
43 t.Fatal("not deterministic")
44 }
45 seen[o]++
46 }
47 // All 24 hours occur over 500 periods, each roughly 500/24 times.
48 if len(seen) != 24 {
49 t.Fatalf("only %d of 24 hours occur", len(seen))
50 }
51 for hr, n := range seen {
52 if n < 8 || n > 40 {
53 t.Fatalf("hour %d occurs %d times of ~21", hr, n)
54 }
55 }
56 // The definition: the digest as a big-endian integer, mod 24.
57 var b [4]byte
58 binary.BigEndian.PutUint32(b[:], 7)
59 h := sha256.Sum256(b[:])
60 want := new(big.Int).Mod(new(big.Int).SetBytes(h[:]), big.NewInt(24)).Uint64()
61 if EventOffset(7) != want {
62 t.Fatal("definition")
63 }
64 t.Logf("EventOffset(0..3) = %d %d %d %d hours", EventOffset(0), EventOffset(1), EventOffset(2), EventOffset(3))
65}
66
67func TestEventSlot(t *testing.T) {
68 // Genesis Saturday 2027-01-02 07:00 UTC; a tip clock as state.State keeps it.
69 g := time.Date(2027, 1, 2, 7, 0, 0, 0, time.UTC)
70 timeOf := func(slot uint64) time.Time { return g.Add(time.Duration(slot) * time.Minute) }
71 for p := uint64(0); p < 200; p++ {
72 es := EventSlot(p)
73 if es < Start(p) || es-Start(p) != EventOffset(uint32(p))*60 || es-Start(p) > 23*60 {
74 t.Fatalf("period %d: event slot %d", p, es)
75 }
76 if Phase(es) != PhaseEvent || Phase(es+EventSlots) != PhaseEvent {
77 t.Fatalf("period %d: event outside phase 0", p)
78 }
79 // Every period starts Saturday 07:00; the event is on the hour,
80 // Saturday 07:00 through Sunday 06:00.
81 ps := timeOf(Start(p))
82 if ps.Weekday() != time.Saturday || ps.Hour() != 7 || ps.Minute() != 0 {
83 t.Fatalf("period %d starts %v", p, ps)
84 }
85 et := timeOf(es)
86 if et.Minute() != 0 || (et.Weekday() != time.Saturday && et.Weekday() != time.Sunday) {
87 t.Fatalf("period %d: event at %v", p, et)
88 }
89 }
90 t.Logf("period 0 event at %v", timeOf(EventSlot(0)))
91}