Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Access] Added metrics for transaction validator #6239

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
27ed743
Added new metrics for tx validation
Guitarheroua Jul 18, 2024
353e672
Implemented methods
Guitarheroua Jul 18, 2024
1d8fc89
Added validator metrics raw
Guitarheroua Jul 18, 2024
b0718b1
Added metrics to collection node
Guitarheroua Jul 22, 2024
02cb7b4
Fixed issue with moks and interfaces
Guitarheroua Jul 22, 2024
2865fa8
Added tx validation metrics to collection node metrics
Guitarheroua Jul 22, 2024
3224109
refactor validation method
Guitarheroua Jul 22, 2024
78f70d1
Merge branch 'master' of github.com:The-K-R-O-K/flow-go into AndriiSl…
Guitarheroua Jul 22, 2024
1430dc3
Fixed imports
Guitarheroua Jul 22, 2024
4e5d1e4
Fixed remarks
Guitarheroua Jul 25, 2024
059889d
Added comments
Guitarheroua Jul 25, 2024
f996ba2
Merge branch 'master' of github.com:The-K-R-O-K/flow-go into AndriiSl…
Guitarheroua Jul 25, 2024
88c1171
Merge branch 'master' into AndriiSlisarchuk/6128-metrics-for-tx-valid…
Guitarheroua Jul 26, 2024
e01131d
Update access/validator.go
Guitarheroua Jul 30, 2024
a6cf701
Merge branch 'master' into AndriiSlisarchuk/6128-metrics-for-tx-valid…
Guitarheroua Jul 30, 2024
3986b90
make constant private in module
Guitarheroua Jul 30, 2024
67088f1
fixed typo
Guitarheroua Jul 30, 2024
dea4035
Fixed emulator issue
Guitarheroua Jul 30, 2024
8152bca
Fixed remarks
Guitarheroua Aug 1, 2024
1afc820
Merge branch 'master' of github.com:The-K-R-O-K/flow-go into AndriiSl…
Guitarheroua Aug 5, 2024
0cd7852
Added metrics for skipped transaction validation
Guitarheroua Aug 12, 2024
4ae9f68
Merge branch 'master' of github.com:The-K-R-O-K/flow-go into AndriiSl…
Guitarheroua Aug 12, 2024
05d30f2
Apply suggestions from code review
Guitarheroua Aug 13, 2024
fa181d7
Call skiped metric
Guitarheroua Aug 13, 2024
b967f3c
Merge branch 'master' of github.com:The-K-R-O-K/flow-go into AndriiSl…
Guitarheroua Aug 13, 2024
45bba99
Fixed emulator dependencies
Guitarheroua Aug 13, 2024
f67bda3
Merge branch 'master' into AndriiSlisarchuk/6128-metrics-for-tx-valid…
Guitarheroua Aug 14, 2024
4bab3a3
Merge branch 'master' into AndriiSlisarchuk/6128-metrics-for-tx-valid…
Guitarheroua Aug 14, 2024
1c10295
Merge branch 'master' into AndriiSlisarchuk/6128-metrics-for-tx-valid…
Guitarheroua Aug 15, 2024
59214bb
Merge branch 'master' into AndriiSlisarchuk/6128-metrics-for-tx-valid…
Guitarheroua Aug 16, 2024
47513fe
Merge branch 'master' of github.com:The-K-R-O-K/flow-go into AndriiSl…
Guitarheroua Aug 27, 2024
2b52b79
updated emulator
Guitarheroua Aug 27, 2024
b54820f
dixed conflicts
Guitarheroua Aug 29, 2024
c54ab22
removed replace
Guitarheroua Aug 29, 2024
b23c45c
Merge remote-tracking branch 'origin' into AndriiSlisarchuk/6128-metr…
Guitarheroua Aug 29, 2024
9012cd0
Fixed emulator version
Guitarheroua Aug 29, 2024
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
140 changes: 71 additions & 69 deletions access/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ import (
"errors"
"fmt"

"github.com/rs/zerolog/log"

"github.com/onflow/cadence"
jsoncdc "github.com/onflow/cadence/encoding/json"
"github.com/onflow/cadence/runtime/parser"
"github.com/onflow/crypto"
"github.com/onflow/flow-core-contracts/lib/go/templates"
"github.com/rs/zerolog/log"

cadenceutils "github.com/onflow/flow-go/access/utils"
"github.com/onflow/flow-go/fvm"
"github.com/onflow/flow-go/fvm/systemcontracts"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/module"
"github.com/onflow/flow-go/module/execution"
"github.com/onflow/flow-go/module/metrics"
"github.com/onflow/flow-go/state"
"github.com/onflow/flow-go/state/protocol"
)
Expand Down Expand Up @@ -87,19 +90,28 @@ type TransactionValidationOptions struct {
CheckPayerBalance bool
}

