gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/workeraccount.go (about)

     1  package renter
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"math/big"
     8  	"net"
     9  	"runtime"
    10  	"strings"
    11  	"sync"
    12  	"sync/atomic"
    13  	"time"
    14  
    15  	"gitlab.com/SkynetLabs/skyd/build"
    16  	"gitlab.com/SkynetLabs/skyd/persist"
    17  	"gitlab.com/SkynetLabs/skyd/skymodules"
    18  	"gitlab.com/SkynetLabs/skyd/skymodules/renter/contractor"
    19  	"go.sia.tech/siad/crypto"
    20  	"go.sia.tech/siad/modules"
    21  	"go.sia.tech/siad/modules/host"
    22  	"go.sia.tech/siad/types"
    23  
    24  	"gitlab.com/NebulousLabs/errors"
    25  	"gitlab.com/NebulousLabs/fastrand"
    26  	"gitlab.com/NebulousLabs/siamux"
    27  )
    28  
    29  const (
    30  	// accountBalanceDriftPercentageThreshold is the percentage threshold, in
    31  	// relation to the balance target, at which we consider the drift to be
    32  	// large enough to register an alert for it
    33  	accountBalanceDriftPercentageThreshold = .1
    34  
    35  	// withdrawalValidityPeriod defines the period (in blocks) a withdrawal
    36  	// message remains spendable after it has been created. Together with the
    37  	// current block height at time of creation, this period makes up the
    38  	// WithdrawalMessage's expiry height.
    39  	withdrawalValidityPeriod = 6
    40  
    41  	// fundAccountGougingPercentageThreshold is the percentage threshold, in
    42  	// relation to the allowance, at which we consider the cost of funding an
    43  	// account to be too expensive. E.g. the cost of funding the account as many
    44  	// times as necessary to spend the total allowance should never exceed 1% of
    45  	// the total allowance.
    46  	fundAccountGougingPercentageThreshold = .01
    47  )
    48  
    49  const (
    50  	// the following categories are constants used to determine the
    51  	// corresponding spending field in the account's spending details whenever
    52  	// we pay for an rpc request using the ephemeral account as payment method.
    53  	categoryErr spendingCategory = iota
    54  	categoryAccountBalance
    55  	categoryDownload
    56  	categoryRegistryRead
    57  	categoryRegistryWrite
    58  	categoryRepairDownload
    59  	categoryRepairUpload
    60  	categorySnapshotDownload
    61  	categorySnapshotUpload
    62  	categorySubscription
    63  	categoryUpdatePriceTable
    64  	categoryUpload
    65  )
    66  
    67  var (
    68  	// accountIdleCheckFrequency establishes how frequently the sync function
    69  	// should check whether the worker is idle. A relatively high frequency is
    70  	// okay, because this function only runs while the worker is frozen and
    71  	// expecting to perform an expensive sync operation.
    72  	accountIdleCheckFrequency = build.Select(build.Var{
    73  		Dev:      time.Second * 4,
    74  		Standard: time.Second * 5,
    75  		Testing:  time.Second * 3,
    76  	}).(time.Duration)
    77  
    78  	// accountSyncRandWaitMilliseconds defines the number of random milliseconds
    79  	// that are added to the wait time. Randomness is used to ensure that
    80  	// workers are not all syncing at the same time - the sync operation freezes
    81  	// workers. This number should be larger than the expected amount of time a
    82  	// worker will be frozen multiplied by the total number of workers.
    83  	accountSyncRandWaitMilliseconds = build.Select(build.Var{
    84  		Dev:      int(1e3 * 60),          // 1 minute
    85  		Standard: int(3 * 1e3 * 60 * 60), // 3 hours
    86  		Testing:  int(1e3 * 15),          // 15 seconds - needs to be long even in testing
    87  	}).(int)
    88  
    89  	// accountSyncMinWaitTime defines the minimum amount of time that a worker
    90  	// will wait between performing sync checks. This should be a large number,
    91  	// on the order of the amount of time a worker is expected to be frozen
    92  	// multiplied by the total number of workers.
    93  	accountSyncMinWaitTime = build.Select(build.Var{
    94  		Dev:      time.Minute,
    95  		Standard: 60 * time.Minute, // 1 hour
    96  		Testing:  10 * time.Second, // needs to be long even in testing
    97  	}).(time.Duration)
    98  
    99  	// accountIdleMaxWait defines the max amount of time that the worker will
   100  	// wait to reach an idle state before firing a build.Critical and giving up
   101  	// on becoming idle. Generally this will indicate that somewhere in the
   102  	// worker code there is a job that is not timing out correctly.
   103  	accountIdleMaxWait = build.Select(build.Var{
   104  		Dev:      10 * time.Minute,
   105  		Standard: 40 * time.Minute,
   106  		Testing:  5 * time.Minute, // needs to be long even in testing
   107  	}).(time.Duration)
   108  )
   109  
   110  type (
   111  	// account represents a renter's ephemeral account on a host.
   112  	account struct {
   113  		// Information related to host communications.
   114  		staticID        modules.AccountID
   115  		staticHostKey   types.SiaPublicKey
   116  		staticSecretKey crypto.SecretKey
   117  
   118  		// Money has multiple states in an account, this is all the information
   119  		// we need to understand the current state of the account's balance and
   120  		// pending updates.
   121  		//
   122  		// The two drift fields keep track of the delta between our version of
   123  		// the balance and the host's version of the balance. We want to keep
   124  		// track of this drift as in the future we might add code that acts upon
   125  		// it and penalizes the host if we find they are cheating us, or
   126  		// behaving sub-optimally.
   127  		balance              types.Currency
   128  		balanceDriftPositive types.Currency
   129  		balanceDriftNegative types.Currency
   130  		pendingDeposits      types.Currency
   131  		pendingWithdrawals   types.Currency
   132  		negativeBalance      types.Currency
   133  
   134  		// hostBalance is a field in which we store the balance the host claims
   135  		// is in the account when we perform a sync. We can not reset our
   136  		// balance to that as we can not blindly trust the host, but we need to
   137  		// store it nonetheless to be able to accurately reflect the accrued
   138  		// positive or negative balance drift
   139  		//
   140  		// hostBalanceNegative allows the hostBalance to go negative
   141  		hostBalance         types.Currency
   142  		hostBalanceNegative types.Currency
   143  
   144  		// residue is the amount of money that is still left in the
   145  		// ephemeral account at the time the contract renews and the
   146  		// FundAccountCost resets, we need to store this field in order to
   147  		// properly represent the spending details in the next period.
   148  		residue types.Currency
   149  
   150  		// Spending details contain a breakdown of how much money from the
   151  		// ephemeral account got spent on what type of action. Examples of such
   152  		// actions are downloads, registry reads, registry writes, etc.
   153  		spending spendingDetails
   154  
   155  		// Error tracking.
   156  		recentErr         error
   157  		recentErrTime     time.Time
   158  		recentSuccessTime time.Time
   159  
   160  		// syncAt defines what time the renter should be syncing the account to
   161  		// the host.
   162  		syncAt time.Time
   163  
   164  		// forcedSyncAt is the time at which the renter scheduled a forced sync
   165  		// of the renter's ea balance with the host
   166  		forcedSyncAt time.Time
   167  
   168  		// Variables to manage a race condition around account creation, where
   169  		// the account must be available in the data structure before it has
   170  		// been synced to disk successfully (to avoid holding a lock on the
   171  		// account manager during a disk fsync). Anyone trying to use the
   172  		// account will need to block on 'staticReady', and then after that is
   173  		// closed needs to check the status of 'externActive', 'false'
   174  		// indicating that account creation failed and the account was deleted.
   175  		//
   176  		// 'externActive' can be accessed freely once 'staticReady' has been
   177  		// closed.
   178  		staticReady  chan struct{}
   179  		externActive bool
   180  
   181  		// Utils. The offset refers to the offset within the file that the
   182  		// account uses.
   183  		mu                  sync.Mutex
   184  		staticAlerter       *modules.GenericAlerter
   185  		staticBalanceTarget types.Currency
   186  		staticFile          modules.File
   187  		staticLog           *persist.Logger
   188  		staticOffset        int64
   189  	}
   190  
   191  	// spendingDetails contains a breakdown of all spending metrics, all money
   192  	// that is being spent from an ephemeral account is accounted for in one of
   193  	// these categories. Every field of this struct should have a corresponding
   194  	// 'spendingCategory'.
   195  	spendingDetails struct {
   196  		accountBalance    types.Currency
   197  		downloads         types.Currency
   198  		registryReads     types.Currency
   199  		registryWrites    types.Currency
   200  		repairDownloads   types.Currency
   201  		repairUploads     types.Currency
   202  		snapshotDownloads types.Currency
   203  		snapshotUploads   types.Currency
   204  		subscriptions     types.Currency
   205  		updatePriceTable  types.Currency
   206  		uploads           types.Currency
   207  	}
   208  
   209  	// spendingCategory defines an enum that represent a category in the
   210  	// spending details
   211  	spendingCategory uint64
   212  )
   213  
   214  // sum returns the sum of all spending details
   215  func (s *spendingDetails) sum() types.Currency {
   216  	var total types.Currency
   217  	total = total.Add(s.accountBalance)
   218  	total = total.Add(s.downloads)
   219  	total = total.Add(s.registryReads)
   220  	total = total.Add(s.registryWrites)
   221  	total = total.Add(s.repairDownloads)
   222  	total = total.Add(s.repairUploads)
   223  	total = total.Add(s.snapshotDownloads)
   224  	total = total.Add(s.snapshotUploads)
   225  	total = total.Add(s.subscriptions)
   226  	total = total.Add(s.updatePriceTable)
   227  	total = total.Add(s.uploads)
   228  	return total
   229  }
   230  
   231  // add will add the given amount to the appropriate spending category
   232  func (s *spendingDetails) add(category spendingCategory, amount types.Currency) {
   233  	s.set(category, s.get(category).Add(amount))
   234  }
   235  
   236  // get returns the spending details for given category
   237  func (s *spendingDetails) get(category spendingCategory) types.Currency {
   238  	switch category {
   239  	case categoryAccountBalance:
   240  		return s.accountBalance
   241  	case categoryDownload:
   242  		return s.downloads
   243  	case categorySnapshotDownload:
   244  		return s.snapshotDownloads
   245  	case categorySnapshotUpload:
   246  		return s.snapshotUploads
   247  	case categoryRegistryRead:
   248  		return s.registryReads
   249  	case categoryRegistryWrite:
   250  		return s.registryWrites
   251  	case categoryRepairDownload:
   252  		return s.repairDownloads
   253  	case categoryRepairUpload:
   254  		return s.repairUploads
   255  	case categorySubscription:
   256  		return s.subscriptions
   257  	case categoryUpdatePriceTable:
   258  		return s.updatePriceTable
   259  	case categoryUpload:
   260  		return s.uploads
   261  	case categoryErr:
   262  		build.Critical("category is not set, developer error")
   263  	default:
   264  		build.Critical("category is not handled, developer error")
   265  	}
   266  
   267  	return types.ZeroCurrency
   268  }
   269  
   270  // set sets the spending details for given category and amount
   271  func (s *spendingDetails) set(category spendingCategory, amount types.Currency) {
   272  	switch category {
   273  	case categoryAccountBalance:
   274  		s.accountBalance = amount
   275  	case categoryDownload:
   276  		s.downloads = amount
   277  	case categorySnapshotDownload:
   278  		s.snapshotDownloads = amount
   279  	case categorySnapshotUpload:
   280  		s.snapshotUploads = amount
   281  	case categoryRegistryRead:
   282  		s.registryReads = amount
   283  	case categoryRegistryWrite:
   284  		s.registryWrites = amount
   285  	case categoryRepairDownload:
   286  		s.repairDownloads = amount
   287  	case categoryRepairUpload:
   288  		s.repairUploads = amount
   289  	case categorySubscription:
   290  		s.subscriptions = amount
   291  	case categoryUpdatePriceTable:
   292  		s.updatePriceTable = amount
   293  	case categoryUpload:
   294  		s.uploads = amount
   295  	case categoryErr:
   296  		build.Critical("category is not set, developer error")
   297  	default:
   298  		build.Critical("category is not handled, developer error")
   299  	}
   300  }
   301  
   302  // sub will subtract the given amount from the appropriate spending category
   303  func (s *spendingDetails) sub(category spendingCategory, amount types.Currency) {
   304  	// safeSub is a helper function that subtracts two currencies, if the result
   305  	// would be negative the zero currency gets returned
   306  	safeSub := func(x, y types.Currency) types.Currency {
   307  		if y.Cmp(x) > 0 {
   308  			return types.ZeroCurrency
   309  		}
   310  		return x.Sub(y)
   311  	}
   312  
   313  	s.set(category, safeSub(s.get(category), amount))
   314  }
   315  
   316  // ProvidePayment takes a stream and various payment details and handles the
   317  // payment by sending and processing payment request and response objects.
   318  // Returns an error in case of failure.
   319  //
   320  // Note that this implementation does not 'Read' from the stream. This allows
   321  // the caller to pass in a buffer if he so pleases in order to optimise the
   322  // amount of writes on the actual stream.
   323  func (a *account) ProvidePayment(stream io.ReadWriter, amount types.Currency, blockHeight types.BlockHeight) error {
   324  	// NOTE: we purposefully do not verify if the account has sufficient funds.
   325  	// Seeing as withdrawals are a blocking action on the host, it is perfectly
   326  	// ok to trigger them from an account with insufficient balance.
   327  
   328  	// create a withdrawal message
   329  	msg := newWithdrawalMessage(a.staticID, amount, blockHeight)
   330  	sig := crypto.SignHash(crypto.HashObject(msg), a.staticSecretKey)
   331  
   332  	buffer := staticPoolProvidePaymentBuffers.Get()
   333  	defer staticPoolProvidePaymentBuffers.Put(buffer)
   334  
   335  	// send PaymentRequest
   336  	err := modules.RPCWrite(buffer, modules.PaymentRequest{Type: modules.PayByEphemeralAccount})
   337  	if err != nil {
   338  		return err
   339  	}
   340  
   341  	// send PayByEphemeralAccountRequest
   342  	err = modules.RPCWrite(buffer, modules.PayByEphemeralAccountRequest{
   343  		Message:   msg,
   344  		Signature: sig,
   345  	})
   346  	if err != nil {
   347  		return err
   348  	}
   349  	_, err = buffer.WriteTo(stream)
   350  	return err
   351  }
   352  
   353  // String returns a string representation of the account
   354  func (a *account) String() string {
   355  	var sb strings.Builder
   356  	sb.WriteString("HostKey: " + a.staticHostKey.String() + "\n")
   357  	sb.WriteString("\n")
   358  	sb.WriteString("Balance: " + a.balance.HumanString() + "\n")
   359  	sb.WriteString("    Balance Drift Pos  : " + a.balanceDriftPositive.HumanString() + "\n")
   360  	sb.WriteString("    Balance Drift Neg  : " + a.balanceDriftNegative.HumanString() + "\n")
   361  	sb.WriteString("    Pending Deposits   : " + a.pendingDeposits.HumanString() + "\n")
   362  	sb.WriteString("    Pending Withdrawals: " + a.pendingWithdrawals.HumanString() + "\n")
   363  	sb.WriteString("    Negative Balance   : " + a.negativeBalance.HumanString() + "\n")
   364  	sb.WriteString("Residue: " + a.residue.HumanString() + "\n")
   365  	sb.WriteString("\n")
   366  	sb.WriteString("Spending Details: \n")
   367  	sb.WriteString("    Account Balance   : " + a.spending.accountBalance.HumanString() + "\n")
   368  	sb.WriteString("    Downloads         : " + a.spending.downloads.HumanString() + "\n")
   369  	sb.WriteString("    Registry Reads    : " + a.spending.registryReads.HumanString() + "\n")
   370  	sb.WriteString("    Registry Writes   : " + a.spending.registryWrites.HumanString() + "\n")
   371  	sb.WriteString("    Repair Downloads  : " + a.spending.repairDownloads.HumanString() + "\n")
   372  	sb.WriteString("    Repair Uploads    : " + a.spending.repairUploads.HumanString() + "\n")
   373  	sb.WriteString("    Snapshot Downloads: " + a.spending.snapshotDownloads.HumanString() + "\n")
   374  	sb.WriteString("    Snapshot Uploads  : " + a.spending.snapshotUploads.HumanString() + "\n")
   375  	sb.WriteString("    Subscriptions     : " + a.spending.subscriptions.HumanString() + "\n")
   376  	sb.WriteString("    Update PriceTable : " + a.spending.updatePriceTable.HumanString() + "\n")
   377  	sb.WriteString("    Uploads           : " + a.spending.uploads.HumanString() + "\n")
   378  	sb.WriteString("\n")
   379  	sb.WriteString("Host Balance: \n")
   380  	sb.WriteString("    Host Balance    : " + a.hostBalance.HumanString() + "\n")
   381  	sb.WriteString("    Host Balance Neg: " + a.hostBalanceNegative.HumanString() + "\n")
   382  	sb.WriteString("\n")
   383  	return sb.String()
   384  }
   385  
   386  // availableBalance returns a new balance that takes into account pending spends
   387  // and pending funds, yielding the balance that is available
   388  func (a *account) availableBalance() types.Currency {
   389  	return availableBalance(a.balance, a.negativeBalance, a.pendingDeposits, a.pendingWithdrawals)
   390  }
   391  
   392  // availableHostBalance returns a new balance, based on the host balance fields,
   393  // that takes into account pending spends and pending funds
   394  func (a *account) availableHostBalance() types.Currency {
   395  	return availableBalance(a.hostBalance, a.hostBalanceNegative, a.pendingDeposits, a.pendingWithdrawals)
   396  }
   397  
   398  // callForcedSyncScheduled returns whether or not a forced account sync was
   399  // scheduled.
   400  func (a *account) callForcedSyncScheduled() bool {
   401  	a.mu.Lock()
   402  	defer a.mu.Unlock()
   403  	return !a.forcedSyncAt.IsZero()
   404  }
   405  
   406  // callNeedsToSync returns whether or not the account needs to sync to the host.
   407  func (a *account) callNeedsToSync() bool {
   408  	a.mu.Lock()
   409  	defer a.mu.Unlock()
   410  	return a.syncAt.Before(time.Now())
   411  }
   412  
   413  // callSpendingDetails returns the spending details for the account
   414  func (a *account) callSpendingDetails() spendingDetails {
   415  	a.mu.Lock()
   416  	defer a.mu.Unlock()
   417  	return a.spending
   418  }
   419  
   420  // deposit commits a pending deposit, either after success or failure.
   421  // Depending on the outcome the given amount will be added to the balance or
   422  // not. If the pending delta is zero, and we altered the account balance, we
   423  // update the account.
   424  func (a *account) deposit(amount types.Currency) {
   425  	// reflect the deposit in the balance field
   426  	if amount.Cmp(a.negativeBalance) <= 0 {
   427  		a.negativeBalance = a.negativeBalance.Sub(amount)
   428  	} else {
   429  		amount = amount.Sub(a.negativeBalance)
   430  		a.negativeBalance = types.ZeroCurrency
   431  		a.balance = a.balance.Add(amount)
   432  	}
   433  
   434  	// mirror the balance updates on the host balance, the host balance
   435  	// fields are kept purely for tracking the drift
   436  	if amount.Cmp(a.hostBalanceNegative) <= 0 {
   437  		a.hostBalanceNegative = a.hostBalanceNegative.Sub(amount)
   438  	} else {
   439  		amount = amount.Sub(a.hostBalanceNegative)
   440  		a.hostBalanceNegative = types.ZeroCurrency
   441  		a.hostBalance = a.hostBalance.Add(amount)
   442  	}
   443  }
   444  
   445  // managedAvailableBalance returns the amount of money that is available to
   446  // spend. It is calculated by taking into account pending spends and pending
   447  // funds.
   448  func (a *account) managedAvailableBalance() types.Currency {
   449  	a.mu.Lock()
   450  	defer a.mu.Unlock()
   451  	return a.availableBalance()
   452  }
   453  
   454  // managedMaxExpectedBalance returns the max amount of money that this
   455  // account is expected to contain after the renter has shut down.
   456  func (a *account) managedMaxExpectedBalance() types.Currency {
   457  	a.mu.Lock()
   458  	defer a.mu.Unlock()
   459  	return a.maxExpectedBalance()
   460  }
   461  
   462  // managedResetSpending is called when the contract corresponding to this
   463  // account gets renewed, when that happens the FundAccountCost is zero again,
   464  // and thus the spending details have to be reset. In order to properly reflect
   465  // the spending breakdown, we have to keep track of the current balance we carry
   466  // over into the next period, which we call the residue.
   467  func (a *account) managedResetSpending() {
   468  	a.mu.Lock()
   469  	defer a.mu.Unlock()
   470  
   471  	// set the residue to the current balance and reset the spending details
   472  	a.residue = a.balance
   473  	a.spending = spendingDetails{}
   474  
   475  	// persist the account upon reset
   476  	err := a.persist()
   477  	if err != nil {
   478  		a.staticLog.Critical(fmt.Sprintf("failed to persist account, err: %v", err))
   479  	}
   480  }
   481  
   482  // maxExpectedBalance returns the max amount of money that this account is
   483  // expected to contain after the renter has shut down.
   484  func (a *account) maxExpectedBalance() types.Currency {
   485  	// NOTE: there is an edge case where an EA has some drift, positive drift
   486  	// where the host balance is higher than the renter balance, if the account
   487  	// is prevented from being refilled, the negative balance can be higher than
   488  	// the expected balance so we have to safe sub here
   489  	expectedBalance := a.balance.Add(a.pendingDeposits)
   490  	if a.negativeBalance.Cmp(expectedBalance) > 0 {
   491  		return types.ZeroCurrency
   492  	}
   493  	return expectedBalance.Sub(a.negativeBalance)
   494  }
   495  
   496  // managedMinExpectedBalance returns the min amount of money that this
   497  // account is expected to contain after the renter has shut down.
   498  func (a *account) managedMinExpectedBalance() types.Currency {
   499  	a.mu.Lock()
   500  	defer a.mu.Unlock()
   501  	return a.minExpectedBalance()
   502  }
   503  
   504  // minExpectedBalance returns the min amount of money that this account is
   505  // expected to contain after the renter has shut down.
   506  func (a *account) minExpectedBalance() types.Currency {
   507  	return minExpectedBalance(a.balance, a.negativeBalance, a.pendingWithdrawals)
   508  }
   509  
   510  // managedCommitDeposit commits a pending deposit, either after success or
   511  // failure. Depending on the outcome the given amount will be added to the
   512  // balance or not. If the pending delta is zero, and we altered the account
   513  // balance, we update the account.
   514  func (a *account) managedCommitDeposit(amount types.Currency, success bool) {
   515  	a.mu.Lock()
   516  	defer a.mu.Unlock()
   517  
   518  	// (no need to sanity check - the implementation of 'Sub' does this for us)
   519  	a.pendingDeposits = a.pendingDeposits.Sub(amount)
   520  
   521  	// deposit the money in case of success
   522  	if success {
   523  		a.deposit(amount)
   524  	}
   525  }
   526  
   527  // managedCommitRefund commits a refund for the given spending category, it's
   528  // treated as a simple deposit and subtracted from the corresponding field in
   529  // the spending details
   530  func (a *account) managedCommitRefund(category spendingCategory, refund types.Currency) {
   531  	a.mu.Lock()
   532  	defer a.mu.Unlock()
   533  	a.deposit(refund)
   534  	a.trackSpending(category, refund, true)
   535  }
   536  
   537  // managedCommitWithdrawal commits a pending withdrawal, either after success or
   538  // failure. Depending on the outcome the given withdrawal amount will be
   539  // deducted from the balance or not. If the pending delta is zero, and we
   540  // altered the account balance, we update the account. The refund is given
   541  // because both the refund and the withdrawal amount need to be subtracted from
   542  // the pending withdrawals, seeing is it is no longer 'pending'. Only the
   543  // withdrawal amount has to be subtracted from the balance because the refund
   544  // got refunded by the host already.
   545  func (a *account) managedCommitWithdrawal(category spendingCategory, withdrawal, refund types.Currency, withdrawalErr error) {
   546  	a.mu.Lock()
   547  	defer a.mu.Unlock()
   548  
   549  	// conventience variables
   550  	alerter := a.staticAlerter
   551  	balanceTarget := a.staticBalanceTarget
   552  	workerHostKey := a.staticHostKey.String()
   553  
   554  	// (no need to sanity check - the implementation of 'Sub' does this for us)
   555  	a.pendingWithdrawals = a.pendingWithdrawals.Sub(withdrawal.Add(refund))
   556  
   557  	// reflect the successful withdrawal in the balance field
   558  	if withdrawalErr == nil {
   559  		if a.balance.Cmp(withdrawal) >= 0 {
   560  			a.balance = a.balance.Sub(withdrawal)
   561  		} else {
   562  			remainder := withdrawal.Sub(a.balance)
   563  			a.balance = types.ZeroCurrency
   564  			a.negativeBalance = a.negativeBalance.Add(remainder)
   565  		}
   566  
   567  		// mirror the balance updates on the host balance, the host balance
   568  		// fields are kept purely for tracking the drift
   569  		if a.hostBalance.Cmp(withdrawal) >= 0 {
   570  			a.hostBalance = a.hostBalance.Sub(withdrawal)
   571  		} else {
   572  			remainder := withdrawal.Sub(a.hostBalance)
   573  			a.hostBalance = types.ZeroCurrency
   574  			a.hostBalanceNegative = a.hostBalanceNegative.Add(remainder)
   575  		}
   576  
   577  		// only in case of success we track the spend and what it was spent on
   578  		a.trackSpending(category, withdrawal, false)
   579  	}
   580  
   581  	// register an alert if the host indicates our EA is out of funds, but the
   582  	// renter's account has not even reached the refill threshold, unregister it
   583  	// if a withdrawal was successful
   584  	if isInsufficientBalanceError(withdrawalErr) && !a.needsToRefill(balanceTarget.Div64(2)) {
   585  		msg := fmt.Sprintf("host %v indicates the ephemeral account is out of balance while the account has not exceeded the refill threshold", workerHostKey)
   586  		alerter.RegisterAlert(skymodules.AlertIDWorkerAccountOutOfFunds(workerHostKey), msg, workerHostKey, modules.SeverityError)
   587  	} else if withdrawalErr == nil {
   588  		alerter.UnregisterAlert(skymodules.AlertIDWorkerAccountOutOfFunds(workerHostKey))
   589  	}
   590  }
   591  
   592  // managedNeedsToRefill returns whether or not the account needs to be refilled.
   593  func (a *account) managedNeedsToRefill(target types.Currency) bool {
   594  	a.mu.Lock()
   595  	defer a.mu.Unlock()
   596  	return a.needsToRefill(target)
   597  }
   598  
   599  // managedSyncBalance updates the account's balance related fields to "sync"
   600  // with the given balance, which was returned by the host. If the given balance
   601  // is higher or lower than the account's available balance, we update the drift
   602  // fields in the positive or negative direction. The method returns the drift
   603  // between the given host balance and the renter balance.
   604  func (a *account) managedSyncBalance(balance types.Currency, forced bool) *big.Int {
   605  	a.mu.Lock()
   606  	defer a.mu.Unlock()
   607  
   608  	// Reset the host balance on every sync
   609  	defer a.resetHostBalance(balance)
   610  
   611  	// Reset the forced sync at if this sync was forced
   612  	if forced {
   613  		a.forcedSyncAt = time.Time{}
   614  	}
   615  
   616  	// Determine how long to wait before attempting to sync again, and then
   617  	// update the syncAt time. There is significant randomness in the
   618  	// waiting because syncing with the host requires freezing up the
   619  	// worker. We do not want to freeze up a large number of workers at
   620  	// once, nor do we want to freeze them frequently.
   621  	defer func() {
   622  		randWait := fastrand.Intn(accountSyncRandWaitMilliseconds)
   623  		waitTime := time.Duration(randWait) * time.Millisecond
   624  		waitTime += accountSyncMinWaitTime
   625  		a.syncAt = time.Now().Add(waitTime)
   626  	}()
   627  
   628  	// If our balance is equal to what the host communicated, we're done.
   629  	drift := big.NewInt(0)
   630  	currBalance := a.availableBalance()
   631  	if currBalance.Equals(balance) {
   632  		return drift
   633  	}
   634  
   635  	// However, if it is lower, or if we want to force sync, we want to reset
   636  	// our account balance
   637  	if forced || currBalance.Cmp(balance) < 0 {
   638  		a.resetBalance(balance)
   639  	}
   640  
   641  	// Calculate the drift, which is negative if the host reports a lower balance
   642  	if currBalance.Cmp(balance) < 0 {
   643  		drift = balance.Sub(currBalance).Big()
   644  	} else {
   645  		drift = drift.Neg(currBalance.Sub(balance).Big())
   646  	}
   647  
   648  	// To avoid an exponential increase of the positive and negative drift
   649  	// values, we keep track of the host's balance and reset it on every sync.
   650  	// That way, the positive and negative deltas values measure the drift since
   651  	// the last sync, as opposed to the drift since the account was created
   652  	currBalance = a.availableHostBalance()
   653  	if currBalance.Cmp(balance) < 0 {
   654  		delta := balance.Sub(currBalance)
   655  		a.balanceDriftPositive = a.balanceDriftPositive.Add(delta)
   656  	}
   657  
   658  	// If it's higher we only track the amount we drifted.
   659  	if currBalance.Cmp(balance) > 0 {
   660  		delta := currBalance.Sub(balance)
   661  		a.balanceDriftNegative = a.balanceDriftNegative.Add(delta)
   662  	}
   663  
   664  	// Persist the account
   665  	err := a.persist()
   666  	if err != nil {
   667  		a.staticLog.Printf("could not persist account, err: %v\n", err)
   668  	}
   669  
   670  	return drift
   671  }
   672  
   673  // managedStatus returns the status of the account
   674  func (a *account) managedStatus() skymodules.WorkerAccountStatus {
   675  	a.mu.Lock()
   676  	defer a.mu.Unlock()
   677  
   678  	var recentErrStr string
   679  	if a.recentErr != nil {
   680  		recentErrStr = a.recentErr.Error()
   681  	}
   682  
   683  	return skymodules.WorkerAccountStatus{
   684  		AvailableBalance: a.availableBalance(),
   685  		NegativeBalance:  a.negativeBalance,
   686  		HostBalance:      a.hostBalance,
   687  
   688  		SyncAt:       a.syncAt,
   689  		ForcedSyncAt: a.forcedSyncAt,
   690  
   691  		RecentErr:         recentErrStr,
   692  		RecentErrTime:     a.recentErrTime,
   693  		RecentSuccessTime: a.recentSuccessTime,
   694  	}
   695  }
   696  
   697  // managedTrackDeposit keeps track of pending deposits by adding the given
   698  // amount to the 'pendingDeposits' field.
   699  func (a *account) managedTrackDeposit(amount types.Currency) {
   700  	a.mu.Lock()
   701  	defer a.mu.Unlock()
   702  	a.pendingDeposits = a.pendingDeposits.Add(amount)
   703  }
   704  
   705  // managedTrackWithdrawal keeps track of pending withdrawals by adding the given
   706  // amount to the 'pendingWithdrawals' field.
   707  func (a *account) managedTrackWithdrawal(amount types.Currency) {
   708  	a.mu.Lock()
   709  	defer a.mu.Unlock()
   710  	a.pendingWithdrawals = a.pendingWithdrawals.Add(amount)
   711  }
   712  
   713  // needsToRefill returns whether or not the account needs to be refilled.
   714  func (a *account) needsToRefill(target types.Currency) bool {
   715  	return a.availableBalance().Cmp(target) < 0
   716  }
   717  
   718  // resetBalance sets the given balance and resets the account's balance
   719  // delta state variables. This happens when we have performanced a balance
   720  // inquiry on the host and we decide to trust his version of the balance.
   721  func (a *account) resetBalance(balance types.Currency) {
   722  	a.balance = balance
   723  	a.pendingDeposits = types.ZeroCurrency
   724  	a.pendingWithdrawals = types.ZeroCurrency
   725  	a.negativeBalance = types.ZeroCurrency
   726  }
   727  
   728  // resetHostBalance sets the given balance and resets the account's host balance
   729  // delta state variables. This happens every time we get a balance from the host.
   730  func (a *account) resetHostBalance(balance types.Currency) {
   731  	a.hostBalance = balance
   732  	a.hostBalanceNegative = types.ZeroCurrency
   733  }
   734  
   735  // trackSpending will keep track of the amount spent for the given spend category
   736  func (a *account) trackSpending(category spendingCategory, amount types.Currency, refund bool) {
   737  	// sanity check the category was set
   738  	if category == categoryErr {
   739  		build.Critical("tracked a spend using an uninitialized category, this is prevented as we want to track all money that is being spent without exception")
   740  		return
   741  	}
   742  
   743  	// update the spending metrics
   744  	if refund {
   745  		a.spending.sub(category, amount)
   746  	} else {
   747  		a.spending.add(category, amount)
   748  	}
   749  
   750  	// every time we update we write the account to disk
   751  	err := a.persist()
   752  	if err != nil {
   753  		a.staticLog.Critical(fmt.Sprintf("failed to persist account, err: %v", err))
   754  	}
   755  }
   756  
   757  // newWithdrawalMessage is a helper function that takes a set of parameters and
   758  // a returns a new WithdrawalMessage.
   759  func newWithdrawalMessage(id modules.AccountID, amount types.Currency, blockHeight types.BlockHeight) modules.WithdrawalMessage {
   760  	expiry := blockHeight + withdrawalValidityPeriod
   761  	var nonce [modules.WithdrawalNonceSize]byte
   762  	fastrand.Read(nonce[:])
   763  	return modules.WithdrawalMessage{
   764  		Account: id,
   765  		Expiry:  expiry,
   766  		Amount:  amount,
   767  		Nonce:   nonce,
   768  	}
   769  }
   770  
   771  // callScheduleForcedAccountSync schedules a forced sync of the worker's
   772  // ephemeral account with the host's balance.
   773  func (w *worker) callScheduleForcedAccountSync() {
   774  	w.staticAccount.mu.Lock()
   775  	defer w.staticAccount.mu.Unlock()
   776  	w.staticAccount.forcedSyncAt = time.Now()
   777  }
   778  
   779  // externSyncAccountBalanceToHost is executed before the worker loop and
   780  // corrects the account balance in case of an unclean renter shutdown. It does
   781  // so by performing the AccountBalanceRPC and resetting the account to the
   782  // balance communicated by the host. This only happens if our account balance is
   783  // zero, which indicates an unclean shutdown.
   784  //
   785  // NOTE: it is important this function is only used when the worker has no
   786  // in-progress jobs, neither serial nor async, to ensure the account balance
   787  // sync does not leave the account in an undesired state. The worker should not
   788  // be launching new jobs while this function is running. To achieve this, we
   789  // ensure that this thread is only run from the primary work loop, which is also
   790  // the only thread that is allowed to launch jobs. As long as this function is
   791  // only called by that thread, and no other thread launches jobs, this function
   792  // is threadsafe.
   793  func (w *worker) externSyncAccountBalanceToHost(forced bool) error {
   794  	// Return if the renter's shutting down
   795  	if w.staticIsShuttingDown() {
   796  		return nil
   797  	}
   798  
   799  	// Spin/block until the worker has no jobs in motion. This should only be
   800  	// called from the primary loop of the worker, meaning that no new jobs will
   801  	// be launched while we spin.
   802  	isIdle := func() bool {
   803  		sls := w.staticLoopState
   804  		a := atomic.LoadUint64(&sls.atomicSerialJobRunning) == 0
   805  		b := atomic.LoadUint64(&sls.atomicAsyncJobsRunning) == 0
   806  		return a && b
   807  	}
   808  	start := time.Now()
   809  	for !isIdle() {
   810  		if time.Since(start) > accountIdleMaxWait {
   811  			// The worker failed to go idle for too long. Print the loop state,
   812  			// so we know what kind of task is keeping it busy.
   813  			w.staticRenter.staticLog.Printf("Worker static loop state: %+v\n\n", w.staticLoopState)
   814  			// Get the stack traces of all running goroutines.
   815  			buf := make([]byte, skymodules.StackSize) // 64MB
   816  			n := runtime.Stack(buf, true)
   817  			w.staticRenter.staticLog.Println(string(buf[:n]))
   818  			w.staticRenter.staticLog.Critical(fmt.Sprintf("worker has taken more than %v minutes to go idle", accountIdleMaxWait.Minutes()))
   819  			return nil
   820  		}
   821  		awake := w.staticTG.Sleep(accountIdleCheckFrequency)
   822  		if !awake {
   823  			return nil
   824  		}
   825  	}
   826  
   827  	// Grab the account sync lock to prevent the subscription loop from
   828  	// starting new pending deposits or withdrawals. We do this after the
   829  	// idle check since it might take a while for all jobs to finish and we
   830  	// don't want to unnecessarily block subscriptions.
   831  	w.accountSyncMu.Lock()
   832  	defer w.accountSyncMu.Unlock()
   833  
   834  	// Do a check to ensure that the worker is still idle after the function is
   835  	// complete. This should help to catch any situation where the worker is
   836  	// spinning up new jobs, even though it is not supposed to be spinning up
   837  	// new jobs while it is performing the sync operation.
   838  	defer func() {
   839  		if !isIdle() {
   840  			w.staticRenter.staticLog.Critical("worker appears to be spinning up new jobs during managedSyncAccountBalanceToHost")
   841  		}
   842  	}()
   843  
   844  	// Sanity check the account's deltas are zero, indicating there are no
   845  	// in-progress jobs
   846  	w.staticAccount.mu.Lock()
   847  	deltasAreZero := w.staticAccount.pendingDeposits.IsZero() && w.staticAccount.pendingWithdrawals.IsZero()
   848  	w.staticAccount.mu.Unlock()
   849  	if !deltasAreZero {
   850  		build.Critical("managedSyncAccountBalanceToHost is called on a worker with an account that has non-zero deltas, indicating in-progress jobs")
   851  	}
   852  
   853  	// Track the outcome of the account sync - this ensures a proper working of
   854  	// the maintenance cooldown mechanism.
   855  	balance, err := w.managedHostAccountBalance()
   856  	w.managedTrackAccountSyncErr(err)
   857  	if err != nil {
   858  		w.staticRenter.staticLog.Debugf("ERROR: failed to check account balance on host %v failed, err: %v\n", w.staticHostPubKeyStr, err)
   859  		return err
   860  	}
   861  
   862  	// Sync the account with the host's version of our balance. This will update
   863  	// our balance in case the host tells us we actually have more money, and it
   864  	// will keep track of drift in both directions.
   865  	drift := w.staticAccount.managedSyncBalance(balance, forced)
   866  	if drift.Sign() == -1 {
   867  		// if we have negative drift, we want to register an alert if it exceeds a certain threshold
   868  		driftCurr := types.NewCurrency(drift.Abs(drift))
   869  		threshold := w.staticRenter.staticAccountBalanceTarget.MulFloat(accountBalanceDriftPercentageThreshold)
   870  		if driftCurr.Cmp(threshold) > 0 {
   871  			msg := fmt.Sprintf("host %v, host balance has drifted away from the renter balance by %v", w.staticHostPubKeyStr, driftCurr.HumanString())
   872  			w.staticRenter.staticAlerter.RegisterAlert(skymodules.AlertIDWorkerAccountBalanceDrift(w.staticHostPubKeyStr), msg, "", modules.SeverityWarning)
   873  		}
   874  	} else {
   875  		// if we're in sync with the host balance, unregister the drift alert
   876  		w.staticRenter.staticAlerter.UnregisterAlert(skymodules.AlertIDWorkerAccountBalanceDrift(w.staticHostPubKeyStr))
   877  	}
   878  
   879  	// TODO perform a thorough balance comparison to decide whether the drift in
   880  	// the account balance is warranted. If not the host needs to be penalized
   881  	// accordingly. Perform this check at startup and periodically.
   882  
   883  	return nil
   884  }
   885  
   886  // managedForcedAccountSyncScheduled returns true if a forced sync was
   887  // scheduled. A forced sync means we will adopt the host's balance regardless of
   888  // the renter's account balance.
   889  func (w *worker) managedForcedAccountSyncScheduled() bool {
   890  	w.staticAccount.mu.Lock()
   891  	defer w.staticAccount.mu.Unlock()
   892  	return !w.staticAccount.forcedSyncAt.IsZero()
   893  }
   894  
   895  // managedNeedsToRefillAccount will check whether the worker's account needs to
   896  // be refilled. This function will return false if any conditions are met which
   897  // are likely to prevent the refill from being successful.
   898  func (w *worker) managedNeedsToRefillAccount() (refill bool) {
   899  	// No need to refill the account if the worker is on maintenance cooldown.
   900  	if w.managedOnMaintenanceCooldown() {
   901  		return false
   902  	}
   903  
   904  	// No need to refill if the price table is not valid, as it would only
   905  	// result in failure anyway.
   906  	if !w.staticPriceTable().staticValid() {
   907  		return false
   908  	}
   909  
   910  	return w.staticAccount.managedNeedsToRefill(w.staticRenter.staticAccountBalanceTarget.Div64(2))
   911  }
   912  
   913  // managedNeedsToSyncAccountBalanceToHost returns whether the renter needs to
   914  // sync the renter's account balance with the host's version of the account. The
   915  // second return value indicates whether the sync was forced and should adopt
   916  // the host balance.
   917  func (w *worker) managedNeedsToSyncAccountBalanceToHost() (bool, bool) {
   918  	// No need to sync if the price table is not valid, as it would only
   919  	// result in failure anyway.
   920  	if !w.staticPriceTable().staticValid() {
   921  		return false, false
   922  	}
   923  
   924  	// Check whether we want to force sync the account
   925  	if w.managedForcedAccountSyncScheduled() {
   926  		return true, true
   927  	}
   928  
   929  	// No need to sync the account if the worker's RHP3 is on cooldown.
   930  	if w.managedOnMaintenanceCooldown() {
   931  		return false, false
   932  	}
   933  
   934  	return w.staticAccount.callNeedsToSync(), false
   935  }
   936  
   937  // managedRefillAccount will refill the account if it needs to be refilled
   938  func (w *worker) managedRefillAccount() (err error) {
   939  	// Disrupt the refill
   940  	deps := w.staticRenter.staticDeps
   941  	if deps.Disrupt("DisableFunding") || deps.Disrupt("DisableRefill") {
   942  		return
   943  	}
   944  
   945  	// Sanity check we have a valid price table
   946  	if !w.staticPriceTable().staticValid() {
   947  		return errors.New("can not refill account with an invalid price table")
   948  	}
   949  
   950  	// The account balance dropped to below half the balance target, refill. Use
   951  	// the max expected balance when refilling to avoid exceeding any host
   952  	// maximums.
   953  	balance := w.staticAccount.managedMaxExpectedBalance()
   954  	amount := types.ZeroCurrency
   955  	if w.staticRenter.staticAccountBalanceTarget.Cmp(balance) > 0 {
   956  		amount = w.staticRenter.staticAccountBalanceTarget.Sub(balance)
   957  	}
   958  	if amount.IsZero() {
   959  		return // nothing to do
   960  	}
   961  	pt := w.staticPriceTable().staticPriceTable
   962  
   963  	// If the target amount is larger than the remaining money, adjust the
   964  	// target. Make sure it can still cover the funding cost.
   965  	if contract, ok := w.staticRenter.staticHostContractor.ContractByPublicKey(w.staticHostPubKey); ok {
   966  		if amount.Add(pt.FundAccountCost).Cmp(contract.RenterFunds) > 0 && contract.RenterFunds.Cmp(pt.FundAccountCost) > 0 {
   967  			amount = contract.RenterFunds.Sub(pt.FundAccountCost)
   968  		}
   969  	}
   970  
   971  	// We track that there is a deposit in progress. Because filling an account
   972  	// is an interactive protocol with another machine, we are never sure of the
   973  	// exact moment that the deposit has reached our account. Instead, we track
   974  	// the deposit as a "maybe" until we know for sure that the deposit has
   975  	// either reached the remote machine or failed.
   976  	//
   977  	// At the same time that we track the deposit, we defer a function to check
   978  	// the error on the deposit
   979  	w.staticAccount.managedTrackDeposit(amount)
   980  	defer func() {
   981  		// If there was no error, the account should now be full, and will not
   982  		// need to be refilled until the worker has spent up the funds in the
   983  		// account.
   984  		w.staticAccount.managedCommitDeposit(amount, err == nil)
   985  
   986  		// Track the outcome of the account refill - this ensures a proper
   987  		// working of the maintenance cooldown mechanism.
   988  		cd := w.managedTrackAccountRefillErr(err)
   989  
   990  		// If the error is nil, return.
   991  		if err == nil {
   992  			w.staticAccount.mu.Lock()
   993  			w.staticAccount.recentSuccessTime = time.Now()
   994  			w.staticAccount.mu.Unlock()
   995  			return
   996  		}
   997  
   998  		// Track the error on the account for debugging purposes.
   999  		w.staticAccount.mu.Lock()
  1000  		w.staticAccount.recentErr = err
  1001  		w.staticAccount.recentErrTime = time.Now()
  1002  		w.staticAccount.mu.Unlock()
  1003  
  1004  		// Have the threadgroup wake the worker when the account comes off of
  1005  		// cooldown.
  1006  		w.staticTG.AfterFunc(time.Until(cd), func() {
  1007  			w.staticWake()
  1008  		})
  1009  	}()
  1010  
  1011  	// check the current price table for gouging errors
  1012  	err = checkFundAccountGouging(w.staticPriceTable().staticPriceTable, w.staticCache().staticRenterAllowance, w.staticRenter.staticAccountBalanceTarget)
  1013  	if err != nil {
  1014  		return
  1015  	}
  1016  
  1017  	// create a new stream
  1018  	var stream net.Conn
  1019  	stream, err = w.staticNewStream()
  1020  	if err != nil {
  1021  		err = errors.AddContext(err, "Unable to create a new stream")
  1022  		return
  1023  	}
  1024  	defer func() {
  1025  		closeErr := stream.Close()
  1026  		if closeErr != nil {
  1027  			w.staticRenter.staticLog.Println("ERROR: failed to close stream", closeErr)
  1028  		}
  1029  	}()
  1030  
  1031  	// prepare a buffer so we can optimize our writes
  1032  	buffer := bytes.NewBuffer(nil)
  1033  
  1034  	// write the specifier
  1035  	err = modules.RPCWrite(buffer, modules.RPCFundAccount)
  1036  	if err != nil {
  1037  		err = errors.AddContext(err, "could not write fund account specifier")
  1038  		return
  1039  	}
  1040  
  1041  	// send price table uid
  1042  	err = modules.RPCWrite(buffer, pt.UID)
  1043  	if err != nil {
  1044  		err = errors.AddContext(err, "could not write price table uid")
  1045  		return
  1046  	}
  1047  
  1048  	// send fund account request
  1049  	err = modules.RPCWrite(buffer, modules.FundAccountRequest{Account: w.staticAccount.staticID})
  1050  	if err != nil {
  1051  		err = errors.AddContext(err, "could not write the fund account request")
  1052  		return
  1053  	}
  1054  
  1055  	// write contents of the buffer to the stream
  1056  	_, err = stream.Write(buffer.Bytes())
  1057  	if err != nil {
  1058  		err = errors.AddContext(err, "could not write the buffer contents")
  1059  		return
  1060  	}
  1061  
  1062  	// build payment details
  1063  	details := contractor.PaymentDetails{
  1064  		Host:          w.staticHostPubKey,
  1065  		Amount:        amount.Add(pt.FundAccountCost),
  1066  		RefundAccount: modules.ZeroAccountID,
  1067  		SpendingDetails: skymodules.SpendingDetails{
  1068  			FundAccountSpending: amount,
  1069  			MaintenanceSpending: skymodules.MaintenanceSpending{
  1070  				FundAccountCost: pt.FundAccountCost,
  1071  			},
  1072  		},
  1073  	}
  1074  
  1075  	// provide payment
  1076  	err = w.staticRenter.staticHostContractor.ProvidePayment(stream, &pt, details)
  1077  	if err != nil && strings.Contains(err.Error(), "balance exceeded") {
  1078  		// The host reporting that the balance has been exceeded suggests that
  1079  		// the host believes that we have more money than we believe that we
  1080  		// have.
  1081  		if !w.staticRenter.staticDeps.Disrupt("DisableCriticalOnMaxBalance") {
  1082  			// Log a critical in testing as this is very unlikely to happen due
  1083  			// to the order of events in the worker loop, seeing as we just
  1084  			// synced our account balance with the host if that was necessary
  1085  			if build.Release == "testing" {
  1086  				msg := fmt.Sprintf("amount: %v, estimatedBalance: %v, target: %v", amount, balance, w.staticRenter.staticAccountBalanceTarget)
  1087  				build.Critical("worker account refill failed with a max balance - ", msg, " - are the host max balance settings lower than the threshold balance?", err)
  1088  			}
  1089  			w.staticRenter.staticLog.Println("worker account refill failed", err)
  1090  		}
  1091  		w.staticAccount.mu.Lock()
  1092  		w.staticAccount.syncAt = time.Time{}
  1093  		w.staticAccount.mu.Unlock()
  1094  	}
  1095  	if err != nil {
  1096  		err = errors.AddContext(err, "could not provide payment for the account")
  1097  		return
  1098  	}
  1099  
  1100  	// receive FundAccountResponse. The response contains a receipt and a
  1101  	// signature, which is useful for places where accountability is required,
  1102  	// but no accountability is required in this case, so we ignore the
  1103  	// response.
  1104  	var resp modules.FundAccountResponse
  1105  	err = modules.RPCRead(stream, &resp)
  1106  	if err != nil {
  1107  		err = errors.AddContext(err, "could not read the account response")
  1108  	}
  1109  
  1110  	// Wake the worker so that any jobs potentially blocking on getting more
  1111  	// money in the account can be activated.
  1112  	w.staticWake()
  1113  
  1114  	return nil
  1115  }
  1116  
  1117  // managedHostAccountBalance performs the AccountBalanceRPC on the host
  1118  func (w *worker) managedHostAccountBalance() (_ types.Currency, err error) {
  1119  	// sanity check - only one account balance check should be running at a
  1120  	// time.
  1121  	if !atomic.CompareAndSwapUint64(&w.atomicAccountBalanceCheckRunning, 0, 1) {
  1122  		w.staticRenter.staticLog.Critical("account balance is being checked in two threads concurrently")
  1123  	}
  1124  	defer atomic.StoreUint64(&w.atomicAccountBalanceCheckRunning, 0)
  1125  
  1126  	// Get a stream.
  1127  	stream, err := w.staticNewStream()
  1128  	if err != nil {
  1129  		return types.ZeroCurrency, err
  1130  	}
  1131  	defer func() {
  1132  		if err := stream.Close(); err != nil {
  1133  			w.staticRenter.staticLog.Println("ERROR: failed to close stream", err)
  1134  		}
  1135  	}()
  1136  
  1137  	// try and pay by EA if possible
  1138  	if w.managedMaintenancePayByEA() {
  1139  		return w.managedHostAccountBalancePayByEA(stream)
  1140  	}
  1141  	return w.managedHostAccountBalancePayByContract(stream)
  1142  }
  1143  
  1144  // managedHostAccountBalancePayByEA performs the AccountBalanceRPC on the host
  1145  // and performs a payment using the renter's ephemeral account.
  1146  func (w *worker) managedHostAccountBalancePayByEA(stream siamux.Stream) (_ types.Currency, err error) {
  1147  	// prepare a buffer so we can optimize our writes
  1148  	buffer := staticPoolCheckAccountBalanceBuffers.Get()
  1149  	defer staticPoolCheckAccountBalanceBuffers.Put(buffer)
  1150  
  1151  	// write the specifier
  1152  	err = modules.RPCWrite(buffer, modules.RPCAccountBalance)
  1153  	if err != nil {
  1154  		return types.ZeroCurrency, err
  1155  	}
  1156  
  1157  	// send price table uid
  1158  	pt := w.staticPriceTable().staticPriceTable
  1159  	err = modules.RPCWrite(buffer, pt.UID)
  1160  	if err != nil {
  1161  		return types.ZeroCurrency, err
  1162  	}
  1163  	cost := pt.AccountBalanceCost
  1164  
  1165  	// track the withdrawal
  1166  	w.staticAccount.managedTrackWithdrawal(cost)
  1167  
  1168  	// provide payment
  1169  	bh, _ := w.managedSyncInfo()
  1170  	err = w.staticAccount.ProvidePayment(buffer, cost, bh)
  1171  	if err != nil {
  1172  		// commit the withdrawal
  1173  		w.staticAccount.managedCommitWithdrawal(categoryAccountBalance, cost, types.ZeroCurrency, err)
  1174  		err = errors.AddContext(err, "unable to provide payment by ephemeral account")
  1175  		return
  1176  	}
  1177  
  1178  	// commit the withdrawal
  1179  	w.staticAccount.managedCommitWithdrawal(categoryAccountBalance, cost, types.ZeroCurrency, nil)
  1180  
  1181  	// prepare the request.
  1182  	abr := modules.AccountBalanceRequest{Account: w.staticAccount.staticID}
  1183  	err = modules.RPCWrite(buffer, abr)
  1184  	if err != nil {
  1185  		return types.ZeroCurrency, err
  1186  	}
  1187  
  1188  	// write contents of the buffer to the stream
  1189  	_, err = buffer.WriteTo(stream)
  1190  	if err != nil {
  1191  		err = errors.AddContext(err, "Failed to write buffer to stream")
  1192  		return
  1193  	}
  1194  
  1195  	// read the response
  1196  	var resp modules.AccountBalanceResponse
  1197  	err = modules.RPCRead(stream, &resp)
  1198  	if err != nil {
  1199  		return types.ZeroCurrency, err
  1200  	}
  1201  
  1202  	// disrupt if necessary
  1203  	if w.staticRenter.staticDeps.Disrupt("ZeroBalance") {
  1204  		return types.ZeroCurrency, nil
  1205  	}
  1206  	return resp.Balance, nil
  1207  }
  1208  
  1209  // managedHostAccountBalancePayByContract performs the AccountBalanceRPC on the
  1210  // host and performs a payment using a contract.
  1211  func (w *worker) managedHostAccountBalancePayByContract(stream siamux.Stream) (_ types.Currency, err error) {
  1212  	// write the specifier
  1213  	err = modules.RPCWrite(stream, modules.RPCAccountBalance)
  1214  	if err != nil {
  1215  		return types.ZeroCurrency, err
  1216  	}
  1217  
  1218  	// send price table uid
  1219  	pt := w.staticPriceTable().staticPriceTable
  1220  	err = modules.RPCWrite(stream, pt.UID)
  1221  	if err != nil {
  1222  		return types.ZeroCurrency, err
  1223  	}
  1224  
  1225  	// build payment details
  1226  	details := contractor.PaymentDetails{
  1227  		Host:          w.staticHostPubKey,
  1228  		Amount:        pt.AccountBalanceCost,
  1229  		RefundAccount: w.staticAccount.staticID,
  1230  		SpendingDetails: skymodules.SpendingDetails{
  1231  			MaintenanceSpending: skymodules.MaintenanceSpending{
  1232  				AccountBalanceCost: pt.AccountBalanceCost,
  1233  			},
  1234  		},
  1235  	}
  1236  
  1237  	// provide payment
  1238  	err = w.staticRenter.staticHostContractor.ProvidePayment(stream, &pt, details)
  1239  	if err != nil {
  1240  		return types.ZeroCurrency, err
  1241  	}
  1242  
  1243  	// prepare the request.
  1244  	abr := modules.AccountBalanceRequest{Account: w.staticAccount.staticID}
  1245  	err = modules.RPCWrite(stream, abr)
  1246  	if err != nil {
  1247  		return types.ZeroCurrency, err
  1248  	}
  1249  
  1250  	// read the response
  1251  	var resp modules.AccountBalanceResponse
  1252  	err = modules.RPCRead(stream, &resp)
  1253  	if err != nil {
  1254  		return types.ZeroCurrency, err
  1255  	}
  1256  
  1257  	// disrupt if necessary
  1258  	if w.staticRenter.staticDeps.Disrupt("ZeroBalance") {
  1259  		return types.ZeroCurrency, nil
  1260  	}
  1261  	return resp.Balance, nil
  1262  }
  1263  
  1264  // checkFundAccountGouging verifies the cost of funding an ephemeral account on
  1265  // the host is reasonable, if deemed unreasonable we will block the refill and
  1266  // the worker will eventually be put into cooldown.
  1267  func checkFundAccountGouging(pt modules.RPCPriceTable, allowance skymodules.Allowance, targetBalance types.Currency) error {
  1268  	// If there is no allowance, price gouging checks have to be disabled,
  1269  	// because there is no baseline for understanding what might count as price
  1270  	// gouging.
  1271  	if allowance.Funds.IsZero() {
  1272  		return nil
  1273  	}
  1274  
  1275  	// In order to decide whether or not the fund account cost is too expensive,
  1276  	// we first calculate how many times we can refill the account, taking into
  1277  	// account the refill amount and the cost to effectively fund the account.
  1278  	//
  1279  	// Note: we divide the target balance by two because more often than not the
  1280  	// refill happens the moment we drop below half of the target, this means
  1281  	// that we actually refill half the target amount most of the time.
  1282  	costOfRefill := targetBalance.Div64(2).Add(pt.FundAccountCost)
  1283  	numRefills, err := allowance.Funds.Div(costOfRefill).Uint64()
  1284  	if err != nil {
  1285  		return errors.AddContext(err, "unable to check fund account gouging, could not calculate the amount of refills")
  1286  	}
  1287  
  1288  	// The cost of funding is considered too expensive if the total cost is
  1289  	// above a certain % of the allowance.
  1290  	totalFundAccountCost := pt.FundAccountCost.Mul64(numRefills)
  1291  	if totalFundAccountCost.Cmp(allowance.Funds.MulFloat(fundAccountGougingPercentageThreshold)) > 0 {
  1292  		return fmt.Errorf("fund account cost %v is considered too high, the total cost of refilling the account to spend the total allowance exceeds %v%% of the allowance - price gouging protection enabled", pt.FundAccountCost, fundAccountGougingPercentageThreshold)
  1293  	}
  1294  
  1295  	return nil
  1296  }
  1297  
  1298  // availableBalance takes a balance, its negative counter part, pending deposits
  1299  // and withdrawals and returns a currency that reflects the resulting available
  1300  // balance
  1301  func availableBalance(balance, balanceNeg, pendingDeposits, pendingWithdrawals types.Currency) types.Currency {
  1302  	total := balance.Add(pendingDeposits)
  1303  	if total.Cmp(balanceNeg) <= 0 {
  1304  		return types.ZeroCurrency
  1305  	}
  1306  	total = total.Sub(balanceNeg)
  1307  	if pendingWithdrawals.Cmp(total) < 0 {
  1308  		return total.Sub(pendingWithdrawals)
  1309  	}
  1310  	return types.ZeroCurrency
  1311  }
  1312  
  1313  // minExpectedBalance returns the min amount of money that this account is
  1314  // expected to contain after the renter has shut down.
  1315  func minExpectedBalance(balance, balanceNegative, pendingWithdrawals types.Currency) types.Currency {
  1316  	// subtract all pending withdrawals
  1317  	if balance.Cmp(pendingWithdrawals) <= 0 {
  1318  		return types.ZeroCurrency
  1319  	}
  1320  	balance = balance.Sub(pendingWithdrawals)
  1321  
  1322  	// subtract the negative balance
  1323  	if balanceNegative.Cmp(balance) > 0 {
  1324  		return types.ZeroCurrency
  1325  	}
  1326  	balance = balance.Sub(balanceNegative)
  1327  	return balance
  1328  }
  1329  
  1330  // isInsufficientBalanceError is a helper function that verifies whether the
  1331  // given error indicates the ephemeral account on the host is out of balance.
  1332  //
  1333  // NOTE: the host only returns this error after blocking the withdrawal for a
  1334  // certain amount of time, awaiting a potential deposit
  1335  func isInsufficientBalanceError(err error) bool {
  1336  	return err != nil && strings.Contains(err.Error(), host.ErrBalanceInsufficient.Error())
  1337  }