From 8def6147b97fc913d3e883137dc2ee1f057faab6 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Thu, 14 Dec 2023 12:57:45 +0100 Subject: [PATCH 1/3] worker: fix refresh panic --- api/contract.go | 6 +++--- autopilot/contractor.go | 35 +++++++++++++++++++++++++++++------ autopilot/hostfilter.go | 2 +- worker/host.go | 3 +-- worker/rhpv3.go | 31 +++++++++++++++++-------------- 5 files changed, 51 insertions(+), 26 deletions(-) diff --git a/api/contract.go b/api/contract.go index ae0cf2956..29f75ea28 100644 --- a/api/contract.go +++ b/api/contract.go @@ -206,9 +206,9 @@ func (c Contract) RenterFunds() types.Currency { } // RemainingCollateral returns the remaining collateral in the contract. -func (c Contract) RemainingCollateral(s rhpv2.HostSettings) types.Currency { - if c.Revision.MissedHostPayout().Cmp(s.ContractPrice) < 0 { +func (c Contract) RemainingCollateral() types.Currency { + if c.Revision.MissedHostPayout().Cmp(c.ContractPrice) < 0 { return types.ZeroCurrency } - return c.Revision.MissedHostPayout().Sub(s.ContractPrice) + return c.Revision.MissedHostPayout().Sub(c.ContractPrice) } diff --git a/autopilot/contractor.go b/autopilot/contractor.go index eb2b66763..deac9ee42 100644 --- a/autopilot/contractor.go +++ b/autopilot/contractor.go @@ -1441,10 +1441,15 @@ func (c *contractor) refreshContract(ctx context.Context, w Worker, ci contractI } // calculate the renter funds - renterFunds, err := c.refreshFundingEstimate(ctx, state.cfg, ci, state.fee) - if err != nil { - c.logger.Errorw(fmt.Sprintf("could not get refresh funding estimate, err: %v", err), "hk", hk, "fcid", fcid) - return api.ContractMetadata{}, true, err + var renterFunds types.Currency + if isOutOfFunds(state.cfg, ci.settings, ci.contract) { + renterFunds, err = c.refreshFundingEstimate(ctx, state.cfg, ci, state.fee) + if err != nil { + c.logger.Errorw(fmt.Sprintf("could not get refresh funding estimate, err: %v", err), "hk", hk, "fcid", fcid) + return api.ContractMetadata{}, true, err + } + } else { + renterFunds = rev.ValidRenterPayout() // don't increase funds } // check our budget @@ -1456,11 +1461,28 @@ func (c *contractor) refreshContract(ctx context.Context, w Worker, ci contractI // calculate the new collateral expectedStorage := renterFundsToExpectedStorage(renterFunds, contract.EndHeight()-cs.BlockHeight, ci.priceTable) unallocatedCollateral := rev.MissedHostPayout().Sub(contract.ContractPrice) - minNewCollateral := minNewCollateral(unallocatedCollateral) + + // calculate the expected new collateral to determine the minNewCollateral. + // If the contract isn't below the min collateral, we don't enforce a + // minimum. + var minNewColl types.Currency + _, _, expectedNewCollateral := rhpv3.RenewalCosts(contract.Revision.FileContract, ci.priceTable, expectedStorage, contract.EndHeight()) + if isBelowCollateralThreshold(expectedNewCollateral, unallocatedCollateral) { + minNewColl = minNewCollateral(unallocatedCollateral) + } // renew the contract - resp, err := w.RHPRenew(ctx, contract.ID, contract.EndHeight(), hk, contract.SiamuxAddr, settings.Address, state.address, renterFunds, minNewCollateral, expectedStorage, settings.WindowSize) + resp, err := w.RHPRenew(ctx, contract.ID, contract.EndHeight(), hk, contract.SiamuxAddr, settings.Address, state.address, renterFunds, minNewColl, expectedStorage, settings.WindowSize) if err != nil { + if strings.Contains(err.Error(), "new collateral is too low") { + c.logger.Debugw("refresh failed: contract wouldn't have enough collateral after refresh", + "hk", hk, + "fcid", fcid, + "unallocatedCollateral", unallocatedCollateral.String(), + "minNewCollateral", minNewColl.String(), + ) + return api.ContractMetadata{}, true, err + } c.logger.Errorw("refresh failed", zap.Error(err), "hk", hk, "fcid", fcid) if strings.Contains(err.Error(), wallet.ErrInsufficientBalance.Error()) { return api.ContractMetadata{}, false, err @@ -1484,6 +1506,7 @@ func (c *contractor) refreshContract(ctx context.Context, w Worker, ci contractI "fcid", refreshedContract.ID, "renewedFrom", contract.ID, "renterFunds", renterFunds.String(), + "minNewCollateral", minNewColl.String(), "newCollateral", newCollateral.String(), ) return refreshedContract, true, nil diff --git a/autopilot/hostfilter.go b/autopilot/hostfilter.go index 22ddb9bae..a8a674e0a 100644 --- a/autopilot/hostfilter.go +++ b/autopilot/hostfilter.go @@ -313,7 +313,7 @@ func isOutOfCollateral(c api.Contract, s rhpv2.HostSettings, pt rhpv3.HostPriceT expectedStorage = s.RemainingStorage } _, _, newCollateral := rhpv3.RenewalCosts(c.Revision.FileContract, pt, expectedStorage, c.EndHeight()) - return isBelowCollateralThreshold(newCollateral, c.RemainingCollateral(s)) + return isBelowCollateralThreshold(newCollateral, c.RemainingCollateral()) } // isBelowCollateralThreshold returns true if the remainingCollateral is below a diff --git a/worker/host.go b/worker/host.go index ccf9f5645..7f6d28d72 100644 --- a/worker/host.go +++ b/worker/host.go @@ -167,8 +167,7 @@ func (h *host) RenewContract(ctx context.Context, rrr api.RHPRenewRequest) (_ rh err = h.transportPool.withTransportV3(ctx, h.hk, 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) + rev, txnSet, contractPrice, renewErr = RPCRenew(ctx, rrr, h.bus, t, pt, *revision, h.renterKey, h.logger) return rhpv3.HostPriceTable{}, nil, nil }) return err diff --git a/worker/rhpv3.go b/worker/rhpv3.go index 15bc96575..45cb6ad0d 100644 --- a/worker/rhpv3.go +++ b/worker/rhpv3.go @@ -1184,11 +1184,11 @@ func RPCAppendSector(ctx context.Context, t *transportV3, renterKey types.Privat return } -func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transportV3, pt *rhpv3.HostPriceTable, rev types.FileContractRevision, renterKey types.PrivateKey, l *zap.SugaredLogger) (_ rhpv2.ContractRevision, _ []types.Transaction, err error) { +func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transportV3, pt *rhpv3.HostPriceTable, rev types.FileContractRevision, renterKey types.PrivateKey, l *zap.SugaredLogger) (_ rhpv2.ContractRevision, _ []types.Transaction, _ types.Currency, err error) { defer wrapErr(&err, "RPCRenew") s, err := t.DialStream(ctx) if err != nil { - return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to dial stream: %w", err) + return rhpv2.ContractRevision{}, nil, types.ZeroCurrency, fmt.Errorf("failed to dial stream: %w", err) } defer s.Close() @@ -1198,7 +1198,7 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor ptUID = pt.UID } if err = s.WriteRequest(rhpv3.RPCRenewContractID, &ptUID); err != nil { - return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to send ptUID: %w", err) + return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to send ptUID: %w", err) } // If we didn't have a valid pricetable, read the temporary one from the @@ -1206,28 +1206,31 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor if ptUID == (rhpv3.SettingsID{}) { var ptResp rhpv3.RPCUpdatePriceTableResponse if err = s.ReadResponse(&ptResp, defaultRPCResponseMaxSize); err != nil { - return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to read RPCUpdatePriceTableResponse: %w", err) + return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to read RPCUpdatePriceTableResponse: %w", err) } pt = new(rhpv3.HostPriceTable) if err = json.Unmarshal(ptResp.PriceTableJSON, pt); err != nil { - return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to unmarshal price table: %w", err) + return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to unmarshal price table: %w", err) } } + // Remember contract price. + contractPrice := pt.ContractPrice + // Perform gouging checks. gc, err := GougingCheckerFromContext(ctx, false) if err != nil { - return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to get gouging checker: %w", err) + return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to get gouging checker: %w", err) } if breakdown := gc.Check(nil, pt); breakdown.Gouging() { - return rhpv2.ContractRevision{}, nil, fmt.Errorf("host gouging during renew: %v", breakdown) + return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("host gouging during renew: %v", breakdown) } // 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.MinNewCollateral, *pt, rrr.EndHeight, rrr.WindowSize, rrr.ExpectedNewStorage) if err != nil { - return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to prepare renew: %w", err) + return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to prepare renew: %w", err) } // Starting from here, we need to make sure to release the txn on error. @@ -1250,13 +1253,13 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor FinalRevisionSignature: finalRevisionSignature, } if err = s.WriteResponse(&req); err != nil { - return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to send RPCRenewContractRequest: %w", err) + return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to send RPCRenewContractRequest: %w", err) } // Incorporate the host's additions. var hostAdditions rhpv3.RPCRenewContractHostAdditions if err = s.ReadResponse(&hostAdditions, defaultRPCResponseMaxSize); err != nil { - return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to read RPCRenewContractHostAdditions: %w", err) + return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to read RPCRenewContractHostAdditions: %w", err) } parents = append(parents, hostAdditions.Parents...) txn.SiacoinInputs = append(txn.SiacoinInputs, hostAdditions.SiacoinInputs...) @@ -1288,7 +1291,7 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor Signatures: []uint64{0, 1}, } if err := bus.WalletSign(ctx, &txn, wprr.ToSign, cf); err != nil { - return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to sign transaction: %w", err) + return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to sign transaction: %w", err) } // Create a new no-op revision and sign it. @@ -1312,13 +1315,13 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor RevisionSignature: renterNoOpRevisionSignature, } if err = s.WriteResponse(&rs); err != nil { - return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to send RPCRenewSignatures: %w", err) + return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to send RPCRenewSignatures: %w", err) } // Receive the host's signatures. var hostSigs rhpv3.RPCRenewSignatures if err = s.ReadResponse(&hostSigs, defaultRPCResponseMaxSize); err != nil { - return rhpv2.ContractRevision{}, nil, fmt.Errorf("failed to read RPCRenewSignatures: %w", err) + return rhpv2.ContractRevision{}, nil, types.Currency{}, fmt.Errorf("failed to read RPCRenewSignatures: %w", err) } txn.Signatures = append(txn.Signatures, hostSigs.TransactionSignatures...) @@ -1328,7 +1331,7 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor return rhpv2.ContractRevision{ Revision: noOpRevision, Signatures: [2]types.TransactionSignature{renterNoOpRevisionSignature, hostSigs.RevisionSignature}, - }, txnSet, nil + }, txnSet, contractPrice, nil } // initialRevision returns the first revision of a file contract formation From aa521cc91d639ee4586bc64e798fa1f69d155e6b Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Thu, 14 Dec 2023 15:56:23 +0100 Subject: [PATCH 2/3] cmd: update version --- cmd/renterd/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/renterd/main.go b/cmd/renterd/main.go index 9a7004999..a884a3c42 100644 --- a/cmd/renterd/main.go +++ b/cmd/renterd/main.go @@ -296,7 +296,7 @@ func main() { flag.Parse() - log.Println("renterd v0.6.0") + log.Println("renterd v0.7.1") log.Println("Network", build.NetworkName()) if flag.Arg(0) == "version" { log.Println("Commit:", githash) From ab1f39107befa9c619c19f4bbb3ee393dc3fe962 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Thu, 14 Dec 2023 17:38:31 +0100 Subject: [PATCH 3/3] autopilot: address comments --- autopilot/contractor.go | 1 - worker/rhpv3.go | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/autopilot/contractor.go b/autopilot/contractor.go index deac9ee42..16666d16c 100644 --- a/autopilot/contractor.go +++ b/autopilot/contractor.go @@ -1458,7 +1458,6 @@ func (c *contractor) refreshContract(ctx context.Context, w Worker, ci contractI return api.ContractMetadata{}, false, fmt.Errorf("insufficient budget: %s < %s", budget.String(), renterFunds.String()) } - // calculate the new collateral expectedStorage := renterFundsToExpectedStorage(renterFunds, contract.EndHeight()-cs.BlockHeight, ci.priceTable) unallocatedCollateral := rev.MissedHostPayout().Sub(contract.ContractPrice) diff --git a/worker/rhpv3.go b/worker/rhpv3.go index 45cb6ad0d..06254e0f0 100644 --- a/worker/rhpv3.go +++ b/worker/rhpv3.go @@ -1214,9 +1214,6 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor } } - // Remember contract price. - contractPrice := pt.ContractPrice - // Perform gouging checks. gc, err := GougingCheckerFromContext(ctx, false) if err != nil { @@ -1331,7 +1328,7 @@ func RPCRenew(ctx context.Context, rrr api.RHPRenewRequest, bus Bus, t *transpor return rhpv2.ContractRevision{ Revision: noOpRevision, Signatures: [2]types.TransactionSignature{renterNoOpRevisionSignature, hostSigs.RevisionSignature}, - }, txnSet, contractPrice, nil + }, txnSet, pt.ContractPrice, nil } // initialRevision returns the first revision of a file contract formation