github.com/MetalBlockchain/metalgo@v1.11.9/snow/acceptor.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package snow
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  
    10  	"go.uber.org/zap"
    11  
    12  	"github.com/MetalBlockchain/metalgo/ids"
    13  	"github.com/MetalBlockchain/metalgo/utils/logging"
    14  )
    15  
    16  var (
    17  	_ Acceptor = acceptorWrapper{}
    18  
    19  	_ AcceptorGroup = (*acceptorGroup)(nil)
    20  )
    21  
    22  // Acceptor is implemented when a struct is monitoring if a message is accepted
    23  type Acceptor interface {
    24  	// Accept must be called before [containerID] is committed to the VM as
    25  	// accepted.
    26  	//
    27  	// If the returned error is non-nil, the chain associated with [ctx] should
    28  	// shut down and not commit [container] or any other container to its
    29  	// database as accepted.
    30  	Accept(ctx *ConsensusContext, containerID ids.ID, container []byte) error
    31  }
    32  
    33  type acceptorWrapper struct {
    34  	Acceptor
    35  
    36  	// If true and Accept returns an error, the chain this callback corresponds
    37  	// to will stop.
    38  	dieOnError bool
    39  }
    40  
    41  type AcceptorGroup interface {
    42  	// Calling Accept() calls all of the registered acceptors for the relevant
    43  	// chain.
    44  	Acceptor
    45  
    46  	// RegisterAcceptor causes [acceptor] to be called every time an operation
    47  	// is accepted on chain [chainID].
    48  	// If [dieOnError], chain [chainID] stops if Accept returns a non-nil error.
    49  	RegisterAcceptor(chainID ids.ID, acceptorName string, acceptor Acceptor, dieOnError bool) error
    50  
    51  	// DeregisterAcceptor removes an acceptor from the group.
    52  	DeregisterAcceptor(chainID ids.ID, acceptorName string) error
    53  }
    54  
    55  type acceptorGroup struct {
    56  	log logging.Logger
    57  
    58  	lock sync.RWMutex
    59  	// Chain ID --> Acceptor Name --> Acceptor
    60  	acceptors map[ids.ID]map[string]acceptorWrapper
    61  }
    62  
    63  func NewAcceptorGroup(log logging.Logger) AcceptorGroup {
    64  	return &acceptorGroup{
    65  		log:       log,
    66  		acceptors: make(map[ids.ID]map[string]acceptorWrapper),
    67  	}
    68  }
    69  
    70  func (a *acceptorGroup) Accept(ctx *ConsensusContext, containerID ids.ID, container []byte) error {
    71  	a.lock.RLock()
    72  	defer a.lock.RUnlock()
    73  
    74  	for acceptorName, acceptor := range a.acceptors[ctx.ChainID] {
    75  		if err := acceptor.Accept(ctx, containerID, container); err != nil {
    76  			a.log.Error("failed accepting container",
    77  				zap.String("acceptorName", acceptorName),
    78  				zap.Stringer("chainID", ctx.ChainID),
    79  				zap.Stringer("containerID", containerID),
    80  				zap.Error(err),
    81  			)
    82  			if acceptor.dieOnError {
    83  				return fmt.Errorf("acceptor %s on chain %s erred while accepting %s: %w", acceptorName, ctx.ChainID, containerID, err)
    84  			}
    85  		}
    86  	}
    87  	return nil
    88  }
    89  
    90  func (a *acceptorGroup) RegisterAcceptor(chainID ids.ID, acceptorName string, acceptor Acceptor, dieOnError bool) error {
    91  	a.lock.Lock()
    92  	defer a.lock.Unlock()
    93  
    94  	acceptors, exist := a.acceptors[chainID]
    95  	if !exist {
    96  		acceptors = make(map[string]acceptorWrapper)
    97  		a.acceptors[chainID] = acceptors
    98  	}
    99  
   100  	if _, ok := acceptors[acceptorName]; ok {
   101  		return fmt.Errorf("callback %s already exists on chain %s", acceptorName, chainID)
   102  	}
   103  
   104  	acceptors[acceptorName] = acceptorWrapper{
   105  		Acceptor:   acceptor,
   106  		dieOnError: dieOnError,
   107  	}
   108  	return nil
   109  }
   110  
   111  func (a *acceptorGroup) DeregisterAcceptor(chainID ids.ID, acceptorName string) error {
   112  	a.lock.Lock()
   113  	defer a.lock.Unlock()
   114  
   115  	acceptors, exist := a.acceptors[chainID]
   116  	if !exist {
   117  		return fmt.Errorf("chain %s has no callbacks", chainID)
   118  	}
   119  
   120  	if _, ok := acceptors[acceptorName]; !ok {
   121  		return fmt.Errorf("callback %s does not exist on chain %s", acceptorName, chainID)
   122  	}
   123  
   124  	if len(acceptors) == 1 {
   125  		delete(a.acceptors, chainID)
   126  	} else {
   127  		delete(acceptors, acceptorName)
   128  	}
   129  	return nil
   130  }