From f25db39ada48523e83d1e57eedd6c3b0e71451d8 Mon Sep 17 00:00:00 2001 From: Rongjian Lan Date: Mon, 3 Sep 2018 15:40:17 -0700 Subject: [PATCH 1/5] Refactor client wallet, add more comments --- client/wallet/main.go | 89 ++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/client/wallet/main.go b/client/wallet/main.go index bffb9a350..4ae2a7c67 100644 --- a/client/wallet/main.go +++ b/client/wallet/main.go @@ -28,42 +28,27 @@ import ( func main() { // Account subcommands accountImportCommand := flag.NewFlagSet("import", flag.ExitOnError) - transferCommand := flag.NewFlagSet("transfer", flag.ExitOnError) - //accountListCommand := flag.NewFlagSet("list", flag.ExitOnError) - // - //// Transaction subcommands - //transactionNewCommand := flag.NewFlagSet("new", flag.ExitOnError) - // - //// Account subcommand flag pointers - //// Adding a new choice for --metric of 'substring' and a new --substring flag accountImportPtr := accountImportCommand.String("privateKey", "", "Specify the private key to import") + // Transfer subcommands + transferCommand := flag.NewFlagSet("transfer", flag.ExitOnError) transferSenderPtr := transferCommand.String("sender", "0", "Specify the sender account address or index") transferReceiverPtr := transferCommand.String("receiver", "", "Specify the receiver account") transferAmountPtr := transferCommand.Int("amount", 0, "Specify the amount to transfer") - //accountListPtr := accountNewCommand.Bool("new", false, "N/A") - // - //// Transaction subcommand flag pointers - //transactionNewPtr := transactionNewCommand.String("text", "", "Text to parse. (Required)") // Verify that a subcommand has been provided // os.Arg[0] is the main command // os.Arg[1] will be the subcommand if len(os.Args) < 2 { - fmt.Println("account or transaction subcommand is required") + fmt.Println("account or transfer subcommand is required") os.Exit(1) } // Switch on the subcommand - // Parse the flags for appropriate FlagSet - // FlagSet.Parse() requires a set of arguments to parse as input - // os.Args[2:] will be all arguments starting after the subcommand at os.Args[1] switch os.Args[1] { case "account": switch os.Args[2] { case "new": - fmt.Println("Creating new account...") - randomBytes := [32]byte{} _, err := io.ReadFull(rand.Reader, randomBytes[:]) @@ -74,7 +59,7 @@ func main() { priKey := crypto.Ed25519Curve.Scalar().SetBytes(randomBytes[:]) priKeyBytes, err := priKey.MarshalBinary() if err != nil { - panic("Failed to generate private key") + panic("Failed to serialize the private key") } pubKey := pki.GetPublicKeyFromScalar(priKey) address := pki.GetAddressFromPublicKey(pubKey) @@ -82,13 +67,12 @@ func main() { fmt.Printf("New account created:\nAddress: {%x}\n", address) case "list": for i, address := range ReadAddresses() { - fmt.Printf("Account %d:\n {%x}\n", i+1, address) + fmt.Printf("Account %d:\n {%x}\n", i+1, address) } case "clearAll": - fmt.Println("Deleting existing accounts...") - DeletePrivateKey() + ClearKeystore() + fmt.Println("All existing accounts deleted...") case "import": - fmt.Println("Importing private key...") accountImportCommand.Parse(os.Args[3:]) priKey := *accountImportPtr if !accountImportCommand.Parsed() { @@ -99,15 +83,10 @@ func main() { panic("Failed to parse the private key into bytes") } StorePrivateKey(priKeyBytes) + fmt.Println("Private key imported...") case "showBalance": - configr := client_config.NewConfig() - configr.ReadConfigFile("local_config_shards.txt") - leaders, _ := configr.GetLeadersAndShardIds() - clientPeer := configr.GetClientPeer() - walletNode := node.New(nil, nil) - walletNode.Client = client.NewClient(&leaders) - walletNode.ClientPeer = clientPeer - go walletNode.StartServer(clientPeer.Port) + walletNode := CreateWalletServerNode() + go walletNode.StartServer(walletNode.ClientPeer.Port) shardUtxoMap, err := FetchUtxos(ReadAddresses(), walletNode) if err != nil { @@ -115,6 +94,7 @@ func main() { } PrintUtxoBalance(shardUtxoMap) case "test": + // Testing code priKey := pki.GetPrivateKeyScalarFromInt(444) address := pki.GetAddressFromPrivateKey(priKey) priKeyBytes, err := priKey.MarshalBinary() @@ -138,7 +118,7 @@ func main() { } priKeys := ReadPrivateKeys() if len(priKeys) == 0 { - fmt.Println("No existing account to send money from.") + fmt.Println("No imported account to use.") return } senderIndex, err := strconv.Atoi(sender) @@ -153,7 +133,7 @@ func main() { } } if senderIndex == -1 { - fmt.Println("Specified sender account is not imported yet.") + fmt.Println("The specified sender account is not imported yet.") break } } @@ -163,7 +143,7 @@ func main() { } receiverAddress, err := hex.DecodeString(receiver) if err != nil || len(receiverAddress) != 20 { - fmt.Println("The receiver address is not a valid address.") + fmt.Println("The receiver address is not a valid.") return } @@ -175,14 +155,8 @@ func main() { senderAddressBytes := pki.GetAddressFromPrivateKey(senderPriKey) // Start client server - configr := client_config.NewConfig() - configr.ReadConfigFile("local_config_shards.txt") - leaders, _ := configr.GetLeadersAndShardIds() - clientPeer := configr.GetClientPeer() - walletNode := node.New(nil, nil) - walletNode.Client = client.NewClient(&leaders) - walletNode.ClientPeer = clientPeer - go walletNode.StartServer(clientPeer.Port) + walletNode := CreateWalletServerNode() + go walletNode.StartServer(walletNode.ClientPeer.Port) shardUtxoMap, err := FetchUtxos([][20]byte{senderAddressBytes}, walletNode) if err != nil { @@ -227,21 +201,37 @@ func main() { fmt.Println("Failed to deserialize public key", "error", err) } - ExecuteTransaction(tx, walletNode) + err = ExecuteTransaction(tx, walletNode) + + if err != nil { + fmt.Println(err) + } else { + fmt.Println("Transaction submitted successfully") + } default: flag.PrintDefaults() os.Exit(1) } } +func CreateWalletServerNode() *node.Node { + configr := client_config.NewConfig() + configr.ReadConfigFile("local_config_shards.txt") + leaders, _ := configr.GetLeadersAndShardIds() + clientPeer := configr.GetClientPeer() + walletNode := node.New(nil, nil) + walletNode.Client = client.NewClient(&leaders) + walletNode.ClientPeer = clientPeer + return walletNode +} + +// Issue the transaction to the Harmony network func ExecuteTransaction(tx blockchain.Transaction, walletNode *node.Node) error { if tx.IsCrossShard() { walletNode.Client.PendingCrossTxsMutex.Lock() walletNode.Client.PendingCrossTxs[tx.ID] = &tx walletNode.Client.PendingCrossTxsMutex.Unlock() - } - fmt.Println("Sending transaction...") msg := proto_node.ConstructTransactionListMessage([]*blockchain.Transaction{&tx}) p2p.BroadcastMessage(*walletNode.Client.Leaders, msg) @@ -261,10 +251,11 @@ func ExecuteTransaction(tx blockchain.Transaction, walletNode *node.Node) error time.Sleep(100 * time.Millisecond) return nil case <-time.After(5 * time.Second): - return errors.New("Cross-shard tx timed out") + return errors.New("Cross-shard Transaction processing timed out") } } +// Fetch utxos of specified address from the Harmony network func FetchUtxos(addresses [][20]byte, walletNode *node.Node) (map[uint32]blockchain.UtxoMap, error) { fmt.Println("Fetching account balance...") walletNode.Client.ShardUtxoMap = make(map[uint32]blockchain.UtxoMap) @@ -310,6 +301,7 @@ func PrintUtxoBalance(shardUtxoMap map[uint32]blockchain.UtxoMap) { } } +// Read the addresses stored in local keystore func ReadAddresses() [][20]byte { priKeys := ReadPrivateKeys() addresses := [][20]byte{} @@ -319,6 +311,7 @@ func ReadAddresses() [][20]byte { return addresses } +// Store the specified private key in local keystore func StorePrivateKey(priKey []byte) { for _, address := range ReadAddresses() { if address == pki.GetAddressFromPrivateKey(crypto.Ed25519Curve.Scalar().SetBytes(priKey)) { @@ -339,10 +332,12 @@ func StorePrivateKey(priKey []byte) { f.Close() } -func DeletePrivateKey() { +// Delete all data in the local keystore +func ClearKeystore() { ioutil.WriteFile("keystore", []byte{}, 0644) } +// Read all the private key stored in local keystore func ReadPrivateKeys() []kyber.Scalar { keys, err := ioutil.ReadFile("keystore") if err != nil { From 695e473784cfc8841049e263eac9ea30c203fc2a Mon Sep 17 00:00:00 2001 From: Rongjian Lan Date: Mon, 3 Sep 2018 16:48:49 -0700 Subject: [PATCH 2/5] Create state block from utxo pool --- blockchain/block.go | 18 ++++++++++++++++++ blockchain/utxopool.go | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/blockchain/block.go b/blockchain/block.go index 805631d19..696630db8 100644 --- a/blockchain/block.go +++ b/blockchain/block.go @@ -112,3 +112,21 @@ func NewBlock(transactions []*Transaction, prevBlockHash [32]byte, shardId uint3 func NewGenesisBlock(coinbase *Transaction, shardId uint32) *Block { return NewBlock([]*Transaction{coinbase}, [32]byte{}, shardId) } + +// NewStateBlock creates and returns a state Block based on utxo pool. +func NewStateBlock(utxoPool *UTXOPool) *Block { + stateTransactions := []*Transaction{} + for address, txHash2Vout2AmountMap := range utxoPool.UtxoMap { + stateTransaction := Transaction{} + for _, vout2AmountMap := range txHash2Vout2AmountMap { + for _, amount := range vout2AmountMap { + stateTransaction.TxOutput = append(stateTransaction.TxOutput, TXOutput{Amount: amount, Address: address, ShardID: utxoPool.ShardID}) + } + } + if len(stateTransaction.TxOutput) != 0 { + stateTransaction.SetID() + stateTransactions = append(stateTransactions, &stateTransaction) + } + } + return NewBlock(stateTransactions, [32]byte{}, utxoPool.ShardID) +} diff --git a/blockchain/utxopool.go b/blockchain/utxopool.go index 0b08fbe83..184027362 100644 --- a/blockchain/utxopool.go +++ b/blockchain/utxopool.go @@ -456,3 +456,8 @@ func (utxoPool *UTXOPool) GetSizeInByteOfUtxoMap() int { encoder.Encode(utxoPool.UtxoMap) return len(byteBuffer.Bytes()) } + +// Create state block based on the utxos. +func (utxoPool *UTXOPool) CreateStateBlock() *Block { + return NewStateBlock(utxoPool) +} From 80d98946d407993b2d82e8e22ff3619379e31d47 Mon Sep 17 00:00:00 2001 From: Rongjian Lan Date: Mon, 3 Sep 2018 17:24:49 -0700 Subject: [PATCH 3/5] Create state block every 10 tx blocks --- consensus/consensus_leader.go | 1 - node/node_handler.go | 44 +++++++++++++++++++++-------------- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/consensus/consensus_leader.go b/consensus/consensus_leader.go index 3d8ba5084..c46d173de 100644 --- a/consensus/consensus_leader.go +++ b/consensus/consensus_leader.go @@ -367,7 +367,6 @@ func (consensus *Consensus) processResponseMessage(payload []byte, targetState C } // Sign the block - // TODO(RJ): populate bitmap copy(blockHeaderObj.Signature[:], collectiveSig[:]) copy(blockHeaderObj.Bitmap[:], bitmap) consensus.OnConsensusDone(&blockHeaderObj) diff --git a/node/node_handler.go b/node/node_handler.go index c1ebd384a..621fd2a3b 100644 --- a/node/node_handler.go +++ b/node/node_handler.go @@ -20,6 +20,8 @@ import ( const ( // The max number of transaction per a block. MaxNumberOfTransactionsPerBlock = 3000 + // The number of blocks allowed before generating state block + NumBlocksBeforeStateBlock = 10 ) // NodeHandler handles a new incoming connection. @@ -206,26 +208,32 @@ func (node *Node) WaitForConsensusReady(readySignal chan int) { //node.log.Debug("Adding new block", "currentChainSize", len(node.blockchain.Blocks), "numTxs", len(node.blockchain.GetLatestBlock().Transactions), "PrevHash", node.blockchain.GetLatestBlock().PrevBlockHash, "Hash", node.blockchain.GetLatestBlock().Hash) if !retry { - for { - // Once we have pending transactions we will try creating a new block - if len(node.pendingTransactions) >= 1 { - selectedTxs, crossShardTxAndProofs := node.getTransactionsForNewBlock(MaxNumberOfTransactionsPerBlock) - - if len(selectedTxs) == 0 { - node.log.Debug("No valid transactions exist", "pendingTx", len(node.pendingTransactions)) - } else { - node.log.Debug("Creating new block", "numTxs", len(selectedTxs), "pendingTxs", len(node.pendingTransactions), "currentChainSize", len(node.blockchain.Blocks)) - - node.transactionInConsensus = selectedTxs - node.log.Debug("CROSS SHARD TX", "num", len(crossShardTxAndProofs)) - node.CrossTxsInConsensus = crossShardTxAndProofs - newBlock = blockchain.NewBlock(selectedTxs, node.blockchain.GetLatestBlock().Hash, node.Consensus.ShardID) - break + if len(node.blockchain.Blocks) > NumBlocksBeforeStateBlock { + // Generate state block and run consensus on it + newBlock = node.UtxoPool.CreateStateBlock() + } else { + // Normal tx block consensus + for { + // Once we have pending transactions we will try creating a new block + if len(node.pendingTransactions) >= 1 { + selectedTxs, crossShardTxAndProofs := node.getTransactionsForNewBlock(MaxNumberOfTransactionsPerBlock) + + if len(selectedTxs) == 0 { + node.log.Debug("No valid transactions exist", "pendingTx", len(node.pendingTransactions)) + } else { + node.log.Debug("Creating new block", "numTxs", len(selectedTxs), "pendingTxs", len(node.pendingTransactions), "currentChainSize", len(node.blockchain.Blocks)) + + node.transactionInConsensus = selectedTxs + node.log.Debug("CROSS SHARD TX", "num", len(crossShardTxAndProofs)) + node.CrossTxsInConsensus = crossShardTxAndProofs + newBlock = blockchain.NewBlock(selectedTxs, node.blockchain.GetLatestBlock().Hash, node.Consensus.ShardID) + break + } } + // If not enough transactions to run Consensus, + // periodically check whether we have enough transactions to package into block. + time.Sleep(1 * time.Second) } - // If not enough transactions to run Consensus, - // periodically check whether we have enough transactions to package into block. - time.Sleep(1 * time.Second) } } From 3919360ef6ab7e2905e2b746c166b3ece7bfe23f Mon Sep 17 00:00:00 2001 From: Rongjian Lan Date: Mon, 3 Sep 2018 17:34:10 -0700 Subject: [PATCH 4/5] Wire state block creation and verificaiton into consensus --- blockchain/utxopool.go | 6 ++++++ node/node_handler.go | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/blockchain/utxopool.go b/blockchain/utxopool.go index 184027362..c99644aa9 100644 --- a/blockchain/utxopool.go +++ b/blockchain/utxopool.go @@ -85,6 +85,12 @@ func (utxoPool *UTXOPool) VerifyTransactions(transactions []*Transaction) bool { return true } +// VerifyStateBlock verifies if the given state block matches the current utxo pool. +func (utxoPool *UTXOPool) VerifyStateBlock(stateBlock *Block) bool { + // TODO: implement this + return true +} + // VerifyOneTransaction verifies if a list of transactions valid. func (utxoPool *UTXOPool) VerifyOneTransaction(tx *Transaction, spentTXOs *map[[20]byte]map[string]map[uint32]bool) (valid, crossShard bool) { if len(tx.Proofs) != 0 { diff --git a/node/node_handler.go b/node/node_handler.go index 621fd2a3b..e7a3819ff 100644 --- a/node/node_handler.go +++ b/node/node_handler.go @@ -271,7 +271,11 @@ func (node *Node) BroadcastNewBlock(newBlock *blockchain.Block) { // This is called by consensus participants to verify the block they are running consensus on func (node *Node) VerifyNewBlock(newBlock *blockchain.Block) bool { - return node.UtxoPool.VerifyTransactions(newBlock.Transactions) + if bytes.Equal(newBlock.PrevBlockHash[:], (&[32]byte{})[:]) { + return node.UtxoPool.VerifyStateBlock(newBlock) + } else { + return node.UtxoPool.VerifyTransactions(newBlock.Transactions) + } } // This is called by consensus participants, after consensus is done, to: From c09dc508f331a839f2f71d41099bead79b046583 Mon Sep 17 00:00:00 2001 From: Rongjian Lan Date: Mon, 3 Sep 2018 17:54:30 -0700 Subject: [PATCH 5/5] Add processing after state block consensus --- blockchain/block.go | 8 ++++++++ node/node_handler.go | 45 ++++++++++++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/blockchain/block.go b/blockchain/block.go index 696630db8..21d1a1a95 100644 --- a/blockchain/block.go +++ b/blockchain/block.go @@ -28,6 +28,10 @@ type Block struct { Signature [66]byte // Schnorr collective signature } +func (b *Block) IsStateBlock() bool { + return bytes.Equal(b.PrevBlockHash[:], (&[32]byte{})[:]) // TODO: think of a better indicator to check +} + // Serialize serializes the block func (b *Block) Serialize() []byte { var result bytes.Buffer @@ -75,6 +79,10 @@ func (b *Block) Write(db db.Database, key string) error { return db.Put([]byte(key), b.Serialize()) } +func Delete(db db.Database, key string) error { + return db.Delete([]byte(key)) +} + // CalculateBlockHash returns a hash of the block func (b *Block) CalculateBlockHash() []byte { var hashes [][]byte diff --git a/node/node_handler.go b/node/node_handler.go index e7a3819ff..20f9e47ff 100644 --- a/node/node_handler.go +++ b/node/node_handler.go @@ -271,7 +271,7 @@ func (node *Node) BroadcastNewBlock(newBlock *blockchain.Block) { // This is called by consensus participants to verify the block they are running consensus on func (node *Node) VerifyNewBlock(newBlock *blockchain.Block) bool { - if bytes.Equal(newBlock.PrevBlockHash[:], (&[32]byte{})[:]) { + if newBlock.IsStateBlock() { return node.UtxoPool.VerifyStateBlock(newBlock) } else { return node.UtxoPool.VerifyTransactions(newBlock.Transactions) @@ -282,22 +282,36 @@ func (node *Node) VerifyNewBlock(newBlock *blockchain.Block) bool { // 1. add the new block to blockchain // 2. [leader] move cross shard tx and proof to the list where they wait to be sent to the client func (node *Node) PostConsensusProcessing(newBlock *blockchain.Block) { - node.AddNewBlock(newBlock) - - if node.Consensus.IsLeader { - // Move crossTx-in-consensus into the list to be returned to client - for _, crossTxAndProof := range node.CrossTxsInConsensus { - crossTxAndProof.Proof.BlockHash = newBlock.Hash - // TODO: fill in the signature proofs - } - if len(node.CrossTxsInConsensus) != 0 { - node.addCrossTxsToReturn(node.CrossTxsInConsensus) - node.CrossTxsInConsensus = []*blockchain.CrossShardTxAndProof{} + if newBlock.IsStateBlock() { + // Clear out old tx blocks and put state block as genesis + if node.db != nil { + node.log.Info("Deleting old blocks.") + for i := 1; i <= len(node.blockchain.Blocks); i++ { + blockchain.Delete(node.db, strconv.Itoa(i)) + } } + node.blockchain.Blocks = []*blockchain.Block{} + node.AddNewBlock(newBlock) + } else { + node.AddNewBlock(newBlock) + node.UpdateUtxoAndState(newBlock) + + if node.Consensus.IsLeader { + // Move crossTx-in-consensus into the list to be returned to client + for _, crossTxAndProof := range node.CrossTxsInConsensus { + crossTxAndProof.Proof.BlockHash = newBlock.Hash + // TODO: fill in the signature proofs + } + if len(node.CrossTxsInConsensus) != 0 { + node.addCrossTxsToReturn(node.CrossTxsInConsensus) + node.CrossTxsInConsensus = []*blockchain.CrossShardTxAndProof{} + } - node.SendBackProofOfAcceptOrReject() - node.BroadcastNewBlock(newBlock) + node.SendBackProofOfAcceptOrReject() + node.BroadcastNewBlock(newBlock) + } } + } func (node *Node) AddNewBlock(newBlock *blockchain.Block) { @@ -308,6 +322,9 @@ func (node *Node) AddNewBlock(newBlock *blockchain.Block) { node.log.Info("Writing new block into disk.") newBlock.Write(node.db, strconv.Itoa(len(node.blockchain.Blocks))) } +} + +func (node *Node) UpdateUtxoAndState(newBlock *blockchain.Block) { // Update UTXO pool node.UtxoPool.Update(newBlock.Transactions) // Clear transaction-in-Consensus list