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 = <ypes.Address{ 483 ChainId: chainId, 484 Local: fromLocal, 485 } 486 toAddress = <ypes.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: <ypes.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: <ypes.BigUInt{Value: common.BigUInt{Int: event.TokenId}}, 576 Amount: <ypes.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: <ypes.BigUInt{Value: common.BigUInt{Int: event.TokenId}}, 616 Amount: <ypes.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] = <ypes.BigUInt{Value: common.BigUInt{Int: tokenType}} 658 amounts[index] = <ypes.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 }