From 0534a5928ddbc5851a4aa78254b7d675490e6a7c Mon Sep 17 00:00:00 2001 From: Nate Date: Tue, 10 Dec 2024 14:44:38 -0800 Subject: [PATCH 1/3] fix(rhp4): Include storage cost in renter renewal cost --- rhp/v4/rhp.go | 3 +- rhp/v4/rhp_test.go | 210 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+), 1 deletion(-) diff --git a/rhp/v4/rhp.go b/rhp/v4/rhp.go index 3e454c4..05d1f16 100644 --- a/rhp/v4/rhp.go +++ b/rhp/v4/rhp.go @@ -582,7 +582,8 @@ func ContractCost(cs consensus.State, p HostPrices, fc types.V2FileContract, min // RenewalCost calculates the cost to the host and renter for renewing a contract. func RenewalCost(cs consensus.State, p HostPrices, r types.V2FileContractRenewal, minerFee types.Currency) (renter, host types.Currency) { - renter = r.NewContract.RenterOutput.Value.Add(p.ContractPrice).Add(minerFee).Add(cs.V2FileContractTax(r.NewContract)).Sub(r.RenterRollover) + contractCost := r.NewContract.HostOutput.Value.Sub(r.NewContract.TotalCollateral) // (contract price + storage cost + locked collateral) - locked collateral + renter = r.NewContract.RenterOutput.Value.Add(contractCost).Add(minerFee).Add(cs.V2FileContractTax(r.NewContract)).Sub(r.RenterRollover) host = r.NewContract.TotalCollateral.Sub(r.HostRollover) return } diff --git a/rhp/v4/rhp_test.go b/rhp/v4/rhp_test.go index ae4f0f0..295094b 100644 --- a/rhp/v4/rhp_test.go +++ b/rhp/v4/rhp_test.go @@ -3,6 +3,7 @@ package rhp import ( "testing" + "go.sia.tech/core/consensus" "go.sia.tech/core/types" ) @@ -19,3 +20,212 @@ func TestMinRenterAllowance(t *testing.T) { t.Fatalf("expected %v, got %v", expected, minAllowance) } } + +func TestRenewalCost(t *testing.T) { + const ( + initialProofHeight = 1000 + initialExpiration = initialProofHeight + ProofWindow + + renewalHeight = 150 + extension = 10 + renewalProofHeight = initialProofHeight + extension + renewalExpiration = renewalProofHeight + ProofWindow + renewalDuration = renewalExpiration - renewalHeight + ) + cs := consensus.State{} + prices := HostPrices{ + ContractPrice: types.NewCurrency64(100), + Collateral: types.NewCurrency64(200), + StoragePrice: types.NewCurrency64(300), + IngressPrice: types.NewCurrency64(400), + EgressPrice: types.NewCurrency64(500), + FreeSectorPrice: types.NewCurrency64(600), + } + renterKey, hostKey := types.GeneratePrivateKey().PublicKey(), types.GeneratePrivateKey().PublicKey() + + type testCase struct { + Description string + Modify func(*types.V2FileContract, *RPCRenewContractParams) + RenterCost types.Currency + HostCost types.Currency + } + + cases := []testCase{ + { + Description: "empty", + Modify: func(*types.V2FileContract, *RPCRenewContractParams) {}, + RenterCost: prices.ContractPrice, + }, + { + Description: "no storage", + Modify: func(rev *types.V2FileContract, p *RPCRenewContractParams) { + p.Allowance = rev.RenterOutput.Value.Add(types.Siacoins(20)) + p.Collateral = rev.TotalCollateral.Add(types.Siacoins(10)) + }, + RenterCost: types.Siacoins(20).Add(prices.ContractPrice), + HostCost: types.Siacoins(10), + }, + { + Description: "no storage - no renter rollover", + Modify: func(rev *types.V2FileContract, p *RPCRenewContractParams) { + p.Allowance = rev.RenterOutput.Value.Add(types.Siacoins(20)) + p.Collateral = rev.TotalCollateral.Add(types.Siacoins(10)) + // transfer all of the renter funds to the host so the renter will need to put up the entire allowance + rev.HostOutput.Value, rev.RenterOutput.Value = rev.HostOutput.Value.Add(rev.RenterOutput.Value), types.ZeroCurrency + }, + RenterCost: types.Siacoins(320).Add(prices.ContractPrice), + HostCost: types.Siacoins(10), + }, + { + Description: "renewed storage - no additional funds", + Modify: func(rev *types.V2FileContract, p *RPCRenewContractParams) { + // add storage + rev.Capacity = SectorSize + rev.Filesize = SectorSize + }, + RenterCost: prices.ContractPrice.Add(prices.StoragePrice.Mul64(SectorSize).Mul64(extension)), // storage cost is calculated for just the extension + HostCost: types.ZeroCurrency, // collateral lock up is less than rollover + }, + { + Description: "renewed storage", + Modify: func(rev *types.V2FileContract, p *RPCRenewContractParams) { + // add storage + rev.Capacity = SectorSize + rev.Filesize = SectorSize + + // adjust the renewal params + p.Allowance = rev.RenterOutput.Value.Add(types.Siacoins(20)) + p.Collateral = rev.TotalCollateral.Add(types.Siacoins(10)) + }, + RenterCost: types.Siacoins(20).Add(prices.ContractPrice).Add(prices.StoragePrice.Mul64(SectorSize).Mul64(extension)), // storage cost is calculated for just the extension + HostCost: types.Siacoins(10).Add(prices.Collateral.Mul64(SectorSize).Mul64(renewalDuration)), // collateral is calculated for the full duration + }, + { + Description: "renewed storage - no renter rollover", + Modify: func(rev *types.V2FileContract, p *RPCRenewContractParams) { + // adjust the renewal params + p.Allowance = rev.RenterOutput.Value.Add(types.Siacoins(20)) + p.Collateral = rev.TotalCollateral.Add(types.Siacoins(10)) + + // add storage + rev.Capacity = SectorSize + rev.Filesize = SectorSize + // transfer all the renter funds to the host so the renter will need to put up more allowance + rev.HostOutput.Value, rev.RenterOutput.Value = rev.HostOutput.Value.Add(rev.RenterOutput.Value), types.ZeroCurrency + }, + RenterCost: types.Siacoins(320).Add(prices.ContractPrice).Add(prices.StoragePrice.Mul64(SectorSize).Mul64(extension)), // storage cost is calculated for just the extension + HostCost: types.Siacoins(10).Add(prices.Collateral.Mul64(SectorSize).Mul64(renewalDuration)), // collateral is calculated for the full duration + }, + } + for _, tc := range cases { + t.Run(tc.Description, func(t *testing.T) { + contract, _ := NewContract(prices, RPCFormContractParams{ + RenterPublicKey: renterKey, + RenterAddress: types.StandardAddress(renterKey), + Allowance: types.Siacoins(300), + Collateral: types.Siacoins(400), + ProofHeight: initialProofHeight, + }, hostKey, types.StandardAddress(hostKey)) + + params := RPCRenewContractParams{ + ProofHeight: renewalProofHeight, + } + tc.Modify(&contract, ¶ms) + + prices.TipHeight = renewalHeight + renewal, _ := RenewContract(contract, prices, params) + tax := cs.V2FileContractTax(renewal.NewContract) + renter, host := RenewalCost(cs, prices, renewal, types.ZeroCurrency) + if !renter.Equals(tc.RenterCost.Add(tax)) { + t.Errorf("expected renter cost %v, got %v", tc.RenterCost, renter.Sub(tax)) + } else if !host.Equals(tc.HostCost) { + t.Errorf("expected host cost %v, got %v", tc.HostCost, host) + } + }) + } +} + +func TestRefreshCost(t *testing.T) { + const initialProofHeight = 1000 + + cs := consensus.State{} + prices := HostPrices{ + ContractPrice: types.NewCurrency64(100), + Collateral: types.NewCurrency64(200), + StoragePrice: types.NewCurrency64(300), + IngressPrice: types.NewCurrency64(400), + EgressPrice: types.NewCurrency64(500), + FreeSectorPrice: types.NewCurrency64(600), + } + renterKey, hostKey := types.GeneratePrivateKey().PublicKey(), types.GeneratePrivateKey().PublicKey() + + type testCase struct { + Description string + Modify func(*types.V2FileContract) + } + + cases := []testCase{ + { + Description: "no storage", + Modify: func(rev *types.V2FileContract) {}, + }, + { + Description: "no storage - no renter rollover", + Modify: func(rev *types.V2FileContract) { + // transfer all of the renter funds to the host so the renter rolls over nothing + rev.HostOutput.Value, rev.RenterOutput.Value = rev.HostOutput.Value.Add(rev.RenterOutput.Value), types.ZeroCurrency + }, + }, + { + Description: "renewed storage", + Modify: func(rev *types.V2FileContract) { + // add storage + rev.Capacity = SectorSize + rev.Filesize = SectorSize + }, + }, + { + Description: "renewed storage - no renter rollover", + Modify: func(rev *types.V2FileContract) { + // add storage + rev.Capacity = SectorSize + rev.Filesize = SectorSize + // transfer all the renter funds to the host + rev.HostOutput.Value, rev.RenterOutput.Value = rev.HostOutput.Value.Add(rev.RenterOutput.Value), types.ZeroCurrency + }, + }, + } + + // the actual cost to the renter and host should always be the additional allowance and collateral + // on top of the existing contract costs + additionalAllowance, additionalCollateral := types.Siacoins(20), types.Siacoins(10) + renterCost := additionalAllowance.Add(prices.ContractPrice) + hostCost := additionalCollateral + + for _, tc := range cases { + t.Run(tc.Description, func(t *testing.T) { + contract, _ := NewContract(prices, RPCFormContractParams{ + RenterPublicKey: renterKey, + RenterAddress: types.StandardAddress(renterKey), + Allowance: types.Siacoins(300), + Collateral: types.Siacoins(400), + ProofHeight: initialProofHeight, + }, hostKey, types.StandardAddress(hostKey)) + + params := RPCRefreshContractParams{ + Allowance: additionalAllowance, + Collateral: additionalCollateral, + } + tc.Modify(&contract) + + refresh, _ := RefreshContract(contract, prices, params) + tax := cs.V2FileContractTax(refresh.NewContract) + renter, host := RefreshCost(cs, prices, refresh, types.ZeroCurrency) + if !renter.Equals(renterCost.Add(tax)) { + t.Errorf("expected renter cost %v, got %v", renterCost, renter.Sub(tax)) + } else if !host.Equals(hostCost) { + t.Errorf("expected host cost %v, got %v", hostCost, host) + } + }) + } +} From 8f10c6e4728a64b6f025e6bdce496ce0887d1b95 Mon Sep 17 00:00:00 2001 From: Nate Date: Wed, 11 Dec 2024 08:45:35 -0800 Subject: [PATCH 2/3] add miner fee --- rhp/v4/rhp_test.go | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/rhp/v4/rhp_test.go b/rhp/v4/rhp_test.go index 295094b..3d55271 100644 --- a/rhp/v4/rhp_test.go +++ b/rhp/v4/rhp_test.go @@ -1,10 +1,12 @@ package rhp import ( + "math" "testing" "go.sia.tech/core/consensus" "go.sia.tech/core/types" + "lukechampine.com/frand" ) func TestMinRenterAllowance(t *testing.T) { @@ -41,6 +43,7 @@ func TestRenewalCost(t *testing.T) { EgressPrice: types.NewCurrency64(500), FreeSectorPrice: types.NewCurrency64(600), } + minerFee := types.NewCurrency64(frand.Uint64n(math.MaxUint64)) renterKey, hostKey := types.GeneratePrivateKey().PublicKey(), types.GeneratePrivateKey().PublicKey() type testCase struct { @@ -135,12 +138,18 @@ func TestRenewalCost(t *testing.T) { prices.TipHeight = renewalHeight renewal, _ := RenewContract(contract, prices, params) tax := cs.V2FileContractTax(renewal.NewContract) - renter, host := RenewalCost(cs, prices, renewal, types.ZeroCurrency) - if !renter.Equals(tc.RenterCost.Add(tax)) { - t.Errorf("expected renter cost %v, got %v", tc.RenterCost, renter.Sub(tax)) + renter, host := RenewalCost(cs, prices, renewal, minerFee) + if !renter.Equals(tc.RenterCost.Add(tax).Add(minerFee)) { + t.Errorf("expected renter cost %v, got %v", tc.RenterCost, renter.Sub(tax).Sub(minerFee)) } else if !host.Equals(tc.HostCost) { t.Errorf("expected host cost %v, got %v", tc.HostCost, host) } + + contractTotal := renewal.NewContract.HostOutput.Value.Add(renewal.NewContract.RenterOutput.Value) + totalCost := renter.Add(host).Add(renewal.HostRollover).Add(renewal.RenterRollover).Sub(tax).Sub(minerFee) + if !contractTotal.Equals(totalCost) { + t.Fatalf("expected contract sum %v, got %v", contractTotal, totalCost) + } }) } } @@ -158,6 +167,7 @@ func TestRefreshCost(t *testing.T) { FreeSectorPrice: types.NewCurrency64(600), } renterKey, hostKey := types.GeneratePrivateKey().PublicKey(), types.GeneratePrivateKey().PublicKey() + minerFee := types.NewCurrency64(frand.Uint64n(math.MaxUint64)) type testCase struct { Description string @@ -220,12 +230,18 @@ func TestRefreshCost(t *testing.T) { refresh, _ := RefreshContract(contract, prices, params) tax := cs.V2FileContractTax(refresh.NewContract) - renter, host := RefreshCost(cs, prices, refresh, types.ZeroCurrency) - if !renter.Equals(renterCost.Add(tax)) { - t.Errorf("expected renter cost %v, got %v", renterCost, renter.Sub(tax)) + renter, host := RefreshCost(cs, prices, refresh, minerFee) + if !renter.Equals(renterCost.Add(tax).Add(minerFee)) { + t.Errorf("expected renter cost %v, got %v", renterCost, renter.Sub(tax).Sub(minerFee)) } else if !host.Equals(hostCost) { t.Errorf("expected host cost %v, got %v", hostCost, host) } + + contractTotal := refresh.NewContract.HostOutput.Value.Add(refresh.NewContract.RenterOutput.Value) + totalCost := renter.Add(host).Add(refresh.HostRollover).Add(refresh.RenterRollover).Sub(tax).Sub(minerFee) + if !contractTotal.Equals(totalCost) { + t.Fatalf("expected contract sum %v, got %v", contractTotal, totalCost) + } }) } } From b7ffe9568fd479db5a387edb3c85f46037db6957 Mon Sep 17 00:00:00 2001 From: Nate Date: Wed, 11 Dec 2024 08:51:19 -0800 Subject: [PATCH 3/3] rhp4: add cases for capacity --- rhp/v4/rhp_test.go | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/rhp/v4/rhp_test.go b/rhp/v4/rhp_test.go index 3d55271..0e21b0e 100644 --- a/rhp/v4/rhp_test.go +++ b/rhp/v4/rhp_test.go @@ -89,6 +89,16 @@ func TestRenewalCost(t *testing.T) { RenterCost: prices.ContractPrice.Add(prices.StoragePrice.Mul64(SectorSize).Mul64(extension)), // storage cost is calculated for just the extension HostCost: types.ZeroCurrency, // collateral lock up is less than rollover }, + { + Description: "renewed storage - greater capacity", + Modify: func(rev *types.V2FileContract, p *RPCRenewContractParams) { + // add storage + rev.Capacity = SectorSize * 2 + rev.Filesize = SectorSize + }, + RenterCost: prices.ContractPrice.Add(prices.StoragePrice.Mul64(SectorSize).Mul64(extension)), // storage cost is calculated for just the filesize & extension + HostCost: types.ZeroCurrency, // collateral lock up is less than rollover + }, { Description: "renewed storage", Modify: func(rev *types.V2FileContract, p *RPCRenewContractParams) { @@ -147,8 +157,13 @@ func TestRenewalCost(t *testing.T) { contractTotal := renewal.NewContract.HostOutput.Value.Add(renewal.NewContract.RenterOutput.Value) totalCost := renter.Add(host).Add(renewal.HostRollover).Add(renewal.RenterRollover).Sub(tax).Sub(minerFee) - if !contractTotal.Equals(totalCost) { + switch { + case !contractTotal.Equals(totalCost): t.Fatalf("expected contract sum %v, got %v", contractTotal, totalCost) + case contract.Filesize != renewal.NewContract.Filesize: + t.Fatalf("expected contract size %d, got %d", contract.Filesize, renewal.NewContract.Filesize) + case contract.Filesize != renewal.NewContract.Capacity: // renewals reset capacity + t.Fatalf("expected contract capacity %d, got %d", contract.Filesize, renewal.NewContract.Capacity) } }) } @@ -194,6 +209,14 @@ func TestRefreshCost(t *testing.T) { rev.Filesize = SectorSize }, }, + { + Description: "renewed storage - greater capacity", + Modify: func(rev *types.V2FileContract) { + // add storage + rev.Capacity = SectorSize * 4 + rev.Filesize = SectorSize + }, + }, { Description: "renewed storage - no renter rollover", Modify: func(rev *types.V2FileContract) { @@ -239,8 +262,14 @@ func TestRefreshCost(t *testing.T) { contractTotal := refresh.NewContract.HostOutput.Value.Add(refresh.NewContract.RenterOutput.Value) totalCost := renter.Add(host).Add(refresh.HostRollover).Add(refresh.RenterRollover).Sub(tax).Sub(minerFee) - if !contractTotal.Equals(totalCost) { + + switch { + case !contractTotal.Equals(totalCost): t.Fatalf("expected contract sum %v, got %v", contractTotal, totalCost) + case contract.Filesize != refresh.NewContract.Filesize: + t.Fatalf("expected contract size %d, got %d", contract.Filesize, refresh.NewContract.Filesize) + case contract.Capacity != refresh.NewContract.Capacity: + t.Fatalf("expected contract capacity %d, got %d", contract.Capacity, refresh.NewContract.Capacity) } }) }