github.com/juliankolbe/go-ethereum@v1.9.992/les/vflux/server/balance_tracker.go (about)

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