diff --git a/consensus/validation.go b/consensus/validation.go index 66cb2ce..61f09e6 100644 --- a/consensus/validation.go +++ b/consensus/validation.go @@ -780,12 +780,24 @@ func validateV2FileContracts(ms *MidState, txn types.V2Transaction) error { case *types.V2FileContractRenewal: renewal := *r + if fc.RenterPublicKey != renewal.NewContract.RenterPublicKey { + return fmt.Errorf("file contract renewal %v changes renter public key", i) + } else if fc.HostPublicKey != renewal.NewContract.HostPublicKey { + return fmt.Errorf("file contract renewal %v changes host public key", i) + } + + // validate that the renewal value is equal to existing contract's value. + // This must be done as a sum of the outputs, since the individual payouts may have + // changed in an unbroadcast revision. + totalPayout := renewal.FinalRenterOutput.Value.Add(renewal.RenterRollover). + Add(renewal.FinalHostOutput.Value).Add(renewal.HostRollover) + existingPayout := fc.RenterOutput.Value.Add(fc.HostOutput.Value) + if totalPayout != existingPayout { + return fmt.Errorf("file contract renewal %d renewal payout (%s) does not match existing contract payout %s", i, totalPayout, existingPayout) + } + newContractCost := renewal.NewContract.RenterOutput.Value.Add(renewal.NewContract.HostOutput.Value).Add(ms.base.V2FileContractTax(renewal.NewContract)) - if totalRenter := renewal.FinalRenterOutput.Value.Add(renewal.RenterRollover); totalRenter != fc.RenterOutput.Value { - return fmt.Errorf("file contract renewal %v renter payout plus rollover (%d H) does not match old contract payout (%d H)", i, totalRenter, fc.RenterOutput.Value) - } else if totalHost := renewal.FinalHostOutput.Value.Add(renewal.HostRollover); totalHost != fc.HostOutput.Value { - return fmt.Errorf("file contract renewal %v host payout plus rollover (%d H) does not match old contract payout (%d H)", i, totalHost, fc.HostOutput.Value) - } else if rollover := renewal.RenterRollover.Add(renewal.HostRollover); rollover.Cmp(newContractCost) > 0 { + if rollover := renewal.RenterRollover.Add(renewal.HostRollover); rollover.Cmp(newContractCost) > 0 { return fmt.Errorf("file contract renewal %v has rollover (%d H) exceeding new contract cost (%d H)", i, rollover, newContractCost) } else if err := validateContract(renewal.NewContract); err != nil { return fmt.Errorf("file contract renewal %v initial revision %s", i, err) diff --git a/consensus/validation_test.go b/consensus/validation_test.go index 685504a..12fc671 100644 --- a/consensus/validation_test.go +++ b/consensus/validation_test.go @@ -1925,6 +1925,28 @@ func TestV2RenewalResolution(t *testing.T) { }, errString: "invalid host signature", }, + { + desc: "invalid renewal - different host key", + renewFn: func(txn *types.V2Transaction) { + renewal := txn.FileContractResolutions[0].Resolution.(*types.V2FileContractRenewal) + sk := types.GeneratePrivateKey() + renewal.NewContract.HostPublicKey = sk.PublicKey() + contractSigHash := cs.ContractSigHash(renewal.NewContract) + renewal.NewContract.HostSignature = sk.SignHash(contractSigHash) + }, + errString: "changes host public key", + }, + { + desc: "invalid renewal - different renter key", + renewFn: func(txn *types.V2Transaction) { + renewal := txn.FileContractResolutions[0].Resolution.(*types.V2FileContractRenewal) + sk := types.GeneratePrivateKey() + renewal.NewContract.RenterPublicKey = sk.PublicKey() + contractSigHash := cs.ContractSigHash(renewal.NewContract) + renewal.NewContract.RenterSignature = sk.SignHash(contractSigHash) + }, + errString: "changes renter public key", + }, { desc: "invalid renewal - not enough host funds", renewFn: func(txn *types.V2Transaction) { diff --git a/rhp/v4/encoding.go b/rhp/v4/encoding.go index 238986a..8172400 100644 --- a/rhp/v4/encoding.go +++ b/rhp/v4/encoding.go @@ -263,10 +263,12 @@ func (r *RPCRenewContractResponse) maxLen() int { func (r *RPCRenewContractSecondResponse) encodeTo(e *types.Encoder) { r.RenterRenewalSignature.EncodeTo(e) + r.RenterContractSignature.EncodeTo(e) types.EncodeSlice(e, r.RenterSatisfiedPolicies) } func (r *RPCRenewContractSecondResponse) decodeFrom(d *types.Decoder) { r.RenterRenewalSignature.DecodeFrom(d) + r.RenterContractSignature.DecodeFrom(d) types.DecodeSlice(d, &r.RenterSatisfiedPolicies) } func (r *RPCRenewContractSecondResponse) maxLen() int { @@ -331,10 +333,12 @@ func (r *RPCRefreshContractResponse) maxLen() int { func (r *RPCRefreshContractSecondResponse) encodeTo(e *types.Encoder) { r.RenterRenewalSignature.EncodeTo(e) + r.RenterContractSignature.EncodeTo(e) types.EncodeSlice(e, r.RenterSatisfiedPolicies) } func (r *RPCRefreshContractSecondResponse) decodeFrom(d *types.Decoder) { r.RenterRenewalSignature.DecodeFrom(d) + r.RenterContractSignature.DecodeFrom(d) types.DecodeSlice(d, &r.RenterSatisfiedPolicies) } func (r *RPCRefreshContractSecondResponse) maxLen() int { diff --git a/rhp/v4/rhp.go b/rhp/v4/rhp.go index 55fb2b3..3e454c4 100644 --- a/rhp/v4/rhp.go +++ b/rhp/v4/rhp.go @@ -310,6 +310,7 @@ type ( // RPCRefreshContractSecondResponse implements Object. RPCRefreshContractSecondResponse struct { RenterRenewalSignature types.Signature `json:"renterRenewalSignature"` + RenterContractSignature types.Signature `json:"renterContractSignature"` RenterSatisfiedPolicies []types.SatisfiedPolicy `json:"renterSatisfiedPolicies"` } // RPCRefreshContractThirdResponse implements Object. @@ -344,6 +345,7 @@ type ( // RPCRenewContractSecondResponse implements Object. RPCRenewContractSecondResponse struct { RenterRenewalSignature types.Signature `json:"renterRenewalSignature"` + RenterContractSignature types.Signature `json:"renterContractSignature"` RenterSatisfiedPolicies []types.SatisfiedPolicy `json:"renterSatisfiedPolicies"` } // RPCRenewContractThirdResponse implements Object.