package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/tendermint/go-event-meter"
|
|
"github.com/tendermint/go-wire"
|
|
|
|
"github.com/tendermint/netmon/types"
|
|
)
|
|
|
|
type NetMonResult interface {
|
|
}
|
|
|
|
// for wire.readReflect
|
|
var _ = wire.RegisterInterface(
|
|
struct{ NetMonResult }{},
|
|
wire.ConcreteType{&types.ChainAndValidatorSetIDs{}, 0x01},
|
|
wire.ConcreteType{&types.ChainState{}, 0x02},
|
|
wire.ConcreteType{&types.ValidatorSet{}, 0x10},
|
|
wire.ConcreteType{&types.Validator{}, 0x11},
|
|
wire.ConcreteType{&types.ValidatorConfig{}, 0x12},
|
|
wire.ConcreteType{&eventmeter.EventMetric{}, 0x20},
|
|
)
|
|
|
|
//---------------------------------------------
|
|
// global state and backend functions
|
|
|
|
// TODO: relax the locking (use RWMutex, reduce scope)
|
|
type TendermintNetwork struct {
|
|
mtx sync.Mutex
|
|
Chains map[string]*types.ChainState `json:"blockchains"`
|
|
ValSets map[string]*types.ValidatorSet `json:"validator_sets"`
|
|
}
|
|
|
|
func NewTendermintNetwork() *TendermintNetwork {
|
|
network := &TendermintNetwork{
|
|
Chains: make(map[string]*types.ChainState),
|
|
ValSets: make(map[string]*types.ValidatorSet),
|
|
}
|
|
return network
|
|
}
|
|
|
|
//------------
|
|
// Public Methods
|
|
|
|
func (tn *TendermintNetwork) Stop() {
|
|
tn.mtx.Lock()
|
|
defer tn.mtx.Unlock()
|
|
wg := new(sync.WaitGroup)
|
|
for _, c := range tn.Chains {
|
|
for _, v := range c.Config.Validators {
|
|
wg.Add(1)
|
|
go func(val *types.ValidatorState) {
|
|
val.Stop()
|
|
wg.Done()
|
|
}(v)
|
|
}
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
//-----------------------------------------------------------
|
|
// RPC funcs
|
|
//-----------------------------------------------------------
|
|
|
|
//------------------
|
|
// Status
|
|
|
|
// Returns sorted lists of all chains and validator sets
|
|
func (tn *TendermintNetwork) Status() (*types.ChainAndValidatorSetIDs, error) {
|
|
tn.mtx.Lock()
|
|
defer tn.mtx.Unlock()
|
|
chains := make([]string, len(tn.Chains))
|
|
valSets := make([]string, len(tn.ValSets))
|
|
i := 0
|
|
for chain, _ := range tn.Chains {
|
|
chains[i] = chain
|
|
i += 1
|
|
}
|
|
i = 0
|
|
for valset, _ := range tn.ValSets {
|
|
valSets[i] = valset
|
|
i += 1
|
|
}
|
|
sort.StringSlice(chains).Sort()
|
|
sort.StringSlice(valSets).Sort()
|
|
return &types.ChainAndValidatorSetIDs{
|
|
ChainIDs: chains,
|
|
ValidatorSetIDs: valSets,
|
|
}, nil
|
|
|
|
}
|
|
|
|
// NOTE: returned values should not be manipulated by callers as they are pointers to the state!
|
|
//------------------
|
|
// Blockchains
|
|
|
|
// Get the current state of a chain
|
|
func (tn *TendermintNetwork) GetChain(chainID string) (*types.ChainState, error) {
|
|
tn.mtx.Lock()
|
|
defer tn.mtx.Unlock()
|
|
chain, ok := tn.Chains[chainID]
|
|
if !ok {
|
|
return nil, fmt.Errorf("Unknown chain %s", chainID)
|
|
}
|
|
chain.Status.RealTimeUpdates()
|
|
return chain, nil
|
|
}
|
|
|
|
// Register a new chain on the network.
|
|
// For each validator, start a websocket connection to listen for new block events and record latency
|
|
func (tn *TendermintNetwork) RegisterChain(chainConfig *types.BlockchainConfig) (*types.ChainState, error) {
|
|
// Don't bother locking until we touch the TendermintNetwork object
|
|
|
|
chainState := &types.ChainState{
|
|
Config: chainConfig,
|
|
Status: types.NewBlockchainStatus(),
|
|
}
|
|
chainState.Status.NumValidators = len(chainConfig.Validators)
|
|
|
|
// so we can easily lookup validators by id rather than index
|
|
chainState.Config.PopulateValIDMap()
|
|
|
|
// start the event meter and listen for new blocks on each validator
|
|
for _, v := range chainConfig.Validators {
|
|
v.Status = &types.ValidatorStatus{}
|
|
|
|
var err error
|
|
RETRYLOOP:
|
|
for i := 0; i < 10; i++ {
|
|
if err = v.Start(); err == nil {
|
|
break RETRYLOOP
|
|
}
|
|
time.Sleep(time.Second)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error starting validator %s: %v", v.Config.Validator.ID, err)
|
|
}
|
|
|
|
// register callbacks for the validator
|
|
tn.registerCallbacks(chainState, v)
|
|
|
|
// the DisconnectCallback will set us offline and start a reconnect routine
|
|
chainState.Status.SetOnline(v, true)
|
|
|
|
// get/set the validator's pub key
|
|
// TODO: make this authenticate...
|
|
v.PubKey()
|
|
}
|
|
|
|
tn.mtx.Lock()
|
|
defer tn.mtx.Unlock()
|
|
tn.Chains[chainState.Config.ID] = chainState
|
|
return chainState, nil
|
|
}
|
|
|
|
//------------------
|
|
// Validators
|
|
|
|
func (tn *TendermintNetwork) GetValidatorSet(valSetID string) (*types.ValidatorSet, error) {
|
|
tn.mtx.Lock()
|
|
defer tn.mtx.Unlock()
|
|
valSet, ok := tn.ValSets[valSetID]
|
|
if !ok {
|
|
return nil, fmt.Errorf("Unknown validator set %s", valSetID)
|
|
}
|
|
return valSet, nil
|
|
}
|
|
|
|
func (tn *TendermintNetwork) RegisterValidatorSet(valSet *types.ValidatorSet) (*types.ValidatorSet, error) {
|
|
tn.mtx.Lock()
|
|
defer tn.mtx.Unlock()
|
|
tn.ValSets[valSet.ID] = valSet
|
|
return valSet, nil
|
|
}
|
|
|
|
func (tn *TendermintNetwork) GetValidator(valSetID, valID string) (*types.Validator, error) {
|
|
tn.mtx.Lock()
|
|
defer tn.mtx.Unlock()
|
|
valSet, ok := tn.ValSets[valSetID]
|
|
if !ok {
|
|
return nil, fmt.Errorf("Unknown validator set %s", valSetID)
|
|
}
|
|
val, err := valSet.Validator(valID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
// Update the validator's rpc address (for now its the only thing that can be updated!)
|
|
func (tn *TendermintNetwork) UpdateValidator(chainID, valID, rpcAddr string) (*types.ValidatorConfig, error) {
|
|
tn.mtx.Lock()
|
|
defer tn.mtx.Unlock()
|
|
val, err := tn.getChainVal(chainID, valID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
val.Config.UpdateRPCAddress(rpcAddr)
|
|
log.Debug("Update validator rpc address", "chain", chainID, "val", valID, "rpcAddr", rpcAddr)
|
|
return val.Config, nil
|
|
}
|
|
|
|
//------------------
|
|
// Event metering
|
|
|
|
func (tn *TendermintNetwork) StartMeter(chainID, valID, eventID string) error {
|
|
tn.mtx.Lock()
|
|
defer tn.mtx.Unlock()
|
|
val, err := tn.getChainVal(chainID, valID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return val.EventMeter().Subscribe(eventID, nil)
|
|
}
|
|
|
|
func (tn *TendermintNetwork) StopMeter(chainID, valID, eventID string) error {
|
|
tn.mtx.Lock()
|
|
defer tn.mtx.Unlock()
|
|
val, err := tn.getChainVal(chainID, valID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return val.EventMeter().Unsubscribe(eventID)
|
|
}
|
|
|
|
func (tn *TendermintNetwork) GetMeter(chainID, valID, eventID string) (*eventmeter.EventMetric, error) {
|
|
tn.mtx.Lock()
|
|
defer tn.mtx.Unlock()
|
|
val, err := tn.getChainVal(chainID, valID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return val.EventMeter().GetMetric(eventID)
|
|
}
|
|
|
|
// assumes lock is held
|
|
func (tn *TendermintNetwork) getChainVal(chainID, valID string) (*types.ValidatorState, error) {
|
|
chain, ok := tn.Chains[chainID]
|
|
if !ok {
|
|
return nil, fmt.Errorf("Unknown chain %s", chainID)
|
|
}
|
|
val, err := chain.Config.GetValidatorByID(valID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return val, nil
|
|
}
|