All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sss.openstar.test.MultiIdentityTestTransactionSender.scala Maven / Gradle / Ivy

package sss.openstar.test


import akka.actor.Status.{Failure => FutureFailure}
import akka.actor.{Actor, ActorContext, ActorLogging, ActorRef, Cancellable, Props}
import akka.pattern.pipe
import sss.openstar.{Currency, UniqueNodeIdentifier}
import sss.openstar.account.{NodeIdTag, NodeIdentity, NodeIdentityManager}
import sss.openstar.block.Synchronized
import sss.openstar.chains.TxWriterActor.{InternalAck, InternalCommit, InternalTempNack, InternalTxResult}
import sss.openstar.network.MessageEventBus
import sss.openstar.test.MultiIdentityTestTransactionSender.{FireTxs, Register}
import sss.openstar.tools.SendTxSupport.SendTx
import sss.openstar.wallet.UnlockedWallet
import sss.openstar.wallet.UtxoTracker.TrackWallet
import sss.openstar.wallet.Wallet.Payment
import us.monoid.web.Resty

import java.util.concurrent.atomic.{AtomicInteger, AtomicLong}
import scala.concurrent.duration._
import scala.util.{Failure, Random, Success, Try}
import sss.ancillary.ByteArrayEncodedStrOps._
import sss.ancillary.FutureOps.AwaitResult
import sss.openstar.common.users.UserDirectory
object MultiIdentityTestTransactionSender {

  case object FireTxs
  case class Register(missingUsers: Seq[String])
  case class TestUsersConfig(numUsers: Int = 10, identityRoot: String = "test_user")

  def apply[C <: Currency](nodeIdentityManager: NodeIdentityManager,
                           users: UserDirectory,
                           testUsersConfig: Int,
                           claimHttpUrl: String,
                           buildUnlockedWallet: NodeIdentity => UnlockedWallet[C],
                           maxInProgress: Int
                          )(implicit actorSystem: ActorContext,
                            sendTx: SendTx,
                            messageEventBus: MessageEventBus
                          ): ActorRef = {

    actorSystem.actorOf(
      Props(classOf[MultiIdentityTestTransactionSender[C]],
        nodeIdentityManager: NodeIdentityManager,
        users: UserDirectory,
        testUsersConfig: Int,
        claimHttpUrl: String,
        buildUnlockedWallet: NodeIdentity => UnlockedWallet[C],
        maxInProgress,
        sendTx,
        messageEventBus)
      , "MultiIdentityTestTransactionSender")

  }
}

