aboutsummaryrefslogtreecommitdiff
path: root/pkg/blockchain/blockchain.go
blob: 7b92b1a46a66f19b07598bc161c02cf2906abf67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
package blockchain

import (
	"Chain/pkg/block"
	"Chain/pkg/blockchain/blockinfodatabase"
	"Chain/pkg/blockchain/chainwriter"
	"Chain/pkg/blockchain/coindatabase"
	"Chain/pkg/utils"
	"math"
)

// BlockChain is the main type of this project.
// Length is the length of the active chain.
// LastBlock is the last block of the active chain.
// LastHash is the hash of the last block of the active chain.
// UnsafeHashes are the hashes of the "unsafe" blocks on the
// active chain. These "unsafe" blocks may be reverted during a
// fork.
// maxHashes is the number of unsafe hashes that the chain keeps track of.
// BlockInfoDB is a pointer to a block info database
// ChainWriter is a pointer to a chain writer.
// CoinDB is a pointer to a coin database.
type BlockChain struct {
	Length       uint32
	LastBlock    *block.Block
	LastHash     string
	UnsafeHashes []string
	maxHashes    int

	BlockInfoDB *blockinfodatabase.BlockInfoDatabase
	ChainWriter *chainwriter.ChainWriter
	CoinDB      *coindatabase.CoinDatabase
}

// New returns a blockchain given a Config.
func New(config *Config) *BlockChain {
	genBlock := GenesisBlock(config)
	hash := genBlock.Hash()
	bc := &BlockChain{
		Length:       1,
		LastBlock:    genBlock,
		LastHash:     hash,
		UnsafeHashes: []string{},
		maxHashes:    6,
		BlockInfoDB:  blockinfodatabase.New(blockinfodatabase.DefaultConfig()),
		ChainWriter:  chainwriter.New(chainwriter.DefaultConfig()),
		CoinDB:       coindatabase.New(coindatabase.DefaultConfig()),
	}
	// have to store the genesis block
	bc.CoinDB.StoreBlock(genBlock.Transactions, true)
	ub := &chainwriter.UndoBlock{}
	br := bc.ChainWriter.StoreBlock(genBlock, ub, 1)
	bc.BlockInfoDB.StoreBlockRecord(hash, br)
	return bc
}

// GenesisBlock creates the genesis Block, using the Config's
// InitialSubsidy and GenesisPublicKey.
func GenesisBlock(config *Config) *block.Block {
	txo := &block.TransactionOutput{
		Amount:        config.InitialSubsidy,
		LockingScript: config.GenesisPublicKey,
	}
	genTx := &block.Transaction{
		Version:  0,
		Inputs:   nil,
		Outputs:  []*block.TransactionOutput{txo},
		LockTime: 0,
	}
	return &block.Block{
		Header: &block.Header{
			Version:          0,
			PreviousHash:     "",
			MerkleRoot:       "",
			DifficultyTarget: "",
			Nonce:            0,
			Timestamp:        0,
		},
		Transactions: []*block.Transaction{genTx},
	}
}

// HandleBlock handles a new Block. At a high level, it:
// (1) Validates and stores the Block.
// (2) Stores the Block and resulting Undoblock to Disk.
// (3) Stores the BlockRecord in the BlockInfoDatabase.
// (4) Handles a fork, if necessary.
// (5) Updates the BlockChain's fields.
func (bc *BlockChain) HandleBlock(b *block.Block) {
	appends := bc.appendsToActiveChain(b)
	blockHash := b.Hash()

	// 1. Validate Block
	if appends && !bc.CoinDB.ValidateBlock(b.Transactions) {
		return
	}

	// 2. Make Undo Block
	ub := bc.makeUndoBlock(b.Transactions)

	// 3. Store Block in CoinDatabase
	bc.CoinDB.StoreBlock(b.Transactions, appends)

	// 4. Get BlockRecord for previous Block
	previousBr := bc.BlockInfoDB.GetBlockRecord(b.Header.PreviousHash)

	// 5. Store UndoBlock and Block to Disk
	height := previousBr.Height + 1
	br := bc.ChainWriter.StoreBlock(b, ub, height)

	// 6. Store BlockRecord to BlockInfoDatabase
	bc.BlockInfoDB.StoreBlockRecord(blockHash, br)

	if appends {
		// 7. Handle appending Block
		bc.Length++
		bc.LastBlock = b
		bc.LastHash = blockHash
		if len(bc.UnsafeHashes) >= 6 {
			bc.UnsafeHashes = bc.UnsafeHashes[1:]
		}
		bc.UnsafeHashes = append(bc.UnsafeHashes, blockHash)
	} else if height > bc.Length {
		// 8. Handle fork
		bc.handleFork(b, blockHash, height)
	}
}

