contracts_test.go 8.95 KB
Newer Older
David Vorick's avatar
David Vorick committed
1 2
package consensus

3
import (
David Vorick's avatar
David Vorick committed
4 5
	"bytes"
	"crypto/rand"
6 7 8
	"testing"

	"github.com/NebulousLabs/Sia/crypto"
9
	"github.com/NebulousLabs/Sia/encoding"
David Vorick's avatar
David Vorick committed
10
	"github.com/NebulousLabs/Sia/hash"
11 12 13
)

// contractTxn funds and returns a transaction with a file contract.
David Vorick's avatar
David Vorick committed
14
func contractTxn(t *testing.T, s *State, delay BlockHeight, duration BlockHeight) (txn Transaction) {
15 16 17 18 19 20 21 22 23 24
	// Create the keys and a siacoin output that adds coins to the keys.
	sk, pk, err := crypto.GenerateSignatureKeys()
	if err != nil {
		t.Fatal(err)
	}
	spendConditions := SpendConditions{
		NumSignatures: 1,
		PublicKeys: []SiaPublicKey{
			SiaPublicKey{
				Algorithm: ED25519Identifier,
25
				Key:       encoding.Marshal(pk),
26 27 28 29 30 31 32 33 34 35 36 37
			},
		},
	}
	coinAddress := spendConditions.CoinAddress()
	minerPayouts := []SiacoinOutput{
		SiacoinOutput{
			Value:     CalculateCoinbase(s.height() + 1),
			SpendHash: coinAddress,
		},
	}

	// Mine the block that creates the output.
38
	b, err := mineTestingBlock(s.CurrentBlock().ID(), currentTime(), minerPayouts, nil, s.CurrentTarget())
39 40 41 42 43 44 45 46 47 48 49 50 51
	if err != nil {
		t.Fatal(err)
	}
	err = s.AcceptBlock(b)
	if err != nil {
		t.Fatal(err)
	}

	// Create the transaction that spends the output.
	input := SiacoinInput{
		OutputID:        b.MinerPayoutID(0),
		SpendConditions: spendConditions,
	}
52 53 54 55 56
	outputValue := CalculateCoinbase(s.height())
	err = outputValue.Sub(NewCurrency64(12e3))
	if err != nil {
		t.Fatal(err)
	}
57
	output := SiacoinOutput{
58
		Value:     outputValue,
59 60
		SpendHash: ZeroAddress,
	}
David Vorick's avatar
David Vorick committed
61 62
	successAddress := CoinAddress{1}
	failAddress := CoinAddress{2}
63
	contract := FileContract{
64
		FileSize:           4e3,
David Vorick's avatar
David Vorick committed
65 66
		Start:              s.height() + delay,
		End:                s.height() + delay + duration,
67
		Payout:             NewCurrency64(12e3),
David Vorick's avatar
David Vorick committed
68 69
		ValidProofAddress:  successAddress,
		MissedProofAddress: failAddress,
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
	}
	txn = Transaction{
		SiacoinInputs:  []SiacoinInput{input},
		SiacoinOutputs: []SiacoinOutput{output},
		FileContracts:  []FileContract{contract},
	}

	// Sign the transaction.
	sig := TransactionSignature{
		InputID:        input.OutputID,
		CoveredFields:  CoveredFields{WholeTransaction: true},
		PublicKeyIndex: 0,
	}
	txn.Signatures = append(txn.Signatures, sig)
	sigHash := txn.SigHash(0)
85
	rawSig, err := crypto.SignHash(sigHash, sk)
86 87 88
	if err != nil {
		t.Fatal(err)
	}
89
	txn.Signatures[0].Signature = encoding.Marshal(rawSig)
90 91 92
	return
}