type ValidationStep struct {
check func(*flow.TransactionBody) error
failReason string
}

type TransactionValidator struct {
blocks Blocks // for looking up blocks to check transaction expiry
chain flow.Chain // for checking validity of addresses
options TransactionValidationOptions
serviceAccountAddress flow.Address
limiter RateLimiter
scriptExecutor execution.ScriptExecutor
verifyPayerBalanceScript []byte
blocks Blocks // for looking up blocks to check transaction expiry
chain flow.Chain // for checking validity of addresses
options TransactionValidationOptions
serviceAccountAddress flow.Address
limiter RateLimiter
scriptExecutor execution.ScriptExecutor
verifyPayerBalanceScript []byte
transactionValidationMetrics module.TransactionValidationMetrics

validationSteps []ValidationStep
}

func NewTransactionValidator(
blocks Blocks,
chain flow.Chain,
transactionValidationMetrics module.TransactionValidationMetrics,
options TransactionValidationOptions,
executor execution.ScriptExecutor,
) (*TransactionValidator, error) {
Expand All @@ -109,80 +121,68 @@ func NewTransactionValidator(

env := systemcontracts.SystemContractsForChain(chain.ChainID()).AsTemplateEnv()

return &TransactionValidator{
blocks: blocks,
chain: chain,
options: options,
serviceAccountAddress: chain.ServiceAddress(),
limiter: NewNoopLimiter(),
scriptExecutor: executor,
verifyPayerBalanceScript: templates.GenerateVerifyPayerBalanceForTxExecution(env),
}, nil
txValidator := &TransactionValidator{
blocks: blocks,
chain: chain,
options: options,
serviceAccountAddress: chain.ServiceAddress(),
limiter: NewNoopLimiter(),
scriptExecutor: executor,
verifyPayerBalanceScript: templates.GenerateVerifyPayerBalanceForTxExecution(env),
transactionValidationMetrics: transactionValidationMetrics,
}

txValidator.initValidationSteps()

return txValidator, nil
}

func NewTransactionValidatorWithLimiter(
blocks Blocks,
chain flow.Chain,
options TransactionValidationOptions,
transactionValidationMetrics module.TransactionValidationMetrics,
rateLimiter RateLimiter,
) *TransactionValidator {
return &TransactionValidator{
blocks: blocks,
chain: chain,
options: options,
serviceAccountAddress: chain.ServiceAddress(),
limiter: rateLimiter,
}
}

func (v *TransactionValidator) Validate(ctx context.Context, tx *flow.TransactionBody) (err error) {
// rate limit transactions for specific payers.
// a short term solution to prevent attacks that send too many failed transactions
// if a transaction is from a payer that should be rate limited, all the following
// checks will be skipped
err = v.checkRateLimitPayer(tx)
if err != nil {
return err
}

err = v.checkTxSizeLimit(tx)
if err != nil {
return err
}

err = v.checkMissingFields(tx)
if err != nil {
return err
txValidator := &TransactionValidator{
blocks: blocks,
chain: chain,
options: options,
serviceAccountAddress: chain.ServiceAddress(),
limiter: rateLimiter,
transactionValidationMetrics: transactionValidationMetrics,
}

err = v.checkGasLimit(tx)
if err != nil {
return err
}

err = v.checkExpiry(tx)
if err != nil {
return err
}
txValidator.initValidationSteps()

err = v.checkCanBeParsed(tx)
if err != nil {
return err
}
return txValidator
}

err = v.checkAddresses(tx)
if err != nil {
return err
func (v *TransactionValidator) initValidationSteps() {
v.validationSteps = []ValidationStep{
// rate limit transactions for specific payers.
// a short term solution to prevent attacks that send too many failed transactions
// if a transaction is from a payer that should be rate limited, all the following
// checks will be skipped
{v.checkRateLimitPayer, metrics.InvalidTransactionRateLimit},
{v.checkTxSizeLimit, metrics.InvalidTransactionByteSize},
{v.checkMissingFields, metrics.IncompleteTransaction},
{v.checkGasLimit, metrics.InvalidGasLimit},
{v.checkExpiry, metrics.ExpiredTransaction},
{v.checkCanBeParsed, metrics.InvalidScript},
{v.checkAddresses, metrics.InvalidAddresses},
{v.checkSignatureFormat, metrics.InvalidSignature},
{v.checkSignatureDuplications, metrics.DuplicatedSignature},
}
}

err = v.checkSignatureFormat(tx)
if err != nil {
return err
}
func (v *TransactionValidator) Validate(ctx context.Context, tx *flow.TransactionBody) (err error) {

err = v.checkSignatureDuplications(tx)
if err != nil {
return err
for _, step := range v.validationSteps {
if err = step.check(tx); err != nil {
v.transactionValidationMetrics.TransactionValidationFailed(step.failReason)
return err
}
}

err = v.checkSufficientBalanceToPayForTransaction(ctx, tx)
Expand All @@ -192,15 +192,19 @@ func (v *TransactionValidator) Validate(ctx context.Context, tx *flow.Transactio
// are 'internal' and related to script execution process. they shouldn't
// prevent the transaction from proceeding.
if IsInsufficientBalanceError(err) {
v.transactionValidationMetrics.TransactionValidationFailed(metrics.InsufficientBalance)
Guitarheroua marked this conversation as resolved.
Show resolved Hide resolved
return err
}

Guitarheroua marked this conversation as resolved.
Show resolved Hide resolved
// log and ignore all other errors
v.transactionValidationMetrics.TransactionValidationSkipped()
log.Info().Err(err).Msg("check payer validation skipped due to error")
}

// TODO replace checkSignatureFormat by verifying the account/payer signatures

v.transactionValidationMetrics.TransactionValidated()

return nil
}

Expand Down Expand Up @@ -328,7 +332,6 @@ func (v *TransactionValidator) checkCanBeParsed(tx *flow.TransactionBody) error
}

func (v *TransactionValidator) checkAddresses(tx *flow.TransactionBody) error {

for _, address := range append(tx.Authorizers, tx.Payer) {
// we check whether this is a valid output of the address generator
if !v.chain.IsValid(address) {
Expand Down Expand Up @@ -356,7 +359,6 @@ func (v *TransactionValidator) checkSignatureDuplications(tx *flow.TransactionBo
}

func (v *TransactionValidator) checkSignatureFormat(tx *flow.TransactionBody) error {

for _, signature := range append(tx.PayloadSignatures, tx.EnvelopeSignatures...) {
// check the format of the signature is valid.
// a valid signature is an ECDSA signature of either P-256 or secp256k1 curve.
Expand Down
10 changes: 7 additions & 3 deletions access/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import (
accessmock "github.com/onflow/flow-go/access/mock"
"github.com/onflow/flow-go/fvm"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/module"
execmock "github.com/onflow/flow-go/module/execution/mock"
"github.com/onflow/flow-go/module/metrics"
"github.com/onflow/flow-go/utils/unittest"
)

Expand All @@ -31,9 +33,11 @@ type TransactionValidatorSuite struct {
header *flow.Header
chain flow.Chain
validatorOptions access.TransactionValidationOptions
metrics module.TransactionValidationMetrics
}

func (s *TransactionValidatorSuite) SetupTest() {
s.metrics = metrics.NewNoopCollector()
s.blocks = accessmock.NewBlocks(s.T())
assert.NotNil(s.T(), s.blocks)

Expand Down Expand Up @@ -89,7 +93,7 @@ func (s *TransactionValidatorSuite) TestTransactionValidator_ScriptExecutorInter
Return(nil, errors.New("script executor internal error")).
Once()

validator, err := access.NewTransactionValidator(s.blocks, s.chain, s.validatorOptions, scriptExecutor)
validator, err := access.NewTransactionValidator(s.blocks, s.chain, s.metrics, s.validatorOptions, scriptExecutor)
assert.NoError(s.T(), err)
assert.NotNil(s.T(), validator)

Expand All @@ -116,7 +120,7 @@ func (s *TransactionValidatorSuite) TestTransactionValidator_SufficientBalance()
Return(actualResponse, nil).
Once()

validator, err := access.NewTransactionValidator(s.blocks, s.chain, s.validatorOptions, scriptExecutor)
validator, err := access.NewTransactionValidator(s.blocks, s.chain, s.metrics, s.validatorOptions, scriptExecutor)
assert.NoError(s.T(), err)
assert.NotNil(s.T(), validator)

Expand Down Expand Up @@ -147,7 +151,7 @@ func (s *TransactionValidatorSuite) TestTransactionValidator_InsufficientBalance
assert.NoError(s.T(), err)
assert.NotNil(s.T(), actualAccountResponse)

validator, err := access.NewTransactionValidator(s.blocks, s.chain, s.validatorOptions, scriptExecutor)
validator, err := access.NewTransactionValidator(s.blocks, s.chain, s.metrics, s.validatorOptions, scriptExecutor)
assert.NoError(s.T(), err)
assert.NotNil(s.T(), validator)

Expand Down
80 changes: 43 additions & 37 deletions cmd/access/node_builder/access_node_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,43 +287,44 @@ type FlowAccessNodeBuilder struct {
*AccessNodeConfig

// components
FollowerState protocol.FollowerState
SyncCore *chainsync.Core
RpcEng *rpc.Engine
FollowerDistributor *consensuspubsub.FollowerDistributor
CollectionRPC access.AccessAPIClient
TransactionTimings *stdmap.TransactionTimings
CollectionsToMarkFinalized *stdmap.Times
CollectionsToMarkExecuted *stdmap.Times
BlocksToMarkExecuted *stdmap.Times
TransactionMetrics *metrics.TransactionCollector
RestMetrics *metrics.RestCollector
AccessMetrics module.AccessMetrics
PingMetrics module.PingMetrics
Committee hotstuff.DynamicCommittee
Finalized *flow.Header // latest finalized block that the node knows of at startup time
Pending []*flow.Header
FollowerCore module.HotStuffFollower
Validator hotstuff.Validator
ExecutionDataDownloader execution_data.Downloader
PublicBlobService network.BlobService
ExecutionDataRequester state_synchronization.ExecutionDataRequester
ExecutionDataStore execution_data.ExecutionDataStore
ExecutionDataBlobstore blobs.Blobstore
ExecutionDataCache *execdatacache.ExecutionDataCache
ExecutionIndexer *indexer.Indexer
ExecutionIndexerCore *indexer.IndexerCore
ScriptExecutor *backend.ScriptExecutor
RegistersAsyncStore *execution.RegistersAsyncStore
Reporter *index.Reporter
EventsIndex *index.EventsIndex
TxResultsIndex *index.TransactionResultsIndex
IndexerDependencies *cmd.DependencyList
collectionExecutedMetric module.CollectionExecutedMetric
ExecutionDataPruner *pruner.Pruner
ExecutionDatastoreManager edstorage.DatastoreManager
ExecutionDataTracker tracker.Storage
VersionControl *version.VersionControl
FollowerState protocol.FollowerState
SyncCore *chainsync.Core
RpcEng *rpc.Engine
FollowerDistributor *consensuspubsub.FollowerDistributor
CollectionRPC access.AccessAPIClient
TransactionTimings *stdmap.TransactionTimings
CollectionsToMarkFinalized *stdmap.Times
CollectionsToMarkExecuted *stdmap.Times
BlocksToMarkExecuted *stdmap.Times
TransactionMetrics *metrics.TransactionCollector
TransactionValidationMetrics *metrics.TransactionValidationCollector
RestMetrics *metrics.RestCollector
AccessMetrics module.AccessMetrics
PingMetrics module.PingMetrics
Committee hotstuff.DynamicCommittee
Finalized *flow.Header // latest finalized block that the node knows of at startup time
Pending []*flow.Header
FollowerCore module.HotStuffFollower
Validator hotstuff.Validator
ExecutionDataDownloader execution_data.Downloader
PublicBlobService network.BlobService
ExecutionDataRequester state_synchronization.ExecutionDataRequester
ExecutionDataStore execution_data.ExecutionDataStore
ExecutionDataBlobstore blobs.Blobstore
ExecutionDataCache *execdatacache.ExecutionDataCache
ExecutionIndexer *indexer.Indexer
ExecutionIndexerCore *indexer.IndexerCore
ScriptExecutor *backend.ScriptExecutor
RegistersAsyncStore *execution.RegistersAsyncStore
Reporter *index.Reporter
EventsIndex *index.EventsIndex
TxResultsIndex *index.TransactionResultsIndex
IndexerDependencies *cmd.DependencyList
collectionExecutedMetric module.CollectionExecutedMetric
ExecutionDataPruner *pruner.Pruner
ExecutionDatastoreManager edstorage.DatastoreManager
ExecutionDataTracker tracker.Storage
VersionControl *version.VersionControl

// The sync engine participants provider is the libp2p peer store for the access node
// which is not available until after the network has started.
Expand Down Expand Up @@ -1665,6 +1666,10 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) {
)
return nil
}).
Module("transaction validation metrics", func(node *cmd.NodeConfig) error {
builder.TransactionValidationMetrics = metrics.NewTransactionValidationCollector()
return nil
}).
Module("rest metrics", func(node *cmd.NodeConfig) error {
m, err := metrics.NewRestCollector(routes.URLToRoute, node.MetricsRegisterer)
if err != nil {
Expand All @@ -1676,6 +1681,7 @@ func (builder *FlowAccessNodeBuilder) Build() (cmd.Node, error) {
Module("access metrics", func(node *cmd.NodeConfig) error {
builder.AccessMetrics = metrics.NewAccessCollector(
metrics.WithTransactionMetrics(builder.TransactionMetrics),
metrics.WithTransactionValidationMetrics(builder.TransactionValidationMetrics),
metrics.WithBackendScriptsMetrics(builder.TransactionMetrics),
metrics.WithRestMetrics(builder.RestMetrics),
)
Expand Down
Loading
Loading