Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions data/pools/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (C) 2019-2022 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package pools

import (
"errors"
"fmt"

"github.com/algorand/go-algorand/data/basics"
)

// ErrStaleBlockAssemblyRequest returned by AssembleBlock when requested block number is older than the current transaction pool round
// i.e. typically it means that we're trying to make a proposal for an older round than what the ledger is currently pointing at.
var ErrStaleBlockAssemblyRequest = errors.New("AssembleBlock: requested block assembly specified a round that is older than current transaction pool round")

// ErrPendingQueueReachedMaxCap indicates the current transaction pool has reached its max capacity
var ErrPendingQueueReachedMaxCap = errors.New("TransactionPool.checkPendingQueueSize: transaction pool have reached capacity")

// ErrNoPendingBlockEvaluator indicates there is no pending block evaluator to accept a new tx group
var ErrNoPendingBlockEvaluator = errors.New("TransactionPool.ingest: no pending block evaluator")

// ErrTxPoolFeeError is an error type for txpool fee escalation checks
type ErrTxPoolFeeError struct {
fee basics.MicroAlgos
feeThreshold uint64
feePerByte uint64
encodedLength int
}

func (e *ErrTxPoolFeeError) Error() string {
return fmt.Sprintf("fee %d below threshold %d (%d per byte * %d bytes)",
e.fee, e.feeThreshold, e.feePerByte, e.encodedLength)
}
42 changes: 21 additions & 21 deletions data/pools/transactionPool.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,6 @@ const (
generateBlockTransactionDuration = 2155 * time.Nanosecond
)

// ErrStaleBlockAssemblyRequest returned by AssembleBlock when requested block number is older than the current transaction pool round
// i.e. typically it means that we're trying to make a proposal for an older round than what the ledger is currently pointing at.
var ErrStaleBlockAssemblyRequest = fmt.Errorf("AssembleBlock: requested block assembly specified a round that is older than current transaction pool round")

