Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed seed conditions and added tests for applying transactions. #126

Merged
merged 12 commits into from
Jul 18, 2019
Merged
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
4 changes: 2 additions & 2 deletions api/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func (g *Gateway) sendTransaction(ctx *fasthttp.RequestCtx) {

tx := wavelet.AttachSenderToTransaction(
g.keys,
wavelet.Transaction{Tag: sys.Tag(req.Tag), Payload: req.payload, Creator: req.creator, CreatorSignature: req.signature},
&wavelet.Transaction{Tag: sys.Tag(req.Tag), Payload: req.payload, Creator: req.creator, CreatorSignature: req.signature},
g.ledger.Graph().FindEligibleParents()...,
)

Expand All @@ -231,7 +231,7 @@ func (g *Gateway) sendTransaction(ctx *fasthttp.RequestCtx) {
return
}

g.render(ctx, &sendTransactionResponse{ledger: g.ledger, tx: &tx})
g.render(ctx, &sendTransactionResponse{ledger: g.ledger, tx: tx})
}

func (g *Gateway) ledgerStatus(ctx *fasthttp.RequestCtx) {
Expand Down
2 changes: 1 addition & 1 deletion cmd/wavelet/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ func (cli *CLI) withdrawReward(cmd []string) {
Msgf("Success! Your reward withdrawal transaction ID: %x", tx.ID)
}

func (cli *CLI) sendTransaction(tx wavelet.Transaction) (wavelet.Transaction, error) {
func (cli *CLI) sendTransaction(tx *wavelet.Transaction) (*wavelet.Transaction, error) {
tx = wavelet.AttachSenderToTransaction(cli.keys, tx, cli.ledger.Graph().FindEligibleParents()...)

if err := cli.ledger.AddTransaction(tx); err != nil && errors.Cause(err) != wavelet.ErrMissingParents {
Expand Down
248 changes: 248 additions & 0 deletions collapse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
package wavelet

import (
"encoding/binary"
"encoding/hex"
"fmt"
"github.com/perlin-network/wavelet/avl"
"github.com/perlin-network/wavelet/log"
"github.com/perlin-network/wavelet/sys"
queue2 "github.com/phf/go-queue/queue"
"github.com/pkg/errors"
"golang.org/x/crypto/blake2b"
)

func processRewardWithdrawals(round uint64, snapshot *avl.Tree) {
rws := GetRewardWithdrawalRequests(snapshot, round-uint64(sys.RewardWithdrawalsRoundLimit))

for _, rw := range rws {
balance, _ := ReadAccountBalance(snapshot, rw.account)
WriteAccountBalance(snapshot, rw.account, balance+rw.amount)

snapshot.Delete(rw.Key())
}
}

func rewardValidators(g *Graph, snapshot *avl.Tree, root *Transaction, tx *Transaction, logging bool) error {
fee := sys.TransactionFeeAmount

creatorBalance, _ := ReadAccountBalance(snapshot, tx.Creator)

if creatorBalance < fee {
return errors.Errorf("stake: creator %x does not have enough PERLs to pay transaction fees (comprised of %d PERLs)", tx.Creator, fee)
}

WriteAccountBalance(snapshot, tx.Creator, creatorBalance-fee)

var candidates []*Transaction
var stakes []uint64
var totalStake uint64

visited := make(map[TransactionID]struct{})

queue := AcquireQueue()
defer ReleaseQueue(queue)

for _, parentID := range tx.ParentIDs {
if parent := g.FindTransaction(parentID); parent != nil {
queue.PushBack(parent)
}

visited[parentID] = struct{}{}
}

hasher, _ := blake2b.New256(nil)

var depthCounter uint64
var lastDepth = tx.Depth

for queue.Len() > 0 {
popped := queue.PopFront().(*Transaction)

if popped.Depth != lastDepth {
lastDepth = popped.Depth
depthCounter++
}

// If we exceed the max eligible depth we search for candidate
// validators to reward from, stop traversing.

if depthCounter >= sys.MaxDepthDiff {
break
}

// Filter for all ancestral transactions not from the same sender,
// and within the desired graph depth.

if popped.Sender != tx.Sender {
stake, _ := ReadAccountStake(snapshot, popped.Sender)

if stake > sys.MinimumStake {
candidates = append(candidates, popped)
stakes = append(stakes, stake)

totalStake += stake

// Record entropy source.
if _, err := hasher.Write(popped.ID[:]); err != nil {
return errors.Wrap(err, "stake: failed to hash transaction ID for entropy source")
}
}
}

for _, parentID := range popped.ParentIDs {
if _, seen := visited[parentID]; !seen {
if parent := g.FindTransaction(parentID); parent != nil {
queue.PushBack(parent)
}

visited[parentID] = struct{}{}
}
}
}

// If there are no eligible rewardee candidates, do not reward anyone.

if len(candidates) == 0 || len(stakes) == 0 || totalStake == 0 {
return nil
}

entropy := hasher.Sum(nil)
acc, threshold := float64(0), float64(binary.LittleEndian.Uint64(entropy)%uint64(0xffff))/float64(0xffff)

var rewardee *Transaction

// Model a weighted uniform distribution by a random variable X, and select
// whichever validator has a weight X ≥ X' as a reward recipient.

for i, tx := range candidates {
acc += float64(stakes[i]) / float64(totalStake)

if acc >= threshold {
rewardee = tx
break
}
}

// If there is no selected transaction that deserves a reward, give the
// reward to the last reward candidate.

if rewardee == nil {
rewardee = candidates[len(candidates)-1]
}

rewardeeBalance, _ := ReadAccountReward(snapshot, rewardee.Sender)
WriteAccountReward(snapshot, rewardee.Sender, rewardeeBalance+fee)

if logging {
logger := log.Stake("reward_validator")
logger.Info().
Hex("creator", tx.Creator[:]).
Hex("recipient", rewardee.Sender[:]).
Hex("creator_tx_id", tx.ID[:]).
Hex("rewardee_tx_id", rewardee.ID[:]).
Hex("entropy", entropy).
Float64("acc", acc).
Float64("threshold", threshold).Msg("Rewarded validator.")
}

return nil
}

func collapseTransactions(g *Graph, accounts *Accounts, round uint64, latestRound *Round, root *Transaction, end *Transaction, logging bool) (*collapseResults, error) {
res := &collapseResults{snapshot: accounts.Snapshot()}
res.snapshot.SetViewID(round)

visited := map[TransactionID]struct{}{root.ID: {}}

queue := queue2.New()
queue.PushBack(end)

order := queue2.New()

for queue.Len() > 0 {
popped := queue.PopFront().(*Transaction)

if popped.Depth <= root.Depth {
continue
}

order.PushBack(popped)

for _, parentID := range popped.ParentIDs {
if _, seen := visited[parentID]; seen {
continue
}

visited[parentID] = struct{}{}

parent := g.FindTransaction(parentID)

if parent == nil {
g.MarkTransactionAsMissing(parentID, popped.Depth)
return nil, errors.Errorf("missing ancestor %x to correctly collapse down ledger state from critical transaction %x", parentID, end.ID)
}

queue.PushBack(parent)
}
}

res.applied = make([]*Transaction, 0, order.Len())
res.rejected = make([]*Transaction, 0, order.Len())
res.rejectedErrors = make([]error, 0, order.Len())

// Apply transactions in reverse order from the end of the round
// all the way down to the beginning of the round.

for order.Len() > 0 {
popped := order.PopBack().(*Transaction)

// Update nonce.

nonce, exists := ReadAccountNonce(res.snapshot, popped.Creator)
if !exists {
WriteAccountsLen(res.snapshot, ReadAccountsLen(res.snapshot)+1)
}
WriteAccountNonce(res.snapshot, popped.Creator, nonce+1)

// FIXME(kenta): FOR TESTNET ONLY. FAUCET DOES NOT GET ANY PERLs DEDUCTED.
if hex.EncodeToString(popped.Creator[:]) != sys.FaucetAddress {
if err := rewardValidators(g, res.snapshot, root, popped, logging); err != nil {
res.rejected = append(res.rejected, popped)
res.rejectedErrors = append(res.rejectedErrors, err)
res.rejectedCount += popped.LogicalUnits()

continue
}
}

if err := ApplyTransaction(latestRound, res.snapshot, popped); err != nil {
res.rejected = append(res.rejected, popped)
res.rejectedErrors = append(res.rejectedErrors, err)
res.rejectedCount += popped.LogicalUnits()

fmt.Println(err)

continue
}

// Update statistics.

res.applied = append(res.applied, popped)
res.appliedCount += popped.LogicalUnits()
}

startDepth, endDepth := root.Depth+1, end.Depth

for _, tx := range g.GetTransactionsByDepth(&startDepth, &endDepth) {
res.ignoredCount += tx.LogicalUnits()
}

res.ignoredCount -= res.appliedCount + res.rejectedCount

if round >= uint64(sys.RewardWithdrawalsRoundLimit) {
processRewardWithdrawals(round, res.snapshot)
}

return res, nil
}
12 changes: 7 additions & 5 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ import (
import _ "github.com/perlin-network/wavelet/internal/snappy"

const (
SizeTransactionID = blake2b.Size256
SizeRoundID = blake2b.Size256
SizeMerkleNodeID = md5.Size
SizeAccountID = 32
SizeSignature = 64
SizeTransactionID = blake2b.Size256
SizeTransactionSeed = blake2b.Size256
SizeRoundID = blake2b.Size256
SizeMerkleNodeID = md5.Size
SizeAccountID = 32
SizeSignature = 64
)

type TransactionID = [SizeTransactionID]byte
type TransactionSeed = [SizeTransactionSeed]byte
type RoundID = [SizeRoundID]byte
type MerkleNodeID = [SizeMerkleNodeID]byte
type AccountID = [SizeAccountID]byte
Expand Down
13 changes: 8 additions & 5 deletions genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package wavelet
import (
"encoding/hex"
"github.com/perlin-network/wavelet/avl"
"github.com/perlin-network/wavelet/log"
"github.com/pkg/errors"
"github.com/valyala/fastjson"
)
Expand All @@ -44,6 +45,8 @@ const defaultGenesis = `
// performInception loads data expected to exist at the birth of any node in this ledgers network.
// The data is fed in as .json.
func performInception(tree *avl.Tree, genesis *string) Round {
logger := log.Node()

var buf []byte

if genesis != nil {
Expand All @@ -57,13 +60,13 @@ func performInception(tree *avl.Tree, genesis *string) Round {
parsed, err := p.ParseBytes(buf)

if err != nil {
panic(err)
logger.Fatal().Err(err).Msg("ParseBytes()")
}

accounts, err := parsed.Object()

if err != nil {
panic(err)
logger.Fatal().Err(err).Msg("parsed.Object()")
}

var balance, stake, reward uint64
Expand Down Expand Up @@ -146,11 +149,11 @@ func performInception(tree *avl.Tree, genesis *string) Round {
})

if err != nil {
panic(err)
logger.Fatal().Err(err).Msg("accounts.Visit")
}

tx := Transaction{}
tx := &Transaction{}
tx.rehash()

return NewRound(0, tree.Checksum(), 0, Transaction{}, tx)
return NewRound(0, tree.Checksum(), 0, &Transaction{}, tx)
}
2 changes: 1 addition & 1 deletion gossip.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func NewGossiper(ctx context.Context, client *skademlia.Client, metrics *Metrics
return g
}

func (g *Gossiper) Push(tx Transaction) {
func (g *Gossiper) Push(tx *Transaction) {
g.debouncer.Add(debounce.Bytes(tx.Marshal()))

if g.metrics != nil {
Expand Down
Loading