Skip to content

Commit

Permalink
feat: build simulator
Browse files Browse the repository at this point in the history
  • Loading branch information
vaibhavchellani committed Feb 4, 2021
1 parent d2ce7e7 commit 11818e3
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 4 deletions.
1 change: 1 addition & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

const (
FlagFromID = "from"
FlagUsers = "users"
FlagToPubkey = "to-pub"
FlagToID = "to"
FlagStateID = "id"
Expand Down
3 changes: 2 additions & 1 deletion cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func main() {
rootCmd.AddCommand(configureGenesisCmd())
rootCmd.AddCommand(startCmd())
rootCmd.AddCommand(startRestServerCmd())
rootCmd.AddCommand(startSimulator())
rootCmd.AddCommand(sendTransferTx())
rootCmd.AddCommand(sendCreate2TransferTx())
rootCmd.AddCommand(createDatabase())
Expand Down Expand Up @@ -87,7 +88,7 @@ func createUsers() *cobra.Command {
Use: "create-users",
Short: "Create users to be used in simulations",
RunE: func(cmd *cobra.Command, args []string) error {
userCount, err := cmd.Flags().GetInt(FlagDatabaseName)
userCount, err := cmd.Flags().GetInt(FlagUserCount)
if err != nil {
return err
}
Expand Down
71 changes: 71 additions & 0 deletions cmd/simulator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package main

import (
"encoding/json"
"io/ioutil"
"log"
"os"
"os/signal"
"runtime"

"github.com/BOPR/config"
"github.com/BOPR/simulator"
"github.com/spf13/cobra"
)

// startSimulator starts the daemon
func startSimulator() *cobra.Command {
return &cobra.Command{
Use: "simulate",
Short: "Starts hubble simulator",
RunE: func(cmd *cobra.Command, args []string) error {
jsonFile, err := os.Open("users.json")
if err != nil {
return err
}
// defer the closing of our jsonFile so that we can parse it later on
defer jsonFile.Close()

byteValue, _ := ioutil.ReadAll(jsonFile)

// we initialize our Users array
var users simulator.UserList

// we unmarshal our byteArray which contains our
// jsonFile's content into 'users' which we defined above
err = json.Unmarshal(byteValue, &users)
if err != nil {
return err
}

cfg, err := config.ParseConfig()
if err != nil {
return err
}

sim := simulator.NewSimulator(cfg, users)

// go routine to catch signal
catchSignal := make(chan os.Signal, 1)
signal.Notify(catchSignal, os.Interrupt)

go func() {
// sig is a ^C, handle it
for range catchSignal {
if err := sim.Stop(); err != nil {
log.Fatalln("Unable to stop simulator", "error", err)
}
// exit
os.Exit(1)
}
}()

if err := sim.Start(); err != nil {
return err
}

runtime.Goexit()
return nil
},
}
}
152 changes: 149 additions & 3 deletions simulator/simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package simulator

import (
"context"
"encoding/hex"
"errors"
"fmt"
"strconv"
"sync"
"time"

Expand All @@ -16,8 +19,19 @@ import (
var (
SimulatorService = "simulator"
ErrIncorrectTxCount = errors.New("inaccurate number of transactions")
DefaultAmount = 1
DefaultFee = 1
)

type UserList struct {
Users []User `json:"users"`
}

type User struct {
PublicKey string `json:"pubkey"`
PrivKey string `json:"privkey"`
}

type Simulator struct {
// Base service
core.BaseService
Expand All @@ -36,30 +50,78 @@ type Simulator struct {

// wait group
wg sync.WaitGroup

users UserList

states map[uint64]User
}

// NewSimulator returns new simulator object
func NewSimulator(cfg config.Configuration) *Simulator {
// create logger
func NewSimulator(cfg config.Configuration, users UserList) *Simulator {
logger := log.Logger.With("module", SimulatorService)
simulator := &Simulator{}
simulator.BaseService = *core.NewBaseService(logger, SimulatorService, simulator)

// create DB obj
DB, err := db.NewDB(cfg)
if err != nil {
panic(err)
}

// create bazooka obj
bz, err := bazooka.NewPreLoadedBazooka(cfg)
if err != nil {
panic(err)
}

if len(users.Users) < 2 {
panic(errors.New("simulator needs 2 or more than 2 state to function"))
}

simulator.Bazooka = bz
simulator.DB = DB
simulator.cfg = cfg

simulator.users = users
simulator.states = make(map[uint64]User)
err = simulator.ParseUserList()
if err != nil {
panic(err)
}
return simulator
}

// ParseUserList parse user list to states
func (s *Simulator) ParseUserList() error {
states := make(map[uint64]User)
// var token uint64
for _, u := range s.users.Users {
pubkeyBz, err := hex.DecodeString(u.PublicKey)
if err != nil {
return err
}
account, err := s.DB.GetAccountByPubkey(pubkeyBz)
if err != nil {
return err
}
statesList, err := s.DB.GetStateByAccID(account.AccountID)
if err != nil {
return err
}
for _, statesInList := range statesList {
if len(statesInList.Path) == 0 {
continue
}
stateID, err := strconv.Atoi(statesInList.Path)
if err != nil {
return err
}
states[uint64(stateID)] = u
}
}
s.states = states
return nil
}

// OnStart starts new block subscription
func (s *Simulator) OnStart() error {
err := s.BaseService.OnStart() // Always call the overridden method.
Expand All @@ -68,6 +130,7 @@ func (s *Simulator) OnStart() error {
}
ctx, cancelAggregating := context.WithCancel(context.Background())
s.cancelSimulator = cancelAggregating

// start polling for checkpoint in buffer
go s.startAggregating(ctx, s.cfg.PollingInterval)
return nil
Expand Down Expand Up @@ -97,6 +160,89 @@ func (s *Simulator) startAggregating(ctx context.Context, interval time.Duration
}
}

// AttemptTransfer attempts to make a transfer using one of the states
func (s *Simulator) AttemptTransfer() {
defer s.wg.Done()
// pick the first state in the states list
for stateID, user := range s.states {
state, err := s.DB.GetStateByIndex(stateID)
if err != nil {
return
}

_, bal, _, token, err := s.Bazooka.DecodeState(state.Data)
if err != nil {
return
}

// if balance is >1 use this account to make a transfer tx, else move on
if bal.Int64() > 1 {
// fetch pending nonce
pendingNonce, err := s.DB.GetPendingNonce(stateID)
if err != nil {
return
}

receiverStateID, err := s.findReceiver(stateID)
if err != nil {
return
}

txData, err := s.Bazooka.EncodeTransferTx(int64(stateID), int64(receiverStateID), int64(DefaultFee), int64(pendingNonce+1), int64(DefaultAmount), core.TX_TRANSFER_TYPE)
if err != nil {
return
}

tx, err := core.NewPendingTx(txData, nil, stateID, pendingNonce+1, uint64(DefaultFee), token.Uint64(), core.TX_TRANSFER_TYPE)
if err != nil {
return
}
privBz, err := hex.DecodeString(user.PrivKey)
if err != nil {
return
}
pubkeyBz, err := hex.DecodeString(user.PublicKey)
if err != nil {
return
}

if err = signAndBroadcast(&s.Bazooka, &s.DB, tx, privBz, pubkeyBz); err != nil {
return
}

return
}
}
}

func (s *Simulator) findReceiver(senderStateID uint64) (receiverStateID uint64, err error) {
for stateID := range s.states {
if stateID != senderStateID {
return stateID, nil
}
}
return 0, errors.New("no receiver state found")
}

func signAndBroadcast(b *bazooka.Bazooka, DBI *db.DB, tx core.Tx, priv, pub []byte) (err error) {
txBytes, err := bazooka.GetSignBytes(*b, &tx)
if err != nil {
return
}

err = tx.SignTx(priv, pub, txBytes)
if err != nil {
return
}
err = tx.AssignHash()
if err != nil {
return
}

fmt.Println("Sending new tx", tx.String())
err = DBI.InsertTx(&tx)
if err != nil {
return err
}
return nil
}

0 comments on commit 11818e3

Please sign in to comment.