github.com/loomnetwork/gamechain@v0.0.0-20200406110549-36c47eb97a92/oracle/oracle.go (about)

     1  package oracle
     2  
     3  import (
     4  	"encoding/base64"
     5  	"io/ioutil"
     6  	"reflect"
     7  	"runtime"
     8  	"runtime/debug"
     9  	"sort"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    14  	ethcommon "github.com/ethereum/go-ethereum/common"
    15  	ethtypes "github.com/ethereum/go-ethereum/core/types"
    16  	"github.com/loomnetwork/gamechain/tools/battleground_utility"
    17  	orctype "github.com/loomnetwork/gamechain/types/oracle"
    18  	"github.com/loomnetwork/go-loom"
    19  	"github.com/loomnetwork/go-loom/auth"
    20  	"github.com/loomnetwork/go-loom/client"
    21  	"github.com/loomnetwork/go-loom/common"
    22  	ptypes "github.com/loomnetwork/go-loom/plugin/types"
    23  	ltypes "github.com/loomnetwork/go-loom/types"
    24  	"github.com/pkg/errors"
    25  )
    26  
    27  type Status struct {
    28  	Version                    string
    29  	OracleAddress              string
    30  	GamechainGatewayAddress    string
    31  	GamechainGatewayLastSeen   time.Time
    32  	PlasmachainGatewayAddress  string
    33  	PlasmachainGatewayLastSeen time.Time
    34  	NextPlasmachainBlockNumber uint64 `json:",string"`
    35  	// Number of Plamachain events submitted to the DAppChain Gateway successfully
    36  	PlasmachainEventsFetchedCount uint64 `json:",string"`
    37  	// Total number of Plamachain events fetched
    38  	PlasmachainEventsSubmittedCount uint64 `json:",string"`
    39  }
    40  
    41  type Oracle struct {
    42  	cfg     Config
    43  	chainID string
    44  	// Plasmachain address
    45  	pcAddress                loom.Address
    46  	pcSigner                 auth.Signer
    47  	pcZbgCardContractAddress loom.Address
    48  	pcGateway                *PlasmachainGateway
    49  	pcPollInterval           time.Duration
    50  	// Gamechain
    51  	gcAddress loom.Address
    52  	gcSigner  auth.Signer
    53  	gcGateway *GamechainGateway
    54  	// oracle
    55  	logger            *loom.Logger
    56  	reconnectInterval time.Duration
    57  	statusMutex       sync.RWMutex
    58  	status            Status
    59  	metrics           *Metrics
    60  	// Used to sign tx/data sent to the DAppChain Gateway contract
    61  	signer                        auth.Signer
    62  	startupDelay                  time.Duration
    63  	startBlock                    uint64
    64  	numPlasmachainEventsFetched   uint64
    65  	numPlasmachainEventsSubmitted uint64
    66  
    67  	hashPool *recentHashPool
    68  
    69  	maxBlockRange uint64
    70  }
    71  
    72  func CreateOracle(cfg *Config, metricSubsystem string) (*Oracle, error) {
    73  	logger := loom.NewLoomLogger(cfg.OracleLogLevel, cfg.OracleLogDestination)
    74  
    75  	privKey, err := LoadDappChainPrivateKey(cfg.GamechainPrivateKey)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	gcSigner := auth.NewEd25519Signer(privKey)
    80  	gcAddress := loom.Address{
    81  		ChainID: cfg.GamechainChainID,
    82  		Local:   loom.LocalAddressFromPublicKey(gcSigner.PublicKey()),
    83  	}
    84  
    85  	privKey, err = LoadDappChainPrivateKey(cfg.PlasmachainPrivateKey)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	pcSigner := auth.NewEd25519Signer(privKey)
    90  	pcAddress := loom.Address{
    91  		ChainID: cfg.PlasmachainChainID,
    92  		Local:   loom.LocalAddressFromPublicKey(pcSigner.PublicKey()),
    93  	}
    94  
    95  	pcZbgCardContractLocalAddress, err := loom.LocalAddressFromHexString(cfg.PlasmachainZbgCardContractHexAddress)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  
   100  	pcZbgCardContractAddress := loom.Address{
   101  		ChainID: cfg.PlasmachainChainID,
   102  		Local:   pcZbgCardContractLocalAddress,
   103  	}
   104  
   105  	hashPool := newRecentHashPool(time.Duration(cfg.PlasmachainPollInterval) * time.Second * 4)
   106  	hashPool.startCleanupRoutine()
   107  
   108  	logger.Info("Gamechain address " + gcAddress.String())
   109  	logger.Info("Plasmachain address " + pcAddress.String())
   110  	logger.Info("Plasmachain ZBGCard contract address " + pcZbgCardContractAddress.String())
   111  
   112  	maxBlockRange := uint64(cfg.PlasmachainMaxBlockRange)
   113  
   114  	return &Oracle{
   115  		cfg:                      *cfg,
   116  		gcAddress:                gcAddress,
   117  		gcSigner:                 gcSigner,
   118  		pcAddress:                pcAddress,
   119  		pcSigner:                 pcSigner,
   120  		pcZbgCardContractAddress: pcZbgCardContractAddress,
   121  		metrics:                  NewMetrics(metricSubsystem),
   122  		logger:                   logger,
   123  		pcPollInterval:           time.Duration(cfg.PlasmachainPollInterval) * time.Second,
   124  		startupDelay:             time.Duration(cfg.OracleStartupDelay) * time.Second,
   125  		reconnectInterval:        time.Duration(cfg.OracleReconnectInterval) * time.Second,
   126  		status: Status{
   127  			Version: "1.0.0",
   128  		},
   129  		hashPool:      hashPool,
   130  		maxBlockRange: maxBlockRange,
   131  	}, nil
   132  }
   133  
   134  // Status returns some basic info about the current state of the Oracle.
   135  func (orc *Oracle) Status() *Status {
   136  	orc.statusMutex.RLock()
   137  
   138  	s := orc.status
   139  
   140  	orc.statusMutex.RUnlock()
   141  	return &s
   142  }
   143  
   144  func (orc *Oracle) updateStatus() {
   145  	orc.statusMutex.Lock()
   146  
   147  	orc.status.NextPlasmachainBlockNumber = orc.startBlock
   148  	orc.status.PlasmachainEventsFetchedCount = orc.numPlasmachainEventsFetched
   149  	orc.status.PlasmachainEventsSubmittedCount = orc.numPlasmachainEventsSubmitted
   150  
   151  	if orc.gcGateway != nil {
   152  		orc.status.GamechainGatewayAddress = orc.gcGateway.Address.String()
   153  		orc.status.GamechainGatewayLastSeen = orc.gcGateway.LastResponseTime
   154  	}
   155  	if orc.pcGateway != nil {
   156  		orc.status.PlasmachainGatewayAddress = orc.pcGateway.Address.String()
   157  		orc.status.PlasmachainGatewayLastSeen = orc.pcGateway.LastResponseTime
   158  	}
   159  
   160  	orc.statusMutex.Unlock()
   161  }
   162  
   163  // Status returns some basic info about the current state of the Oracle.
   164  func (orc *Oracle) connect() error {
   165  	var err error
   166  	if orc.pcGateway == nil {
   167  		dappClient := client.NewDAppChainRPCClient(orc.cfg.PlasmachainChainID, orc.cfg.PlasmachainWriteURI, orc.cfg.PlasmachainReadURI)
   168  		orc.pcGateway, err = ConnectToPlasmachainGateway(dappClient, orc.pcAddress, orc.pcZbgCardContractAddress, orc.pcSigner, orc.logger)
   169  		if err != nil {
   170  			return errors.Wrap(err, "failed to create plasmachain gateway")
   171  		}
   172  		orc.logger.Info("connected to Plasmachain")
   173  	}
   174  
   175  	if orc.gcGateway == nil {
   176  		dappClient := client.NewDAppChainRPCClient(orc.cfg.GamechainChainID, orc.cfg.GamechainWriteURI, orc.cfg.GamechainReadURI)
   177  		orc.gcGateway, err = ConnectToGamechainGateway(dappClient, orc.gcAddress, orc.cfg.GamechainContractName, orc.gcSigner, orc.logger)
   178  		if err != nil {
   179  			return errors.Wrap(err, "failed to create gamechain gateway")
   180  		}
   181  		orc.logger.Info("connected to Gamechain")
   182  	}
   183  
   184  	return nil
   185  }
   186  
   187  // RunWithRecovery should run in a goroutine, it will ensure the oracle keeps on running as long
   188  // as it doesn't panic due to a runtime error.
   189  func (orc *Oracle) RunWithRecovery() {
   190  	orc.logger.Info("Running Oracle...")
   191  	defer func() {
   192  		if r := recover(); r != nil {
   193  			orc.logger.Error("recovered from panic in Oracle", "r", r, "stacktrace", string(debug.Stack()))
   194  			// Unless it's a runtime error restart the goroutine
   195  			if _, ok := r.(runtime.Error); !ok {
   196  				time.Sleep(30 * time.Second)
   197  				orc.logger.Info("Restarting Oracle...")
   198  				go orc.RunWithRecovery()
   199  			}
   200  		}
   201  	}()
   202  
   203  	// When running in-process give the node a bit of time to spin up.
   204  	if orc.startupDelay > 0 {
   205  		time.Sleep(orc.startupDelay)
   206  	}
   207  
   208  	orc.Run()
   209  }
   210  
   211  // TODO: Graceful shutdown
   212  func (orc *Oracle) Run() {
   213  	for {
   214  		err := orc.connect()
   215  		if err == nil {
   216  			break
   217  		}
   218  		orc.logger.Info(err.Error())
   219  		orc.updateStatus()
   220  		time.Sleep(orc.reconnectInterval)
   221  	}
   222  
   223  	skipSleep := true
   224  	for {
   225  		if !skipSleep {
   226  			time.Sleep(orc.pcPollInterval)
   227  		} else {
   228  			skipSleep = false
   229  		}
   230  		err := orc.doCommunicationRound()
   231  		if err != nil {
   232  			orc.logger.Error(err.Error())
   233  		}
   234  	}
   235  }
   236  
   237  func (orc *Oracle) doCommunicationRound() error {
   238  	latestPlasmaBlock, err := orc.pollPlasmachainForEvents()
   239  	if err != nil {
   240  		return errors.Wrap(err, "failed to poll Plasmachain for events")
   241  	}
   242  
   243  	if err := orc.executeGamechainCommands(latestPlasmaBlock); err != nil {
   244  		return errors.Wrap(err, "failed to execute Gamechain commands")
   245  	}
   246  
   247  	return nil
   248  }
   249  
   250  func (orc *Oracle) executeGamechainCommands(latestPlasmaBlock uint64) error {
   251  	orc.logger.Debug("Fetching Gamechain commands")
   252  
   253  	commandRequests, err := orc.gcGateway.GetOracleCommandRequestList()
   254  	if err != nil {
   255  		return err
   256  	}
   257  
   258  	commandResponses := make([]*orctype.OracleCommandResponse, 0, len(commandRequests))
   259  
   260  	orc.logger.Debug("Executing Gamechain commands", "len(commandRequests)", len(commandRequests))
   261  	for _, commandRequestWrapper := range commandRequests {
   262  		orc.logger.Info("Executing command", "commandId", commandRequestWrapper.CommandId, "commandType", reflect.TypeOf(commandRequestWrapper.GetCommand()).String())
   263  		switch commandRequest := commandRequestWrapper.Command.(type) {
   264  		case *orctype.OracleCommandRequest_GetUserFullCardCollection:
   265  			userAddress := commandRequest.GetUserFullCardCollection.UserAddress
   266  			tokensOwned, err := orc.pcGateway.GetTokensOwned(loom.UnmarshalAddressPB(userAddress).Local)
   267  			if err != nil {
   268  				return err
   269  			}
   270  
   271  			orc.logger.Info("GetUserFullCardCollection", "userAddress", loom.UnmarshalAddressPB(userAddress), "tokensOwned", len(tokensOwned))
   272  
   273  			response := &orctype.OracleCommandResponse_GetUserFullCardCollectionCommandResponse{
   274  				UserAddress: userAddress,
   275  				BlockHeight: latestPlasmaBlock,
   276  			}
   277  
   278  			for _, tokensOwnedResponseItem := range tokensOwned {
   279  				response.OwnedCards = append(response.OwnedCards, &orctype.RawCardCollectionCard{
   280  					CardTokenId: battleground_utility.MarshalBigIntProto(tokensOwnedResponseItem.Index),
   281  					Amount:      battleground_utility.MarshalBigIntProto(tokensOwnedResponseItem.Balance),
   282  				})
   283  			}
   284  
   285  			responseWrapper := &orctype.OracleCommandResponse{
   286  				CommandId: commandRequestWrapper.CommandId,
   287  				Command: &orctype.OracleCommandResponse_GetUserFullCardCollection{
   288  					GetUserFullCardCollection: response,
   289  				},
   290  			}
   291  			commandResponses = append(commandResponses, responseWrapper)
   292  		default:
   293  			orc.logger.Warn("unknown command type", "commandType", reflect.TypeOf(commandRequestWrapper.GetCommand()).String())
   294  		}
   295  	}
   296  
   297  	if len(commandResponses) > 0 {
   298  		orc.logger.Debug("Sending executed command responses to Gamechain", "len(commandResponses)", len(commandResponses))
   299  		err := orc.gcGateway.ProcessOracleCommandResponseBatch(commandResponses)
   300  		if err != nil {
   301  			return err
   302  		}
   303  	}
   304  
   305  	orc.logger.Debug("Finished executing Gamechain commands")
   306  	return nil
   307  }
   308  
   309  func (orc *Oracle) pollPlasmachainForEvents() (latestPlasmaBlock uint64, err error) {
   310  	orc.logger.Info("Start polling Plasmachain")
   311  	lastPlasmachainBlockNumber, err := orc.gcGateway.GetLastPlasmaBlockNumber()
   312  	if err != nil {
   313  		orc.logger.Error("failed to obtain last Plasmachain block number from Gamechain", "err", err)
   314  		return 0, err
   315  	}
   316  
   317  	orc.logger.Debug("got last processed Plasmachain block number from Gamechain", "lastPlasmachainBlockNumber", lastPlasmachainBlockNumber)
   318  	if lastPlasmachainBlockNumber == 0 {
   319  		err = errors.New("last processed Plasmachain block number from Gamechain == 0, unable to proceed, will retry")
   320  		return 0, err
   321  	}
   322  
   323  	startBlock := lastPlasmachainBlockNumber + 1
   324  	if orc.startBlock > startBlock {
   325  		startBlock = orc.startBlock
   326  	}
   327  
   328  	latestBlock, err := orc.getLatestEthBlockNumber()
   329  	if err != nil {
   330  		orc.logger.Error("failed to obtain latest Plasmachain block number", "err", err)
   331  		return 0, err
   332  	}
   333  
   334  	orc.logger.Debug("current latest Plasmachain block number", "latestBlock", latestBlock)
   335  
   336  	if latestBlock < startBlock {
   337  		// Wait for Plasmachain to produce a new block...
   338  		return 0, nil
   339  	}
   340  
   341  	if latestBlock-startBlock > orc.maxBlockRange {
   342  		latestBlock = startBlock + orc.maxBlockRange
   343  		orc.logger.Info("adjust latestBlock due to range limit", "startBlock", startBlock, "latestBlock", latestBlock)
   344  	}
   345  
   346  	orc.logger.Info("fetching events", "startBlock", startBlock, "latestBlock", latestBlock)
   347  	events, err := orc.fetchEvents(startBlock, latestBlock)
   348  	if err != nil {
   349  		orc.logger.Error("failed to fetch events from Plasmachain", "err", err)
   350  		return 0, err
   351  	}
   352  
   353  	orc.logger.Debug("finished fetching events", "len(events)", len(events))
   354  
   355  	if len(events) > 0 {
   356  		orc.numPlasmachainEventsFetched = orc.numPlasmachainEventsFetched + uint64(len(events))
   357  		orc.updateStatus()
   358  
   359  		orc.logger.Debug("calling ProcessOracleEventBatch")
   360  		if err := orc.gcGateway.ProcessOracleEventBatch(events, latestBlock, orc.pcZbgCardContractAddress); err != nil {
   361  			return 0, err
   362  		}
   363  		orc.logger.Debug("finished calling ProcessOracleEventBatch")
   364  
   365  		orc.numPlasmachainEventsSubmitted = orc.numPlasmachainEventsSubmitted + uint64(len(events))
   366  		orc.metrics.SubmittedPlasmachainEvents(len(events))
   367  		orc.updateStatus()
   368  	} else {
   369  		// If there were no events, just update the latest Plasmachain block number
   370  		// so that we won't process same events again.
   371  		orc.logger.Info("calling SetLastPlasmaBlockNumber")
   372  		if err := orc.gcGateway.SetLastPlasmaBlockNumber(latestBlock); err != nil {
   373  			orc.logger.Warn(err.Error())
   374  			return 0, err
   375  		}
   376  	}
   377  
   378  	orc.startBlock = latestBlock + 1
   379  	return latestBlock, nil
   380  }
   381  
   382  func (orc *Oracle) getLatestEthBlockNumber() (uint64, error) {
   383  	return orc.pcGateway.LastBlockNumber()
   384  }
   385  
   386  // Fetches all relevent events from an Plasmachain node from startBlock to endBlock (inclusive)
   387  func (orc *Oracle) fetchEvents(startBlock, endBlock uint64) ([]*orctype.PlasmachainEvent, error) {
   388  	// NOTE: Currently either all blocks from w.StartBlock are processed successfully or none are.
   389  	filterOpts := &bind.FilterOpts{
   390  		Start: startBlock,
   391  		End:   &endBlock,
   392  	}
   393  
   394  	var rawEvents []*plasmachainEventInfo
   395  	var err error
   396  
   397  	rawEvents, err = orc.fetchTransferEvents(filterOpts)
   398  	if err != nil {
   399  		return nil, errors.Wrap(err, "failed to fetch transfer events")
   400  	}
   401  
   402  	sortPlasmachainEvents(rawEvents)
   403  	events := make([]*orctype.PlasmachainEvent, len(rawEvents))
   404  	for i, event := range rawEvents {
   405  		events[i] = event.Event
   406  	}
   407  
   408  	if len(rawEvents) > 0 {
   409  		orc.logger.Debug("fetched Plasmachain events",
   410  			"startBlock", startBlock,
   411  			"endBlock", endBlock,
   412  			"eventCount", len(rawEvents),
   413  		)
   414  	}
   415  
   416  	return events, nil
   417  }
   418  
   419  func sortPlasmachainEvents(events []*plasmachainEventInfo) {
   420  	// Sort events by block & tx index (within the block)?
   421  	// Need to check if plasmachain event contains TxIdx
   422  	sort.SliceStable(events, func(i, j int) bool {
   423  		if events[i].BlockNum == events[j].BlockNum {
   424  			return events[i].TxIdx < events[j].TxIdx
   425  		}
   426  		return events[i].BlockNum < events[j].BlockNum
   427  	})
   428  }
   429  
   430  func LoadDappChainPrivateKeyFile(path string) ([]byte, error) {
   431  	privKeyB64, err := ioutil.ReadFile(path)
   432  	if err != nil {
   433  		return nil, err
   434  	}
   435  
   436  	privKey, err := base64.StdEncoding.DecodeString(string(privKeyB64))
   437  	if err != nil {
   438  		return nil, err
   439  	}
   440  
   441  	return privKey, nil
   442  }
   443  
   444  func LoadDappChainPrivateKey(privKeyB64 string) ([]byte, error) {
   445  	privKey, err := base64.StdEncoding.DecodeString(string(privKeyB64))
   446  	if err != nil {
   447  		return nil, err
   448  	}
   449  
   450  	return privKey, nil
   451  }
   452  
   453  func (orc *Oracle) processSingleRawEvent(rawEvent ethtypes.Log) (eventInfo *plasmachainEventInfo, receipt *ptypes.EvmTxReceipt, err error) {
   454  	receiptRaw, err := orc.pcGateway.client.GetEvmTxReceipt(rawEvent.TxHash.Bytes())
   455  	if err != nil {
   456  		orc.logger.Error(err.Error(), "txHash", rawEvent.TxHash.Hex())
   457  		return nil, nil, err
   458  	}
   459  
   460  	receipt = &receiptRaw
   461  
   462  	return &plasmachainEventInfo{
   463  		BlockNum: rawEvent.BlockNumber,
   464  		TxIdx:    rawEvent.TxIndex,
   465  		Event: &orctype.PlasmachainEvent{
   466  			EthBlock: rawEvent.BlockNumber,
   467  		},
   468  	}, receipt, nil
   469  }
   470  
   471  func parseBasicEventData(ethFromAddress *ethcommon.Address, ethToAddress *ethcommon.Address, chainId string) (fromAddress *ltypes.Address, toAddress *ltypes.Address, err error) {
   472  	fromLocal, err := loom.LocalAddressFromHexString(ethFromAddress.Hex())
   473  	if err != nil {
   474  		return nil, nil, errors.Wrapf(err, "error parsing address %s", ethFromAddress.Hex())
   475  	}
   476  
   477  	toLocal, err := loom.LocalAddressFromHexString(ethToAddress.Hex())
   478  	if err != nil {
   479  		return nil, nil, errors.Wrapf(err, "error parsing address %s", ethToAddress.Hex())
   480  	}
   481  
   482  	fromAddress = &ltypes.Address{
   483  		ChainId: chainId,
   484  		Local:   fromLocal,
   485  	}
   486  	toAddress = &ltypes.Address{
   487  		ChainId: chainId,
   488  		Local:   toLocal,
   489  	}
   490  
   491  	return fromAddress, toAddress, nil
   492  }
   493  
   494  func (orc *Oracle) fetchTransferEvents(filterOpts *bind.FilterOpts) ([]*plasmachainEventInfo, error) {
   495  	var err error
   496  	numTransferEvents := 0
   497  	numTransferTokenEvents := 0
   498  	numTransferWithQuantityEvents := 0
   499  	numBatchTransferEvents := 0
   500  	defer func(begin time.Time) {
   501  		orc.metrics.MethodCalled(begin, "fetchTransferEvents", err)
   502  		orc.metrics.FetchedPlasmachainEvents(numTransferEvents, "Transfer")
   503  		orc.metrics.FetchedPlasmachainEvents(numTransferTokenEvents, "TransferToken")
   504  		orc.metrics.FetchedPlasmachainEvents(numTransferWithQuantityEvents, "TransferWithQuantity")
   505  		orc.metrics.FetchedPlasmachainEvents(numBatchTransferEvents, "BatchTransfer")
   506  		orc.updateStatus()
   507  	}(time.Now())
   508  
   509  	var chainID = orc.pcGateway.client.GetChainID()
   510  	events := make([]*plasmachainEventInfo, 0)
   511  
   512  	// Transfer
   513  	transferIterator, err := orc.pcGateway.zbgCard.FilterTransfer(filterOpts, nil, nil, nil)
   514  	if err != nil {
   515  		return nil, errors.Wrap(err, "failed to get logs for Transfer")
   516  	}
   517  	for {
   518  		ok := transferIterator.Next()
   519  		if ok {
   520  			event := transferIterator.Event
   521  			eventInfo, _, err := orc.processSingleRawEvent(event.Raw)
   522  			if err != nil {
   523  				return nil, err
   524  			}
   525  
   526  			fromAddress, toAddress, err := parseBasicEventData(&event.From, &event.To, chainID)
   527  			if err != nil {
   528  				return nil, errors.Wrap(err, "error parsing basic event data")
   529  			}
   530  
   531  			eventInfo.Event.Payload = &orctype.PlasmachainEvent_Transfer{
   532  				Transfer: &orctype.PlasmachainEventTransfer{
   533  					From:    fromAddress,
   534  					To:      toAddress,
   535  					TokenId: &ltypes.BigUInt{Value: common.BigUInt{Int: event.TokenId}},
   536  				},
   537  			}
   538  
   539  			events = append(events, eventInfo)
   540  			numTransferEvents++
   541  		} else {
   542  			err = transferIterator.Error()
   543  			if err != nil {
   544  				return nil, errors.Wrap(err, "Failed to get event data for Transfer")
   545  			}
   546  			transferIterator.Close()
   547  			break
   548  		}
   549  	}
   550  
   551  	// TransferToken
   552  	transferTokenIterator, err := orc.pcGateway.zbgCard.FilterTransferToken(filterOpts, nil, nil, nil)
   553  	if err != nil {
   554  		return nil, errors.Wrap(err, "failed to get logs for TransferToken")
   555  	}
   556  	for {
   557  		ok := transferTokenIterator.Next()
   558  		if ok {
   559  			event := transferTokenIterator.Event
   560  			eventInfo, _, err := orc.processSingleRawEvent(event.Raw)
   561  			if err != nil {
   562  				return nil, err
   563  			}
   564  
   565  			fromAddress, toAddress, err := parseBasicEventData(&event.From, &event.To, chainID)
   566  			if err != nil {
   567  				return nil, errors.Wrap(err, "error parsing basic event data")
   568  			}
   569  
   570  			// TransferToken is just an old name for TransferWithQuantity, we can use the same event type
   571  			eventInfo.Event.Payload = &orctype.PlasmachainEvent_TransferWithQuantity{
   572  				TransferWithQuantity: &orctype.PlasmachainEventTransferWithQuantity{
   573  					From:    fromAddress,
   574  					To:      toAddress,
   575  					TokenId: &ltypes.BigUInt{Value: common.BigUInt{Int: event.TokenId}},
   576  					Amount:  &ltypes.BigUInt{Value: common.BigUInt{Int: event.Quantity}},
   577  				},
   578  			}
   579  
   580  			events = append(events, eventInfo)
   581  			numTransferTokenEvents++
   582  		} else {
   583  			err = transferTokenIterator.Error()
   584  			if err != nil {
   585  				return nil, errors.Wrap(err, "Failed to get event data for TransferToken")
   586  			}
   587  			transferTokenIterator.Close()
   588  			break
   589  		}
   590  	}
   591  
   592  	// TransferWithQuantity
   593  	transferWithQuantityIterator, err := orc.pcGateway.zbgCard.FilterTransferWithQuantity(filterOpts, nil)
   594  	if err != nil {
   595  		return nil, errors.Wrap(err, "failed to get logs for TransferWithQuantity")
   596  	}
   597  	for {
   598  		ok := transferWithQuantityIterator.Next()
   599  		if ok {
   600  			event := transferWithQuantityIterator.Event
   601  			eventInfo, _, err := orc.processSingleRawEvent(event.Raw)
   602  			if err != nil {
   603  				return nil, err
   604  			}
   605  
   606  			fromAddress, toAddress, err := parseBasicEventData(&event.From, &event.To, chainID)
   607  			if err != nil {
   608  				return nil, errors.Wrap(err, "error parsing basic event data")
   609  			}
   610  
   611  			eventInfo.Event.Payload = &orctype.PlasmachainEvent_TransferWithQuantity{
   612  				TransferWithQuantity: &orctype.PlasmachainEventTransferWithQuantity{
   613  					From:    fromAddress,
   614  					To:      toAddress,
   615  					TokenId: &ltypes.BigUInt{Value: common.BigUInt{Int: event.TokenId}},
   616  					Amount:  &ltypes.BigUInt{Value: common.BigUInt{Int: event.Amount}},
   617  				},
   618  			}
   619  
   620  			events = append(events, eventInfo)
   621  			numTransferWithQuantityEvents++
   622  		} else {
   623  			err = transferWithQuantityIterator.Error()
   624  			if err != nil {
   625  				return nil, errors.Wrap(err, "Failed to get event data for TransferWithQuantity")
   626  			}
   627  			transferWithQuantityIterator.Close()
   628  			break
   629  		}
   630  	}
   631  
   632  	// BatchTransfer
   633  	batchTransferIterator, err := orc.pcGateway.zbgCard.FilterBatchTransfer(filterOpts)
   634  	if err != nil {
   635  		return nil, errors.Wrap(err, "failed to get logs for BatchTransfer")
   636  	}
   637  	for {
   638  		ok := batchTransferIterator.Next()
   639  		if ok {
   640  			event := batchTransferIterator.Event
   641  			eventInfo, _, err := orc.processSingleRawEvent(event.Raw)
   642  			if err != nil {
   643  				return nil, err
   644  			}
   645  
   646  			fromAddress, toAddress, err := parseBasicEventData(&event.From, &event.To, chainID)
   647  			if err != nil {
   648  				return nil, errors.Wrap(err, "error parsing basic event data")
   649  			}
   650  
   651  			tokenIds := make([]*ltypes.BigUInt, len(event.TokenTypes))
   652  			amounts := make([]*ltypes.BigUInt, len(event.Amounts))
   653  
   654  			for index, tokenType := range event.TokenTypes {
   655  				amount := event.Amounts[index]
   656  
   657  				tokenIds[index] = &ltypes.BigUInt{Value: common.BigUInt{Int: tokenType}}
   658  				amounts[index] = &ltypes.BigUInt{Value: common.BigUInt{Int: amount}}
   659  			}
   660  
   661  			eventInfo.Event.Payload = &orctype.PlasmachainEvent_BatchTransfer{
   662  				BatchTransfer: &orctype.PlasmachainEventBatchTransfer{
   663  					From:     fromAddress,
   664  					To:       toAddress,
   665  					TokenIds: tokenIds,
   666  					Amounts:  amounts,
   667  				},
   668  			}
   669  
   670  			events = append(events, eventInfo)
   671  			numBatchTransferEvents++
   672  		} else {
   673  			err = batchTransferIterator.Error()
   674  			if err != nil {
   675  				return nil, errors.Wrap(err, "Failed to get event data for BatchTransfer")
   676  			}
   677  			batchTransferIterator.Close()
   678  			break
   679  		}
   680  	}
   681  
   682  	return events, nil
   683  }