blockchain sandbox: UTXO chain, p2p mining, attacker node

This commit is contained in:
2026-07-26 20:37:26 +03:00
commit 7ce39c8964
18 changed files with 2536 additions and 0 deletions
+204
View File
@@ -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
}