github.com/0chain/gosdk@v1.17.11/zcnbridge/authorizers_query.go (about)

     1  package zcnbridge
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"math"
     8  	"net/http"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/0chain/gosdk/core/common"
    13  	"github.com/0chain/gosdk/zcnbridge/errors"
    14  	"github.com/0chain/gosdk/zcnbridge/ethereum"
    15  	h "github.com/0chain/gosdk/zcnbridge/http"
    16  	"github.com/0chain/gosdk/zcnbridge/log"
    17  	"github.com/0chain/gosdk/zcnbridge/wallet"
    18  	"github.com/0chain/gosdk/zcnbridge/zcnsc"
    19  	"github.com/0chain/gosdk/zcncore"
    20  	"go.uber.org/zap"
    21  )
    22  
    23  type (
    24  	// authorizerResponse is HTTP client response event
    25  	authorizerResponse struct {
    26  		// 	AuthorizerID is authorizer where the job was performed
    27  		AuthorizerID string
    28  		// event is server job event
    29  		event JobResult
    30  		// error describes an error occurred during event processing on client side during the call to server
    31  		error
    32  	}
    33  
    34  	requestHandler struct {
    35  		path        string
    36  		values      map[string]string
    37  		bodyDecoder func([]byte) (JobResult, error)
    38  	}
    39  
    40  	responseChannelType chan *authorizerResponse
    41  	eventsChannelType   chan []JobResult
    42  )
    43  
    44  var (
    45  	client *http.Client
    46  )
    47  
    48  // QueryEthereumMintPayload gets burn ticket and creates mint payload to be minted in the Ethereum chain
    49  // zchainBurnHash - Ethereum burn transaction hash
    50  func (b *BridgeClient) QueryEthereumMintPayload(zchainBurnHash string) (*ethereum.MintPayload, error) {
    51  	client = h.CleanClient()
    52  	authorizers, err := getAuthorizers(true)
    53  
    54  	if err != nil || len(authorizers) == 0 {
    55  		return nil, errors.Wrap("get_authorizers", "failed to get authorizers", err)
    56  	}
    57  
    58  	var (
    59  		totalWorkers = len(authorizers)
    60  		values       = map[string]string{
    61  			"hash": zchainBurnHash,
    62  		}
    63  	)
    64  
    65  	handler := &requestHandler{
    66  		path:   wallet.BurnNativeTicketPath,
    67  		values: values,
    68  		bodyDecoder: func(body []byte) (JobResult, error) {
    69  			ev := &ProofZCNBurn{}
    70  			err := json.Unmarshal(body, ev)
    71  			return ev, err
    72  		},
    73  	}
    74  
    75  	thresh := b.ConsensusThreshold
    76  	results := queryAllAuthorizers(authorizers, handler)
    77  	numSuccess := len(results)
    78  	quorum := math.Ceil((float64(numSuccess) * 100) / float64(totalWorkers))
    79  
    80  	if numSuccess > 0 && quorum >= thresh {
    81  		burnTicket, ok := results[0].(*ProofZCNBurn)
    82  		if !ok {
    83  			return nil, errors.Wrap("type_cast", "failed to convert to *proofEthereumBurn", err)
    84  		}
    85  
    86  		var sigs []*ethereum.AuthorizerSignature
    87  		for _, result := range results {
    88  			ticket := result.(*ProofZCNBurn)
    89  			sig := &ethereum.AuthorizerSignature{
    90  				ID:        ticket.GetAuthorizerID(),
    91  				Signature: ticket.Signature,
    92  			}
    93  			sigs = append(sigs, sig)
    94  		}
    95  
    96  		payload := &ethereum.MintPayload{
    97  			ZCNTxnID:   burnTicket.TxnID,
    98  			Amount:     burnTicket.Amount,
    99  			To:         burnTicket.To,
   100  			Nonce:      burnTicket.Nonce,
   101  			Signatures: sigs,
   102  		}
   103  
   104  		return payload, nil
   105  	}
   106  
   107  	text := fmt.Sprintf("failed to reach the quorum. #Success: %d from #Total: %d", numSuccess, totalWorkers)
   108  	return nil, errors.New("get_burn_ticket", text)
   109  }
   110  
   111  // QueryEthereumBurnEvents gets ethereum burn events
   112  func (b *BridgeClient) QueryEthereumBurnEvents(startNonce string) ([]*ethereum.BurnEvent, error) {
   113  	client = h.CleanClient()
   114  	authorizers, err := getAuthorizers(true)
   115  
   116  	if err != nil || len(authorizers) == 0 {
   117  		return nil, errors.Wrap("get_authorizers", "failed to get authorizers", err)
   118  	}
   119  
   120  	var (
   121  		totalWorkers = len(authorizers)
   122  		values       = map[string]string{
   123  			"clientid":        zcncore.GetClientWalletID(),
   124  			"ethereumaddress": b.EthereumAddress,
   125  			"startnonce":      startNonce,
   126  		}
   127  	)
   128  
   129  	handler := &requestHandler{
   130  		path:   wallet.BurnWzcnBurnEventsPath,
   131  		values: values,
   132  		bodyDecoder: func(body []byte) (JobResult, error) {
   133  			ev := &EthereumBurnEvents{}
   134  			err := json.Unmarshal(body, ev)
   135  			return ev, err
   136  		},
   137  	}
   138  
   139  	thresh := b.ConsensusThreshold
   140  	results := queryAllAuthorizers(authorizers, handler)
   141  	numSuccess := len(results)
   142  	quorum := math.Ceil((float64(numSuccess) * 100) / float64(totalWorkers))
   143  
   144  	if numSuccess > 0 && quorum >= thresh {
   145  		burnEvents, ok := results[0].(*EthereumBurnEvents)
   146  		if !ok {
   147  			return nil, errors.Wrap("type_cast", "failed to convert to *ethereumBurnEvents", err)
   148  		}
   149  
   150  		result := make([]*ethereum.BurnEvent, 0)
   151  
   152  		for _, burnEvent := range burnEvents.BurnEvents {
   153  			result = append(result, &ethereum.BurnEvent{
   154  				Nonce:           burnEvent.Nonce,
   155  				Amount:          burnEvent.Amount,
   156  				TransactionHash: burnEvent.TransactionHash,
   157  			})
   158  		}
   159  
   160  		return result, nil
   161  	}
   162  
   163  	text := fmt.Sprintf("failed to reach the quorum. #Success: %d from #Total: %d", numSuccess, totalWorkers)
   164  	return nil, errors.New("get_burn_events", text)
   165  }
   166  
   167  // QueryZChainMintPayload gets burn ticket and creates mint payload to be minted in the ZChain
   168  // ethBurnHash - Ethereum burn transaction hash
   169  func (b *BridgeClient) QueryZChainMintPayload(ethBurnHash string) (*zcnsc.MintPayload, error) {
   170  	client = h.CleanClient()
   171  	authorizers, err := getAuthorizers(true)
   172  	log.Logger.Info("Got authorizers", zap.Int("amount", len(authorizers)))
   173  
   174  	if err != nil || len(authorizers) == 0 {
   175  		return nil, errors.Wrap("get_authorizers", "failed to get authorizers", err)
   176  	}
   177  
   178  	var (
   179  		totalWorkers = len(authorizers)
   180  		values       = map[string]string{
   181  			"hash":     ethBurnHash,
   182  			"clientid": zcncore.GetClientWalletID(),
   183  		}
   184  	)
   185  
   186  	handler := &requestHandler{
   187  		path:   wallet.BurnWzcnTicketPath,
   188  		values: values,
   189  		bodyDecoder: func(body []byte) (JobResult, error) {
   190  			ev := &WZCNBurnEvent{}
   191  			err := json.Unmarshal(body, ev)
   192  			return ev, err
   193  		},
   194  	}
   195  
   196  	thresh := b.ConsensusThreshold
   197  	results := queryAllAuthorizers(authorizers, handler)
   198  	numSuccess := len(results)
   199  	quorum := math.Ceil((float64(numSuccess) * 100) / float64(totalWorkers))
   200  
   201  	if numSuccess > 0 && quorum >= thresh {
   202  		burnTicket, ok := results[0].Data().(*ProofEthereumBurn)
   203  		if !ok {
   204  			return nil, errors.Wrap("type_cast", "failed to convert to *proofEthereumBurn", err)
   205  		}
   206  
   207  		var sigs []*zcnsc.AuthorizerSignature
   208  		for _, result := range results {
   209  			ticket := result.Data().(*ProofEthereumBurn)
   210  			sig := &zcnsc.AuthorizerSignature{
   211  				ID:        result.GetAuthorizerID(),
   212  				Signature: ticket.Signature,
   213  			}
   214  			sigs = append(sigs, sig)
   215  		}
   216  
   217  		payload := &zcnsc.MintPayload{
   218  			EthereumTxnID:     burnTicket.TxnID,
   219  			Amount:            common.Balance(burnTicket.Amount),
   220  			Nonce:             burnTicket.Nonce,
   221  			Signatures:        sigs,
   222  			ReceivingClientID: burnTicket.ReceivingClientID,
   223  		}
   224  
   225  		return payload, nil
   226  	}
   227  
   228  	text := fmt.Sprintf("failed to reach the quorum. #Success: %d from #Total: %d", numSuccess, totalWorkers)
   229  	return nil, errors.New("get_burn_ticket", text)
   230  }
   231  
   232  func queryAllAuthorizers(authorizers []*AuthorizerNode, handler *requestHandler) []JobResult {
   233  	var (
   234  		totalWorkers    = len(authorizers)
   235  		eventsChannel   = make(eventsChannelType)
   236  		responseChannel = make(responseChannelType, totalWorkers)
   237  	)
   238  	defer close(eventsChannel)
   239  
   240  	var wg sync.WaitGroup
   241  
   242  	for _, authorizer := range authorizers {
   243  		wg.Add(1)
   244  		go queryAuthorizer(authorizer, handler, responseChannel)
   245  	}
   246  
   247  	go handleResponse(responseChannel, eventsChannel, &wg)
   248  
   249  	wg.Wait()
   250  	close(responseChannel)
   251  	results := <-eventsChannel
   252  
   253  	return results
   254  }
   255  
   256  func handleResponse(responseChannel responseChannelType, eventsChannel eventsChannelType, wg *sync.WaitGroup) {
   257  	var events []JobResult
   258  	for job := range responseChannel {
   259  		if job.error == nil {
   260  			event := job.event
   261  			event.SetAuthorizerID(job.AuthorizerID)
   262  			events = append(events, event)
   263  		}
   264  		wg.Done()
   265  	}
   266  	eventsChannel <- events
   267  }
   268  
   269  func queryAuthorizer(au *AuthorizerNode, request *requestHandler, responseChannel responseChannelType) {
   270  	Logger.Info("Query from authorizer", zap.String("ID", au.ID), zap.String("URL", au.URL))
   271  	ticketURL := strings.TrimSuffix(au.URL, "/") + request.path
   272  
   273  	req, err := http.NewRequest("GET", ticketURL, nil)
   274  	if err != nil {
   275  		log.Logger.Error("failed to create request", zap.Error(err))
   276  		return
   277  	}
   278  
   279  	q := req.URL.Query()
   280  	for k, v := range request.values {
   281  		q.Add(k, v)
   282  	}
   283  	req.URL.RawQuery = q.Encode()
   284  	Logger.Info(req.URL.String())
   285  	resp, body := readResponse(client.Do(req))
   286  	resp.AuthorizerID = au.ID
   287  
   288  	if resp.error != nil {
   289  		Logger.Error(
   290  			"failed to process response",
   291  			zap.Error(resp.error),
   292  			zap.String("node.id", au.ID),
   293  			zap.String("node.url", au.URL),
   294  		)
   295  	}
   296  
   297  	event, errEvent := request.bodyDecoder(body)
   298  	event.SetAuthorizerID(au.ID)
   299  
   300  	if errEvent != nil {
   301  		err := errors.Wrap("decode_message_body", "failed to decode message body", errEvent)
   302  		log.Logger.Error(
   303  			"failed to decode event body",
   304  			zap.Error(err),
   305  			zap.String("node.id", au.ID),
   306  			zap.String("node.url", au.URL),
   307  			zap.String("body", string(body)),
   308  		)
   309  	}
   310  
   311  	resp.event = event
   312  
   313  	responseChannel <- resp
   314  }
   315  
   316  func readResponse(response *http.Response, err error) (res *authorizerResponse, body []byte) {
   317  	res = &authorizerResponse{}
   318  	if err != nil {
   319  		err = errors.Wrap("authorizer_post_process", "failed to call the authorizer", err)
   320  		Logger.Error("request response error", zap.Error(err))
   321  	}
   322  
   323  	if response == nil {
   324  		res.error = err
   325  		Logger.Error("response is empty", zap.Error(err))
   326  		return res, nil
   327  	}
   328  
   329  	if response.StatusCode >= 400 {
   330  		err = errors.Wrap("authorizer_post_process", fmt.Sprintf("error %d", response.StatusCode), err)
   331  		Logger.Error("request response status", zap.Error(err))
   332  	}
   333  
   334  	body, er := ioutil.ReadAll(response.Body)
   335  	log.Logger.Debug("response", zap.String("response", string(body)))
   336  	defer response.Body.Close()
   337  
   338  	if er != nil || len(body) == 0 {
   339  		var errstrings []string
   340  		er = errors.Wrap("authorizer_post_process", "failed to read body", er)
   341  		if err != nil {
   342  			errstrings = append(errstrings, err.Error())
   343  		}
   344  		errstrings = append(errstrings, er.Error())
   345  		err = fmt.Errorf(strings.Join(errstrings, ":"))
   346  	}
   347  
   348  	res.error = err
   349  
   350  	return res, body
   351  }