package native import ( "encoding/binary" "errors" "github.com/tutus-one/tutus-chain/pkg/core/dao" "github.com/tutus-one/tutus-chain/pkg/core/storage" "github.com/tutus-one/tutus-chain/pkg/util" ) // ARCH-001: Circuit Breaker System // Provides automatic protection against anomalous behavior by halting // contract operations when thresholds are exceeded. This is a critical // safety mechanism for production deployments. // CircuitState represents the current state of a circuit breaker. type CircuitState uint8 const ( // CircuitClosed means normal operation (requests flow through) CircuitClosed CircuitState = iota // CircuitOpen means halted (requests are rejected) CircuitOpen // CircuitHalfOpen means testing recovery (limited requests allowed) CircuitHalfOpen ) // TripReason identifies why a circuit breaker was tripped. type TripReason uint8 const ( TripReasonManual TripReason = iota TripReasonRateLimit TripReasonBalanceAnomaly TripReasonSecurityBreach TripReasonExternalDependency TripReasonResourceExhaustion TripReasonConsensusFailure ) // CircuitBreakerConfig contains settings for a circuit breaker. type CircuitBreakerConfig struct { // Name identifies this circuit breaker Name string // ContractID is the contract this breaker protects ContractID int32 // FailureThreshold is failures before tripping FailureThreshold uint32 // SuccessThreshold is successes needed to close after half-open SuccessThreshold uint32 // TimeoutBlocks is blocks before moving from open to half-open TimeoutBlocks uint32 // CooldownBlocks is minimum blocks between state changes CooldownBlocks uint32 // AutoRecover determines if breaker can auto-recover AutoRecover bool } // CircuitBreakerState tracks the current state of a circuit breaker. type CircuitBreakerState struct { State CircuitState FailureCount uint32 SuccessCount uint32 LastStateChange uint32 LastFailure uint32 TripReason TripReason TrippedBy util.Uint160 TotalTrips uint64 ConsecutiveTrips uint32 } // Storage prefixes for circuit breakers. const ( circuitPrefixConfig byte = 0xCB // name -> CircuitBreakerConfig circuitPrefixState byte = 0xCC // name -> CircuitBreakerState circuitPrefixHistory byte = 0xCD // name + timestamp -> TripEvent circuitPrefixGlobal byte = 0xCE // -> GlobalCircuitState ) // Circuit breaker errors. var ( ErrCircuitOpen = errors.New("circuit breaker is open") ErrCircuitHalfOpen = errors.New("circuit breaker is half-open, limited operations") ErrCircuitCooldown = errors.New("circuit breaker cooldown period active") ErrCircuitNotFound = errors.New("circuit breaker not found") ErrCircuitAutoRecover = errors.New("circuit breaker cannot be manually closed when auto-recover enabled") ) // Default circuit breaker settings. const ( DefaultFailureThreshold = 10 DefaultSuccessThreshold = 5 DefaultTimeoutBlocks = 100 DefaultCooldownBlocks = 10 ) // CircuitBreaker provides circuit breaker functionality for contracts. type CircuitBreaker struct { contractID int32 } // NewCircuitBreaker creates a new circuit breaker manager. func NewCircuitBreaker(contractID int32) *CircuitBreaker { return &CircuitBreaker{contractID: contractID} } // RegisterBreaker registers a new circuit breaker with configuration. func (cb *CircuitBreaker) RegisterBreaker(d *dao.Simple, cfg *CircuitBreakerConfig) { key := cb.makeConfigKey(cfg.Name) data := cb.serializeConfig(cfg) d.PutStorageItem(cb.contractID, key, data) // Initialize state as closed state := &CircuitBreakerState{ State: CircuitClosed, } cb.putState(d, cfg.Name, state) } // GetState retrieves the current state of a circuit breaker. func (cb *CircuitBreaker) GetState(d *dao.Simple, name string) *CircuitBreakerState { key := cb.makeStateKey(name) si := d.GetStorageItem(cb.contractID, key) if si == nil { return nil } return cb.deserializeState(si) } // GetConfig retrieves circuit breaker configuration. func (cb *CircuitBreaker) GetConfig(d *dao.Simple, name string) *CircuitBreakerConfig { key := cb.makeConfigKey(name) si := d.GetStorageItem(cb.contractID, key) if si == nil { return nil } return cb.deserializeConfig(si) } // AllowRequest checks if a request should be allowed through. func (cb *CircuitBreaker) AllowRequest(d *dao.Simple, name string, currentBlock uint32) error { state := cb.GetState(d, name) if state == nil { return ErrCircuitNotFound } switch state.State { case CircuitClosed: return nil case CircuitOpen: cfg := cb.GetConfig(d, name) if cfg == nil { return ErrCircuitNotFound } // Check if timeout has elapsed for potential recovery if cfg.AutoRecover && currentBlock >= state.LastStateChange+cfg.TimeoutBlocks { // Transition to half-open cb.transitionState(d, name, CircuitHalfOpen, currentBlock) return ErrCircuitHalfOpen } return ErrCircuitOpen case CircuitHalfOpen: return ErrCircuitHalfOpen } return nil } // RecordSuccess records a successful operation. func (cb *CircuitBreaker) RecordSuccess(d *dao.Simple, name string, currentBlock uint32) { state := cb.GetState(d, name) if state == nil { return } if state.State == CircuitHalfOpen { state.SuccessCount++ cfg := cb.GetConfig(d, name) if cfg != nil && state.SuccessCount >= cfg.SuccessThreshold { // Close the circuit cb.transitionState(d, name, CircuitClosed, currentBlock) return } } // Reset failure count on success when closed if state.State == CircuitClosed { state.FailureCount = 0 } cb.putState(d, name, state) } // RecordFailure records a failed operation. func (cb *CircuitBreaker) RecordFailure(d *dao.Simple, name string, currentBlock uint32, reason TripReason) { state := cb.GetState(d, name) if state == nil { return } state.FailureCount++ state.LastFailure = currentBlock cfg := cb.GetConfig(d, name) if cfg == nil { return } switch state.State { case CircuitClosed: if state.FailureCount >= cfg.FailureThreshold { cb.tripBreaker(d, name, currentBlock, reason, util.Uint160{}) } else { cb.putState(d, name, state) } case CircuitHalfOpen: // Any failure in half-open immediately trips cb.tripBreaker(d, name, currentBlock, reason, util.Uint160{}) } } // TripBreaker manually trips a circuit breaker. func (cb *CircuitBreaker) TripBreaker(d *dao.Simple, name string, currentBlock uint32, reason TripReason, tripper util.Uint160) error { state := cb.GetState(d, name) if state == nil { return ErrCircuitNotFound } cfg := cb.GetConfig(d, name) if cfg == nil { return ErrCircuitNotFound } // Check cooldown if currentBlock < state.LastStateChange+cfg.CooldownBlocks { return ErrCircuitCooldown } cb.tripBreaker(d, name, currentBlock, reason, tripper) return nil } // ResetBreaker manually resets a circuit breaker to closed. func (cb *CircuitBreaker) ResetBreaker(d *dao.Simple, name string, currentBlock uint32) error { state := cb.GetState(d, name) if state == nil { return ErrCircuitNotFound } cfg := cb.GetConfig(d, name) if cfg == nil { return ErrCircuitNotFound } // Check cooldown if currentBlock < state.LastStateChange+cfg.CooldownBlocks { return ErrCircuitCooldown } cb.transitionState(d, name, CircuitClosed, currentBlock) return nil } // tripBreaker internal method to trip the breaker. func (cb *CircuitBreaker) tripBreaker(d *dao.Simple, name string, currentBlock uint32, reason TripReason, tripper util.Uint160) { state := cb.GetState(d, name) if state == nil { return } state.State = CircuitOpen state.TripReason = reason state.TrippedBy = tripper state.LastStateChange = currentBlock state.TotalTrips++ state.ConsecutiveTrips++ state.SuccessCount = 0 cb.putState(d, name, state) cb.recordTripEvent(d, name, currentBlock, reason, tripper) } // transitionState changes the circuit state. func (cb *CircuitBreaker) transitionState(d *dao.Simple, name string, newState CircuitState, currentBlock uint32) { state := cb.GetState(d, name) if state == nil { return } state.State = newState state.LastStateChange = currentBlock if newState == CircuitClosed { state.FailureCount = 0 state.SuccessCount = 0 state.ConsecutiveTrips = 0 } else if newState == CircuitHalfOpen { state.SuccessCount = 0 } cb.putState(d, name, state) } // TripEvent records a circuit breaker trip for auditing. type TripEvent struct { Name string BlockHeight uint32 Reason TripReason TrippedBy util.Uint160 } // recordTripEvent stores a trip event in history. func (cb *CircuitBreaker) recordTripEvent(d *dao.Simple, name string, blockHeight uint32, reason TripReason, tripper util.Uint160) { key := make([]byte, 1+len(name)+4) key[0] = circuitPrefixHistory copy(key[1:], name) binary.BigEndian.PutUint32(key[1+len(name):], blockHeight) data := make([]byte, 21) data[0] = byte(reason) copy(data[1:], tripper.BytesBE()) d.PutStorageItem(cb.contractID, key, data) } // GetTripHistory retrieves trip history for a circuit breaker. func (cb *CircuitBreaker) GetTripHistory(d *dao.Simple, name string, limit int) []TripEvent { var events []TripEvent prefix := make([]byte, 1+len(name)) prefix[0] = circuitPrefixHistory copy(prefix[1:], name) count := 0 d.Seek(cb.contractID, storage.SeekRange{Prefix: prefix, Backwards: true}, func(k, v []byte) bool { if count >= limit || len(k) < 4 || len(v) < 21 { return false } event := TripEvent{ Name: name, BlockHeight: binary.BigEndian.Uint32(k[len(k)-4:]), Reason: TripReason(v[0]), } event.TrippedBy, _ = util.Uint160DecodeBytesBE(v[1:21]) events = append(events, event) count++ return true }) return events } // IsOpen returns true if the circuit is open (blocking requests). func (cb *CircuitBreaker) IsOpen(d *dao.Simple, name string) bool { state := cb.GetState(d, name) return state != nil && state.State == CircuitOpen } // IsClosed returns true if the circuit is closed (allowing requests). func (cb *CircuitBreaker) IsClosed(d *dao.Simple, name string) bool { state := cb.GetState(d, name) return state != nil && state.State == CircuitClosed } // GlobalCircuitState tracks system-wide circuit breaker status. type GlobalCircuitState struct { // EmergencyShutdown halts all protected operations EmergencyShutdown bool // ShutdownBlock is when emergency was triggered ShutdownBlock uint32 // ShutdownBy is who triggered emergency ShutdownBy util.Uint160 // ActiveBreakers is count of currently open breakers ActiveBreakers uint32 } // GetGlobalState retrieves the global circuit breaker state. func (cb *CircuitBreaker) GetGlobalState(d *dao.Simple) *GlobalCircuitState { key := []byte{circuitPrefixGlobal} si := d.GetStorageItem(cb.contractID, key) if si == nil { return &GlobalCircuitState{} } if len(si) < 26 { return &GlobalCircuitState{} } return &GlobalCircuitState{ EmergencyShutdown: si[0] == 1, ShutdownBlock: binary.BigEndian.Uint32(si[1:5]), ShutdownBy: mustDecodeUint160(si[5:25]), ActiveBreakers: binary.BigEndian.Uint32(si[25:29]), } } // SetEmergencyShutdown enables or disables emergency shutdown. func (cb *CircuitBreaker) SetEmergencyShutdown(d *dao.Simple, enabled bool, blockHeight uint32, triggeredBy util.Uint160) { state := cb.GetGlobalState(d) state.EmergencyShutdown = enabled if enabled { state.ShutdownBlock = blockHeight state.ShutdownBy = triggeredBy } key := []byte{circuitPrefixGlobal} data := make([]byte, 29) if state.EmergencyShutdown { data[0] = 1 } binary.BigEndian.PutUint32(data[1:5], state.ShutdownBlock) copy(data[5:25], state.ShutdownBy.BytesBE()) binary.BigEndian.PutUint32(data[25:29], state.ActiveBreakers) d.PutStorageItem(cb.contractID, key, data) } // IsEmergencyShutdown returns true if emergency shutdown is active. func (cb *CircuitBreaker) IsEmergencyShutdown(d *dao.Simple) bool { state := cb.GetGlobalState(d) return state.EmergencyShutdown } // Helper methods. func (cb *CircuitBreaker) makeConfigKey(name string) []byte { key := make([]byte, 1+len(name)) key[0] = circuitPrefixConfig copy(key[1:], name) return key } func (cb *CircuitBreaker) makeStateKey(name string) []byte { key := make([]byte, 1+len(name)) key[0] = circuitPrefixState copy(key[1:], name) return key } func (cb *CircuitBreaker) putState(d *dao.Simple, name string, state *CircuitBreakerState) { key := cb.makeStateKey(name) data := cb.serializeState(state) d.PutStorageItem(cb.contractID, key, data) } // Serialization helpers. func (cb *CircuitBreaker) serializeConfig(cfg *CircuitBreakerConfig) []byte { nameBytes := []byte(cfg.Name) data := make([]byte, 4+len(nameBytes)+4+16+1) offset := 0 binary.BigEndian.PutUint32(data[offset:], uint32(len(nameBytes))) offset += 4 copy(data[offset:], nameBytes) offset += len(nameBytes) binary.BigEndian.PutUint32(data[offset:], uint32(cfg.ContractID)) offset += 4 binary.BigEndian.PutUint32(data[offset:], cfg.FailureThreshold) offset += 4 binary.BigEndian.PutUint32(data[offset:], cfg.SuccessThreshold) offset += 4 binary.BigEndian.PutUint32(data[offset:], cfg.TimeoutBlocks) offset += 4 binary.BigEndian.PutUint32(data[offset:], cfg.CooldownBlocks) offset += 4 if cfg.AutoRecover { data[offset] = 1 } return data } func (cb *CircuitBreaker) deserializeConfig(data []byte) *CircuitBreakerConfig { if len(data) < 8 { return nil } cfg := &CircuitBreakerConfig{} offset := 0 nameLen := binary.BigEndian.Uint32(data[offset:]) offset += 4 if offset+int(nameLen) > len(data) { return nil } cfg.Name = string(data[offset : offset+int(nameLen)]) offset += int(nameLen) if offset+17 > len(data) { return nil } cfg.ContractID = int32(binary.BigEndian.Uint32(data[offset:])) offset += 4 cfg.FailureThreshold = binary.BigEndian.Uint32(data[offset:]) offset += 4 cfg.SuccessThreshold = binary.BigEndian.Uint32(data[offset:]) offset += 4 cfg.TimeoutBlocks = binary.BigEndian.Uint32(data[offset:]) offset += 4 cfg.CooldownBlocks = binary.BigEndian.Uint32(data[offset:]) offset += 4 cfg.AutoRecover = data[offset] == 1 return cfg } func (cb *CircuitBreaker) serializeState(state *CircuitBreakerState) []byte { data := make([]byte, 46) data[0] = byte(state.State) binary.BigEndian.PutUint32(data[1:5], state.FailureCount) binary.BigEndian.PutUint32(data[5:9], state.SuccessCount) binary.BigEndian.PutUint32(data[9:13], state.LastStateChange) binary.BigEndian.PutUint32(data[13:17], state.LastFailure) data[17] = byte(state.TripReason) copy(data[18:38], state.TrippedBy.BytesBE()) binary.BigEndian.PutUint64(data[38:46], state.TotalTrips) return data } func (cb *CircuitBreaker) deserializeState(data []byte) *CircuitBreakerState { if len(data) < 46 { return nil } state := &CircuitBreakerState{ State: CircuitState(data[0]), FailureCount: binary.BigEndian.Uint32(data[1:5]), SuccessCount: binary.BigEndian.Uint32(data[5:9]), LastStateChange: binary.BigEndian.Uint32(data[9:13]), LastFailure: binary.BigEndian.Uint32(data[13:17]), TripReason: TripReason(data[17]), TotalTrips: binary.BigEndian.Uint64(data[38:46]), } state.TrippedBy, _ = util.Uint160DecodeBytesBE(data[18:38]) return state } func mustDecodeUint160(data []byte) util.Uint160 { u, _ := util.Uint160DecodeBytesBE(data) return u } // StandardCircuitBreakers defines common circuit breaker names. var StandardCircuitBreakers = struct { VTSTransfers string VitaRegistration string CrossChainBridge string HealthRecords string InvestmentOps string GovernanceVoting string TributeAssessment string }{ VTSTransfers: "vts_transfers", VitaRegistration: "vita_registration", CrossChainBridge: "cross_chain_bridge", HealthRecords: "health_records", InvestmentOps: "investment_ops", GovernanceVoting: "governance_voting", TributeAssessment: "tribute_assessment", }