205 lines
3.7 KiB
Go
205 lines
3.7 KiB
Go
package chain
|
|
|
|
import (
|
|
"blockchain/config"
|
|
"bytes"
|
|
"sync"
|
|
)
|
|
|
|
// Snapshot is a read-only view of the chain state for API queries.
|
|
|
|
type Blockchain struct {
|
|
blocks []*Block
|
|
coins CoinStore
|
|
mx sync.RWMutex
|
|
}
|
|
|
|
func NewBlockchain() *Blockchain {
|
|
genesis := NewBlock(0, nil, "")
|
|
genesis.Hash = genesis.CalcHash()
|
|
return &Blockchain{
|
|
blocks: []*Block{genesis},
|
|
coins: make(CoinStore),
|
|
}
|
|
}
|
|
|
|
type Snapshot struct {
|
|
Blocks []*Block
|
|
Coins CoinStore
|
|
}
|
|
|
|
// Snapshot returns a consistent read-only view of the chain state.
|
|
func (cs *Blockchain) Snapshot() Snapshot {
|
|
cs.mx.RLock()
|
|
defer cs.mx.RUnlock()
|
|
|
|
blocks := make([]*Block, len(cs.blocks))
|
|
copy(blocks, cs.blocks)
|
|
|
|
return Snapshot{
|
|
Blocks: blocks,
|
|
Coins: cs.coins,
|
|
}
|
|
}
|
|
|
|
func (cs *Blockchain) LastBlock() *Block {
|
|
cs.mx.RLock()
|
|
defer cs.mx.RUnlock()
|
|
|
|
return cs.blocks[len(cs.blocks)-1]
|
|
}
|
|
|
|
func (cs *Blockchain) AddBlock(b *Block) bool {
|
|
cs.mx.Lock()
|
|
defer cs.mx.Unlock()
|
|
|
|
return cs.addBlock(b)
|
|
}
|
|
|
|
// addBlock validates and appends a block. Caller must hold write lock.
|
|
func (cs *Blockchain) addBlock(b *Block) bool {
|
|
last := cs.blocks[len(cs.blocks)-1]
|
|
if b.PrevHash != last.Hash || b.Height != last.Height+1 {
|
|
return false
|
|
}
|
|
if !b.CheckProofOfWork() {
|
|
return false
|
|
}
|
|
if !cs.validateTransactions(b.Transactions) {
|
|
return false
|
|
}
|
|
cs.applyBlock(b)
|
|
cs.blocks = append(cs.blocks, b)
|
|
return true
|
|
}
|
|
|
|
func (cs *Blockchain) validateTransactions(txs []*Transaction) bool {
|
|
working := cs.coins.Clone()
|
|
coinbaseCount := 0
|
|
|
|
for _, tx := range txs {
|
|
if tx.IsCoinbase() {
|
|
coinbaseCount++
|
|
if coinbaseCount > 1 {
|
|
return false
|
|
}
|
|
if len(tx.Coins) != 1 || tx.Coins[0].Amount != config.Default.Chain.MiningReward {
|
|
return false
|
|
}
|
|
continue
|
|
}
|
|
|
|
if !cs.validateTx(tx, working) {
|
|
return false
|
|
}
|
|
|
|
for _, s := range tx.Spends {
|
|
working.Spend(s.CoinID)
|
|
}
|
|
txID := tx.TxID()
|
|
for i, c := range tx.Coins {
|
|
working.Add(txID, i, c)
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (cs *Blockchain) validateTx(tx *Transaction, coins CoinStore) bool {
|
|
if !tx.Verify() {
|
|
return false
|
|
}
|
|
|
|
inputSum := 0
|
|
for _, s := range tx.Spends {
|
|
coin := coins.Get(s.CoinID)
|
|
if coin == nil {
|
|
return false
|
|
}
|
|
if !bytes.Equal(coin.Owner, s.Owner) {
|
|
return false
|
|
}
|
|
inputSum += coin.Amount
|
|
}
|
|
|
|
outputSum := 0
|
|
for _, c := range tx.Coins {
|
|
if c.Amount <= 0 {
|
|
return false
|
|
}
|
|
outputSum += c.Amount
|
|
}
|
|
|
|
return outputSum <= inputSum
|
|
}
|
|
|
|
func (cs *Blockchain) applyBlock(b *Block) {
|
|
for _, tx := range b.Transactions {
|
|
for _, s := range tx.Spends {
|
|
cs.coins.Spend(s.CoinID)
|
|
}
|
|
txID := tx.TxID()
|
|
for i, c := range tx.Coins {
|
|
cs.coins.Add(txID, i, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (cs *Blockchain) rebuildCoins() {
|
|
cs.coins = make(CoinStore)
|
|
for _, b := range cs.blocks {
|
|
cs.applyBlock(b)
|
|
}
|
|
}
|
|
|
|
func (cs *Blockchain) ReplaceBlocks(blocks []*Block) bool {
|
|
cs.mx.Lock()
|
|
defer cs.mx.Unlock()
|
|
|
|
if len(blocks) == 0 {
|
|
return false
|
|
}
|
|
|
|
first := blocks[0]
|
|
attachIdx := -1
|
|
for i, b := range cs.blocks {
|
|
if b.Hash == first.PrevHash {
|
|
attachIdx = i
|
|
break
|
|
}
|
|
}
|
|
if attachIdx == -1 {
|
|
return false
|
|
}
|
|
if attachIdx+1+len(blocks) <= len(cs.blocks) {
|
|
return false
|
|
}
|
|
|
|
oldBlocks := make([]*Block, len(cs.blocks))
|
|
copy(oldBlocks, cs.blocks)
|
|
oldCoins := cs.coins
|
|
|
|
cs.blocks = cs.blocks[:attachIdx+1]
|
|
cs.rebuildCoins()
|
|
|
|
for _, b := range blocks {
|
|
if !cs.addBlock(b) {
|
|
cs.blocks = oldBlocks
|
|
cs.coins = oldCoins
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// BlocksAfterHeight returns all blocks after the given height.
|
|
func (cs *Blockchain) BlocksAfterHeight(height int) []*Block {
|
|
cs.mx.RLock()
|
|
defer cs.mx.RUnlock()
|
|
if height+1 >= len(cs.blocks) {
|
|
return nil
|
|
}
|
|
result := make([]*Block, len(cs.blocks)-height-1)
|
|
copy(result, cs.blocks[height+1:])
|
|
return result
|
|
}
|