From ac2a998a3683b84aef5df08fb642a5ba861f942a Mon Sep 17 00:00:00 2001 From: Rongjian Lan Date: Thu, 13 Dec 2018 22:41:26 -0800 Subject: [PATCH] add transfer functionality for wallet --- client/service/client.go | 1 - client/service/server.go | 17 ++- client/wallet/main.go | 215 ++++++++++++++++------------------ consensus/consensus_leader.go | 2 +- node/node.go | 15 +-- node/node_handler.go | 11 +- node/worker/worker.go | 1 + 7 files changed, 117 insertions(+), 145 deletions(-) diff --git a/client/service/client.go b/client/service/client.go index 2832e7ac7..348beb176 100644 --- a/client/service/client.go +++ b/client/service/client.go @@ -40,7 +40,6 @@ func (client *Client) Close() { // GetBalance gets block hashes from all the peers by calling grpc request. func (client *Client) GetBalance(address common.Address) *proto.FetchAccountStateResponse { - log.Println("Getting balance from address: ", address.Hex()) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() request := &proto.FetchAccountStateRequest{Address: address.Bytes()} diff --git a/client/service/server.go b/client/service/server.go index 9bc898342..0a7646e66 100644 --- a/client/service/server.go +++ b/client/service/server.go @@ -12,14 +12,9 @@ import ( proto "github.com/harmony-one/harmony/client/service/proto" ) -// Constants for downloader server. -const ( - DefaultDownloadPort = "6666" -) - // Server is the Server struct for downloader package. type Server struct { - state *state.StateDB + stateReader func() (*state.StateDB, error) } // FetchAccountState implements the FetchAccountState interface to return account state. @@ -27,7 +22,11 @@ func (s *Server) FetchAccountState(ctx context.Context, request *proto.FetchAcco var address common.Address address.SetBytes(request.Address) log.Println("Returning FetchAccountStateResponse for address: ", address.Hex()) - return &proto.FetchAccountStateResponse{Balance: s.state.GetBalance(address).Bytes(), Nonce: s.state.GetNonce(address)}, nil + state, err := s.stateReader() + if err != nil { + return nil, err + } + return &proto.FetchAccountStateResponse{Balance: state.GetBalance(address).Bytes(), Nonce: state.GetNonce(address)}, nil } // Start starts the Server on given ip and port. @@ -46,7 +45,7 @@ func (s *Server) Start(ip, port string) (*grpc.Server, error) { } // NewServer creates new Server which implements ClientServiceServer interface. -func NewServer(state *state.StateDB) *Server { - s := &Server{state} +func NewServer(stateReader func() (*state.StateDB, error)) *Server { + s := &Server{stateReader} return s } diff --git a/client/wallet/main.go b/client/wallet/main.go index aaa1b266d..c4f914de8 100644 --- a/client/wallet/main.go +++ b/client/wallet/main.go @@ -11,6 +11,7 @@ import ( crypto2 "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" client2 "github.com/harmony-one/harmony/client/service" + "github.com/harmony-one/harmony/core/types" "log" "math/big" "strings" @@ -20,8 +21,6 @@ import ( "github.com/harmony-one/harmony/blockchain" "github.com/harmony-one/harmony/client" client_config "github.com/harmony-one/harmony/client/config" - "github.com/harmony-one/harmony/crypto" - "github.com/harmony-one/harmony/crypto/pki" "github.com/harmony-one/harmony/node" "github.com/harmony-one/harmony/p2p" proto_node "github.com/harmony-one/harmony/proto/node" @@ -32,6 +31,12 @@ import ( "time" ) +// AccountState includes the state of an account +type AccountState struct { + balance *big.Int + nonce uint64 +} + func main() { // Account subcommands accountImportCommand := flag.NewFlagSet("import", flag.ExitOnError) @@ -39,79 +44,78 @@ func main() { // 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") + transferSenderPtr := transferCommand.String("from", "0", "Specify the sender account address or index") + transferReceiverPtr := transferCommand.String("to", "", "Specify the receiver account") transferAmountPtr := transferCommand.Int("amount", 0, "Specify the amount to transfer") // 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 transfer subcommand is required") + fmt.Println("Usage:") + fmt.Println(" wallet ") + fmt.Println("Actions:") + fmt.Println(" 1. new - Generates a new account and store the private key locally") + fmt.Println(" 2. list - Lists all accounts in local keystore") + fmt.Println(" 3. removeAll - Removes all accounts in local keystore") + fmt.Println(" 4. import - Imports a new account by private key") + fmt.Println(" --privateKey - the private key to import") + fmt.Println(" 5. balances - Shows the balances of all accounts") + fmt.Println(" 6. transfer") + fmt.Println(" --from - The sender account's address or index in the local keystore") + fmt.Println(" --to - The receiver account's address") + fmt.Println(" --amount - The amount of token to transfer") os.Exit(1) } // Switch on the subcommand switch os.Args[1] { - case "account": - switch os.Args[2] { - case "new": - randomBytes := [32]byte{} - _, err := io.ReadFull(rand.Reader, randomBytes[:]) - - if err != nil { - fmt.Println("Failed to create a new private key...") - return - } - priKey := crypto.Ed25519Curve.Scalar().SetBytes(randomBytes[:]) - priKeyBytes, err := priKey.MarshalBinary() - if err != nil { - panic("Failed to serialize the private key") - } - pubKey := pki.GetPublicKeyFromScalar(priKey) - address := pki.GetAddressFromPublicKey(pubKey) - StorePrivateKey(priKeyBytes) - fmt.Printf("New account created:\nAddress: {%x}\n", address) - case "list": - for i, address := range ReadAddresses() { - fmt.Printf("Account %d:\n {%s}\n", i+1, address.Hex()) - } - case "clearAll": - ClearKeystore() - fmt.Println("All existing accounts deleted...") - case "import": - accountImportCommand.Parse(os.Args[3:]) - priKey := *accountImportPtr - if priKey == "" { - fmt.Println("Error: --privateKey is required") - return - } - if !accountImportCommand.Parsed() { - fmt.Println("Failed to parse flags") - } - priKeyBytes, err := hex.DecodeString(priKey) - if err != nil { - panic("Failed to parse the private key into bytes") - } - StorePrivateKey(priKeyBytes) - fmt.Println("Private key imported...") - case "showBalance": - walletNode := CreateWalletServerNode() + case "new": + randomBytes := [32]byte{} + _, err := io.ReadFull(rand.Reader, randomBytes[:]) - for _, address := range ReadAddresses() { - fmt.Printf("Account %s:\n %d ether \n", address.Hex(), FetchBalance(address, walletNode).Uint64()/params.Ether) - - } - case "test": - // Testing code - priKey := pki.GetPrivateKeyScalarFromInt(444) - address := pki.GetAddressFromPrivateKey(priKey) - priKeyBytes, err := priKey.MarshalBinary() - if err != nil { - panic("Failed to deserialize private key scalar.") + if err != nil { + fmt.Println("Failed to get randomness for the private key...") + return + } + priKey, err := crypto2.GenerateKey() + if err != nil { + panic("Failed to generate the private key") + } + StorePrivateKey(crypto2.FromECDSA(priKey)) + fmt.Printf("New account created with address:\n {%s}\n", crypto2.PubkeyToAddress(priKey.PublicKey).Hex()) + fmt.Printf("Please keep a copy of the private key:\n {%s}\n", hex.EncodeToString(crypto2.FromECDSA(priKey))) + case "list": + for i, address := range ReadAddresses() { + fmt.Printf("Account %d:\n {%s}\n", i, address.Hex()) + } + case "removeAll": + ClearKeystore() + fmt.Println("All existing accounts deleted...") + case "import": + accountImportCommand.Parse(os.Args[3:]) + priKey := *accountImportPtr + if priKey == "" { + fmt.Println("Error: --privateKey is required") + return + } + if !accountImportCommand.Parsed() { + fmt.Println("Failed to parse flags") + } + priKeyBytes, err := hex.DecodeString(priKey) + if err != nil { + panic("Failed to parse the private key into bytes") + } + StorePrivateKey(priKeyBytes) + fmt.Println("Private key imported...") + case "balances": + walletNode := CreateWalletNode() + + for i, address := range ReadAddresses() { + fmt.Printf("Account %d: %s:\n", i, address.Hex()) + for shardID, balanceNonce := range FetchBalance(address, walletNode) { + fmt.Printf(" Balance in Shard %d: %.2f \n", shardID, float32(balanceNonce.balance.Uint64()/params.Ether)) } - fmt.Printf("Private Key :\n {%x}\n", priKeyBytes) - fmt.Printf("Address :\n {%x}\n", address) } case "transfer": transferCommand.Parse(os.Args[2:]) @@ -131,39 +135,44 @@ func main() { return } senderIndex, err := strconv.Atoi(sender) - senderAddress := "" addresses := ReadAddresses() if err != nil { senderIndex = -1 for i, address := range addresses { - if fmt.Sprintf("%x", address) == senderAddress { + if address.Hex() == sender { senderIndex = i break } } if senderIndex == -1 { - fmt.Println("The specified sender account is not imported yet.") + fmt.Println("The specified sender account does not exist in the wallet.") break } } + if senderIndex >= len(priKeys) { fmt.Println("Sender account index out of bounds.") return } - receiverAddress, err := hex.DecodeString(receiver) - if err != nil || len(receiverAddress) != 20 { - fmt.Println("The receiver address is not a valid.") + + receiverAddress := common.HexToAddress(receiver) + if len(receiverAddress) != 20 { + fmt.Println("The receiver address is not valid.") return } // Generate transaction - trimmedReceiverAddress := [20]byte{} - copy(trimmedReceiverAddress[:], receiverAddress[:20]) - senderPriKey := priKeys[senderIndex] - _ = senderPriKey + senderAddress := addresses[senderIndex] + walletNode := CreateWalletNode() + shardIDToAccountState := FetchBalance(senderAddress, walletNode) - // TODO: implement account transaction logic + if shardIDToAccountState[0].balance.Uint64()/params.Ether < uint64(amount) { + fmt.Printf("Balance is not enough for the transfer: %d harmony token\n", shardIDToAccountState[0].balance.Uint64()/params.Ether) + return + } + tx, _ := types.SignTx(types.NewTransaction(shardIDToAccountState[0].nonce, receiverAddress, 0, big.NewInt(int64(amount*params.Ether)), params.TxGas, nil, nil), types.HomesteadSigner{}, senderPriKey) + SubmitTransaction(tx, walletNode) default: flag.PrintDefaults() os.Exit(1) @@ -193,8 +202,8 @@ func getShardIDToLeaderMap() map[uint32]p2p.Peer { return shardIDLeaderMap } -// CreateWalletServerNode creates wallet server node. -func CreateWalletServerNode() *node.Node { +// CreateWalletNode creates wallet server node. +func CreateWalletNode() *node.Node { configr := client_config.NewConfig() var shardIDLeaderMap map[uint32]p2p.Peer var clientPeer *p2p.Peer @@ -206,63 +215,37 @@ func CreateWalletServerNode() *node.Node { shardIDLeaderMap = getShardIDToLeaderMap() clientPeer = &p2p.Peer{Port: "127.0.0.1", IP: "1234"} } + host := p2pimpl.NewHost(*clientPeer) walletNode := node.New(host, nil, nil) walletNode.Client = client.NewClient(walletNode.GetHost(), &shardIDLeaderMap) return walletNode } -// ExecuteTransaction issues 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() - } - - msg := proto_node.ConstructTransactionListMessage([]*blockchain.Transaction{&tx}) +// SubmitTransaction submits the transaction to the Harmony network +func SubmitTransaction(tx *types.Transaction, walletNode *node.Node) error { + msg := proto_node.ConstructTransactionListMessageAccount(types.Transactions{tx}) walletNode.BroadcastMessage(walletNode.Client.GetLeaders(), msg) - - doneSignal := make(chan int) - go func() { - for { - if len(walletNode.Client.PendingCrossTxs) == 0 { - doneSignal <- 0 - break - } - } - }() - - select { - case <-doneSignal: - time.Sleep(100 * time.Millisecond) - return nil - case <-time.After(5 * time.Second): - return errors.New("Cross-shard Transaction processing timed out") - } + time.Sleep(300 * time.Millisecond) + return nil } // FetchBalance fetches account balance of specified address from the Harmony network -func FetchBalance(address common.Address, walletNode *node.Node) *big.Int { - fmt.Println("Fetching account balance...") - clients := []*client2.Client{} - for _, leader := range *walletNode.Client.Leaders { - clients = append(clients, client2.NewClient(leader.IP, "1841")) - } - - balance := big.NewInt(0) - for _, client := range clients { +func FetchBalance(address common.Address, walletNode *node.Node) map[uint32]AccountState { + result := make(map[uint32]AccountState) + for shardID, leader := range *walletNode.Client.Leaders { + client := client2.NewClient(leader.IP, node.ClientServicePort) response := client.GetBalance(address) - theirBalance := big.NewInt(0) - theirBalance.SetBytes(response.Balance) - balance.Add(balance, theirBalance) + balance := big.NewInt(0) + balance.SetBytes(response.Balance) + result[shardID] = AccountState{balance, response.Nonce} } - return balance + + return result } // FetchUtxos fetches 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) walletNode.BroadcastMessage(walletNode.Client.GetLeaders(), proto_node.ConstructFetchUtxoMessage(*walletNode.ClientPeer, addresses)) diff --git a/consensus/consensus_leader.go b/consensus/consensus_leader.go index 59179c31f..5c28fb826 100644 --- a/consensus/consensus_leader.go +++ b/consensus/consensus_leader.go @@ -80,7 +80,7 @@ func (consensus *Consensus) WaitForNewBlockAccount(blockChannel chan *types.Bloc } startTime = time.Now() - consensus.Log.Debug("STARTING CONSENSUS", "consensus", consensus, "startTime", startTime, "publicKeys", len(consensus.PublicKeys)) + consensus.Log.Debug("STARTING CONSENSUS", "numTxs", len(newBlock.Transactions()), "consensus", consensus, "startTime", startTime, "publicKeys", len(consensus.PublicKeys)) for consensus.state == Finished { // time.Sleep(500 * time.Millisecond) data, err := rlp.EncodeToBytes(newBlock) diff --git a/node/node.go b/node/node.go index f3d33448d..b07aa7a9b 100644 --- a/node/node.go +++ b/node/node.go @@ -80,7 +80,8 @@ const ( syncingPortDifference = 3000 waitBeforeJoinShard = time.Second * 3 timeOutToJoinShard = time.Minute * 10 - clientServicePort = "1841" + // ClientServicePort is the port for client service + ClientServicePort = "18411" ) // NetworkNode ... @@ -342,7 +343,7 @@ func New(host host.Host, consensus *bft.Consensus, db *hdb.LDBDatabase) *Node { node.BlockChannelAccount = make(chan *types.Block) node.Worker = worker.New(params.TestChainConfig, chain, node.Consensus, pki.GetAddressFromPublicKey(node.SelfPeer.PubKey)) //Initialize the pending transactions with smart contract transactions - node.AddSmartContractsToPendingTransactions() + //node.AddSmartContractsToPendingTransactions() } if consensus != nil && consensus.IsLeader { @@ -479,17 +480,13 @@ func (node *Node) SupportClient() { // InitClientServer initializes client server. func (node *Node) InitClientServer() { - state, err := node.Chain.State() - if err != nil { - log.Error("Failed fetching state from blockchain") - } - node.clientServer = clientService.NewServer(state) + node.clientServer = clientService.NewServer(node.Chain.State) } // StartClientServer starts client server. func (node *Node) StartClientServer() { - node.log.Info("support_client: StartClientServer on port:", "port", clientServicePort) - node.clientServer.Start(node.SelfPeer.IP, clientServicePort) + node.log.Info("support_client: StartClientServer on port:", "port", ClientServicePort) + node.clientServer.Start(node.SelfPeer.IP, ClientServicePort) } // SupportSyncing keeps sleeping until it's doing consensus or it's a leader. diff --git a/node/node_handler.go b/node/node_handler.go index 19543df8f..1d9ecc60c 100644 --- a/node/node_handler.go +++ b/node/node_handler.go @@ -348,7 +348,8 @@ func (node *Node) WaitForConsensusReadyAccount(readySignal chan struct{}) { if !retry { for { - if len(node.pendingTransactionsAccount) >= 1000 { + node.log.Debug("Num Pending Txs", "Num", len(node.pendingTransactionsAccount)) + if len(node.pendingTransactionsAccount) >= 1 { // Normal tx block consensus selectedTxs, _ := node.getTransactionsForNewBlockAccount(MaxNumberOfTransactionsPerBlock) node.Worker.CommitTransactions(selectedTxs) @@ -478,9 +479,6 @@ func (node *Node) AddNewBlockAccount(newBlock *types.Block) { num, err := node.Chain.InsertChain([]*types.Block{newBlock}) if err != nil { node.log.Debug("Error adding to chain", "numBlocks", num, "Error", err) - if node.Consensus != nil { - fmt.Println("SHARD ID", node.Consensus.ShardID) - } } } @@ -514,11 +512,6 @@ func (node *Node) UpdateUtxoAndState(newBlock *blockchain.Block) { } // Clear transaction-in-Consensus list node.transactionInConsensus = []*blockchain.Transaction{} - if node.Consensus.IsLeader { - node.log.Info("TX in New BLOCK", "num", len(newBlock.Transactions), "ShardID", node.UtxoPool.ShardID, "IsStateBlock", newBlock.IsStateBlock()) - node.log.Info("LEADER CURRENT UTXO", "num", node.UtxoPool.CountNumOfUtxos(), "ShardID", node.UtxoPool.ShardID) - node.log.Info("LEADER LOCKED UTXO", "num", node.UtxoPool.CountNumOfLockedUtxos(), "ShardID", node.UtxoPool.ShardID) - } } func (node *Node) pingMessageHandler(msgPayload []byte) int { diff --git a/node/worker/worker.go b/node/worker/worker.go index 56981f1a1..4618dc2a1 100644 --- a/node/worker/worker.go +++ b/node/worker/worker.go @@ -56,6 +56,7 @@ func (w *Worker) SelectTransactionsForNewBlock(txs types.Transactions, maxNumTxs if err != nil { w.current.state.RevertToSnapshot(snap) invalid = append(invalid, tx) + log.Debug("Invalid transaction", "Error", err) } else { selected = append(selected, tx) }