Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions config/activations.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ var (
//
// Activation of 2.0.5
PIP10AverageActivation uint32 = 295190

// PIP18DelegateStakingActivation implements delegate staking by using PEG addresses.
// 1. Balances of PEG for each address is more complicated. It is the balance of PEG for the address (assuming it has not be delegated)
// 2. We quit looking at the rich list, and just consider the top 100 submissions with the highest stake
// 3. We pay out with the ratio of the total PEG staked. (Removes old top 100 PEG addresses staking reward and give staking opportunity to all PEG holders)
PIP18DelegateStakingActivation uint32 = 314482
)

func SetAllActivations(act uint32) {
Expand All @@ -97,4 +103,5 @@ func SetAllActivations(act uint32) {
V204EnhanceActivation = act
V204BurnMintedTokenActivation = act
PIP10AverageActivation = act
PIP18DelegateStakingActivation = act
}
19 changes: 19 additions & 0 deletions node/pegnet/addresses.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,25 @@ func (p *Pegnet) IsIncludedTopPEGAddress(address []byte) bool {
return false
}

func (p *Pegnet) GetPEGAddress(address []byte) (uint64, error) {
stmt2 := `SELECT peg_balance FROM pn_addresses WHERE address = ?;`
rows, err2 := p.DB.Query(stmt2, address)
if err2 != nil {
fmt.Println("DB query is failed")
return 0, err2
}
defer rows.Close()

for rows.Next() {
var pegBalance uint64
if err := rows.Scan(&pegBalance); err != nil {
return 0, err
}
return pegBalance, nil
}
return 0, nil
}

// SelectRichList returns the balance of all addresses for a given ticker
func (p *Pegnet) SelectRichList(ticker fat2.PTicker, count int) ([]BalancePair, error) {
if ticker <= fat2.PTickerInvalid || fat2.PTickerMax <= ticker {
Expand Down
42 changes: 41 additions & 1 deletion node/pegnet/txhistory.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/pegnet/pegnet/modules/graderDelegateStake"
"strings"
"time"

Expand Down Expand Up @@ -426,6 +427,45 @@ func (p *Pegnet) InsertStaking100Coinbase(tx *sql.Tx, winner *graderStake.Gradin
return nil
}

// InsertCoinbase inserts the payouts from staking into the history system.
// There is one transaction per winning SPR, with the entry hash pointing to that specific spr
func (p *Pegnet) InsertStaking100CoinbaseDelegate(tx *sql.Tx, winner *graderDelegateStake.GradingDelegatedSPR, addr []byte, timestamp time.Time) error {
stmt, err := tx.Prepare(`INSERT INTO "pn_history_txbatch"
(entry_hash, height, blockorder, timestamp, executed) VALUES
(?, ?, ?, ?, ?)`)
if err != nil {
return err
}

lookup, err := tx.Prepare(insertLookupQuery)
if err != nil {
return err
}

_, err = stmt.Exec(winner.EntryHash, winner.SPR.GetHeight(), 0, timestamp.Unix(), winner.SPR.GetHeight())
if err != nil {
return err
}

coinbaseStatement, err := tx.Prepare(`INSERT INTO "pn_history_transaction"
(entry_hash, tx_index, action_type, from_address, from_asset, from_amount, to_asset, to_amount, outputs) VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?)`)
if err != nil {
return err
}

_, err = coinbaseStatement.Exec(winner.EntryHash, 0, Coinbase, addr, "", 0, "PEG", winner.Payout(), "")
if err != nil {
return err
}

if _, err = lookup.Exec(winner.EntryHash, 0, addr); err != nil {
return err
}

return nil
}

// InsertStakingCoinbase inserts the payouts from mining into the history system.
// There is one transaction per winning OPR, with the entry hash pointing to that specific opr
func (p *Pegnet) InsertStakingCoinbase(tx *sql.Tx, txid string, height uint32, heightTimestamp time.Time, payouts map[string]uint64, addressMap map[string]factom.FAAddress) error {
Expand Down Expand Up @@ -614,4 +654,4 @@ func (p *Pegnet) InsertZeroingCoinbase(tx *sql.Tx, txid string, addTxid string,
}

return nil
}
}
104 changes: 90 additions & 14 deletions node/spr.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package node
import (
"context"
"fmt"
"github.com/pegnet/pegnet/modules/spr"

"github.com/Factom-Asset-Tokens/factom"
"github.com/pegnet/pegnet/modules/graderDelegateStake"
"github.com/pegnet/pegnet/modules/graderStake"
"github.com/pegnet/pegnetd/config"
)
Expand All @@ -31,27 +33,101 @@ func (d *Pegnetd) GradeS(ctx context.Context, block *factom.EBlock) (graderStake
if block.Height >= config.V202EnhanceActivation {
ver = 7
}
if block.Height >= config.PIP18DelegateStakingActivation {
ver = 8
}

if ver < 8 {
g, err := graderStake.NewGrader(ver, int32(block.Height))
if err != nil {
return nil, err
}
for _, entry := range block.Entries {
extids := make([][]byte, len(entry.ExtIDs))
for i := range entry.ExtIDs {
extids[i] = entry.ExtIDs[i]
}
// allow only top 100 stake holders submit prices
stakerRCD := extids[1]
if d.Pegnet.IsIncludedTopPEGAddress(stakerRCD) {
// ignore bad opr errors
err = g.AddSPR(entry.Hash[:], extids, entry.Content)
if err != nil {
// This is a noisy debug print
//logrus.WithError(err).WithFields(logrus.Fields{"hash": entry.Hash.String()}).Debug("failed to add spr")
}
}
}
return g.Grade(), nil
}
return nil, nil
}

func isElementExist(element string, list []string) bool {
isExist := false
for j := 0; j < len(list); j++ {
if list[j] == element {
isExist = true
}
}
return isExist
}

// Grade Staking Price Records
func (d *Pegnetd) GradeDelegatedS(ctx context.Context, block *factom.EBlock) (graderDelegateStake.DelegatedGradedBlock, error) {
if block == nil {
// TODO: Handle the case where there is no opr block.
// Must delay conversions if this- happens
return nil, nil
}

if *block.ChainID != config.SPRChain {
return nil, fmt.Errorf("trying to grade a non-spr chain")
}

g, err := graderStake.NewGrader(ver, int32(block.Height))
if err != nil {
return nil, err
ver := uint8(8)
if block.Height >= config.PIP18DelegateStakingActivation {
ver = 8
}
for _, entry := range block.Entries {
extids := make([][]byte, len(entry.ExtIDs))
for i := range entry.ExtIDs {
extids[i] = entry.ExtIDs[i]

if ver == 8 {
g, err := graderDelegateStake.NewDelegatedGrader(ver, int32(block.Height))
if err != nil {
return nil, err
}
// allow only top 100 stake holders submit prices
stakerRCD := extids[1]
if d.Pegnet.IsIncludedTopPEGAddress(stakerRCD) {
// ignore bad opr errors
err = g.AddSPR(entry.Hash[:], extids, entry.Content)
var groupOfDelegatorsAddress []string
for _, entry := range block.Entries {
extids := make([][]byte, len(entry.ExtIDs))
for i := range entry.ExtIDs {
extids[i] = entry.ExtIDs[i]
}
o2, errP := spr.ParseS1Content(entry.Content)
var balanceOfPEG uint64 = 0
if errP == nil {
balanceOfPEG, _ = d.Pegnet.GetPEGAddress([]byte(o2.Address))
}
if errP == nil && len(extids) == 5 && len(extids[0]) == 1 && extids[0][0] == 8 {
listOfDelegatorsAddress, err := g.GetDelegatorsAddress(extids[3], extids[4], o2.Address)
if err != nil {
continue
}
for i := 0; i < len(listOfDelegatorsAddress); i++ {
isDuplicatedAddress := isElementExist(listOfDelegatorsAddress[i], groupOfDelegatorsAddress)
if isDuplicatedAddress {
continue
}
individualBalance, _ := d.Pegnet.GetPEGAddress([]byte(listOfDelegatorsAddress[i]))
balanceOfPEG += individualBalance
groupOfDelegatorsAddress = append(groupOfDelegatorsAddress, listOfDelegatorsAddress[i])
}
}
err = g.AddSPRV4(entry.Hash[:], extids, entry.Content, balanceOfPEG)
if err != nil {
// This is a noisy debug print
//logrus.WithError(err).WithFields(logrus.Fields{"hash": entry.Hash.String()}).Debug("failed to add spr")
}
}
return g.Grade(), nil
}

return g.Grade(), nil
return nil, nil
}
72 changes: 64 additions & 8 deletions node/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
"github.com/pegnet/pegnet/modules/graderDelegateStake"
"math/big"
"sort"
"time"
Expand Down Expand Up @@ -380,7 +381,17 @@ func (d *Pegnetd) SyncBlock(ctx context.Context, tx *sql.Tx, height uint32) erro
// Then, grade the new OPR Block. The results of this will be used
// to execute conversions that are in holding.
gradedBlock, err := d.Grade(ctx, oprEBlock)
gradedSPRBlock, err_s := d.GradeS(ctx, sprEBlock)

var gradedSPRBlock graderStake.GradedBlock
var gradedDelegatedSPRBlock graderDelegateStake.DelegatedGradedBlock
var err_s error
if sprEBlock != nil {
if height < config.PIP18DelegateStakingActivation {
gradedSPRBlock, err_s = d.GradeS(ctx, sprEBlock)
} else {
gradedDelegatedSPRBlock, err_s = d.GradeDelegatedS(ctx, sprEBlock)
}
}
isRatesAvailable := false
if height < config.V20HeightActivation {
if err != nil {
Expand Down Expand Up @@ -441,10 +452,19 @@ func (d *Pegnetd) SyncBlock(ctx context.Context, tx *sql.Tx, height uint32) erro
oprWinners = winnersOpr[0].OPR.GetOrderedAssetsUint()
}
}
if gradedSPRBlock != nil {
winnersSpr := gradedSPRBlock.Winners()
if 0 < len(winnersSpr) {
sprWinners = winnersSpr[0].SPR.GetOrderedAssetsUint()
if height < config.PIP18DelegateStakingActivation {
if gradedSPRBlock != nil {
winnersSpr := gradedSPRBlock.Winners()
if 0 < len(winnersSpr) {
sprWinners = winnersSpr[0].SPR.GetOrderedAssetsUint()
}
}
} else {
if gradedDelegatedSPRBlock != nil {
winnersSpr := gradedDelegatedSPRBlock.Winners()
if 0 < len(winnersSpr) {
sprWinners = winnersSpr[0].SPR.GetOrderedAssetsUint()
}
}
}
if 0 < len(oprWinners) || 0 < len(sprWinners) {
Expand Down Expand Up @@ -555,9 +575,17 @@ func (d *Pegnetd) SyncBlock(ctx context.Context, tx *sql.Tx, height uint32) erro
if height >= config.V20HeightActivation {
// 5) Apply effects of graded SPR Block (PEG rewards, if any)
// These funds will be available for transactions and conversions executed in the next block
if gradedSPRBlock != nil {
if err := d.ApplyGradedSPRBlock(tx, gradedSPRBlock, dblock.Timestamp); err != nil {
return err
if height < config.PIP18DelegateStakingActivation {
if gradedSPRBlock != nil {
if err := d.ApplyGradedSPRBlock(tx, gradedSPRBlock, dblock.Timestamp); err != nil {
return err
}
}
} else {
if gradedDelegatedSPRBlock != nil {
if err := d.ApplyGradedDelegatedSPRBlock(tx, gradedDelegatedSPRBlock, dblock.Timestamp); err != nil {
return err
}
}
}
}
Expand Down Expand Up @@ -1421,6 +1449,34 @@ func (d *Pegnetd) ApplyGradedSPRBlock(tx *sql.Tx, gradedSPRBlock graderStake.Gra
return nil
}

// ApplyGradedDelegatedSPRBlock pays out PEG to the winners of the given GradedBlock.
// If an error is returned, the sql.Tx should be rolled back by the caller.
func (d *Pegnetd) ApplyGradedDelegatedSPRBlock(tx *sql.Tx, gradedSPRBlock graderDelegateStake.DelegatedGradedBlock, timestamp time.Time) error {
winners := gradedSPRBlock.Winners()
for i := range winners {
addr, err := factom.NewFAAddress(winners[i].SPR.GetAddress())
if err != nil {
// TODO: This is kinda an odd case. I think we should just drop the rewards
// for an invalid address. We can always add back the rewards and they will have
// a higher balance after a change.
log.WithError(err).WithFields(log.Fields{
"height": winners[i].SPR.GetHeight(),
"ehash": fmt.Sprintf("%x", winners[i].EntryHash),
}).Warnf("failed to reward")
continue
}

if _, err := d.Pegnet.AddToBalance(tx, &addr, fat2.PTickerPEG, uint64(winners[i].Payout())); err != nil {
return err
}

if err := d.Pegnet.InsertStaking100CoinbaseDelegate(tx, winners[i], addr[:], timestamp); err != nil {
return err
}
}
return nil
}

func isDone(ctx context.Context) bool {
select {
case <-ctx.Done():
Expand Down