// Reset resets the content of the transaction pool
func (pool *TransactionPool) Reset() {
pool.mu.Lock()
Expand Down Expand Up @@ -291,7 +287,7 @@ func (pool *TransactionPool) checkPendingQueueSize(txnGroup []transactions.Signe
return nil
}
}
return fmt.Errorf("TransactionPool.checkPendingQueueSize: transaction pool have reached capacity")
return ErrPendingQueueReachedMaxCap
}
return nil
}
Expand Down Expand Up @@ -360,8 +356,12 @@ func (pool *TransactionPool) checkSufficientFee(txgroup []transactions.SignedTxn
for _, t := range txgroup {
feeThreshold := feePerByte * uint64(t.GetEncodedLength())
if t.Txn.Fee.Raw < feeThreshold {
return fmt.Errorf("fee %d below threshold %d (%d per byte * %d bytes)",
t.Txn.Fee, feeThreshold, feePerByte, t.GetEncodedLength())
return &ErrTxPoolFeeError{
fee: t.Txn.Fee,
feeThreshold: feeThreshold,
feePerByte: feePerByte,
encodedLength: t.GetEncodedLength(),
}
}
}

Expand Down Expand Up @@ -415,7 +415,7 @@ func (pool *TransactionPool) add(txgroup []transactions.SignedTxn, stats *teleme
// while it waits for OnNewBlock() to be called.
func (pool *TransactionPool) ingest(txgroup []transactions.SignedTxn, params poolIngestParams) error {
if pool.pendingBlockEvaluator == nil {
return fmt.Errorf("TransactionPool.ingest: no pending block evaluator")
return ErrNoPendingBlockEvaluator
}

if !params.recomputing {
Expand All @@ -427,7 +427,7 @@ func (pool *TransactionPool) ingest(txgroup []transactions.SignedTxn, params poo
for pool.pendingBlockEvaluator.Round() <= latest && time.Now().Before(waitExpires) {
condvar.TimedWait(&pool.cond, timeoutOnNewBlock)
if pool.pendingBlockEvaluator == nil {
return fmt.Errorf("TransactionPool.ingest: no pending block evaluator")
return ErrNoPendingBlockEvaluator
}
}

Expand Down Expand Up @@ -467,7 +467,7 @@ func (pool *TransactionPool) Remember(txgroup []transactions.SignedTxn) error {

err := pool.remember(txgroup)
if err != nil {
return fmt.Errorf("TransactionPool.Remember: %v", err)
return fmt.Errorf("TransactionPool.Remember: %w", err)
}

pool.rememberCommit(false)
Expand Down Expand Up @@ -581,7 +581,7 @@ func (pool *TransactionPool) addToPendingBlockEvaluatorOnce(txgroup []transactio
r := pool.pendingBlockEvaluator.Round() + pool.numPendingWholeBlocks
for _, tx := range txgroup {
if tx.Txn.LastValid < r {
return transactions.TxnDeadError{
return &transactions.TxnDeadError{
Round: r,
FirstValid: tx.Txn.FirstValid,
LastValid: tx.Txn.LastValid,
Expand All @@ -600,7 +600,7 @@ func (pool *TransactionPool) addToPendingBlockEvaluatorOnce(txgroup []transactio

if recomputing {
if !pool.assemblyResults.assemblyCompletedOrAbandoned {
transactionGroupDuration := time.Now().Sub(transactionGroupStartsTime)
transactionGroupDuration := time.Since(transactionGroupStartsTime)
pool.assemblyMu.Lock()
defer pool.assemblyMu.Unlock()
if pool.assemblyRound > pool.pendingBlockEvaluator.Round() {
Expand Down Expand Up @@ -630,7 +630,7 @@ func (pool *TransactionPool) addToPendingBlockEvaluatorOnce(txgroup []transactio
} else {
pool.assemblyResults.blk = lvb
}
stats.BlockGenerationDuration = uint64(time.Now().Sub(blockGenerationStarts))
stats.BlockGenerationDuration = uint64(time.Since(blockGenerationStarts))
pool.assemblyResults.stats = *stats
pool.assemblyCond.Broadcast()
} else {
Expand Down Expand Up @@ -742,7 +742,7 @@ func (pool *TransactionPool) recomputeBlockEvaluator(committedTxIds map[transact
case *ledgercore.TransactionInLedgerError:
asmStats.CommittedCount++
stats.RemovedInvalidCount++
case transactions.TxnDeadError:
case *transactions.TxnDeadError:
if int(terr.LastValid-terr.FirstValid) > 20 {
// cutoff value here is picked as a somewhat arbitrary cutoff trying to separate longer lived transactions from very short lived ones
asmStats.ExpiredLongLivedCount++
Expand All @@ -753,7 +753,7 @@ func (pool *TransactionPool) recomputeBlockEvaluator(committedTxIds map[transact
asmStats.LeaseErrorCount++
stats.RemovedInvalidCount++
pool.log.Infof("Cannot re-add pending transaction to pool: %v", err)
case transactions.MinFeeError:
case *transactions.MinFeeError:
asmStats.MinFeeErrorCount++
stats.RemovedInvalidCount++
pool.log.Infof("Cannot re-add pending transaction to pool: %v", err)
Expand Down Expand Up @@ -784,7 +784,7 @@ func (pool *TransactionPool) recomputeBlockEvaluator(committedTxIds map[transact
} else {
pool.assemblyResults.blk = lvb
}
asmStats.BlockGenerationDuration = uint64(time.Now().Sub(blockGenerationStarts))
asmStats.BlockGenerationDuration = uint64(time.Since(blockGenerationStarts))
pool.assemblyResults.stats = asmStats
pool.assemblyCond.Broadcast()
}
Expand Down Expand Up @@ -835,7 +835,7 @@ func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Tim
}

// Measure time here because we want to know how close to deadline we are
dt := time.Now().Sub(start)
dt := time.Since(start)
stats.Nanoseconds = dt.Nanoseconds()

payset := assembled.Block().Payset
Expand Down Expand Up @@ -906,7 +906,7 @@ func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Tim
pool.assemblyDeadline = deadline
pool.assemblyRound = round
for time.Now().Before(deadline) && (!pool.assemblyResults.ok || pool.assemblyResults.roundStartedEvaluating != round) {
condvar.TimedWait(&pool.assemblyCond, deadline.Sub(time.Now()))
condvar.TimedWait(&pool.assemblyCond, time.Until(deadline))
}

if !pool.assemblyResults.ok {
Expand All @@ -919,15 +919,15 @@ func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Tim

if pool.assemblyResults.roundStartedEvaluating > round {
// this case is expected to happen only if the transaction pool was able to construct *two* rounds during the time we were trying to assemble the empty block.
// while this is extreamly unlikely, we need to handle this. the handling it quite straight-forward :
// while this is extremely unlikely, we need to handle this. the handling it quite straight-forward :
// since the network is already ahead of us, there is no issue here in not generating a block ( since the block would get discarded anyway )
pool.log.Infof("AssembleBlock: requested round is behind transaction pool round after timing out %d < %d", round, pool.assemblyResults.roundStartedEvaluating)
return nil, ErrStaleBlockAssemblyRequest
}

deadline = deadline.Add(assemblyWaitEps)
for time.Now().Before(deadline) && (!pool.assemblyResults.ok || pool.assemblyResults.roundStartedEvaluating != round) {
condvar.TimedWait(&pool.assemblyCond, deadline.Sub(time.Now()))
condvar.TimedWait(&pool.assemblyCond, time.Until(deadline))
}

// check to see if the extra time helped us to get a block.
Expand All @@ -949,7 +949,7 @@ func (pool *TransactionPool) AssembleBlock(round basics.Round, deadline time.Tim
if pool.assemblyResults.roundStartedEvaluating > round {
// this scenario should not happen unless the txpool is receiving the new blocks via OnNewBlock
// with "jumps" between consecutive blocks ( which is why it's a warning )
// The "normal" usecase is evaluated on the top of the function.
// The "normal" use case is evaluated on the top of the function.
pool.log.Warnf("AssembleBlock: requested round is behind transaction pool round %d < %d", round, pool.assemblyResults.roundStartedEvaluating)
return nil, ErrStaleBlockAssemblyRequest
} else if pool.assemblyResults.roundStartedEvaluating == round.SubSaturate(1) {
Expand Down
16 changes: 8 additions & 8 deletions data/pools/transactionPool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func mockLedger(t TestingT, initAccounts map[basics.Address]basics.AccountData,
genesisInitState := ledgercore.InitState{Block: initBlock, Accounts: initAccounts, GenesisHash: hash}
cfg := config.GetDefaultLocal()
cfg.Archival = true
l, err := ledger.OpenLedger(logging.Base(), fn, true, genesisInitState, cfg)
l, err := ledger.OpenLedger(logging.Base(), fn, inMem, genesisInitState, cfg)
require.NoError(t, err)
return l
}
Expand Down Expand Up @@ -967,7 +967,7 @@ func TestTransactionPool_CurrentFeePerByte(t *testing.T) {
Amount: basics.MicroAlgos{Raw: proto.MinBalance},
},
}
tx.Note = make([]byte, 8, 8)
tx.Note = make([]byte, 8)
crypto.RandBytes(tx.Note)
signedTx := tx.Sign(secrets[i])
err := transactionPool.RememberOne(signedTx)
Expand Down Expand Up @@ -1018,7 +1018,7 @@ func BenchmarkTransactionPoolRememberOne(b *testing.B) {
Amount: basics.MicroAlgos{Raw: proto.MinBalance},
},
}
tx.Note = make([]byte, 8, 8)
tx.Note = make([]byte, 8)
crypto.RandBytes(tx.Note)
signedTx := tx.Sign(secrets[i])
signedTransactions = append(signedTransactions, signedTx)
Expand Down Expand Up @@ -1081,7 +1081,7 @@ func BenchmarkTransactionPoolPending(b *testing.B) {
Amount: basics.MicroAlgos{Raw: proto.MinBalance},
},
}
tx.Note = make([]byte, 8, 8)
tx.Note = make([]byte, 8)
crypto.RandBytes(tx.Note)
signedTx := tx.Sign(secrets[i])
err := transactionPool.RememberOne(signedTx)
Expand Down Expand Up @@ -1247,7 +1247,7 @@ func BenchmarkTransactionPoolSteadyState(b *testing.B) {
Amount: basics.MicroAlgos{Raw: proto.MinBalance},
},
}
tx.Note = make([]byte, 8, 8)
tx.Note = make([]byte, 8)
crypto.RandBytes(tx.Note)

signedTx, err := transactions.AssembleSignedTxn(tx, crypto.Signature{}, crypto.MultisigSig{})
Expand Down Expand Up @@ -1453,7 +1453,7 @@ func TestStateProofLogging(t *testing.T) {
require.NoError(t, err)
b.BlockHeader.Branch = phdr.Hash()

eval, err := mockLedger.StartEvaluator(b.BlockHeader, 0, 10000)
_, err = mockLedger.StartEvaluator(b.BlockHeader, 0, 10000)
require.NoError(t, err)

// Simulate the blocks up to round 512 without any transactions
Expand All @@ -1477,7 +1477,7 @@ func TestStateProofLogging(t *testing.T) {
break
}

eval, err = mockLedger.StartEvaluator(b.BlockHeader, 0, 10000)
_, err = mockLedger.StartEvaluator(b.BlockHeader, 0, 10000)
require.NoError(t, err)
}

Expand Down Expand Up @@ -1520,7 +1520,7 @@ func TestStateProofLogging(t *testing.T) {
require.NoError(t, err)

// Add it to the transaction pool and assemble the block
eval, err = mockLedger.StartEvaluator(b.BlockHeader, 0, 1000000)
eval, err := mockLedger.StartEvaluator(b.BlockHeader, 0, 1000000)
require.NoError(t, err)

err = eval.Transaction(stxn, transactions.ApplyData{})
Expand Down
12 changes: 7 additions & 5 deletions data/transactions/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@ import (
)

// MinFeeError defines an error type which could be returned from the method WellFormed
//msgp:ignore MinFeeError
type MinFeeError string

func (err MinFeeError) Error() string {
return string(err)
func (err *MinFeeError) Error() string {
return string(*err)
}

func makeMinFeeErrorf(format string, args ...interface{}) MinFeeError {
return MinFeeError(fmt.Sprintf(format, args...))
func makeMinFeeErrorf(format string, args ...interface{}) *MinFeeError {
err := MinFeeError(fmt.Sprintf(format, args...))
return &err
}

// TxnDeadError defines an error type which indicates a transaction is outside of the
Expand All @@ -41,6 +43,6 @@ type TxnDeadError struct {
LastValid basics.Round
}

func (err TxnDeadError) Error() string {
func (err *TxnDeadError) Error() string {
return fmt.Sprintf("txn dead: round %d outside of %d--%d", err.Round, err.FirstValid, err.LastValid)
}
22 changes: 18 additions & 4 deletions data/transactions/logic/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -778,19 +778,28 @@ func EvalApp(program []byte, gi int, aid basics.AppIndex, params *EvalParams) (b
return pass, err
}

// EvalSignature evaluates the logicsig of the ith transaction in params.
// EvalSignatureFull evaluates the logicsig of the ith transaction in params.
// A program passes successfully if it finishes with one int element on the stack that is non-zero.
func EvalSignature(gi int, params *EvalParams) (pass bool, err error) {
// It returns EvalContext suitable for obtaining additional info about the execution.
func EvalSignatureFull(gi int, params *EvalParams) (pass bool, pcx *EvalContext, err error) {
if params.SigLedger == nil {
return false, errors.New("no sig ledger in signature eval")
return false, nil, errors.New("no sig ledger in signature eval")
}
cx := EvalContext{
EvalParams: params,
runModeFlags: modeSig,
groupIndex: gi,
txn: &params.TxnGroup[gi],
}
return eval(cx.txn.Lsig.Logic, &cx)
pass, err = eval(cx.txn.Lsig.Logic, &cx)
return pass, &cx, err
}

// EvalSignature evaluates the logicsig of the ith transaction in params.
// A program passes successfully if it finishes with one int element on the stack that is non-zero.
func EvalSignature(gi int, params *EvalParams) (pass bool, err error) {
pass, _, err = EvalSignatureFull(gi, params)
return pass, err
}

// eval implementation
Expand Down Expand Up @@ -997,6 +1006,11 @@ func boolToSV(x bool) stackValue {
return stackValue{Uint: boolToUint(x)}
}

// Cost return cost incurred so far
func (cx *EvalContext) Cost() int {
return cx.cost
}

func (cx *EvalContext) remainingBudget() int {
if cx.runModeFlags == modeSig {
return int(cx.Proto.LogicSigMaxCost) - cx.cost
Expand Down
7 changes: 5 additions & 2 deletions data/transactions/logic/eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,10 @@ byte base64 5rZMNsevs5sULO+54aN+OvU6lQ503z2X+SSYUABIx7E=
ep := defaultEvalParams(txn)
err := CheckSignature(0, ep)
require.NoError(t, err)
pass, err := EvalSignature(0, ep)
pass, cx, err := EvalSignatureFull(0, ep)
require.True(t, pass)
require.NoError(t, err)
require.Greater(t, cx.Cost(), 0)
})
}
}
Expand Down Expand Up @@ -454,10 +455,12 @@ func TestTLHC(t *testing.T) {
t.Log(ep.Trace.String())
}
require.NoError(t, err)
pass, err := EvalSignature(0, ep)
pass, cx, err := EvalSignatureFull(0, ep)
if pass {
t.Log(hex.EncodeToString(ops.Program))
t.Log(ep.Trace.String())
require.Greater(t, cx.cost, 0)
require.Greater(t, cx.Cost(), 0)
}
require.False(t, pass)
isNotPanic(t, err)
Expand Down
Loading