Skip to content

Commit fdb5d74

Browse files
committed
PIP 14
- Grade Staking Price Records - IsIncludedTopPEGAddress (top 100) - Getting Winner from OPR & SPR
1 parent 6b5860c commit fdb5d74

4 files changed

Lines changed: 230 additions & 14 deletions

File tree

node/pegnet/addresses.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package pegnet
22

33
import (
4+
"bytes"
45
"database/sql"
56
"fmt"
67
"strings"
@@ -438,6 +439,29 @@ type BalancesPair struct {
438439
Balances []uint64
439440
}
440441

442+
func (p *Pegnet) IsIncludedTopPEGAddress(address []byte) bool {
443+
count := 100 // Top 100 addresses
444+
stmtStringFmt := `SELECT address, %[1]s_balance FROM pn_addresses WHERE %[1]s_balance > 0 ORDER BY %[1]s_balance DESC LIMIT ?;`
445+
stmt := fmt.Sprintf(stmtStringFmt, strings.ToLower("PEG"))
446+
rows, err := p.DB.Query(stmt, count)
447+
if err != nil {
448+
return false
449+
}
450+
defer rows.Close()
451+
452+
for rows.Next() {
453+
var Balance uint64
454+
var adr []byte
455+
if err := rows.Scan(&adr, &Balance); err != nil {
456+
return false
457+
}
458+
if bytes.Equal(address, adr) {
459+
return true
460+
}
461+
}
462+
return false
463+
}
464+
441465
// SelectRichList returns the balance of all addresses for a given ticker
442466
func (p *Pegnet) SelectRichList(ticker fat2.PTicker, count int) ([]BalancePair, error) {
443467
if ticker <= fat2.PTickerInvalid || fat2.PTickerMax <= ticker {

node/pegnet/txhistory.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/Factom-Asset-Tokens/factom"
1212
"github.com/pegnet/pegnet/modules/grader"
13+
"github.com/pegnet/pegnet/modules/graderStake"
1314
"github.com/pegnet/pegnetd/fat/fat2"
1415
log "github.com/sirupsen/logrus"
1516
)
@@ -386,6 +387,45 @@ func (p *Pegnet) InsertCoinbase(tx *sql.Tx, winner *grader.GradingOPR, addr []by
386387
return nil
387388
}
388389

390+
// InsertCoinbase inserts the payouts from staking into the history system.
391+
// There is one transaction per winning SPR, with the entry hash pointing to that specific spr
392+
func (p *Pegnet) InsertStaking100Coinbase(tx *sql.Tx, winner *graderStake.GradingSPR, addr []byte, timestamp time.Time) error {
393+
stmt, err := tx.Prepare(`INSERT INTO "pn_history_txbatch"
394+
(entry_hash, height, blockorder, timestamp, executed) VALUES
395+
(?, ?, ?, ?, ?)`)
396+
if err != nil {
397+
return err
398+
}
399+
400+
lookup, err := tx.Prepare(insertLookupQuery)
401+
if err != nil {
402+
return err
403+
}
404+
405+
_, err = stmt.Exec(winner.EntryHash, winner.SPR.GetHeight(), 0, timestamp.Unix(), winner.SPR.GetHeight())
406+
if err != nil {
407+
return err
408+
}
409+
410+
coinbaseStatement, err := tx.Prepare(`INSERT INTO "pn_history_transaction"
411+
(entry_hash, tx_index, action_type, from_address, from_asset, from_amount, to_asset, to_amount, outputs) VALUES
412+
(?, ?, ?, ?, ?, ?, ?, ?, ?)`)
413+
if err != nil {
414+
return err
415+
}
416+
417+
_, err = coinbaseStatement.Exec(winner.EntryHash, 0, Coinbase, addr, "", 0, "PEG", winner.Payout(), "")
418+
if err != nil {
419+
return err
420+
}
421+
422+
if _, err = lookup.Exec(winner.EntryHash, 0, addr); err != nil {
423+
return err
424+
}
425+
426+
return nil
427+
}
428+
389429
// InsertStakingCoinbase inserts the payouts from mining into the history system.
390430
// There is one transaction per winning OPR, with the entry hash pointing to that specific opr
391431
func (p *Pegnet) InsertStakingCoinbase(tx *sql.Tx, txid string, height uint32, heightTimestamp time.Time, payouts map[string]uint64, addressMap map[string]factom.FAAddress) error {

node/spr.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package node
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/Factom-Asset-Tokens/factom"
7+
"github.com/pegnet/pegnet/modules/graderStake"
8+
)
9+
10+
// Grade Staking Price Records
11+
func (d *Pegnetd) GradeS(ctx context.Context, block *factom.EBlock) (graderStake.GradedBlock, error) {
12+
if block == nil {
13+
// TODO: Handle the case where there is no opr block.
14+
// Must delay conversions if this- happens
15+
return nil, nil
16+
}
17+
18+
if *block.ChainID != SPRChain {
19+
return nil, fmt.Errorf("trying to grade a non-spr chain")
20+
}
21+
22+
ver := uint8(5)
23+
if block.Height >= V20HeightActivation {
24+
ver = 5
25+
}
26+
27+
g, err := graderStake.NewGrader(ver, int32(block.Height))
28+
if err != nil {
29+
return nil, err
30+
}
31+
for _, entry := range block.Entries {
32+
extids := make([][]byte, len(entry.ExtIDs))
33+
for i := range entry.ExtIDs {
34+
extids[i] = entry.ExtIDs[i]
35+
}
36+
// allow only top 100 stake holders submit prices
37+
stakerRCD := extids[1]
38+
if !d.Pegnet.IsIncludedTopPEGAddress(stakerRCD) {
39+
return nil, fmt.Errorf("trying to submit without enough peg. only top 100 peg holders can submit prices")
40+
}
41+
// ignore bad opr errors
42+
err = g.AddSPR(entry.Hash[:], extids, entry.Content)
43+
if err != nil {
44+
// This is a noisy debug print
45+
//logrus.WithError(err).WithFields(logrus.Fields{"hash": entry.Hash.String()}).Debug("failed to add spr")
46+
}
47+
}
48+
49+
return g.Grade(), nil
50+
}

node/sync.go

Lines changed: 116 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"github.com/Factom-Asset-Tokens/factom"
1212
"github.com/pegnet/pegnet/modules/conversions"
1313
"github.com/pegnet/pegnet/modules/grader"
14+
"github.com/pegnet/pegnet/modules/graderStake"
15+
"github.com/pegnet/pegnet/modules/opr"
1416
"github.com/pegnet/pegnet/modules/transactionid"
1517
"github.com/pegnet/pegnetd/config"
1618
"github.com/pegnet/pegnetd/fat/fat2"
@@ -257,11 +259,14 @@ func (d *Pegnetd) SyncBlock(ctx context.Context, tx *sql.Tx, height uint32) erro
257259
// Then, grade the new OPR Block. The results of this will be used
258260
// to execute conversions that are in holding.
259261
gradedBlock, err := d.Grade(ctx, oprEBlock)
260-
gradedSPRBlock, err_s := d.Grade(ctx, sprEBlock)
261-
if err != nil {
262-
return err
263-
} else if gradedBlock != nil {
264-
if height < V20HeightActivation {
262+
gradedSPRBlock, err_s := d.GradeS(ctx, sprEBlock)
263+
isRatesAvailable := false
264+
if height < V20HeightActivation {
265+
if err != nil {
266+
return err
267+
}
268+
isRatesAvailable = gradedBlock != nil && 0 < len(gradedBlock.Winners())
269+
if gradedBlock != nil {
265270
err = d.Pegnet.InsertGradeBlock(tx, oprEBlock, gradedBlock)
266271
if err != nil {
267272
return err
@@ -291,15 +296,47 @@ func (d *Pegnetd) SyncBlock(ctx context.Context, tx *sql.Tx, height uint32) erro
291296
fLog.WithFields(log.Fields{"section": "grading", "reason": "no winners"}).Tracef("block not graded")
292297
}
293298
} else {
294-
if err_s != nil {
295-
return err_s
296-
}
297-
// Todo:
298-
// 1. Determine the rate from 3 OPRs (OPR, SPR, DPR)
299-
// 2. Insert rate to DB
299+
fLog.WithFields(log.Fields{"section": "grading", "reason": "no graded block"}).Tracef("block not graded")
300300
}
301301
} else {
302-
fLog.WithFields(log.Fields{"section": "grading", "reason": "no graded block"}).Tracef("block not graded")
302+
if err != nil {
303+
return err
304+
}
305+
if err_s != nil {
306+
return err_s
307+
}
308+
// 1. Determine the rates from 2 OPRs (OPR, SPR)
309+
// 2. Insert rates to DB
310+
var oprWinners []opr.AssetUint
311+
var sprWinners []opr.AssetUint
312+
313+
if gradedBlock != nil {
314+
winnersOpr := gradedBlock.Winners()
315+
if 0 < len(winnersOpr) {
316+
oprWinners = winnersOpr[0].OPR.GetOrderedAssetsUint()
317+
}
318+
}
319+
if gradedSPRBlock != nil {
320+
winnersSpr := gradedSPRBlock.Winners()
321+
if 0 < len(winnersSpr) {
322+
sprWinners = winnersSpr[0].SPR.GetOrderedAssetsUint()
323+
}
324+
}
325+
if 0 < len(oprWinners) || 0 < len(sprWinners) {
326+
filteredRates, errRate := d.GetAssetRates(oprWinners, sprWinners)
327+
if errRate != nil {
328+
return err
329+
}
330+
isRatesAvailable = true
331+
var phase pegnet.PEGPricingPhase
332+
phase = pegnet.PEGPriceIsFloating
333+
err = d.Pegnet.InsertRates(tx, height, filteredRates, phase)
334+
if err != nil {
335+
return err
336+
}
337+
} else {
338+
fLog.WithFields(log.Fields{"section": "grading", "reason": "no winners from OPR & SPR"}).Tracef("block not graded")
339+
}
303340
}
304341

305342
// Only apply transactions if we crossed the activation
@@ -340,7 +377,7 @@ func (d *Pegnetd) SyncBlock(ctx context.Context, tx *sql.Tx, height uint32) erro
340377
// At this point, we start making updates to the database in a specific order:
341378
// TODO: ensure we rollback the tx when needed
342379
// 1) Apply transaction batches that are in holding (conversions are always applied here)
343-
if gradedBlock != nil && 0 < len(gradedBlock.Winners()) {
380+
if isRatesAvailable {
344381
// Before conversions can be run, we have to adjust and discover the bank's value.
345382
// We also only sync the bank if the block is a pegnet block
346383
if err := d.SyncBank(ctx, tx, height); err != nil {
@@ -382,7 +419,7 @@ func (d *Pegnetd) SyncBlock(ctx context.Context, tx *sql.Tx, height uint32) erro
382419
// 5) Apply effects of graded SPR Block (PEG rewards, if any)
383420
// These funds will be available for transactions and conversions executed in the next block
384421
if gradedSPRBlock != nil {
385-
if err := d.ApplyGradedOPRBlock(tx, gradedSPRBlock, dblock.Timestamp); err != nil {
422+
if err := d.ApplyGradedSPRBlock(tx, gradedSPRBlock, dblock.Timestamp); err != nil {
386423
return err
387424
}
388425
}
@@ -1114,6 +1151,34 @@ func (d *Pegnetd) ApplyGradedOPRBlock(tx *sql.Tx, gradedBlock grader.GradedBlock
11141151
return nil
11151152
}
11161153

1154+
// ApplyGradedOPRBlock pays out PEG to the winners of the given GradedBlock.
1155+
// If an error is returned, the sql.Tx should be rolled back by the caller.
1156+
func (d *Pegnetd) ApplyGradedSPRBlock(tx *sql.Tx, gradedSPRBlock graderStake.GradedBlock, timestamp time.Time) error {
1157+
winners := gradedSPRBlock.Winners()
1158+
for i := range winners {
1159+
addr, err := factom.NewFAAddress(winners[i].SPR.GetAddress())
1160+
if err != nil {
1161+
// TODO: This is kinda an odd case. I think we should just drop the rewards
1162+
// for an invalid address. We can always add back the rewards and they will have
1163+
// a higher balance after a change.
1164+
log.WithError(err).WithFields(log.Fields{
1165+
"height": winners[i].SPR.GetHeight(),
1166+
"ehash": fmt.Sprintf("%x", winners[i].EntryHash),
1167+
}).Warnf("failed to reward")
1168+
continue
1169+
}
1170+
1171+
if _, err := d.Pegnet.AddToBalance(tx, &addr, fat2.PTickerPEG, uint64(winners[i].Payout())); err != nil {
1172+
return err
1173+
}
1174+
1175+
if err := d.Pegnet.InsertStaking100Coinbase(tx, winners[i], addr[:], timestamp); err != nil {
1176+
return err
1177+
}
1178+
}
1179+
return nil
1180+
}
1181+
11171182
func isDone(ctx context.Context) bool {
11181183
select {
11191184
case <-ctx.Done():
@@ -1122,3 +1187,40 @@ func isDone(ctx context.Context) bool {
11221187
return false
11231188
}
11241189
}
1190+
1191+
func (d *Pegnetd) GetAssetRates(oprWinners []opr.AssetUint, sprWinners []opr.AssetUint) ([]opr.AssetUint, error) {
1192+
if 0 < len(oprWinners) && 0 == len(sprWinners) {
1193+
return oprWinners, nil
1194+
}
1195+
if 0 == len(oprWinners) && 0 < len(sprWinners) {
1196+
return sprWinners, nil
1197+
}
1198+
if 0 < len(oprWinners) && 0 < len(sprWinners) {
1199+
// 1) sprWinners determine tolerance range
1200+
// 2) oprWinners set the real rates
1201+
if len(oprWinners) != len(sprWinners) {
1202+
return nil, fmt.Errorf("SPR & OPR use different assets version")
1203+
}
1204+
1205+
var filteredRates []opr.AssetUint
1206+
for i := range oprWinners {
1207+
if oprWinners[i].Name == sprWinners[i].Name {
1208+
sprRate := sprWinners[i].Value
1209+
oprRate := oprWinners[i].Value
1210+
toleranceRate := 0.01 // 1% band
1211+
if sprRate >= 100000 {
1212+
toleranceRate = 0.001 // 0.1% band
1213+
}
1214+
toleranceBandHigh := float64(sprRate) * (1 + toleranceRate)
1215+
toleranceBandLow := float64(sprRate) * (1 - toleranceRate)
1216+
if (float64(oprRate) >= toleranceBandLow) && (float64(oprRate) <= toleranceBandHigh) {
1217+
filteredRates = append(filteredRates, oprWinners[i])
1218+
} else {
1219+
filteredRates = append(filteredRates, sprWinners[i])
1220+
}
1221+
}
1222+
}
1223+
return filteredRates, nil
1224+
}
1225+
return nil, fmt.Errorf("no winners")
1226+
}

0 commit comments

Comments
 (0)