blockchain sandbox: UTXO chain, p2p mining, attacker node
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user