From 6a67c9a918dc2f0c99bdd094f5a3020895bee9b2 Mon Sep 17 00:00:00 2001 From: lukechampine Date: Mon, 2 Dec 2024 13:55:34 -0500 Subject: [PATCH] types,consensus: Simplify V2FileContractRenewal Instead of a full revision, renewals now specify just the final outputs and rollover amounts. This sidesteps existing inconsistencies around whether the "final revision" should be inserted into the accumulator or not, as well as the issue of the revision being valid in a standalone transaction. --- consensus/state.go | 1 - consensus/update.go | 4 +- consensus/validation.go | 63 ++++++----------- consensus/validation_test.go | 129 ++++++++++++++++------------------- rhp/v4/rhp.go | 27 ++++---- types/encoding.go | 11 +-- types/types.go | 9 +-- 7 files changed, 101 insertions(+), 143 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 2db710f..8cd0bfe 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -574,7 +574,6 @@ func (s State) ContractSigHash(fc types.V2FileContract) types.Hash256 { func (s State) RenewalSigHash(fcr types.V2FileContractRenewal) types.Hash256 { nilSigs( &fcr.NewContract.RenterSignature, &fcr.NewContract.HostSignature, - &fcr.FinalRevision.RenterSignature, &fcr.FinalRevision.HostSignature, &fcr.RenterSignature, &fcr.HostSignature, ) return hashAll("sig/filecontractrenewal", s.v2ReplayPrefix(), fcr) diff --git a/consensus/update.go b/consensus/update.go index a383a81..92370f3 100644 --- a/consensus/update.go +++ b/consensus/update.go @@ -548,9 +548,7 @@ func (ms *MidState) ApplyV2Transaction(txn types.V2Transaction) { var renter, host types.SiacoinOutput switch r := fcr.Resolution.(type) { case *types.V2FileContractRenewal: - renter, host = r.FinalRevision.RenterOutput, r.FinalRevision.HostOutput - renter.Value = renter.Value.Sub(r.RenterRollover) - host.Value = host.Value.Sub(r.HostRollover) + renter, host = r.FinalRenterOutput, r.FinalHostOutput ms.addV2FileContractElement(fce.ID.V2RenewalID(), r.NewContract) case *types.V2StorageProof: renter, host = fc.RenterOutput, fc.HostOutput diff --git a/consensus/validation.go b/consensus/validation.go index 143c457..a600fe2 100644 --- a/consensus/validation.go +++ b/consensus/validation.go @@ -690,32 +690,17 @@ func validateV2FileContracts(ms *MidState, txn types.V2Transaction) error { return nil } - validateSignatures := func(fc types.V2FileContract, renter, host types.PublicKey, renewal bool) error { - if renewal { - // The sub-contracts of a renewal must have empty signatures; - // otherwise they would be independently valid, i.e. the atomicity - // of the renewal could be violated. Consider a host who has lost or - // deleted their contract data; all they have to do is wait for a - // renter to initiate a renewal, then broadcast just the - // finalization of the old contract, allowing them to successfully - // resolve the contract without a storage proof. - if fc.RenterSignature != (types.Signature{}) { - return errors.New("has non-empty renter signature") - } else if fc.HostSignature != (types.Signature{}) { - return errors.New("has non-empty host signature") - } - } else { - contractHash := ms.base.ContractSigHash(fc) - if !renter.VerifyHash(contractHash, fc.RenterSignature) { - return errors.New("has invalid renter signature") - } else if !host.VerifyHash(contractHash, fc.HostSignature) { - return errors.New("has invalid host signature") - } + validateSignatures := func(fc types.V2FileContract, renter, host types.PublicKey) error { + contractHash := ms.base.ContractSigHash(fc) + if !renter.VerifyHash(contractHash, fc.RenterSignature) { + return errors.New("has invalid renter signature") + } else if !host.VerifyHash(contractHash, fc.HostSignature) { + return errors.New("has invalid host signature") } return nil } - validateContract := func(fc types.V2FileContract, renewal bool) error { + validateContract := func(fc types.V2FileContract) error { switch { case fc.Filesize > fc.Capacity: return fmt.Errorf("has filesize (%v) exceeding capacity (%v)", fc.Filesize, fc.Capacity) @@ -730,10 +715,10 @@ func validateV2FileContracts(ms *MidState, txn types.V2Transaction) error { case fc.TotalCollateral.Cmp(fc.HostOutput.Value) > 0: return fmt.Errorf("has total collateral (%d H) exceeding valid host value (%d H)", fc.TotalCollateral, fc.HostOutput.Value) } - return validateSignatures(fc, fc.RenterPublicKey, fc.HostPublicKey, renewal) + return validateSignatures(fc, fc.RenterPublicKey, fc.HostPublicKey) } - validateRevision := func(fce types.V2FileContractElement, rev types.V2FileContract, renewal bool) error { + validateRevision := func(fce types.V2FileContractElement, rev types.V2FileContract) error { cur := fce.V2FileContract if priorRev, ok := ms.v2revs[fce.ID]; ok { cur = priorRev.V2FileContract @@ -761,11 +746,11 @@ func validateV2FileContracts(ms *MidState, txn types.V2Transaction) error { return fmt.Errorf("leaves no time between proof height (%v) and expiration height (%v)", rev.ProofHeight, rev.ExpirationHeight) } // NOTE: very important that we verify with the *current* keys! - return validateSignatures(rev, cur.RenterPublicKey, cur.HostPublicKey, renewal) + return validateSignatures(rev, cur.RenterPublicKey, cur.HostPublicKey) } for i, fc := range txn.FileContracts { - if err := validateContract(fc, false); err != nil { + if err := validateContract(fc); err != nil { return fmt.Errorf("file contract %v %s", i, err) } } @@ -780,7 +765,7 @@ func validateV2FileContracts(ms *MidState, txn types.V2Transaction) error { // NOTE: disallowing this means that resolutions always take // precedence over revisions return fmt.Errorf("file contract revision %v resolves contract", i) - } else if err := validateRevision(fcr.Parent, rev, false); err != nil { + } else if err := validateRevision(fcr.Parent, rev); err != nil { return fmt.Errorf("file contract revision %v %s", i, err) } revised[fcr.Parent.ID] = i @@ -794,25 +779,17 @@ func validateV2FileContracts(ms *MidState, txn types.V2Transaction) error { switch r := fcr.Resolution.(type) { case *types.V2FileContractRenewal: renewal := *r - old, renewed := renewal.FinalRevision, renewal.NewContract - if old.RevisionNumber != types.MaxRevisionNumber { - return fmt.Errorf("file contract renewal %v does not finalize old contract", i) - } else if err := validateRevision(fcr.Parent, old, true); err != nil { - return fmt.Errorf("file contract renewal %v final revision %s", i, err) - } else if err := validateContract(renewed, false); err != nil { - return fmt.Errorf("file contract renewal %v initial revision %s", i, err) - } - rollover := renewal.RenterRollover.Add(renewal.HostRollover) - newContractCost := renewed.RenterOutput.Value.Add(renewed.HostOutput.Value).Add(ms.base.V2FileContractTax(renewed)) - if renewal.RenterRollover.Cmp(old.RenterOutput.Value) > 0 { - return fmt.Errorf("file contract renewal %v has renter rollover (%d H) exceeding old output (%d H)", i, renewal.RenterRollover, old.RenterOutput.Value) - } else if renewal.HostRollover.Cmp(old.HostOutput.Value) > 0 { - return fmt.Errorf("file contract renewal %v has host rollover (%d H) exceeding old output (%d H)", i, renewal.HostRollover, old.HostOutput.Value) - } else if rollover.Cmp(newContractCost) > 0 { + 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 { 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) } - renewalHash := ms.base.RenewalSigHash(renewal) if !fc.RenterPublicKey.VerifyHash(renewalHash, renewal.RenterSignature) { return fmt.Errorf("file contract renewal %v has invalid renter signature", i) diff --git a/consensus/validation_test.go b/consensus/validation_test.go index bf3f61a..685504a 100644 --- a/consensus/validation_test.go +++ b/consensus/validation_test.go @@ -1453,26 +1453,6 @@ func TestValidateV2Block(t *testing.T) { }} }, }, - { - "file contract renewal that does not finalize old contract", - func(b *types.Block) { - txn := &b.V2.Transactions[0] - txn.SiacoinInputs = []types.V2SiacoinInput{{ - Parent: sces[1], - SatisfiedPolicy: types.SatisfiedPolicy{Policy: giftPolicy}, - }} - - rev := testFces[0].V2FileContract - resolution := types.V2FileContractRenewal{ - FinalRevision: rev, - NewContract: testFces[0].V2FileContract, - } - txn.FileContractResolutions = []types.V2FileContractResolution{{ - Parent: testFces[0], - Resolution: &resolution, - }} - }, - }, { "file contract renewal with invalid final revision", func(b *types.Block) { @@ -1482,12 +1462,9 @@ func TestValidateV2Block(t *testing.T) { SatisfiedPolicy: types.SatisfiedPolicy{Policy: giftPolicy}, }} - rev := testFces[0].V2FileContract - rev.RevisionNumber = types.MaxRevisionNumber - rev.TotalCollateral = types.ZeroCurrency resolution := types.V2FileContractRenewal{ - FinalRevision: rev, - NewContract: testFces[0].V2FileContract, + FinalRenterOutput: types.SiacoinOutput{Value: types.Siacoins(1e6)}, + NewContract: testFces[0].V2FileContract, } txn.FileContractResolutions = []types.V2FileContractResolution{{ Parent: testFces[0], @@ -1506,11 +1483,10 @@ func TestValidateV2Block(t *testing.T) { rev := testFces[0].V2FileContract rev.ExpirationHeight = rev.ProofHeight - finalRev := testFces[0].V2FileContract - finalRev.RevisionNumber = types.MaxRevisionNumber resolution := types.V2FileContractRenewal{ - FinalRevision: finalRev, - NewContract: rev, + FinalRenterOutput: rev.RenterOutput, + FinalHostOutput: rev.HostOutput, + NewContract: rev, } txn.FileContractResolutions = []types.V2FileContractResolution{{ Parent: testFces[0], @@ -1885,7 +1861,6 @@ func TestV2RenewalResolution(t *testing.T) { tests := []struct { desc string renewFn func(*types.V2Transaction) - errors bool errString string }{ { @@ -1896,6 +1871,7 @@ func TestV2RenewalResolution(t *testing.T) { desc: "valid renewal - no renter rollover", renewFn: func(txn *types.V2Transaction) { renewal := txn.FileContractResolutions[0].Resolution.(*types.V2FileContractRenewal) + renewal.FinalRenterOutput.Value = renewal.RenterRollover renewal.RenterRollover = types.ZeroCurrency // subtract the renter cost from the change output txn.SiacoinOutputs[0].Value = txn.SiacoinInputs[0].Parent.SiacoinOutput.Value.Sub(renewal.NewContract.RenterOutput.Value).Sub(cs.V2FileContractTax(renewal.NewContract)) @@ -1905,6 +1881,7 @@ func TestV2RenewalResolution(t *testing.T) { desc: "valid renewal - no host rollover", renewFn: func(txn *types.V2Transaction) { renewal := txn.FileContractResolutions[0].Resolution.(*types.V2FileContractRenewal) + renewal.FinalHostOutput.Value = renewal.HostRollover renewal.HostRollover = types.ZeroCurrency // subtract the host cost from the change output txn.SiacoinOutputs[0].Value = txn.SiacoinInputs[0].Parent.SiacoinOutput.Value.Sub(renewal.NewContract.HostOutput.Value).Sub(cs.V2FileContractTax(renewal.NewContract)) @@ -1914,19 +1891,39 @@ func TestV2RenewalResolution(t *testing.T) { desc: "valid renewal - partial host rollover", renewFn: func(txn *types.V2Transaction) { renewal := txn.FileContractResolutions[0].Resolution.(*types.V2FileContractRenewal) - renewal.HostRollover = renewal.NewContract.MissedHostValue.Div64(2) + partial := renewal.NewContract.MissedHostValue.Div64(2) + renewal.FinalHostOutput.Value = partial + renewal.HostRollover = renewal.HostRollover.Sub(partial) // subtract the host cost from the change output - txn.SiacoinOutputs[0].Value = txn.SiacoinInputs[0].Parent.SiacoinOutput.Value.Sub(renewal.NewContract.HostOutput.Value.Div64(2)).Sub(cs.V2FileContractTax(renewal.NewContract)) + txn.SiacoinOutputs[0].Value = txn.SiacoinInputs[0].Parent.SiacoinOutput.Value.Sub(partial).Sub(cs.V2FileContractTax(renewal.NewContract)) }, }, { desc: "valid renewal - partial renter rollover", renewFn: func(txn *types.V2Transaction) { renewal := txn.FileContractResolutions[0].Resolution.(*types.V2FileContractRenewal) - renewal.RenterRollover = renewal.NewContract.RenterOutput.Value.Div64(2) + partial := renewal.NewContract.RenterOutput.Value.Div64(2) + renewal.FinalRenterOutput.Value = partial + renewal.RenterRollover = renewal.RenterRollover.Sub(partial) // subtract the host cost from the change output - txn.SiacoinOutputs[0].Value = txn.SiacoinInputs[0].Parent.SiacoinOutput.Value.Sub(renewal.NewContract.RenterOutput.Value.Div64(2)).Sub(cs.V2FileContractTax(renewal.NewContract)) + txn.SiacoinOutputs[0].Value = txn.SiacoinInputs[0].Parent.SiacoinOutput.Value.Sub(partial).Sub(cs.V2FileContractTax(renewal.NewContract)) + }, + }, + { + desc: "invalid renewal - bad new contract renter signature", + renewFn: func(txn *types.V2Transaction) { + renewal := txn.FileContractResolutions[0].Resolution.(*types.V2FileContractRenewal) + renewal.NewContract.RenterSignature[0] ^= 1 }, + errString: "invalid renter signature", + }, + { + desc: "invalid renewal - bad new contract host signature", + renewFn: func(txn *types.V2Transaction) { + renewal := txn.FileContractResolutions[0].Resolution.(*types.V2FileContractRenewal) + renewal.NewContract.HostSignature[0] ^= 1 + }, + errString: "invalid host signature", }, { desc: "invalid renewal - not enough host funds", @@ -1935,7 +1932,6 @@ func TestV2RenewalResolution(t *testing.T) { renewal.HostRollover = renewal.NewContract.MissedHostValue.Div64(2) // do not adjust the change output }, - errors: true, errString: "do not equal outputs", }, { @@ -1945,7 +1941,6 @@ func TestV2RenewalResolution(t *testing.T) { renewal.RenterRollover = renewal.NewContract.RenterOutput.Value.Div64(2) // do not adjust the change output }, - errors: true, errString: "do not equal outputs", }, { @@ -1963,7 +1958,6 @@ func TestV2RenewalResolution(t *testing.T) { escapeAmount := renewal.HostRollover.Sub(renewal.NewContract.HostOutput.Value) txn.SiacoinOutputs = append(txn.SiacoinOutputs, types.SiacoinOutput{Value: escapeAmount, Address: types.VoidAddress}) }, - errors: true, errString: "exceeding new contract cost", }, { @@ -1980,18 +1974,12 @@ func TestV2RenewalResolution(t *testing.T) { escapeAmount := renewal.RenterRollover.Sub(renewal.NewContract.RenterOutput.Value) txn.SiacoinOutputs = append(txn.SiacoinOutputs, types.SiacoinOutput{Value: escapeAmount, Address: types.VoidAddress}) }, - errors: true, errString: "exceeding new contract cost", }, } for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - finalRevision := fc - finalRevision.RevisionNumber = types.MaxRevisionNumber - finalRevision.RenterSignature = types.Signature{} - finalRevision.HostSignature = types.Signature{} - - fc := types.V2FileContract{ + newContract := types.V2FileContract{ ProofHeight: 100, ExpirationHeight: 150, RenterPublicKey: pk.PublicKey(), @@ -2004,30 +1992,30 @@ func TestV2RenewalResolution(t *testing.T) { }, MissedHostValue: types.Siacoins(10), } - tax := cs.V2FileContractTax(fc) + newContract.RenterSignature = pk.SignHash(cs.ContractSigHash(newContract)) + newContract.HostSignature = pk.SignHash(cs.ContractSigHash(newContract)) + renewTxn := types.V2Transaction{ - FileContractResolutions: []types.V2FileContractResolution{ - { - Parent: fces[contractID], - Resolution: &types.V2FileContractRenewal{ - FinalRevision: finalRevision, - NewContract: fc, - RenterRollover: types.Siacoins(10), - HostRollover: types.Siacoins(10), - }, + FileContractResolutions: []types.V2FileContractResolution{{ + Parent: fces[contractID], + Resolution: &types.V2FileContractRenewal{ + FinalRenterOutput: types.SiacoinOutput{Address: fc.RenterOutput.Address, Value: types.ZeroCurrency}, + FinalHostOutput: types.SiacoinOutput{Address: fc.HostOutput.Address, Value: types.ZeroCurrency}, + NewContract: newContract, + RenterRollover: types.Siacoins(10), + HostRollover: types.Siacoins(10), }, - }, - SiacoinInputs: []types.V2SiacoinInput{ - { - Parent: genesisOutput, - SatisfiedPolicy: types.SatisfiedPolicy{ - Policy: types.AnyoneCanSpend(), - }, + }}, + SiacoinInputs: []types.V2SiacoinInput{{ + Parent: genesisOutput, + SatisfiedPolicy: types.SatisfiedPolicy{ + Policy: types.AnyoneCanSpend(), }, - }, - SiacoinOutputs: []types.SiacoinOutput{ - {Address: addr, Value: genesisOutput.SiacoinOutput.Value.Sub(tax)}, - }, + }}, + SiacoinOutputs: []types.SiacoinOutput{{ + Address: addr, + Value: genesisOutput.SiacoinOutput.Value.Sub(cs.V2FileContractTax(newContract)), + }}, } resolution, ok := renewTxn.FileContractResolutions[0].Resolution.(*types.V2FileContractRenewal) if !ok { @@ -2038,9 +2026,6 @@ func TestV2RenewalResolution(t *testing.T) { test.renewFn(&renewTxn) // sign the renewal - newContract := &renewTxn.FileContractResolutions[0].Resolution.(*types.V2FileContractRenewal).NewContract - newContract.RenterSignature = pk.SignHash(cs.ContractSigHash(*newContract)) - newContract.HostSignature = pk.SignHash(cs.ContractSigHash(*newContract)) sigHash := cs.RenewalSigHash(*resolution) resolution.RenterSignature = pk.SignHash(sigHash) resolution.HostSignature = pk.SignHash(sigHash) @@ -2048,13 +2033,13 @@ func TestV2RenewalResolution(t *testing.T) { ms := NewMidState(cs) err := ValidateV2Transaction(ms, renewTxn) switch { - case test.errors && err == nil: + case test.errString != "" && err == nil: t.Fatal("expected error") - case test.errors && test.errString == "": + case test.errString != "" && test.errString == "": t.Fatalf("received error %q, missing error string to compare", err) - case test.errors && !strings.Contains(err.Error(), test.errString): + case test.errString != "" && !strings.Contains(err.Error(), test.errString): t.Fatalf("expected error %q to contain %q", err, test.errString) - case !test.errors && err != nil: + case test.errString == "" && err != nil: t.Fatalf("unexpected error: %q", err) } }) diff --git a/rhp/v4/rhp.go b/rhp/v4/rhp.go index e501513..3acc307 100644 --- a/rhp/v4/rhp.go +++ b/rhp/v4/rhp.go @@ -661,12 +661,8 @@ func MinRenterAllowance(hp HostPrices, duration uint64, collateral types.Currenc // RenewContract creates a contract renewal for the renew RPC func RenewContract(fc types.V2FileContract, prices HostPrices, rp RPCRenewContractParams) (types.V2FileContractRenewal, Usage) { var renewal types.V2FileContractRenewal - // clear the old contract - renewal.FinalRevision = fc - renewal.FinalRevision.RevisionNumber = types.MaxRevisionNumber - renewal.FinalRevision.FileMerkleRoot = types.Hash256{} - renewal.FinalRevision.RenterSignature = types.Signature{} - renewal.FinalRevision.HostSignature = types.Signature{} + renewal.FinalRenterOutput = fc.RenterOutput + renewal.FinalHostOutput = fc.HostOutput // create the new contract renewal.NewContract = fc @@ -708,6 +704,7 @@ func RenewContract(fc types.V2FileContract, prices HostPrices, rp RPCRenewContra } else { renewal.HostRollover = fc.TotalCollateral } + renewal.FinalHostOutput.Value = renewal.FinalHostOutput.Value.Sub(renewal.HostRollover) // if the remaining renter output is greater than the required allowance, // only roll over the new allowance. Otherwise, roll over the remaining @@ -717,6 +714,8 @@ func RenewContract(fc types.V2FileContract, prices HostPrices, rp RPCRenewContra } else { renewal.RenterRollover = fc.RenterOutput.Value } + renewal.FinalRenterOutput.Value = renewal.FinalRenterOutput.Value.Sub(renewal.RenterRollover) + return renewal, Usage{ RPC: prices.ContractPrice, Storage: renewal.NewContract.HostOutput.Value.Sub(renewal.NewContract.TotalCollateral).Sub(prices.ContractPrice), @@ -727,12 +726,13 @@ func RenewContract(fc types.V2FileContract, prices HostPrices, rp RPCRenewContra // RefreshContract creates a contract renewal for the refresh RPC. func RefreshContract(fc types.V2FileContract, prices HostPrices, rp RPCRefreshContractParams) (types.V2FileContractRenewal, Usage) { var renewal types.V2FileContractRenewal - - // clear the old contract - renewal.FinalRevision = fc - renewal.FinalRevision.RevisionNumber = types.MaxRevisionNumber - renewal.FinalRevision.RenterSignature = types.Signature{} - renewal.FinalRevision.HostSignature = types.Signature{} + // roll over everything from the existing contract + renewal.FinalRenterOutput = fc.RenterOutput + renewal.FinalHostOutput = fc.HostOutput + renewal.FinalRenterOutput.Value = types.ZeroCurrency + renewal.FinalHostOutput.Value = types.ZeroCurrency + renewal.HostRollover = fc.HostOutput.Value + renewal.RenterRollover = fc.RenterOutput.Value // create the new contract renewal.NewContract = fc @@ -745,9 +745,6 @@ func RefreshContract(fc types.V2FileContract, prices HostPrices, rp RPCRefreshCo renewal.NewContract.MissedHostValue = fc.MissedHostValue.Add(rp.Collateral) // total collateral includes the additional requested collateral renewal.NewContract.TotalCollateral = fc.TotalCollateral.Add(rp.Collateral) - // roll over everything from the existing contract - renewal.HostRollover = fc.HostOutput.Value - renewal.RenterRollover = fc.RenterOutput.Value return renewal, Usage{ RPC: prices.ContractPrice, Storage: renewal.NewContract.HostOutput.Value.Sub(renewal.NewContract.TotalCollateral).Sub(prices.ContractPrice), diff --git a/types/encoding.go b/types/encoding.go index 0c8677d..28724ac 100644 --- a/types/encoding.go +++ b/types/encoding.go @@ -703,10 +703,11 @@ func (rev V2FileContractRevision) EncodeTo(e *Encoder) { // EncodeTo implements types.EncoderTo. func (ren V2FileContractRenewal) EncodeTo(e *Encoder) { - ren.FinalRevision.EncodeTo(e) - ren.NewContract.EncodeTo(e) + V2SiacoinOutput(ren.FinalRenterOutput).EncodeTo(e) + V2SiacoinOutput(ren.FinalHostOutput).EncodeTo(e) V2Currency(ren.RenterRollover).EncodeTo(e) V2Currency(ren.HostRollover).EncodeTo(e) + ren.NewContract.EncodeTo(e) ren.RenterSignature.EncodeTo(e) ren.HostSignature.EncodeTo(e) } @@ -853,7 +854,6 @@ func (txn V2TransactionSemantics) EncodeTo(e *Encoder) { renewal := *res nilSigs( &renewal.NewContract.RenterSignature, &renewal.NewContract.HostSignature, - &renewal.FinalRevision.RenterSignature, &renewal.FinalRevision.HostSignature, &renewal.RenterSignature, &renewal.HostSignature, ) fcr.Resolution = &renewal @@ -1264,10 +1264,11 @@ func (rev *V2FileContractRevision) DecodeFrom(d *Decoder) { // DecodeFrom implements types.DecoderFrom. func (ren *V2FileContractRenewal) DecodeFrom(d *Decoder) { - ren.FinalRevision.DecodeFrom(d) - ren.NewContract.DecodeFrom(d) + (*V2SiacoinOutput)(&ren.FinalRenterOutput).DecodeFrom(d) + (*V2SiacoinOutput)(&ren.FinalHostOutput).DecodeFrom(d) (*V2Currency)(&ren.RenterRollover).DecodeFrom(d) (*V2Currency)(&ren.HostRollover).DecodeFrom(d) + ren.NewContract.DecodeFrom(d) ren.RenterSignature.DecodeFrom(d) ren.HostSignature.DecodeFrom(d) } diff --git a/types/types.go b/types/types.go index 5f617b2..b41ff69 100644 --- a/types/types.go +++ b/types/types.go @@ -555,10 +555,11 @@ func (*V2FileContractExpiration) isV2FileContractResolution() {} // A V2FileContractRenewal renews a file contract. type V2FileContractRenewal struct { - FinalRevision V2FileContract `json:"finalRevision"` - NewContract V2FileContract `json:"newContract"` - RenterRollover Currency `json:"renterRollover"` - HostRollover Currency `json:"hostRollover"` + FinalRenterOutput SiacoinOutput `json:"finalRenterOutput"` + FinalHostOutput SiacoinOutput `json:"finalHostOutput"` + RenterRollover Currency `json:"renterRollover"` + HostRollover Currency `json:"hostRollover"` + NewContract V2FileContract `json:"newContract"` // signatures cover above fields RenterSignature Signature `json:"renterSignature"`