From 773ee727995bbbc244aa952483c8358a7c9c6ac3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:23:46 +0000 Subject: [PATCH 1/2] build(deps): bump golang.org/x/crypto from 0.27.0 to 0.28.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.27.0 to 0.28.0. - [Commits](https://github.com/golang/crypto/compare/v0.27.0...v0.28.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index f065e5d..3c0869f 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( go.etcd.io/bbolt v1.3.11 go.sia.tech/core v0.4.7 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.27.0 + golang.org/x/crypto v0.28.0 lukechampine.com/frand v1.4.2 ) @@ -14,5 +14,5 @@ require ( github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect go.sia.tech/mux v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sys v0.25.0 // indirect + golang.org/x/sys v0.26.0 // indirect ) diff --git a/go.sum b/go.sum index 1ffc411..66b7874 100644 --- a/go.sum +++ b/go.sum @@ -18,13 +18,13 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= lukechampine.com/frand v1.4.2 h1:RzFIpOvkMXuPMBb9maa4ND4wjBn71E1Jpf8BzJHMaVw= From 15e898dcf945f1aa29c65df83fdea68ede037f73 Mon Sep 17 00:00:00 2001 From: Nate Maninger Date: Tue, 8 Oct 2024 07:40:53 -0700 Subject: [PATCH 2/2] rhp4,chain: simplify renew, move proof updates to chain manager (#102) * rhp4,chain: simplify renew, move proof updates to chain manager * rhp4,chain: address comments * rhp4: fix renew signatures --- chain/manager.go | 147 ++++++++++++++-------------- go.mod | 2 +- go.sum | 4 +- rhp/v4/rpc.go | 64 +++++------- rhp/v4/server.go | 250 +++++++++++------------------------------------ 5 files changed, 158 insertions(+), 309 deletions(-) diff --git a/chain/manager.go b/chain/manager.go index 32efce9..39e408b 100644 --- a/chain/manager.go +++ b/chain/manager.go @@ -949,7 +949,7 @@ func (m *Manager) V2TransactionSet(basis types.ChainIndex, txn types.V2Transacti m.revalidatePool() // update the transaction's basis to match tip - _, txns, err := m.updateV2TransactionSet(basis, []types.V2Transaction{txn}) + txns, err := m.UpdateV2TransactionSet([]types.V2Transaction{txn}, basis, m.tipState.Index) if err != nil { return types.ChainIndex{}, nil, fmt.Errorf("failed to update transaction set basis: %w", err) } else if len(txns) == 0 { @@ -1041,82 +1041,25 @@ func (m *Manager) markBadTxnSet(setID types.Hash256, err error) error { return err } -// AddPoolTransactions validates a transaction set and adds it to the txpool. If -// any transaction references an element (SiacoinOutput, SiafundOutput, or -// FileContract) not present in the blockchain, that element must be created by -// a previous transaction in the set. -// -// If any transaction in the set is invalid, the entire set is rejected and none -// of the transactions are added to the pool. If all of the transactions are -// already known to the pool, AddPoolTransactions returns true. -func (m *Manager) AddPoolTransactions(txns []types.Transaction) (known bool, err error) { - m.mu.Lock() - defer m.mu.Unlock() - m.revalidatePool() - - setID, known := m.checkDupTxnSet(txns, nil) - if known { - return true, m.txpool.invalidTxnSets[setID] - } - - // validate as a standalone set - ms := consensus.NewMidState(m.tipState) - for _, txn := range txns { - ts := m.store.SupplementTipTransaction(txn) - if err := consensus.ValidateTransaction(ms, txn, ts); err != nil { - return false, m.markBadTxnSet(setID, fmt.Errorf("transaction %v is invalid: %w", txn.ID(), err)) - } - ms.ApplyTransaction(txn, ts) - } - - for _, txn := range txns { - txid := txn.ID() - if _, ok := m.txpool.indices[txid]; ok { - continue // skip transactions already in pool - } - m.txpool.ms.ApplyTransaction(txn, m.store.SupplementTipTransaction(txn)) - m.txpool.indices[txid] = len(m.txpool.txns) - m.txpool.txns = append(m.txpool.txns, txn) - m.txpool.weight += m.tipState.TransactionWeight(txn) - } - - // invalidate caches - m.txpool.medianFee = nil - m.txpool.parentMap = nil - return -} - -// updateV2TransactionSet updates the basis of a transaction set to the current -// tip. If the basis is already the tip, the transaction set is returned as-is. -// Any transactions that were confirmed are removed from the set. Any ephemeral -// state elements that were created by an update are updated. -// -// If it is undesirable to modify the transaction set, deep-copy it -// before calling this method. -func (m *Manager) updateV2TransactionSet(basis types.ChainIndex, txns []types.V2Transaction) (types.ChainIndex, []types.V2Transaction, error) { - if basis == m.tipState.Index { - return basis, txns, nil - } - - // bring txns up-to-date - revert, apply, err := m.reorgPath(basis, m.tipState.Index) +func (m *Manager) updateV2TransactionProofs(txns []types.V2Transaction, from, to types.ChainIndex) ([]types.V2Transaction, error) { + revert, apply, err := m.reorgPath(from, to) if err != nil { - return types.ChainIndex{}, nil, fmt.Errorf("couldn't determine reorg path from %v to %v: %w", basis, m.tipState.Index, err) + return nil, fmt.Errorf("couldn't determine reorg path from %v to %v: %w", from, to, err) } else if len(revert)+len(apply) > 144 { - return types.ChainIndex{}, nil, fmt.Errorf("reorg path from %v to %v is too long (-%v +%v)", basis, m.tipState.Index, len(revert), len(apply)) + return nil, fmt.Errorf("reorg path from %v to %v is too long (-%v +%v)", from, to, len(revert), len(apply)) } for _, index := range revert { b, _, cs, ok := blockAndParent(m.store, index.ID) if !ok { - return types.ChainIndex{}, nil, fmt.Errorf("missing reverted block at index %v", index) + return nil, fmt.Errorf("missing reverted block at index %v", index) } else if b.V2 == nil { - return types.ChainIndex{}, nil, fmt.Errorf("reorg path from %v to %v contains a non-v2 block (%v)", basis, m.tipState.Index, index) + return nil, fmt.Errorf("reorg path from %v to %v contains a non-v2 block (%v)", from, to, index) } // NOTE: since we are post-hardfork, we don't need a v1 supplement cru := consensus.RevertBlock(cs, b, consensus.V1BlockSupplement{}) for i := range txns { if !updateTxnProofs(&txns[i], cru.UpdateElementProof, cs.Elements.NumLeaves) { - return types.ChainIndex{}, nil, fmt.Errorf("transaction %v references element that does not exist in our chain", txns[i].ID()) + return nil, fmt.Errorf("transaction %v references element that does not exist in our chain", txns[i].ID()) } } } @@ -1124,9 +1067,9 @@ func (m *Manager) updateV2TransactionSet(basis types.ChainIndex, txns []types.V2 for _, index := range apply { b, _, cs, ok := blockAndParent(m.store, index.ID) if !ok { - return types.ChainIndex{}, nil, fmt.Errorf("missing applied block at index %v", index) + return nil, fmt.Errorf("missing applied block at index %v", index) } else if b.V2 == nil { - return types.ChainIndex{}, nil, fmt.Errorf("reorg path from %v to %v contains a non-v2 block (%v)", basis, m.tipState.Index, index) + return nil, fmt.Errorf("reorg path from %v to %v contains a non-v2 block (%v)", from, to, index) } // NOTE: since we are post-hardfork, we don't need a v1 supplement or ancestorTimestamp cs, cau := consensus.ApplyBlock(cs, b, consensus.V1BlockSupplement{}, time.Time{}) @@ -1154,7 +1097,6 @@ func (m *Manager) updateV2TransactionSet(basis types.ChainIndex, txns []types.V2 // remove any transactions that were confirmed in this block continue } - rem = append(rem, txns[i]) // update the state elements for any confirmed ephemeral elements for j := range txns[i].SiacoinInputs { @@ -1182,11 +1124,74 @@ func (m *Manager) updateV2TransactionSet(basis types.ChainIndex, txns []types.V2 // NOTE: all elements guaranteed to exist from here on, so no // need to check this return value - updateTxnProofs(&rem[len(rem)-1], cau.UpdateElementProof, cs.Elements.NumLeaves) + updateTxnProofs(&txns[i], cau.UpdateElementProof, cs.Elements.NumLeaves) + rem = append(rem, txns[i]) } txns = rem } - return m.tipState.Index, txns, nil + return txns, nil +} + +// AddPoolTransactions validates a transaction set and adds it to the txpool. If +// any transaction references an element (SiacoinOutput, SiafundOutput, or +// FileContract) not present in the blockchain, that element must be created by +// a previous transaction in the set. +// +// If any transaction in the set is invalid, the entire set is rejected and none +// of the transactions are added to the pool. If all of the transactions are +// already known to the pool, AddPoolTransactions returns true. +func (m *Manager) AddPoolTransactions(txns []types.Transaction) (known bool, err error) { + m.mu.Lock() + defer m.mu.Unlock() + m.revalidatePool() + + setID, known := m.checkDupTxnSet(txns, nil) + if known { + return true, m.txpool.invalidTxnSets[setID] + } + + // validate as a standalone set + ms := consensus.NewMidState(m.tipState) + for _, txn := range txns { + ts := m.store.SupplementTipTransaction(txn) + if err := consensus.ValidateTransaction(ms, txn, ts); err != nil { + return false, m.markBadTxnSet(setID, fmt.Errorf("transaction %v is invalid: %w", txn.ID(), err)) + } + ms.ApplyTransaction(txn, ts) + } + + for _, txn := range txns { + txid := txn.ID() + if _, ok := m.txpool.indices[txid]; ok { + continue // skip transactions already in pool + } + m.txpool.ms.ApplyTransaction(txn, m.store.SupplementTipTransaction(txn)) + m.txpool.indices[txid] = len(m.txpool.txns) + m.txpool.txns = append(m.txpool.txns, txn) + m.txpool.weight += m.tipState.TransactionWeight(txn) + } + + // invalidate caches + m.txpool.medianFee = nil + m.txpool.parentMap = nil + return +} + +// UpdateV2TransactionSet updates the basis of a transaction set from "from" to "to". +// If from and to are equal, the transaction set is returned as-is. +// Any transactions that were confirmed are removed from the set. +// Any ephemeral state elements that were created by an update are updated. +// +// If it is undesirable to modify the transaction set, deep-copy it +// before calling this method. +func (m *Manager) UpdateV2TransactionSet(txns []types.V2Transaction, from, to types.ChainIndex) ([]types.V2Transaction, error) { + if from == to { + return txns, nil + } + + m.mu.Lock() + defer m.mu.Unlock() + return m.updateV2TransactionProofs(txns, from, to) } // AddV2PoolTransactions validates a transaction set and adds it to the txpool. @@ -1220,7 +1225,7 @@ func (m *Manager) AddV2PoolTransactions(basis types.ChainIndex, txns []types.V2T } // update the transaction set to the current tip - _, txns, err := m.updateV2TransactionSet(basis, txns) + txns, err := m.updateV2TransactionProofs(txns, basis, m.tipState.Index) if err != nil { return false, m.markBadTxnSet(setID, fmt.Errorf("failed to update set basis: %w", err)) } else if len(txns) == 0 { diff --git a/go.mod b/go.mod index 61a0be5..e93a82f 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23.0 require ( go.etcd.io/bbolt v1.3.11 - go.sia.tech/core v0.4.8-0.20241002233523-418236837b5a + go.sia.tech/core v0.4.8-0.20241003192046-425f95763c90 go.sia.tech/mux v1.3.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.27.0 diff --git a/go.sum b/go.sum index c93ea9f..f5b7a6e 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.sia.tech/core v0.4.8-0.20241002233523-418236837b5a h1:Lz7h42eZCk8iI4/agdWNwlVneqaybxkV1lor+Eavzso= -go.sia.tech/core v0.4.8-0.20241002233523-418236837b5a/go.mod h1:j2Ke8ihV8or7d2VDrFZWcCkwSVHO0DNMQJAGs9Qop2M= +go.sia.tech/core v0.4.8-0.20241003192046-425f95763c90 h1:R/G7XXyzLKelfGBGT6XQpih379v5jJx6T4AsjLwyjJc= +go.sia.tech/core v0.4.8-0.20241003192046-425f95763c90/go.mod h1:j2Ke8ihV8or7d2VDrFZWcCkwSVHO0DNMQJAGs9Qop2M= go.sia.tech/mux v1.3.0 h1:hgR34IEkqvfBKUJkAzGi31OADeW2y7D6Bmy/Jcbop9c= go.sia.tech/mux v1.3.0/go.mod h1:I46++RD4beqA3cW9Xm9SwXbezwPqLvHhVs9HLpDtt58= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= diff --git a/rhp/v4/rpc.go b/rhp/v4/rpc.go index 7e372f7..08eb4c6 100644 --- a/rhp/v4/rpc.go +++ b/rhp/v4/rpc.go @@ -649,48 +649,26 @@ func RPCRenewContract(ctx context.Context, t TransportClient, tp TxPool, signer } renterCost, hostCost := rhp4.RenewalCost(cs, p, renewal, renewalTxn.MinerFee) - - basis := cs.Index // start with a decent basis and overwrite it if a setup transaction is needed - var renewalParents []types.V2Transaction - if !renterCost.IsZero() { - setupTxn := types.V2Transaction{ - SiacoinOutputs: []types.SiacoinOutput{ - {Address: renewal.NewContract.RenterOutput.Address, Value: renterCost}, - }, - } - var err error - var toSign []int - basis, toSign, err = signer.FundV2Transaction(&setupTxn, renterCost) - if err != nil { - return RPCRenewContractResult{}, fmt.Errorf("failed to fund transaction: %w", err) - } - signer.SignV2Inputs(&setupTxn, toSign) - - basis, renewalParents, err = tp.V2TransactionSet(basis, setupTxn) - if err != nil { - return RPCRenewContractResult{}, fmt.Errorf("failed to get transaction set: %w", err) - } - setupTxn = renewalParents[len(renewalParents)-1] - - renewalTxn.SiacoinInputs = append(renewalTxn.SiacoinInputs, types.V2SiacoinInput{ - Parent: setupTxn.EphemeralSiacoinOutput(0), - }) - signer.SignV2Inputs(&renewalTxn, []int{0}) + req := rhp4.RPCRenewContractRequest{ + Prices: p, + Renewal: params, + MinerFee: renewalTxn.MinerFee, } - renterSiacoinElements := make([]types.SiacoinElement, 0, len(renewalTxn.SiacoinInputs)) - for _, i := range renewalTxn.SiacoinInputs { - renterSiacoinElements = append(renterSiacoinElements, i.Parent) + basis, toSign, err := signer.FundV2Transaction(&renewalTxn, renterCost) + if err != nil { + return RPCRenewContractResult{}, fmt.Errorf("failed to fund transaction: %w", err) } - req := rhp4.RPCRenewContractRequest{ - Prices: p, - Renewal: params, - MinerFee: renewalTxn.MinerFee, - Basis: basis, - RenterInputs: renterSiacoinElements, - RenterParents: renewalParents, + req.Basis, req.RenterParents, err = tp.V2TransactionSet(basis, renewalTxn) + if err != nil { + return RPCRenewContractResult{}, fmt.Errorf("failed to get transaction set: %w", err) } + for _, si := range renewalTxn.SiacoinInputs { + req.RenterInputs = append(req.RenterInputs, si.Parent) + } + req.RenterParents = req.RenterParents[:len(req.RenterParents)-1] // last transaction is the renewal + sigHash := req.ChallengeSigHash(existing.RevisionNumber) req.ChallengeSignature = signer.SignHash(sigHash) @@ -714,12 +692,18 @@ func RPCRenewContract(ctx context.Context, t TransportClient, tp TxPool, signer } // verify the host added enough inputs - if !hostInputSum.Equals(hostCost) { + if n := hostInputSum.Cmp(hostCost); n < 0 { return RPCRenewContractResult{}, fmt.Errorf("expected host to fund %v, got %v", hostCost, hostInputSum) + } else if n > 0 { + // add change output + renewalTxn.SiacoinOutputs = append(renewalTxn.SiacoinOutputs, types.SiacoinOutput{ + Address: existing.HostOutput.Address, + Value: hostInputSum.Sub(hostCost), + }) } // sign the renter inputs - signer.SignV2Inputs(&renewalTxn, []int{0}) + signer.SignV2Inputs(&renewalTxn, toSign) // sign the renewal renewalSigHash := cs.RenewalSigHash(renewal) renewal.RenterSignature = signer.SignHash(renewalSigHash) @@ -728,7 +712,7 @@ func RPCRenewContract(ctx context.Context, t TransportClient, tp TxPool, signer renterPolicyResp := rhp4.RPCRenewContractSecondResponse{ RenterRenewalSignature: renewal.RenterSignature, } - for _, si := range renewalTxn.SiacoinInputs[:len(renterSiacoinElements)] { + for _, si := range renewalTxn.SiacoinInputs[:len(req.RenterInputs)] { renterPolicyResp.RenterSatisfiedPolicies = append(renterPolicyResp.RenterSatisfiedPolicies, si.SatisfiedPolicy) } if err := rhp4.WriteResponse(s, &renterPolicyResp); err != nil { diff --git a/rhp/v4/server.go b/rhp/v4/server.go index 8c74a95..1e09dfc 100644 --- a/rhp/v4/server.go +++ b/rhp/v4/server.go @@ -12,16 +12,11 @@ import ( "go.sia.tech/core/consensus" rhp4 "go.sia.tech/core/rhp/v4" "go.sia.tech/core/types" - "go.sia.tech/coreutils/chain" "go.sia.tech/coreutils/wallet" "go.uber.org/zap" "lukechampine.com/frand" ) -// maxBasisDiff is the maximum number of blocks by which a transaction's basis -// can differ from the current tip. -const maxBasisDiff = 20 - var protocolVersion = [3]byte{4, 0, 0} type ( @@ -59,9 +54,15 @@ type ( AddV2PoolTransactions(types.ChainIndex, []types.V2Transaction) (known bool, err error) // RecommendedFee returns the recommended fee per weight RecommendedFee() types.Currency - // UpdatesSince returns at most max updates on the path between index and the - // Manager's current tip. - UpdatesSince(index types.ChainIndex, maxBlocks int) (rus []chain.RevertUpdate, aus []chain.ApplyUpdate, err error) + + // UpdateV2TransactionSet updates the basis of a transaction set from "from" to "to". + // If from and to are equal, the transaction set is returned as-is. + // Any transactions that were confirmed are removed from the set. + // Any ephemeral state elements that were created by an update are updated. + // + // If it is undesirable to modify the transaction set, deep-copy it + // before calling this method. + UpdateV2TransactionSet(txns []types.V2Transaction, from, to types.ChainIndex) ([]types.V2Transaction, error) } // A Syncer broadcasts transactions to its peers. @@ -580,9 +581,14 @@ func (s *Server) handleRPCFormContract(stream net.Conn) error { // update renter input basis to reflect our funding basis if basis != req.Basis { - if err := updateSiacoinElementBasis(s.chain, req.Basis, basis, formationTxn.SiacoinInputs[:len(req.RenterInputs)]); err != nil { + hostInputs := formationTxn.SiacoinInputs[len(formationTxn.SiacoinInputs)-len(req.RenterInputs)] + formationTxn.SiacoinInputs = formationTxn.SiacoinInputs[:len(formationTxn.SiacoinInputs)-len(req.RenterInputs)] + txnset, err := s.chain.UpdateV2TransactionSet([]types.V2Transaction{formationTxn}, req.Basis, basis) + if err != nil { return errorBadRequest("failed to update renter inputs from %q to %q: %v", req.Basis, basis, err) } + formationTxn = txnset[0] + formationTxn.SiacoinInputs = append(formationTxn.SiacoinInputs, hostInputs) } // send the host inputs to the renter @@ -684,20 +690,12 @@ func (s *Server) handleRPCRenewContract(stream net.Conn) error { return errorBadRequest("invalid challenge signature") } + cs := s.chain.TipState() renewal := rhp4.NewRenewal(existing, prices, req.Renewal) - - // create the renewal transaction + renterCost, hostCost := rhp4.RenewalCost(cs, prices, renewal, req.MinerFee) renewalTxn := types.V2Transaction{ MinerFee: req.MinerFee, - FileContractResolutions: []types.V2FileContractResolution{ - { - Resolution: &renewal, - }, - }, } - // calculate the renter funding - cs := s.chain.TipState() - renterCost, hostCost := rhp4.RenewalCost(cs, prices, renewal, renewalTxn.MinerFee) // add the renter inputs var renterInputSum types.Currency @@ -708,61 +706,56 @@ func (s *Server) handleRPCRenewContract(stream net.Conn) error { renterInputSum = renterInputSum.Add(si.SiacoinOutput.Value) } - // validate the renter added enough inputs - if !renterInputSum.Equals(renterCost) { + if n := renterInputSum.Cmp(renterCost); n < 0 { return errorBadRequest("expected renter to fund %v, got %v", renterInputSum, renterCost) + } else if n > 0 { + // if the renter added too much, add a change output + renewalTxn.SiacoinOutputs = append(renewalTxn.SiacoinOutputs, types.SiacoinOutput{ + Address: renewal.NewContract.RenterOutput.Address, + Value: renterInputSum.Sub(renterCost), + }) } - fceBasis, fce, err := s.contractor.ContractElement(req.Renewal.ContractID) + elementBasis, fce, err := s.contractor.ContractElement(req.Renewal.ContractID) if err != nil { return fmt.Errorf("failed to get contract element: %w", err) } - renewalTxn.FileContractResolutions[0].Parent = fce - - basis := cs.Index // start with a decent basis and overwrite it if a setup transaction is needed - var renewalParents []types.V2Transaction - if !hostCost.IsZero() { - // fund the locked collateral - setupTxn := types.V2Transaction{ - SiacoinOutputs: []types.SiacoinOutput{ - {Address: renewal.NewContract.HostOutput.Address, Value: hostCost}, - }, - } - - var toSign []int - basis, toSign, err = s.wallet.FundV2Transaction(&setupTxn, hostCost, true) - if errors.Is(err, wallet.ErrNotEnoughFunds) { - return rhp4.ErrHostFundError - } else if err != nil { - return fmt.Errorf("failed to fund transaction: %w", err) - } - // sign the transaction inputs - s.wallet.SignV2Inputs(&setupTxn, toSign) - - basis, renewalParents, err = s.chain.V2TransactionSet(basis, setupTxn) - if err != nil { - return fmt.Errorf("failed to get setup transaction set: %w", err) - } - renewalTxn.SiacoinInputs = append(renewalTxn.SiacoinInputs, types.V2SiacoinInput{ - Parent: renewalParents[len(renewalParents)-1].EphemeralSiacoinOutput(0), - }) - s.wallet.SignV2Inputs(&renewalTxn, []int{len(renewalTxn.SiacoinInputs) - 1}) + basis, toSign, err := s.wallet.FundV2Transaction(&renewalTxn, hostCost, true) + if errors.Is(err, wallet.ErrNotEnoughFunds) { + return rhp4.ErrHostFundError + } else if err != nil { + return fmt.Errorf("failed to fund transaction: %w", err) } - // update renter input basis to reflect our funding basis + // update renter inputs to reflect our chain state if basis != req.Basis { - if err := updateSiacoinElementBasis(s.chain, req.Basis, basis, renewalTxn.SiacoinInputs[:len(req.RenterInputs)]); err != nil { + hostInputs := renewalTxn.SiacoinInputs[len(renewalTxn.SiacoinInputs)-len(req.RenterInputs):] + renewalTxn.SiacoinInputs = renewalTxn.SiacoinInputs[:len(renewalTxn.SiacoinInputs)-len(req.RenterInputs)] + updated, err := s.chain.UpdateV2TransactionSet([]types.V2Transaction{renewalTxn}, req.Basis, basis) + if err != nil { return errorBadRequest("failed to update renter inputs from %q to %q: %v", req.Basis, basis, err) } + renewalTxn = updated[0] + renewalTxn.SiacoinInputs = append(renewalTxn.SiacoinInputs, hostInputs...) } - // update the file contract element to reflect the funding basis - if fceBasis != basis { - if err := updateStateElementBasis(s.chain, fceBasis, basis, &fce.StateElement); err != nil { - return errorBadRequest("failed to update file contract basis: %v", err) + + if elementBasis != basis { + tempTxn := types.V2Transaction{ + FileContractResolutions: []types.V2FileContractResolution{ + {Parent: fce, Resolution: &renewal}, + }, + } + updated, err := s.chain.UpdateV2TransactionSet([]types.V2Transaction{tempTxn}, elementBasis, basis) + if err != nil { + return fmt.Errorf("failed to update contract element: %w", err) } + fce = updated[0].FileContractResolutions[0].Parent } - + renewalTxn.FileContractResolutions = []types.V2FileContractResolution{ + {Parent: fce, Resolution: &renewal}, + } + s.wallet.SignV2Inputs(&renewalTxn, toSign) // send the host inputs to the renter hostInputsResp := rhp4.RPCRenewContractResponse{ HostInputs: renewalTxn.SiacoinInputs[len(req.RenterInputs):], @@ -787,11 +780,9 @@ func (s *Server) handleRPCRenewContract(stream net.Conn) error { renewal.RenterSignature = renterSigResp.RenterRenewalSignature // apply the renter's signatures - for i := range renewalTxn.SiacoinInputs[:len(req.RenterInputs)] { - renewalTxn.SiacoinInputs[i].SatisfiedPolicy = renterSigResp.RenterSatisfiedPolicies[i] + for i, policy := range renterSigResp.RenterSatisfiedPolicies { + renewalTxn.SiacoinInputs[i].SatisfiedPolicy = policy } - - // sign the renewal renewal.HostSignature = s.hostKey.SignHash(renewalSigHash) // add the renter's parents to our transaction pool to ensure they are valid @@ -802,19 +793,12 @@ func (s *Server) handleRPCRenewContract(stream net.Conn) error { } } - // add the setup parents to the transaction pool - if len(renewalParents) > 0 { - if _, err := s.chain.AddV2PoolTransactions(basis, renewalParents); err != nil { - return errorBadRequest("failed to add setup parents to transaction pool: %v", err) - } - } - // get the full updated transaction set for the renewal transaction basis, renewalSet, err := s.chain.V2TransactionSet(basis, renewalTxn) if err != nil { return fmt.Errorf("failed to get transaction set: %w", err) } else if _, err = s.chain.AddV2PoolTransactions(basis, renewalSet); err != nil { - return errorBadRequest("failed to broadcast setup transaction: %v", err) + return errorBadRequest("failed to broadcast renewal set: %v", err) } // broadcast the transaction set s.syncer.BroadcastV2TransactionSet(basis, renewalSet) @@ -864,130 +848,6 @@ func (s *Server) handleRPCVerifySector(stream net.Conn) error { return rhp4.WriteResponse(stream, &resp) } -func updateStateElementBasis(cm ChainManager, base, target types.ChainIndex, element *types.StateElement) error { - reverted, applied, err := cm.UpdatesSince(base, 144) - if err != nil { - return err - } - - if len(reverted)+len(applied) > maxBasisDiff { - return fmt.Errorf("too many updates between %v and %v", base, target) - } - - for _, cru := range reverted { - revertedIndex := types.ChainIndex{ - Height: cru.State.Index.Height + 1, - ID: cru.Block.ParentID, - } - if revertedIndex == target { - return nil - } - - cru.UpdateElementProof(element) - base = revertedIndex - } - - for _, cau := range applied { - if cau.State.Index == target { - return nil - } - - modified := make(map[types.Hash256]types.StateElement) - cau.ForEachSiacoinElement(func(sce types.SiacoinElement, created bool, spent bool) { - if created { - modified[sce.ID] = sce.StateElement - } - }) - - cau.UpdateElementProof(element) - base = cau.State.Index - } - - if base != target { - return fmt.Errorf("failed to update basis to target %v, current %v", target, base) - } - return nil -} - -// updateSiacoinElementBasis is a helper to update a transaction's siacoin elements -// to the target basis. If an error is returned, inputs must be considered invalid. -func updateSiacoinElementBasis(cm ChainManager, base, target types.ChainIndex, inputs []types.V2SiacoinInput) error { - reverted, applied, err := cm.UpdatesSince(base, 144) - if err != nil { - return err - } - - if len(reverted)+len(applied) > maxBasisDiff { - return fmt.Errorf("too many updates between %v and %v", base, target) - } - - for _, cru := range reverted { - revertedIndex := types.ChainIndex{ - Height: cru.State.Index.Height + 1, - ID: cru.Block.ParentID, - } - if revertedIndex == target { - return nil - } - - modified := make(map[types.Hash256]types.StateElement) - cru.ForEachSiacoinElement(func(sce types.SiacoinElement, created bool, spent bool) { - if created { - modified[sce.ID] = types.StateElement{ - ID: sce.ID, - LeafIndex: types.UnassignedLeafIndex, - } - } - }) - - for i := range inputs { - if se, ok := modified[inputs[i].Parent.ID]; ok { - inputs[i].Parent.StateElement = se - } - - if inputs[i].Parent.LeafIndex == types.UnassignedLeafIndex { - continue - } else if inputs[i].Parent.LeafIndex >= cru.State.Elements.NumLeaves { - return fmt.Errorf("siacoin input %v is not in the correct state", inputs[i].Parent.ID) - } - cru.UpdateElementProof(&inputs[i].Parent.StateElement) - } - base = revertedIndex - } - - for _, cau := range applied { - if cau.State.Index == target { - return nil - } - - modified := make(map[types.Hash256]types.StateElement) - cau.ForEachSiacoinElement(func(sce types.SiacoinElement, created bool, spent bool) { - if created { - modified[sce.ID] = sce.StateElement - } - }) - - for i := range inputs { - if se, ok := modified[inputs[i].Parent.ID]; ok { - inputs[i].Parent.StateElement = se - } - - if inputs[i].Parent.LeafIndex == types.UnassignedLeafIndex { - continue - } else if inputs[i].Parent.LeafIndex >= cau.State.Elements.NumLeaves { - return fmt.Errorf("siacoin input %v is not in the correct state", inputs[i].Parent.ID) - } - cau.UpdateElementProof(&inputs[i].Parent.StateElement) - } - base = cau.State.Index - } - - if base != target { - return fmt.Errorf("failed to update basis to target %v, current %v", target, base) - } - return nil -} - func (s *Server) handleHostStream(stream net.Conn, log *zap.Logger) { defer stream.Close()