Skip to content

Commit

Permalink
[Feature] Adding loadbot ERC20 and ERC721 token transfer modes (0xPol…
Browse files Browse the repository at this point in the history
…ygon#425)

* Added erc20 and erc721 contract bytecode and ABI
* Added new flag option for erc20 and erc721 transfers
* Added contract deployment metrics to the output
* Added support for ERC20 token transfer
* Added percentual gas usage metric
* Added percentual average blocks utilization metric
* Added support for ERC721 token minting
* Set http `max-conns` default to very high value
* Added `max-wait` flag for custom time to wait for receipts
* Increased default max wait time for receipts to 2 min.
* Gas metrics (block gas usage) are now calculated async

Co-authored-by: dbrajovic <dbrajovic3@gmail.com>
  • Loading branch information
ZeljkoBenovic and dbrajovic authored Mar 30, 2022
1 parent 3543e50 commit f59d3ca
Show file tree
Hide file tree
Showing 18 changed files with 1,345 additions and 95 deletions.
637 changes: 637 additions & 0 deletions command/loadbot/abis.go

Large diffs are not rendered by default.

100 changes: 100 additions & 0 deletions command/loadbot/deploy_contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package loadbot

import (
"context"
"fmt"
"sync/atomic"
"time"

"github.com/0xPolygon/polygon-edge/command/loadbot/generator"
"github.com/0xPolygon/polygon-edge/helper/tests"
txpoolOp "github.com/0xPolygon/polygon-edge/txpool/proto"
"github.com/0xPolygon/polygon-edge/types"

"github.com/umbracle/go-web3/jsonrpc"
)

func (l *Loadbot) deployContract(
grpcClient txpoolOp.TxnPoolOperatorClient,
jsonClient *jsonrpc.Client,
receiptTimeout time.Duration) error {
start := time.Now()

_, ok := l.generator.(generator.ContractTxnGenerator)
if !ok {
return fmt.Errorf("invalid generator type, it needs to be a generator.ContractTxnGenerator interface")
}

// deploy SC
txHash, err := l.executeTxn(grpcClient)
if err != nil {
//nolint:forcetypeassert
l.generator.(generator.ContractTxnGenerator).MarkFailedContractTxn(&generator.FailedContractTxnInfo{
TxHash: txHash.String(),
Error: &generator.TxnError{
Error: err,
ErrorType: generator.AddErrorType,
},
})
atomic.AddUint64(&l.metrics.ContractMetrics.FailedContractTransactionsCount, 1)

return fmt.Errorf("could not execute transaction, %w", err)
}

// set timeout
ctx, cancel := context.WithTimeout(context.Background(), receiptTimeout)
defer cancel()

// and wait for receipt
receipt, err := tests.WaitForReceipt(ctx, jsonClient.Eth(), txHash)

if err != nil {
//nolint:forcetypeassert
l.generator.(generator.ContractTxnGenerator).MarkFailedContractTxn(&generator.FailedContractTxnInfo{
TxHash: txHash.String(),
Error: &generator.TxnError{
Error: err,
ErrorType: generator.ReceiptErrorType,
},
})
atomic.AddUint64(&l.metrics.ContractMetrics.FailedContractTransactionsCount, 1)

return fmt.Errorf("could not get the receipt, %w", err)
}

end := time.Now()
// initialize gas metrics map with block nuber as index
l.metrics.ContractMetrics.ContractGasMetrics.Blocks[receipt.BlockNumber] = GasMetrics{}
// fetch contract address
l.metrics.ContractMetrics.ContractAddress = receipt.ContractAddress
// set contract address in order to get new example txn and gas estimate
//nolint:forcetypeassert
l.generator.(generator.ContractTxnGenerator).SetContractAddress(types.StringToAddress(
receipt.ContractAddress.String(),
))

// we're done with SC deployment
// we defined SC address and
// now get new gas estimates for CS token transfers
if err := l.updateGasEstimate(jsonClient); err != nil {
return fmt.Errorf("unable to get gas estimate, %w", err)
}

// record contract deployment metrics
l.metrics.ContractMetrics.ContractDeploymentDuration.reportTurnAroundTime(
txHash,
&metadata{
turnAroundTime: end.Sub(start),
blockNumber: receipt.BlockNumber,
},
)
// calculate contract deployment metrics
if err := l.calculateGasMetrics(jsonClient, l.metrics.ContractMetrics.ContractGasMetrics); err != nil {
return fmt.Errorf("unable to calculate contract block gas metrics: %w", err)
}

l.metrics.ContractMetrics.ContractDeploymentDuration.calcTurnAroundMetrics()
l.metrics.ContractMetrics.ContractDeploymentDuration.TotalExecTime = end.Sub(start)

return nil
}
120 changes: 87 additions & 33 deletions command/loadbot/execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@ import (
"context"
"crypto/ecdsa"
"fmt"
"math/big"
"sync"
"sync/atomic"
"time"

"github.com/0xPolygon/polygon-edge/command/loadbot/generator"
"github.com/0xPolygon/polygon-edge/helper/tests"
txpoolOp "github.com/0xPolygon/polygon-edge/txpool/proto"
"github.com/golang/protobuf/ptypes/any"
"github.com/umbracle/go-web3/jsonrpc"
"math/big"
"sync"
"sync/atomic"
"time"

"github.com/0xPolygon/polygon-edge/types"
"github.com/umbracle/go-web3"
)

const (
maxReceiptWait = 5 * time.Minute
minReceiptWait = 30 * time.Second
minReceiptWait = 1 * time.Minute

defaultFastestTurnAround = time.Hour * 24
defaultSlowestTurnAround = time.Duration(0)
Expand All @@ -33,6 +34,8 @@ type Mode string
const (
transfer Mode = "transfer"
deploy Mode = "deploy"
erc20 Mode = "erc20"
erc721 Mode = "erc721"
)

type Account struct {
Expand All @@ -54,6 +57,8 @@ type Configuration struct {
GasPrice *big.Int
GasLimit *big.Int
ContractArtifact *generator.ContractArtifact
ConstructorArgs []byte // smart contract constructor args
MaxWait uint64 // max wait time for receipts in minutes
}

type metadata struct {
Expand All @@ -64,10 +69,30 @@ type metadata struct {
blockNumber uint64
}

type GasMetrics struct {
GasUsed uint64
GasLimit uint64
Utilization float64
}

type BlockGasMetrics struct {
Blocks map[uint64]GasMetrics
BlockGasMutex *sync.Mutex
}

type ContractMetricsData struct {
FailedContractTransactionsCount uint64
ContractDeploymentDuration ExecDuration
ContractAddress web3.Address
ContractGasMetrics *BlockGasMetrics
}

type Metrics struct {
TotalTransactionsSentCount uint64
FailedTransactionsCount uint64
TransactionDuration ExecDuration
ContractMetrics ContractMetricsData
GasMetrics *BlockGasMetrics
}

type Loadbot struct {
Expand All @@ -85,6 +110,19 @@ func NewLoadbot(cfg *Configuration) *Loadbot {
TransactionDuration: ExecDuration{
blockTransactions: make(map[uint64]uint64),
},
ContractMetrics: ContractMetricsData{
ContractDeploymentDuration: ExecDuration{
blockTransactions: make(map[uint64]uint64),
},
ContractGasMetrics: &BlockGasMetrics{
Blocks: make(map[uint64]GasMetrics),
BlockGasMutex: &sync.Mutex{},
},
},
GasMetrics: &BlockGasMetrics{
Blocks: make(map[uint64]GasMetrics),
BlockGasMutex: &sync.Mutex{},
},
},
}
}
Expand Down Expand Up @@ -135,60 +173,70 @@ func (l *Loadbot) Run() error {

// Set up the transaction generator
generatorParams := &generator.GeneratorParams{
Nonce: nonce,
ChainID: l.cfg.ChainID,
SenderAddress: sender.Address,
SenderKey: sender.PrivateKey,
GasPrice: gasPrice,
Value: l.cfg.Value,
Nonce: nonce,
ChainID: l.cfg.ChainID,
SenderAddress: sender.Address,
RecieverAddress: l.cfg.Receiver,
SenderKey: sender.PrivateKey,
GasPrice: gasPrice,
Value: l.cfg.Value,
ContractArtifact: l.cfg.ContractArtifact,
ConstructorArgs: l.cfg.ConstructorArgs,
}

var (
txnGenerator generator.TransactionGenerator
genErr error = nil
txnGenerator generator.TransactionGenerator
tokenTxnGenerator generator.ContractTxnGenerator
genErr error
)

switch l.cfg.GeneratorMode {
case transfer:
txnGenerator, genErr = generator.NewTransferGenerator(generatorParams)
case deploy:
txnGenerator, genErr = generator.NewDeployGenerator(generatorParams)
case erc20:
tokenTxnGenerator, genErr = generator.NewERC20Generator(generatorParams)
case erc721:
tokenTxnGenerator, genErr = generator.NewERC721Generator(generatorParams)
}

if genErr != nil {
return fmt.Errorf("unable to start generator, %w", genErr)
}

l.generator = txnGenerator

// Get the gas estimate
exampleTxn, err := l.generator.GetExampleTransaction()
if err != nil {
return fmt.Errorf("unable to get example transaction, %w", err)
switch l.cfg.GeneratorMode {
case erc20, erc721:
l.generator = tokenTxnGenerator
default:
l.generator = txnGenerator
}

gasLimit := l.cfg.GasLimit
if gasLimit == nil {
// No gas limit specified, query the network for an estimation
gasEstimate, estimateErr := estimateGas(jsonClient, exampleTxn)
if estimateErr != nil {
return fmt.Errorf("unable to get gas estimate, %w", err)
}

gasLimit = new(big.Int).SetUint64(gasEstimate)
if err := l.updateGasEstimate(jsonClient); err != nil {
return fmt.Errorf("could not update gas estimate, %w", err)
}

l.generator.SetGasEstimate(gasLimit.Uint64())

ticker := time.NewTicker(1 * time.Second / time.Duration(l.cfg.TPS))
defer ticker.Stop()

var wg sync.WaitGroup

receiptTimeout := calcMaxTimeout(l.cfg.Count, l.cfg.TPS)
var receiptTimeout time.Duration
// if max-wait flag is not set it will be calculated dynamically
if l.cfg.MaxWait == 0 {
receiptTimeout = calcMaxTimeout(l.cfg.Count, l.cfg.TPS)
} else {
receiptTimeout = time.Duration(l.cfg.MaxWait) * time.Minute
}

startTime := time.Now()

if l.isTokenTransferMode() {
if err := l.deployContract(grpcClient, jsonClient, receiptTimeout); err != nil {
return fmt.Errorf("unable to deploy smart contract, %w", err)
}
}

var wg sync.WaitGroup

for i := uint64(0); i < l.cfg.Count; i++ {
<-ticker.C

Expand Down Expand Up @@ -236,6 +284,8 @@ func (l *Loadbot) Run() error {
return
}

l.initGasMetricsBlocksMap(receipt.BlockNumber)

// Stop the performance timer
end := time.Now()

Expand All @@ -253,6 +303,10 @@ func (l *Loadbot) Run() error {

endTime := time.Now()

if err := l.calculateGasMetrics(jsonClient, l.metrics.GasMetrics); err != nil {
return fmt.Errorf("unable to calculate block gas metrics: %w", err)
}

// Calculate the turn around metrics now that the loadbot is done
l.metrics.TransactionDuration.calcTurnAroundMetrics()
l.metrics.TransactionDuration.TotalExecTime = endTime.Sub(startTime)
Expand Down
3 changes: 2 additions & 1 deletion command/loadbot/generator/base.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package generator

import (
"github.com/0xPolygon/polygon-edge/crypto"
"sync"

"github.com/0xPolygon/polygon-edge/crypto"
)

type BaseGenerator struct {
Expand Down
Loading

0 comments on commit f59d3ca

Please sign in to comment.