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 }