Commit 084ec26d authored by David Vorick's avatar David Vorick Committed by GitHub

Merge pull request #1581 from NebulousLabs/cached-renew

use cachedRevision if renew fails
parents 1715993e 661a6cbd
......@@ -955,3 +955,99 @@ func TestIntegrationEditorCaching(t *testing.T) {
}
d4.Close()
}
// TestIntegrationCachedRenew tests that the contractor can renew with a host
// after being interrupted during contract revision.
func TestIntegrationCachedRenew(t *testing.T) {
if testing.Short() {
t.SkipNow()
}
t.Parallel()
// create testing trio
h, c, _, err := newTestingTrio("TestIntegrationCachedRenew")
if err != nil {
t.Fatal(err)
}
defer h.Close()
// get the host's entry from the db
hostEntry, ok := c.hdb.Host(h.PublicKey())
if !ok {
t.Fatal("no entry for host in db")
}
// form a contract with the host
contract, err := c.managedNewContract(hostEntry, 10, c.blockHeight+100)
if err != nil {
t.Fatal(err)
}
c.mu.Lock()
c.contracts[contract.ID] = contract
c.mu.Unlock()
// revise the contract
editor, err := c.Editor(contract.ID)
if err != nil {
t.Fatal(err)
}
data, err := crypto.RandBytes(int(modules.SectorSize))
if err != nil {
t.Fatal(err)
}
root, err := editor.Upload(data)
if err != nil {
t.Fatal(err)
}
err = editor.Close()
if err != nil {
t.Fatal(err)
}
// download the data
downloader, err := c.Downloader(contract.ID)
if err != nil {
t.Fatal(err)
}
retrieved, err := downloader.Sector(root)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(data, retrieved) {
t.Fatal("downloaded data does not match original")
}
err = downloader.Close()
if err != nil {
t.Fatal(err)
}
contract = c.contracts[contract.ID]
// corrupt the contract and cachedRevision
badContract := contract
badContract.LastRevision.NewRevisionNumber--
badContract.LastRevisionTxn.TransactionSignatures = nil // delete signatures
c.mu.Lock()
cr := c.cachedRevisions[contract.ID]
cr.Revision.NewRevisionNumber = 0
cr.Revision.NewRevisionNumber--
c.cachedRevisions[contract.ID] = cr
c.contracts[badContract.ID] = badContract
c.mu.Unlock()
// Renew should fail with the bad contract + cachedRevision
_, err = c.managedRenew(badContract, 20, c.blockHeight+200)
if !proto.IsRevisionMismatch(err) {
t.Fatal("expected revision mismatch, got", err)
}
// add cachedRevision
cachedRev := cachedRevision{contract.LastRevision, contract.MerkleRoots}
c.mu.Lock()
c.cachedRevisions[contract.ID] = cachedRev
c.mu.Unlock()
// Renew should now succeed after loading the cachedRevision
_, err = c.managedRenew(badContract, 20, c.blockHeight+200)
if err != nil {
t.Fatal(err)
}
}
......@@ -46,6 +46,24 @@ func (c *Contractor) managedRenew(contract modules.RenterContract, numSectors ui
// execute negotiation protocol
newContract, err := proto.Renew(contract, params, txnBuilder, c.tpool)
if proto.IsRevisionMismatch(err) {
// return unused outputs to wallet
txnBuilder.Drop()
// try again with the cached revision
c.mu.RLock()
cached, ok := c.cachedRevisions[contract.ID]
c.mu.RUnlock()
if !ok {
// nothing we can do; return original error
c.log.Printf("wanted to recover contract %v with host %v, but no revision was cached", contract.ID, contract.NetAddress)
return modules.RenterContract{}, err
}
c.log.Printf("host %v has different revision for %v; retrying with cached revision", contract.NetAddress, contract.ID)
contract.LastRevision = cached.Revision
// need to start a new transaction
txnBuilder = c.wallet.StartTransaction()
newContract, err = proto.Renew(contract, params, txnBuilder, c.tpool)
}
if err != nil {
txnBuilder.Drop() // return unused outputs to wallet
return modules.RenterContract{}, err
......
......@@ -104,7 +104,9 @@ func Renew(contract modules.RenterContract, params ContractParams, txnBuilder tr
}
// verify that both parties are renewing the same contract
if err = verifyRecentRevision(conn, contract); err != nil {
return modules.RenterContract{}, errors.New("revision exchange failed: " + err.Error())
// don't add context; want to preserve the original error type so that
// callers can check using IsRevisionMismatch
return modules.RenterContract{}, err
}
// verify the host's settings and confirm its identity
host, err = verifySettings(conn, host)
......
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