// handleFork updates the BlockChain when a fork occurs. First, it
// finds the Blocks the BlockChain must revert. Once found, it uses
// those Blocks to update the CoinDatabase. Lastly, it updates the
// BlockChain's fields to reflect the fork.
func (bc *BlockChain) handleFork(b *block.Block, blockHash string, height uint32) {
	bc.Length = height
	forkedBlocks := bc.getForkedBlocks(blockHash)
	var forkedUnsafeHashes []string
	min := int(math.Min(float64(6), float64(len(forkedBlocks))))
	for i := 0; i < min; i++ {
		forkedHash := forkedBlocks[i].Hash()
		forkedUnsafeHashes = append(forkedUnsafeHashes, forkedHash)
	}
	// likely have to add unsafe hashes
	bc.UnsafeHashes = forkedUnsafeHashes
	blocks, undoBlocks := bc.getBlocksAndUndoBlocks(len(forkedBlocks) - 1)
	bc.CoinDB.UndoCoins(blocks, undoBlocks)
	for _, bl := range forkedBlocks {
		if !bc.CoinDB.ValidateBlock(bl.Transactions) {
			utils.Debug.Printf("Validation failed for forked block {%v}", b.Hash())
		}
		bc.CoinDB.StoreBlock(bl.Transactions, true)
	}
	bc.LastBlock = b
	bc.LastHash = b.Hash()
}

// makeUndoBlock returns an UndoBlock given a slice of Transaction.
func (bc *BlockChain) makeUndoBlock(txs []*block.Transaction) *chainwriter.UndoBlock {
	var transactionHashes []string
	var outputIndexes []uint32
	var amounts []uint32
	var lockingScripts []string
	for _, tx := range txs {
		for _, txi := range tx.Inputs {
			cl := coindatabase.CoinLocator{
				ReferenceTransactionHash: txi.ReferenceTransactionHash,
				OutputIndex:              txi.OutputIndex,
			}
			coin := bc.CoinDB.GetCoin(cl)
			// if the coin is nil it means this isn't even a possible fork
			if coin == nil {
				return &chainwriter.UndoBlock{
					TransactionInputHashes: nil,
					OutputIndexes:          nil,
					Amounts:                nil,
					LockingScripts:         nil,
				}
			}
			transactionHashes = append(transactionHashes, txi.ReferenceTransactionHash)
			outputIndexes = append(outputIndexes, txi.OutputIndex)
			amounts = append(amounts, coin.TransactionOutput.Amount)
			lockingScripts = append(lockingScripts, coin.TransactionOutput.LockingScript)
		}
	}
	return &chainwriter.UndoBlock{
		TransactionInputHashes: transactionHashes,
		OutputIndexes:          outputIndexes,
		Amounts:                amounts,
		LockingScripts:         lockingScripts,
	}
}

// getBlock uses the ChainWriter to retrieve a Block from Disk
// given that Block's hash
func (bc *BlockChain) getBlock(blockHash string) *block.Block {
	br := bc.BlockInfoDB.GetBlockRecord(blockHash)
	fi := &chainwriter.FileInfo{
		FileName:    br.BlockFile,
		StartOffset: br.BlockStartOffset,
		EndOffset:   br.BlockEndOffset,
	}
	return bc.ChainWriter.ReadBlock(fi)
}

// getUndoBlock uses the ChainWriter to retrieve an UndoBlock
// from Disk given the corresponding Block's hash
func (bc *BlockChain) getUndoBlock(blockHash string) *chainwriter.UndoBlock {
	br := bc.BlockInfoDB.GetBlockRecord(blockHash)
	fi := &chainwriter.FileInfo{
		FileName:    br.UndoFile,
		StartOffset: br.UndoStartOffset,
		EndOffset:   br.UndoEndOffset,
	}
	return bc.ChainWriter.ReadUndoBlock(fi)
}

