The core protocol of WoopChain
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.
woop/client/client.go

147 lines
5.2 KiB

package client
import (
"bytes"
"encoding/gob"
"github.com/simple-rules/harmony-benchmark/proto/node"
"sync"
"github.com/simple-rules/harmony-benchmark/blockchain"
"github.com/simple-rules/harmony-benchmark/log"
"github.com/simple-rules/harmony-benchmark/p2p"
client_proto "github.com/simple-rules/harmony-benchmark/proto/client"
)
// A client represents a node (e.g. a wallet) which sends transactions and receives responses from the harmony network
type Client struct {
PendingCrossTxs map[[32]byte]*blockchain.Transaction // Map of TxId to pending cross shard txs. Pending means the proof-of-accept/rejects are not complete
PendingCrossTxsMutex sync.Mutex // Mutex for the pending txs list
leaders *[]p2p.Peer // All the leaders for each shard
UpdateBlocks func([]*blockchain.Block) // Closure function used to sync new block with the leader. Once the leader finishes the consensus on a new block, it will send it to the clients. Clients use this method to update their blockchain
UtxoMap blockchain.UtxoMap
ShardResponseTracker map[uint32]bool // A map containing the shard id of responded shard.
log log.Logger // Log utility
}
// The message handler for CLIENT/TRANSACTION messages.
func (client *Client) TransactionMessageHandler(msgPayload []byte) {
messageType := client_proto.TransactionMessageType(msgPayload[0])
switch messageType {
case client_proto.PROOF_OF_LOCK:
// Decode the list of blockchain.CrossShardTxProof
txDecoder := gob.NewDecoder(bytes.NewReader(msgPayload[1:])) // skip the PROOF_OF_LOCK messge type
proofs := new([]blockchain.CrossShardTxProof)
err := txDecoder.Decode(proofs)
if err != nil {
client.log.Error("Failed deserializing cross transaction proof list")
}
client.handleProofOfLockMessage(proofs)
case client_proto.UTXO_RESPONSE:
txDecoder := gob.NewDecoder(bytes.NewReader(msgPayload[1:])) // skip the PROOF_OF_LOCK messge type
fetchUtxoResponse := new(client_proto.FetchUtxoResponseMessage)
err := txDecoder.Decode(fetchUtxoResponse)
if err != nil {
client.log.Error("Failed deserializing utxo resposne")
}
client.handleFetchUtxoResponseMessage(*fetchUtxoResponse)
}
}
// Client once receives a list of proofs from a leader, for each proof:
// 1) retreive the pending cross shard transaction
// 2) add the proof to the transaction
// 3) checks whether all input utxos of the transaction have a corresponding proof.
// 4) for all transactions with full proofs, broadcast them back to the leaders
func (client *Client) handleProofOfLockMessage(proofs *[]blockchain.CrossShardTxProof) {
txsToSend := []blockchain.Transaction{}
// Loop through the newly received list of proofs
client.PendingCrossTxsMutex.Lock()
for _, proof := range *proofs {
// Find the corresponding pending cross tx
txAndProofs, ok := client.PendingCrossTxs[proof.TxID]
readyToUnlock := true // A flag used to mark whether whether this pending cross tx have all the proofs for its utxo input
if ok {
// Add the new proof to the cross tx's proof list
txAndProofs.Proofs = append(txAndProofs.Proofs, proof)
// Check whether this pending cross tx have all the proofs for its utxo inputs
txInputs := make(map[blockchain.TXInput]bool)
for _, curProof := range txAndProofs.Proofs {
for _, txInput := range curProof.TxInput {
txInputs[txInput] = true
}
}
for _, txInput := range txAndProofs.TxInput {
val, ok := txInputs[txInput]
if !ok || !val {
readyToUnlock = false
}
}
} else {
readyToUnlock = false
}
if readyToUnlock {
txsToSend = append(txsToSend, *txAndProofs)
}
}
// Delete all the transactions with full proofs from the pending cross txs
for _, txToSend := range txsToSend {
delete(client.PendingCrossTxs, txToSend.ID)
}
client.PendingCrossTxsMutex.Unlock()
// Broadcast the cross txs with full proofs for unlock-to-commit/abort
if len(txsToSend) != 0 {
client.broadcastCrossShardTxUnlockMessage(&txsToSend)
}
}
func (client *Client) handleFetchUtxoResponseMessage(utxoResponse client_proto.FetchUtxoResponseMessage) {
_, ok := client.ShardResponseTracker[utxoResponse.ShardId]
if ok {
return
}
client.ShardResponseTracker[utxoResponse.ShardId] = true
// Merge utxo response into client utxo map.
for address, txHash2Vout2AmountMap := range utxoResponse.UtxoMap {
clientTxHashMap, ok := client.UtxoMap[address]
if ok {
for txHash, vout2AmountMap := range txHash2Vout2AmountMap {
clientVout2AmountMap, ok := clientTxHashMap[txHash]
if ok {
for vout, amount := range vout2AmountMap {
clientVout2AmountMap[vout] = amount
}
} else {
clientTxHashMap[txHash] = vout2AmountMap
}
}
} else {
client.UtxoMap[address] = txHash2Vout2AmountMap
}
}
}
func (client *Client) broadcastCrossShardTxUnlockMessage(txsToSend *[]blockchain.Transaction) {
p2p.BroadcastMessage(*client.leaders, node.ConstructUnlockToCommitOrAbortMessage(*txsToSend))
}
// Create a new Client
func NewClient(leaders *[]p2p.Peer) *Client {
client := Client{}
client.PendingCrossTxs = make(map[[32]byte]*blockchain.Transaction)
client.leaders = leaders
// Logger
client.log = log.New()
return &client
}