class MultiIdentityTestTransactionSender[C <: Currency](nodeIdentityManager: NodeIdentityManager,
                                                        users: UserDirectory,
                                                        testUsersConfig: Int,
                                                        claimHttpUrl: String,
                                                        buildUnlockedWallet: NodeIdentity => UnlockedWallet[C],
                                                        maxInProgress: Int
                                                       )(implicit
                                                         sendTx: SendTx,
                                                         messageEventBus: MessageEventBus) extends Actor with ActorLogging {


  override def preStart(): Unit = {
    super.preStart()
    messageEventBus.subscribe(classOf[Synchronized])
  }


  import context.dispatcher


  private def chars = ('a' to 'z').to(LazyList)

  def streamOfUserNames: LazyList[String] =  for {
    a <- chars
    b <- chars
    c <- chars
    d <- chars
    e <- chars
    f <- chars
  } yield (Seq(a,b,c, d, e,f).mkString)



  val testUsers = streamOfUserNames.take(testUsersConfig)

  def missingUsers: Seq[String] = {
    testUsers.filterNot(users.listUsers().contains)
  }

  private var testUsersWallets: Seq[UnlockedWallet[C]] = {
    testUsers.filter(users.listUsers().contains) map { user =>
      val ni = nodeIdentityManager(user, NodeIdTag.defaultTag, "password").await()
      buildUnlockedWallet(ni)
    }
  }

  private var schedFire: Option[Cancellable] = None

  private def scheduleFireTxs(delayInSeconds: Int) = {

    schedFire = schedFire match {
      case None =>
        Option(context.system.scheduler.scheduleOnce(delayInSeconds.seconds, self, FireTxs))
      case x => x
    }

  }

  private def randomWallet: UnlockedWallet[C] = {
    testUsersWallets(Random.nextInt(testUsersWallets.size))
  }

  private var inProgress = 0
  private val totalTxsProcessed: AtomicInteger = new AtomicInteger(0)
  private val totalTimeProcessing = new AtomicLong(0)


  private def calculateLatency(): Double = {
    val time = totalTimeProcessing.get()
    val totalTxs = totalTxsProcessed.get()
    if(time > 0 && totalTxs > 0) {
      time.toDouble / totalTxs.toDouble
    } else {
      0
    }
  }

  def sendTransactions: Receive = {

    case FutureFailure(e) =>
      log.warning("Failed, retry, " + e.toString)
      inProgress -= 1
      scheduleFireTxs(2)


    case com : InternalCommit =>
      log.info(s"Got commit: $com, going again")
      inProgress -= 1
      self ! FireTxs

    case ack : InternalAck =>
    //log.info(s"Got ack: $ack")

    case r: InternalTempNack =>
      inProgress -= 1
      if(inProgress % 20 == 0) {
        log.info(r.toString + s" TxResult $r rescheule")
      }
      scheduleFireTxs(2)

    case r : InternalTxResult =>
      inProgress -= 1
      self ! FireTxs

    case FireTxs if inProgress < maxInProgress =>
      schedFire = None
      var fired = false
      (0 until (maxInProgress - inProgress)) foreach {
        _ =>
          val wallet = randomWallet
          val bal = wallet.w.balance().get.value
          log.info(s"User: ${wallet.w.walletOwner} -> balance: ${bal}")
          if (bal > 0) {
            Try(wallet.payAsync(Payment(wallet.w.walletOwner, 1, 5))) match {
              case Success(f) =>
                inProgress += 1
                val now = System.currentTimeMillis()
                f andThen { case Success(_) =>
                  totalTxsProcessed.incrementAndGet()
                  totalTimeProcessing.addAndGet(System.currentTimeMillis() - now)
                }
                f pipeTo self
              case Failure(e) =>
                log.debug(e.toString)
                log.info("Problem, try again")
                if (!fired) {
                  fired = true
                  scheduleFireTxs(1)
                }
            }
          } else if (!fired) {
            fired = true
            scheduleFireTxs(1)
          }
      }

    case FireTxs =>
      schedFire = None
      if(inProgress % 20 == 0) {
        log.info(s"$inProgress in progress, wait a few.... (total ${totalTxsProcessed.get()}) average latency = ${calculateLatency()} ms")
      }
      scheduleFireTxs(1)

  }

  override def receive: Receive = {

    case _ : Synchronized =>
      context become registerMissingUSers
      self ! Register(missingUsers)

  }

  private def registerMissingUSers: Receive = {
    case Register(Seq()) =>
      val total = testUsersWallets.map(_.w.balance().get.value).sum
      val number = testUsersWallets.size
      log.info(s"$number wallets have $total in total")
      //context become sendTransactions
      //self ! FireTxs

    case Register(user +: rest) => Try {
      log.debug(s"Register $user, rest is $rest")
      val nodeIdentity = nodeIdentityManager(user, NodeIdTag.defaultTag, "passworD10!").await()

      val publicKey = nodeIdentity.publicKey.toBase64Str

      Try(new Resty().text(
        s"${claimHttpUrl}console/command?1=claim&2=${user}&3=${publicKey}")) match {

        case Success(tr) if tr.toString.contains("ok") =>

          val wallet = buildUnlockedWallet(nodeIdentity)
          messageEventBus publish TrackWallet(wallet.w, wallet.w.amount(0))

        /*fund(nodeIdentity.id) match {
          case Success(_) =>
            testUsersWallets =  wallet +: testUsersWallets.filter(_.w.walletOwner != wallet.w.walletOwner)

          case Failure(e) =>
            log.warning(s"Failed to fund user $user ${e.toString}")
        }*/

        case Success(s) =>
          log.debug(s.toString)
          hideUserKeyFile(user)

        case Failure(e) =>
          log.warning(e.toString)
          hideUserKeyFile(user)
      }

    } recover {
      case e => log.warning(s"Failed to register user $user ${e.toString}")
    }

      self ! Register(rest)
  }


  private def fund(nodeIdentity: UniqueNodeIdentifier): Try[Unit] = {
    val amount = 100
    Try(new Resty().text(s"${claimHttpUrl}claim/debit?to=${nodeIdentity}&amount=$amount"))
  }

  private def hideUserKeyFile(user: String) = users.deleteUser(user).await()

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy