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