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 }