package core
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/arnaucube/slowlorisdb/db"
|
|
log "github.com/sirupsen/logrus"
|
|
lvldberrors "github.com/syndtr/goleveldb/leveldb/errors"
|
|
)
|
|
|
|
type PoA struct {
|
|
AuthMiners []*ecdsa.PublicKey
|
|
}
|
|
|
|
type Blockchain struct {
|
|
Id []byte // Id allows to have multiple blockchains
|
|
Difficulty uint64
|
|
Genesis Hash
|
|
LastBlock *Block
|
|
blockdb *db.Db
|
|
txdb *db.Db
|
|
addressdb *db.Db
|
|
walletsdb *db.Db
|
|
PoA PoA
|
|
}
|
|
|
|
func NewBlockchain(database *db.Db, dif uint64) *Blockchain {
|
|
blockchain := &Blockchain{
|
|
Id: []byte{},
|
|
Difficulty: dif,
|
|
Genesis: HashBytes([]byte("genesis")),
|
|
LastBlock: &Block{},
|
|
blockdb: database,
|
|
PoA: PoA{},
|
|
}
|
|
return blockchain
|
|
}
|
|
|
|
func NewPoABlockchain(database *db.Db, authNodes []*ecdsa.PublicKey) *Blockchain {
|
|
blockDb := database.WithPrefix([]byte("blockDb"))
|
|
txDb := database.WithPrefix([]byte("txDb"))
|
|
addressDb := database.WithPrefix([]byte("addressDb"))
|
|
|
|
poa := PoA{
|
|
AuthMiners: authNodes,
|
|
}
|
|
blockchain := &Blockchain{
|
|
Id: []byte{},
|
|
Difficulty: uint64(0),
|
|
Genesis: HashBytes([]byte("genesis")), // tmp
|
|
LastBlock: &Block{},
|
|
blockdb: blockDb,
|
|
txdb: txDb,
|
|
addressdb: addressDb,
|
|
PoA: poa,
|
|
}
|
|
return blockchain
|
|
}
|
|
|
|
func (bc *Blockchain) GetHeight() uint64 {
|
|
return bc.LastBlock.Height
|
|
}
|
|
|
|
func (bc *Blockchain) GetLastBlock() *Block {
|
|
return bc.LastBlock
|
|
}
|
|
|
|
func (bc *Blockchain) AddBlock(block *Block) error {
|
|
if !bc.VerifyBlock(block) {
|
|
return errors.New("Block could not be verified")
|
|
}
|
|
bc.LastBlock = block.Copy()
|
|
err := bc.blockdb.Put(block.Hash[:], block.Bytes())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// add each tx to txDb & update addressDb balances
|
|
for _, tx := range block.Txs {
|
|
err = bc.txdb.Put(tx.TxId[:], tx.Bytes())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bc.UpdateWalletsWithNewTx(&tx)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (bc *Blockchain) UpdateWalletsWithNewTx(tx *Tx) error {
|
|
for _, in := range tx.Inputs {
|
|
balanceBytes, err := bc.addressdb.Get(PackPubK(tx.From))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
balance := Uint64FromBytes(balanceBytes)
|
|
balance = balance - in.Value
|
|
err = bc.addressdb.Put(PackPubK(tx.From), Uint64ToBytes(balance))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Info("sent-->: balance of " + hex.EncodeToString(PackPubK(tx.From)[:10]) + ": " + strconv.Itoa(int(balance)))
|
|
}
|
|
for _, out := range tx.Outputs {
|
|
balanceBytes, err := bc.addressdb.Get(PackPubK(tx.To))
|
|
if err != nil && err != lvldberrors.ErrNotFound {
|
|
return err
|
|
}
|
|
if err == lvldberrors.ErrNotFound {
|
|
balanceBytes = Uint64ToBytes(uint64(0))
|
|
}
|
|
balance := Uint64FromBytes(balanceBytes)
|
|
balance = balance + out.Value
|
|
err = bc.addressdb.Put(PackPubK(tx.To), Uint64ToBytes(balance))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Info("--> received: balance of " + hex.EncodeToString(PackPubK(tx.To)[:10]) + ": " + strconv.Itoa(int(balance)))
|
|
}
|
|
return nil
|
|
|
|
}
|
|
|
|
func (bc *Blockchain) GetBlock(hash Hash) (*Block, error) {
|
|
v, err := bc.blockdb.Get(hash[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
block, err := BlockFromBytes(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return block, nil
|
|
}
|
|
|
|
func (bc *Blockchain) GetBalance(pubK *ecdsa.PublicKey) (uint64, error) {
|
|
balanceBytes, err := bc.addressdb.Get(PackPubK(pubK))
|
|
if err != nil {
|
|
return uint64(0), err
|
|
}
|
|
balance := Uint64FromBytes(balanceBytes)
|
|
return balance, 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
|
|
}
|
|
|
|
func (bc *Blockchain) verifyBlockSignature(block *Block) bool {
|
|
// check if the signer is one of the blockchain.AuthMiners
|
|
signerIsMiner := false
|
|
for _, pubK := range bc.PoA.AuthMiners {
|
|
if bytes.Equal(PackPubK(pubK), PackPubK(block.MinerPubK)) {
|
|
signerIsMiner = true
|
|
}
|
|
}
|
|
if !signerIsMiner && len(bc.PoA.AuthMiners) > 0 {
|
|
log.Error("signer is not miner")
|
|
return false
|
|
}
|
|
|
|
// get the signature
|
|
sig, err := SignatureFromBytes(block.Signature)
|
|
if err != nil {
|
|
log.Error("error parsing signature")
|
|
return false
|
|
}
|
|
|
|
// check if the signature is by the miner
|
|
return VerifySignature(block.MinerPubK, block.Hash[:], *sig)
|
|
}
|
|
|
|
func (bc *Blockchain) VerifyBlock(block *Block) bool {
|
|
// verify block signature
|
|
// for the moment just covered the case of PoA blockchain
|
|
if !bc.verifyBlockSignature(block) {
|
|
log.Error("signature verification error")
|
|
return false
|
|
}
|
|
|
|
// verify timestamp
|
|
if block.Timestamp.Unix() < bc.LastBlock.Timestamp.Unix() {
|
|
return false
|
|
}
|
|
|
|
// verify prev hash
|
|
// check that the block.PrevHash is the blockchain current last block
|
|
if !bytes.Equal(block.PrevHash[:], bc.LastBlock.Hash[:]) {
|
|
fmt.Println(block.PrevHash.String())
|
|
fmt.Println(bc.LastBlock.Hash.String())
|
|
log.Error("block.PrevHash not equal to last block hash")
|
|
return false
|
|
}
|
|
|
|
// verify block height
|
|
// check that the block height is the last block + 1
|
|
if block.Height != bc.LastBlock.Height+1 {
|
|
log.Error("block.Height error")
|
|
return false
|
|
}
|
|
|
|
// verify block transactions (not if the block is the genesis block)
|
|
if !bytes.Equal(block.Txs[0].TxId[:], GenesisHashTxInput[:]) {
|
|
for _, tx := range block.Txs {
|
|
txVerified := CheckTx(&tx)
|
|
if !txVerified {
|
|
log.Error("tx could not be verified")
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO in --> out0
|
|
// -> out1
|
|
// -> ...
|
|
|
|
return true
|
|
}
|
|
|
|
// func (bc *Blockchain) Mint(toAddr Address, amount uint64) error {
|
|
// fromAddr := Address(HashBytes([]byte("mint")))
|
|
// out :=
|
|
// tx := NewTx(fromAddr, toAddr, []Input, )
|
|
// }
|