Skip to content

Commit

Permalink
Merge pull request #2799 from oasislabs/kostko/feature/tx-size-gas
Browse files Browse the repository at this point in the history
go/consensus: Introduce gas cost based on tx size
  • Loading branch information
kostko authored Apr 1, 2020
2 parents fde4900 + 6b9a315 commit 54a287b
Show file tree
Hide file tree
Showing 11 changed files with 80 additions and 15 deletions.
1 change: 1 addition & 0 deletions .changelog/2761.breaking.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
go/consensus: Introduce gas cost based on tx size
8 changes: 8 additions & 0 deletions go/consensus/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,16 @@ type Parameters struct {
MaxBlockSize uint64 `json:"max_block_size"`
MaxBlockGas transaction.Gas `json:"max_block_gas"`
MaxEvidenceAge uint64 `json:"max_evidence_age"`

// GasCosts are the base transaction gas costs.
GasCosts transaction.Costs `json:"gas_costs,omitempty"`
}

const (
// GasOpTxByte is the gas operation identifier for costing each transaction byte.
GasOpTxByte transaction.Op = "tx_byte"
)

// SanityCheck does basic sanity checking on the genesis state.
func (g *Genesis) SanityCheck() error {
if g.Parameters.TimeoutCommit < 1*time.Millisecond && !g.Parameters.SkipTimeoutCommit {
Expand Down
3 changes: 2 additions & 1 deletion go/consensus/tendermint/abci/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package abci

import (
"errors"
"fmt"
"math"

"github.com/oasislabs/oasis-core/go/consensus/api/transaction"
Expand Down Expand Up @@ -53,7 +54,7 @@ func (ga *basicGasAccountant) UseGas(multiplier int, op transaction.Op, costs tr
}

if ga.usedGas+amount > ga.maxUsedGas {
return ErrOutOfGas
return fmt.Errorf("%w (limit: %d wanted: %d)", ErrOutOfGas, ga.maxUsedGas, ga.usedGas+amount)
}

ga.usedGas += amount
Expand Down
11 changes: 6 additions & 5 deletions go/consensus/tendermint/abci/gas_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package abci

import (
"errors"
"math"
"testing"

Expand Down Expand Up @@ -44,13 +45,13 @@ func TestBasicGasAccountant(t *testing.T) {
// Overflow.
err = a.UseGas(1, overflowOp, costs)
require.Error(err, "UseGas should fail on overflow")
require.Equal(ErrGasOverflow, err)
require.True(errors.Is(err, ErrGasOverflow))
require.EqualValues(30, a.GasUsed(), "GasUsed")

// Out of gas.
err = a.UseGas(1, expensiveOp, costs)
require.Error(err, "UseGas should fail when out of gas")
require.Equal(ErrOutOfGas, err)
require.True(errors.Is(err, ErrOutOfGas))
require.EqualValues(30, a.GasUsed(), "GasUsed")

require.EqualValues(100, a.GasWanted(), "GasWanted")
Expand Down Expand Up @@ -123,15 +124,15 @@ func TestCompositeGasAccountant(t *testing.T) {
// Overflow.
err = c.UseGas(1, overflowOp, costs)
require.Error(err, "UseGas should fail on overflow")
require.Equal(ErrGasOverflow, err)
require.True(errors.Is(err, ErrGasOverflow))
require.EqualValues(10, c.GasUsed(), "GasUsed")
require.EqualValues(10, a.GasUsed(), "GasUsed")
require.EqualValues(10, b.GasUsed(), "GasUsed")

// Out of gas.
err = a.UseGas(1, expensiveOp, costs)
require.Error(err, "UseGas should fail when out of gas")
require.Equal(ErrOutOfGas, err)
require.True(errors.Is(err, ErrOutOfGas))
require.EqualValues(10, c.GasUsed(), "GasUsed")
require.EqualValues(10, a.GasUsed(), "GasUsed")
require.EqualValues(10, b.GasUsed(), "GasUsed")
Expand All @@ -146,7 +147,7 @@ func TestCompositeGasAccountant(t *testing.T) {

err = c.UseGas(1, cheapOp, costs)
require.Error(err, "UseGas should fail when out of gas")
require.Equal(ErrOutOfGas, err)
require.True(errors.Is(err, ErrOutOfGas))
require.EqualValues(10, c.GasUsed(), "GasUsed")
require.EqualValues(10, a.GasUsed(), "GasUsed")
require.EqualValues(10, b.GasUsed(), "GasUsed")
Expand Down
32 changes: 29 additions & 3 deletions go/consensus/tendermint/abci/mux.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"math"
"sort"
"sync"
"time"
Expand All @@ -25,6 +26,7 @@ import (
"github.com/oasislabs/oasis-core/go/common/version"
consensus "github.com/oasislabs/oasis-core/go/consensus/api"
"github.com/oasislabs/oasis-core/go/consensus/api/transaction"
consensusGenesis "github.com/oasislabs/oasis-core/go/consensus/genesis"
epochtime "github.com/oasislabs/oasis-core/go/epochtime/api"
genesis "github.com/oasislabs/oasis-core/go/genesis/api"
upgrade "github.com/oasislabs/oasis-core/go/upgrade/api"
Expand Down Expand Up @@ -621,7 +623,7 @@ func (mux *abciMux) decodeTx(ctx *Context, rawTx []byte) (*transaction.Transacti
return &tx, &sigTx, nil
}

func (mux *abciMux) processTx(ctx *Context, tx *transaction.Transaction) error {
func (mux *abciMux) processTx(ctx *Context, tx *transaction.Transaction, txSize int) error {
// Pass the transaction through the fee handler if configured.
if txAuthHandler := mux.state.txAuthHandler; txAuthHandler != nil {
if err := txAuthHandler.AuthenticateTx(ctx, tx); err != nil {
Expand All @@ -635,6 +637,15 @@ func (mux *abciMux) processTx(ctx *Context, tx *transaction.Transaction) error {
}
}

// Charge gas based on the size of the transaction.
params, err := mux.state.ConsensusParameters()
if err != nil {
return fmt.Errorf("failed to fetch consensus parameters: %w", err)
}
if err = ctx.Gas().UseGas(txSize, consensusGenesis.GasOpTxByte, params.GasCosts); err != nil {
return err
}

// Route to correct handler.
app := mux.appsByMethod[tx.Method]
if app == nil {
Expand Down Expand Up @@ -678,7 +689,7 @@ func (mux *abciMux) executeTx(ctx *Context, rawTx []byte) error {
// Set authenticated transaction signer.
ctx.SetTxSigner(sigTx.Signature.PublicKey)

return mux.processTx(ctx, tx)
return mux.processTx(ctx, tx, len(rawTx))
}

func (mux *abciMux) EstimateGas(caller signature.PublicKey, tx *transaction.Transaction) (transaction.Gas, error) {
Expand All @@ -689,11 +700,26 @@ func (mux *abciMux) EstimateGas(caller signature.PublicKey, tx *transaction.Tran
ctx := mux.state.NewContext(ContextSimulateTx, time.Time{})
defer ctx.Close()

// Modify transaction to include maximum possible gas in order to estimate the upper limit on
// the serialized transaction size. For amount, use a reasonable amount (in theory the actual
// amount could be bigger depending on the gas price).
tx.Fee = &transaction.Fee{
Gas: transaction.Gas(math.MaxUint64),
}
_ = tx.Fee.Amount.FromUint64(math.MaxUint64)

ctx.SetTxSigner(caller)
mockSignedTx := transaction.SignedTransaction{
Signed: signature.Signed{
Blob: cbor.Marshal(tx),
// Signature is fixed-size, so we can leave it as default.
},
}
txSize := len(cbor.Marshal(mockSignedTx))

// Ignore any errors that occurred during simulation as we only need to estimate gas even if the
// transaction seems like it will fail.
_ = mux.processTx(ctx, tx)
_ = mux.processTx(ctx, tx, txSize)

return ctx.Gas().GasUsed(), nil
}
Expand Down
4 changes: 3 additions & 1 deletion go/oasis-node/cmd/debug/txsource/workload/oversized.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/oasislabs/oasis-core/go/common/logging"
consensus "github.com/oasislabs/oasis-core/go/consensus/api"
"github.com/oasislabs/oasis-core/go/consensus/api/transaction"
consensusGenesis "github.com/oasislabs/oasis-core/go/consensus/genesis"
staking "github.com/oasislabs/oasis-core/go/staking/api"
)

Expand Down Expand Up @@ -48,10 +49,11 @@ func (oversized) Run(
if err != nil {
return fmt.Errorf("failed to query state at genesis: %w", err)
}
params := genesisDoc.Consensus.Parameters

var nonce uint64
fee := transaction.Fee{
Gas: oversizedTxGasAmount,
Gas: oversizedTxGasAmount + transaction.Gas(params.MaxTxSize)*params.GasCosts[consensusGenesis.GasOpTxByte],
}
if err = fee.Amount.FromInt64(oversizedTxGasAmount * gasPrice); err != nil {
return fmt.Errorf("Fee amount error: %w", err)
Expand Down
25 changes: 20 additions & 5 deletions go/oasis-node/cmd/debug/txsource/workload/parallel.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const (
parallelSendWaitTimeoutInterval = 30 * time.Second
parallelSendTimeoutInterval = 60 * time.Second
parallelConcurency = 200
parallelTxGasAmount = 10
parallelTxTransferAmount = 100
parallelTxFundInterval = 10
)
Expand All @@ -43,6 +42,22 @@ func (parallel) Run(
ctx := context.Background()
var err error

// Estimate gas needed for the used transfer transaction.
var txGasAmount transaction.Gas
xfer := &staking.Transfer{
To: fundingAccount.Public(),
}
if err = xfer.Tokens.FromInt64(parallelTxTransferAmount); err != nil {
return fmt.Errorf("transfer tokens FromInt64 %d: %w", parallelTxTransferAmount, err)
}
txGasAmount, err = cnsc.EstimateGas(ctx, &consensus.EstimateGasRequest{
Caller: fundingAccount.Public(),
Transaction: staking.NewTransferTx(0, nil, xfer),
})
if err != nil {
return fmt.Errorf("failed to estimate gas: %w", err)
}

accounts := make([]signature.Signer, parallelConcurency)
fac := memorySigner.NewFactory()
for i := range accounts {
Expand All @@ -53,7 +68,7 @@ func (parallel) Run(

// Initial funding of accounts.
fundAmount := parallelTxTransferAmount + // self transfer amount
parallelTxFundInterval*parallelTxGasAmount*gasPrice // gas for `parallelTxFundInterval` transfers.
parallelTxFundInterval*txGasAmount*gasPrice // gas for `parallelTxFundInterval` transfers.
if err = transferFunds(ctx, parallelLogger, cnsc, fundingAccount, accounts[i].Public(), int64(fundAmount)); err != nil {
return fmt.Errorf("account funding failure: %w", err)
}
Expand All @@ -63,9 +78,9 @@ func (parallel) Run(
// complete before proceeding with a new batch.
var nonce uint64
fee := transaction.Fee{
Gas: parallelTxGasAmount,
Gas: txGasAmount,
}
if err = fee.Amount.FromInt64(parallelTxGasAmount); err != nil {
if err = fee.Amount.FromUint64(uint64(txGasAmount) * gasPrice); err != nil {
return fmt.Errorf("Fee amount error: %w", err)
}

Expand Down Expand Up @@ -136,7 +151,7 @@ func (parallel) Run(
if i%parallelTxFundInterval == 0 {
// Re-fund accounts for next `parallelTxFundInterval` transfers.
for i := range accounts {
fundAmount := parallelTxFundInterval * parallelTxGasAmount * gasPrice // gas for `parallelTxFundInterval` transfers.
fundAmount := parallelTxFundInterval * txGasAmount * gasPrice // gas for `parallelTxFundInterval` transfers.
if err = transferFunds(ctx, parallelLogger, cnsc, fundingAccount, accounts[i].Public(), int64(fundAmount)); err != nil {
return fmt.Errorf("account funding failure: %w", err)
}
Expand Down
5 changes: 5 additions & 0 deletions go/oasis-node/cmd/genesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const (
cfgConsensusMaxBlockSizeBytes = "consensus.tendermint.max_block_size"
cfgConsensusMaxBlockGas = "consensus.tendermint.max_block_gas"
cfgConsensusMaxEvidenceAge = "consensus.tendermint.max_evidence_age"
CfgConsensusGasCostsTxByte = "consensus.gas_costs.tx_byte"

// Consensus backend config flag.
cfgConsensusBackend = "consensus.backend"
Expand Down Expand Up @@ -223,6 +224,9 @@ func doInitGenesis(cmd *cobra.Command, args []string) {
MaxBlockSize: uint64(viper.GetSizeInBytes(cfgConsensusMaxBlockSizeBytes)),
MaxBlockGas: transaction.Gas(viper.GetUint64(cfgConsensusMaxBlockGas)),
MaxEvidenceAge: viper.GetUint64(cfgConsensusMaxEvidenceAge),
GasCosts: transaction.Costs{
consensusGenesis.GasOpTxByte: transaction.Gas(viper.GetUint64(CfgConsensusGasCostsTxByte)),
},
},
}

Expand Down Expand Up @@ -720,6 +724,7 @@ func init() {
initGenesisFlags.String(cfgConsensusMaxBlockSizeBytes, "21mb", "tendermint maximum block size (in bytes)")
initGenesisFlags.Uint64(cfgConsensusMaxBlockGas, 0, "tendermint max gas used per block")
initGenesisFlags.Uint64(cfgConsensusMaxEvidenceAge, 100000, "tendermint max evidence age (in blocks)")
initGenesisFlags.Uint64(CfgConsensusGasCostsTxByte, 1, "consensus gas costs: each transaction byte")

// Consensus backend flag.
initGenesisFlags.String(cfgConsensusBackend, tendermint.BackendName, "consensus backend")
Expand Down
4 changes: 4 additions & 0 deletions go/oasis-test-runner/oasis/oasis.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ type NetworkCfg struct { // nolint: maligned
// ConsensusTimeoutCommit is the consensus commit timeout.
ConsensusTimeoutCommit time.Duration `json:"consensus_timeout_commit"`

// ConsensusGasCostsTxByte is the gas cost of each transaction byte.
ConsensusGasCostsTxByte uint64 `json:"consensus_gas_costs_tx_byte"`

// HaltEpoch is the halt epoch height flag.
HaltEpoch uint64 `json:"halt_epoch"`

Expand Down Expand Up @@ -678,6 +681,7 @@ func (net *Network) makeGenesis() error {
"--registry.debug.allow_unroutable_addresses", "true",
"--" + genesis.CfgRegistryDebugAllowTestRuntimes, "true",
"--scheduler.max_validators_per_entity", strconv.Itoa(len(net.Validators())),
"--" + genesis.CfgConsensusGasCostsTxByte, strconv.FormatUint(net.cfg.ConsensusGasCostsTxByte, 10),
}
if net.cfg.EpochtimeMock {
args = append(args, "--epochtime.debug.mock_backend")
Expand Down
1 change: 1 addition & 0 deletions go/oasis-test-runner/scenario/e2e/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func (sc *basicImpl) Fixture() (*oasis.NetworkFixture, error) {
NodeBinary: viper.GetString(cfgNodeBinary),
RuntimeLoaderBinary: viper.GetString(cfgRuntimeLoader),
DefaultLogWatcherHandlerFactories: DefaultBasicLogWatcherHandlerFactories,
ConsensusGasCostsTxByte: 1,
},
Entities: []oasis.EntityCfg{
oasis.EntityCfg{IsDebugTestEntity: true},
Expand Down
1 change: 1 addition & 0 deletions go/oasis-test-runner/scenario/e2e/gas_fees_staking.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func (sc *gasFeesImpl) Fixture() (*oasis.NetworkFixture, error) {
EpochtimeMock: true,
StakingGenesis: "tests/fixture-data/gas-fees/staking-genesis.json",
DefaultLogWatcherHandlerFactories: DefaultBasicLogWatcherHandlerFactories,
ConsensusGasCostsTxByte: 0, // So we can control gas more easily.
}
f.Entities = []oasis.EntityCfg{
oasis.EntityCfg{IsDebugTestEntity: true},
Expand Down

0 comments on commit 54a287b

Please sign in to comment.