From b56c19a4df4d1582adcdce2021412c233fde6726 Mon Sep 17 00:00:00 2001 From: Nate Maninger Date: Fri, 29 Sep 2023 07:00:31 -0600 Subject: [PATCH 1/2] rhp, wallet: add ErrNoFunds and expose it to the renter --- rhp/v2/rpc.go | 13 +++++++++++-- rhp/v3/rpc.go | 8 ++++++-- wallet/wallet.go | 42 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/rhp/v2/rpc.go b/rhp/v2/rpc.go index 3ecbdee4..346ddcc0 100644 --- a/rhp/v2/rpc.go +++ b/rhp/v2/rpc.go @@ -13,6 +13,7 @@ import ( "go.sia.tech/core/types" "go.sia.tech/hostd/host/contracts" "go.sia.tech/hostd/rhp" + "go.sia.tech/hostd/wallet" "go.uber.org/zap" ) @@ -159,7 +160,11 @@ func (sh *SessionHandler) rpcFormContract(s *session, log *zap.Logger) (contract renterInputs, renterOutputs := len(formationTxn.SiacoinInputs), len(formationTxn.SiacoinOutputs) toSign, discard, err := sh.wallet.FundTransaction(formationTxn, hostCollateral) if err != nil { - s.t.WriteResponseErr(ErrHostInternalError) + remoteErr := ErrHostInternalError + if errors.Is(err, wallet.ErrNotEnoughFunds) { + remoteErr = wallet.ErrNotEnoughFunds + } + s.t.WriteResponseErr(fmt.Errorf("failed to fund formation transaction: %w", remoteErr)) return contracts.Usage{}, fmt.Errorf("failed to fund formation transaction: %w", err) } defer discard() @@ -322,7 +327,11 @@ func (sh *SessionHandler) rpcRenewAndClearContract(s *session, log *zap.Logger) renterInputs, renterOutputs := len(renewalTxn.SiacoinInputs), len(renewalTxn.SiacoinOutputs) toSign, discard, err := sh.wallet.FundTransaction(&renewalTxn, lockedCollateral) if err != nil { - s.t.WriteResponseErr(ErrHostInternalError) + remoteErr := ErrHostInternalError + if errors.Is(err, wallet.ErrNotEnoughFunds) { + remoteErr = wallet.ErrNotEnoughFunds + } + s.t.WriteResponseErr(fmt.Errorf("failed to fund renewal transaction: %w", remoteErr)) return contracts.Usage{}, fmt.Errorf("failed to fund renewal transaction: %w", err) } defer discard() diff --git a/rhp/v3/rpc.go b/rhp/v3/rpc.go index 33941b8a..c714cb96 100644 --- a/rhp/v3/rpc.go +++ b/rhp/v3/rpc.go @@ -15,7 +15,7 @@ import ( "go.sia.tech/hostd/host/accounts" "go.sia.tech/hostd/host/contracts" "go.sia.tech/hostd/rhp" - "go.sia.tech/renterd/wallet" + "go.sia.tech/hostd/wallet" "go.uber.org/zap" "lukechampine.com/frand" ) @@ -339,7 +339,11 @@ func (sh *SessionHandler) handleRPCRenew(s *rhp3.Stream, log *zap.Logger) (contr renterInputs, renterOutputs := len(renewalTxn.SiacoinInputs), len(renewalTxn.SiacoinOutputs) toSign, release, err := sh.wallet.FundTransaction(&renewalTxn, lockedCollateral) if err != nil { - s.WriteResponseErr(fmt.Errorf("failed to fund renewal transaction: %w", ErrHostInternalError)) + remoteErr := ErrHostInternalError + if errors.Is(err, wallet.ErrNotEnoughFunds) { + remoteErr = wallet.ErrNotEnoughFunds + } + s.WriteResponseErr(fmt.Errorf("failed to fund renewal transaction: %w", remoteErr)) return contracts.Usage{}, fmt.Errorf("failed to fund renewal transaction: %w", err) } defer release() diff --git a/wallet/wallet.go b/wallet/wallet.go index fff6d046..c8f76949 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -29,6 +29,10 @@ const ( TxnSourceFoundationPayout TransactionSource = "foundation" ) +var ( + ErrNotEnoughFunds = errors.New("not enough funds") +) + type ( // A TransactionSource is a string indicating the source of a transaction. TransactionSource string @@ -274,7 +278,7 @@ func (sw *SingleAddressWallet) FundTransaction(txn *types.Transaction, amount ty } } if inputSum.Cmp(amount) < 0 { - return nil, nil, errors.New("insufficient balance") + return nil, nil, ErrNotEnoughFunds } else if inputSum.Cmp(amount) > 0 { txn.SiacoinOutputs = append(txn.SiacoinOutputs, types.SiacoinOutput{ Value: inputSum.Sub(amount), @@ -725,6 +729,42 @@ func convertToCore(siad encoding.SiaMarshaler, core types.DecoderFrom) { } } +// ExplicitCoveredFields returns a CoveredFields that covers all elements +// present in txn. +func ExplicitCoveredFields(txn types.Transaction) (cf types.CoveredFields) { + for i := range txn.SiacoinInputs { + cf.SiacoinInputs = append(cf.SiacoinInputs, uint64(i)) + } + for i := range txn.SiacoinOutputs { + cf.SiacoinOutputs = append(cf.SiacoinOutputs, uint64(i)) + } + for i := range txn.FileContracts { + cf.FileContracts = append(cf.FileContracts, uint64(i)) + } + for i := range txn.FileContractRevisions { + cf.FileContractRevisions = append(cf.FileContractRevisions, uint64(i)) + } + for i := range txn.StorageProofs { + cf.StorageProofs = append(cf.StorageProofs, uint64(i)) + } + for i := range txn.SiafundInputs { + cf.SiafundInputs = append(cf.SiafundInputs, uint64(i)) + } + for i := range txn.SiafundOutputs { + cf.SiafundOutputs = append(cf.SiafundOutputs, uint64(i)) + } + for i := range txn.MinerFees { + cf.MinerFees = append(cf.MinerFees, uint64(i)) + } + for i := range txn.ArbitraryData { + cf.ArbitraryData = append(cf.ArbitraryData, uint64(i)) + } + for i := range txn.Signatures { + cf.Signatures = append(cf.Signatures, uint64(i)) + } + return +} + // NewSingleAddressWallet returns a new SingleAddressWallet using the provided private key and store. func NewSingleAddressWallet(priv types.PrivateKey, cm ChainManager, tp TransactionPool, store SingleAddressStore, log *zap.Logger) (*SingleAddressWallet, error) { changeID, scanHeight, err := store.LastWalletChange() From b4ebe6953048bfacd7182b7452a93a03e99747cb Mon Sep 17 00:00:00 2001 From: Nate Maninger Date: Fri, 29 Sep 2023 07:01:41 -0600 Subject: [PATCH 2/2] wallet: fix lint --- wallet/wallet.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wallet/wallet.go b/wallet/wallet.go index c8f76949..adf57339 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -30,6 +30,8 @@ const ( ) var ( + // ErrNotEnoughFunds is returned when there are not enough unspent outputs + // to fund a transaction. ErrNotEnoughFunds = errors.New("not enough funds") )