gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/contractor/negotiate_test.go (about) 1 package contractor 2 3 import ( 4 "path/filepath" 5 "testing" 6 7 "gitlab.com/NebulousLabs/ratelimit" 8 "gitlab.com/SkynetLabs/skyd/build" 9 "gitlab.com/SkynetLabs/skyd/skymodules" 10 "gitlab.com/SkynetLabs/skyd/skymodules/renter/hostdb" 11 "go.sia.tech/siad/crypto" 12 "go.sia.tech/siad/modules" 13 "go.sia.tech/siad/modules/consensus" 14 "go.sia.tech/siad/modules/gateway" 15 "go.sia.tech/siad/modules/miner" 16 "go.sia.tech/siad/modules/transactionpool" 17 modWallet "go.sia.tech/siad/modules/wallet" // name conflicts with type 18 "go.sia.tech/siad/types" 19 20 "gitlab.com/NebulousLabs/errors" 21 ) 22 23 // contractorTester contains all of the modules that are used while testing the contractor. 24 type contractorTester struct { 25 cs modules.ConsensusSet 26 gateway modules.Gateway 27 miner modules.TestMiner 28 tpool modules.TransactionPool 29 wallet modules.Wallet 30 hdb hostDB 31 32 contractor *Contractor 33 } 34 35 // Close shuts down the contractor tester. 36 func (rt *contractorTester) Close() error { 37 errs := []error{ 38 rt.gateway.Close(), 39 rt.cs.Close(), 40 rt.tpool.Close(), 41 rt.miner.Close(), 42 rt.wallet.Close(), 43 } 44 return build.JoinErrors(errs, ": ") 45 } 46 47 // newContractorTester creates a ready-to-use contractor tester with money in the 48 // wallet. 49 func newContractorTester(name string) (*contractorTester, closeFn, error) { 50 // Create the skymodules. 51 testdir := build.TempDir("contractor", name) 52 g, err := gateway.New("localhost:0", false, filepath.Join(testdir, modules.GatewayDir)) 53 if err != nil { 54 return nil, nil, err 55 } 56 cs, errChan := consensus.New(g, false, filepath.Join(testdir, modules.ConsensusDir)) 57 if err := <-errChan; err != nil { 58 return nil, nil, err 59 } 60 tp, err := transactionpool.New(cs, g, filepath.Join(testdir, modules.TransactionPoolDir)) 61 if err != nil { 62 return nil, nil, err 63 } 64 w, err := modWallet.New(cs, tp, filepath.Join(testdir, modules.WalletDir)) 65 if err != nil { 66 return nil, nil, err 67 } 68 key := crypto.GenerateSiaKey(crypto.TypeDefaultWallet) 69 _, err = w.Encrypt(key) 70 if err != nil { 71 return nil, nil, err 72 } 73 err = w.Unlock(key) 74 if err != nil { 75 return nil, nil, err 76 } 77 siaMuxDir := filepath.Join(testdir, modules.SiaMuxDir) 78 mux, err := modules.NewSiaMux(siaMuxDir, testdir, "localhost:0", "localhost:0") 79 if err != nil { 80 return nil, nil, err 81 } 82 hdb, errChan := hostdb.New(g, cs, tp, mux, filepath.Join(testdir, skymodules.RenterDir)) 83 if err := <-errChan; err != nil { 84 return nil, nil, err 85 } 86 m, err := miner.New(cs, tp, w, filepath.Join(testdir, modules.MinerDir)) 87 if err != nil { 88 return nil, nil, err 89 } 90 rl := ratelimit.NewRateLimit(0, 0, 0) 91 c, errChan := New(cs, w, tp, hdb, rl, filepath.Join(testdir, skymodules.RenterDir)) 92 if err := <-errChan; err != nil { 93 return nil, nil, err 94 } 95 96 // Assemble all pieces into a contractor tester. 97 ct := &contractorTester{ 98 cs: cs, 99 gateway: g, 100 miner: m, 101 tpool: tp, 102 wallet: w, 103 hdb: hdb, 104 105 contractor: c, 106 } 107 108 // Mine blocks until there is money in the wallet. 109 for i := types.BlockHeight(0); i <= types.MaturityDelay; i++ { 110 _, err := ct.miner.AddBlock() 111 if err != nil { 112 return nil, nil, err 113 } 114 } 115 116 cf := func() error { 117 return errors.Compose(c.Close(), m.Close(), hdb.Close(), mux.Close(), w.Close(), tp.Close(), cs.Close(), g.Close()) 118 } 119 return ct, cf, nil 120 } 121 122 func TestNegotiateContract(t *testing.T) { 123 if testing.Short() { 124 t.SkipNow() 125 } 126 t.Parallel() 127 ct, cf, err := newContractorTester(t.Name()) 128 if err != nil { 129 t.Fatal(err) 130 } 131 defer tryClose(cf, t) 132 133 payout := types.NewCurrency64(1e16) 134 135 fc := types.FileContract{ 136 FileSize: 0, 137 FileMerkleRoot: crypto.Hash{}, // no proof possible without data 138 WindowStart: 100, 139 WindowEnd: 1000, 140 Payout: payout, 141 ValidProofOutputs: []types.SiacoinOutput{ 142 {Value: types.PostTax(ct.contractor.blockHeight, payout), UnlockHash: types.UnlockHash{}}, 143 {Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}}, 144 }, 145 MissedProofOutputs: []types.SiacoinOutput{ 146 // same as above 147 {Value: types.PostTax(ct.contractor.blockHeight, payout), UnlockHash: types.UnlockHash{}}, 148 // goes to the void, not the hostdb 149 {Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}}, 150 }, 151 UnlockHash: types.UnlockHash{}, 152 RevisionNumber: 0, 153 } 154 155 txnBuilder, err := ct.wallet.StartTransaction() 156 if err != nil { 157 t.Fatal(err) 158 } 159 err = txnBuilder.FundSiacoins(fc.Payout) 160 if err != nil { 161 t.Fatal(err) 162 } 163 txnBuilder.AddFileContract(fc) 164 signedTxnSet, err := txnBuilder.Sign(true) 165 if err != nil { 166 t.Fatal(err) 167 } 168 169 err = ct.tpool.AcceptTransactionSet(signedTxnSet) 170 if err != nil { 171 t.Fatal(err) 172 } 173 } 174 175 func TestReviseContract(t *testing.T) { 176 if testing.Short() { 177 t.SkipNow() 178 } 179 t.Parallel() 180 ct, cf, err := newContractorTester(t.Name()) 181 if err != nil { 182 t.Fatal(err) 183 } 184 defer tryClose(cf, t) 185 186 // get an address 187 ourAddr, err := ct.wallet.NextAddress() 188 if err != nil { 189 t.Fatal(err) 190 } 191 192 // generate keys 193 sk, pk := crypto.GenerateKeyPair() 194 renterPubKey := types.SiaPublicKey{ 195 Algorithm: types.SignatureEd25519, 196 Key: pk[:], 197 } 198 199 uc := types.UnlockConditions{ 200 PublicKeys: []types.SiaPublicKey{renterPubKey, renterPubKey}, 201 SignaturesRequired: 1, 202 } 203 204 // create file contract 205 payout := types.NewCurrency64(1e16) 206 207 fc := types.FileContract{ 208 FileSize: 0, 209 FileMerkleRoot: crypto.Hash{}, // no proof possible without data 210 WindowStart: 100, 211 WindowEnd: 1000, 212 Payout: payout, 213 UnlockHash: uc.UnlockHash(), 214 RevisionNumber: 0, 215 } 216 // outputs need account for tax 217 fc.ValidProofOutputs = []types.SiacoinOutput{ 218 {Value: types.PostTax(ct.contractor.blockHeight, payout), UnlockHash: ourAddr.UnlockHash()}, 219 {Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}}, // no collateral 220 } 221 fc.MissedProofOutputs = []types.SiacoinOutput{ 222 // same as above 223 fc.ValidRenterOutput(), 224 // goes to the void, not the hostdb 225 {Value: types.ZeroCurrency, UnlockHash: types.UnlockHash{}}, 226 } 227 228 txnBuilder, err := ct.wallet.StartTransaction() 229 if err != nil { 230 t.Fatal(err) 231 } 232 err = txnBuilder.FundSiacoins(fc.Payout) 233 if err != nil { 234 t.Fatal(err) 235 } 236 txnBuilder.AddFileContract(fc) 237 signedTxnSet, err := txnBuilder.Sign(true) 238 if err != nil { 239 t.Fatal(err) 240 } 241 242 // submit contract 243 err = ct.tpool.AcceptTransactionSet(signedTxnSet) 244 if err != nil { 245 t.Fatal(err) 246 } 247 248 // create revision 249 fcid := signedTxnSet[len(signedTxnSet)-1].FileContractID(0) 250 rev := types.FileContractRevision{ 251 ParentID: fcid, 252 UnlockConditions: uc, 253 NewFileSize: 10, 254 NewWindowStart: 100, 255 NewWindowEnd: 1000, 256 NewRevisionNumber: 1, 257 NewValidProofOutputs: fc.ValidProofOutputs, 258 NewMissedProofOutputs: fc.MissedProofOutputs, 259 } 260 261 // create transaction containing the revision 262 signedTxn := types.Transaction{ 263 FileContractRevisions: []types.FileContractRevision{rev}, 264 TransactionSignatures: []types.TransactionSignature{{ 265 ParentID: crypto.Hash(fcid), 266 CoveredFields: types.CoveredFields{FileContractRevisions: []uint64{0}}, 267 PublicKeyIndex: 0, // hostdb key is always first -- see negotiateContract 268 }}, 269 } 270 271 // sign the transaction 272 encodedSig := crypto.SignHash(signedTxn.SigHash(0, ct.cs.Height()), sk) 273 signedTxn.TransactionSignatures[0].Signature = encodedSig[:] 274 275 err = signedTxn.StandaloneValid(ct.contractor.blockHeight) 276 if err != nil { 277 t.Fatal(err) 278 } 279 280 // submit revision 281 err = ct.tpool.AcceptTransactionSet([]types.Transaction{signedTxn}) 282 if err != nil { 283 t.Fatal(err) 284 } 285 }