[NOD-1417] Implement reachability (#964)

* [NOD-1417] Implement reachability

* [NOD-1417] Rename package name

* [NOD-1417] Add UpdateReindexRoot to interface api

* [NOD-1417] Remove redundant type

* [NOD-1417] Rename reachabilityTreeManager/reachabilityTree to reachabilityManager

* [NOD-1417] Fix typo

* [NOD-1417] Remove redundant copyright message

* [NOD-1417] Fix comment
This commit is contained in:
Ori Newman 2020-10-28 03:19:50 -07:00 committed by GitHub
parent a132f55302
commit a436b30ebf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1629 additions and 55 deletions

View File

@ -25,7 +25,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/processes/ghostdagmanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/pastmediantimemanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/pruningmanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/reachabilitytree"
"github.com/kaspanet/kaspad/domain/consensus/processes/reachabilitymanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/transactionvalidator"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/db/database"
@ -56,12 +56,14 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db database.Database
dbManager := dbmanager.New(db)
// Processes
reachabilityTree := reachabilitytree.New(
reachabilityManager := reachabilitymanager.New(
dbManager,
ghostdagDataStore,
blockRelationStore,
reachabilityDataStore)
dagTopologyManager := dagtopologymanager.New(
dbManager,
reachabilityTree,
reachabilityManager,
blockRelationStore)
ghostdagManager := ghostdagmanager.New(
dbManager,
@ -137,7 +139,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db database.Database
pruningManager,
blockValidator,
dagTopologyManager,
reachabilityTree,
reachabilityManager,
difficultyManager,
pastMedianTimeManager,
ghostdagManager,

View File

@ -8,4 +8,5 @@ type ReachabilityTree interface {
AddBlock(blockHash *externalapi.DomainHash) error
IsReachabilityTreeAncestorOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error)
IsDAGAncestorOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error)
UpdateReindexRoot(selectedTip *externalapi.DomainHash) error
}

View File

@ -1,5 +1,7 @@
package model
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// ReachabilityData holds the set of data required to answer
// reachability queries
type ReachabilityData struct {
@ -23,8 +25,8 @@ type ReachabilityData struct {
// case, and so reindexing should always succeed unless more than
// 2^64 blocks are added to the DAG/tree.
type ReachabilityTreeNode struct {
Children []*ReachabilityTreeNode
Parent *ReachabilityTreeNode
Children []*externalapi.DomainHash
Parent *externalapi.DomainHash
// interval is the index interval containing all intervals of
// blocks in this node's subtree
@ -39,12 +41,6 @@ type ReachabilityInterval struct {
End uint64
}
// OrderedTreeNodeSet is an ordered set of reachabilityTreeNodes
// Note that this type does not validate order validity. It's the
// responsibility of the caller to construct instances of this
// type properly.
type OrderedTreeNodeSet []*ReachabilityTreeNode
// FutureCoveringTreeNodeSet represents a collection of blocks in the future of
// a certain block. Once a block B is added to the DAG, every block A_i in
// B's selected parent anticone must register B in its FutureCoveringTreeNodeSet. This allows
@ -58,4 +54,4 @@ type OrderedTreeNodeSet []*ReachabilityTreeNode
//
// See insertNode, hasAncestorOf, and reachabilityTree.isInPast for further
// details.
type FutureCoveringTreeNodeSet OrderedTreeNodeSet
type FutureCoveringTreeNodeSet []*externalapi.DomainHash

View File

@ -0,0 +1,59 @@
package reachabilitymanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
func (rt *reachabilityManager) data(blockHash *externalapi.DomainHash) (*model.ReachabilityData, error) {
return rt.reachabilityDataStore.ReachabilityData(rt.databaseContext, blockHash)
}
func (rt *reachabilityManager) futureCoveringSet(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
data, err := rt.data(blockHash)
if err != nil {
return nil, err
}
return data.FutureCoveringSet, nil
}
func (rt *reachabilityManager) treeNode(blockHash *externalapi.DomainHash) (*model.ReachabilityTreeNode, error) {
data, err := rt.data(blockHash)
if err != nil {
return nil, err
}
return data.TreeNode, nil
}
func (rt *reachabilityManager) interval(blockHash *externalapi.DomainHash) (*model.ReachabilityInterval, error) {
treeNode, err := rt.treeNode(blockHash)
if err != nil {
return nil, err
}
return treeNode.Interval, nil
}
func (rt *reachabilityManager) children(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
data, err := rt.data(blockHash)
if err != nil {
return nil, err
}
return data.TreeNode.Children, nil
}
func (rt *reachabilityManager) parent(blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error) {
data, err := rt.data(blockHash)
if err != nil {
return nil, err
}
return data.TreeNode.Parent, nil
}
func (rt *reachabilityManager) reindexRoot() (*externalapi.DomainHash, error) {
return rt.reachabilityDataStore.ReachabilityReindexRoot(rt.databaseContext)
}

View File

@ -0,0 +1,132 @@
package reachabilitymanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
// futureCoveringTreeNodeSet represents a collection of blocks in the future of
// a certain block. Once a block B is added to the DAG, every block A_i in
// B's selected parent anticone must register B in its futureCoveringTreeNodeSet. This allows
// to relatively quickly (O(log(|futureCoveringTreeNodeSet|))) query whether B
// is a descendent (is in the "future") of any block that previously
// registered it.
//
// Note that futureCoveringTreeNodeSet is meant to be queried only if B is not
// a reachability tree descendant of the block in question, as reachability
// tree queries are always O(1).
//
// See insertNode, hasAncestorOf, and reachabilityTree.isInPast for further
// details.
type futureCoveringTreeNodeSet orderedTreeNodeSet
// insertToFutureCoveringSet inserts the given block into this node's FutureCoveringSet
// while keeping it ordered by interval.
// If a block B ∈ node.FutureCoveringSet exists such that its interval
// contains block's interval, block need not be added. If block's
// interval contains B's interval, it replaces it.
//
// Notes:
// * Intervals never intersect unless one contains the other
// (this follows from the tree structure and the indexing rule).
// * Since node.FutureCoveringSet is kept ordered, a binary search can be
// used for insertion/queries.
// * Although reindexing may change a block's interval, the
// is-superset relation will by definition
// be always preserved.
func (rt *reachabilityManager) insertToFutureCoveringSet(node, futureNode *externalapi.DomainHash) error {
futureCoveringSet, err := rt.futureCoveringSet(node)
if err != nil {
return err
}
ancestorIndex, ok, err := rt.findAncestorIndexOfNode(futureCoveringSet, futureNode)
if err != nil {
return err
}
if !ok {
newSet := append([]*externalapi.DomainHash{futureNode}, futureCoveringSet...)
err := rt.stageFutureCoveringSet(node, newSet)
if err != nil {
return err
}
return nil
}
candidate := futureCoveringSet[ancestorIndex]
candidateIsAncestorOfFutureNode, err := rt.IsReachabilityTreeAncestorOf(candidate, futureNode)
if err != nil {
return err
}
if candidateIsAncestorOfFutureNode {
// candidate is an ancestor of futureNode, no need to insert
return nil
}
futureNodeIsAncestorOfCandidate, err := rt.IsReachabilityTreeAncestorOf(futureNode, candidate)
if err != nil {
return err
}
if futureNodeIsAncestorOfCandidate {
// futureNode is an ancestor of candidate, and can thus replace it
newSet := make([]*externalapi.DomainHash, len(futureCoveringSet))
copy(newSet, futureCoveringSet)
newSet[ancestorIndex] = futureNode
return rt.stageFutureCoveringSet(node, newSet)
}
// Insert futureNode in the correct index to maintain futureCoveringTreeNodeSet as
// a sorted-by-interval list.
// Note that ancestorIndex might be equal to len(futureCoveringTreeNodeSet)
left := futureCoveringSet[:ancestorIndex+1]
right := append([]*externalapi.DomainHash{futureNode}, futureCoveringSet[ancestorIndex+1:]...)
newSet := append(left, right...)
return rt.stageFutureCoveringSet(node, newSet)
}
// futureCoveringSetHasAncestorOf resolves whether the given node `other` is in the subtree of
// any node in this.FutureCoveringSet.
// See insertNode method for the complementary insertion behavior.
//
// Like the insert method, this method also relies on the fact that
// this.FutureCoveringSet is kept ordered by interval to efficiently perform a
// binary search over this.FutureCoveringSet and answer the query in
// O(log(|futureCoveringTreeNodeSet|)).
func (rt *reachabilityManager) futureCoveringSetHasAncestorOf(this, other *externalapi.DomainHash) (bool, error) {
futureCoveringSet, err := rt.futureCoveringSet(this)
if err != nil {
return false, err
}
ancestorIndex, ok, err := rt.findAncestorIndexOfNode(futureCoveringSet, other)
if err != nil {
return false, err
}
if !ok {
// No candidate to contain other
return false, nil
}
candidate := futureCoveringSet[ancestorIndex]
return rt.IsReachabilityTreeAncestorOf(candidate, other)
}
// futureCoveringSetString returns a string representation of the intervals in this futureCoveringSet.
func (rt *reachabilityManager) futureCoveringSetString(futureCoveringSet []*externalapi.DomainHash) (string, error) {
intervalsString := ""
for _, node := range futureCoveringSet {
nodeInterval, err := rt.interval(node)
if err != nil {
return "", err
}
intervalsString += intervalString(nodeInterval)
}
return intervalsString, nil
}

View File

@ -0,0 +1,123 @@
package reachabilitymanager
import (
"fmt"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/pkg/errors"
"math"
)
func newReachabilityInterval(start uint64, end uint64) *model.ReachabilityInterval {
return &model.ReachabilityInterval{Start: start, End: end}
}
// intervalSize returns the size of this interval. Note that intervals are
// inclusive from both sides.
func intervalSize(ri *model.ReachabilityInterval) uint64 {
return ri.End - ri.Start + 1
}
// intervalSplitInHalf splits this interval by a fraction of 0.5.
// See splitFraction for further details.
func intervalSplitInHalf(ri *model.ReachabilityInterval) (
left *model.ReachabilityInterval, right *model.ReachabilityInterval, err error) {
return intervalSplitFraction(ri, 0.5)
}
// intervalSplitFraction splits this interval to two parts such that their
// union is equal to the original interval and the first (left) part
// contains the given fraction of the original interval's size.
// Note: if the split results in fractional parts, this method rounds
// the first part up and the last part down.
func intervalSplitFraction(ri *model.ReachabilityInterval, fraction float64) (
left *model.ReachabilityInterval, right *model.ReachabilityInterval, err error) {
if fraction < 0 || fraction > 1 {
return nil, nil, errors.Errorf("fraction must be between 0 and 1")
}
if intervalSize(ri) == 0 {
return nil, nil, errors.Errorf("cannot split an empty interval")
}
allocationSize := uint64(math.Ceil(float64(intervalSize(ri)) * fraction))
left = newReachabilityInterval(ri.Start, ri.Start+allocationSize-1)
right = newReachabilityInterval(ri.Start+allocationSize, ri.End)
return left, right, nil
}
// intervalSplitExact splits this interval to exactly |sizes| parts where
// |part_i| = sizes[i]. This method expects sum(sizes) to be exactly
// equal to the interval's size.
func intervalSplitExact(ri *model.ReachabilityInterval, sizes []uint64) ([]*model.ReachabilityInterval, error) {
sizesSum := uint64(0)
for _, size := range sizes {
sizesSum += size
}
if sizesSum != intervalSize(ri) {
return nil, errors.Errorf("sum of sizes must be equal to the interval's size")
}
intervals := make([]*model.ReachabilityInterval, len(sizes))
start := ri.Start
for i, size := range sizes {
intervals[i] = newReachabilityInterval(start, start+size-1)
start += size
}
return intervals, nil
}
// intervalSplitWithExponentialBias splits this interval to |sizes| parts
// by the allocation rule described below. This method expects sum(sizes)
// to be smaller or equal to the interval's size. Every part_i is
// allocated at least sizes[i] capacity. The remaining budget is
// split by an exponentially biased rule described below.
//
// This rule follows the GHOSTDAG protocol behavior where the child
// with the largest subtree is expected to dominate the competition
// for new blocks and thus grow the most. However, we may need to
// add slack for non-largest subtrees in order to make CPU reindexing
// attacks unworthy.
func intervalSplitWithExponentialBias(ri *model.ReachabilityInterval, sizes []uint64) ([]*model.ReachabilityInterval, error) {
intervalSize := intervalSize(ri)
sizesSum := uint64(0)
for _, size := range sizes {
sizesSum += size
}
if sizesSum > intervalSize {
return nil, errors.Errorf("sum of sizes must be less than or equal to the interval's size")
}
if sizesSum == intervalSize {
return intervalSplitExact(ri, sizes)
}
// Add a fractional bias to every size in the given sizes
totalBias := intervalSize - sizesSum
remainingBias := totalBias
biasedSizes := make([]uint64, len(sizes))
fractions := exponentialFractions(sizes)
for i, fraction := range fractions {
var bias uint64
if i == len(fractions)-1 {
bias = remainingBias
} else {
bias = uint64(math.Round(float64(totalBias) * fraction))
if bias > remainingBias {
bias = remainingBias
}
}
biasedSizes[i] = sizes[i] + bias
remainingBias -= bias
}
return intervalSplitExact(ri, biasedSizes)
}
// intervalContains returns true if ri contains other.
func intervalContains(ri *model.ReachabilityInterval, other *model.ReachabilityInterval) bool {
return ri.Start <= other.Start && other.End <= ri.End
}
// intervalString returns a string representation of the interval.
func intervalString(ri *model.ReachabilityInterval) string {
return fmt.Sprintf("[%d,%d]", ri.Start, ri.End)
}

View File

@ -0,0 +1,7 @@
package reachabilitymanager
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
)
var log, _ = logger.Get(logger.SubsystemTags.REAC)

View File

@ -0,0 +1,79 @@
package reachabilitymanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
// orderedTreeNodeSet is an ordered set of model.ReachabilityTreeNodes
// Note that this type does not validate order validity. It's the
// responsibility of the caller to construct instances of this
// type properly.
type orderedTreeNodeSet []*externalapi.DomainHash
// findAncestorOfNode finds the reachability tree ancestor of `node`
// among the nodes in `tns`.
func (rt *reachabilityManager) findAncestorOfNode(tns orderedTreeNodeSet, node *externalapi.DomainHash) (*externalapi.DomainHash, bool) {
ancestorIndex, ok, err := rt.findAncestorIndexOfNode(tns, node)
if err != nil {
return nil, false
}
if !ok {
return nil, false
}
return tns[ancestorIndex], true
}
// findAncestorIndexOfNode finds the index of the reachability tree
// ancestor of `node` among the nodes in `tns`. It does so by finding
// the index of the block with the maximum start that is below the
// given block.
func (rt *reachabilityManager) findAncestorIndexOfNode(tns orderedTreeNodeSet, node *externalapi.DomainHash) (int, bool, error) {
treeNode, err := rt.treeNode(node)
if err != nil {
return 0, false, err
}
blockInterval := treeNode.Interval
end := blockInterval.End
low := 0
high := len(tns)
for low < high {
middle := (low + high) / 2
middleInterval, err := rt.interval(tns[middle])
if err != nil {
return 0, false, err
}
if end < middleInterval.Start {
high = middle
} else {
low = middle + 1
}
}
if low == 0 {
return 0, false, nil
}
return low - 1, true, nil
}
func (rt *reachabilityManager) propagateIntervals(tns orderedTreeNodeSet, intervals []*model.ReachabilityInterval,
subtreeSizeMaps []map[externalapi.DomainHash]uint64) error {
for i, node := range tns {
err := rt.stageInterval(node, intervals[i])
if err != nil {
return err
}
subtreeSizeMap := subtreeSizeMaps[i]
err = rt.propagateInterval(node, subtreeSizeMap)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,30 @@
package reachabilitymanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
// IsDAGAncestorOf returns true if blockHashA is an ancestor of
// blockHashB in the DAG.
//
// Note: this method will return true if blockHashA == blockHashB
// The complexity of this method is O(log(|this.futureCoveringTreeNodeSet|))
func (rt *reachabilityManager) IsDAGAncestorOf(blockHashA, blockHashB *externalapi.DomainHash) (bool, error) {
// Check if this node is a reachability tree ancestor of the
// other node
isReachabilityTreeAncestor, err := rt.IsReachabilityTreeAncestorOf(blockHashA, blockHashB)
if err != nil {
return false, err
}
if isReachabilityTreeAncestor {
return true, nil
}
// Otherwise, use previously registered future blocks to complete the
// reachability test
return rt.futureCoveringSetHasAncestorOf(blockHashA, blockHashB)
}
func (rt *reachabilityManager) UpdateReindexRoot(selectedTip *externalapi.DomainHash) error {
return rt.updateReindexRoot(selectedTip)
}

View File

@ -0,0 +1,77 @@
package reachabilitymanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
// reachabilityManager maintains a structure that allows to answer
// reachability queries in sub-linear time
type reachabilityManager struct {
databaseContext model.DBReader
blockRelationStore model.BlockRelationStore
reachabilityDataStore model.ReachabilityDataStore
ghostdagDataStore model.GHOSTDAGDataStore
}
// New instantiates a new reachabilityManager
func New(
databaseContext model.DBReader,
ghostdagDataStore model.GHOSTDAGDataStore,
blockRelationStore model.BlockRelationStore,
reachabilityDataStore model.ReachabilityDataStore,
) model.ReachabilityTree {
return &reachabilityManager{
databaseContext: databaseContext,
ghostdagDataStore: ghostdagDataStore,
blockRelationStore: blockRelationStore,
reachabilityDataStore: reachabilityDataStore,
}
}
// AddBlock adds the block with the given blockHash into the reachability tree.
func (rt *reachabilityManager) AddBlock(blockHash *externalapi.DomainHash) error {
// Allocate a new reachability tree node
newTreeNode := newReachabilityTreeNode()
err := rt.stageTreeNode(blockHash, newTreeNode)
if err != nil {
return err
}
ghostdagData, err := rt.ghostdagDataStore.Get(rt.databaseContext, blockHash)
if err != nil {
return err
}
// If this is the genesis node, simply initialize it and return
if ghostdagData.SelectedParent == nil {
rt.stageReindexRoot(blockHash)
return nil
}
reindexRoot, err := rt.reindexRoot()
if err != nil {
return err
}
// Insert the node into the selected parent's reachability tree
err = rt.addChild(ghostdagData.SelectedParent, blockHash, reindexRoot)
if err != nil {
return err
}
// Add the block to the futureCoveringSets of all the blocks
// in the merget set
mergeSet := make([]*externalapi.DomainHash, len(ghostdagData.MergeSetBlues)+len(ghostdagData.MergeSetReds))
copy(mergeSet, ghostdagData.MergeSetBlues)
copy(mergeSet[len(ghostdagData.MergeSetBlues):], ghostdagData.MergeSetReds)
for _, current := range mergeSet {
err = rt.insertToFutureCoveringSet(current, blockHash)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,66 @@
package reachabilitymanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
func (rt *reachabilityManager) stageData(blockHash *externalapi.DomainHash, data *model.ReachabilityData) {
rt.reachabilityDataStore.StageReachabilityData(blockHash, data)
}
func (rt *reachabilityManager) stageFutureCoveringSet(blockHash *externalapi.DomainHash, set model.FutureCoveringTreeNodeSet) error {
data, err := rt.data(blockHash)
if err != nil {
return err
}
data.FutureCoveringSet = set
rt.reachabilityDataStore.StageReachabilityData(blockHash, data)
return nil
}
func (rt *reachabilityManager) stageTreeNode(blockHash *externalapi.DomainHash, node *model.ReachabilityTreeNode) error {
data, err := rt.data(blockHash)
if err != nil {
return err
}
data.TreeNode = node
rt.reachabilityDataStore.StageReachabilityData(blockHash, data)
return nil
}
func (rt *reachabilityManager) stageReindexRoot(blockHash *externalapi.DomainHash) {
rt.reachabilityDataStore.StageReachabilityReindexRoot(blockHash)
}
func (rt *reachabilityManager) addChildAndStage(node, child *externalapi.DomainHash) error {
nodeData, err := rt.data(node)
if err != nil {
return err
}
nodeData.TreeNode.Children = append(nodeData.TreeNode.Children, child)
return rt.stageTreeNode(node, nodeData.TreeNode)
}
func (rt *reachabilityManager) stageParent(node, parent *externalapi.DomainHash) error {
treeNode, err := rt.treeNode(node)
if err != nil {
return err
}
treeNode.Parent = parent
return rt.stageTreeNode(node, treeNode)
}
func (rt *reachabilityManager) stageInterval(node *externalapi.DomainHash, interval *model.ReachabilityInterval) error {
treeNode, err := rt.treeNode(node)
if err != nil {
return err
}
treeNode.Interval = interval
return rt.stageTreeNode(node, treeNode)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +0,0 @@
package reachabilitytree
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
// reachabilityTree maintains a structure that allows to answer
// reachability queries in sub-linear time
type reachabilityTree struct {
blockRelationStore model.BlockRelationStore
reachabilityDataStore model.ReachabilityDataStore
}
// New instantiates a new ReachabilityTree
func New(
blockRelationStore model.BlockRelationStore,
reachabilityDataStore model.ReachabilityDataStore) model.ReachabilityTree {
return &reachabilityTree{
blockRelationStore: blockRelationStore,
reachabilityDataStore: reachabilityDataStore,
}
}
// AddBlock adds the block with the given blockHash into the reachability tree.
func (rt *reachabilityTree) AddBlock(blockHash *externalapi.DomainHash) error {
return nil
}
// IsReachabilityTreeAncestorOf returns true if blockHashA is an
// ancestor of blockHashB in the reachability tree. Note that this
// does not necessarily mean that it isn't its ancestor in the DAG.
func (rt *reachabilityTree) IsReachabilityTreeAncestorOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error) {
return false, nil
}
// IsDAGAncestorOf returns true if blockHashA is an ancestor of
// blockHashB in the DAG.
func (rt *reachabilityTree) IsDAGAncestorOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error) {
return false, nil
}

View File

@ -54,6 +54,7 @@ var (
snvrLog = BackendLog.Logger("SNVR")
ibdsLog = BackendLog.Logger("IBDS")
wsvcLog = BackendLog.Logger("WSVC")
reacLog = BackendLog.Logger("REAC")
)
// SubsystemTags is an enum of all sub system tags
@ -85,7 +86,8 @@ var SubsystemTags = struct {
DNSS,
SNVR,
IBDS,
WSVC string
WSVC,
REAC string
}{
ADXR: "ADXR",
AMGR: "AMGR",
@ -115,6 +117,7 @@ var SubsystemTags = struct {
SNVR: "SNVR",
IBDS: "IBDS",
WSVC: "WSVC",
REAC: "REAC",
}
// subsystemLoggers maps each subsystem identifier to its associated logger.
@ -147,6 +150,7 @@ var subsystemLoggers = map[string]*Logger{
SubsystemTags.SNVR: snvrLog,
SubsystemTags.IBDS: ibdsLog,
SubsystemTags.WSVC: wsvcLog,
SubsystemTags.REAC: reacLog,
}
// InitLog attaches log file and error log file to the backend log.