diff --git a/cmd/root.go b/cmd/root.go index e7d873d..e5444d2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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" ) @@ -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()) } } diff --git a/config/config.go b/config/config.go index bdc6280..699f78d 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,7 @@ package config import ( + "crypto/ecdsa" "math/big" "os" ) @@ -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 { diff --git a/consensus/pow/pow.go b/consensus/pow/pow.go index 141b41a..3edbc34 100644 --- a/consensus/pow/pow.go +++ b/consensus/pow/pow.go @@ -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, } } @@ -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() @@ -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 } diff --git a/core/blockchain.go b/core/blockchain.go index 98629f1..7936f0d 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1,6 +1,7 @@ package core import ( + "fmt" "math/big" "sync" @@ -8,6 +9,7 @@ import ( "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" @@ -15,14 +17,16 @@ import ( ) 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. @@ -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() @@ -70,40 +74,48 @@ 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) @@ -111,9 +123,9 @@ func (bc *Blockchain) AddBlock(data []byte) { 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) @@ -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 } @@ -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 { diff --git a/core/blockchain_test.go b/core/blockchain_test.go new file mode 100644 index 0000000..c3d399d --- /dev/null +++ b/core/blockchain_test.go @@ -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), + } +} diff --git a/dbstore/db.go b/dbstore/db.go index 03e1583..5a6f45e 100644 --- a/dbstore/db.go +++ b/dbstore/db.go @@ -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. diff --git a/executer/tx_processor.go b/executer/tx_processor.go new file mode 100644 index 0000000..2bc6b6a --- /dev/null +++ b/executer/tx_processor.go @@ -0,0 +1,137 @@ +package executer + +import ( + "errors" + "math/big" + + "github.com/0xsharma/compact-chain/dbstore" + "github.com/0xsharma/compact-chain/types" + "github.com/0xsharma/compact-chain/util" +) + +var ( + ErrInvalidTransaction = errors.New("invalid transaction") +) + +type TxProcessor struct { + MinFee *big.Int + State *dbstore.DB + Signer *util.Address +} + +func NewTxProcessor(state *dbstore.DB, minFee *big.Int, signer *util.Address) *TxProcessor { + return &TxProcessor{ + MinFee: minFee, + State: state, + Signer: signer, + } +} + +func (txp *TxProcessor) IsValid(tx *types.Transaction) bool { + from := tx.From + balance, err := txp.State.Get(dbstore.PrefixKey(dbstore.BalanceKey, from.String())) + + if err != nil { + return false + } + + balanceBig := new(big.Int).SetBytes(balance) + + // Add Fee to Value + totalValue := big.NewInt(0).Add(tx.Value, tx.Fee) + + if balanceBig.Cmp(totalValue) < 0 { + return false + } + + var nonceBig *big.Int + + nonce, err := txp.State.Get(dbstore.PrefixKey(dbstore.NonceKey, from.String())) + if err != nil { + nonceBig = big.NewInt(-1) + } else { + nonceBig = new(big.Int).SetBytes(nonce) + } + + if big.NewInt(0).Sub(tx.Nonce, nonceBig).Cmp(big.NewInt(1)) != 0 { + return false + } + + return true +} + +// ProcessTx processes a transaction and returns the transaction fee. +func (txp *TxProcessor) ProcessTx(tx *types.Transaction) error { + if !txp.IsValid(tx) { + return ErrInvalidTransaction + } + + from := tx.From + to := tx.To + value := tx.Value + + dbBatch := txp.State.NewBatch() + + // Get sender balance. + senderBalance, err := txp.State.Get(dbstore.PrefixKey(dbstore.BalanceKey, from.String())) + if err != nil { + return err + } + + sendBalanceBig := new(big.Int).SetBytes(senderBalance) + + // Get receiver balance. + var receiverBalanceBig *big.Int + + receiverBalance, err := txp.State.Get(dbstore.PrefixKey(dbstore.BalanceKey, to.String())) + if err != nil { + receiverBalanceBig = big.NewInt(0) + } else { + receiverBalanceBig = new(big.Int).SetBytes(receiverBalance) + + } + + // Update sender balance. + sendBalanceBig.Sub(sendBalanceBig, value) + dbBatch.Put([]byte(dbstore.PrefixKey(dbstore.BalanceKey, from.String())), sendBalanceBig.Bytes()) + + // Update receiver balance. + receiverBalanceBig.Add(receiverBalanceBig, value) + dbBatch.Put([]byte(dbstore.PrefixKey(dbstore.BalanceKey, to.String())), receiverBalanceBig.Bytes()) + + // Update sender nonce. + var nonceBig *big.Int + + nonce, err := txp.State.Get(dbstore.PrefixKey(dbstore.NonceKey, from.String())) + if err != nil { + nonceBig = big.NewInt(-1) + } else { + nonceBig = new(big.Int).SetBytes(nonce) + } + + nonceBig.Add(nonceBig, big.NewInt(1)) + dbBatch.Put([]byte(dbstore.PrefixKey(dbstore.NonceKey, from.String())), nonceBig.Bytes()) + + // Get Miner balance. + var minerBalanceBig *big.Int + + minerBalance, err := txp.State.Get(dbstore.PrefixKey(dbstore.BalanceKey, txp.Signer.String())) + if err != nil { + minerBalanceBig = big.NewInt(0) + } else { + minerBalanceBig = new(big.Int).SetBytes(minerBalance) + + } + + // Update Miner Fee. + minerBalanceBig.Add(minerBalanceBig, tx.Fee) + dbBatch.Put([]byte(dbstore.PrefixKey(dbstore.BalanceKey, txp.Signer.String())), minerBalanceBig.Bytes()) + + // Commit batch to db + err = txp.State.WriteBatch(dbBatch) + if err != nil { + panic(err) + } + + return nil +} diff --git a/go.mod b/go.mod index 0818769..cab6b6b 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/0xsharma/compact-chain go 1.19 require ( + github.com/cbergoon/merkletree v0.2.0 github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.2 github.com/syndtr/goleveldb v1.0.0 diff --git a/go.sum b/go.sum index 98fd3d0..3a01140 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/cbergoon/merkletree v0.2.0 h1:Bttqr3OuoiZEo4ed1L7fTasHka9II+BF9fhBfbNEEoQ= +github.com/cbergoon/merkletree v0.2.0/go.mod h1:5c15eckUgiucMGDOCanvalj/yJnD+KAZj1qyJtRW5aM= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/rpc/rpc_test.go b/rpc/rpc_test.go index 77b9709..27ec99b 100644 --- a/rpc/rpc_test.go +++ b/rpc/rpc_test.go @@ -20,13 +20,16 @@ func TestTxpoolRPC(t *testing.T) { t.Parallel() rpcPort := ":1711" + pkey := util.HexToPrivateKey("c3fc038a9abc0f483e2e1f8a0b4db676bce3eaebd7d9afc68e1e7e28ca8738a6") + ua := util.NewUnlockedAccount(pkey) - txpool := txpool.NewTxPool(config.DefaultConfig().MinFee) + txpool := txpool.NewTxPool(config.DefaultConfig().MinFee, nil) NewRPCServer(rpcPort, &RPCDomains{TxPool: txpool}) time.Sleep(2 * time.Second) // Send add Transacation request 1 - tx1 := newTransaction(t, []byte{0x01}, []byte{0x02}, "hello", 100, 100) + tx1 := newTransaction(t, []byte{0x01}, []byte{0x02}, "hello", 100, 100, 0) + tx1.Sign(ua) res, err := SendRpcRequest(t, "TxPool.AddTx_RPC", tx1, rpcPort) if err != nil { @@ -38,7 +41,7 @@ func TestTxpoolRPC(t *testing.T) { } // Send add Transacation request 2 - tx2 := newTransaction(t, []byte{0x02}, []byte{0x03}, "hello1", 101, 101) + tx2 := newTransaction(t, []byte{0x02}, []byte{0x03}, "hello1", 101, 101, 1) res, err = SendRpcRequest(t, "TxPool.AddTx_RPC", tx2, rpcPort) if err != nil { @@ -92,14 +95,15 @@ func SendRpcRequest(t *testing.T, method string, params interface{}, addr string return reply, nil } -func newTransaction(t *testing.T, from, to []byte, msg string, fee, value int64) *types.Transaction { +func newTransaction(t *testing.T, from, to []byte, msg string, fee, value int64, nonce int64) *types.Transaction { t.Helper() return &types.Transaction{ - From: *util.NewAddress(from), - To: *util.NewAddress(to), + From: *util.BytesToAddress(from), + To: *util.BytesToAddress(to), Msg: []byte(msg), Fee: big.NewInt(fee), Value: big.NewInt(value), + Nonce: big.NewInt(nonce), } } diff --git a/txpool/txpool.go b/txpool/txpool.go index 0d95453..afa8c06 100644 --- a/txpool/txpool.go +++ b/txpool/txpool.go @@ -1,25 +1,89 @@ package txpool import ( + "errors" + "fmt" "math/big" "sort" + "github.com/0xsharma/compact-chain/dbstore" "github.com/0xsharma/compact-chain/types" ) +var ( + ErrInvalidTransaction = errors.New("invalid transaction") +) + type TxPool struct { + MinFee *big.Int + State *dbstore.DB Transactions []*types.Transaction } -func NewTxPool(minFee *big.Int) *TxPool { - return &TxPool{} +func NewTxPool(minFee *big.Int, db *dbstore.DB) *TxPool { + if db == nil { + fmt.Println("DB is nil, running in mock mode for tests") + } + + return &TxPool{ + MinFee: minFee, + State: db, + } } func intToBool(n int) bool { return n >= 0 } +func (txp *TxPool) IsValid(tx *types.Transaction) bool { + if txp.State == nil { + return true + } + + if tx.Fee.Cmp(txp.MinFee) < 0 { + return false + } + + from := tx.From + + balance, err := txp.State.Get(dbstore.PrefixKey(dbstore.BalanceKey, from.String())) + if err != nil { + return false + } + + balanceBig := new(big.Int).SetBytes(balance) + + // Add Fee to Value + totalValue := big.NewInt(0).Add(tx.Value, tx.Fee) + + // nolint : gosimple + if balanceBig.Cmp(totalValue) < 0 { + return false + } + + // TODO : Write nonce logic in txpool and enable this check + + // var nonceBig *big.Int + + // nonce, err := txp.State.Get(dbstore.PrefixKey(dbstore.NonceKey, from.String())) + // if err != nil { + // nonceBig = big.NewInt(-1) + // } else { + // nonceBig = new(big.Int).SetBytes(nonce) + // } + + // if big.NewInt(0).Sub(tx.Nonce, nonceBig).Cmp(big.NewInt(1)) != 0 { + // return false + // } + + return true +} + func (tp *TxPool) AddTx(tx *types.Transaction) { + if !tp.IsValid(tx) { + return + } + txs := append(tp.Transactions, tx) sort.Slice(txs, func(i, j int) bool { return intToBool(txs[i].Fee.Cmp(txs[j].Fee)) @@ -29,7 +93,15 @@ func (tp *TxPool) AddTx(tx *types.Transaction) { } func (tp *TxPool) AddTxs(txs []*types.Transaction) { - txpoolTxs := append(tp.Transactions, txs...) + validTxs := make([]*types.Transaction, 0, len(txs)) + + for _, tx := range txs { + if tp.IsValid(tx) { + validTxs = append(validTxs, tx) + } + } + + txpoolTxs := append(tp.Transactions, validTxs...) sort.Slice(txpoolTxs, func(i, j int) bool { return intToBool(txpoolTxs[i].Fee.Cmp(txpoolTxs[j].Fee)) }) diff --git a/txpool/txpool_test.go b/txpool/txpool_test.go index ba30e6c..4463cad 100644 --- a/txpool/txpool_test.go +++ b/txpool/txpool_test.go @@ -22,7 +22,7 @@ func NewRandomTx(t *testing.T) *types.Transaction { func TestTxpoolAdd(t *testing.T) { t.Parallel() - txpool := NewTxPool(big.NewInt(0)) + txpool := NewTxPool(big.NewInt(0), nil) for i := 0; i < 100; i++ { txpool.AddTx(NewRandomTx(t)) diff --git a/types/block.go b/types/block.go index 0360cfc..f70a80e 100644 --- a/types/block.go +++ b/types/block.go @@ -6,15 +6,17 @@ import ( "math/big" "github.com/0xsharma/compact-chain/util" + "github.com/cbergoon/merkletree" ) // Block is the basic unit of the blockchain. type Block struct { - Number *big.Int - Hash *util.Hash - ParentHash *util.Hash - Data []byte - Nonce *big.Int + Number *big.Int + ParentHash *util.Hash + ExtraData []byte + Nonce *big.Int + Transactions []*Transaction + TxRoot *util.Hash } // NewBlock creates a new block and sets the hash. @@ -22,29 +24,45 @@ func NewBlock(number *big.Int, parentHash *util.Hash, data []byte) *Block { block := &Block{ Number: number, ParentHash: parentHash, - Data: data, + ExtraData: data, Nonce: big.NewInt(0), } - block.Hash = block.DeriveHash() - return block } // Clone returns a duplicate block from the source block. func (dst *Block) Clone(src *Block) { dst.Number = src.Number - dst.Hash = src.Hash dst.ParentHash = src.ParentHash - dst.Data = src.Data + dst.ExtraData = src.ExtraData dst.Nonce = src.Nonce } // DeriveHash derives the hash of the block. func (b *Block) DeriveHash() *util.Hash { - blockHash := bytes.Join([][]byte{b.Number.Bytes(), b.ParentHash.Bytes(), b.Data, b.Nonce.Bytes()}, []byte{}) + blockHash := bytes.Join([][]byte{b.Number.Bytes(), b.ParentHash.Bytes(), b.ExtraData, b.Nonce.Bytes(), b.TxRootHash().Bytes()}, []byte{}) + + return util.HashData(blockHash) +} - return util.NewHash(blockHash) +func (b *Block) TxRootHash() *util.Hash { + if len(b.Transactions) == 0 { + return util.HashData([]byte{}) + } else { + var list []merkletree.Content + for _, tx := range b.Transactions { + list = append(list, tx) + } + + //Create a new Merkle Tree from the list of Content + t, err := merkletree.NewTree(list) + if err != nil { + panic(err) + } + mr := t.MerkleRoot() + return util.ByteToHash(mr) + } } // SetNonce sets the nonce of the block. @@ -52,11 +70,6 @@ func (b *Block) SetNonce(n *big.Int) { b.Nonce = n } -// SetHash sets the hash of the block. -func (b *Block) SetHash(h *util.Hash) { - b.Hash = h -} - // Serialize serializes the block object into bytes. func (b *Block) Serialize() []byte { var res bytes.Buffer diff --git a/types/transaction.go b/types/transaction.go index 3062a8e..591f3b2 100644 --- a/types/transaction.go +++ b/types/transaction.go @@ -2,9 +2,11 @@ package types import ( "bytes" + "encoding/gob" "math/big" "github.com/0xsharma/compact-chain/util" + "github.com/cbergoon/merkletree" ) type Transactions []*Transaction @@ -19,10 +21,69 @@ type Transaction struct { Value *big.Int Msg []byte Fee *big.Int + Nonce *big.Int + R *big.Int + S *big.Int } func (tx *Transaction) Hash() *util.Hash { - txHash := bytes.Join([][]byte{tx.From.Bytes(), tx.To.Bytes(), tx.Value.Bytes(), tx.Msg}, []byte{}) + txHash := bytes.Join([][]byte{tx.From.Bytes(), tx.To.Bytes(), tx.Value.Bytes(), tx.Msg, tx.Fee.Bytes(), tx.Nonce.Bytes()}, []byte{}) - return util.NewHash(txHash) + return util.HashData(txHash) +} + +func (tx *Transaction) CalculateHash() ([]byte, error) { + return tx.Hash().Bytes(), nil +} + +// Equals tests for equality of two Contents +func (tx *Transaction) Equals(other merkletree.Content) (bool, error) { + OtherFrom := other.(*Transaction).From + OtherTo := other.(*Transaction).To + OtherValue := other.(*Transaction).Value.Bytes() + OtherMsg := other.(*Transaction).Msg + OtherFee := other.(*Transaction).Fee.Bytes() + OtherNonce := other.(*Transaction).Nonce.Bytes() + OtherR := other.(*Transaction).R.Bytes() + OtherS := other.(*Transaction).S.Bytes() + + out := tx.From == OtherFrom && tx.To == OtherTo && bytes.Equal(tx.Value.Bytes(), OtherValue) && bytes.Equal(tx.Msg, OtherMsg) && bytes.Equal(tx.Fee.Bytes(), OtherFee) && bytes.Equal(tx.Nonce.Bytes(), OtherNonce) && bytes.Equal(tx.R.Bytes(), OtherR) && bytes.Equal(tx.S.Bytes(), OtherS) + + return out, nil +} + +func (tx *Transaction) Serialize() []byte { + var res bytes.Buffer + encoder := gob.NewEncoder(&res) + + err := encoder.Encode(tx) + + if err != nil { + panic(err) + } + + return res.Bytes() +} + +func DeserializeTransaction(data []byte) *Transaction { + var tx Transaction + + decoder := gob.NewDecoder(bytes.NewReader(data)) + + err := decoder.Decode(&tx) + if err != nil { + panic(err) + } + + return &tx +} + +func (tx *Transaction) Sign(ua *util.UnlockedAccount) { + r, s, err := ua.Sign(tx.Hash().Bytes()) + if err != nil { + panic(err) + } + + tx.R = r + tx.S = s } diff --git a/util/hash.go b/util/hash.go index 6eaf9e3..5f17ade 100644 --- a/util/hash.go +++ b/util/hash.go @@ -47,7 +47,7 @@ func ByteToHash(b []byte) *Hash { } // NewHash creates a new sha256 hash from the given byte array. -func NewHash(b []byte) *Hash { +func HashData(b []byte) *Hash { sum := sha256.Sum256(b) hash := Hash{} @@ -57,11 +57,9 @@ func NewHash(b []byte) *Hash { } // NewAddress creates a new address from the given byte array. -func NewAddress(b []byte) *Address { - sum := sha256.Sum256(b) - +func BytesToAddress(b []byte) *Address { address := Address{} - copy(address[:], sum[:]) + copy(address[:], b[:]) return &address } diff --git a/util/signature.go b/util/signature.go new file mode 100644 index 0000000..0c30586 --- /dev/null +++ b/util/signature.go @@ -0,0 +1,63 @@ +package util + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/hex" + "math/big" +) + +type UnlockedAccount struct { + privateKey *ecdsa.PrivateKey +} + +func NewUnlockedAccount(privateKey *ecdsa.PrivateKey) *UnlockedAccount { + return &UnlockedAccount{ + privateKey: privateKey, + } +} + +func (ua *UnlockedAccount) PublicKey() *ecdsa.PublicKey { + return &ua.privateKey.PublicKey +} + +func PublicKeyToAddress(pubkey *ecdsa.PublicKey) *Address { + data := elliptic.Marshal(pubkey, pubkey.X, pubkey.Y) + + address := Address{} + copy(address[:], data[2:]) + + return &address +} + +func (ua *UnlockedAccount) Address() *Address { + pubkey := ua.privateKey.PublicKey + return PublicKeyToAddress(&pubkey) +} + +func (ua *UnlockedAccount) Sign(data []byte) (*big.Int, *big.Int, error) { + return ecdsa.Sign(rand.Reader, ua.privateKey, data) +} + +func (ua *UnlockedAccount) Verify(data []byte, r *big.Int, s *big.Int) bool { + return ecdsa.Verify(ua.PublicKey(), data, r, s) +} + +func HexToPrivateKey(hexStr string) *ecdsa.PrivateKey { + bytes, err := hex.DecodeString(hexStr) + if err != nil { + panic(err) + } + + k := new(big.Int) + k.SetBytes(bytes) + + priv := new(ecdsa.PrivateKey) + curve := elliptic.P256() + priv.PublicKey.Curve = curve + priv.D = k + priv.PublicKey.X, priv.PublicKey.Y = curve.ScalarBaseMult(k.Bytes()) + + return priv +} diff --git a/util/signature_test.go b/util/signature_test.go new file mode 100644 index 0000000..e043d3c --- /dev/null +++ b/util/signature_test.go @@ -0,0 +1,26 @@ +package util + +import ( + "testing" +) + +func TestSigning(t *testing.T) { + t.Parallel() + + // Address = 0xa52c981eee8687b5e4afd69aa5006548c24d7685 + pkey := HexToPrivateKey("c3fc038a9abc0f483e2e1f8a0b4db676bce3eaebd7d9afc68e1e7e28ca8738a6") + ua := NewUnlockedAccount(pkey) + + data := []byte("data") + + r, s, err := ua.Sign(data) + if err != nil { + t.Fatal(err) + } + + verified := ua.Verify(data, r, s) + + if !verified { + t.Fatal("expected true", "got", verified) + } +}