Skip to content

Commit 0f345dc

Browse files
authored
Merge pull request #140 from pegnet/feature/PIP-14
Feature/pip 14
2 parents 21e9f7e + a4777b3 commit 0f345dc

7 files changed

Lines changed: 269 additions & 25 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/AdamSLevy/jsonrpc2/v13 v13.0.1
77
github.com/Factom-Asset-Tokens/factom v0.0.0-20191114224337-71de98ff5b3e
88
github.com/mattn/go-sqlite3 v1.11.0
9-
github.com/pegnet/pegnet v0.5.1-0.20200723222201-c2cae6127a81
9+
github.com/pegnet/pegnet v0.5.1-0.20200817185314-09e233fbd64d // this is staging branch of pegnet
1010
github.com/rs/cors v1.7.0
1111
github.com/sirupsen/logrus v1.4.2
1212
github.com/spf13/cobra v0.0.5

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ github.com/pegnet/pegnet v0.4.1-0.20200203165724-3fc45a9a417a h1:JWKI8cKFamD9yYr
254254
github.com/pegnet/pegnet v0.4.1-0.20200203165724-3fc45a9a417a/go.mod h1:Fy8Qohe9zIuqO9Q/ZOLUNpP2kuiFqxjevzaS3IL62+E=
255255
github.com/pegnet/pegnet v0.5.1-0.20200723222201-c2cae6127a81 h1:CPsIntmwrXaKJSXnxT3vP3iK3KQ9ZVLOHD7e6HXICOo=
256256
github.com/pegnet/pegnet v0.5.1-0.20200723222201-c2cae6127a81/go.mod h1:Fy8Qohe9zIuqO9Q/ZOLUNpP2kuiFqxjevzaS3IL62+E=
257+
github.com/pegnet/pegnet v0.5.1-0.20200817185314-09e233fbd64d h1:bvSLli3ILdKD6bGmu8memh1+QY3KIrJ3OtvOm/tpKwo=
258+
github.com/pegnet/pegnet v0.5.1-0.20200817185314-09e233fbd64d/go.mod h1:Fy8Qohe9zIuqO9Q/ZOLUNpP2kuiFqxjevzaS3IL62+E=
257259
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
258260
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
259261
github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=

node/node.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
var (
1919
OPRChain = factom.NewBytes32("a642a8674f46696cc47fdb6b65f9c87b2a19c5ea8123b3d2f0c13b6f33a9d5ef")
20+
SPRChain = factom.NewBytes32("d5e395125335a21cef0ceca528168e87fe929fdac1f156870c1b1be6502448b4")
2021
TransactionChain = factom.NewBytes32("cffce0f409ebba4ed236d49d89c70e4bd1f1367d86402a3363366683265a242d")
2122

2223
// Acivation Heights

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: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
// ignore bad opr errors
40+
err = g.AddSPR(entry.Hash[:], extids, entry.Content)
41+
if err != nil {
42+
// This is a noisy debug print
43+
//logrus.WithError(err).WithFields(logrus.Fields{"hash": entry.Hash.String()}).Debug("failed to add spr")
44+
}
45+
}
46+
}
47+
48+
return g.Grade(), nil
49+
}

node/sync.go

Lines changed: 152 additions & 24 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"
@@ -247,43 +249,94 @@ func (d *Pegnetd) SyncBlock(ctx context.Context, tx *sql.Tx, height uint32) erro
247249
return err
248250
}
249251
}
252+
sprEBlock := dblock.EBlock(SPRChain)
253+
if sprEBlock != nil {
254+
if err := multiFetch(sprEBlock, d.FactomClient); err != nil {
255+
return err
256+
}
257+
}
250258

