Commit 3fbe9b54 authored by Matthew Sevey's avatar Matthew Sevey

Merge branch 'ea-fund-missedhostpayout' into 'master'

Move money to missedhostpayout instead of void when funding EA

See merge request !4553
parents 40d40475 9974e02d
Pipeline #152572699 failed with stages
in 30 minutes and 4 seconds
......@@ -132,6 +132,10 @@ var (
// ErrUnknownModification is returned if the host receives a modification
// action from the renter that it does not understand.
ErrUnknownModification = ErrorCommunication("renter is attempting an action that the host does not understand")
// ErrVoidPayoutChanged is returned if the void payout changed even though
// it wasn't expected to.
ErrVoidPayoutChanged = ErrorCommunication("void payout shouldn't change")
)
// finalizeContractArgs are the arguments passed into managedFinalizeContract.
......
......@@ -204,6 +204,11 @@ func verifyPaymentRevision(existingRevision, paymentRevision types.FileContractR
s := fmt.Sprintf("expected exactly %v to be transferred to the host, but %v was transferred: ", fromRenter, toHost)
return errors.AddContext(ErrLowHostValidOutput, s)
}
// The amount of money moved to the missing host output should match the
// money moved to the valid output.
if !paymentRevision.MissedHostPayout().Equals(existingRevision.MissedHostPayout().Add(toHost)) {
return ErrLowHostMissedOutput
}
// If the renter's valid proof output is larger than the renter's missed
// proof output, the renter has incentive to see the host fail. Make sure
......@@ -213,10 +218,9 @@ func verifyPaymentRevision(existingRevision, paymentRevision types.FileContractR
}
// Check that the host is not going to be posting collateral.
if paymentRevision.MissedHostOutput().Value.Cmp(existingRevision.MissedHostOutput().Value) < 0 {
collateral := existingRevision.MissedHostOutput().Value.Sub(paymentRevision.MissedHostOutput().Value)
s := fmt.Sprintf("host not expecting to post any collateral, but contract has host posting %v collateral", collateral)
return errors.AddContext(ErrLowHostMissedOutput, s)
if !existingVoidOutput.Value.Equals(paymentVoidOutput.Value) {
s := fmt.Sprintf("void payout wasn't expected to change")
return errors.AddContext(ErrVoidPayoutChanged, s)
}
// Check that the revision count has increased.
......@@ -246,9 +250,6 @@ func verifyPaymentRevision(existingRevision, paymentRevision types.FileContractR
if paymentRevision.NewUnlockHash != existingRevision.NewUnlockHash {
return ErrBadUnlockHash
}
if !paymentRevision.MissedHostOutput().Value.Equals(existingRevision.MissedHostOutput().Value) {
return ErrLowHostMissedOutput
}
return nil
}
......
......@@ -49,7 +49,7 @@ func (h *Host) staticPayByEphemeralAccount(stream siamux.Stream) (modules.Paymen
}
// Payment done through EAs don't move collateral
return newPaymentDetails(req.Message.Account, req.Message.Amount, types.ZeroCurrency), nil
return newPaymentDetails(req.Message.Account, req.Message.Amount), nil
}
// managedPayByContract processes a PayByContractRequest coming in over the
......@@ -92,7 +92,7 @@ func (h *Host) managedPayByContract(stream siamux.Stream) (modules.PaymentDetail
paymentRevision := revisionFromRequest(currentRevision, pbcr)
// verify the payment revision
amount, collateral, err := verifyPayByContractRevision(currentRevision, paymentRevision, bh)
amount, err := verifyPayByContractRevision(currentRevision, paymentRevision, bh)
if err != nil {
return nil, errors.AddContext(err, "Invalid payment revision")
}
......@@ -127,7 +127,7 @@ func (h *Host) managedPayByContract(stream siamux.Stream) (modules.PaymentDetail
return nil, errors.AddContext(err, "Could not send PayByContractResponse")
}
return newPaymentDetails(accountID, amount, collateral), nil
return newPaymentDetails(accountID, amount), nil
}
// managedFundAccount processes a PayByContractRequest coming in over the given
......@@ -169,13 +169,10 @@ func (h *Host) managedFundAccount(stream siamux.Stream, request modules.FundAcco
paymentRevision := revisionFromRequest(currentRevision, pbcr)
// verify the payment revision
amount, collateral, err := verifyPayByContractRevision(currentRevision, paymentRevision, bh)
amount, err := verifyPayByContractRevision(currentRevision, paymentRevision, bh)
if err != nil {
return types.ZeroCurrency, errors.AddContext(err, "Invalid payment revision")
}
if !collateral.IsZero() {
return types.ZeroCurrency, errors.AddContext(err, "Invalid payment revision, collateral was not zero")
}
// sign the revision
renterSignature := signatureFromRequest(currentRevision, pbcr)
......@@ -274,31 +271,28 @@ func signatureFromRequest(recent types.FileContractRevision, pbcr modules.PayByC
// verifyPayByContractRevision verifies the given payment revision and returns
// the amount that was transferred, the collateral that was moved and a
// potential error.
func verifyPayByContractRevision(current, payment types.FileContractRevision, blockHeight types.BlockHeight) (amount, collateral types.Currency, err error) {
func verifyPayByContractRevision(current, payment types.FileContractRevision, blockHeight types.BlockHeight) (amount types.Currency, err error) {
if err = verifyPaymentRevision(current, payment, blockHeight, types.ZeroCurrency); err != nil {
return
}
// Note that we can safely subtract the values of the outputs seeing as verifyPaymentRevision will have checked for potential underflows
amount = payment.ValidHostPayout().Sub(current.ValidHostPayout())
collateral = current.MissedHostOutput().Value.Sub(payment.MissedHostOutput().Value)
return
}
// payment details is a helper struct that implements the PaymentDetails
// interface.
type paymentDetails struct {
account modules.AccountID
amount types.Currency
addedCollateral types.Currency
account modules.AccountID
amount types.Currency
}
// newPaymentDetails returns a new paymentDetails object using the given values
func newPaymentDetails(account modules.AccountID, amountPaid, addedCollateral types.Currency) *paymentDetails {
func newPaymentDetails(account modules.AccountID, amountPaid types.Currency) *paymentDetails {
return &paymentDetails{
account: account,
amount: amountPaid,
addedCollateral: addedCollateral,
account: account,
amount: amountPaid,
}
}
......@@ -308,7 +302,3 @@ func (pd *paymentDetails) AccountID() modules.AccountID { return pd.account }
// Amount returns how much money the host received.
func (pd *paymentDetails) Amount() types.Currency { return pd.amount }
// AddedCollatoral returns the amount of collateral that moved from the host to
// the void output.
func (pd *paymentDetails) AddedCollateral() types.Currency { return pd.addedCollateral }
......@@ -230,7 +230,7 @@ func TestVerifyPaymentRevision(t *testing.T) {
// expect ErrLowHostMissedOutput
badCurr = deepCopy(curr)
badCurr.SetMissedHostPayout(payment.MissedHostOutput().Value.Sub64(1))
badCurr.SetMissedHostPayout(payment.MissedHostPayout().Add64(1))
err = verifyPaymentRevision(badCurr, payment, height, amount)
if err != ErrLowHostMissedOutput {
t.Fatalf("Expected ErrLowHostMissedOutput but received '%v'", err)
......@@ -352,9 +352,6 @@ func testPayByContract(t *testing.T, pair *renterHostPair) {
if !payment.Amount().Equals(amount) {
t.Fatalf("Unexpected amount paid, expected %v actual %v", amountStr, payment.Amount().HumanString())
}
if !payment.AddedCollateral().IsZero() {
t.Fatalf("Unexpected collateral added, expected 0H actual %v", payment.AddedCollateral())
}
// prepare a set of payouts that do not deduct payment from the renter
validPayouts, missedPayouts := updated.payouts()
......
......@@ -55,10 +55,6 @@ func (h *Host) managedRPCExecuteProgram(stream siamux.Stream) error {
}
}()
}()
// Don't expect any added collateral.
if !pd.AddedCollateral().IsZero() {
return fmt.Errorf("no collateral should be moved but got %v", pd.AddedCollateral().HumanString())
}
// Read request
var epr modules.RPCExecuteProgramRequest
......
......@@ -236,7 +236,7 @@ func TestFundEphemeralAccountRPC(t *testing.T) {
t.Fatal(err)
}
numRevisions := len(so.RevisionTransactionSet)
so.RevisionTransactionSet[numRevisions-1].FileContractRevisions[0].SetMissedHostPayout(collateral)
so.RevisionTransactionSet[numRevisions-1].FileContractRevisions[0].SetMissedVoidPayout(collateral)
ht.host.managedLockStorageObligation(so.id())
err = ht.host.managedModifyStorageObligation(so, []crypto.Hash{}, make(map[crypto.Hash][]byte))
if err != nil {
......@@ -246,19 +246,19 @@ func TestFundEphemeralAccountRPC(t *testing.T) {
// create a revision and move some collateral
rev, _, err = pair.managedPaymentRevision(funding.Add(pt.FundAccountCost))
rev.SetMissedHostPayout(rev.MissedHostOutput().Value.Sub(collateral))
voidOutput, err := rev.MissedVoidOutput()
if err != nil {
t.Fatal(err)
}
rev.SetMissedHostPayout(rev.MissedHostOutput().Value.Sub(collateral))
err = rev.SetMissedVoidPayout(voidOutput.Value.Add(collateral))
if err != nil {
t.Fatal(err)
}
_, _, err = runWithRequest(newPayByContractRequest(rev, pair.managedSign(rev), refundAccount))
if err == nil || !strings.Contains(err.Error(), "host not expecting to post any collateral") {
t.Fatalf("Expected error '%v', instead error was '%v'", "host not expecting to post any collateral", err)
if err == nil || !strings.Contains(err.Error(), ErrLowHostMissedOutput.Error()) {
t.Fatalf("Expected error ErrLowHostMissedOutput, instead error was '%v'", err)
}
// undo host collateral update
......@@ -266,7 +266,7 @@ func TestFundEphemeralAccountRPC(t *testing.T) {
if err != nil {
t.Fatal(err)
}
so.RevisionTransactionSet[numRevisions-1].FileContractRevisions[0].SetMissedHostPayout(types.ZeroCurrency)
so.RevisionTransactionSet[numRevisions-1].FileContractRevisions[0].SetMissedVoidPayout(types.ZeroCurrency)
ht.host.managedLockStorageObligation(so.id())
err = ht.host.managedModifyStorageObligation(so, []crypto.Hash{}, make(map[crypto.Hash][]byte))
if err != nil {
......
......@@ -3,7 +3,6 @@ package host
import (
"container/heap"
"encoding/json"
"fmt"
"sync"
"time"
......@@ -133,10 +132,6 @@ func (h *Host) managedRPCUpdatePriceTable(stream siamux.Stream) error {
if payment.Amount().Cmp(pt.UpdatePriceTableCost) < 0 {
return modules.ErrInsufficientPaymentForRPC
}
// Don't expect any added collateral.
if !payment.AddedCollateral().IsZero() {
return fmt.Errorf("no collateral should be moved but got %v", payment.AddedCollateral().HumanString())
}
// after payment has been received, track the price table in the host's list
// of price tables and signal the renter we consider the price table valid
......
......@@ -73,7 +73,6 @@ type PaymentProvider interface {
type PaymentDetails interface {
AccountID() AccountID
Amount() types.Currency
AddedCollateral() types.Currency
}
// Payment identifiers
......
......@@ -211,6 +211,9 @@ func (r *Renter) newWorker(hostPubKey types.SiaPublicKey) (*worker, error) {
// TODO: check that the balance target makes sense in function of the
// amount of MDM programs it can run with that amount of money
balanceTarget := types.SiacoinPrecision
if r.deps.Disrupt("DisableFunding") {
balanceTarget = types.ZeroCurrency
}
w := &worker{
staticHostPubKey: hostPubKey,
......
......@@ -237,6 +237,9 @@ func (w *worker) managedAccountNeedsRefill() bool {
// managedRefillAccount will refill the account if it needs to be refilled
func (w *worker) managedRefillAccount() {
if w.renter.deps.Disrupt("DisableFunding") {
return // don't refill account
}
// the account balance dropped to below half the balance target, refill
balance := w.staticAccount.managedAvailableBalance()
amount := w.staticBalanceTarget.Sub(balance)
......
......@@ -9,6 +9,10 @@ import (
)
type (
// DependencyPreventEARefill prevents EAs from being refilled automatically.
DependencyPreventEARefill struct {
modules.ProductionDependencies
}
// DependencyLowFundsFormationFail will cause contract formation to fail due
// to low funds in the allowance.
DependencyLowFundsFormationFail struct {
......@@ -199,6 +203,11 @@ func newDependencyInterruptAfterNCalls(str string, n int) *DependencyInterruptAf
}
}
// Disrupt returns true if the correct string is provided.
func (d *DependencyPreventEARefill) Disrupt(s string) bool {
return s == "DisableFunding"
}
// Disrupt returns true if the correct string is provided.
func (d *DependencyBlockResumeJobDownloadUntilTimeout) Disrupt(s string) bool {
if s == "BlockUntilTimeout" {
......
......@@ -366,8 +366,8 @@ func TestHostContracts(t *testing.T) {
t.Fatal("valid payout should be greater than old valid payout")
}
if hc.Contracts[0].MissedProofOutputs[1].Value.Cmp(prevMissPayout) != -1 {
t.Fatal("missed payout should be less than old missed payout")
if cmp := hc.Contracts[0].MissedProofOutputs[1].Value.Cmp(prevMissPayout); cmp != 1 {
t.Fatal("missed payout should be more than old missed payout", cmp)
}
}
......
......@@ -575,9 +575,8 @@ func TestWalletSend(t *testing.T) {
// Create a testgroup
groupParams := siatest.GroupParams{
Hosts: 0,
Renters: 2,
Miners: 1,
Hosts: 0,
Miners: 1,
}
tg, err := siatest.NewGroupFromTemplate(walletTestDir(t.Name()), groupParams)
if err != nil {
......@@ -589,6 +588,14 @@ func TestWalletSend(t *testing.T) {
}
}()
// Add 2 renters.
rp := node.RenterTemplate
rp.RenterDeps = &dependencies.DependencyPreventEARefill{}
_, err = tg.AddNodeN(rp, 2)
if err != nil {
t.Fatal(err)
}
renters := tg.Renters()
renter1, renter2 := renters[0], renters[1]
miner := tg.Miners()[0]
......
......@@ -142,16 +142,9 @@ func (fcr FileContractRevision) PaymentRevision(amount Currency) (FileContractRe
rev.SetValidRenterPayout(fcr.ValidRenterPayout().Sub(amount))
rev.SetValidHostPayout(fcr.ValidHostPayout().Add(amount))
// move missed payout from renter to void
// move missed payout from renter to host
rev.SetMissedRenterPayout(fcr.MissedRenterOutput().Value.Sub(amount))
voidOutput, err := fcr.MissedVoidOutput()
if err != nil {
return FileContractRevision{}, errors.AddContext(err, "failed to get missed void output")
}
err = rev.SetMissedVoidPayout(voidOutput.Value.Add(amount))
if err != nil {
return FileContractRevision{}, errors.AddContext(err, "failed to set missed void output")
}
rev.SetMissedHostPayout(rev.MissedHostPayout().Add(amount))
// increment revision number
rev.NewRevisionNumber++
......
package types
import (
"strings"
"testing"
"gitlab.com/NebulousLabs/errors"
......@@ -94,14 +93,6 @@ func TestPaymentRevision(t *testing.T) {
t.Fatalf("Unexpected error '%v'", err)
}
// expect error if void output is missing
rev = mock(100, 100)
rev.NewMissedProofOutputs = append([]SiacoinOutput{}, rev.NewMissedProofOutputs[0], rev.NewMissedProofOutputs[1])
_, err = rev.PaymentRevision(NewCurrency64(100))
if err == nil || !strings.Contains(err.Error(), "failed to get missed void output") {
t.Fatalf("Unexpected error '%v'", err)
}
// verify funds moved to the appropriate outputs
existing := mock(100, 0)
amount := NewCurrency64(99)
......@@ -115,6 +106,9 @@ func TestPaymentRevision(t *testing.T) {
if !payment.ValidHostPayout().Sub(existing.ValidHostPayout()).Equals(amount) {
t.Fatal("Unexpected payout moved from renter to host")
}
if !payment.MissedHostPayout().Sub(existing.MissedHostPayout()).Equals(amount) {
t.Fatal("Unexpected payout moved from renter to host")
}
if !payment.MissedRenterOutput().Value.Equals(existing.MissedRenterOutput().Value.Sub(amount)) {
t.Fatal("Unexpected payout moved from renter to void")
}
......@@ -123,7 +117,7 @@ func TestPaymentRevision(t *testing.T) {
if err := errors.Compose(err1, err2); err != nil {
t.Fatal(err)
}
if !pmvo.Value.Equals(emvo.Value.Add(amount)) {
if !pmvo.Value.Equals(emvo.Value) {
t.Fatal("Unexpected payout moved from renter to void")
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment