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

Add VerifyTx to executor.Manager #2293

Merged
merged 7 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
46 changes: 7 additions & 39 deletions vms/platformvm/block/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,13 @@ var (

ErrEndOfTime = errors.New("program time is suspiciously far in the future")
ErrNoPendingBlocks = errors.New("no pending blocks")
ErrChainNotSynced = errors.New("chain not synced")
)

type Builder interface {
mempool.Mempool
mempool.BlockTimer
Network

// set preferred block on top of which we'll build next
SetPreference(blockID ids.ID)

// get preferred block on top of which we'll build next
Preferred() (snowman.Block, error)

// AddUnverifiedTx verifier the tx before adding it to mempool
AddUnverifiedTx(tx *txs.Tx) error

Expand All @@ -70,9 +63,6 @@ type builder struct {
txExecutorBackend *txexecutor.Backend
blkManager blockexecutor.Manager

// ID of the preferred block to build on top of
preferredBlockID ids.ID

// channel to send messages to the consensus engine
toEngine chan<- common.Message

Expand Down Expand Up @@ -110,39 +100,16 @@ func New(
return builder
}

func (b *builder) SetPreference(blockID ids.ID) {
if blockID == b.preferredBlockID {
// If the preference didn't change, then this is a noop
return
}
b.preferredBlockID = blockID
b.ResetBlockTimer()
}

func (b *builder) Preferred() (snowman.Block, error) {
return b.blkManager.GetBlock(b.preferredBlockID)
}

// AddUnverifiedTx verifies a transaction and attempts to add it to the mempool
func (b *builder) AddUnverifiedTx(tx *txs.Tx) error {
if !b.txExecutorBackend.Bootstrapped.Get() {
return ErrChainNotSynced
}

txID := tx.ID()
if b.Mempool.Has(txID) {
abi87 marked this conversation as resolved.
Show resolved Hide resolved
// If the transaction is already in the mempool - then it looks the same
// as if it was successfully added
return nil
}

verifier := txexecutor.MempoolTxVerifier{
Backend: b.txExecutorBackend,
ParentID: b.preferredBlockID, // We want to build off of the preferred block
StateVersions: b.blkManager,
Tx: tx,
}
if err := tx.Unsigned.Visit(&verifier); err != nil {
if err := b.blkManager.VerifyTx(tx); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename VerifyTx to something like VerifyMempoolCandidate both here and in X-chain.
This way we can avoid confusion and minimize awkwardness of having to state verify mempool candidate in blkManager. We can fully solve the awkwardness later on and jontly across P-chain and X-chain

b.MarkDropped(txID, err)
return err
}
Expand Down Expand Up @@ -186,11 +153,11 @@ func (b *builder) BuildBlock(context.Context) (snowman.Block, error) {
// Only modifies state to remove expired proposal txs.
func (b *builder) buildBlock() (block.Block, error) {
// Get the block to build on top of and retrieve the new block's context.
preferred, err := b.Preferred()
preferredID := b.blkManager.Preferred()
preferred, err := b.blkManager.GetBlock(preferredID)
if err != nil {
return nil, err
}
preferredID := preferred.ID()
nextHeight := preferred.Height() + 1
preferredState, ok := b.blkManager.GetState(preferredID)
if !ok {
Expand Down Expand Up @@ -266,11 +233,12 @@ func (b *builder) setNextBuildBlockTime() {
}

// Wake up when it's time to add/remove the next validator/delegator
preferredState, ok := b.blkManager.GetState(b.preferredBlockID)
preferredID := b.blkManager.Preferred()
preferredState, ok := b.blkManager.GetState(preferredID)
if !ok {
// The preferred block should always be a decision block
ctx.Log.Error("couldn't get preferred block state",
zap.Stringer("preferredID", b.preferredBlockID),
zap.Stringer("preferredID", preferredID),
zap.Stringer("lastAcceptedID", b.blkManager.LastAccepted()),
)
return
Expand All @@ -279,7 +247,7 @@ func (b *builder) setNextBuildBlockTime() {
nextStakerChangeTime, err := txexecutor.GetNextStakerChangeTime(preferredState)
if err != nil {
ctx.Log.Error("couldn't get next staker change time",
zap.Stringer("preferredID", b.preferredBlockID),
zap.Stringer("preferredID", preferredID),
zap.Stringer("lastAcceptedID", b.blkManager.LastAccepted()),
zap.Error(err),
)
Expand Down
2 changes: 1 addition & 1 deletion vms/platformvm/block/builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func TestNoErrorOnUnexpectedSetPreferenceDuringBootstrapping(t *testing.T) {
require.NoError(t, shutdownEnvironment(env))
}()

env.Builder.SetPreference(ids.GenerateTestID()) // should not panic
require.False(t, env.blkManager.SetPreference(ids.GenerateTestID())) // should not panic
}

func TestGetNextStakerToReward(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion vms/platformvm/block/builder/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func newEnvironment(t *testing.T) *environment {
res.sender,
)

res.Builder.SetPreference(genesisID)
res.blkManager.SetPreference(genesisID)
addSubnet(t, res)

return res
Expand Down
48 changes: 46 additions & 2 deletions vms/platformvm/block/executor/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,41 @@
package executor

import (
"errors"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/snow/consensus/snowman"
"github.com/ava-labs/avalanchego/vms/platformvm/block"
"github.com/ava-labs/avalanchego/vms/platformvm/metrics"
"github.com/ava-labs/avalanchego/vms/platformvm/state"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
"github.com/ava-labs/avalanchego/vms/platformvm/txs/executor"
"github.com/ava-labs/avalanchego/vms/platformvm/txs/mempool"
"github.com/ava-labs/avalanchego/vms/platformvm/validators"
)

var _ Manager = (*manager)(nil)
var (
_ Manager = (*manager)(nil)

ErrChainNotSynced = errors.New("chain not synced")
)

type Manager interface {
state.Versions

// Returns the ID of the most recently accepted block.
LastAccepted() ids.ID

SetPreference(blkID ids.ID) (updated bool)
Preferred() ids.ID

GetBlock(blkID ids.ID) (snowman.Block, error)
GetStatelessBlock(blkID ids.ID) (block.Block, error)
NewBlock(block.Block) snowman.Block

// VerifyTx verifies that the transaction can be issued based on the
// currently preferred state.
VerifyTx(tx *txs.Tx) error
}

func NewManager(
Expand All @@ -33,9 +48,10 @@ func NewManager(
txExecutorBackend *executor.Backend,
validatorManager validators.Manager,
) Manager {
lastAccepted := s.GetLastAccepted()
backend := &backend{
Mempool: mempool,
lastAccepted: s.GetLastAccepted(),
lastAccepted: lastAccepted,
state: s,
ctx: txExecutorBackend.Ctx,
blkIDToState: map[ids.ID]*blockState{},
Expand All @@ -57,6 +73,8 @@ func NewManager(
backend: backend,
addTxsToMempool: !txExecutorBackend.Config.PartialSyncPrimaryNetwork,
},
preferred: lastAccepted,
txExecutorBackend: txExecutorBackend,
}
}

Expand All @@ -65,6 +83,9 @@ type manager struct {
verifier block.Visitor
acceptor block.Visitor
rejector block.Visitor

preferred ids.ID
txExecutorBackend *executor.Backend
}

func (m *manager) GetBlock(blkID ids.ID) (snowman.Block, error) {
Expand All @@ -85,3 +106,26 @@ func (m *manager) NewBlock(blk block.Block) snowman.Block {
Block: blk,
}
}

func (m *manager) SetPreference(blockID ids.ID) (updated bool) {
updated = m.preferred == blockID
m.preferred = blockID
return updated
}

func (m *manager) Preferred() ids.ID {
return m.preferred
}

func (m *manager) VerifyTx(tx *txs.Tx) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what I think it's a bit confusing here is that VerifyTx is to verify mempool transactions but we already have a MempoolTxVerifier visitor that basically does all of it already.
So maybe the change to be done is to move the "isBootstrapped" check into the mempoolVerified and that's it.
This would avoid increasing the blkManager interface (as per @danlaine 's comment) and make blkManager more of a backend object for verification of both mempool txs and txs in block.

Copy link
Contributor Author

@dhrubabasu dhrubabasu Nov 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function uses the MempoolTxVerifier. I don't think it's a good design pattern to require consumers of this package to create a struct themselves. I think this pattern (from #2296 https://github.com/ava-labs/avalanchego/blob/remove-add-unverified-tx/vms/platformvm/network/network.go#L162-L181) is much more digestible:

	// Verify the tx at the currently preferred state
	if err := n.manager.VerifyTx(tx); err != nil {
		n.mempool.MarkDropped(txID, err)
		return err
	}

than the existing:

	verifier := txexecutor.MempoolTxVerifier{
		Backend:       b.txExecutorBackend,
		ParentID:      b.blkManager.Preferred(), // We want to build off of the preferred block
		StateVersions: b.blkManager,
		Tx:            tx,
	}
	if err := tx.Unsigned.Visit(&verifier); err != nil {
		b.MarkDropped(txID, err)
		return err
	}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really think it's more digestible but I'd suggest we find a proper name for manager.VerifyTx (my suggestion being manager.VerifyMempoolCandidate) and we defer discussions around mempool refactoring.

if !m.txExecutorBackend.Bootstrapped.Get() {
return ErrChainNotSynced
}

return tx.Unsigned.Visit(&executor.MempoolTxVerifier{
Backend: m.txExecutorBackend,
ParentID: m.preferred,
StateVersions: m,
Tx: tx,
})
}
43 changes: 43 additions & 0 deletions vms/platformvm/block/executor/mock_manager.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 3 additions & 11 deletions vms/platformvm/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1943,11 +1943,8 @@ func (s *Service) GetBlockchainStatus(r *http.Request, args *GetBlockchainStatus
return nil
}

preferredBlk, err := s.vm.Preferred()
if err != nil {
return fmt.Errorf("could not retrieve preferred block, err %w", err)
}
preferred, err := s.chainExists(ctx, preferredBlk.ID(), blockchainID)
preferredBlkID := s.vm.manager.Preferred()
preferred, err := s.chainExists(ctx, preferredBlkID, blockchainID)
if err != nil {
return fmt.Errorf("problem looking up blockchain: %w", err)
}
Expand Down Expand Up @@ -2246,12 +2243,7 @@ func (s *Service) GetTxStatus(_ *http.Request, args *GetTxStatusArgs, response *

// The status of this transaction is not in the database - check if the tx
// is in the preferred block's db. If so, return that it's processing.
prefBlk, err := s.vm.Preferred()
if err != nil {
return err
}

preferredID := prefBlk.ID()
preferredID := s.vm.manager.Preferred()
onAccept, ok := s.vm.manager.GetState(preferredID)
if !ok {
return fmt.Errorf("could not retrieve state for block %s", preferredID)
Expand Down
3 changes: 2 additions & 1 deletion vms/platformvm/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -784,7 +784,8 @@ func TestGetBlock(t *testing.T) {
)
require.NoError(err)

preferred, err := service.vm.Builder.Preferred()
preferredID := service.vm.manager.Preferred()
preferred, err := service.vm.manager.GetBlock(preferredID)
require.NoError(err)

statelessBlock, err := block.NewBanffStandardBlock(
Expand Down
4 changes: 3 additions & 1 deletion vms/platformvm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,9 @@ func (vm *VM) LastAccepted(context.Context) (ids.ID, error) {

// SetPreference sets the preferred block to be the one with ID [blkID]
func (vm *VM) SetPreference(_ context.Context, blkID ids.ID) error {
vm.Builder.SetPreference(blkID)
if vm.manager.SetPreference(blkID) {
vm.Builder.ResetBlockTimer()
}
return nil
}

Expand Down
23 changes: 10 additions & 13 deletions vms/platformvm/vm_regression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,11 +410,10 @@ func TestUnverifiedParentPanicRegression(t *testing.T) {
)
require.NoError(err)

preferred, err := vm.Builder.Preferred()
preferredID := vm.manager.Preferred()
preferred, err := vm.manager.GetBlock(preferredID)
require.NoError(err)

preferredChainTime := preferred.Timestamp()
preferredID := preferred.ID()
preferredHeight := preferred.Height()

statelessStandardBlk, err := block.NewBanffStandardBlock(
Expand Down Expand Up @@ -493,11 +492,10 @@ func TestRejectedStateRegressionInvalidValidatorTimestamp(t *testing.T) {
require.NoError(err)

// Create the standard block to add the new validator
preferred, err := vm.Builder.Preferred()
preferredID := vm.manager.Preferred()
preferred, err := vm.manager.GetBlock(preferredID)
require.NoError(err)

preferredChainTime := preferred.Timestamp()
preferredID := preferred.ID()
preferredHeight := preferred.Height()

statelessBlk, err := block.NewBanffStandardBlock(
Expand Down Expand Up @@ -706,11 +704,10 @@ func TestRejectedStateRegressionInvalidValidatorReward(t *testing.T) {
require.NoError(err)

// Create the standard block to add the first new validator
preferred, err := vm.Builder.Preferred()
preferredID := vm.manager.Preferred()
preferred, err := vm.manager.GetBlock(preferredID)
require.NoError(err)

preferredChainTime := preferred.Timestamp()
preferredID := preferred.ID()
preferredHeight := preferred.Height()

statelessAddValidatorStandardBlk0, err := block.NewBanffStandardBlock(
Expand Down Expand Up @@ -1044,11 +1041,10 @@ func TestValidatorSetAtCacheOverwriteRegression(t *testing.T) {
require.NoError(err)

// Create the standard block to add the first new validator
preferred, err := vm.Builder.Preferred()
preferredID := vm.manager.Preferred()
preferred, err := vm.manager.GetBlock(preferredID)
require.NoError(err)

preferredChainTime := preferred.Timestamp()
preferredID := preferred.ID()
preferredHeight := preferred.Height()

statelessStandardBlk, err := block.NewBanffStandardBlock(
Expand Down Expand Up @@ -1081,7 +1077,8 @@ func TestValidatorSetAtCacheOverwriteRegression(t *testing.T) {

// Create the standard block that moves the first new validator from the
// pending validator set into the current validator set.
preferred, err = vm.Builder.Preferred()
preferredID = vm.manager.Preferred()
preferred, err = vm.manager.GetBlock(preferredID)
require.NoError(err)
preferredID = preferred.ID()
preferredHeight = preferred.Height()
Expand Down
Loading
Loading