diff --git a/.changelog/2773.feature.md b/.changelog/2773.feature.md new file mode 100644 index 00000000000..de191d34363 --- /dev/null +++ b/.changelog/2773.feature.md @@ -0,0 +1 @@ +go/txsource/transfer: inlcude burn transactions in transfer workload diff --git a/go/oasis-node/cmd/debug/txsource/workload/delegation.go b/go/oasis-node/cmd/debug/txsource/workload/delegation.go index e49f5b18a8e..324df38fe6c 100644 --- a/go/oasis-node/cmd/debug/txsource/workload/delegation.go +++ b/go/oasis-node/cmd/debug/txsource/workload/delegation.go @@ -80,42 +80,14 @@ func (d *delegation) doEscrowTx(ctx context.Context, rng *rand.Rand, cnsc consen tx := staking.NewAddEscrowTx(d.accounts[selectedIdx].reckonedNonce, &transaction.Fee{}, escrow) d.accounts[selectedIdx].reckonedNonce++ - - // Estimate gas. - gas, err := cnsc.EstimateGas(ctx, &consensus.EstimateGasRequest{ - Caller: d.accounts[selectedIdx].signer.Public(), - Transaction: tx, - }) - if err != nil { - return fmt.Errorf("failed to estimate gas: %w", err) - } - tx.Fee.Gas = gas - feeAmount := int64(gas) * gasPrice - if err = tx.Fee.Amount.FromInt64(feeAmount); err != nil { - return fmt.Errorf("fee amount from int64: %w", err) - } - - // Fund account to cover Escrow fees. // We only do one escrow per account at a time, so `delegateAmount` // funds (that are Escrowed) should already be in the balance. - fundAmount := int64(gas) * gasPrice // transaction costs - if err = transferFunds(ctx, d.logger, cnsc, d.fundingAccount, d.accounts[selectedIdx].signer.Public(), fundAmount); err != nil { - return fmt.Errorf("account funding failure: %w", err) - } - - // Sign transaction. - signedTx, err := transaction.Sign(d.accounts[selectedIdx].signer, tx) - if err != nil { - return fmt.Errorf("transaction.Sign: %w", err) - } - d.logger.Debug("submitting escrow transaction", - "from", d.accounts[selectedIdx].signer.Public(), - "to", d.accounts[selectedIdx].delegatedTo, - ) - - // Submit transaction. - if err = cnsc.SubmitTx(ctx, signedTx); err != nil { - return fmt.Errorf("cnsc.SubmitTx: %w", err) + if err := fundSignAndSubmitTx(ctx, d.logger, cnsc, d.accounts[selectedIdx].signer, tx, d.fundingAccount); err != nil { + d.logger.Error("failed to sign and submit escrow transaction", + "tx", tx, + "signer", d.accounts[selectedIdx].signer, + ) + return fmt.Errorf("failed to sign and submit tx: %w", err) } return nil @@ -166,40 +138,12 @@ func (d *delegation) doReclaimEscrowTx(ctx context.Context, rng *rand.Rand, cnsc } tx := staking.NewReclaimEscrowTx(d.accounts[selectedIdx].reckonedNonce, &transaction.Fee{}, reclaim) d.accounts[selectedIdx].reckonedNonce++ - - // Estimate gas. - gas, err := cnsc.EstimateGas(ctx, &consensus.EstimateGasRequest{ - Caller: d.accounts[selectedIdx].signer.Public(), - Transaction: tx, - }) - if err != nil { - return fmt.Errorf("failed to estimate gas: %w", err) - } - tx.Fee.Gas = gas - feeAmount := int64(gas) * gasPrice - if err = tx.Fee.Amount.FromInt64(feeAmount); err != nil { - return fmt.Errorf("fee amount from int64: %w", err) - } - - // Fund account to cover reclaim escrow fees. - fundAmount := int64(gas) * gasPrice // transaction costs - if err = transferFunds(ctx, d.logger, cnsc, d.fundingAccount, d.accounts[selectedIdx].signer.Public(), fundAmount); err != nil { - return fmt.Errorf("account funding failure: %w", err) - } - - signedTx, err := transaction.Sign(d.accounts[selectedIdx].signer, tx) - if err != nil { - return fmt.Errorf("transaction.Sign: %w", err) - } - - d.logger.Debug("submitting reclaim escrow transaction", - "reclaim_from", d.accounts[selectedIdx].delegatedTo, - "account", d.accounts[selectedIdx].signer.Public(), - ) - - // Submit transaction. - if err = cnsc.SubmitTx(ctx, signedTx); err != nil { - return fmt.Errorf("cnsc.SubmitTx: %w", err) + if err = fundSignAndSubmitTx(ctx, d.logger, cnsc, d.accounts[selectedIdx].signer, tx, d.fundingAccount); err != nil { + d.logger.Error("failed to sign and submit reclaim escrow transaction", + "tx", tx, + "signer", d.accounts[selectedIdx].signer, + ) + return fmt.Errorf("failed to sign and submit tx: %w", err) } // Query debonding end epoch for the account. diff --git a/go/oasis-node/cmd/debug/txsource/workload/registration.go b/go/oasis-node/cmd/debug/txsource/workload/registration.go index 4781b96ad23..1596121ca30 100644 --- a/go/oasis-node/cmd/debug/txsource/workload/registration.go +++ b/go/oasis-node/cmd/debug/txsource/workload/registration.go @@ -243,30 +243,12 @@ func (r *registration) Run( // nolint: gocyclo // Estimate gas and submit transaction. tx := registry.NewRegisterEntityTx(entityAccs[i].reckonedNonce, &transaction.Fee{}, sigEntity) entityAccs[i].reckonedNonce++ - gas, err := cnsc.EstimateGas(ctx, &consensus.EstimateGasRequest{ - Caller: entityAccs[i].signer.Public(), - Transaction: tx, - }) - if err != nil { - return fmt.Errorf("failed to estimate gas: %w", err) - } - tx.Fee.Gas = gas - feeAmount := int64(gas) * gasPrice - if err = tx.Fee.Amount.FromInt64(feeAmount); err != nil { - return fmt.Errorf("fee amount from int64: %w", err) - } - - // Fund entity account to cover registration fees. - if err = transferFunds(ctx, registrationLogger, cnsc, fundingAccount, entityAccs[i].signer.Public(), feeAmount); err != nil { - return fmt.Errorf("account funding failure: %w", err) - } - - signedTx, err := transaction.Sign(entityAccs[i].signer, tx) - if err != nil { - return fmt.Errorf("transaction.Sign: %w", err) - } - if err = cnsc.SubmitTx(ctx, signedTx); err != nil { - return fmt.Errorf("cnsc.SubmitTx: %w", err) + if err := fundSignAndSubmitTx(ctx, registrationLogger, cnsc, entityAccs[i].signer, tx, fundingAccount); err != nil { + registrationLogger.Error("failed to sign and submit regsiter entity transaction", + "tx", tx, + "signer", entityAccs[i].signer, + ) + return fmt.Errorf("failed to sign and submit tx: %w", err) } // Register runtime. @@ -280,31 +262,13 @@ func (r *registration) Run( // nolint: gocyclo } tx := registry.NewRegisterRuntimeTx(entityAccs[i].reckonedNonce, &transaction.Fee{}, sigRuntime) - gas, err := cnsc.EstimateGas(ctx, &consensus.EstimateGasRequest{ - Caller: entityAccs[i].signer.Public(), - Transaction: tx, - }) - if err != nil { - return fmt.Errorf("failed to estimate gas: %w", err) - } - tx.Fee.Gas = gas - feeAmount := int64(gas) * gasPrice - if err = tx.Fee.Amount.FromInt64(feeAmount); err != nil { - return fmt.Errorf("fee amount from uint64: %w", err) - } - - // Fund entity account to cover entity registration fees. - if err = transferFunds(ctx, registrationLogger, cnsc, fundingAccount, entityAccs[i].signer.Public(), feeAmount); err != nil { - return fmt.Errorf("account funding failure: %w", err) - } - entityAccs[i].reckonedNonce++ - signedTx, err := transaction.Sign(entityAccs[i].signer, tx) - if err != nil { - return fmt.Errorf("transaction.Sign: %w", err) - } - if err = cnsc.SubmitTx(ctx, signedTx); err != nil { - return fmt.Errorf("cnsc.SubmitTx: %w", err) + if err := fundSignAndSubmitTx(ctx, registrationLogger, cnsc, entityAccs[i].signer, tx, fundingAccount); err != nil { + registrationLogger.Error("failed to sign and submit register runtime transaction", + "tx", tx, + "signer", entityAccs[i].signer, + ) + return fmt.Errorf("failed to sign and submit tx: %w", err) } } } @@ -331,35 +295,15 @@ func (r *registration) Run( // nolint: gocyclo // Register node. tx := registry.NewRegisterNodeTx(selectedNode.reckonedNonce, &transaction.Fee{}, sigNode) - gas, err := cnsc.EstimateGas(ctx, &consensus.EstimateGasRequest{ - Caller: selectedNode.id.NodeSigner.Public(), - Transaction: tx, - }) - if err != nil { - return fmt.Errorf("failed to estimate gas: %w", err) - } - tx.Fee.Gas = gas - feeAmount := gas * gasPrice - if err = tx.Fee.Amount.FromUint64(uint64(feeAmount)); err != nil { - return fmt.Errorf("fee amount from uint64: %w", err) - } - - // Fund node account to cover registration fees. - if err = transferFunds(ctx, registrationLogger, cnsc, fundingAccount, selectedNode.id.NodeSigner.Public(), int64(feeAmount)); err != nil { - return fmt.Errorf("account funding failure: %w", err) - } selectedNode.reckonedNonce++ - - signedTx, err := transaction.Sign(selectedNode.id.NodeSigner, tx) - if err != nil { - return fmt.Errorf("transaction.Sign: %w", err) - } - registrationLogger.Debug("submitting registration", - "node", selectedNode.nodeDesc, - ) - if err = cnsc.SubmitTx(ctx, signedTx); err != nil { - return fmt.Errorf("cnsc.SubmitTx: %w", err) + if err := fundSignAndSubmitTx(ctx, registrationLogger, cnsc, selectedNode.id.NodeSigner, tx, fundingAccount); err != nil { + registrationLogger.Error("failed to sign and submit register node transaction", + "tx", tx, + "signer", selectedNode.id.NodeSigner, + ) + return fmt.Errorf("failed to sign and submit tx: %w", err) } + registrationLogger.Debug("registered node", "node", selectedNode.nodeDesc, ) diff --git a/go/oasis-node/cmd/debug/txsource/workload/transfer.go b/go/oasis-node/cmd/debug/txsource/workload/transfer.go index 43820845de8..c04417d96d3 100644 --- a/go/oasis-node/cmd/debug/txsource/workload/transfer.go +++ b/go/oasis-node/cmd/debug/txsource/workload/transfer.go @@ -19,19 +19,94 @@ import ( const ( // NameTransfer is the name of the transfer workload. + // + // Transfer workload continiously submits transfer and burn transactions. NameTransfer = "transfer" - transferNumAccounts = 10 - transferAmount = 1 - transferFundInterval = 10 - transferGasCost = 10 + transferNumAccounts = 10 + transferAmount = 1 + transferBurnAmount = 10 ) -var transferLogger = logging.GetLogger("cmd/txsource/workload/transfer") +type transfer struct { + logger *logging.Logger -type transfer struct{} + consensus consensus.ClientBackend -func (transfer) Run( + accounts []struct { + signer signature.Signer + reckonedNonce uint64 + reckonedBalance quantity.Quantity + } + fundingAccount signature.Signer +} + +func (t *transfer) doTransferTx(ctx context.Context, fromIdx int, toIdx int) error { + from := &t.accounts[fromIdx] + to := &t.accounts[toIdx] + + transfer := staking.Transfer{To: to.signer.Public()} + if err := transfer.Tokens.FromInt64(transferAmount); err != nil { + return fmt.Errorf("transfer tokens FromInt64 %d: %w", transferAmount, err) + } + tx := staking.NewTransferTx(from.reckonedNonce, &transaction.Fee{}, &transfer) + from.reckonedNonce++ + + t.logger.Debug("Transfering tokens", + "from", from, + "to", to, + "amount", transferAmount, + ) + if err := fundSignAndSubmitTx(ctx, t.logger, t.consensus, from.signer, tx, t.fundingAccount); err != nil { + t.logger.Error("failed to sign and submit transfer transaction", + "tx", tx, + "signer", from.signer, + ) + return fmt.Errorf("failed to sign and submit tx: %w", err) + } + + // Update reckoned state. + if err := from.reckonedBalance.Sub(&transfer.Tokens); err != nil { + return fmt.Errorf("from reckoned balance %v Sub transfer tokens %v: %w", from.reckonedBalance, transfer.Tokens, err) + } + if err := to.reckonedBalance.Add(&transfer.Tokens); err != nil { + return fmt.Errorf("to reckoned balance %v Add transfer tokens %v: %w", to.reckonedBalance, transfer.Tokens, err) + } + + return nil +} + +func (t *transfer) doBurnTx(ctx context.Context, idx int) error { + acc := &t.accounts[idx] + + // Fund account with tokens that will be burned. + if err := transferFunds(ctx, t.logger, t.consensus, t.fundingAccount, acc.signer.Public(), int64(transferBurnAmount)); err != nil { + return fmt.Errorf("workload/transfer: account funding failure: %w", err) + } + + burn := staking.Burn{} + if err := burn.Tokens.FromInt64(transferBurnAmount); err != nil { + return fmt.Errorf("burn tokens FromInt64 %d: %w", transferBurnAmount, err) + } + tx := staking.NewBurnTx(acc.reckonedNonce, &transaction.Fee{}, &burn) + acc.reckonedNonce++ + + t.logger.Debug("Burning tokens", + "account", acc, + "amount", transferBurnAmount, + ) + if err := fundSignAndSubmitTx(ctx, t.logger, t.consensus, acc.signer, tx, t.fundingAccount); err != nil { + t.logger.Error("failed to sign and submit transfer transaction", + "tx", tx, + "signer", acc.signer, + ) + return fmt.Errorf("failed to sign and submit tx: %w", err) + } + + return nil +} + +func (t *transfer) Run( gracefulExit context.Context, rng *rand.Rand, conn *grpc.ClientConn, @@ -42,16 +117,20 @@ func (transfer) Run( var err error ctx := context.Background() - fac := memorySigner.NewFactory() - // Load all the keys up front. Like, how annoyed would you be if down the line one of them turned out to be - // corrupted or something, ya know? - accounts := make([]struct { + t.logger = logging.GetLogger("cmd/txsource/workload/transfer") + t.consensus = cnsc + t.accounts = make([]struct { signer signature.Signer reckonedNonce uint64 reckonedBalance quantity.Quantity }, transferNumAccounts) - for i := range accounts { - accounts[i].signer, err = fac.Generate(signature.SignerEntity, rng) + t.fundingAccount = fundingAccount + + fac := memorySigner.NewFactory() + // Load all the keys up front. Like, how annoyed would you be if down the line one of them turned out to be + // corrupted or something, ya know? + for i := range t.accounts { + t.accounts[i].signer, err = fac.Generate(signature.SignerEntity, rng) if err != nil { return fmt.Errorf("memory signer factory Generate account %d: %w", i, err) } @@ -59,105 +138,65 @@ func (transfer) Run( // Read all the account info up front. stakingClient := staking.NewStakingClient(conn) - for i := range accounts { - fundAmount := transferAmount*transferFundInterval + // funds for `transferFundInterval` transfers - transferGasCost*gasPrice*transferFundInterval // gas costs for `transferFundInterval` transfers - if err = transferFunds(ctx, transferLogger, cnsc, fundingAccount, accounts[i].signer.Public(), int64(fundAmount)); err != nil { + for i := range t.accounts { + fundAmount := transferAmount // funds for for a transfer + if err = transferFunds(ctx, t.logger, cnsc, t.fundingAccount, t.accounts[i].signer.Public(), int64(fundAmount)); err != nil { return fmt.Errorf("workload/transfer: account funding failure: %w", err) } var account *staking.Account account, err = stakingClient.AccountInfo(ctx, &staking.OwnerQuery{ Height: consensus.HeightLatest, - Owner: accounts[i].signer.Public(), + Owner: t.accounts[i].signer.Public(), }) if err != nil { - return fmt.Errorf("stakingClient.AccountInfo %s: %w", accounts[i].signer.Public(), err) + return fmt.Errorf("stakingClient.AccountInfo %s: %w", t.accounts[i].signer.Public(), err) } - transferLogger.Debug("account info", + t.logger.Debug("account info", "i", i, - "pub", accounts[i].signer.Public(), + "pub", t.accounts[i].signer.Public(), "info", account, ) - accounts[i].reckonedNonce = account.General.Nonce - accounts[i].reckonedBalance = account.General.Balance - } - - fee := transaction.Fee{ - Gas: transferGasCost, - } - if err = fee.Amount.FromInt64(transferGasCost * gasPrice); err != nil { - return fmt.Errorf("Fee amount error: %w", err) + t.accounts[i].reckonedNonce = account.General.Nonce + t.accounts[i].reckonedBalance = account.General.Balance } var minBalance quantity.Quantity if err = minBalance.FromInt64(transferAmount); err != nil { return fmt.Errorf("min balance FromInt64 %d: %w", transferAmount, err) } - if err = minBalance.Add(&fee.Amount); err != nil { - return fmt.Errorf("min balance %v Add fee amount %v: %w", minBalance, fee.Amount, err) - } for { - perm := rng.Perm(transferNumAccounts) - fromPermIdx := 0 - for ; fromPermIdx < transferNumAccounts; fromPermIdx++ { - if accounts[perm[fromPermIdx]].reckonedBalance.Cmp(&minBalance) >= 0 { - break - } - } - if fromPermIdx >= transferNumAccounts { - return fmt.Errorf("all accounts %#v have gone broke", accounts) - } - toPermIdx := (fromPermIdx + 1) % transferNumAccounts - from := &accounts[perm[fromPermIdx]] - to := &accounts[perm[toPermIdx]] - - transfer := staking.Transfer{ - To: to.signer.Public(), - } - if err = transfer.Tokens.FromInt64(transferAmount); err != nil { - return fmt.Errorf("transfer tokens FromInt64 %d: %w", transferAmount, err) - } - tx := staking.NewTransferTx(from.reckonedNonce, &fee, &transfer) - signedTx, err := transaction.Sign(from.signer, tx) - if err != nil { - return fmt.Errorf("transaction.Sign: %w", err) - } - transferLogger.Debug("submitting transfer", - "from", from.signer.Public(), - "to", to.signer.Public(), - ) - if err = cnsc.SubmitTx(ctx, signedTx); err != nil { - return fmt.Errorf("cnsc.SubmitTx: %w", err) - } - from.reckonedNonce++ - if err = from.reckonedBalance.Sub(&fee.Amount); err != nil { - return fmt.Errorf("from reckoned balance %v Sub fee amount %v: %w", from.reckonedBalance, fee.Amount, err) - } - if err = from.reckonedBalance.Sub(&transfer.Tokens); err != nil { - return fmt.Errorf("from reckoned balance %v Sub transfer tokens %v: %w", from.reckonedBalance, transfer.Tokens, err) - } - if err = to.reckonedBalance.Add(&transfer.Tokens); err != nil { - return fmt.Errorf("to reckoned balance %v Add transfer tokens %v: %w", to.reckonedBalance, transfer.Tokens, err) - } - if from.reckonedNonce%transferFundInterval == 0 { - // Re-fund account for next `transferFundInterval` transfers. - fundAmount := transferGasCost * gasPrice * transferFundInterval // gas costs for `transferFundInterval` transfers. - if err = transferFunds(ctx, parallelLogger, cnsc, fundingAccount, from.signer.Public(), int64(fundAmount)); err != nil { - return fmt.Errorf("account funding failure: %w", err) + // Decide between doing a transfer or burn tx. + switch rng.Intn(2) { + case 0: + // Transfer tx. + perm := rng.Perm(transferNumAccounts) + fromPermIdx := 0 + for ; fromPermIdx < transferNumAccounts; fromPermIdx++ { + if t.accounts[perm[fromPermIdx]].reckonedBalance.Cmp(&minBalance) >= 0 { + break + } } - var fundAmountQ quantity.Quantity - if err = fundAmountQ.FromInt64(int64(fundAmount)); err != nil { - return fmt.Errorf("fundAmountQ FromInt64(%d): %w", fundAmount, err) + if fromPermIdx >= transferNumAccounts { + return fmt.Errorf("all accounts %#v have gone broke", t.accounts) } - if err = from.reckonedBalance.Add(&fundAmountQ); err != nil { - return fmt.Errorf("to reckoned balance %v Add fund amount %v: %w", to.reckonedBalance, fundAmountQ, err) + toPermIdx := (fromPermIdx + 1) % transferNumAccounts + + if err = t.doTransferTx(ctx, perm[fromPermIdx], perm[toPermIdx]); err != nil { + return fmt.Errorf("transfer tx failure: %w", err) + } + case 1: + // Burn tx. + if err = t.doBurnTx(ctx, rng.Intn(transferNumAccounts)); err != nil { + return fmt.Errorf("burn tx failure: %w", err) } - } + default: + return fmt.Errorf("unimplemented") + } select { case <-gracefulExit.Done(): - transferLogger.Debug("time's up") + t.logger.Debug("time's up") return nil default: } diff --git a/go/oasis-node/cmd/debug/txsource/workload/workload.go b/go/oasis-node/cmd/debug/txsource/workload/workload.go index 4df4982937a..2198f240b2e 100644 --- a/go/oasis-node/cmd/debug/txsource/workload/workload.go +++ b/go/oasis-node/cmd/debug/txsource/workload/workload.go @@ -33,6 +33,60 @@ func FundAccountFromTestEntity(ctx context.Context, logger *logging.Logger, cnsc return transferFunds(ctx, logger, cnsc, testEntitySigner, to, fundAccountAmount) } +func fundSignAndSubmitTx(ctx context.Context, logger *logging.Logger, cnsc consensus.ClientBackend, caller signature.Signer, tx *transaction.Transaction, fundingAccount signature.Signer) error { + // Estimate gas needed if not set. + if tx.Fee.Gas == 0 { + gas, err := cnsc.EstimateGas(ctx, &consensus.EstimateGasRequest{ + Caller: caller.Public(), + Transaction: tx, + }) + if err != nil { + return fmt.Errorf("failed to estimate gas: %w", err) + } + tx.Fee.Gas = gas + } + + // Fund caller to cover transaction fees. + feeAmount := int64(tx.Fee.Gas) * gasPrice + if err := tx.Fee.Amount.FromInt64(feeAmount); err != nil { + return fmt.Errorf("fee amount from int64: %w", err) + } + if err := transferFunds(ctx, logger, cnsc, fundingAccount, caller.Public(), feeAmount); err != nil { + return fmt.Errorf("account funding failure: %w", err) + } + + // Sign tx. + signedTx, err := transaction.Sign(caller, tx) + if err != nil { + return fmt.Errorf("transaction.Sign: %w", err) + } + + logger.Debug("submitting transaction", + "tx", tx, + "signed_tx", signedTx, + "caller", caller, + ) + + // SubmitTx. + // Wait for a maximum of 60 seconds to submit transaction. + submitCtx, cancel := context.WithTimeout(ctx, 60*time.Second) + err = cnsc.SubmitTx(submitCtx, signedTx) + switch err { + case nil: + cancel() + return nil + default: + cancel() + logger.Error("failed to submit transaction", + "err", err, + "tx", tx, + "signed_tx", signedTx, + "caller", caller, + ) + return fmt.Errorf("cnsc.SubmitTx: %w", err) + } +} + // transferFunds transfer funds between accounts. func transferFunds(ctx context.Context, logger *logging.Logger, cnsc consensus.ClientBackend, from signature.Signer, to signature.PublicKey, transferAmount int64) error { sched := backoff.NewExponentialBackOff() @@ -127,5 +181,5 @@ var ByName = map[string]Workload{ NameParallel: parallel{}, NameRegistration: ®istration{}, NameRuntime: &runtime{}, - NameTransfer: transfer{}, + NameTransfer: &transfer{}, }