From 322d1b40f7a61c8dbec88234e7da0bea945f68ea Mon Sep 17 00:00:00 2001 From: Minh Doan Date: Mon, 19 Nov 2018 17:40:43 -0800 Subject: [PATCH] create new wallet client v2 --- client/wallet_v2/main.go | 390 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) create mode 100644 client/wallet_v2/main.go diff --git a/client/wallet_v2/main.go b/client/wallet_v2/main.go new file mode 100644 index 000000000..ed15f4e9e --- /dev/null +++ b/client/wallet_v2/main.go @@ -0,0 +1,390 @@ +package main + +import ( + "crypto/rand" + "encoding/hex" + "errors" + "flag" + "fmt" + "log" + "strings" + + "io" + "io/ioutil" + math_rand "math/rand" + "os" + "strconv" + "time" + + "github.com/dedis/kyber" + "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" + "github.com/harmony-one/harmony/utils" +) + +func main() { + // Account subcommands + accountImportCommand := flag.NewFlagSet("import", flag.ExitOnError) + 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") + + // 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") + 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 {%x}\n", i+1, address) + } + 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() + go walletNode.StartServer(walletNode.ClientPeer.Port) + + shardUtxoMap, err := FetchUtxos(ReadAddresses(), walletNode) + if err != nil { + fmt.Println(err) + } + PrintUtxoBalance(shardUtxoMap) + 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.") + } + fmt.Printf("Private Key :\n {%x}\n", priKeyBytes) + fmt.Printf("Address :\n {%x}\n", address) + } + case "transfer": + transferCommand.Parse(os.Args[2:]) + if !transferCommand.Parsed() { + fmt.Println("Failed to parse flags") + } + sender := *transferSenderPtr + receiver := *transferReceiverPtr + amount := *transferAmountPtr + + if amount <= 0 { + fmt.Println("Please specify positive amount to transfer") + } + priKeys := ReadPrivateKeys() + if len(priKeys) == 0 { + fmt.Println("No imported account to use.") + 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 { + senderIndex = i + break + } + } + if senderIndex == -1 { + fmt.Println("The specified sender account is not imported yet.") + 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.") + return + } + + // Generate transaction + trimmedReceiverAddress := [20]byte{} + copy(trimmedReceiverAddress[:], receiverAddress[:20]) + + senderPriKey := priKeys[senderIndex] + senderAddressBytes := pki.GetAddressFromPrivateKey(senderPriKey) + + // Start client server + walletNode := CreateWalletServerNode() + go walletNode.StartServer(walletNode.ClientPeer.Port) + + shardUtxoMap, err := FetchUtxos([][20]byte{senderAddressBytes}, walletNode) + if err != nil { + fmt.Printf("Failed to fetch utxos: %s\n", err) + } + + cummulativeBalance := 0 + txInputs := []blockchain.TXInput{} + LOOP: + for shardID, utxoMap := range shardUtxoMap { + for txID, vout2AmountMap := range utxoMap[senderAddressBytes] { + txIDBytes, err := utils.Get32BytesFromString(txID) + if err != nil { + fmt.Println("Failed to parse txID") + continue + } + for voutIndex, utxoAmount := range vout2AmountMap { + cummulativeBalance += utxoAmount + txIn := blockchain.NewTXInput(blockchain.NewOutPoint(&txIDBytes, voutIndex), senderAddressBytes, shardID) + txInputs = append(txInputs, *txIn) + if cummulativeBalance >= amount { + break LOOP + } + } + } + } + txout := blockchain.TXOutput{Amount: amount, Address: trimmedReceiverAddress, ShardID: uint32(math_rand.Intn(len(shardUtxoMap)))} + + txOutputs := []blockchain.TXOutput{txout} + if cummulativeBalance > amount { + changeTxOut := blockchain.TXOutput{Amount: cummulativeBalance - amount, Address: senderAddressBytes, ShardID: uint32(math_rand.Intn(len(shardUtxoMap)))} + txOutputs = append(txOutputs, changeTxOut) + } + + tx := blockchain.Transaction{ID: [32]byte{}, PublicKey: pki.GetBytesFromPublicKey(pki.GetPublicKeyFromScalar(senderPriKey)), TxInput: txInputs, TxOutput: txOutputs, Proofs: nil} + tx.SetID() // TODO(RJ): figure out the correct way to set Tx ID. + tx.Sign(senderPriKey) + + pubKey := crypto.Ed25519Curve.Point() + err = pubKey.UnmarshalBinary(tx.PublicKey[:]) + if err != nil { + fmt.Println("Failed to deserialize public key", "error", err) + } + + err = ExecuteTransaction(tx, walletNode) + + if err != nil { + fmt.Println(err) + } else { + fmt.Println("Transaction submitted successfully") + } + default: + flag.PrintDefaults() + os.Exit(1) + } +} + +func getShardIDToLeaderMap() map[uint32]p2p.Peer { + // TODO(ricl): Later use data.harmony.one for API. + str, _ := client.DownloadUrlAsString("https://s3-us-west-2.amazonaws.com/unique-bucket-bin/leaders.txt") + lines := strings.Split(str, "\n") + shardIDLeaderMap := map[uint32]p2p.Peer{} + log.Print(lines) + for _, line := range lines { + if line == "" { + continue + } + parts := strings.Split(line, " ") + + shardID := parts[3] + id, err := strconv.Atoi(shardID) + if err == nil { + shardIDLeaderMap[uint32(id)] = p2p.Peer{Ip: parts[0], Port: parts[1]} + } else { + log.Print("[Generator] Error parsing the shard Id ", shardID) + } + } + return shardIDLeaderMap +} + +func CreateWalletServerNode() *node.Node { + configr := client_config.NewConfig() + var shardIDLeaderMap map[uint32]p2p.Peer + var clientPeer *p2p.Peer + if true { + configr.ReadConfigFile("local_config2.txt") + shardIDLeaderMap = configr.GetShardIDToLeaderMap() + clientPeer = configr.GetClientPeer() + } else { + shardIDLeaderMap = getShardIDToLeaderMap() + clientPeer = &p2p.Peer{Port: "127.0.0.1", Ip: "1234"} + } + walletNode := node.New(nil, nil) + walletNode.Client = client.NewClient(&shardIDLeaderMap) + 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() + } + + msg := proto_node.ConstructTransactionListMessage([]*blockchain.Transaction{&tx}) + p2p.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") + } +} + +// 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) + p2p.BroadcastMessage(walletNode.Client.GetLeaders(), proto_node.ConstructFetchUtxoMessage(*walletNode.ClientPeer, addresses)) + + doneSignal := make(chan int) + go func() { + for { + if len(walletNode.Client.ShardUtxoMap) == len(*walletNode.Client.Leaders) { + doneSignal <- 0 + break + } + } + }() + + select { + case <-doneSignal: + return walletNode.Client.ShardUtxoMap, nil + case <-time.After(3 * time.Second): + return nil, errors.New("Utxo fetch timed out") + } +} + +func PrintUtxoBalance(shardUtxoMap map[uint32]blockchain.UtxoMap) { + addressBalance := make(map[[20]byte]int) + for _, utxoMap := range shardUtxoMap { + for address, txHash2Vout2AmountMap := range utxoMap { + for _, vout2AmountMap := range txHash2Vout2AmountMap { + for _, amount := range vout2AmountMap { + value, ok := addressBalance[address] + if ok { + addressBalance[address] = value + amount + } else { + addressBalance[address] = amount + } + } + } + } + } + for address, balance := range addressBalance { + fmt.Printf("Address: {%x}\n", address) + fmt.Printf("Balance: %d\n", balance) + } +} + +// Read the addresses stored in local keystore +func ReadAddresses() [][20]byte { + priKeys := ReadPrivateKeys() + addresses := [][20]byte{} + for _, key := range priKeys { + addresses = append(addresses, pki.GetAddressFromPrivateKey(key)) + } + 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)) { + fmt.Println("The key already exists in the keystore") + return + } + } + f, err := os.OpenFile("keystore", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + + if err != nil { + panic("Failed to open keystore") + } + _, err = f.Write(priKey) + + if err != nil { + panic("Failed to write to keystore") + } + f.Close() +} + +// 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 { + return []kyber.Scalar{} + } + keyScalars := []kyber.Scalar{} + for i := 0; i < len(keys); i += 32 { + priKey := crypto.Ed25519Curve.Scalar() + priKey.UnmarshalBinary(keys[i : i+32]) + keyScalars = append(keyScalars, priKey) + } + return keyScalars +}