Skip to content

Commit 1fff1c3

Browse files
committed
Fixed a few bugs, optimized calculating the averages, added a unit test
1 parent ba4f9cd commit 1fff1c3

3 files changed

Lines changed: 127 additions & 27 deletions

File tree

node/average.go

Lines changed: 66 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,42 +23,71 @@ func (d *Pegnetd) GetPegNetRateAverages(ctx context.Context, height uint32) (Avg
2323
return d.LastAverages
2424
}
2525

26-
ratesOverPeriod := map[fat2.PTicker][]uint64{} // First collect all the values over the blocks
27-
averages := map[fat2.PTicker]uint64{} // in the average period, then compute the averages
26+
ratesOverPeriod := d.LastAveragesData // First collect all the values over the blocks
27+
averages := map[fat2.PTicker]uint64{} // in the average period, then compute the averages
28+
if ratesOverPeriod == nil { // If no map exists yet
29+
ratesOverPeriod = map[fat2.PTicker][]uint64{} // create one.
30+
}
2831

2932
defer func() { // Always set up the cache when exiting the routine
30-
d.LastAveragesHeight = height
31-
d.LastAverages = averages
33+
d.LastAveragesData = ratesOverPeriod // Save the data we used to create averages
34+
d.LastAveragesHeight = height // Save the height of this data
35+
d.LastAverages = averages // Save the averages we computed
3236
}()
3337

