Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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 = 999999
)

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")
Comment thread
StarNeit marked this conversation as resolved.
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) {
Comment thread
StarNeit marked this conversation as resolved.
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 {
Comment thread
StarNeit marked this conversation as resolved.
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{
Comment thread
StarNeit marked this conversation as resolved.
"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