You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
391 lines
11 KiB
391 lines
11 KiB
6 years ago
|
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
|
||
|
}
|