David Vorick's avatar
David Vorick committed
93 94 95 96 97 98 99 100 101 102 103 104 105
// storageProofTxn funds a contract, puts it in the state, and then returns a
// transaction with a storage proof for the contract.
func storageProofTxn(t *testing.T, s *State) (txn Transaction, cid ContractID) {
	// Create the keys and a siacoin output that adds coins to the keys.
	sk, pk, err := crypto.GenerateSignatureKeys()
	if err != nil {
		t.Fatal(err)
	}
	spendConditions := SpendConditions{
		NumSignatures: 1,
		PublicKeys: []SiaPublicKey{
			SiaPublicKey{
				Algorithm: ED25519Identifier,
106
				Key:       encoding.Marshal(pk),
David Vorick's avatar
David Vorick committed
107 108 109 110 111 112 113 114 115 116 117 118
			},
		},
	}
	coinAddress := spendConditions.CoinAddress()
	minerPayouts := []SiacoinOutput{
		SiacoinOutput{
			Value:     CalculateCoinbase(s.height() + 1),
			SpendHash: coinAddress,
		},
	}

	// Mine the block that creates the output.
119
	b, err := mineTestingBlock(s.CurrentBlock().ID(), currentTime(), minerPayouts, nil, s.CurrentTarget())
David Vorick's avatar
David Vorick committed
120 121 122 123 124 125 126 127 128
	if err != nil {
		t.Fatal(err)
	}
	err = s.AcceptBlock(b)
	if err != nil {
		t.Fatal(err)
	}

	// Create the file that the storage proof happens over.
129
	simpleFile := make([]byte, 4e3)
David Vorick's avatar
David Vorick committed
130 131 132 133 134 135 136
	rand.Read(simpleFile)

	// Create the transaction that spends the output.
	input := SiacoinInput{
		OutputID:        b.MinerPayoutID(0),
		SpendConditions: spendConditions,
	}
137 138 139 140 141
	outputValue := CalculateCoinbase(s.height())
	err = outputValue.Sub(NewCurrency64(12e3))
	if err != nil {
		t.Fatal(err)
	}
David Vorick's avatar
David Vorick committed
142
	output := SiacoinOutput{
143
		Value:     outputValue,
David Vorick's avatar
David Vorick committed
144 145 146 147 148 149 150 151
		SpendHash: ZeroAddress,
	}
	merkleRoot, err := hash.BytesMerkleRoot(simpleFile)
	if err != nil {
		t.Fatal(err)
	}
	contract := FileContract{
		FileMerkleRoot: merkleRoot,
152
		FileSize:       4e3,
David Vorick's avatar
David Vorick committed
153
		Start:          s.height() + 2,
154
		End:            s.height() + 2 + 25e3,
155
		Payout:         NewCurrency64(12e3),
David Vorick's avatar
David Vorick committed
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
	}
	txn = Transaction{
		SiacoinInputs:  []SiacoinInput{input},
		SiacoinOutputs: []SiacoinOutput{output},
		FileContracts:  []FileContract{contract},
	}

	// Sign the transaction.
	sig := TransactionSignature{
		InputID:        input.OutputID,
		CoveredFields:  CoveredFields{WholeTransaction: true},
		PublicKeyIndex: 0,
	}
	txn.Signatures = append(txn.Signatures, sig)
	sigHash := txn.SigHash(0)
171
	rawSig, err := crypto.SignHash(sigHash, sk)
David Vorick's avatar
David Vorick committed
172 173 174
	if err != nil {
		t.Fatal(err)
	}
175
	txn.Signatures[0].Signature = encoding.Marshal(rawSig)
David Vorick's avatar
David Vorick committed
176 177

	// Put the transaction into a block.
178
	b, err = mineTestingBlock(s.CurrentBlock().ID(), currentTime(), nullMinerPayouts(s.Height()+1), []Transaction{txn}, s.CurrentTarget())
David Vorick's avatar
David Vorick committed
179 180 181 182 183 184 185 186 187 188 189 190 191 192
	if err != nil {
		t.Fatal(err)
	}
	err = s.AcceptBlock(b)
	if err != nil {
		t.Error(err)
	}

	// Create the transaction that has the storage proof.
	cid = txn.FileContractID(0)
	segmentIndex, err := s.StorageProofSegment(cid)
	if err != nil {
		t.Fatal(err)
	}
193
	numSegments := hash.CalculateSegments(4e3)
David Vorick's avatar
David Vorick committed
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
	segment, hashes, err := hash.BuildReaderProof(bytes.NewReader(simpleFile), numSegments, segmentIndex)
	if err != nil {
		t.Fatal(err)
	}
	sp := StorageProof{
		ContractID: txn.FileContractID(0),
		Segment:    segment,
		HashSet:    hashes,
	}
	txn = Transaction{
		StorageProofs: []StorageProof{sp},
	}

	return
}

// testContractCreation adds a block with a file contract to the state and
// checks that the contract is accepted.
212
func testContractCreation(t *testing.T, s *State) {
213
	txn := contractTxn(t, s, 2, 25e3)
214
	b, err := mineTestingBlock(s.CurrentBlock().ID(), currentTime(), nullMinerPayouts(s.Height()+1), []Transaction{txn}, s.CurrentTarget())
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
	if err != nil {
		t.Fatal(err)
	}
	err = s.AcceptBlock(b)
	if err != nil {
		t.Error(err)
	}

	// Check that the contract made it into the state.
	_, exists := s.openContracts[txn.FileContractID(0)]
	if !exists {
		t.Error("file contract not found found in state after being created")
	}
}