34-
startHeight := height - (uint32(AveragePeriod)) + 1 // The startHeight is AveragePeriod before height+1
35-
// (add 1 so the block at height is included)
36-
if startHeight < 1 { // If AveragePeriod blocks don't exist, then ignore
37-
return averages
38-
}
39-
40-
for h := startHeight; height <= height; h++ { // Collect rates over the blocks (including height)
41-
var rates map[fat2.PTicker]uint64 // Collect all the rates
42-
var err error
43-
44-
if rates, err = d.Pegnet.SelectRates(ctx, h); err != nil { // Pull the rates out of the database at each
45-
return averages // height. Return averages if an error
38+
// collectRatesAtHeight
39+
// This routine collects all the data used to compute an average. If any data is missing, then
40+
// that data is represented by a zero.
41+
collectRatesAtHeight := func(h uint32) {
42+
for k := range ratesOverPeriod { // Make sure there is room for a new height
43+
for len(ratesOverPeriod[k]) >= int(AveragePeriod) { // If at the limit or above,
44+
copy(ratesOverPeriod[k], ratesOverPeriod[k][1:]) // Shift data down 1 element
45+
ratesOverPeriod[k] = ratesOverPeriod[k][:len(ratesOverPeriod[k])-1] // And drop off the last value
46+
}
4647
}
4748

48-
for k, v := range rates { // For all the rates
49-
if ratesOverPeriod[k] == nil { // if no rates yet, at a slice for them
50-
ratesOverPeriod[k] = []uint64{} // Allocate the slice
51-
}
52-
if v != 0 { // Only collect non-zero rates
49+
if rates, err := d.Pegnet.SelectRates(ctx, h); err != nil { // Pull the rates out of the database at each
50+
panic("no recovery from a database error getting rates")
51+
} else {
52+
for k, v := range rates { // For all the rates
53+
if ratesOverPeriod[k] == nil { // if no rates yet, at a slice for them
54+
ratesOverPeriod[k] = []uint64{} // Allocate the slice
55+
}
5356
ratesOverPeriod[k] = append(ratesOverPeriod[k], v) // Add the rates we find
5457
}
5558
}
5659
}
5760

58-
for k, v := range ratesOverPeriod { // When we average the rates, we return a zero for
59-
averages[k] = 0 // any asset that doesn't have a rate in all blocks
60-
if uint64(len(v)) < AverageRequired { // We can see missing rates because the list isn't
61-
continue // long enough. Too many missing rates, and we skip it
61+
switch {
62+
// If the LastAveragesHeight is out of range given our current height, we just need to load
63+
// all the values
64+
case d.LastAveragesHeight+1 < height || d.LastAveragesHeight > height:
65+
for k, v := range ratesOverPeriod {
66+
if v != nil {
67+
ratesOverPeriod[k] = ratesOverPeriod[k][:0]
68+
}
69+
}
70+
startHeightS := int64(height) - (int64(AveragePeriod)) + 1 // startHeight is AveragePeriod before height+1
71+
// (add 1 so the block at height is included)
72+
if startHeightS < 1 { // If AveragePeriod blocks don't exist,
73+
startHeightS = 1 // then flour the start to 1
74+
}
75+
76+
startHeight := uint32(startHeightS)
77+
78+
for h := startHeight; h <= height; h++ { // Collect rates over the blocks (including height)
79+
collectRatesAtHeight(h) // and add them to ratesOverPeriod
80+
}
81+
82+
// If all we need is the next height, then only collect that height.
83+
case d.LastAveragesHeight+1 == height:
84+
collectRatesAtHeight(height) // Add the current height to the dataset so far
85+
}
86+
87+
for k, v := range ratesOverPeriod { // The average rate is zero for any asset without
88+
averages[k] = 0 // the number of required rates
89+
if AveragePeriod-numberMissing(v) < AverageRequired { // Count the missing values, and if not enough
90+
continue // skip it
6291
}
6392
for _, v2 := range v { // Sum up all the rates found for an asset
6493
averages[k] += v2 // The assumption is that rates are no where near
@@ -68,3 +97,15 @@ func (d *Pegnetd) GetPegNetRateAverages(ctx context.Context, height uint32) (Avg
6897

6998
return averages // Return the rates we found.
7099
}
100+
101+
func numberMissing(dataset []uint64) (numZeros uint64) {
102+
for _, v := range dataset {
103+
if v == 0 {
104+
numZeros++
105+
}
106+
}
107+
if len(dataset) < int(AveragePeriod) {
108+
numZeros += AveragePeriod - uint64(len(dataset))
109+
}
110+
return numZeros
111+
}

node/average_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package node
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"testing"
8+
9+
"github.com/pegnet/pegnetd/config"
10+
11+
"github.com/pegnet/pegnetd/fat/fat2"
12+
13+
"github.com/pegnet/pegnetd/exit"
14+
"github.com/spf13/viper"
15+
)
16+
17+
func TestAveragePeriod(t *testing.T) {
18+
ctx, cancel := context.WithCancel(context.Background())
19+
exit.GlobalExitHandler.AddCancel(cancel)
20+
_ = ctx
21+
22+
fctDat, _ := os.Create("FCT.tsv")
23+
defer fctDat.Close()
24+
25+
// Get the config
26+
conf := viper.GetViper()
27+
conf.Set(config.Network, "MainNet")
28+
conf.SetConfigName("pegnetd-conf")
29+
conf.Set(config.SqliteDBPath, "$HOME/.pegnetd/mainnet/sql.db")
30+
configpath := os.ExpandEnv("$HOME/.pegnetd/pegnetd-conf.toml")
31+
conf.SetConfigFile(os.ExpandEnv(configpath))
32+
33+
n, err := NewPegnetd(ctx, conf)
34+
if err != nil {
35+
t.Fatal(err)
36+
}
37+
// Min 206422 293471
38+
for i := uint32(208500); i < 210500; i++ {
39+
40+
averages := n.GetPegNetRateAverages(ctx, i).(map[fat2.PTicker]uint64)
41+
for pAsset, v := range averages {
42+
fmt.Sprintf("%6s %15d", pAsset, v)
43+
}
44+
// Get the rate for FCT at the current height
45+
price := n.LastAveragesData[fat2.PTickerFCT][len(n.LastAveragesData[fat2.PTickerFCT])-1]
46+
avgPrice := averages[fat2.PTickerFCT]
47+
fctDat.WriteString(fmt.Sprintf("%f\t%f\n", float64(price)/100000000, float64(avgPrice)/100000000))
48+
_ = averages
49+
50+
if i%10000 == 0 {
51+
fmt.Printf("%6d ", i)
52+
println()
53+
if i == 206709 {
54+
println()
55+
}
56+
}
57+
}
58+
}

node/node.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ type Pegnetd struct {
2222
Sync *pegnet.BlockSync
2323
Pegnet *pegnet.Pegnet
2424

25-
LastAverages map[fat2.PTicker]uint64 // Cache for averages when requested for the same height
26-
LastAveragesHeight uint32 // Height of the current cache
25+
LastAveragesData map[fat2.PTicker][]uint64 // The last set of data used to create averages
26+
LastAverages map[fat2.PTicker]uint64 // Cache for averages when requested for the same height
27+
LastAveragesHeight uint32 // Height of the current cache
2728
}
2829

2930
func NewPegnetd(ctx context.Context, conf *viper.Viper) (*Pegnetd, error) {

0 commit comments

Comments
 (0)