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  }