add transfer functionality for wallet

pull/152/head
Rongjian Lan 6 years ago
parent 09efd9ef61
commit ac2a998a36
  1. 1
      client/service/client.go
  2. 17
      client/service/server.go
  3. 215
      client/wallet/main.go
  4. 2
      consensus/consensus_leader.go
  5. 15
      node/node.go
  6. 11
      node/node_handler.go
  7. 1
      node/worker/worker.go

@ -40,7 +40,6 @@ func (client *Client) Close() {
// GetBalance gets block hashes from all the peers by calling grpc request. // GetBalance gets block hashes from all the peers by calling grpc request.
func (client *Client) GetBalance(address common.Address) *proto.FetchAccountStateResponse { 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) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() defer cancel()
request := &proto.FetchAccountStateRequest{Address: address.Bytes()} request := &proto.FetchAccountStateRequest{Address: address.Bytes()}

@ -12,14 +12,9 @@ import (
proto "github.com/harmony-one/harmony/client/service/proto" proto "github.com/harmony-one/harmony/client/service/proto"
) )
// Constants for downloader server.
const (
DefaultDownloadPort = "6666"
)
// Server is the Server struct for downloader package. // Server is the Server struct for downloader package.
type Server struct { type Server struct {
state *state.StateDB stateReader func() (*state.StateDB, error)
} }
// FetchAccountState implements the FetchAccountState interface to return account state. // 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 var address common.Address
address.SetBytes(request.Address) address.SetBytes(request.Address)
log.Println("Returning FetchAccountStateResponse for address: ", address.Hex()) 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. // 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. // NewServer creates new Server which implements ClientServiceServer interface.
func NewServer(state *state.StateDB) *Server { func NewServer(stateReader func() (*state.StateDB, error)) *Server {
s := &Server{state} s := &Server{stateReader}
return s return s
} }

