gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/proto/contract_test.go (about)

     1  package proto
     2  
     3  import (
     4  	"bytes"
     5  	"path/filepath"
     6  	"reflect"
     7  	"testing"
     8  
     9  	"gitlab.com/SiaPrime/SiaPrime/build"
    10  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    11  	"gitlab.com/SiaPrime/SiaPrime/encoding"
    12  	"gitlab.com/SiaPrime/SiaPrime/modules"
    13  	"gitlab.com/SiaPrime/SiaPrime/types"
    14  )
    15  
    16  // TestContractUncommittedTxn tests that if a contract revision is left in an
    17  // uncommitted state, either version of the contract can be recovered.
    18  func TestContractUncommittedTxn(t *testing.T) {
    19  	if testing.Short() {
    20  		t.SkipNow()
    21  	}
    22  	// create contract set with one contract
    23  	dir := build.TempDir(filepath.Join("proto", t.Name()))
    24  	cs, err := NewContractSet(dir, modules.ProdDependencies)
    25  	if err != nil {
    26  		t.Fatal(err)
    27  	}
    28  	initialHeader := contractHeader{
    29  		Transaction: types.Transaction{
    30  			FileContractRevisions: []types.FileContractRevision{{
    31  				NewRevisionNumber:    1,
    32  				NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
    33  				UnlockConditions: types.UnlockConditions{
    34  					PublicKeys: []types.SiaPublicKey{{}, {}},
    35  				},
    36  			}},
    37  		},
    38  	}
    39  	initialRoots := []crypto.Hash{{1}}
    40  	c, err := cs.managedInsertContract(initialHeader, initialRoots)
    41  	if err != nil {
    42  		t.Fatal(err)
    43  	}
    44  
    45  	// apply an update to the contract, but don't commit it
    46  	sc := cs.mustAcquire(t, c.ID)
    47  	revisedHeader := contractHeader{
    48  		Transaction: types.Transaction{
    49  			FileContractRevisions: []types.FileContractRevision{{
    50  				NewRevisionNumber:    2,
    51  				NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
    52  				UnlockConditions: types.UnlockConditions{
    53  					PublicKeys: []types.SiaPublicKey{{}, {}},
    54  				},
    55  			}},
    56  		},
    57  		StorageSpending: types.NewCurrency64(7),
    58  		UploadSpending:  types.NewCurrency64(17),
    59  	}
    60  	revisedRoots := []crypto.Hash{{1}, {2}}
    61  	fcr := revisedHeader.Transaction.FileContractRevisions[0]
    62  	newRoot := revisedRoots[1]
    63  	storageCost := revisedHeader.StorageSpending.Sub(initialHeader.StorageSpending)
    64  	bandwidthCost := revisedHeader.UploadSpending.Sub(initialHeader.UploadSpending)
    65  	walTxn, err := sc.managedRecordUploadIntent(fcr, newRoot, storageCost, bandwidthCost)
    66  	if err != nil {
    67  		t.Fatal(err)
    68  	}
    69  
    70  	// the state of the contract should match the initial state
    71  	// NOTE: can't use reflect.DeepEqual for the header because it contains
    72  	// types.Currency fields
    73  	merkleRoots, err := sc.merkleRoots.merkleRoots()
    74  	if err != nil {
    75  		t.Fatal("failed to get merkle roots", err)
    76  	}
    77  	if !bytes.Equal(encoding.Marshal(sc.header), encoding.Marshal(initialHeader)) {
    78  		t.Fatal("contractHeader should match initial contractHeader")
    79  	} else if !reflect.DeepEqual(merkleRoots, initialRoots) {
    80  		t.Fatal("Merkle roots should match initial Merkle roots")
    81  	}
    82  
    83  	// close and reopen the contract set.
    84  	if err := cs.Close(); err != nil {
    85  		t.Fatal(err)
    86  	}
    87  	cs, err = NewContractSet(dir, modules.ProdDependencies)
    88  	if err != nil {
    89  		t.Fatal(err)
    90  	}
    91  	// the uncommitted transaction should be stored in the contract
    92  	sc = cs.mustAcquire(t, c.ID)
    93  	if len(sc.unappliedTxns) != 1 {
    94  		t.Fatal("expected 1 unappliedTxn, got", len(sc.unappliedTxns))
    95  	} else if !bytes.Equal(sc.unappliedTxns[0].Updates[0].Instructions, walTxn.Updates[0].Instructions) {
    96  		t.Fatal("WAL transaction changed")
    97  	}
    98  	// the state of the contract should match the initial state
    99  	merkleRoots, err = sc.merkleRoots.merkleRoots()
   100  	if err != nil {
   101  		t.Fatal("failed to get merkle roots:", err)
   102  	}
   103  	if !bytes.Equal(encoding.Marshal(sc.header), encoding.Marshal(initialHeader)) {
   104  		t.Fatal("contractHeader should match initial contractHeader", sc.header, initialHeader)
   105  	} else if !reflect.DeepEqual(merkleRoots, initialRoots) {
   106  		t.Fatal("Merkle roots should match initial Merkle roots")
   107  	}
   108  
   109  	// apply the uncommitted transaction
   110  	err = sc.managedCommitTxns()
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  	// the uncommitted transaction should be gone now
   115  	if len(sc.unappliedTxns) != 0 {
   116  		t.Fatal("expected 0 unappliedTxns, got", len(sc.unappliedTxns))
   117  	}
   118  	// the state of the contract should now match the revised state
   119  	merkleRoots, err = sc.merkleRoots.merkleRoots()
   120  	if err != nil {
   121  		t.Fatal("failed to get merkle roots:", err)
   122  	}
   123  	if !bytes.Equal(encoding.Marshal(sc.header), encoding.Marshal(revisedHeader)) {
   124  		t.Fatal("contractHeader should match revised contractHeader", sc.header, revisedHeader)
   125  	} else if !reflect.DeepEqual(merkleRoots, revisedRoots) {
   126  		t.Fatal("Merkle roots should match revised Merkle roots")
   127  	}
   128  }
   129  
   130  // TestContractIncompleteWrite tests that if the merkle root section has the wrong
   131  // length due to an incomplete write, it is truncated and the wal transactions
   132  // are applied.
   133  func TestContractIncompleteWrite(t *testing.T) {
   134  	if testing.Short() {
   135  		t.SkipNow()
   136  	}
   137  	// create contract set with one contract
   138  	dir := build.TempDir(filepath.Join("proto", t.Name()))
   139  	cs, err := NewContractSet(dir, modules.ProdDependencies)
   140  	if err != nil {
   141  		t.Fatal(err)
   142  	}
   143  	initialHeader := contractHeader{
   144  		Transaction: types.Transaction{
   145  			FileContractRevisions: []types.FileContractRevision{{
   146  				NewRevisionNumber:    1,
   147  				NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
   148  				UnlockConditions: types.UnlockConditions{
   149  					PublicKeys: []types.SiaPublicKey{{}, {}},
   150  				},
   151  			}},
   152  		},
   153  	}
   154  	initialRoots := []crypto.Hash{{1}}
   155  	c, err := cs.managedInsertContract(initialHeader, initialRoots)
   156  	if err != nil {
   157  		t.Fatal(err)
   158  	}
   159  
   160  	// apply an update to the contract, but don't commit it
   161  	sc := cs.mustAcquire(t, c.ID)
   162  	revisedHeader := contractHeader{
   163  		Transaction: types.Transaction{
   164  			FileContractRevisions: []types.FileContractRevision{{
   165  				NewRevisionNumber:    2,
   166  				NewValidProofOutputs: []types.SiacoinOutput{{}, {}},
   167  				UnlockConditions: types.UnlockConditions{
   168  					PublicKeys: []types.SiaPublicKey{{}, {}},
   169  				},
   170  			}},
   171  		},
   172  		StorageSpending: types.NewCurrency64(7),
   173  		UploadSpending:  types.NewCurrency64(17),
   174  	}
   175  	revisedRoots := []crypto.Hash{{1}, {2}}
   176  	fcr := revisedHeader.Transaction.FileContractRevisions[0]
   177  	newRoot := revisedRoots[1]
   178  	storageCost := revisedHeader.StorageSpending.Sub(initialHeader.StorageSpending)
   179  	bandwidthCost := revisedHeader.UploadSpending.Sub(initialHeader.UploadSpending)
   180  	_, err = sc.managedRecordUploadIntent(fcr, newRoot, storageCost, bandwidthCost)
   181  	if err != nil {
   182  		t.Fatal(err)
   183  	}
   184  
   185  	// the state of the contract should match the initial state
   186  	// NOTE: can't use reflect.DeepEqual for the header because it contains
   187  	// types.Currency fields
   188  	merkleRoots, err := sc.merkleRoots.merkleRoots()
   189  	if err != nil {
   190  		t.Fatal("failed to get merkle roots", err)
   191  	}
   192  	if !bytes.Equal(encoding.Marshal(sc.header), encoding.Marshal(initialHeader)) {
   193  		t.Fatal("contractHeader should match initial contractHeader")
   194  	} else if !reflect.DeepEqual(merkleRoots, initialRoots) {
   195  		t.Fatal("Merkle roots should match initial Merkle roots")
   196  	}
   197  
   198  	// get the size of the merkle roots file.
   199  	size, err := sc.merkleRoots.rootsFile.Size()
   200  	if err != nil {
   201  		t.Fatal(err)
   202  	}
   203  	// the size should be crypto.HashSize since we have exactly one root.
   204  	if size != crypto.HashSize {
   205  		t.Fatal("unexpected merkle root file size", size)
   206  	}
   207  	// truncate the rootsFile to simulate a corruption while writing the second
   208  	// root.
   209  	err = sc.merkleRoots.rootsFile.Truncate(size + crypto.HashSize/2)
   210  	if err != nil {
   211  		t.Fatal(err)
   212  	}
   213  
   214  	// close and reopen the contract set.
   215  	if err := cs.Close(); err != nil {
   216  		t.Fatal(err)
   217  	}
   218  	cs, err = NewContractSet(dir, modules.ProdDependencies)
   219  	if err != nil {
   220  		t.Fatal(err)
   221  	}
   222  	// the uncommitted txn should be gone.
   223  	sc = cs.mustAcquire(t, c.ID)
   224  	if len(sc.unappliedTxns) != 0 {
   225  		t.Fatal("expected 0 unappliedTxn, got", len(sc.unappliedTxns))
   226  	}
   227  	if sc.merkleRoots.len() != 2 {
   228  		t.Fatal("expected 2 roots, got", sc.merkleRoots.len())
   229  	}
   230  	cs.Return(sc)
   231  	cs.Close()
   232  }