Skip to content

Commit

Permalink
worker: take expectedStorage and minNewCollateral as params instead o…
Browse files Browse the repository at this point in the history
…f newCollateral
  • Loading branch information
ChrisSchinnerl committed Dec 11, 2023
1 parent 0776d92 commit bb2be05
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 56 deletions.
20 changes: 11 additions & 9 deletions api/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,22 +115,24 @@ type (

// RHPRenewRequest is the request type for the /rhp/renew endpoint.
RHPRenewRequest struct {
ContractID types.FileContractID `json:"contractID"`
EndHeight uint64 `json:"endHeight"`
HostAddress types.Address `json:"hostAddress"`
HostKey types.PublicKey `json:"hostKey"`
SiamuxAddr string `json:"siamuxAddr"`
NewCollateral types.Currency `json:"newCollateral"`
RenterAddress types.Address `json:"renterAddress"`
RenterFunds types.Currency `json:"renterFunds"`
WindowSize uint64 `json:"windowSize"`
ContractID types.FileContractID `json:"contractID"`
EndHeight uint64 `json:"endHeight"`
ExpectedStorage uint64 `json:"expectedStorage"`
HostAddress types.Address `json:"hostAddress"`
HostKey types.PublicKey `json:"hostKey"`
MinNewCollateral types.Currency `json:"minNewCollateral"`
SiamuxAddr string `json:"siamuxAddr"`
RenterAddress types.Address `json:"renterAddress"`
RenterFunds types.Currency `json:"renterFunds"`
WindowSize uint64 `json:"windowSize"`
}

// RHPRenewResponse is the response type for the /rhp/renew endpoint.
RHPRenewResponse struct {
Error string `json:"error"`
ContractID types.FileContractID `json:"contractID"`
Contract rhpv2.ContractRevision `json:"contract"`
ContractPrice types.Currency `json:"contractPrice"`
TransactionSet []types.Transaction `json:"transactionSet"`
}

Expand Down
28 changes: 8 additions & 20 deletions autopilot/contractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -1371,10 +1371,9 @@ func (c *contractor) renewContract(ctx context.Context, w Worker, ci contractInf

// calculate the host collateral
expectedStorage := renterFundsToExpectedStorage(renterFunds, endHeight-cs.BlockHeight, ci.priceTable)
newCollateral := rhpv2.ContractRenewalCollateral(rev.FileContract, expectedStorage, settings, cs.BlockHeight, endHeight)

// renew the contract
newRevision, _, err := w.RHPRenew(ctx, fcid, endHeight, hk, contract.SiamuxAddr, settings.Address, state.address, renterFunds, newCollateral, settings.WindowSize)
resp, err := w.RHPRenew(ctx, fcid, endHeight, hk, contract.SiamuxAddr, settings.Address, state.address, renterFunds, types.ZeroCurrency, expectedStorage, settings.WindowSize)
if err != nil {
c.logger.Errorw(fmt.Sprintf("renewal failed, err: %v", err), "hk", hk, "fcid", fcid)
if strings.Contains(err.Error(), wallet.ErrInsufficientBalance.Error()) {
Expand All @@ -1387,8 +1386,8 @@ func (c *contractor) renewContract(ctx context.Context, w Worker, ci contractInf
*budget = budget.Sub(renterFunds)

// persist the contract
contractPrice := newRevision.Revision.MissedHostPayout().Sub(newCollateral)
renewedContract, err := c.ap.bus.AddRenewedContract(ctx, newRevision, contractPrice, renterFunds, cs.BlockHeight, fcid, api.ContractStatePending)
newCollateral := resp.Contract.Revision.MissedHostPayout().Sub(resp.ContractPrice)
renewedContract, err := c.ap.bus.AddRenewedContract(ctx, resp.Contract, resp.ContractPrice, renterFunds, cs.BlockHeight, fcid, api.ContractStatePending)
if err != nil {
c.logger.Errorw(fmt.Sprintf("renewal failed to persist, err: %v", err), "hk", hk, "fcid", fcid)
return api.ContractMetadata{}, false, err
Expand Down Expand Up @@ -1448,22 +1447,11 @@ func (c *contractor) refreshContract(ctx context.Context, w Worker, ci contractI

// calculate the new collateral
expectedStorage := renterFundsToExpectedStorage(renterFunds, contract.EndHeight()-cs.BlockHeight, ci.priceTable)
newCollateral := rhpv2.ContractRenewalCollateral(rev.FileContract, expectedStorage, settings, cs.BlockHeight, contract.EndHeight())

// do not refresh if the contract's updated collateral will fall below the threshold anyway
_, hostMissedPayout, _, _ := rhpv2.CalculateHostPayouts(rev.FileContract, newCollateral, settings, contract.EndHeight())
var newRemainingCollateral types.Currency
if hostMissedPayout.Cmp(settings.ContractPrice) > 0 {
newRemainingCollateral = hostMissedPayout.Sub(settings.ContractPrice)
}
if isBelowCollateralThreshold(newCollateral, newRemainingCollateral) {
err := errors.New("refresh failed, new collateral is below the threshold")
c.logger.Errorw(err.Error(), "hk", hk, "fcid", fcid, "expectedCollateral", newCollateral.String(), "actualCollateral", newRemainingCollateral.String(), "maxCollateral", settings.MaxCollateral)
return api.ContractMetadata{}, true, err
}
unallocatedCollateral := rev.MissedHostPayout().Sub(contract.ContractPrice)
minNewCollateral := minNewCollateral(unallocatedCollateral)

// renew the contract
newRevision, _, err := w.RHPRenew(ctx, contract.ID, contract.EndHeight(), hk, contract.SiamuxAddr, settings.Address, state.address, renterFunds, newCollateral, settings.WindowSize)
resp, err := w.RHPRenew(ctx, contract.ID, contract.EndHeight(), hk, contract.SiamuxAddr, settings.Address, state.address, renterFunds, minNewCollateral, expectedStorage, settings.WindowSize)
if err != nil {
c.logger.Errorw(fmt.Sprintf("refresh failed, err: %v", err), "hk", hk, "fcid", fcid)
if strings.Contains(err.Error(), wallet.ErrInsufficientBalance.Error()) {
Expand All @@ -1476,8 +1464,8 @@ func (c *contractor) refreshContract(ctx context.Context, w Worker, ci contractI
*budget = budget.Sub(renterFunds)

// persist the contract
contractPrice := newRevision.Revision.MissedHostPayout().Sub(newCollateral)
refreshedContract, err := c.ap.bus.AddRenewedContract(ctx, newRevision, contractPrice, renterFunds, cs.BlockHeight, contract.ID, api.ContractStatePending)
newCollateral := resp.Contract.Revision.MissedHostPayout().Sub(resp.ContractPrice)
refreshedContract, err := c.ap.bus.AddRenewedContract(ctx, resp.Contract, resp.ContractPrice, renterFunds, cs.BlockHeight, contract.ID, api.ContractStatePending)
if err != nil {
c.logger.Errorw(fmt.Sprintf("refresh failed, err: %v", err), "hk", hk, "fcid", fcid)
return api.ContractMetadata{}, false, err
Expand Down
13 changes: 9 additions & 4 deletions autopilot/hostfilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func isOutOfCollateral(c api.Contract, s rhpv2.HostSettings, pt rhpv3.HostPriceT
if expectedStorage > s.RemainingStorage {
expectedStorage = s.RemainingStorage
}
newCollateral := rhpv2.ContractRenewalCollateral(c.Revision.FileContract, expectedStorage, s, blockHeight, c.EndHeight())
newCollateral := worker.ContractRenewalCollateral(c.Revision.FileContract, expectedStorage, pt, blockHeight, c.EndHeight())
return isBelowCollateralThreshold(newCollateral, c.RemainingCollateral(s))
}

Expand All @@ -335,9 +335,14 @@ func isBelowCollateralThreshold(newCollateral, actualCollateral types.Currency)
// contracts out eventually.
return false
}
collateral := big.NewRat(0, 1).SetFrac(actualCollateral.Big(), newCollateral.Big())
threshold := big.NewRat(minContractCollateralThresholdNumerator, minContractCollateralThresholdDenominator)
return collateral.Cmp(threshold) < 0
return newCollateral.Cmp(minNewCollateral(actualCollateral)) < 0
}

// minNewCollateral returns the minimum amount of unallocated collateral that a
// contract should contain after a refresh given the current amount of
// unallocated collateral.
func minNewCollateral(unallocatedCollateral types.Currency) types.Currency {
return unallocatedCollateral.Mul64(minContractCollateralThresholdDenominator).Div64(minContractCollateralThresholdNumerator)
}

func isUpForRenewal(cfg api.AutopilotConfig, r types.FileContractRevision, blockHeight uint64) (shouldRenew, secondHalf bool) {
Expand Down
2 changes: 1 addition & 1 deletion autopilot/workerpool.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type Worker interface {
RHPFund(ctx context.Context, contractID types.FileContractID, hostKey types.PublicKey, hostIP, siamuxAddr string, balance types.Currency) (err error)
RHPPriceTable(ctx context.Context, hostKey types.PublicKey, siamuxAddr string, timeout time.Duration) (hostdb.HostPriceTable, error)
RHPPruneContract(ctx context.Context, fcid types.FileContractID, timeout time.Duration) (pruned, remaining uint64, err error)
RHPRenew(ctx context.Context, fcid types.FileContractID, endHeight uint64, hk types.PublicKey, hostIP string, hostAddress, renterAddress types.Address, renterFunds, newCollateral types.Currency, windowSize uint64) (rhpv2.ContractRevision, []types.Transaction, error)
RHPRenew(ctx context.Context, fcid types.FileContractID, endHeight uint64, hk types.PublicKey, hostIP string, hostAddress, renterAddress types.Address, renterFunds, minNewCollateral types.Currency, expectedStorage, windowSize uint64) (api.RHPRenewResponse, error)
RHPScan(ctx context.Context, hostKey types.PublicKey, hostIP string, timeout time.Duration) (api.RHPScanResponse, error)
RHPSync(ctx context.Context, contractID types.FileContractID, hostKey types.PublicKey, hostIP, siamuxAddr string) (err error)
}
Expand Down
4 changes: 2 additions & 2 deletions bus/bus.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,8 @@ func (b *bus) Handler() http.Handler {
"GET /host/:hostkey": b.hostsPubkeyHandlerGET,
"POST /host/:hostkey/resetlostsectors": b.hostsResetLostSectorsPOST,

"PUT /metric/:key": b.metricsHandlerPUT,
"GET /metric/:key": b.metricsHandlerGET,
"PUT /metric/:key": b.metricsHandlerPUT,
"GET /metric/:key": b.metricsHandlerGET,

"POST /multipart/create": b.multipartHandlerCreatePOST,
"POST /multipart/abort": b.multipartHandlerAbortPOST,
Expand Down
27 changes: 13 additions & 14 deletions worker/client/rhp.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,21 @@ func (c *Client) RHPPruneContract(ctx context.Context, contractID types.FileCont
}

// RHPRenew renews an existing contract with a host.
func (c *Client) RHPRenew(ctx context.Context, contractID types.FileContractID, endHeight uint64, hostKey types.PublicKey, siamuxAddr string, hostAddress, renterAddress types.Address, renterFunds, newCollateral types.Currency, windowSize uint64) (rhpv2.ContractRevision, []types.Transaction, error) {
func (c *Client) RHPRenew(ctx context.Context, contractID types.FileContractID, endHeight uint64, hostKey types.PublicKey, siamuxAddr string, hostAddress, renterAddress types.Address, renterFunds, minNewCollateral types.Currency, expectedStorage, windowSize uint64) (resp api.RHPRenewResponse, err error) {
req := api.RHPRenewRequest{
ContractID: contractID,
EndHeight: endHeight,
HostAddress: hostAddress,
HostKey: hostKey,
NewCollateral: newCollateral,
RenterAddress: renterAddress,
RenterFunds: renterFunds,
SiamuxAddr: siamuxAddr,
WindowSize: windowSize,
ContractID: contractID,
EndHeight: endHeight,
ExpectedStorage: expectedStorage,
HostAddress: hostAddress,
HostKey: hostKey,
MinNewCollateral: minNewCollateral,
RenterAddress: renterAddress,
RenterFunds: renterFunds,
SiamuxAddr: siamuxAddr,
WindowSize: windowSize,
}

var resp api.RHPRenewResponse
err := c.c.WithContext(ctx).POST("/rhp/renew", req, &resp)
return resp.Contract, resp.TransactionSet, err
err = c.c.WithContext(ctx).POST("/rhp/renew", req, &resp)
return
}

// RHPScan scans a host, returning its current settings.
Expand Down
50 changes: 46 additions & 4 deletions worker/rhpv3.go
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ type PriceTablePaymentFunc func(pt rhpv3.HostPriceTable) (rhpv3.PaymentMethod, e
// Renew renews a contract with a host. To avoid an edge case where the contract
// is drained and can therefore not be used to pay for the revision, we simply
// don't pay for it.
func (h *host) Renew(ctx context.Context, rrr api.RHPRenewRequest) (_ rhpv2.ContractRevision, _ []types.Transaction, err error) {
func (h *host) Renew(ctx context.Context, rrr api.RHPRenewRequest) (_ rhpv2.ContractRevision, _ []types.Transaction, _ types.Currency, err error) {
// Try to get a valid pricetable.
ptCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
Expand All @@ -935,21 +935,23 @@ func (h *host) Renew(ctx context.Context, rrr api.RHPRenewRequest) (_ rhpv2.Cont
h.logger.Debugf("unable to fetch price table for renew: %v", err)
}

var contractPrice types.Currency
var rev rhpv2.ContractRevision
var txnSet []types.Transaction
var renewErr error
err = h.transportPool.withTransportV3(ctx, h.HostKey(), h.siamuxAddr, func(ctx context.Context, t *transportV3) (err error) {
_, err = RPCLatestRevision(ctx, t, h.fcid, func(revision *types.FileContractRevision) (rhpv3.HostPriceTable, rhpv3.PaymentMethod, error) {
// Renew contract.
contractPrice = pt.ContractPrice
rev, txnSet, renewErr = RPCRenew(ctx, rrr, h.bus, t, pt, *revision, h.renterKey, h.logger)
return rhpv3.HostPriceTable{}, nil, nil
})
return err
})
if err != nil {
return rhpv2.ContractRevision{}, nil, err
return rhpv2.ContractRevision{}, nil, contractPrice, err
}
return rev, txnSet, renewErr
return rev, txnSet, contractPrice, renewErr
}

func (h *host) FetchPriceTable(ctx context.Context, rev *types.FileContractRevision) (hpt hostdb.HostPriceTable, err error) {
Expand Down Expand Up @@ -1386,9 +1388,15 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor
return rhpv2.ContractRevision{}, nil, fmt.Errorf("host gouging during renew: %v", breakdown.Reasons())
}

// Compute the additional collateral we can put into the contract.
newCollateral := ContractRenewalCollateral(rev.FileContract, rrr.ExpectedStorage, *pt, pt.HostBlockHeight, rrr.EndHeight)
if newCollateral.Cmp(rrr.MinNewCollateral) < 0 {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("newCollateral < minNewCollateral: %v < %v", newCollateral, rrr.MinNewCollateral)
}

// Prepare the signed transaction that contains the final revision as well
// as the new contract
wprr, err := bus.WalletPrepareRenew(ctx, rev, rrr.HostAddress, rrr.RenterAddress, renterKey, rrr.RenterFunds, rrr.NewCollateral, *pt, rrr.EndHeight, rrr.WindowSize)
wprr, err := bus.WalletPrepareRenew(ctx, rev, rrr.HostAddress, rrr.RenterAddress, renterKey, rrr.RenterFunds, newCollateral, *pt, rrr.EndHeight, rrr.WindowSize)
if err != nil {
return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to prepare renew: %w", err)
}
Expand Down Expand Up @@ -1571,3 +1579,37 @@ func payByContract(rev *types.FileContractRevision, amount types.Currency, refun
}
return payment, nil
}

// TODO: get this into core and eventually use that instead of this function
func ContractRenewalCollateral(fc types.FileContract, expectedNewStorage uint64, pt rhpv3.HostPriceTable, blockHeight, endHeight uint64) types.Currency {
if endHeight < fc.EndHeight() {
panic("endHeight should be at least the current end height of the contract")
}
extension := endHeight - fc.EndHeight()
if endHeight < blockHeight {
panic("current blockHeight should be lower than the endHeight")
}
duration := endHeight - blockHeight

// calculate the base collateral - if it exceeds MaxCollateral we can't add more collateral
baseCollateral := pt.CollateralCost.Mul64(fc.Filesize).Mul64(extension)
if baseCollateral.Cmp(pt.MaxCollateral) >= 0 {
return types.ZeroCurrency
}

// calculate the new collateral
newCollateral := pt.CollateralCost.Mul64(expectedNewStorage).Mul64(duration)

// if the total collateral is more than the MaxCollateral subtract the
// delta.
totalCollateral := baseCollateral.Add(newCollateral)
if totalCollateral.Cmp(pt.MaxCollateral) > 0 {
delta := totalCollateral.Sub(pt.MaxCollateral)
if delta.Cmp(newCollateral) > 0 {
newCollateral = types.ZeroCurrency
} else {
newCollateral = newCollateral.Sub(delta)
}
}
return newCollateral
}
6 changes: 4 additions & 2 deletions worker/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ type hostV3 interface {
FetchPriceTable(ctx context.Context, rev *types.FileContractRevision) (hpt hostdb.HostPriceTable, err error)
FetchRevision(ctx context.Context, fetchTimeout time.Duration, blockHeight uint64) (types.FileContractRevision, error)
FundAccount(ctx context.Context, balance types.Currency, rev *types.FileContractRevision) error
Renew(ctx context.Context, rrr api.RHPRenewRequest) (_ rhpv2.ContractRevision, _ []types.Transaction, err error)
Renew(ctx context.Context, rrr api.RHPRenewRequest) (_ rhpv2.ContractRevision, _ []types.Transaction, _ types.Currency, err error)
SyncAccount(ctx context.Context, rev *types.FileContractRevision) error
UploadSector(ctx context.Context, sector *[rhpv2.SectorSize]byte, rev types.FileContractRevision) (types.Hash256, error)
}
Expand Down Expand Up @@ -742,9 +742,10 @@ func (w *worker) rhpRenewHandler(jc jape.Context) {
// renew the contract
var renewed rhpv2.ContractRevision
var txnSet []types.Transaction
var contractPrice types.Currency
if jc.Check("couldn't renew contract", w.withRevision(ctx, defaultRevisionFetchTimeout, rrr.ContractID, rrr.HostKey, rrr.SiamuxAddr, lockingPriorityRenew, cs.BlockHeight, func(_ types.FileContractRevision) (err error) {
h := w.newHostV3(rrr.ContractID, rrr.HostKey, rrr.SiamuxAddr)
renewed, txnSet, err = h.Renew(ctx, rrr)
renewed, txnSet, contractPrice, err = h.Renew(ctx, rrr)
return err
})) != nil {
return
Expand All @@ -760,6 +761,7 @@ func (w *worker) rhpRenewHandler(jc jape.Context) {
jc.Encode(api.RHPRenewResponse{
ContractID: renewed.ID(),
Contract: renewed,
ContractPrice: contractPrice,
TransactionSet: txnSet,
})
}
Expand Down

0 comments on commit bb2be05

Please sign in to comment.