@ -11,6 +11,7 @@ import (
crypto2 "github.com/ethereum/go-ethereum/crypto" crypto2 "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
client2 "github.com/harmony-one/harmony/client/service" client2 "github.com/harmony-one/harmony/client/service"
"github.com/harmony-one/harmony/core/types"
"log" "log"
"math/big" "math/big"
"strings" "strings"
@ -20,8 +21,6 @@ import (
"github.com/harmony-one/harmony/blockchain" "github.com/harmony-one/harmony/blockchain"
"github.com/harmony-one/harmony/client" "github.com/harmony-one/harmony/client"
client_config "github.com/harmony-one/harmony/client/config" 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/node"
"github.com/harmony-one/harmony/p2p" "github.com/harmony-one/harmony/p2p"
proto_node "github.com/harmony-one/harmony/proto/node" proto_node "github.com/harmony-one/harmony/proto/node"
@ -32,6 +31,12 @@ import (
"time" "time"
) )
// AccountState includes the state of an account
type AccountState struct {
balance *big.Int
nonce uint64
}
func main() { func main() {
// Account subcommands // Account subcommands
accountImportCommand := flag.NewFlagSet("import", flag.ExitOnError) accountImportCommand := flag.NewFlagSet("import", flag.ExitOnError)
@ -39,79 +44,78 @@ func main() {
// Transfer subcommands // Transfer subcommands
transferCommand := flag.NewFlagSet("transfer", flag.ExitOnError) transferCommand := flag.NewFlagSet("transfer", flag.ExitOnError)
transferSenderPtr := transferCommand.String("sender", "0", "Specify the sender account address or index") transferSenderPtr := transferCommand.String("from", "0", "Specify the sender account address or index")
transferReceiverPtr := transferCommand.String("receiver", "", "Specify the receiver account") transferReceiverPtr := transferCommand.String("to", "", "Specify the receiver account")
transferAmountPtr := transferCommand.Int("amount", 0, "Specify the amount to transfer") transferAmountPtr := transferCommand.Int("amount", 0, "Specify the amount to transfer")
// Verify that a subcommand has been provided // Verify that a subcommand has been provided
// os.Arg[0] is the main command // os.Arg[0] is the main command
// os.Arg[1] will be the subcommand // os.Arg[1] will be the subcommand
if len(os.Args) < 2 { if len(os.Args) < 2 {
fmt.Println("account or transfer subcommand is required") fmt.Println("Usage:")
fmt.Println(" wallet <action> <params>")
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) os.Exit(1)
} }
// Switch on the subcommand // Switch on the subcommand
switch os.Args[1] { switch os.Args[1] {
case "account": case "new":
switch os.Args[2] { randomBytes := [32]byte{}
case "new": _, err := io.ReadFull(rand.Reader, randomBytes[:])
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()
for _, address := range ReadAddresses() { if err != nil {
fmt.Printf("Account %s:\n %d ether \n", address.Hex(), FetchBalance(address, walletNode).Uint64()/params.Ether) fmt.Println("Failed to get randomness for the private key...")
return
} }
case "test": priKey, err := crypto2.GenerateKey()
// Testing code if err != nil {
priKey := pki.GetPrivateKeyScalarFromInt(444) panic("Failed to generate the private key")
address := pki.GetAddressFromPrivateKey(priKey) }
priKeyBytes, err := priKey.MarshalBinary() StorePrivateKey(crypto2.FromECDSA(priKey))
if err != nil { fmt.Printf("New account created with address:\n {%s}\n", crypto2.PubkeyToAddress(priKey.PublicKey).Hex())
panic("Failed to deserialize private key scalar.") 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": case "transfer":
transferCommand.Parse(os.Args[2:]) transferCommand.Parse(os.Args[2:])
@ -131,39 +135,44 @@ func main() {
return return
} }
senderIndex, err := strconv.Atoi(sender) senderIndex, err := strconv.Atoi(sender)
senderAddress := ""
addresses := ReadAddresses() addresses := ReadAddresses()
if err != nil { if err != nil {
senderIndex = -1 senderIndex = -1
for i, address := range addresses { for i, address := range addresses {
if fmt.Sprintf("%x", address) == senderAddress { if address.Hex() == sender {
senderIndex = i senderIndex = i
break break
} }
} }
if senderIndex == -1 { 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 break
} }
} }
if senderIndex >= len(priKeys) { if senderIndex >= len(priKeys) {
fmt.Println("Sender account index out of bounds.") fmt.Println("Sender account index out of bounds.")
return return
} }
receiverAddress, err := hex.DecodeString(receiver)
if err != nil || len(receiverAddress) != 20 { receiverAddress := common.HexToAddress(receiver)
fmt.Println("The receiver address is not a valid.") if len(receiverAddress) != 20 {
fmt.Println("The receiver address is not valid.")
return return
} }
// Generate transaction // Generate transaction
trimmedReceiverAddress := [20]byte{}
copy(trimmedReceiverAddress[:], receiverAddress[:20])
senderPriKey := priKeys[senderIndex] 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: default:
flag.PrintDefaults() flag.PrintDefaults()
os.Exit(1) os.Exit(1)
@ -193,8 +202,8 @@ func getShardIDToLeaderMap() map[uint32]p2p.Peer {
return shardIDLeaderMap return shardIDLeaderMap
} }
// CreateWalletServerNode creates wallet server node. // CreateWalletNode creates wallet server node.
func CreateWalletServerNode() *node.Node { func CreateWalletNode() *node.Node {
configr := client_config.NewConfig() configr := client_config.NewConfig()
var shardIDLeaderMap map[uint32]p2p.Peer var shardIDLeaderMap map[uint32]p2p.Peer
var clientPeer *p2p.Peer var clientPeer *p2p.Peer
@ -206,63 +215,37 @@ func CreateWalletServerNode() *node.Node {
shardIDLeaderMap = getShardIDToLeaderMap() shardIDLeaderMap = getShardIDToLeaderMap()
clientPeer = &p2p.Peer{Port: "127.0.0.1", IP: "1234"} clientPeer = &p2p.Peer{Port: "127.0.0.1", IP: "1234"}
} }
host := p2pimpl.NewHost(*clientPeer) host := p2pimpl.NewHost(*clientPeer)
walletNode := node.New(host, nil, nil) walletNode := node.New(host, nil, nil)
walletNode.Client = client.NewClient(walletNode.GetHost(), &shardIDLeaderMap) walletNode.Client = client.NewClient(walletNode.GetHost(), &shardIDLeaderMap)
return walletNode return walletNode
} }
// ExecuteTransaction issues the transaction to the Harmony network // SubmitTransaction submits the transaction to the Harmony network
func ExecuteTransaction(tx blockchain.Transaction, walletNode *node.Node) error { func SubmitTransaction(tx *types.Transaction, walletNode *node.Node) error {
if tx.IsCrossShard() { msg := proto_node.ConstructTransactionListMessageAccount(types.Transactions{tx})
walletNode.Client.PendingCrossTxsMutex.Lock()
walletNode.Client.PendingCrossTxs[tx.ID] = &tx
walletNode.Client.PendingCrossTxsMutex.Unlock()
}
msg := proto_node.ConstructTransactionListMessage([]*blockchain.Transaction{&tx})
walletNode.BroadcastMessage(walletNode.Client.GetLeaders(), msg) walletNode.BroadcastMessage(walletNode.Client.GetLeaders(), msg)
time.Sleep(300 * time.Millisecond)
doneSignal := make(chan int) return nil
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")
}
} }
// FetchBalance fetches account balance of specified address from the Harmony network // FetchBalance fetches account balance of specified address from the Harmony network
func FetchBalance(address common.Address, walletNode *node.Node) *big.Int { func FetchBalance(address common.Address, walletNode *node.Node) map[uint32]AccountState {
fmt.Println("Fetching account balance...") result := make(map[uint32]AccountState)
clients := []*client2.Client{} for shardID, leader := range *walletNode.Client.Leaders {
for _, leader := range *walletNode.Client.Leaders { client := client2.NewClient(leader.IP, node.ClientServicePort)
clients = append(clients, client2.NewClient(leader.IP, "1841"))
}
balance := big.NewInt(0)
for _, client := range clients {
response := client.GetBalance(address) response := client.GetBalance(address)
theirBalance := big.NewInt(0) balance := big.NewInt(0)
theirBalance.SetBytes(response.Balance) balance.SetBytes(response.Balance)
balance.Add(balance, theirBalance) result[shardID] = AccountState{balance, response.Nonce}
} }
return balance
return result
} }
// FetchUtxos fetches utxos of specified address from the Harmony network // FetchUtxos fetches utxos of specified address from the Harmony network
func FetchUtxos(addresses [][20]byte, walletNode *node.Node) (map[uint32]blockchain.UtxoMap, error) { 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.Client.ShardUtxoMap = make(map[uint32]blockchain.UtxoMap)
walletNode.BroadcastMessage(walletNode.Client.GetLeaders(), proto_node.ConstructFetchUtxoMessage(*walletNode.ClientPeer, addresses)) walletNode.BroadcastMessage(walletNode.Client.GetLeaders(), proto_node.ConstructFetchUtxoMessage(*walletNode.ClientPeer, addresses))

@ -80,7 +80,7 @@ func (consensus *Consensus) WaitForNewBlockAccount(blockChannel chan *types.Bloc
} }
startTime = time.Now() 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 { for consensus.state == Finished {
// time.Sleep(500 * time.Millisecond) // time.Sleep(500 * time.Millisecond)
data, err := rlp.EncodeToBytes(newBlock) data, err := rlp.EncodeToBytes(newBlock)

@ -80,7 +80,8 @@ const (
syncingPortDifference = 3000 syncingPortDifference = 3000
waitBeforeJoinShard = time.Second * 3 waitBeforeJoinShard = time.Second * 3
timeOutToJoinShard = time.Minute * 10 timeOutToJoinShard = time.Minute * 10
clientServicePort = "1841" // ClientServicePort is the port for client service
ClientServicePort = "18411"
) )
// NetworkNode ... // NetworkNode ...
@ -342,7 +343,7 @@ func New(host host.Host, consensus *bft.Consensus, db *hdb.LDBDatabase) *Node {
node.BlockChannelAccount = make(chan *types.Block) node.BlockChannelAccount = make(chan *types.Block)
node.Worker = worker.New(params.TestChainConfig, chain, node.Consensus, pki.GetAddressFromPublicKey(node.SelfPeer.PubKey)) node.Worker = worker.New(params.TestChainConfig, chain, node.Consensus, pki.GetAddressFromPublicKey(node.SelfPeer.PubKey))
//Initialize the pending transactions with smart contract transactions //Initialize the pending transactions with smart contract transactions
node.AddSmartContractsToPendingTransactions() //node.AddSmartContractsToPendingTransactions()
} }
if consensus != nil && consensus.IsLeader { if consensus != nil && consensus.IsLeader {
@ -479,17 +480,13 @@ func (node *Node) SupportClient() {
// InitClientServer initializes client server. // InitClientServer initializes client server.
func (node *Node) InitClientServer() { func (node *Node) InitClientServer() {
state, err := node.Chain.State() node.clientServer = clientService.NewServer(node.Chain.State)
if err != nil {
log.Error("Failed fetching state from blockchain")
}
node.clientServer = clientService.NewServer(state)
} }
// StartClientServer starts client server. // StartClientServer starts client server.
func (node *Node) StartClientServer() { func (node *Node) StartClientServer() {
node.log.Info("support_client: StartClientServer on port:", "port", clientServicePort) node.log.Info("support_client: StartClientServer on port:", "port", ClientServicePort)
node.clientServer.Start(node.SelfPeer.IP, clientServicePort) node.clientServer.Start(node.SelfPeer.IP, ClientServicePort)
} }
// SupportSyncing keeps sleeping until it's doing consensus or it's a leader. // SupportSyncing keeps sleeping until it's doing consensus or it's a leader.

@ -348,7 +348,8 @@ func (node *Node) WaitForConsensusReadyAccount(readySignal chan struct{}) {
if !retry { if !retry {
for { 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 // Normal tx block consensus
selectedTxs, _ := node.getTransactionsForNewBlockAccount(MaxNumberOfTransactionsPerBlock) selectedTxs, _ := node.getTransactionsForNewBlockAccount(MaxNumberOfTransactionsPerBlock)
node.Worker.CommitTransactions(selectedTxs) node.Worker.CommitTransactions(selectedTxs)
@ -478,9 +479,6 @@ func (node *Node) AddNewBlockAccount(newBlock *types.Block) {
num, err := node.Chain.InsertChain([]*types.Block{newBlock}) num, err := node.Chain.InsertChain([]*types.Block{newBlock})
if err != nil { if err != nil {
node.log.Debug("Error adding to chain", "numBlocks", num, "Error", err) 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 // Clear transaction-in-Consensus list
node.transactionInConsensus = []*blockchain.Transaction{} 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 { func (node *Node) pingMessageHandler(msgPayload []byte) int {

@ -56,6 +56,7 @@ func (w *Worker) SelectTransactionsForNewBlock(txs types.Transactions, maxNumTxs
if err != nil { if err != nil {
w.current.state.RevertToSnapshot(snap) w.current.state.RevertToSnapshot(snap)
invalid = append(invalid, tx) invalid = append(invalid, tx)
log.Debug("Invalid transaction", "Error", err)
} else { } else {
selected = append(selected, tx) selected = append(selected, tx)
} }

Loading…
Cancel
Save