// GetBlocks retrieves a slice of blocks from the main chain given a
// starting and ending height, inclusive. Given a chain of length 50,
// GetBlocks(10, 20) returns blocks 10 through 20.
func (bc *BlockChain) GetBlocks(start, end uint32) []*block.Block {
	if start >= end || end <= 0 || start <= 0 || end > bc.Length {
		utils.Debug.Printf("cannot get chain blocks with values start: %v end: %v", start, end)
	}

	var blocks []*block.Block
	currentHeight := bc.Length
	nextHash := bc.LastBlock.Hash()

	for currentHeight >= start {
		br := bc.BlockInfoDB.GetBlockRecord(nextHash)
		fi := &chainwriter.FileInfo{
			FileName:    br.BlockFile,
			StartOffset: br.BlockStartOffset,
			EndOffset:   br.BlockEndOffset,
		}
		if currentHeight <= end {
			nextBlock := bc.ChainWriter.ReadBlock(fi)
			blocks = append(blocks, nextBlock)
		}
		nextHash = br.Header.PreviousHash
		currentHeight--
	}
	return reverseBlocks(blocks)
}

// GetHashes retrieves a slice of hashes from the main chain given a
// starting and ending height, inclusive. Given a BlockChain of length
// 50, GetHashes(10, 20) returns the hashes of Blocks 10 through 20.
func (bc *BlockChain) GetHashes(start, end uint32) []string {
	if start >= end || end <= 0 || start <= 0 || end > bc.Length {
		utils.Debug.Printf("cannot get chain blocks with values start: %v end: %v", start, end)
	}

	var hashes []string
	currentHeight := bc.Length
	nextHash := bc.LastBlock.Hash()

	for currentHeight >= start {
		br := bc.BlockInfoDB.GetBlockRecord(nextHash)
		if currentHeight <= end {
			hashes = append(hashes, nextHash)
		}
		nextHash = br.Header.PreviousHash
		currentHeight--
	}
	return reverseHashes(hashes)
}

// appendsToActiveChain returns whether a Block appends to the
// BlockChain's active chain or not.
func (bc *BlockChain) appendsToActiveChain(b *block.Block) bool {
	return bc.LastBlock.Hash() == b.Header.PreviousHash
}

// getForkedBlocks returns a slice of Blocks given a starting hash.
// It returns a maximum of maxHashes Blocks, where maxHashes is the
// BlockChain's maximum number of unsafe hashes.
func (bc *BlockChain) getForkedBlocks(startHash string) []*block.Block {
	unsafeHashes := make(map[string]bool)
	for _, h := range bc.UnsafeHashes {
		unsafeHashes[h] = true
	}
	var forkedBlocks []*block.Block
	nextHash := startHash
	for i := 0; i < len(bc.UnsafeHashes); i++ {
		forkedBlock := bc.getBlock(nextHash)
		forkedBlocks = append(forkedBlocks, forkedBlock)
		if _, ok := unsafeHashes[nextHash]; ok {
			return forkedBlocks
		}
		nextHash = forkedBlock.Header.PreviousHash
	}
	return forkedBlocks
}

// getBlocksAndUndoBlocks returns a slice of n Blocks with a
// corresponding slice of n UndoBlocks.
func (bc *BlockChain) getBlocksAndUndoBlocks(n int) ([]*block.Block, []*chainwriter.UndoBlock) {
	var blocks []*block.Block
	var undoBlocks []*chainwriter.UndoBlock
	nextHash := bc.LastHash
	for i := 0; i < n; i++ {
		b := bc.getBlock(nextHash)
		ub := bc.getUndoBlock(nextHash)
		blocks = append(blocks, b)
		undoBlocks = append(undoBlocks, ub)
		nextHash = b.Header.PreviousHash
	}
	return blocks, undoBlocks
}

// reverseBlocks returns a reversed slice of Blocks.
func reverseBlocks(s []*block.Block) []*block.Block {
	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
	return s
}

// reverseHashes returns a reversed slice of hashes.
func reverseHashes(s []string) []string {
	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
	return s
}