You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

241 lines
5.7 KiB

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, )
// }