Skip to content

Commit

Permalink
Release 0.5.0-rc4 (#577)
Browse files Browse the repository at this point in the history
* Catch ErrOperationAborted of the solidifier

* Update the outdated root snapshot indexes even if txs were missing

* Do not panic if ErrTransactionNotFound

* Do not update root transaction snapshot indexes if not synced

* Use stack based DFS in TraverseApprovees

This was necessary to get correct ordering for the solidifier and URTS.

* Do not add the startTx to the outdatedTransactions in GetTransactionRootSnapshotIndexes

* Update scores of the URTS tips

* Use stack based BFS in TraverseApprovers

* Fix wrong scoreSum in URTS

* Set higher default verticesLimit in visualizer

* Do not add tips to URTS if the node becomes unsync

* Do not traverse approvers in TraverseApprovers if already discovered

* Avoid calling condition and onSolidEntryPoint multiple times

* Fix nil pointer in removeMessageEventHandlers

* Release 0.5.0-rc4
  • Loading branch information
muXxer authored Jul 28, 2020
1 parent ae2b6ff commit 24d7da6
Show file tree
Hide file tree
Showing 15 changed files with 279 additions and 153 deletions.
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,33 @@

All notable changes to this project will be documented in this file.

## [0.5.0-rc4] - 28.07.2020

**Breaking change:**
** DO NOT USE IT ON MAINNET, IT WILL CRASH IMMEDIATELY AND IT WILL DESTROY YOUR DATABASE !!! **

### Changed

- Use stack based DFS in TraverseApprovees
- Use stack based BFS in TraverseApprovers
- Do not update root transaction snapshot indexes if not synced
- Set higher default verticesLimit in visualizer
- Do not add tips to URTS if the node becomes unsync
- Update scores of the URTS tips

### Fixed

- Fix solidifier
- Catch ErrOperationAborted of the solidifier
- Fix wrong scoreSum in URTS
- Fix nil pointer in removeMessageEventHandlers
- Panic if ErrTransactionNotFound in GetTransactionRootSnapshotIndexes

## [0.5.0-rc3] - 26.07.2020

**Breaking change:**
** DO NOT USE IT ON MAINNET, IT WILL CRASH IMMEDIATELY AND IT WILL DESTROY YOUR DATABASE !!! **

## [0.5.0-rc2] - 26.07.2020

**Breaking change:**
Expand Down
253 changes: 145 additions & 108 deletions pkg/dag/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dag

import (
"bytes"
"container/list"

"github.com/pkg/errors"

Expand All @@ -21,6 +22,7 @@ func FindAllTails(startTxHash hornet.Hash, skipStartTx bool, forceRelease bool)

err := TraverseApprovees(startTxHash,
// traversal stops if no more transactions pass the given condition
// Caution: condition func is not in DFS order
func(cachedTx *tangle.CachedTransaction) (bool, error) { // tx +1
defer cachedTx.Release(true) // tx -1

Expand Down Expand Up @@ -66,150 +68,185 @@ type OnMissingApprovee func(approveeHash hornet.Hash) error
// OnSolidEntryPoint gets called when during traversal the startTx or approvee is a solid entry point.
type OnSolidEntryPoint func(txHash hornet.Hash)

// TraverseApprovees starts to traverse the approvees (past cone) of the given start transaction until
// the traversal stops due to no more transactions passing the given condition.
func TraverseApprovees(startTxHash hornet.Hash, condition Predicate, consumer Consumer, onMissingApprovee OnMissingApprovee, onSolidEntryPoint OnSolidEntryPoint, forceRelease bool, traverseSolidEntryPoints bool, abortSignal <-chan struct{}) error {
// processStackApprovees checks if the current element in the stack must be processed or traversed.
// first the trunk is traversed, then the branch.
func processStackApprovees(stack *list.List, visited map[string]bool, condition Predicate, consumer Consumer, onMissingApprovee OnMissingApprovee, onSolidEntryPoint OnSolidEntryPoint, forceRelease bool, traverseSolidEntryPoints bool, abortSignal <-chan struct{}) error {

if tangle.SolidEntryPointsContain(startTxHash) {
onSolidEntryPoint(startTxHash)
if !traverseSolidEntryPoints {
return nil
}
select {
case <-abortSignal:
return tangle.ErrOperationAborted
default:
}

processed := map[string]struct{}{}
txsToTraverse := map[string]struct{}{string(startTxHash): {}}
for len(txsToTraverse) != 0 {
for txHash := range txsToTraverse {
delete(txsToTraverse, txHash)
// load candidate tx
ele := stack.Front()
currentTxHash := ele.Value.(hornet.Hash)

select {
case <-abortSignal:
return tangle.ErrOperationAborted
default:
}
cachedTx := tangle.GetCachedTransactionOrNil(currentTxHash) // tx +1
if cachedTx == nil {
// remove the transaction from the stack, trunk and branch are not traversed
visited[string(currentTxHash)] = false
stack.Remove(ele)

cachedTx := tangle.GetCachedTransactionOrNil(hornet.Hash(txHash)) // tx +1
if cachedTx == nil {
return errors.Wrapf(tangle.ErrTransactionNotFound, "hash: %s", hornet.Hash(txHash).Trytes())
}
// stop processing the stack if the caller returns an error
return onMissingApprovee(currentTxHash)
}

// check condition to decide if tx should be consumed and traversed
traverse, err := condition(cachedTx.Retain()) // tx + 1
if err != nil {
cachedTx.Release(forceRelease) // tx -1
return err
}
defer cachedTx.Release(forceRelease) // tx -1

if !traverse {
cachedTx.Release(forceRelease) // tx -1
continue
}
traverse, checkedBefore := visited[string(currentTxHash)]
if !checkedBefore {
// check condition to decide if tx should be consumed and traversed
var err error
traverse, err = condition(cachedTx.Retain()) // tx + 1
if err != nil {
// there was an error, stop processing the stack
return err
}

// mark the transaction as visited and remember the result of the traverse condition
visited[string(currentTxHash)] = traverse
}

// consume the tx
if err := consumer(cachedTx.Retain()); err != nil { // tx +1
cachedTx.Release(forceRelease) // tx -1
return err
if !traverse {
// remove the transaction from the stack, trunk and branch are not traversed
// transaction will not get consumed
stack.Remove(ele)
return nil
}

trunkHash := cachedTx.GetTransaction().GetTrunkHash()
branchHash := cachedTx.GetTransaction().GetBranchHash()

approveeHashes := hornet.Hashes{trunkHash}
if !bytes.Equal(trunkHash, branchHash) {
approveeHashes = append(approveeHashes, branchHash)
}

for _, approveeHash := range approveeHashes {
if _, approveeVisited := visited[string(approveeHash)]; !approveeVisited {
if !tangle.SolidEntryPointsContain(approveeHash) {
// traverse this transaction
stack.PushFront(approveeHash)
return nil
}

approveeHashes := map[string]struct{}{
string(cachedTx.GetTransaction().GetTrunkHash()): {},
string(cachedTx.GetTransaction().GetBranchHash()): {},
onSolidEntryPoint(approveeHash)
if traverseSolidEntryPoints {
// traverse this transaction
stack.PushFront(approveeHash)
return nil
}

cachedTx.Release(forceRelease) // tx -1
// mark the approvee as visited if we do not traverse to not call "onSolidEntryPoint" repeatedly
visited[string(approveeHash)] = false
}
}

for approveeHash := range approveeHashes {
if tangle.SolidEntryPointsContain(hornet.Hash(approveeHash)) {
onSolidEntryPoint(hornet.Hash(approveeHash))
if !traverseSolidEntryPoints {
continue
}
}
// remove the transaction from the stack
stack.Remove(ele)

if _, checked := processed[approveeHash]; checked {
continue
}
// consume the transaction
if err := consumer(cachedTx.Retain()); err != nil { // tx +1
// there was an error, stop processing the stack
return err
}

return nil
}

processed[approveeHash] = struct{}{}
// TraverseApprovees starts to traverse the approvees (past cone) of the given start transaction until
// the traversal stops due to no more transactions passing the given condition.
// It is a DFS with trunk / branch.
// Caution: condition func is not in DFS order
func TraverseApprovees(startTxHash hornet.Hash, condition Predicate, consumer Consumer, onMissingApprovee OnMissingApprovee, onSolidEntryPoint OnSolidEntryPoint, forceRelease bool, traverseSolidEntryPoints bool, abortSignal <-chan struct{}) error {

cachedApproveeTx := tangle.GetCachedTransactionOrNil(hornet.Hash(approveeHash)) // approvee +1
if cachedApproveeTx == nil {
if err := onMissingApprovee(hornet.Hash(approveeHash)); err != nil {
return err
}
continue
}
stack := list.New()
stack.PushFront(startTxHash)

// do not force release since it is loaded again
cachedApproveeTx.Release() // approvee -1
// visited map with result of traverse condition
visited := make(map[string]bool)

txsToTraverse[approveeHash] = struct{}{}
}
for stack.Len() > 0 {
if err := processStackApprovees(stack, visited, condition, consumer, onMissingApprovee, onSolidEntryPoint, forceRelease, traverseSolidEntryPoints, abortSignal); err != nil {
return err
}
}

return nil
}

// TraverseApprovers starts to traverse the approvers (future cone) of the given start transaction until
// the traversal stops due to no more transactions passing the given condition.
func TraverseApprovers(startTxHash hornet.Hash, condition Predicate, consumer Consumer, forceRelease bool, abortSignal <-chan struct{}) error {
// processStackApprovers checks if the current element in the stack must be processed and traversed.
// current element gets consumed first, afterwards it's approvers get traversed in random order.
func processStackApprovers(stack *list.List, discovered map[string]struct{}, condition Predicate, consumer Consumer, forceRelease bool, abortSignal <-chan struct{}) error {

processed := map[string]struct{}{}
txsToTraverse := map[string]struct{}{string(startTxHash): {}}
for len(txsToTraverse) != 0 {
for txHash := range txsToTraverse {
delete(txsToTraverse, txHash)
select {
case <-abortSignal:
return tangle.ErrOperationAborted
default:
}

select {
case <-abortSignal:
return tangle.ErrOperationAborted
default:
}
// load candidate tx
ele := stack.Front()
currentTxHash := ele.Value.(hornet.Hash)

cachedTx := tangle.GetCachedTransactionOrNil(hornet.Hash(txHash)) // tx +1
if cachedTx == nil {
return errors.Wrapf(tangle.ErrTransactionNotFound, "hash: %s", hornet.Hash(txHash).Trytes())
}
// remove the transaction from the stack
stack.Remove(ele)

// check condition to decide if tx should be consumed and traversed
traverse, err := condition(cachedTx.Retain()) // tx + 1
if err != nil {
cachedTx.Release(forceRelease) // tx -1
return err
}
cachedTx := tangle.GetCachedTransactionOrNil(currentTxHash) // tx +1
if cachedTx == nil {
// there was an error, stop processing the stack
return errors.Wrapf(tangle.ErrTransactionNotFound, "hash: %s", currentTxHash.Trytes())
}

if !traverse {
cachedTx.Release(forceRelease) // tx -1
continue
}
defer cachedTx.Release(forceRelease) // tx -1

// consume the tx
if err := consumer(cachedTx.Retain()); err != nil { // tx +1
cachedTx.Release(forceRelease) // tx -1
return err
}
cachedTx.Release(forceRelease) // tx -1
// check condition to decide if tx should be consumed and traversed
traverse, err := condition(cachedTx.Retain()) // tx + 1
if err != nil {
// there was an error, stop processing the stack
return err
}

for _, approverHash := range tangle.GetApproverHashes(hornet.Hash(txHash), forceRelease) {
if !traverse {
// transaction will not get consumed and approvers are not traversed
return nil
}

if _, checked := processed[string(approverHash)]; checked {
continue
}
// consume the transaction
if err := consumer(cachedTx.Retain()); err != nil { // tx +1
// there was an error, stop processing the stack
return err
}

processed[string(approverHash)] = struct{}{}
for _, approverHash := range tangle.GetApproverHashes(currentTxHash, forceRelease) {
if _, approverDiscovered := discovered[string(approverHash)]; approverDiscovered {
// approver was already discovered
continue
}

cachedApproverTx := tangle.GetCachedTransactionOrNil(approverHash) // approver +1
if cachedApproverTx == nil {
return errors.Wrapf(tangle.ErrTransactionNotFound, "hash: %s", approverHash.Trytes())
}
// traverse the approver
discovered[string(approverHash)] = struct{}{}
stack.PushBack(approverHash)
}

// do not force release since it is loaded again
cachedApproverTx.Release() // approver -1
return nil
}

txsToTraverse[string(approverHash)] = struct{}{}
}
// TraverseApprovers starts to traverse the approvers (future cone) of the given start transaction until
// the traversal stops due to no more transactions passing the given condition.
// It is unsorted BFS because the approvers are not ordered in the database.
func TraverseApprovers(startTxHash hornet.Hash, condition Predicate, consumer Consumer, forceRelease bool, abortSignal <-chan struct{}) error {

stack := list.New()
stack.PushFront(startTxHash)

discovered := make(map[string]struct{})
discovered[string(startTxHash)] = struct{}{}

for stack.Len() > 0 {
if err := processStackApprovers(stack, discovered, condition, consumer, forceRelease, abortSignal); err != nil {
return err
}
}

Expand Down
Loading

0 comments on commit 24d7da6

Please sign in to comment.