@ -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()) |
|||
} |