diff --git a/bft/bft.go b/bft/bft.go index 9d1b8a7032..0d2b89c814 100644 --- a/bft/bft.go +++ b/bft/bft.go @@ -95,12 +95,13 @@ func (b *BFT) Start() { } for { select { - // PHASE TIMEOUT + // EXECUTE PHASE // - This triggers when the phase's sleep time has expired, indicating that all expected messages for this phase should have already been received case <-b.PhaseTimer.C: func() { b.Controller.Lock() defer b.Controller.Unlock() + // handle the phase b.HandlePhase() }() @@ -108,22 +109,19 @@ func (b *BFT) Start() { // - This triggers when receiving a new Commit Block (QC) from either root-chainId (a) or the Target-ChainId (b) case resetBFT := <-b.ResetBFT: func() { - defer lib.TimeTrack(fmt.Sprintf("BFT.Start.Reset(), %d", len(b.ResetBFT)), time.Now()) b.Controller.Lock() defer b.Controller.Unlock() // if is a root-chain update reset back to round 0 but maintain locks to prevent 'fork attacks' // else increment the height and don't maintain locks - b.NewHeight(resetBFT.IsRootChainUpdate) - // if not a base chain update, reset the timers if !resetBFT.IsRootChainUpdate { b.log.Info("Reset BFT (NEW_HEIGHT)") - // start BFT over + b.NewHeight(false) } else { b.log.Info("Reset BFT (NEW_COMMITTEE)") - // start BFT over after sleeping RootChainPollMS - // add poll ms wait here to ensure ample time for all nested chains to be updated + b.NewHeight(true) } - b.SetWaitTimers(time.Duration(b.Config.RootChainPollMS)*time.Millisecond, resetBFT.ProcessTime) + // set the wait timers to start consensus + b.SetWaitTimers(time.Duration(b.Config.NewHeightTimeoutMs)*time.Millisecond, resetBFT.ProcessTime) }() } } @@ -647,7 +645,7 @@ func (b *BFT) SetTimerForNextPhase(processTime time.Duration) { default: b.Phase++ case CommitProcess: - // no op + return // don't set a timer case Pacemaker: b.Phase = Election } @@ -755,7 +753,7 @@ func (b *BFT) SelfIsValidator() bool { // RunVDF() runs the verifiable delay service func (b *BFT) RunVDF(seed []byte) (err lib.ErrorI) { if !b.Config.RunVDF { - b.log.Infof("RunVDF enabled in RunVDF") + b.log.Infof("VDF enabled") return } // if the vdf seed is nil @@ -885,5 +883,5 @@ const ( RoundInterrupt = lib.Phase_ROUND_INTERRUPT Pacemaker = lib.Phase_PACEMAKER - BlockTimeToVDFTargetCoefficient = .65 // how much the commit process time is reduced for VDF processing + BlockTimeToVDFTargetCoefficient = .50 // how much the commit process time is reduced for VDF processing ) diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 06dafcc57e..c1e02da5c4 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -94,8 +94,6 @@ func Start() { } // initialize the rpc server rpcServer := rpc.NewServer(app, config, l) - // set the remote callbacks - app.RootChainInfo.GetRemoteCallbacks = rpcServer.RemoteCallbacks // start the metrics server metrics.Start() // start the application diff --git a/cmd/rpc/admin_handlers.go b/cmd/rpc/admin_handlers.go index 4aed8c46d7..a0109253c4 100644 --- a/cmd/rpc/admin_handlers.go +++ b/cmd/rpc/admin_handlers.go @@ -352,18 +352,24 @@ func (s *Server) TransactionLockOrder(w http.ResponseWriter, r *http.Request, _ }) } +// TransactionCloseOrder completes a swap func (s *Server) TransactionCloseOrder(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { s.txHandler(w, r, func(p crypto.PrivateKeyI, ptr *txRequest) (lib.TransactionI, error) { // Retrieve the fee required for this type of transaction if err := s.getFeeFromState(w, ptr, fsm.MessageSendName, true); err != nil { return nil, err } - // If the remote callbacks not yet set - if s.remoteCallbacks == nil { - return nil, lib.ErrServerTimeout() + // create a variable for the root chain id + var rootChainId uint64 + // get a read only state + if err := s.readOnlyState(0, func(s *fsm.StateMachine) (err lib.ErrorI) { + rootChainId, err = s.GetRootChainId() + return + }); err != nil { + return nil, err } // Execute rpc call to the root chain - order, err := s.remoteCallbacks.Order(0, ptr.OrderId, s.config.ChainId) + order, err := s.rcManager.GetOrder(rootChainId, 0, ptr.OrderId, s.config.ChainId) if err != nil { return nil, err } diff --git a/cmd/rpc/query_handlers.go b/cmd/rpc/query_handlers.go index 7052e9265b..1082deebad 100644 --- a/cmd/rpc/query_handlers.go +++ b/cmd/rpc/query_handlers.go @@ -215,37 +215,7 @@ func (s *Server) Checkpoint(w http.ResponseWriter, r *http.Request, _ httprouter func (s *Server) RootChainInfo(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // Invoke helper with the HTTP request, response writer and an inline callback s.heightAndIdParams(w, r, func(s *fsm.StateMachine, id uint64) (interface{}, lib.ErrorI) { - // get the previous state machine height - lastSM, err := s.TimeMachine(s.Height() - 1) - if err != nil { - return nil, err - } - // get the committee - validatorSet, err := s.GetCommitteeMembers(id) - if err != nil { - return nil, err - } - // get the previous committee - // allow an error here to have size 0 validator sets - lastValidatorSet, _ := lastSM.GetCommitteeMembers(id) - // get the delegate lottery winner - lotteryWinner, err := s.LotteryWinner(id) - if err != nil { - return nil, err - } - // get the order book - orders, err := s.GetOrderBook(id) - if err != nil { - return nil, err - } - return &lib.RootChainInfo{ - RootChainId: s.Config.ChainId, - Height: s.Height(), - ValidatorSet: validatorSet, - LastValidatorSet: lastValidatorSet, - LotteryWinner: lotteryWinner, - Orders: orders, - }, nil + return s.GetRootChainInfo(id) }) } diff --git a/cmd/rpc/routes.go b/cmd/rpc/routes.go index 7a10fa2a6a..72e10fc942 100644 --- a/cmd/rpc/routes.go +++ b/cmd/rpc/routes.go @@ -55,6 +55,7 @@ const ( RootChainInfoRoutePath = "/v1/query/root-Chain-info" ValidatorSetRoutePath = "/v1/query/validator-set" CheckpointRoutePath = "/v1/query/checkpoint" + SubscribeRCInfoPath = "/v1/subscribe-rc-info" // debug DebugBlockedRoutePath = "/debug/blocked" DebugHeapRoutePath = "/debug/heap" @@ -177,6 +178,7 @@ const ( LogsRouteName = "logs" AddVoteRouteName = "add-vote" DelVoteRouteName = "del-vote" + SubscribeRCInfoName = "subscribe-rc-info" ) // routes contains the method and path for a canopy command @@ -270,6 +272,7 @@ var routePaths = routes{ LogsRouteName: {Method: http.MethodGet, Path: LogsRoutePath}, AddVoteRouteName: {Method: http.MethodPost, Path: AddVoteRoutePath}, DelVoteRouteName: {Method: http.MethodPost, Path: DelVoteRoutePath}, + SubscribeRCInfoName: {Method: http.MethodGet, Path: SubscribeRCInfoPath}, } // httpRouteHandlers is a custom type that maps strings to httprouter handle functions @@ -325,6 +328,7 @@ func createRouter(s *Server) *httprouter.Router { RootChainInfoRouteName: s.RootChainInfo, ValidatorSetRouteName: s.ValidatorSet, CheckpointRouteName: s.Checkpoint, + SubscribeRCInfoName: s.WebSocket, } // Initialize a new router using the httprouter package. diff --git a/cmd/rpc/server.go b/cmd/rpc/server.go index bc6410d991..6ff010d106 100644 --- a/cmd/rpc/server.go +++ b/cmd/rpc/server.go @@ -34,7 +34,6 @@ const ( SoftwareVersion = "0.0.0-alpha" ContentType = "Content-MessageType" ApplicationJSON = "application/json; charset=utf-8" - localhost = "localhost" walletStaticDir = "web/wallet/out" explorerStaticDir = "web/explorer/out" @@ -54,8 +53,8 @@ type Server struct { // Mutex for Poll handler pollMux *sync.RWMutex - // RemoteCallbacks to the root chain rpc - remoteCallbacks *lib.RemoteCallbacks + // handles interactions with the root chain rpc + rcManager *RCManager logger lib.LoggerI } @@ -66,6 +65,7 @@ func NewServer(controller *controller.Controller, config lib.Config, logger lib. controller: controller, config: config, logger: logger, + rcManager: NewRCManager(controller, config, logger), poll: make(fsm.Poll), pollMux: &sync.RWMutex{}, } @@ -79,8 +79,7 @@ func (s *Server) Start() { // Start tasks to update poll results and poll root chain information go s.updatePollResults() - go s.pollRootChainInfo() - + go s.rcManager.Start() go func() { // TODO remove DEBUG ONLY fileName := "heap1.out" for range time.Tick(time.Second * 10) { @@ -120,8 +119,11 @@ func (s *Server) startRPC(router *httprouter.Router, port string) { // Start RPC server s.logger.Infof("Starting RPC server at 0.0.0.0:%s", port) s.logger.Fatal((&http.Server{ - Addr: colon + port, - Handler: cor.Handler(http.TimeoutHandler(router, timeout, lib.ErrServerTimeout().Error())), + Addr: colon + port, + ReadHeaderTimeout: timeout, + ReadTimeout: timeout, + WriteTimeout: timeout, + Handler: cor.Handler(router), }).ListenAndServe().Error()) } @@ -162,102 +164,6 @@ func (s *Server) updatePollResults() { } } -// updateRootChainHeight queries and updates the root chain height -func (s *Server) updateRootChainHeight(state *fsm.StateMachine, rootChainHeight *uint64) (err lib.ErrorI) { - // get the consensus params from the app - consParams, err := state.GetParamsCons() - if err != nil { - return - } - // get the remote callbacks for the root chain id - s.remoteCallbacks, err = s.RemoteCallbacks(consParams.RootChainId) - if err != nil { - s.logger.Errorf("callbacks failed with err: %s") - return err - } - // query the base chain height - height, err := s.remoteCallbacks.Height() - if err != nil { - s.logger.Errorf("GetRootChainHeight failed with err") - return err - } - // check if a new height was received - if *height <= *rootChainHeight { - return - } - // update the root chain height - *rootChainHeight = *height - // if a new height received - s.logger.Infof("New RootChain height %d detected!", *rootChainHeight) - // execute the requests to get the base chain information - for retry := lib.NewRetry(s.config.RootChainPollMS, 10); retry.WaitAndDoRetry(); { - s.logger.Infof("Retrieved root height info for %d!", *rootChainHeight) - // retrieve the root-Chain info - rootChainInfo, e := s.remoteCallbacks.RootChainInfo(*rootChainHeight, s.config.ChainId) - if e == nil && rootChainInfo != nil && rootChainInfo.ValidatorSet.NumValidators != 0 { - // update the controller with new root-Chain info - s.controller.UpdateRootChainInfo(rootChainInfo) - s.logger.Info("Updated RootChain information") - break - } - s.logger.Errorf("GetRootChainInfo failed with err %s", e.Error()) - } - return -} - -// pollRootChainInfo() retrieves information from the root-Chain required for consensus -func (s *Server) pollRootChainInfo() { - // Track the root chain height - rootChainHeight := uint64(0) - // execute the loop every conf.RootChainPollMS duration - ticker := time.NewTicker(time.Duration(s.config.RootChainPollMS) * time.Millisecond) - for range ticker.C { - if err := func() (err error) { - // Create a read-only state machine context - err = s.readOnlyState(0, func(state *fsm.StateMachine) (err lib.ErrorI) { - // Update the root chain height - return s.updateRootChainHeight(state, &rootChainHeight) - }) - return - }(); err != nil { - s.logger.Warnf(err.Error()) - } - } -} - -// RemoteCallbacks() enables the retrieval of remote RPC API calls for a certain root chain id -func (s *Server) RemoteCallbacks(rootChainId uint64) (*lib.RemoteCallbacks, lib.ErrorI) { - // get the url for the root chain as set by the state - var rootChainUrl string - // for each item in the root chain config - for _, chain := range s.config.RootChain { - // if the chain id matches - if chain.ChainId == rootChainId { - // use that root chain url - rootChainUrl = chain.Url - } - } - // check if root chain url isn't empty - if rootChainUrl == "" { - s.logger.Errorf("Config.JSON missing RootChainID=%d failed with", rootChainId) - return nil, lib.ErrEmptyChainId() - } - // create a rpc client - rpcClient := NewClient(rootChainUrl, "") - // set the remote callbacks - return &lib.RemoteCallbacks{ - Height: rpcClient.Height, - RootChainInfo: rpcClient.RootChainInfo, - ValidatorSet: rpcClient.ValidatorSet, - IsValidDoubleSigner: rpcClient.IsValidDoubleSigner, - Lottery: rpcClient.Lottery, - Orders: rpcClient.Orders, - Order: rpcClient.Order, - Checkpoint: rpcClient.Checkpoint, - Transaction: rpcClient.Transaction, - }, nil -} - // startStaticFileServers starts a file server for the wallet and explorer func (s *Server) startStaticFileServers() { s.logger.Infof("Starting Web Wallet 🔑 http://localhost:%s ⬅️", s.config.WalletPort) diff --git a/cmd/rpc/sock.go b/cmd/rpc/sock.go new file mode 100644 index 0000000000..4940dc2339 --- /dev/null +++ b/cmd/rpc/sock.go @@ -0,0 +1,451 @@ +package rpc + +import ( + "fmt" + "github.com/canopy-network/canopy/controller" + "github.com/canopy-network/canopy/lib" + "github.com/gorilla/websocket" + "github.com/julienschmidt/httprouter" + "net/http" + "net/url" + "strconv" + "sync" + "time" +) + +/* This file implements the client & server logic for the 'root-chain info' and corresponding 'on-demand' calls to the rpc */ + +var _ lib.RCManagerI = new(RCManager) + +const chainIdParamName = "chainId" + +// RCManager handles a group of root-chain sock clients +type RCManager struct { + c lib.Config // the global node config + subscriptions map[uint64]*RCSubscription // chainId -> subscription + subscribers map[uint64]*RCSubscriber // chainId -> subscriber + l *sync.Mutex // thread safety + afterRCUpdate func(info *lib.RootChainInfo) // callback after the root chain info update + upgrader websocket.Upgrader // upgrade http connection to ws + log lib.LoggerI // stdout log +} + +// NewRCManager() constructs a new instance of a RCManager +func NewRCManager(controller *controller.Controller, config lib.Config, logger lib.LoggerI) (manager *RCManager) { + // create the manager + manager = &RCManager{ + c: config, + subscriptions: make(map[uint64]*RCSubscription), + subscribers: make(map[uint64]*RCSubscriber), + l: controller.Mutex, + afterRCUpdate: controller.UpdateRootChainInfo, + upgrader: websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}, + log: logger, + } + // set the manager in the controller + controller.RCManager = manager + // exit + return +} + +// Start() attempts to establish a websocket connection with each root chain +func (r *RCManager) Start() { + // for each rc in the config + for _, rc := range r.c.RootChain { + // dial each root chain + r.NewSubscription(rc) + } +} + +// Publish() writes the root-chain info to each client +func (r *RCManager) Publish(chainId uint64, info *lib.RootChainInfo) { + // convert the root-chain info to bytes + protoBytes, err := lib.Marshal(info) + if err != nil { + return + } + // for each ws client + for _, subscriber := range r.subscribers { + // skip if not this chain id + if subscriber.chainId != chainId { + continue + } + // publish to each client + if e := subscriber.conn.WriteMessage(websocket.BinaryMessage, protoBytes); e != nil { + subscriber.Stop(e) + } + } +} + +// ChainIds() returns a list of chainIds for subscribers +func (r *RCManager) ChainIds() (list []uint64) { + // de-duplicate the results + deDupe := lib.NewDeDuplicator[uint64]() + // for each client + for _, client := range r.subscribers { + // if the client chain id isn't empty and not duplicate + if client.chainId != 0 && !deDupe.Found(client.chainId) { + list = append(list, client.chainId) + } + } + return +} + +// GetHeight() returns the height from the root-chain +func (r *RCManager) GetHeight(rootChainId uint64) uint64 { + // check the map to see if the info exists + if sub, found := r.subscriptions[rootChainId]; found { + // exit with the height of the root-chain-info + return sub.Info.Height + } + return 0 +} + +// GetRootChainInfo() retrieves the root chain info from the root chain 'on-demain' +func (r *RCManager) GetRootChainInfo(rootChainId, chainId uint64) (rootChainInfo *lib.RootChainInfo, err lib.ErrorI) { + // lock for thread safety + r.l.Lock() + defer r.l.Unlock() + // if the root chain id is the same as the info + sub, found := r.subscriptions[rootChainId] + if !found { + // exit with 'not subscribed' error + return nil, lib.ErrNotSubscribed() + } + // get the info + info, err := sub.RootChainInfo(0, chainId) + if err != nil { + return nil, err + } + // update the info + sub.Info = info + // exit with the info + return +} + +// GetValidatorSet() returns the validator set from the root-chain +func (r *RCManager) GetValidatorSet(rootChainId, id, rootHeight uint64) (lib.ValidatorSet, lib.ErrorI) { + // if the root chain id is the same as the info + sub, found := r.subscriptions[rootChainId] + if !found { + // exit with 'not subscribed' error + return lib.ValidatorSet{}, lib.ErrNotSubscribed() + } + // if rootHeight is the same as the RootChainInfo height + if rootHeight == sub.Info.Height || rootHeight == 0 { + // exit with a copy the validator set + return lib.NewValidatorSet(sub.Info.ValidatorSet) + } + // if rootHeight is 1 before the RootChainInfo height + if rootHeight == sub.Info.Height-1 { + // exit with a copy of the previous validator set + return lib.NewValidatorSet(sub.Info.LastValidatorSet) + } + // warn of the remote RPC call to the root chain API + r.log.Warnf("Executing remote GetValidatorSet call with requested height=%d for rootChainId=%d", rootHeight, rootChainId) + // execute the remote RPC call to the root chain API + return sub.ValidatorSet(rootHeight, id) +} + +// GetOrders() returns the order book from the root-chain +func (r *RCManager) GetOrders(rootChainId, rootHeight, id uint64) (*lib.OrderBook, lib.ErrorI) { + // if the root chain id is the same as the info + sub, found := r.subscriptions[rootChainId] + if !found { + // exit with 'not subscribed' error + return nil, lib.ErrNotSubscribed() + } + // if the root chain id and height is the same as the info + if sub.Info.Height == rootHeight { + // exit with the order books from memory + return sub.Info.Orders, nil + } + // warn of the remote RPC call to the root chain API + r.log.Warnf("Executing remote GetOrders call with requested height=%d for rootChainId=%d", rootHeight, rootChainId) + // execute the remote call + books, err := sub.Orders(rootHeight, id) + // if an error occurred during the remote call + if err != nil { + // exit with error + return nil, err + } + // ensure the order book isn't empty + if books == nil || len(books.OrderBooks) == 0 { + // exit with error + return nil, lib.ErrEmptyOrderBook() + } + // exit with the first (and only) order book in the list + return books.OrderBooks[0], nil +} + +// Order() returns a specific order from the root order book +func (r *RCManager) GetOrder(rootChainId, height, orderId, chainId uint64) (*lib.SellOrder, lib.ErrorI) { + // if the root chain id is the same as the info + sub, found := r.subscriptions[rootChainId] + if !found { + // exit with 'not subscribed' error + return nil, lib.ErrNotSubscribed() + } + return sub.Order(height, orderId, chainId) +} + +// IsValidDoubleSigner() returns if an address is a valid double signer for a specific 'double sign height' +func (r *RCManager) IsValidDoubleSigner(rootChainId, height uint64, address string) (*bool, lib.ErrorI) { + // if the root chain id is the same as the info + sub, found := r.subscriptions[rootChainId] + if !found { + // exit with 'not subscribed' error + return nil, lib.ErrNotSubscribed() + } + // exit with the results of the remote RPC call to the API of the 'root chain' + return sub.IsValidDoubleSigner(height, address) +} + +// GetCheckpoint() returns the checkpoint if any for a specific chain height +// TODO should be able to get these from the file or the root-chain upon independence +func (r *RCManager) GetCheckpoint(rootChainId, height, chainId uint64) (blockHash lib.HexBytes, err lib.ErrorI) { + // if the root chain id is the same as the info + sub, found := r.subscriptions[rootChainId] + if !found { + // exit with 'not subscribed' error + return nil, lib.ErrNotSubscribed() + } + // exit with the results of the remote RPC call to the API of the 'root chain' + return sub.Checkpoint(height, chainId) +} + +// GetLotteryWinner() returns the winner of the delegate lottery from the root-chain +func (r *RCManager) GetLotteryWinner(rootChainId, height, id uint64) (*lib.LotteryWinner, lib.ErrorI) { + // if the root chain id is the same as the info + sub, found := r.subscriptions[rootChainId] + if !found { + // exit with 'not subscribed' error + return nil, lib.ErrNotSubscribed() + } + // if the root chain id and height is the same as the info + if sub.Info.Height == height { + // exit with the lottery winner + return sub.Info.LotteryWinner, nil + } + // warn of the remote RPC call to the API of the 'root chain' + r.log.Warnf("Executing remote Lottery call with requested height=%d and rootChainId=%d", height, rootChainId) + // exit with the results of the remote RPC call to the API of the 'root chain' + return sub.Lottery(height, id) +} + +// Transaction() executes a transaction on the root chain +func (r *RCManager) Transaction(rootChainId uint64, tx lib.TransactionI) (hash *string, err lib.ErrorI) { + // if the root chain id is the same as the info + sub, found := r.subscriptions[rootChainId] + if !found { + // exit with 'not subscribed' error + return nil, lib.ErrNotSubscribed() + } + return sub.Transaction(tx) +} + +// SUBSCRIPTION CODE BELOW (OUTBOUND) + +// RCSubscription (Root Chain Subscription) implements an efficient subscription to root chain info +type RCSubscription struct { + chainId uint64 // the chain id of the subscription + Info *lib.RootChainInfo // root-chain info cached from the publisher + manager *RCManager // a reference to the manager of the ws clients + conn *websocket.Conn // the underlying ws connection + *Client // use http for 'on-demand' requests + log lib.LoggerI // stdout log +} + +// Dial() dials a root chain via ws +func (r *RCManager) NewSubscription(rc lib.RootChain) { + // create a new web socket client + client := &RCSubscription{ + chainId: rc.ChainId, + Info: new(lib.RootChainInfo), + manager: r, + Client: NewClient(rc.Url, rc.Url), + log: r.log, + } + // start to connect with backoff + go client.dialWithBackoff(r.c.ChainId, rc) +} + +// dialWithBackoff() establishes a websocket connection given a root chain configuration +func (r *RCSubscription) dialWithBackoff(chainId uint64, config lib.RootChain) { + // parse the config + parsedUrl, err := url.Parse(config.Url) + if err != nil { + r.log.Fatal(err.Error()) + } + // get the host + host := parsedUrl.Host + // if the host is empty + if host == "" { + // fallback if url didn't have a scheme and was treated as a path + host = parsedUrl.Path + } + // create a URL to connect to the root chain with + u := url.URL{Scheme: "ws", Host: host, Path: SubscribeRCInfoPath, RawQuery: fmt.Sprintf("%s=%d", chainIdParamName, chainId)} + // create a new retry for backoff + retry := lib.NewRetry(uint64(time.Second.Milliseconds()), 25) + // until backoff fails or connection succeeds + for retry.WaitAndDoRetry() { + // log the connection + r.log.Infof("Connecting to rootChainId=%d @ %s", config.ChainId, u.String()) + // dial the url + conn, _, e := websocket.DefaultDialer.Dial(u.String(), nil) + if e == nil { + // set the connection + r.conn = conn + // start the listener + go r.Listen() + // add to the manager + r.manager.AddSubscription(r) + // exit + return + } + r.log.Error(e.Error()) + } +} + +// Listen() begins listening on the websockets client +func (r *RCSubscription) Listen() { + for { + // get the message from the buffer + _, bz, err := r.conn.ReadMessage() + if err != nil { + r.Stop(err) + return + } + // read the message into a rootChainInfo struct + newInfo := new(lib.RootChainInfo) + // unmarshal proto bytes into the message + if err = lib.Unmarshal(bz, newInfo); err != nil { + r.Stop(err) + return + } + // log the receipt of the root-chain info + r.log.Infof("Received info from RootChainId=%d and Height=%d", newInfo.RootChainId, newInfo.Height) + // thread safety + r.manager.l.Lock() + // update the root chain info + r.Info = newInfo + // execute the callback + r.manager.afterRCUpdate(newInfo) + // release + r.manager.l.Unlock() + } +} + +// Add() adds the client to the manager +func (r *RCManager) AddSubscription(subscription *RCSubscription) { + // lock for thread safety + r.l.Lock() + defer r.l.Unlock() + // add to the map + r.subscriptions[subscription.chainId] = subscription +} + +// RemoveSubscription() gracefully deletes a RC subscription +func (r *RCManager) RemoveSubscription(chainId uint64) { + // lock for thread safety + r.l.Lock() + defer r.l.Unlock() + // remove from the map + delete(r.subscriptions, chainId) + // check if the chainId == a configured root chain + for _, rc := range r.c.RootChain { + // if found + if rc.ChainId == chainId { + // re-dial + r.NewSubscription(rc) + // exit + return + } + } +} + +// Stop() stops the client +func (r *RCSubscription) Stop(err error) { + // log the error + r.log.Errorf("WS Failed with err: %s", err.Error()) + // close the connection + if err = r.conn.Close(); err != nil { + r.log.Error(err.Error()) + } + // remove from the manager + r.manager.RemoveSubscription(r.chainId) +} + +// SUBSCRIBER CODE BELOW (INBOUND) + +// RCSubscriber (Root Chain Subscriber) implements an efficient publishing service to nested chain subscribers +type RCSubscriber struct { + chainId uint64 // the chain id of the publisher + manager *RCManager // a reference to the manager of the ws clients + conn *websocket.Conn // the underlying ws connection + log lib.LoggerI // stdout log +} + +// WebSocket() upgrades a http request to a websockets connection +func (s *Server) WebSocket(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + _ = w.(http.Hijacker) + // upgrade the connection to websockets + conn, err := s.rcManager.upgrader.Upgrade(w, r, nil) + // if an error occurred during the upgrade + if err != nil { + // write the internal server error + write(w, err, http.StatusInternalServerError) + // log the issue + s.logger.Error(err.Error()) + // exit + return + } + // get chain id string from the parameter + chainIdStr := r.URL.Query().Get(chainIdParamName) + // get the chain id from the string + chainId, err := strconv.ParseUint(chainIdStr, 10, 64) + if err != nil { + http.Error(w, "invalid chain id", http.StatusBadRequest) + return + } + // create a new web sockets client + client := &RCSubscriber{ + chainId: chainId, + conn: conn, + manager: s.rcManager, + log: s.logger, + } + // add the connection to the manager + s.rcManager.AddSubscriber(client) +} + +// Add() adds the client to the manager +func (r *RCManager) AddSubscriber(subscriber *RCSubscriber) { + // lock for thread safety + r.l.Lock() + defer r.l.Unlock() + // add to the map + r.subscribers[subscriber.chainId] = subscriber +} + +// RemoveSubscriber() gracefully deletes a RC subscriber +func (r *RCManager) RemoveSubscriber(chainId uint64) { + // lock for thread safety + r.l.Lock() + defer r.l.Unlock() + // remove from the map + delete(r.subscribers, chainId) +} + +// Stop() stops the client +func (r *RCSubscriber) Stop(err error) { + // log the error + r.log.Errorf("WS Failed with err: %s", err.Error()) + // close the connection + if err = r.conn.Close(); err != nil { + r.log.Error(err.Error()) + } + // remove from the manager + r.manager.RemoveSubscriber(r.chainId) +} diff --git a/cmd/rpc/web/explorer/pages/index.js b/cmd/rpc/web/explorer/pages/index.js index f525d1e48f..f95a7d3f83 100644 --- a/cmd/rpc/web/explorer/pages/index.js +++ b/cmd/rpc/web/explorer/pages/index.js @@ -44,6 +44,7 @@ export default function Home() { } const consensusDuration = + settledValues[2].newHeightTimeoutMS + settledValues[2].electionTimeoutMS + settledValues[2].electionVoteTimeoutMS + settledValues[2].proposeTimeoutMS + diff --git a/controller/block.go b/controller/block.go index 5569612a1d..c82c8d5159 100644 --- a/controller/block.go +++ b/controller/block.go @@ -300,6 +300,20 @@ func (c *Controller) CommitCertificate(qc *lib.QuorumCertificate, block *lib.Blo // exit with error return } + // publish the root chain info to the nested chain subscribers + for _, id := range c.RCManager.ChainIds() { + // get the root chain info + info, e := c.FSM.GetRootChainInfo(id) + if e != nil { + // don't log 'no-validators' error as this is possible + if e.Error() != lib.ErrNoValidators().Error() { + c.log.Error(e.Error()) + } + continue + } + // publish root chain information + go c.RCManager.Publish(id, info) + } // update telemetry c.UpdateTelemetry(block, time.Since(start)) // exit @@ -368,7 +382,7 @@ func (c *Controller) HandlePeerBlock(msg *lib.BlockMessage, syncing bool) (*lib. // use checkpoints to protect against long-range attacks if qc.Header.Height%CheckpointFrequency == 0 { // get the checkpoint from the base chain (or file if independent) - checkpoint, err := c.RootChainInfo.GetCheckpoint(c.LoadRootChainId(qc.Header.Height), qc.Header.Height, c.Config.ChainId) + checkpoint, err := c.RCManager.GetCheckpoint(c.LoadRootChainId(qc.Header.Height), qc.Header.Height, c.Config.ChainId) // if getting the checkpoint failed if err != nil { // warn of the inability to get the checkpoint diff --git a/controller/consensus.go b/controller/consensus.go index 9e178ac87f..b51d97eeba 100644 --- a/controller/consensus.go +++ b/controller/consensus.go @@ -347,7 +347,7 @@ func (c *Controller) SendBlock(maxHeight, vdfIterations uint64, blockAndCert *li // INTERNAL HELPERS BELOW // UpdateP2PMustConnect() tells the P2P module which nodes are *required* to be connected to (usually fellow committee members or none if not in committee) -func (c *Controller) UpdateP2PMustConnect() { +func (c *Controller) UpdateP2PMustConnect(v *lib.ConsensusValidators) { // resolve the port to append based on the 'chain id' port, err := lib.ResolvePort(c.Config.ChainId) // if an error occurred @@ -358,14 +358,14 @@ func (c *Controller) UpdateP2PMustConnect() { return } // handle empty validator set - if c.RootChainInfo.ValidatorSet.ValidatorSet == nil { + if v.ValidatorSet == nil { // exit return } // define tracking variables for the 'must connect' peer list and if 'self' is a validator mustConnects, selfIsValidator := make([]*lib.PeerAddress, 0), false // for each member of the committee - for _, member := range c.RootChainInfo.ValidatorSet.ValidatorSet.ValidatorSet { + for _, member := range v.ValidatorSet { // if self is a validator if bytes.Equal(member.PublicKey, c.PublicKey) { // update the variable @@ -457,9 +457,20 @@ func (c *Controller) pollMaxHeight(backoff int) (max, minVDF uint64, syncingPeer // singleNodeNetwork() returns true if there are no other participants in the committee besides self func (c *Controller) singleNodeNetwork() bool { + c.Lock() + defer c.Unlock() + // get the root chain id from state + id, err := c.FSM.GetRootChainId() + if err != nil { + c.log.Fatalf(err.Error()) + } + // get the validator set + v, err := c.RCManager.GetValidatorSet(id, c.Config.ChainId, 0) + if err != nil { + c.log.Fatalf(err.Error()) + } // if self is the only validator, return true - return c.RootChainInfo.ValidatorSet.NumValidators == 0 || c.RootChainInfo.ValidatorSet.NumValidators == 1 && - bytes.Equal(c.RootChainInfo.ValidatorSet.ValidatorSet.ValidatorSet[0].PublicKey, c.PublicKey) + return v.NumValidators == 0 || (v.NumValidators == 1 && bytes.Equal(v.ValidatorSet.ValidatorSet[0].PublicKey, c.PublicKey)) } // syncingDone() checks if the syncing loop may complete for a specific chainId diff --git a/controller/controller.go b/controller/controller.go index 87c7be8ce8..60263d6012 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -29,10 +29,10 @@ type Controller struct { Consensus *bft.BFT // the async consensus process between the committee members for the chain P2P *p2p.P2P // the P2P module the node uses to connect to the network - RootChainInfo lib.RootChainInfo // the latest information from the 'root chain' - isSyncing *atomic.Bool // is the chain currently being downloaded from peers - log lib.LoggerI // object for logging - sync.Mutex // mutex for thread safety + RCManager lib.RCManagerI // the data manager for the 'root chain' + isSyncing *atomic.Bool // is the chain currently being downloaded from peers + log lib.LoggerI // object for logging + *sync.Mutex // mutex for thread safety } // New() creates a new instance of a Controller, this is the entry point when initializing an instance of a Canopy application @@ -53,19 +53,18 @@ func New(fsm *fsm.StateMachine, c lib.Config, valKey crypto.PrivateKeyI, metrics } // create the controller controller = &Controller{ - Address: valKey.PublicKey().Address().Bytes(), - PublicKey: valKey.PublicKey().Bytes(), - PrivateKey: valKey, - Config: c, - Metrics: metrics, - FSM: fsm, - Mempool: mempool, - Consensus: nil, - P2P: p2p.New(valKey, maxMembersPerCommittee, metrics, c, l), - RootChainInfo: lib.RootChainInfo{Log: l}, - isSyncing: &atomic.Bool{}, - log: l, - Mutex: sync.Mutex{}, + Address: valKey.PublicKey().Address().Bytes(), + PublicKey: valKey.PublicKey().Bytes(), + PrivateKey: valKey, + Config: c, + Metrics: metrics, + FSM: fsm, + Mempool: mempool, + Consensus: nil, + P2P: p2p.New(valKey, maxMembersPerCommittee, metrics, c, l), + isSyncing: &atomic.Bool{}, + log: l, + Mutex: &sync.Mutex{}, } // initialize the consensus in the controller, passing a reference to itself controller.Consensus, err = bft.New(c, valKey, fsm.Height(), fsm.Height()-1, controller, c.RunVDF, metrics, l) @@ -80,6 +79,10 @@ func New(fsm *fsm.StateMachine, c lib.Config, valKey crypto.PrivateKeyI, metrics // Start() begins the Controller service func (c *Controller) Start() { + rootChainId, err := c.FSM.GetRootChainId() + if err != nil { + c.log.Fatal(err.Error()) + } // in a non-blocking sub-function go func() { // start the P2P module @@ -92,10 +95,12 @@ func (c *Controller) Start() { defer t.Stop() // each time the timer fires for range t.C { - // if the root chain height is updated - if c.RootChainInfo.GetHeight() != 0 { + // get the root chain info from the rpc + _, e := c.RCManager.GetRootChainInfo(rootChainId, c.Config.ChainId) + if e == nil { break } + c.log.Error(e.Error()) // log error but continue } // start internal Controller listeners for P2P c.StartListeners() @@ -138,18 +143,15 @@ func (c *Controller) Stop() { func (c *Controller) UpdateRootChainInfo(info *lib.RootChainInfo) { c.log.Debugf("Updating root chain info") defer lib.TimeTrack("UpdateRootChainInfo", time.Now()) - // lock the controller for thread safety - c.Lock() - // unlock when the function completes - defer c.Unlock() - // use the logger from the controller - info.Log = c.RootChainInfo.Log - // use the get remote callback 'callback' from the controller - info.GetRemoteCallbacks = c.RootChainInfo.GetRemoteCallbacks - // update the root chain info - c.RootChainInfo = *info + // ensure this root chain is active + activeRootChainId, _ := c.FSM.GetRootChainId() + // if inactive + if activeRootChainId != info.RootChainId { + c.log.Debugf("Detected inactive root-chain update at rootChainId=%d", info.RootChainId) + return + } // if the last validator set is empty - if info.LastValidatorSet.NumValidators == 0 { + if info.LastValidatorSet == nil || len(info.LastValidatorSet.ValidatorSet) == 0 { // signal to reset consensus and start a new height c.Consensus.ResetBFT <- bft.ResetBFT{IsRootChainUpdate: false} } else { @@ -158,17 +160,17 @@ func (c *Controller) UpdateRootChainInfo(info *lib.RootChainInfo) { c.Consensus.ResetBFT <- bft.ResetBFT{IsRootChainUpdate: true} } // update the peer 'must connect' - c.UpdateP2PMustConnect() + c.UpdateP2PMustConnect(info.ValidatorSet) } // LoadCommittee() gets the ValidatorSet that is authorized to come to Consensus agreement on the Proposal for a specific height/chainId func (c *Controller) LoadCommittee(rootChainId, rootHeight uint64) (lib.ValidatorSet, lib.ErrorI) { - return c.RootChainInfo.GetValidatorSet(rootChainId, c.Config.ChainId, rootHeight) + return c.RCManager.GetValidatorSet(rootChainId, c.Config.ChainId, rootHeight) } // LoadRootChainOrderBook() gets the order book from the root-chain func (c *Controller) LoadRootChainOrderBook(rootHeight uint64) (*lib.OrderBook, lib.ErrorI) { - return c.RootChainInfo.GetOrders(c.LoadRootChainId(c.ChainHeight()), rootHeight, c.Config.ChainId) + return c.RCManager.GetOrders(c.LoadRootChainId(c.ChainHeight()), rootHeight, c.Config.ChainId) } // GetRootChainLotteryWinner() gets the pseudorandomly selected delegate to reward and their cut @@ -181,13 +183,13 @@ func (c *Controller) GetRootChainLotteryWinner(rootHeight uint64) (winner *lib.L return nil, err } // execute the remote call - return c.RootChainInfo.GetLotteryWinner(rootChainId, rootHeight, c.Config.ChainId) + return c.RCManager.GetLotteryWinner(rootChainId, rootHeight, c.Config.ChainId) } // IsValidDoubleSigner() checks if the double signer is valid at a certain double sign height func (c *Controller) IsValidDoubleSigner(rootHeight uint64, address []byte) bool { // do a remote call to the root chain to see if the double signer is valid - isValidDoubleSigner, err := c.RootChainInfo.IsValidDoubleSigner(c.LoadRootChainId(c.ChainHeight()), rootHeight, lib.BytesToString(address)) + isValidDoubleSigner, err := c.RCManager.IsValidDoubleSigner(c.LoadRootChainId(c.ChainHeight()), rootHeight, lib.BytesToString(address)) // if an error occurred during the remote call if err != nil { // log the error @@ -294,7 +296,10 @@ func (c *Controller) LoadCommitteeData() (data *lib.CommitteeData, err lib.Error func (c *Controller) Syncing() *atomic.Bool { return c.isSyncing } // RootChainHeight() returns the height of the canopy root-chain -func (c *Controller) RootChainHeight() uint64 { return c.RootChainInfo.GetHeight() } +func (c *Controller) RootChainHeight() uint64 { + chainId, _ := c.FSM.GetRootChainId() + return c.RCManager.GetHeight(chainId) +} // ChainHeight() returns the height of this target chain func (c *Controller) ChainHeight() uint64 { return c.FSM.Height() } diff --git a/controller/result.go b/controller/result.go index 64dec9467e..a03b9883c9 100644 --- a/controller/result.go +++ b/controller/result.go @@ -51,17 +51,8 @@ func (c *Controller) SendCertificateResultsTx(qc *lib.QuorumCertificate) { // exit return } - // get a rpc client for the root chain id - rpcClient, err := c.RootChainInfo.GetRemoteCallbacks(rootChainId) - // if an error occurred getting the callback - if err != nil { - // log the error - c.log.Errorf("Creating auto-certificate-results-txn failed with err: %s", err.Error()) - // exit - return - } // handle the transaction on the root-chain - hash, err := rpcClient.Transaction(tx) + hash, err := c.RCManager.Transaction(rootChainId, tx) // if an error occurred during the tx submission if err != nil { // log the error diff --git a/fsm/state.go b/fsm/state.go index 460e28892d..96b65cfa5e 100644 --- a/fsm/state.go +++ b/fsm/state.go @@ -330,6 +330,42 @@ func (s *StateMachine) GetMaxBlockSize() (uint64, lib.ErrorI) { return consParams.BlockSize, nil } +// GetRootChainInfo() returns the 'need-to-know' information for a nested chain +func (s *StateMachine) GetRootChainInfo(id uint64) (*lib.RootChainInfo, lib.ErrorI) { + // get the previous state machine height + lastSM, err := s.TimeMachine(s.Height() - 1) + if err != nil { + return nil, err + } + // get the committee + validatorSet, err := s.GetCommitteeMembers(id) + if err != nil { + return nil, err + } + // get the previous committee + // allow an error here to have size 0 validator sets + lastValidatorSet, _ := lastSM.GetCommitteeMembers(id) + // get the delegate lottery winner + lotteryWinner, err := s.LotteryWinner(id) + if err != nil { + return nil, err + } + // get the order book + orders, err := s.GetOrderBook(id) + if err != nil { + return nil, err + } + // return the root chain info + return &lib.RootChainInfo{ + RootChainId: s.Config.ChainId, + Height: s.height, + ValidatorSet: validatorSet.ValidatorSet, + LastValidatorSet: lastValidatorSet.ValidatorSet, + LotteryWinner: lotteryWinner, + Orders: orders, + }, nil +} + // Copy() makes a clone of the state machine // this feature is used in mempool operation to be able to maintain a parallel ephemeral state without affecting the underlying state machine func (s *StateMachine) Copy() (*StateMachine, lib.ErrorI) { diff --git a/fsm/validator.pb.go b/fsm/validator.pb.go index cc60d44401..4cbd28089d 100644 --- a/fsm/validator.pb.go +++ b/fsm/validator.pb.go @@ -177,6 +177,66 @@ func (x *Validator) GetCompound() bool { return false } +// ValidatorsList is stored as a single blob (instead of using prefixed keys) for the following reasons: +// +// - Filesystem database iterators (e.g., in LevelDB/BadgerDB) introduce significant overhead and are often slow, +// especially when reading or updating large sets of keys every block. +// +// - The total number of validators is expected to remain below 100,000, and each entry is relatively small (~200 bytes), +// making a full in-memory deserialization manageable and performant. +// +// - Validators are expected to participate in most committees, so the majority of the list is relevant to each context, +// and filtering from a single list is faster than multiple disk reads. +// +// - Storing separate, per-committee lists (especially if sorted and updated every block) would create substantial +// additional write and storage overhead compared to maintaining a single, compact list. +type ValidatorsList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + List []*Validator `protobuf:"bytes,1,rep,name=List,proto3" json:"List,omitempty"` +} + +func (x *ValidatorsList) Reset() { + *x = ValidatorsList{} + if protoimpl.UnsafeEnabled { + mi := &file_validator_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ValidatorsList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ValidatorsList) ProtoMessage() {} + +func (x *ValidatorsList) ProtoReflect() protoreflect.Message { + mi := &file_validator_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ValidatorsList.ProtoReflect.Descriptor instead. +func (*ValidatorsList) Descriptor() ([]byte, []int) { + return file_validator_proto_rawDescGZIP(), []int{1} +} + +func (x *ValidatorsList) GetList() []*Validator { + if x != nil { + return x.List + } + return nil +} + // NonSignerInfo is information that tracks the number of blocks not signed by the Validator within the Non-Sign-Window type NonSigner struct { state protoimpl.MessageState @@ -192,7 +252,7 @@ type NonSigner struct { func (x *NonSigner) Reset() { *x = NonSigner{} if protoimpl.UnsafeEnabled { - mi := &file_validator_proto_msgTypes[1] + mi := &file_validator_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -205,7 +265,7 @@ func (x *NonSigner) String() string { func (*NonSigner) ProtoMessage() {} func (x *NonSigner) ProtoReflect() protoreflect.Message { - mi := &file_validator_proto_msgTypes[1] + mi := &file_validator_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -218,7 +278,7 @@ func (x *NonSigner) ProtoReflect() protoreflect.Message { // Deprecated: Use NonSigner.ProtoReflect.Descriptor instead. func (*NonSigner) Descriptor() ([]byte, []int) { - return file_validator_proto_rawDescGZIP(), []int{1} + return file_validator_proto_rawDescGZIP(), []int{2} } func (x *NonSigner) GetAddress() []byte { @@ -235,6 +295,54 @@ func (x *NonSigner) GetCounter() uint64 { return 0 } +// NonSignerList is a list of information that tracks the number of blocks not signed by the Validator within the Non-Sign-Window +type NonSignerList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + List []*NonSigner `protobuf:"bytes,1,rep,name=List,proto3" json:"List,omitempty"` +} + +func (x *NonSignerList) Reset() { + *x = NonSignerList{} + if protoimpl.UnsafeEnabled { + mi := &file_validator_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NonSignerList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NonSignerList) ProtoMessage() {} + +func (x *NonSignerList) ProtoReflect() protoreflect.Message { + mi := &file_validator_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NonSignerList.ProtoReflect.Descriptor instead. +func (*NonSignerList) Descriptor() ([]byte, []int) { + return file_validator_proto_rawDescGZIP(), []int{3} +} + +func (x *NonSignerList) GetList() []*NonSigner { + if x != nil { + return x.List + } + return nil +} + var File_validator_proto protoreflect.FileDescriptor var file_validator_proto_rawDesc = []byte{ @@ -260,14 +368,21 @@ var file_validator_proto_rawDesc = []byte{ 0x74, 0x70, 0x75, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x3f, 0x0a, 0x09, - 0x4e, 0x6f, 0x6e, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x42, 0x26, 0x5a, - 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61, 0x6e, 0x6f, - 0x70, 0x79, 0x2d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x63, 0x61, 0x6e, 0x6f, 0x70, - 0x79, 0x2f, 0x66, 0x73, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x28, 0x08, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x36, 0x0a, 0x0e, + 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, + 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x04, + 0x4c, 0x69, 0x73, 0x74, 0x22, 0x3f, 0x0a, 0x09, 0x4e, 0x6f, 0x6e, 0x53, 0x69, 0x67, 0x6e, 0x65, + 0x72, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x65, 0x72, 0x22, 0x35, 0x0a, 0x0d, 0x4e, 0x6f, 0x6e, 0x53, 0x69, 0x67, 0x6e, + 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4e, 0x6f, 0x6e, + 0x53, 0x69, 0x67, 0x6e, 0x65, 0x72, 0x52, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x26, 0x5a, 0x24, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61, 0x6e, 0x6f, 0x70, + 0x79, 0x2d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x63, 0x61, 0x6e, 0x6f, 0x70, 0x79, + 0x2f, 0x66, 0x73, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -282,17 +397,21 @@ func file_validator_proto_rawDescGZIP() []byte { return file_validator_proto_rawDescData } -var file_validator_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_validator_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_validator_proto_goTypes = []interface{}{ - (*Validator)(nil), // 0: types.Validator - (*NonSigner)(nil), // 1: types.NonSigner + (*Validator)(nil), // 0: types.Validator + (*ValidatorsList)(nil), // 1: types.ValidatorsList + (*NonSigner)(nil), // 2: types.NonSigner + (*NonSignerList)(nil), // 3: types.NonSignerList } var file_validator_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 0, // 0: types.ValidatorsList.List:type_name -> types.Validator + 2, // 1: types.NonSignerList.List:type_name -> types.NonSigner + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_validator_proto_init() } @@ -314,6 +433,18 @@ func file_validator_proto_init() { } } file_validator_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ValidatorsList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_validator_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*NonSigner); i { case 0: return &v.state @@ -325,6 +456,18 @@ func file_validator_proto_init() { return nil } } + file_validator_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NonSignerList); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -332,7 +475,7 @@ func file_validator_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_validator_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 4, NumExtensions: 0, NumServices: 0, }, diff --git a/go.mod b/go.mod index e433eed118..41960fa51d 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect github.com/google/flatbuffers v1.12.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/holiman/uint256 v1.3.1 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/kilic/bls12-381 v0.1.0 // indirect diff --git a/go.sum b/go.sum index c1a014c1f0..086d5e0e8b 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs= github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= diff --git a/lib/.proto/consensus.proto b/lib/.proto/consensus.proto index a898cca0ec..a81c27dbe4 100644 --- a/lib/.proto/consensus.proto +++ b/lib/.proto/consensus.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package types; option go_package = "github.com/canopy-network/canopy/lib"; +import "swap.proto"; // ***************************************************************************************************** // This file is auto-generated from source files in `/lib/.proto/*` using Protocol Buffers (protobuf) // @@ -141,3 +142,28 @@ message ConsensusValidators { // validator_set: is the actual list of Validators and their respective repeated ConsensusValidator ValidatorSet = 1; // @gotags: json:"validatorSet" } + +// LotteryWinner is a structure that holds the subject of a pseudorandom selection and their % cut of the reward +// This is used for delegation + sub-delegation + sub-validator earnings +message LotteryWinner { + // winner: the 20 byte address of the selected actor + bytes winner = 1; // @gotags: json:"winner" + // cut: the percent cut of the rewards + uint64 cut = 2; // @gotags json:"cut" +} + +// RootChainInfo maintains root-chain data needed for consensus +message RootChainInfo { + // root_chain_id: the chain id of the root chain + uint64 root_chain_id = 1; // @gotags: json:"rootChainId" + // height: the block height of the root chain + uint64 height = 2; // @gotags: json:"height" + // validator_set: the current validator set + ConsensusValidators validator_set = 3; // @gotags: json:"validatorSet" + // last_validator_set: the validator set of the previous height + ConsensusValidators last_validator_set = 4; // @gotags: json:"lastValidatorSet" + // lottery_winner: the selected delegate/pseudo-validator who receives rewards + LotteryWinner lottery_winner = 5; + // orders: the swap order book from the 'root chain' for the 'nested chain' + OrderBook orders = 6; // @gotags: json:"orders" +} diff --git a/lib/config.go b/lib/config.go index 803fa63c66..b5af85b1a4 100644 --- a/lib/config.go +++ b/lib/config.go @@ -101,14 +101,13 @@ func (m *MainConfig) GetLogLevel() int32 { // RPC CONFIG BELOW type RPCConfig struct { - WalletPort string `json:"walletPort"` // the port where the web wallet is hosted - ExplorerPort string `json:"explorerPort"` // the port where the block explorer is hosted - RPCPort string `json:"rpcPort"` // the port where the rpc server is hosted - AdminPort string `json:"adminPort"` // the port where the admin rpc server is hosted - RPCUrl string `json:"rpcURL"` // the url where the rpc server is hosted - AdminRPCUrl string `json:"adminRPCUrl"` // the url where the admin rpc server is hosted - RootChainPollMS uint64 `json:"rootChainPollMS"` // how often to poll the base chain in milliseconds - TimeoutS int `json:"timeoutS"` // the rpc request timeout in seconds + WalletPort string `json:"walletPort"` // the port where the web wallet is hosted + ExplorerPort string `json:"explorerPort"` // the port where the block explorer is hosted + RPCPort string `json:"rpcPort"` // the port where the rpc server is hosted + AdminPort string `json:"adminPort"` // the port where the admin rpc server is hosted + RPCUrl string `json:"rpcURL"` // the url where the rpc server is hosted + AdminRPCUrl string `json:"adminRPCUrl"` // the url where the admin rpc server is hosted + TimeoutS int `json:"timeoutS"` // the rpc request timeout in seconds } // RootChain defines a rpc url to a possible 'root chain' which is used if the governance parameter RootChainId == ChainId @@ -120,14 +119,13 @@ type RootChain struct { // DefaultRPCConfig() sets rpc url to localhost and sets wallet, explorer, rpc, and admin ports from [50000-50003] func DefaultRPCConfig() RPCConfig { return RPCConfig{ - WalletPort: "50000", // find the wallet on localhost:50000 - ExplorerPort: "50001", // find the explorer on localhost:50001 - RPCPort: "50002", // the rpc is served on localhost:50002 - AdminPort: "50003", // the admin rpc is served on localhost:50003 - RPCUrl: "http://localhost:50002", // use a local rpc by default - AdminRPCUrl: "http://localhost:50003", // use a local admin rpc by default - RootChainPollMS: 333, // poll the root chain every 1/3 second - TimeoutS: 3, // the rpc timeout is 3 seconds + WalletPort: "50000", // find the wallet on localhost:50000 + ExplorerPort: "50001", // find the explorer on localhost:50001 + RPCPort: "50002", // the rpc is served on localhost:50002 + AdminPort: "50003", // the admin rpc is served on localhost:50003 + RPCUrl: "http://localhost:50002", // use a local rpc by default + AdminRPCUrl: "http://localhost:50003", // use a local admin rpc by default + TimeoutS: 3, // the rpc timeout is 3 seconds } } @@ -147,6 +145,7 @@ func DefaultStateMachineConfig() StateMachineConfig { return StateMachineConfig{ // - async faults may lead to extended block time // - social consensus dictates BlockTime for the protocol - being too fast or too slow can lead to Non-Signing and Consensus failures type ConsensusConfig struct { + NewHeightTimeoutMs int `json:"newHeightTimeoutMS"` // how long (in milliseconds) the replica sleeps before moving to the ELECTION phase ElectionTimeoutMS int `json:"electionTimeoutMS"` // minus VRF creation time (if Candidate), is how long (in milliseconds) the replica sleeps before moving to ELECTION-VOTE phase ElectionVoteTimeoutMS int `json:"electionVoteTimeoutMS"` // minus QC validation + vote time, is how long (in milliseconds) the replica sleeps before moving to PROPOSE phase ProposeTimeoutMS int `json:"proposeTimeoutMS"` // minus Proposal creation time (if Leader), is how long (in milliseconds) the replica sleeps before moving to PROPOSE-VOTE phase @@ -160,19 +159,21 @@ type ConsensusConfig struct { // DefaultConsensusConfig() configures the block time func DefaultConsensusConfig() ConsensusConfig { return ConsensusConfig{ + NewHeightTimeoutMs: 2000, // 2 seconds ElectionTimeoutMS: 2000, // 2 seconds ElectionVoteTimeoutMS: 2000, // 2 seconds - ProposeTimeoutMS: 3000, // 3 seconds - ProposeVoteTimeoutMS: 3000, // 3 seconds + ProposeTimeoutMS: 4000, // 4 seconds + ProposeVoteTimeoutMS: 4000, // 4 seconds PrecommitTimeoutMS: 2000, // 2 seconds PrecommitVoteTimeoutMS: 2000, // 2 seconds - CommitTimeoutMS: 6000, // 6 seconds + CommitTimeoutMS: 2000, // 2 seconds } } // BlockTimeMS() returns the expected block time in milliseconds func (c *ConsensusConfig) BlockTimeMS() int { - return c.ElectionTimeoutMS + + return c.NewHeightTimeoutMs + + c.ElectionTimeoutMS + c.ElectionVoteTimeoutMS + c.ProposeTimeoutMS + c.ProposeVoteTimeoutMS + diff --git a/lib/consensus.go b/lib/consensus.go index 40481916ba..b335c829c0 100644 --- a/lib/consensus.go +++ b/lib/consensus.go @@ -98,214 +98,19 @@ func (vs *ValidatorSet) GetValidatorAndIdx(targetPublicKey []byte) (val *Consens return nil, 0, ErrValidatorNotInSet(targetPublicKey) } -// RootChainInfo maintains root-chain data needed for consensus -type RootChainInfo struct { - RootChainId uint64 `json:"rootChainId"` // the id of the root chain - Height uint64 `json:"height"` // the height of the root chain - ValidatorSet ValidatorSet `json:"validatorSet"` // the validator set for this 'nested chain' as determined by the 'root chain' - LastValidatorSet ValidatorSet `json:"lastValidatorSet"` // the previous validator set for this 'nested chain' as previously determined by the 'root chain' - LotteryWinner *LotteryWinner `json:"lotteryWinner"` // the 'delegate' lottery winner who may be a 'reward recipient' in the certificate result - Orders *OrderBook `json:"orders"` // the swap order book from the 'root chain' for the 'nested chain' - GetRemoteCallbacks GetRemoteCallbacks `json:"remoteCallbacks"` // the rpc callbacks for the remote chain - Log LoggerI `json:"log"` // the stout logger -} - -// GetRemoteCallbacks is a callback to get the 'rpc client' for the root chain id -type GetRemoteCallbacks func(rootChainId uint64) (*RemoteCallbacks, ErrorI) - -// RemoteCallbacks are fallback rpc callbacks to the root-chain -type RemoteCallbacks struct { - Height func() (*uint64, ErrorI) // retrieve the height of the root chain - RootChainInfo func(height, chainId uint64) (*RootChainInfo, ErrorI) // retrieve the root chain info in 1 call - ValidatorSet func(height, id uint64) (ValidatorSet, ErrorI) // get the validator set for a chain id using the RPC API - Lottery func(height, id uint64) (p *LotteryWinner, err ErrorI) // get the delegate 'lottery winner' for a chain id - Orders func(height, chainId uint64) (p *OrderBooks, err ErrorI) // get the order book for a specific 'chain id' - Order func(height, orderId, chainId uint64) (*SellOrder, ErrorI) // get a specific order from the order book - IsValidDoubleSigner func(height uint64, address string) (p *bool, err ErrorI) // check if a double signer is valid for an address for a specific 'double sign height' - Checkpoint func(height, id uint64) (blockHash HexBytes, i ErrorI) // get a checkpoint at a height and chain id combination - Transaction func(tx TransactionI) (hash *string, err ErrorI) // submit a transaction to the 'root chain' -} - -// GetHeight() returns the height from the root-chain -func (b *RootChainInfo) GetHeight() uint64 { return b.Height } - -// GetValidatorSet() returns the validator set from the root-chain -func (b *RootChainInfo) GetValidatorSet(rootChainId, id, rootHeight uint64) (ValidatorSet, ErrorI) { - // if the root chain id is the same as the info - if rootChainId == b.RootChainId { - // if rootHeight is the same as the RootChainInfo height - if rootHeight == b.Height { - // exit with a copy the validator set - return NewValidatorSet(b.ValidatorSet.ValidatorSet) - } - // if rootHeight is 1 before the RootChainInfo height - if rootHeight == b.Height-1 { - // exit with a copy of the previous validator set - return NewValidatorSet(b.LastValidatorSet.ValidatorSet) - } - } - // warn of the remote RPC call to the root chain API - b.Log.Warnf("Executing remote GetValidatorSet call with requested height=%d for rootChainId=%d", rootHeight, rootChainId) - // get a rpc client for the root chain id - rpcClient, err := b.GetRemoteCallbacks(rootChainId) - // if the rpc client retrieval fails - if err != nil { - // exit with error - return ValidatorSet{}, err - } - // execute the remote RPC call to the root chain API - return rpcClient.ValidatorSet(rootHeight, id) -} - -// GetOrders() returns the order book from the root-chain -func (b *RootChainInfo) GetOrders(rootChainId, rootHeight, id uint64) (*OrderBook, ErrorI) { - // if the root chain id is the same as the info - if rootChainId == b.RootChainId { - // if rootHeight is the same as the RootChainInfo height - if rootHeight == b.Height { - // exit with the order books from memory - return b.Orders, nil - } - } - // warn of the remote RPC call to the root chain API - b.Log.Warnf("Executing remote GetOrders call with requested height=%d for rootChainId=%d", rootHeight, rootChainId) - // get a rpc client for the root chain id - rpcClient, err := b.GetRemoteCallbacks(rootChainId) - // if the rpc client retrieval fails - if err != nil { - // exit with error - return nil, err - } - // execute the remote call - books, err := rpcClient.Orders(rootHeight, id) - // if an error occurred during the remote call - if err != nil { - // exit with error - return nil, err - } - // ensure the order book isn't empty - if books == nil || len(books.OrderBooks) == 0 { - // exit with error - return nil, ErrEmptyOrderBook() - } - // exit with the first (and only) order book in the list - return books.OrderBooks[0], nil -} - -// IsValidDoubleSigner() returns if an address is a valid double signer for a specific 'double sign height' -func (b *RootChainInfo) IsValidDoubleSigner(rootChainId, height uint64, address string) (*bool, ErrorI) { - // warn of the remote RPC call to the API of the 'root chain' - b.Log.Warnf("Executing remote IsValidDoubleSigner call for address=%s at double sign height=%d and rootChainId=%d", address, height, rootChainId) - // get a rpc client for the root chain id - rpcClient, err := b.GetRemoteCallbacks(rootChainId) - // if the rpc client retrieval fails - if err != nil { - // exit with error - return nil, err - } - // exit with the results of the remote RPC call to the API of the 'root chain' - return rpcClient.IsValidDoubleSigner(height, address) -} - -// GetCheckpoint() returns the checkpoint if any for a specific chain height -// TODO should be able to get these from the file or the root-chain upon independence -func (b *RootChainInfo) GetCheckpoint(rootChainId, height, chainId uint64) (blockHash HexBytes, err ErrorI) { - // warn of the remote RPC call to the API of the 'root chain' - b.Log.Warnf("Executing remote GetCheckpoint call for height=%d and chainId=%d and rootChainId=%d", height, chainId, rootChainId) - // get a rpc client for the root chain id - rpcClient, err := b.GetRemoteCallbacks(rootChainId) - // if the rpc client retrieval fails - if err != nil { - // exit with error - return nil, err - } - // exit with the results of the remote RPC call to the API of the 'root chain' - return rpcClient.Checkpoint(height, chainId) -} - -// GetLotteryWinner() returns the winner of the delegate lottery from the root-chain -func (b *RootChainInfo) GetLotteryWinner(rootChainId, height, id uint64) (*LotteryWinner, ErrorI) { - // if the root chain id is the same as the info - if rootChainId == b.RootChainId { - // if rootHeight is the same as the RootChainInfo height - if height == b.Height { - // exit with the lottery winner - return b.LotteryWinner, nil - } - } - // warn of the remote RPC call to the API of the 'root chain' - b.Log.Warnf("Executing remote Lottery call with requested height=%d and rootChainId=%d", height, rootChainId) - // get a rpc client for the root chain id - rpcClient, err := b.GetRemoteCallbacks(rootChainId) - // if the rpc client retrieval fails - if err != nil { - // exit with error - return nil, err - } - // exit with the results of the remote RPC call to the API of the 'root chain' - return rpcClient.Lottery(height, id) -} - -// rootChainInfoJSON is the encoding structure used for json for RootChainInfo -type rootChainInfoJSON struct { - RootChainId uint64 `json:"rootChainId"` - Height uint64 `json:"height"` - Committee *ConsensusValidators `json:"committee"` - LastCommittee *ConsensusValidators `json:"last_committee"` - LotteryWinner *LotteryWinner `json:"lottery_winner"` - Orders *OrderBook `json:"orders"` -} - -// MarshalJSON() implements the json.Marshaller for RootChainInfo -func (b RootChainInfo) MarshalJSON() ([]byte, error) { - return json.Marshal(rootChainInfoJSON{ - RootChainId: b.RootChainId, - Height: b.Height, - Committee: b.ValidatorSet.ValidatorSet, - LastCommittee: b.LastValidatorSet.ValidatorSet, - LotteryWinner: b.LotteryWinner, - Orders: b.Orders, - }) -} - -// UnmarshalJSON() implements the json.Unmarshaler for RootChainInfo -func (b *RootChainInfo) UnmarshalJSON(jsonBytes []byte) (err error) { - // create a new object reference to ensure a non nil result - j := new(rootChainInfoJSON) - // populate the object ref with the json bytes - if err = json.Unmarshal(jsonBytes, j); err != nil { - // exit with error - return - } - // create a validator set from the committee - validatorSet, err := NewValidatorSet(j.Committee) - // if an error occurred during the conversion - if err != nil { - // exit with error - return - } - // create a validator set from the previous committee - ignore error - lastValidatorSet, _ := NewValidatorSet(j.LastCommittee) - // set the underlying object - *b = RootChainInfo{ - RootChainId: j.RootChainId, - Height: j.Height, - ValidatorSet: validatorSet, - LastValidatorSet: lastValidatorSet, - LotteryWinner: j.LotteryWinner, - Orders: j.Orders, - GetRemoteCallbacks: nil, - Log: nil, - } - // exit - return -} - -// LotteryWinner is a structure that holds the subject of a pseudorandom selection and their % cut of the reward -// This is used for delegation + sub-delegation + sub-validator earnings -type LotteryWinner struct { - Winner HexBytes `json:"winner"` // the winner address in hex bytes - Cut uint64 `json:"cut"` // the % of the reward that is allocated to the winner +// RootChainClient executes 'on-demand' calls to the root-chain +type RCManagerI interface { + Publish(chainId uint64, info *RootChainInfo) // publish the root chain info to nested chain listeners + ChainIds() []uint64 // get the list of chain ids of the nested chain subscribers + GetHeight(rootChainId uint64) uint64 // get the height of the root chain + GetRootChainInfo(rootChainId, chainId uint64) (rootChainInfo *RootChainInfo, err ErrorI) // get root-chain info 'on-demand' + GetValidatorSet(rootChainId, height, id uint64) (ValidatorSet, ErrorI) // get the validator set for a chain id using the RPC API + GetLotteryWinner(rootChainId, height, id uint64) (p *LotteryWinner, err ErrorI) // get the delegate 'lottery winner' for a chain id + GetOrders(rootChainId, rootHeight, id uint64) (*OrderBook, ErrorI) // get the order book for a specific 'chain id' + GetOrder(rootChainId, height, orderId, chainId uint64) (*SellOrder, ErrorI) // get a specific order from the order book + IsValidDoubleSigner(rootChainId, height uint64, address string) (p *bool, err ErrorI) // check if a double signer is valid for an address for a specific 'double sign height' + GetCheckpoint(rootChainId, height, id uint64) (blockHash HexBytes, i ErrorI) // get a checkpoint at a height and chain id combination + Transaction(rootChainId uint64, tx TransactionI) (hash *string, err ErrorI) // submit a transaction to the 'root chain' } // CheckBasic() validates the basic structure and length of the AggregateSignature @@ -879,6 +684,35 @@ func (x *Proposers) UnmarshalJSON(jsonBytes []byte) (err error) { return } +// jsonLotteryWinner implements the json marshaller and unmarshaler for 'LotteryWinner +type jsonLotteryWinner struct { + Winner HexBytes `json:"winner"` // the winner address in hex bytes + Cut uint64 `json:"cut"` // the % of the reward that is allocated to the winner +} + +// MarshalJSON() implements the json marshaller for 'LotteryWinner' +func (x *LotteryWinner) MarshalJSON() ([]byte, error) { + return json.Marshal(jsonLotteryWinner{ + Winner: x.Winner, + Cut: x.Cut, + }) +} + +// UnmarshalJSON() implements the json unmarshaler for 'LotteryWinner' +func (x *LotteryWinner) UnmarshalJSON(b []byte) (err error) { + // create a new json object reference to ensure a non-nil result + j := new(jsonLotteryWinner) + // populate the json object reference using the json bytes + if err = json.Unmarshal(b, j); err != nil { + // exit with error + return + } + // populate the underlying object + *x = LotteryWinner{Winner: j.Winner, Cut: j.Cut} + // exit + return +} + // SortitionData is the seed data for the IsCandidate and VRF functions type SortitionData struct { LastProposerAddresses [][]byte // the last N proposers addresses prevents any grinding attacks diff --git a/lib/consensus.pb.go b/lib/consensus.pb.go index 354f66bb7b..ab2889ddcc 100644 --- a/lib/consensus.pb.go +++ b/lib/consensus.pb.go @@ -490,58 +490,235 @@ func (x *ConsensusValidators) GetValidatorSet() []*ConsensusValidator { return nil } +// LotteryWinner is a structure that holds the subject of a pseudorandom selection and their % cut of the reward +// This is used for delegation + sub-delegation + sub-validator earnings +type LotteryWinner struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // winner: the 20 byte address of the selected actor + Winner []byte `protobuf:"bytes,1,opt,name=winner,proto3" json:"winner"` // @gotags: json:"winner" + // cut: the percent cut of the rewards + Cut uint64 `protobuf:"varint,2,opt,name=cut,proto3" json:"cut,omitempty"` // @gotags json:"cut" +} + +func (x *LotteryWinner) Reset() { + *x = LotteryWinner{} + if protoimpl.UnsafeEnabled { + mi := &file_consensus_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LotteryWinner) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LotteryWinner) ProtoMessage() {} + +func (x *LotteryWinner) ProtoReflect() protoreflect.Message { + mi := &file_consensus_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LotteryWinner.ProtoReflect.Descriptor instead. +func (*LotteryWinner) Descriptor() ([]byte, []int) { + return file_consensus_proto_rawDescGZIP(), []int{5} +} + +func (x *LotteryWinner) GetWinner() []byte { + if x != nil { + return x.Winner + } + return nil +} + +func (x *LotteryWinner) GetCut() uint64 { + if x != nil { + return x.Cut + } + return 0 +} + +// RootChainInfo maintains root-chain data needed for consensus +type RootChainInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // root_chain_id: the chain id of the root chain + RootChainId uint64 `protobuf:"varint,1,opt,name=root_chain_id,json=rootChainId,proto3" json:"rootChainId"` // @gotags: json:"rootChainId" + // height: the block height of the root chain + Height uint64 `protobuf:"varint,2,opt,name=height,proto3" json:"height"` // @gotags: json:"height" + // validator_set: the current validator set + ValidatorSet *ConsensusValidators `protobuf:"bytes,3,opt,name=validator_set,json=validatorSet,proto3" json:"validatorSet"` // @gotags: json:"validatorSet" + // last_validator_set: the validator set of the previous height + LastValidatorSet *ConsensusValidators `protobuf:"bytes,4,opt,name=last_validator_set,json=lastValidatorSet,proto3" json:"lastValidatorSet"` // @gotags: json:"lastValidatorSet" + // lottery_winner: the selected delegate/pseudo-validator who receives rewards + LotteryWinner *LotteryWinner `protobuf:"bytes,5,opt,name=lottery_winner,json=lotteryWinner,proto3" json:"lottery_winner,omitempty"` + // orders: the swap order book from the 'root chain' for the 'nested chain' + Orders *OrderBook `protobuf:"bytes,6,opt,name=orders,proto3" json:"orders"` // @gotags: json:"orders" +} + +func (x *RootChainInfo) Reset() { + *x = RootChainInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_consensus_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RootChainInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RootChainInfo) ProtoMessage() {} + +func (x *RootChainInfo) ProtoReflect() protoreflect.Message { + mi := &file_consensus_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RootChainInfo.ProtoReflect.Descriptor instead. +func (*RootChainInfo) Descriptor() ([]byte, []int) { + return file_consensus_proto_rawDescGZIP(), []int{6} +} + +func (x *RootChainInfo) GetRootChainId() uint64 { + if x != nil { + return x.RootChainId + } + return 0 +} + +func (x *RootChainInfo) GetHeight() uint64 { + if x != nil { + return x.Height + } + return 0 +} + +func (x *RootChainInfo) GetValidatorSet() *ConsensusValidators { + if x != nil { + return x.ValidatorSet + } + return nil +} + +func (x *RootChainInfo) GetLastValidatorSet() *ConsensusValidators { + if x != nil { + return x.LastValidatorSet + } + return nil +} + +func (x *RootChainInfo) GetLotteryWinner() *LotteryWinner { + if x != nil { + return x.LotteryWinner + } + return nil +} + +func (x *RootChainInfo) GetOrders() *OrderBook { + if x != nil { + return x.Orders + } + return nil +} + var File_consensus_proto protoreflect.FileDescriptor var file_consensus_proto_rawDesc = []byte{ 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x12, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0xb3, 0x01, 0x0a, 0x04, 0x56, 0x69, 0x65, - 0x77, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, - 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x68, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x48, 0x65, - 0x69, 0x67, 0x68, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x22, 0x0a, 0x05, 0x70, 0x68, - 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0c, 0x2e, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2e, 0x50, 0x68, 0x61, 0x73, 0x65, 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x22, 0x4a, - 0x0a, 0x12, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, - 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x70, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0c, 0x52, 0x06, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x70, 0x22, 0x29, 0x0a, 0x09, 0x50, 0x72, - 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x72, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x77, 0x0a, 0x12, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, - 0x75, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x70, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x6f, - 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0b, 0x76, 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x1f, 0x0a, - 0x0b, 0x6e, 0x65, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0a, 0x6e, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x54, - 0x0a, 0x13, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, - 0x61, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x3d, 0x0a, 0x0c, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, - 0x6f, 0x72, 0x53, 0x65, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x56, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x52, 0x0c, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, - 0x72, 0x53, 0x65, 0x74, 0x2a, 0xbb, 0x01, 0x0a, 0x05, 0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x0b, - 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x45, - 0x4c, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x4c, 0x45, - 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x56, 0x4f, 0x54, 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, - 0x50, 0x52, 0x4f, 0x50, 0x4f, 0x53, 0x45, 0x10, 0x03, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x4f, - 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x56, 0x4f, 0x54, 0x45, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x50, - 0x52, 0x45, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x52, - 0x45, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x5f, 0x56, 0x4f, 0x54, 0x45, 0x10, 0x06, 0x12, 0x0a, - 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4f, - 0x4d, 0x4d, 0x49, 0x54, 0x5f, 0x50, 0x52, 0x4f, 0x43, 0x45, 0x53, 0x53, 0x10, 0x08, 0x12, 0x13, - 0x0a, 0x0f, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x52, 0x55, 0x50, - 0x54, 0x10, 0x09, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x41, 0x43, 0x45, 0x4d, 0x41, 0x4b, 0x45, 0x52, - 0x10, 0x0a, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x63, 0x61, 0x6e, 0x6f, 0x70, 0x79, 0x2d, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, - 0x63, 0x61, 0x6e, 0x6f, 0x70, 0x79, 0x2f, 0x6c, 0x69, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x6f, 0x12, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x0a, 0x73, 0x77, 0x61, 0x70, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb3, 0x01, 0x0a, 0x04, 0x56, 0x69, 0x65, 0x77, 0x12, 0x1d, 0x0a, + 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x09, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, + 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, + 0x1f, 0x0a, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x72, 0x6f, 0x6f, 0x74, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x14, 0x0a, 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x05, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x22, 0x0a, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x68, + 0x61, 0x73, 0x65, 0x52, 0x05, 0x70, 0x68, 0x61, 0x73, 0x65, 0x22, 0x4a, 0x0a, 0x12, 0x41, 0x67, + 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x53, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, + 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x62, 0x69, 0x74, 0x6d, 0x61, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, + 0x62, 0x69, 0x74, 0x6d, 0x61, 0x70, 0x22, 0x29, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x70, 0x6f, 0x73, + 0x65, 0x72, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x09, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x22, 0x77, 0x0a, 0x12, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x56, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x76, 0x6f, 0x74, 0x69, 0x6e, 0x67, + 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x76, 0x6f, + 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6e, 0x65, 0x74, + 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x6e, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x54, 0x0a, 0x13, 0x43, 0x6f, + 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, + 0x73, 0x12, 0x3d, 0x0a, 0x0c, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x65, + 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x6f, 0x72, 0x52, 0x0c, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x65, 0x74, + 0x22, 0x39, 0x0a, 0x0d, 0x4c, 0x6f, 0x74, 0x74, 0x65, 0x72, 0x79, 0x57, 0x69, 0x6e, 0x6e, 0x65, + 0x72, 0x12, 0x16, 0x0a, 0x06, 0x77, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x06, 0x77, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x75, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, 0x63, 0x75, 0x74, 0x22, 0xbd, 0x02, 0x0a, 0x0d, + 0x52, 0x6f, 0x6f, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x22, 0x0a, + 0x0d, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x49, + 0x64, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x06, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x3f, 0x0a, 0x0d, 0x76, 0x61, 0x6c, + 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, + 0x75, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x52, 0x0c, 0x76, 0x61, + 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x53, 0x65, 0x74, 0x12, 0x48, 0x0a, 0x12, 0x6c, 0x61, + 0x73, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x65, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, + 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x73, 0x52, 0x10, 0x6c, 0x61, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x6f, + 0x72, 0x53, 0x65, 0x74, 0x12, 0x3b, 0x0a, 0x0e, 0x6c, 0x6f, 0x74, 0x74, 0x65, 0x72, 0x79, 0x5f, + 0x77, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4c, 0x6f, 0x74, 0x74, 0x65, 0x72, 0x79, 0x57, 0x69, 0x6e, 0x6e, + 0x65, 0x72, 0x52, 0x0d, 0x6c, 0x6f, 0x74, 0x74, 0x65, 0x72, 0x79, 0x57, 0x69, 0x6e, 0x6e, 0x65, + 0x72, 0x12, 0x28, 0x0a, 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x42, + 0x6f, 0x6f, 0x6b, 0x52, 0x06, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x73, 0x2a, 0xbb, 0x01, 0x0a, 0x05, + 0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, + 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, + 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x56, 0x4f, 0x54, + 0x45, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x4f, 0x50, 0x4f, 0x53, 0x45, 0x10, 0x03, + 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x4f, 0x50, 0x4f, 0x53, 0x45, 0x5f, 0x56, 0x4f, 0x54, 0x45, + 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, + 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x50, 0x52, 0x45, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x5f, 0x56, + 0x4f, 0x54, 0x45, 0x10, 0x06, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x10, + 0x07, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x4f, 0x4d, 0x4d, 0x49, 0x54, 0x5f, 0x50, 0x52, 0x4f, 0x43, + 0x45, 0x53, 0x53, 0x10, 0x08, 0x12, 0x13, 0x0a, 0x0f, 0x52, 0x4f, 0x55, 0x4e, 0x44, 0x5f, 0x49, + 0x4e, 0x54, 0x45, 0x52, 0x52, 0x55, 0x50, 0x54, 0x10, 0x09, 0x12, 0x0d, 0x0a, 0x09, 0x50, 0x41, + 0x43, 0x45, 0x4d, 0x41, 0x4b, 0x45, 0x52, 0x10, 0x0a, 0x42, 0x26, 0x5a, 0x24, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x61, 0x6e, 0x6f, 0x70, 0x79, 0x2d, 0x6e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x63, 0x61, 0x6e, 0x6f, 0x70, 0x79, 0x2f, 0x6c, 0x69, + 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -557,7 +734,7 @@ func file_consensus_proto_rawDescGZIP() []byte { } var file_consensus_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_consensus_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_consensus_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_consensus_proto_goTypes = []interface{}{ (Phase)(0), // 0: types.Phase (*View)(nil), // 1: types.View @@ -565,15 +742,22 @@ var file_consensus_proto_goTypes = []interface{}{ (*Proposers)(nil), // 3: types.Proposers (*ConsensusValidator)(nil), // 4: types.ConsensusValidator (*ConsensusValidators)(nil), // 5: types.ConsensusValidators + (*LotteryWinner)(nil), // 6: types.LotteryWinner + (*RootChainInfo)(nil), // 7: types.RootChainInfo + (*OrderBook)(nil), // 8: types.OrderBook } var file_consensus_proto_depIdxs = []int32{ 0, // 0: types.View.phase:type_name -> types.Phase 4, // 1: types.ConsensusValidators.ValidatorSet:type_name -> types.ConsensusValidator - 2, // [2:2] is the sub-list for method output_type - 2, // [2:2] is the sub-list for method input_type - 2, // [2:2] is the sub-list for extension type_name - 2, // [2:2] is the sub-list for extension extendee - 0, // [0:2] is the sub-list for field type_name + 5, // 2: types.RootChainInfo.validator_set:type_name -> types.ConsensusValidators + 5, // 3: types.RootChainInfo.last_validator_set:type_name -> types.ConsensusValidators + 6, // 4: types.RootChainInfo.lottery_winner:type_name -> types.LotteryWinner + 8, // 5: types.RootChainInfo.orders:type_name -> types.OrderBook + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_consensus_proto_init() } @@ -581,6 +765,7 @@ func file_consensus_proto_init() { if File_consensus_proto != nil { return } + file_swap_proto_init() if !protoimpl.UnsafeEnabled { file_consensus_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*View); i { @@ -642,6 +827,30 @@ func file_consensus_proto_init() { return nil } } + file_consensus_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*LotteryWinner); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_consensus_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RootChainInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -649,7 +858,7 @@ func file_consensus_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_consensus_proto_rawDesc, NumEnums: 1, - NumMessages: 5, + NumMessages: 7, NumExtensions: 0, NumServices: 0, }, diff --git a/lib/error.go b/lib/error.go index abc3d66b52..09dd030198 100644 --- a/lib/error.go +++ b/lib/error.go @@ -129,7 +129,7 @@ const ( CodeInvalidValidatorIndex ErrorCode = 31 CodeUnableToAddSigner ErrorCode = 32 CodeEmptyMessage ErrorCode = 33 - CodeInvalidBlockTime ErrorCode = 34 + CodeNotSubscribed ErrorCode = 34 CodeInvalidEvidence ErrorCode = 35 CodeMismatchEvidenceAndHeader ErrorCode = 36 CodeInvalidTxTime ErrorCode = 37 @@ -505,8 +505,8 @@ func ErrInvalidValidatorIndex() ErrorI { return NewError(CodeInvalidValidatorIndex, ConsensusModule, "invalid validator index") } -func ErrInvalidBlockTime() ErrorI { - return NewError(CodeInvalidBlockTime, ConsensusModule, "invalid block time") +func ErrNotSubscribed() ErrorI { + return NewError(CodeNotSubscribed, ConsensusModule, "not subscribed") } func ErrInvalidTxHeight() ErrorI {