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"`