Skip to content

Commit

Permalink
Merge pull request #6 from 0xsharma/shivam/add-txExecution
Browse files Browse the repository at this point in the history
init : executer, signing, state
  • Loading branch information
0xsharma authored Jun 24, 2023
2 parents cb7660a + d28aa4c commit 677ad30
Show file tree
Hide file tree
Showing 17 changed files with 615 additions and 68 deletions.
10 changes: 6 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/0xsharma/compact-chain/config"
"github.com/0xsharma/compact-chain/core"
"github.com/0xsharma/compact-chain/types"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -57,20 +58,21 @@ func demoBlockchain() {
StateDBDir: stateDbPath,
MinFee: big.NewInt(100),
RPCPort: ":6999",
BalanceAlloc: map[string]*big.Int{},
}

chain := core.NewBlockchain(config)
if chain.LastBlock.Number.Int64() == 0 {
fmt.Println("Number : ", chain.LastBlock.Number, "Hash : ", chain.LastBlock.Hash.String())
fmt.Println("Number : ", chain.LastBlock.Number, "Hash : ", chain.LastBlock.DeriveHash().String())
} else {
fmt.Println("LastNumber : ", chain.LastBlock.Number, "LastHash : ", chain.LastBlock.Hash.String())
fmt.Println("LastNumber : ", chain.LastBlock.Number, "LastHash : ", chain.LastBlock.DeriveHash().String())
}

lastNumber := chain.LastBlock.Number

for i := lastNumber.Int64() + 1; i <= lastNumber.Int64()+10; i++ {
time.Sleep(2 * time.Second)
chain.AddBlock([]byte(fmt.Sprintf("Block %d", i)))
fmt.Println("Number : ", chain.LastBlock.Number, "Hash : ", chain.LastBlock.Hash.String())
chain.AddBlock([]byte(fmt.Sprintf("Block %d", i)), []*types.Transaction{})
fmt.Println("Number : ", chain.LastBlock.Number, "Hash : ", chain.LastBlock.DeriveHash().String())
}
}
4 changes: 4 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package config

import (
"crypto/ecdsa"
"math/big"
"os"
)
Expand All @@ -19,6 +20,9 @@ type Config struct {
StateDBDir string
MinFee *big.Int
RPCPort string
SignerPrivateKey *ecdsa.PrivateKey
Mine bool
BalanceAlloc map[string]*big.Int
}

