Go implementation

peoplecoin

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

coin/coin.go

98 lines

1// Package coin computes the value of a coin entry over time: demurrage,
2// rent, the spendable amount and prunability.
3//
4// The nominal amount stored in an entry never changes. Its effective value
5// after t slots is amount × 0.983^(t/SlotsPerPeriod), computed by
6// exponentiation by squaring in 63-bit fixed point:
7//
8// Decay[0] = floor(2^63 × 0.983^(1/SlotsPerPeriod))
9// Decay[i+1] = (Decay[i] × Decay[i]) >> 63 i = 0 .. 25
10//
11// decay(amount, t):
12// if t ≥ 2^27: return 0
13// x = amount
14// for i in 0 .. 26: if bit i of t is set: x = (x × Decay[i]) >> 63
15// return x
16//
17// Decay[i] is what a coin retains after 2^i slots. The squaring chain, not
18// the real-valued curve, is the definition: every node computes the same
19// integer. Beyond 2^27 slots the value is zero — the largest uint64 has
20// decayed below one base unit long before that.
21//
22// Rent is one base unit per 2^RentShift slots, computed from the same t:
23//
24// rent(t) = t >> RentShift
25// spendable = max(effective − rent, 0)
26// prunable = rent ≥ effective
27package coin
28
29import (
30 "math/bits"
31
32 "peoplecoin/params"
33)
34
35// DecayLen is the number of entries in the decay table; t ≥ 2^DecayLen
36// decays to zero.
37const DecayLen = 27
38
39// Decay[i] is the fixed-point factor a coin retains after 2^i slots.
40var Decay [DecayLen]uint64
41
42func init() {
43 Decay[0] = params.Decay0
44 for i := 0; i < DecayLen-1; i++ {
45 Decay[i+1] = mulShift(Decay[i], Decay[i])
46 }
47}
48
49// mulShift returns (a × b) >> DecayShift using a 128-bit intermediate.
50// With b < 2^63 the result always fits in 64 bits.
51func mulShift(a, b uint64) uint64 {
52 hi, lo := bits.Mul64(a, b)
53 return hi<<(64-params.DecayShift) | lo>>params.DecayShift
54}
55
56// Effective returns the value of amount after t slots of demurrage.
57func Effective(amount, t uint64) uint64 {
58 if t >= 1<<DecayLen {
59 return 0
60 }
61 x := amount
62 for i := 0; i < DecayLen && x != 0; i++ {
63 if t&(1<<uint(i)) != 0 {
64 x = mulShift(x, Decay[i])
65 }
66 }
67 return x
68}
69
70// Rent returns the rent accrued over t slots.
71func Rent(t uint64) uint64 {
72 return t >> params.RentShift
73}
74
75// Spendable returns the effective value minus rent, saturating at zero.
76func Spendable(amount, t uint64) uint64 {
77 e := Effective(amount, t)
78 r := Rent(t)
79 if r >= e {
80 return 0
81 }
82 return e - r
83}
84
85// Prunable reports whether rent has consumed the entry's effective value.
86func Prunable(amount, t uint64) bool {
87 return Rent(t) >= Effective(amount, t)
88}
89
90// Elapsed returns t = current − created, the slots an entry has existed;
91// zero if current is earlier (which a valid chain never produces).
92func Elapsed(created uint32, current uint64) uint64 {
93 if current < uint64(created) {
94 return 0
95 }
96 return current - uint64(created)
97}