github.com/core-coin/go-core/v2@v2.1.9/les/lespay/server/balance_tracker.go (about)

     1  // Copyright 2020 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-core library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package server
    18  
    19  import (
    20  	"reflect"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/core-coin/go-core/v2/xcbdb"
    25  
    26  	"github.com/core-coin/go-core/v2/common/mclock"
    27  	"github.com/core-coin/go-core/v2/les/utils"
    28  	"github.com/core-coin/go-core/v2/p2p/enode"
    29  	"github.com/core-coin/go-core/v2/p2p/nodestate"
    30  )
    31  
    32  const (
    33  	posThreshold             = 1000000         // minimum positive balance that is persisted in the database
    34  	negThreshold             = 1000000         // minimum negative balance that is persisted in the database
    35  	persistExpirationRefresh = time.Minute * 5 // refresh period of the token expiration persistence
    36  )
    37  
    38  // BalanceTrackerSetup contains node state flags and fields used by BalanceTracker
    39  type BalanceTrackerSetup struct {
    40  	// controlled by PriorityPool
    41  	PriorityFlag, UpdateFlag nodestate.Flags
    42  	BalanceField             nodestate.Field
    43  	// external connections
    44  	connAddressField, capacityField nodestate.Field
    45  }
    46  
    47  // NewBalanceTrackerSetup creates a new BalanceTrackerSetup and initializes the fields
    48  // and flags controlled by BalanceTracker
    49  func NewBalanceTrackerSetup(setup *nodestate.Setup) BalanceTrackerSetup {
    50  	return BalanceTrackerSetup{
    51  		// PriorityFlag is set if the node has a positive balance
    52  		PriorityFlag: setup.NewFlag("priorityNode"),
    53  		// UpdateFlag set and then immediately reset if the balance has been updated and
    54  		// therefore priority is suddenly changed
    55  		UpdateFlag: setup.NewFlag("balanceUpdate"),
    56  		// BalanceField contains the NodeBalance struct which implements nodePriority,
    57  		// allowing on-demand priority calculation and future priority estimation
    58  		BalanceField: setup.NewField("balance", reflect.TypeOf(&NodeBalance{})),
    59  	}
    60  }
    61  
    62  // Connect sets the fields used by BalanceTracker as an input
    63  func (bts *BalanceTrackerSetup) Connect(connAddressField, capacityField nodestate.Field) {
    64  	bts.connAddressField = connAddressField
    65  	bts.capacityField = capacityField
    66  }
    67  
    68  // BalanceTracker tracks positive and negative balances for connected nodes.
    69  // After connAddressField is set externally, a NodeBalance is created and previous
    70  // balance values are loaded from the database. Both balances are exponentially expired
    71  // values. Costs are deducted from the positive balance if present, otherwise added to
    72  // the negative balance. If the capacity is non-zero then a time cost is applied
    73  // continuously while individual request costs are applied immediately.
    74  // The two balances are translated into a single priority value that also depends
    75  // on the actual capacity.
    76  type BalanceTracker struct {
    77  	BalanceTrackerSetup
    78  	clock              mclock.Clock
    79  	lock               sync.Mutex
    80  	ns                 *nodestate.NodeStateMachine
    81  	ndb                *nodeDB
    82  	posExp, negExp     utils.ValueExpirer
    83  	posExpTC, negExpTC uint64
    84  
    85  	active, inactive utils.ExpiredValue
    86  	balanceTimer     *utils.UpdateTimer
    87  	quit             chan struct{}
    88  }
    89  
    90  // NewBalanceTracker creates a new BalanceTracker
    91  func NewBalanceTracker(ns *nodestate.NodeStateMachine, setup BalanceTrackerSetup, db xcbdb.KeyValueStore, clock mclock.Clock, posExp, negExp utils.ValueExpirer) *BalanceTracker {
    92  	ndb := newNodeDB(db, clock)
    93  	bt := &BalanceTracker{
    94  		ns:                  ns,
    95  		BalanceTrackerSetup: setup,
    96  		ndb:                 ndb,
    97  		clock:               clock,
    98  		posExp:              posExp,
    99  		negExp:              negExp,
   100  		balanceTimer:        utils.NewUpdateTimer(clock, time.Second*10),
   101  		quit:                make(chan struct{}),
   102  	}
   103  	bt.ndb.forEachBalance(false, func(id enode.ID, balance utils.ExpiredValue) bool {
   104  		bt.inactive.AddExp(balance)
   105  		return true
   106  	})
   107  
   108  	ns.SubscribeField(bt.capacityField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
   109  		n, _ := ns.GetField(node, bt.BalanceField).(*NodeBalance)
   110  		if n == nil {
   111  			return
   112  		}
   113  
   114  		ov, _ := oldValue.(uint64)
   115  		nv, _ := newValue.(uint64)
   116  		if ov == 0 && nv != 0 {
   117  			n.activate()
   118  		}
   119  		if nv != 0 {
   120  			n.setCapacity(nv)
   121  		}
   122  		if ov != 0 && nv == 0 {
   123  			n.deactivate()
   124  		}
   125  	})
   126  	ns.SubscribeField(bt.connAddressField, func(node *enode.Node, state nodestate.Flags, oldValue, newValue interface{}) {
   127  		if newValue != nil {
   128  			ns.SetFieldSub(node, bt.BalanceField, bt.newNodeBalance(node, newValue.(string)))
   129  		} else {
   130  			ns.SetStateSub(node, nodestate.Flags{}, bt.PriorityFlag, 0)
   131  			if b, _ := ns.GetField(node, bt.BalanceField).(*NodeBalance); b != nil {
   132  				b.deactivate()
   133  			}
   134  			ns.SetFieldSub(node, bt.BalanceField, nil)
   135  		}
   136  	})
   137  
   138  	// The positive and negative balances of clients are stored in database
   139  	// and both of these decay exponentially over time. Delete them if the
   140  	// value is small enough.
   141  	bt.ndb.evictCallBack = bt.canDropBalance
   142  
   143  	go func() {
   144  		for {
   145  			select {
   146  			case <-clock.After(persistExpirationRefresh):
   147  				now := clock.Now()
   148  				bt.ndb.setExpiration(posExp.LogOffset(now), negExp.LogOffset(now))
   149  			case <-bt.quit:
   150  				return
   151  			}
   152  		}
   153  	}()
   154  	return bt
   155  }
   156  
   157  // Stop saves expiration offset and unsaved node balances and shuts BalanceTracker down
   158  func (bt *BalanceTracker) Stop() {
   159  	now := bt.clock.Now()
   160  	bt.ndb.setExpiration(bt.posExp.LogOffset(now), bt.negExp.LogOffset(now))
   161  	close(bt.quit)
   162  	bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
   163  		if n, ok := bt.ns.GetField(node, bt.BalanceField).(*NodeBalance); ok {
   164  			n.lock.Lock()
   165  			n.storeBalance(true, true)
   166  			n.lock.Unlock()
   167  			bt.ns.SetField(node, bt.BalanceField, nil)
   168  		}
   169  	})
   170  	bt.ndb.close()
   171  }
   172  
   173  // TotalTokenAmount returns the current total amount of service tokens in existence
   174  func (bt *BalanceTracker) TotalTokenAmount() uint64 {
   175  	bt.lock.Lock()
   176  	defer bt.lock.Unlock()
   177  
   178  	bt.balanceTimer.Update(func(_ time.Duration) bool {
   179  		bt.active = utils.ExpiredValue{}
   180  		bt.ns.ForEach(nodestate.Flags{}, nodestate.Flags{}, func(node *enode.Node, state nodestate.Flags) {
   181  			if n, ok := bt.ns.GetField(node, bt.BalanceField).(*NodeBalance); ok {
   182  				pos, _ := n.GetRawBalance()
   183  				bt.active.AddExp(pos)
   184  			}
   185  		})
   186  		return true
   187  	})
   188  	total := bt.active
   189  	total.AddExp(bt.inactive)
   190  	return total.Value(bt.posExp.LogOffset(bt.clock.Now()))
   191  }
   192  
   193  // GetPosBalanceIDs lists node IDs with an associated positive balance
   194  func (bt *BalanceTracker) GetPosBalanceIDs(start, stop enode.ID, maxCount int) (result []enode.ID) {
   195  	return bt.ndb.getPosBalanceIDs(start, stop, maxCount)
   196  }
   197  
   198  // SetExpirationTCs sets positive and negative token expiration time constants.
   199  // Specified in seconds, 0 means infinite (no expiration).
   200  func (bt *BalanceTracker) SetExpirationTCs(pos, neg uint64) {
   201  	bt.lock.Lock()
   202  	defer bt.lock.Unlock()
   203  
   204  	bt.posExpTC, bt.negExpTC = pos, neg
   205  	now := bt.clock.Now()
   206  	if pos > 0 {
   207  		bt.posExp.SetRate(now, 1/float64(pos*uint64(time.Second)))
   208  	} else {
   209  		bt.posExp.SetRate(now, 0)
   210  	}
   211  	if neg > 0 {
   212  		bt.negExp.SetRate(now, 1/float64(neg*uint64(time.Second)))
   213  	} else {
   214  		bt.negExp.SetRate(now, 0)
   215  	}
   216  }
   217  
   218  // GetExpirationTCs returns the current positive and negative token expiration
   219  // time constants
   220  func (bt *BalanceTracker) GetExpirationTCs() (pos, neg uint64) {
   221  	bt.lock.Lock()
   222  	defer bt.lock.Unlock()
   223  
   224  	return bt.posExpTC, bt.negExpTC
   225  }
   226  
   227  // newNodeBalance loads balances from the database and creates a NodeBalance instance
   228  // for the given node. It also sets the PriorityFlag and adds balanceCallbackZero if
   229  // the node has a positive balance.
   230  // Note: this function should run inside a NodeStateMachine operation
   231  func (bt *BalanceTracker) newNodeBalance(node *enode.Node, negBalanceKey string) *NodeBalance {
   232  	pb := bt.ndb.getOrNewBalance(node.ID().Bytes(), false)
   233  	nb := bt.ndb.getOrNewBalance([]byte(negBalanceKey), true)
   234  	n := &NodeBalance{
   235  		bt:          bt,
   236  		node:        node,
   237  		connAddress: negBalanceKey,
   238  		balance:     balance{pos: pb, neg: nb},
   239  		initTime:    bt.clock.Now(),
   240  		lastUpdate:  bt.clock.Now(),
   241  	}
   242  	for i := range n.callbackIndex {
   243  		n.callbackIndex[i] = -1
   244  	}
   245  	if n.checkPriorityStatus() {
   246  		n.bt.ns.SetStateSub(n.node, n.bt.PriorityFlag, nodestate.Flags{}, 0)
   247  	}
   248  	return n
   249  }
   250  
   251  // storeBalance stores either a positive or a negative balance in the database
   252  func (bt *BalanceTracker) storeBalance(id []byte, neg bool, value utils.ExpiredValue) {
   253  	if bt.canDropBalance(bt.clock.Now(), neg, value) {
   254  		bt.ndb.delBalance(id, neg) // balance is small enough, drop it directly.
   255  	} else {
   256  		bt.ndb.setBalance(id, neg, value)
   257  	}
   258  }
   259  
   260  // canDropBalance tells whether a positive or negative balance is below the threshold
   261  // and therefore can be dropped from the database
   262  func (bt *BalanceTracker) canDropBalance(now mclock.AbsTime, neg bool, b utils.ExpiredValue) bool {
   263  	if neg {
   264  		return b.Value(bt.negExp.LogOffset(now)) <= negThreshold
   265  	}
   266  	return b.Value(bt.posExp.LogOffset(now)) <= posThreshold
   267  }
   268  
   269  // updateTotalBalance adjusts the total balance after executing given callback.
   270  func (bt *BalanceTracker) updateTotalBalance(n *NodeBalance, callback func() bool) {
   271  	bt.lock.Lock()
   272  	defer bt.lock.Unlock()
   273  
   274  	n.lock.Lock()
   275  	defer n.lock.Unlock()
   276  
   277  	original, active := n.balance.pos, n.active
   278  	if !callback() {
   279  		return
   280  	}
   281  	if active {
   282  		bt.active.SubExp(original)
   283  	} else {
   284  		bt.inactive.SubExp(original)
   285  	}
   286  	if n.active {
   287  		bt.active.AddExp(n.balance.pos)
   288  	} else {
   289  		bt.inactive.AddExp(n.balance.pos)
   290  	}
   291  }