func DefaultConfig() *Config {
Expand Down
28 changes: 24 additions & 4 deletions consensus/pow/pow.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
package pow

import (
"fmt"
"math/big"

"github.com/0xsharma/compact-chain/executer"
"github.com/0xsharma/compact-chain/types"
)

type POW struct {
difficulty *big.Int
difficulty *big.Int
TxProcessor *executer.TxProcessor
}

// NewPOW creates a new proof of work consensus.
func NewPOW(difficulty int) *POW {
func NewPOW(difficulty int, txProcessor *executer.TxProcessor) *POW {
return &POW{
difficulty: big.NewInt(int64(difficulty)),
difficulty: big.NewInt(int64(difficulty)),
TxProcessor: txProcessor,
}
}

Expand All @@ -37,6 +41,23 @@ func (c *POW) GetTarget() *big.Int {
func (c *POW) Mine(b *types.Block) *types.Block {
nonce := big.NewInt(0)

validTxs := []*types.Transaction{}

for _, tx := range b.Transactions {
if c.TxProcessor.IsValid(tx) {
err := c.TxProcessor.ProcessTx(tx)
if err == nil {
validTxs = append(validTxs, tx)
} else {
fmt.Println("Failed to execute Tx :", "tx :", tx, "error", err)
}
} else {
fmt.Println("Invalid Tx :", "tx :", tx)
}
}

b.Transactions = validTxs

for {
b.SetNonce(nonce)
hash := b.DeriveHash()
Expand All @@ -45,7 +66,6 @@ func (c *POW) Mine(b *types.Block) *types.Block {
hashBig := new(big.Int).SetBytes(hashBytes)

if hashBig.Cmp(c.GetTarget()) < 0 {
b.SetHash(hash)
break
}

Expand Down
78 changes: 52 additions & 26 deletions core/blockchain.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
package core

import (
"fmt"
"math/big"
"sync"

"github.com/0xsharma/compact-chain/config"
"github.com/0xsharma/compact-chain/consensus"
"github.com/0xsharma/compact-chain/consensus/pow"
"github.com/0xsharma/compact-chain/dbstore"
"github.com/0xsharma/compact-chain/executer"
"github.com/0xsharma/compact-chain/rpc"
"github.com/0xsharma/compact-chain/txpool"
"github.com/0xsharma/compact-chain/types"
"github.com/0xsharma/compact-chain/util"
)

type Blockchain struct {
LastBlock *types.Block
Consensus consensus.Consensus
Mutex *sync.RWMutex
LastHash *util.Hash
Db *dbstore.DB
StateDB *dbstore.DB
Txpool *txpool.TxPool
RPCServer *rpc.RPCServer
LastBlock *types.Block
Consensus consensus.Consensus
Mutex *sync.RWMutex
LastHash *util.Hash
Db *dbstore.DB
StateDB *dbstore.DB
Txpool *txpool.TxPool
RPCServer *rpc.RPCServer
TxProcessor *executer.TxProcessor
Signer *util.Address
}

// defaultConsensusDifficulty is the default difficulty for the proof of work consensus.
Expand All @@ -44,8 +48,8 @@ func NewBlockchain(c *config.Config) *Blockchain {

lastBlockBytes, err := db.Get(dbstore.LastHashKey)
if err != nil {
genesis = CreateGenesisBlock()
lastHash := genesis.Hash
genesis = CreateGenesisBlock(c.BalanceAlloc, stateDB)
lastHash := genesis.DeriveHash()

dbBatch := db.NewBatch()

Expand All @@ -70,50 +74,58 @@ func NewBlockchain(c *config.Config) *Blockchain {
lastBlock = types.DeserializeBlock(lastBlockBytes)
}

var txProcessor *executer.TxProcessor

if c.Mine && c.SignerPrivateKey != nil {
p := c.SignerPrivateKey.PublicKey
txProcessor = executer.NewTxProcessor(stateDB, c.MinFee, util.PublicKeyToAddress(&p))
}

var consensus consensus.Consensus

switch c.ConsensusName {
case "pow":
if c.ConsensusDifficulty > 0 {
consensus = pow.NewPOW(c.ConsensusDifficulty)
consensus = pow.NewPOW(c.ConsensusDifficulty, txProcessor)
} else {
consensus = pow.NewPOW(defaultConsensusDifficulty)
consensus = pow.NewPOW(defaultConsensusDifficulty, txProcessor)
}
default:
panic("Invalid consensus algorithm")
}

bc_txpool := txpool.NewTxPool(c.MinFee)

bc := &Blockchain{LastBlock: lastBlock, Consensus: consensus, Mutex: new(sync.RWMutex), Db: db, LastHash: lastBlock.Hash, StateDB: stateDB, Txpool: bc_txpool}
bc_txpool := txpool.NewTxPool(c.MinFee, stateDB)

rpcDomains := &rpc.RPCDomains{
TxPool: bc.Txpool,
TxPool: bc_txpool,
}
rpcServer := rpc.NewRPCServer(c.RPCPort, rpcDomains)

bc.RPCServer = rpc.NewRPCServer(c.RPCPort, rpcDomains)
bc := &Blockchain{LastBlock: lastBlock, Consensus: consensus, Mutex: new(sync.RWMutex), Db: db, LastHash: lastBlock.DeriveHash(), StateDB: stateDB, Txpool: bc_txpool, TxProcessor: txProcessor, RPCServer: rpcServer}

return bc
}

// AddBlock mines and adds a new block to the blockchain.
func (bc *Blockchain) AddBlock(data []byte) {
func (bc *Blockchain) AddBlock(data []byte, txs []*types.Transaction) {
bc.Mutex.Lock()
defer bc.Mutex.Unlock()

prevBlock := bc.LastBlock
blockNumber := big.NewInt(0).Add(prevBlock.Number, big.NewInt(1))
block := types.NewBlock(blockNumber, prevBlock.Hash, data)
block := types.NewBlock(blockNumber, prevBlock.DeriveHash(), data)

block.Transactions = txs

// Mine block
minedBlock := bc.Consensus.Mine(block)

dbBatch := bc.Db.NewBatch()

// Batch write to db
dbBatch.Put([]byte(dbstore.PrefixKey(dbstore.HashesKey, minedBlock.Hash.String())), minedBlock.Serialize())
dbBatch.Put([]byte(dbstore.PrefixKey(dbstore.BlockNumberKey, minedBlock.Number.String())), minedBlock.Hash.Bytes())
dbBatch.Put([]byte(dbstore.LastHashKey), minedBlock.Hash.Bytes())
dbBatch.Put([]byte(dbstore.PrefixKey(dbstore.HashesKey, minedBlock.DeriveHash().String())), minedBlock.Serialize())
dbBatch.Put([]byte(dbstore.PrefixKey(dbstore.BlockNumberKey, minedBlock.Number.String())), minedBlock.DeriveHash().Bytes())
dbBatch.Put([]byte(dbstore.LastHashKey), minedBlock.DeriveHash().Bytes())

// Commit batch to db
err := bc.Db.WriteBatch(dbBatch)
Expand All @@ -124,9 +136,23 @@ func (bc *Blockchain) AddBlock(data []byte) {
bc.LastBlock = minedBlock
}

// Mine the genesis block
func CreateGenesisBlock() *types.Block {
genesis := types.NewBlock(big.NewInt(0), util.NewHash([]byte("0x0")), []byte("Genesis Block"))
// Mine the genesis block and do initial balance allocation.
func CreateGenesisBlock(balanceAlloc map[string]*big.Int, db *dbstore.DB) *types.Block {
genesis := types.NewBlock(big.NewInt(0), util.HashData([]byte("0x0")), []byte("Genesis Block"))

dbBatch := db.NewBatch()

for address, balance := range balanceAlloc {
fmt.Println("Allocating", balance, "to", address)
dbBatch.Put([]byte(dbstore.PrefixKey(dbstore.BalanceKey, address)), balance.Bytes())
}

// Commit batch to db
err := db.WriteBatch(dbBatch)
if err != nil {
panic(err)
}

return genesis
}

Expand All @@ -145,7 +171,7 @@ func (bc *Blockchain) GetBlockByNumber(b *big.Int) (*types.Block, error) {
return nil, err
}

hash := util.NewHash(hashBytes)
hash := util.HashData(hashBytes)
block, err := bc.GetBlockByHash(hash)

if err != nil {
Expand Down
116 changes: 116 additions & 0 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package core

import (
"fmt"
"math/big"
"testing"
"time"

"github.com/0xsharma/compact-chain/config"
"github.com/0xsharma/compact-chain/dbstore"
"github.com/0xsharma/compact-chain/types"
"github.com/0xsharma/compact-chain/util"
"github.com/stretchr/testify/assert"
)

func TestBlockchainStateBalance(t *testing.T) {
t.Parallel()

to1 := util.BytesToAddress([]byte{0x01})
to2 := util.BytesToAddress([]byte{0x02})

config := &config.Config{
ConsensusDifficulty: 16,
ConsensusName: "pow",
DBDir: t.TempDir(),
StateDBDir: t.TempDir(),
MinFee: big.NewInt(100),
RPCPort: ":6999",
BalanceAlloc: map[string]*big.Int{
"0xa52c981eee8687b5e4afd69aa5006548c24d7685": big.NewInt(1000000000000000000), // Allocating funds to 0xa52c981eee8687b5e4afd69aa5006548c24d7685
},
Mine: true,
SignerPrivateKey: util.HexToPrivateKey("e3ddd0f483e2ef1f8a0b4db676bce3eaebd7d9afc68e1e7e28ca8738a6"), // Address = 0x93a63fc45341fc02ac9cce62cc5aeb5c5799403e
}

chain := NewBlockchain(config)
if chain.LastBlock.Number.Int64() == 0 {
fmt.Println("Number : ", chain.LastBlock.Number, "Hash : ", chain.LastBlock.DeriveHash().String())
} else {
fmt.Println("LastNumber : ", chain.LastBlock.Number, "LastHash : ", chain.LastBlock.DeriveHash().String())
}

pkey := util.HexToPrivateKey("c3fc038a9abc0f483e2e1f8a0b4db676bce3eaebd7d9afc68e1e7e28ca8738a6") // Address = 0xa52c981eee8687b5e4afd69aa5006548c24d7685
ua := util.NewUnlockedAccount(pkey)

// tx1
tx1 := newTransaction(t, ua.Address().Bytes(), to1.Bytes(), "hello", 200, 1000, 0)
tx1.Sign(ua)

// tx2
tx2 := newTransaction(t, ua.Address().Bytes(), to2.Bytes(), "hello", 100, 2000, 1)
tx2.Sign(ua)

// Add tx1 and tx2 to txpool
chain.Txpool.AddTxs([]*types.Transaction{tx1, tx2})

// Add block 1
time.Sleep(2 * time.Second)
chain.AddBlock([]byte(fmt.Sprintf("Block %d", chain.LastBlock.Number.Int64()+1)), []*types.Transaction{})

fmt.Println("Number : ", chain.LastBlock.Number, "Hash : ", chain.LastBlock.DeriveHash().String(), "TxCount", len(chain.LastBlock.Transactions))

//Add block 2
time.Sleep(2 * time.Second)
chain.AddBlock([]byte(fmt.Sprintf("Block %d", chain.LastBlock.Number.Int64()+1)), chain.Txpool.GetTxs())

fmt.Println("Number : ", chain.LastBlock.Number, "Hash : ", chain.LastBlock.DeriveHash().String(), "TxCount", len(chain.LastBlock.Transactions))

// Assertions
balanceSender, err := chain.StateDB.Get(dbstore.PrefixKey(dbstore.BalanceKey, ua.Address().String()))
if err != nil {
t.Fatal(err)
}

balanceSenderBig := new(big.Int).SetBytes(balanceSender)
assert.Equal(t, big.NewInt(999999999999997000), balanceSenderBig)

balanceTo1, err := chain.StateDB.Get(dbstore.PrefixKey(dbstore.BalanceKey, to1.String()))
if err != nil {
t.Fatal(err)
}

balanceTo1Big := new(big.Int).SetBytes(balanceTo1)
assert.Equal(t, big.NewInt(1000), balanceTo1Big)

balanceTo2, err := chain.StateDB.Get(dbstore.PrefixKey(dbstore.BalanceKey, to2.String()))
if err != nil {
t.Fatal(err)
}

balanceTo2Big := new(big.Int).SetBytes(balanceTo2)
assert.Equal(t, big.NewInt(2000), balanceTo2Big)

addressMiner := util.NewUnlockedAccount(config.SignerPrivateKey)

balanceMiner, err := chain.StateDB.Get(dbstore.PrefixKey(dbstore.BalanceKey, addressMiner.Address().String()))
if err != nil {
t.Fatal(err)
}

balanceMinerBig := new(big.Int).SetBytes(balanceMiner)
assert.Equal(t, big.NewInt(300), balanceMinerBig)
}

func newTransaction(t *testing.T, from, to []byte, msg string, fee, value int64, nonce int64) *types.Transaction {
t.Helper()

return &types.Transaction{
From: *util.BytesToAddress(from),
To: *util.BytesToAddress(to),
Msg: []byte(msg),
Fee: big.NewInt(fee),
Value: big.NewInt(value),
Nonce: big.NewInt(nonce),
}
}
2 changes: 2 additions & 0 deletions dbstore/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ const (
LastHashKey = "lh" // Last hash key ( lastHash -> hash)
HashesKey = "hs" // Hashes key (hash->block)
BlockNumberKey = "bn" // Block number key (blockNumber -> hash)
BalanceKey = "bl" // Balance key (address -> balance)
NonceKey = "nc" // Nonce key (address -> nonce)
)

// PrefixKey prefixes a string with another string.
Expand Down
Loading

0 comments on commit 677ad30

Please sign in to comment.