David Vorick's avatar
David Vorick committed
230 231 232 233 234
// testMissedProof creates a contract but then doesn't submit the storage
// proof.
func testMissedProof(t *testing.T, s *State) {
	// Get the transaction with the contract that will not be fulfilled.
	txn := contractTxn(t, s, 2, 1)
235
	b, err := mineTestingBlock(s.CurrentBlock().ID(), currentTime(), nullMinerPayouts(s.Height()+1), []Transaction{txn}, s.CurrentTarget())
David Vorick's avatar
David Vorick committed
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
	if err != nil {
		t.Fatal(err)
	}
	err = s.AcceptBlock(b)
	if err != nil {
		t.Error(err)
	}

	// Submit 2 blocks, which means the contract will be a missed proof.
	for i := 0; i < 2; i++ {
		b, err = mineValidBlock(s)
		if err != nil {
			t.Fatal(err)
		}
		err = s.AcceptBlock(b)
		if err != nil {
			t.Error(err)
		}
	}

	// Check that the contract was removed, and that the missed proof output
	// was added.
	cid := txn.FileContractID(0)
	_, exists := s.openContracts[cid]
	if exists {
		t.Error("file contract is still in state despite having terminated")
	}
	output, exists := s.unspentOutputs[cid.StorageProofOutputID(false)]
	if !exists {
		t.Error("missed storage proof output is not in state even though the proof was missed")
	}

	// Check that the money went to the right place.
	if output.SpendHash != txn.FileContracts[0].MissedProofAddress {
		t.Error("missed proof output sent to wrong address!")
	}
}

David Vorick's avatar
David Vorick committed
274 275 276 277 278
// testStorageProofSubmit adds a block with a valid storage proof to the state
// and checks that it is accepted, ending the contract supplying an output to
// the person.
func testStorageProofSubmit(t *testing.T, s *State) {
	txn, cid := storageProofTxn(t, s)
David Vorick's avatar
David Vorick committed
279 280 281 282 283 284 285 286

	// Get the contract for a later check. (Contract disappears after the block
	// is accepted).
	contract, exists := s.openContracts[cid]
	if !exists {
		t.Fatal("file contract doesn't exist in state")
	}

287
	b, err := mineTestingBlock(s.CurrentBlock().ID(), currentTime(), nullMinerPayouts(s.Height()+1), []Transaction{txn}, s.CurrentTarget())
David Vorick's avatar
David Vorick committed
288 289 290 291 292 293 294 295 296
	if err != nil {
		t.Fatal(err)
	}
	err = s.AcceptBlock(b)
	if err != nil {
		t.Error(err)
	}

	// Check that the storage proof made it into the state.
David Vorick's avatar
David Vorick committed
297
	_, exists = s.openContracts[cid]
David Vorick's avatar
David Vorick committed
298 299 300
	if exists {
		t.Error("file contract still in state even though a proof for it has been submitted")
	}
David Vorick's avatar
David Vorick committed
301
	output, exists := s.unspentOutputs[cid.StorageProofOutputID(true)]
David Vorick's avatar
David Vorick committed
302
	if !exists {
David Vorick's avatar
David Vorick committed
303 304 305 306 307 308
		t.Fatal("storage proof output not in state after storage proof was submitted")
	}

	// Check that the money went to the right place.
	if output.SpendHash != contract.ValidProofAddress {
		t.Error("money for valid proof was sent to wrong address")
David Vorick's avatar
David Vorick committed
309 310 311
	}
}

312 313 314
// TestContractCreation creates a new state and uses it to call
// testContractCreation.
func TestContractCreation(t *testing.T) {
315
	s := CreateGenesisState(currentTime())
316 317
	testContractCreation(t, s)
}
David Vorick's avatar
David Vorick committed
318

David Vorick's avatar
David Vorick committed
319 320
// TestMissedProof creates a new state and uses it to call testMissedProof.
func TestMissedProof(t *testing.T) {
321
	s := CreateGenesisState(currentTime())
David Vorick's avatar
David Vorick committed
322 323 324
	testMissedProof(t, s)
}

David Vorick's avatar
David Vorick committed
325 326 327
// TestStorageProofSubmit creates a new state and uses it to call
// testStorageProofSubmit.
func TestStorageProofSubmit(t *testing.T) {
328
	s := CreateGenesisState(currentTime())
David Vorick's avatar
David Vorick committed
329 330
	testStorageProofSubmit(t, s)
}