@ -0,0 +1,80 @@ |
|||||
|
package core |
||||
|
|
||||
|
import ( |
||||
|
"errors" |
||||
|
"slothdb/db" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
type Blockchain struct { |
||||
|
Id []byte // Id allows to have multiple blockchains
|
||||
|
Genesis Hash |
||||
|
LastBlock *Block |
||||
|
db *db.Db |
||||
|
} |
||||
|
|
||||
|
func NewBlockchain(database *db.Db) *Blockchain { |
||||
|
blockchain := &Blockchain{ |
||||
|
Id: []byte{}, |
||||
|
Genesis: Hash{}, |
||||
|
LastBlock: &Block{}, |
||||
|
db: database, |
||||
|
} |
||||
|
return blockchain |
||||
|
} |
||||
|
|
||||
|
func (bc *Blockchain) NewBlock(txs []Tx) *Block { |
||||
|
block := &Block{ |
||||
|
Height: bc.GetHeight() + 1, |
||||
|
PrevHash: bc.LastBlock.Hash, |
||||
|
Txs: txs, |
||||
|
Miner: Address{}, // TODO put the node address
|
||||
|
Timestamp: time.Now(), |
||||
|
Nonce: 0, |
||||
|
Hash: Hash{}, |
||||
|
Signature: []byte{}, |
||||
|
} |
||||
|
return block |
||||
|
} |
||||
|
|
||||
|
func (bc *Blockchain) GetHeight() uint64 { |
||||
|
return bc.LastBlock.Height |
||||
|
} |
||||
|
|
||||
|
func (bc *Blockchain) GetLastBlock() *Block { |
||||
|
return bc.LastBlock |
||||
|
} |
||||
|
|
||||
|
func (bc *Blockchain) AddBlock(block *Block) error { |
||||
|
bc.LastBlock = block |
||||
|
err := bc.db.Put(block.Hash[:], block.Bytes()) |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
func (bc *Blockchain) GetBlock(hash Hash) (*Block, error) { |
||||
|
v, err := bc.db.Get(hash[:]) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
block, err := BlockFromBytes(v) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
return block, nil |
||||
|
} |
||||
|
|
||||
|
func (bc *Blockchain) GetPrevBlock(hash Hash) (*Block, error) { |
||||
|
currentBlock, err := bc.GetBlock(hash) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
if currentBlock.PrevHash.IsZero() { |
||||
|
return nil, errors.New("This was the oldest block") |
||||
|
} |
||||
|
prevBlock, err := bc.GetBlock(currentBlock.PrevHash) |
||||
|
if err != nil { |
||||
|
return nil, err |
||||
|
} |
||||
|
|
||||
|
return prevBlock, nil |
||||
|
} |
@ -0,0 +1,96 @@ |
|||||
|
package core |
||||
|
|
||||
|
import ( |
||||
|
"io/ioutil" |
||||
|
"slothdb/db" |
||||
|
"testing" |
||||
|
|
||||
|
"github.com/stretchr/testify/assert" |
||||
|
) |
||||
|
|
||||
|
func TestBlockchainDataStructure(t *testing.T) { |
||||
|
dir, err := ioutil.TempDir("", "db") |
||||
|
assert.Nil(t, err) |
||||
|
db, err := db.New(dir) |
||||
|
assert.Nil(t, err) |
||||
|
|
||||
|
bc := NewBlockchain(db) |
||||
|
block := bc.NewBlock([]Tx{}) |
||||
|
|
||||
|
block2, err := BlockFromBytes(block.Bytes()) |
||||
|
assert.Nil(t, err) |
||||
|
assert.Equal(t, block2.Bytes(), block.Bytes()) |
||||
|
} |
||||
|
|
||||
|
func TestGetBlock(t *testing.T) { |
||||
|
dir, err := ioutil.TempDir("", "db") |
||||
|
assert.Nil(t, err) |
||||
|
db, err := db.New(dir) |
||||
|
assert.Nil(t, err) |
||||
|
|
||||
|
bc := NewBlockchain(db) |
||||
|
|
||||
|
block := bc.NewBlock([]Tx{}) |
||||
|
assert.Equal(t, block.Height, uint64(1)) |
||||
|
|
||||
|
err = bc.AddBlock(block) |
||||
|
assert.Nil(t, err) |
||||
|
|
||||
|
block2, err := bc.GetBlock(block.Hash) |
||||
|
assert.Nil(t, err) |
||||
|
assert.Equal(t, block.Bytes(), block2.Bytes()) |
||||
|
} |
||||
|
|
||||
|
func TestGetPrevBlock(t *testing.T) { |
||||
|
dir, err := ioutil.TempDir("", "db") |
||||
|
assert.Nil(t, err) |
||||
|
db, err := db.New(dir) |
||||
|
assert.Nil(t, err) |
||||
|
|
||||
|
difficulty := 1 |
||||
|
bc := NewBlockchain(db) |
||||
|
|
||||
|
for i := 0; i < 10; i++ { |
||||
|
block := bc.NewBlock([]Tx{}) |
||||
|
block.CalculatePoW(difficulty) |
||||
|
assert.Equal(t, block.Height, uint64(i+1)) |
||||
|
|
||||
|
err = bc.AddBlock(block) |
||||
|
assert.Nil(t, err) |
||||
|
assert.Equal(t, bc.LastBlock.Height, block.Height) |
||||
|
} |
||||
|
block9, err := bc.GetPrevBlock(bc.LastBlock.Hash) |
||||
|
assert.Nil(t, err) |
||||
|
assert.Equal(t, block9.Height, uint64(9)) |
||||
|
|
||||
|
block8, err := bc.GetPrevBlock(block9.Hash) |
||||
|
assert.Nil(t, err) |
||||
|
assert.Equal(t, block8.Height, uint64(8)) |
||||
|
|
||||
|
currentBlock := bc.LastBlock |
||||
|
for err == nil { |
||||
|
currentBlock, err = bc.GetPrevBlock(currentBlock.Hash) |
||||
|
} |
||||
|
assert.Equal(t, err.Error(), "This was the oldest block") |
||||
|
} |
||||
|
|
||||
|
func TestAddBlockWithTx(t *testing.T) { |
||||
|
addr0 := Address(HashBytes([]byte("addr0"))) |
||||
|
addr1 := Address(HashBytes([]byte("addr1"))) |
||||
|
|
||||
|
dir, err := ioutil.TempDir("", "db") |
||||
|
assert.Nil(t, err) |
||||
|
db, err := db.New(dir) |
||||
|
assert.Nil(t, err) |
||||
|
|
||||
|
bc := NewBlockchain(db) |
||||
|
|
||||
|
var txs []Tx |
||||
|
tx := NewTx(addr0, addr1, []Input{}, []Output{}) |
||||
|
txs = append(txs, *tx) |
||||
|
block := bc.NewBlock(txs) |
||||
|
|
||||
|
block2, err := BlockFromBytes(block.Bytes()) |
||||
|
assert.Nil(t, err) |
||||
|
assert.Equal(t, block2.Bytes(), block.Bytes()) |
||||
|
} |