251259
// Then, grade the new OPR Block. The results of this will be used
252260
// to execute conversions that are in holding.
253261
gradedBlock, err := d.Grade(ctx, oprEBlock)
254-
if err != nil {
255-
return err
256-
} else if gradedBlock != nil {
257-
err = d.Pegnet.InsertGradeBlock(tx, oprEBlock, gradedBlock)
262+
gradedSPRBlock, err_s := d.GradeS(ctx, sprEBlock)
263+
isRatesAvailable := false
264+
if height < V20HeightActivation {
258265
if err != nil {
259266
return err
260267
}
261-
winners := gradedBlock.Winners()
262-
if 0 < len(winners) {
263-
// PEG has 3 current pricing phases
264-
// 1: Price is 0
265-
// 2: Price is determined by equation
266-
// 3: Price is determine by miners
267-
var phase pegnet.PEGPricingPhase
268-
if height < PEGPricingActivation {
269-
phase = pegnet.PEGPriceIsZero
270-
}
271-
if height >= PEGPricingActivation {
272-
phase = pegnet.PEGPriceIsEquation
268+
isRatesAvailable = gradedBlock != nil && 0 < len(gradedBlock.Winners())
269+
if gradedBlock != nil {
270+
err = d.Pegnet.InsertGradeBlock(tx, oprEBlock, gradedBlock)
271+
if err != nil {
272+
return err
273273
}
274-
if height >= PEGFreeFloatingPriceActivation {
275-
phase = pegnet.PEGPriceIsFloating
274+
winners := gradedBlock.Winners()
275+
if 0 < len(winners) {
276+
// PEG has 3 current pricing phases
277+
// 1: Price is 0
278+
// 2: Price is determined by equation
279+
// 3: Price is determine by miners
280+
var phase pegnet.PEGPricingPhase
281+
if height < PEGPricingActivation {
282+
phase = pegnet.PEGPriceIsZero
283+
}
284+
if height >= PEGPricingActivation {
285+
phase = pegnet.PEGPriceIsEquation
286+
}
287+
if height >= PEGFreeFloatingPriceActivation {
288+
phase = pegnet.PEGPriceIsFloating
289+
}
290+
291+
err = d.Pegnet.InsertRates(tx, height, winners[0].OPR.GetOrderedAssetsUint(), phase)
292+
if err != nil {
293+
return err
294+
}
295+
} else {
296+
fLog.WithFields(log.Fields{"section": "grading", "reason": "no winners"}).Tracef("block not graded")
276297
}
298+
} else {
299+
fLog.WithFields(log.Fields{"section": "grading", "reason": "no graded block"}).Tracef("block not graded")
300+
}
301+
} else {
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
277312

278-
err = d.Pegnet.InsertRates(tx, height, winners[0].OPR.GetOrderedAssetsUint(), phase)
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)
279334
if err != nil {
280335
return err
281336
}
282337
} else {
283-
fLog.WithFields(log.Fields{"section": "grading", "reason": "no winners"}).Tracef("block not graded")
338+
fLog.WithFields(log.Fields{"section": "grading", "reason": "no winners from OPR & SPR"}).Tracef("block not graded")
284339
}
285-
} else {
286-
fLog.WithFields(log.Fields{"section": "grading", "reason": "no graded block"}).Tracef("block not graded")
287340
}
288341

289342
// Only apply transactions if we crossed the activation
@@ -324,7 +377,7 @@ func (d *Pegnetd) SyncBlock(ctx context.Context, tx *sql.Tx, height uint32) erro
324377
// At this point, we start making updates to the database in a specific order:
325378
// TODO: ensure we rollback the tx when needed
326379
// 1) Apply transaction batches that are in holding (conversions are always applied here)
327-
if gradedBlock != nil && 0 < len(gradedBlock.Winners()) {
380+
if isRatesAvailable {
328381
// Before conversions can be run, we have to adjust and discover the bank's value.
329382
// We also only sync the bank if the block is a pegnet block
330383
if err := d.SyncBank(ctx, tx, height); err != nil {
@@ -362,7 +415,17 @@ func (d *Pegnetd) SyncBlock(ctx context.Context, tx *sql.Tx, height uint32) erro
362415
}
363416
}
364417

365-
// 5) Apply Developers Rewards
418+
if height >= V20HeightActivation {
419+
// 5) Apply effects of graded SPR Block (PEG rewards, if any)
420+
// These funds will be available for transactions and conversions executed in the next block
421+
if gradedSPRBlock != nil {
422+
if err := d.ApplyGradedSPRBlock(tx, gradedSPRBlock, dblock.Timestamp); err != nil {
423+
return err
424+
}
425+
}
426+
}
427+
428+
// 6) Apply Developers Rewards
366429
if height >= V20HeightActivation && height%pegnet.SnapshotRate == 0 {
367430
err := d.DevelopersPayouts(tx, fLog, height, dblock.Timestamp, DeveloperRewardAddreses)
368431
if err != nil {
@@ -1088,6 +1151,34 @@ func (d *Pegnetd) ApplyGradedOPRBlock(tx *sql.Tx, gradedBlock grader.GradedBlock
10881151
return nil
10891152
}
10901153

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+
10911182
func isDone(ctx context.Context) bool {
10921183
select {
10931184
case <-ctx.Done():
@@ -1096,3 +1187,40 @@ func isDone(ctx context.Context) bool {
10961187
return false
10971188
}
10981189
}
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+
return nil, fmt.Errorf("opr is out side of tolerance band")
1220+
}
1221+
}
1222+
}
1223+
return filteredRates, nil
1224+
}
1225+
return nil, fmt.Errorf("no winners")
1226+
}

0 commit comments

Comments
 (0)