diff --git a/activation_service_poc/config.standalone.client.json b/activation_service_poc/config.standalone.client.json index ce162cca95..335ecd9a26 100644 --- a/activation_service_poc/config.standalone.client.json +++ b/activation_service_poc/config.standalone.client.json @@ -11,7 +11,8 @@ "trtl": "WARN", "beacon": "ERROR", "proposalBuilder": "ERROR", - "atxBuilder": "DEBUG" + "atxBuilder": "DEBUG", + "hare": "DEBUG" }, "main": { "node-service-address": "http://0.0.0.0:9099", diff --git a/activation_service_poc/config.standalone.node-service.json b/activation_service_poc/config.standalone.node-service.json index 99d10328af..e511285b6c 100644 --- a/activation_service_poc/config.standalone.node-service.json +++ b/activation_service_poc/config.standalone.node-service.json @@ -11,13 +11,14 @@ "logging": { "trtl": "WARN", "beacon": "ERROR", - "proposalBuilder": "ERROR" + "proposalBuilder": "ERROR", + "hare": "DEBUG" + }, + "hare3": { + "enable": true }, "main": { "data-folder": "/tmp/spacemesh-node-service", "filelock": "/tmp/spacemesh-node-service/node.lock" - }, - "smeshing": { - "smeshing-start": false } } diff --git a/activation_service_poc/docker-compose.yml b/activation_service_poc/docker-compose.yml index edbb5b0e76..fe40bf6752 100644 --- a/activation_service_poc/docker-compose.yml +++ b/activation_service_poc/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: activation-service: - image: spacemeshos/go-spacemesh-dev:activation-service-poc.0 + image: ${IMAGE} command: ["-c", "/config.json", "--node-service-address", "http://node-service:9099"] volumes: - /tmp/spacemesh-client:/tmp/spacemesh-client @@ -11,8 +11,8 @@ services: - spacemesh-net node-service: - image: spacemeshos/go-spacemesh-dev:activation-service-poc.0 - command: ["-c", "/config.json"] + image: ${IMAGE} + command: ["-c", "/config.json", "--smeshing-opts-datadir", "/tmp/spacemesh-node-post"] volumes: - /tmp/spacemesh-node-service:/tmp/spacemesh-node-service - ./config.standalone.node-service.json:/config.json diff --git a/activation_service_poc/start.sh b/activation_service_poc/start.sh new file mode 100644 index 0000000000..b066400b7d --- /dev/null +++ b/activation_service_poc/start.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# +cd .. +make dockerbuild-go +cd activation_service_poc + +export IMAGE=$(docker images | head -n 2 | tail -n 1 | awk '{print $3}') + +TIME=$(date -u -d '2 minutes' "+%Y-%m-%dT%H:%M:%S%:z") +jq ".genesis.\"genesis-time\" |= \"$TIME\"" config.standalone.client.json +jq ".genesis.\"genesis-time\" |= \"$TIME\"" config.standalone.node-service.json + +rm -rf /tmp/spacemesh* +docker compose up diff --git a/api/node/client/client.gen.go b/api/node/client/client.gen.go index e528badc57..5216946db2 100644 --- a/api/node/client/client.gen.go +++ b/api/node/client/client.gen.go @@ -115,6 +115,18 @@ type ClientInterface interface { // GetActivationPositioningAtxPublishEpoch request GetActivationPositioningAtxPublishEpoch(ctx context.Context, publishEpoch externalRef0.EpochID, reqEditors ...RequestEditorFn) (*http.Response, error) + // GetHareBeaconEpoch request + GetHareBeaconEpoch(ctx context.Context, epoch externalRef0.EpochID, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetHareRoundTemplateLayerIterRound request + GetHareRoundTemplateLayerIterRound(ctx context.Context, layer externalRef0.LayerID, iter externalRef0.HareIter, round externalRef0.HareRound, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetHareTotalWeightLayer request + GetHareTotalWeightLayer(ctx context.Context, layer uint32, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetHareWeightNodeIdLayer request + GetHareWeightNodeIdLayer(ctx context.Context, nodeId externalRef0.NodeID, layer uint32, reqEditors ...RequestEditorFn) (*http.Response, error) + // PostPoetWithBody request with any body PostPoetWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -158,6 +170,54 @@ func (c *Client) GetActivationPositioningAtxPublishEpoch(ctx context.Context, pu return c.Client.Do(req) } +func (c *Client) GetHareBeaconEpoch(ctx context.Context, epoch externalRef0.EpochID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetHareBeaconEpochRequest(c.Server, epoch) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetHareRoundTemplateLayerIterRound(ctx context.Context, layer externalRef0.LayerID, iter externalRef0.HareIter, round externalRef0.HareRound, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetHareRoundTemplateLayerIterRoundRequest(c.Server, layer, iter, round) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetHareTotalWeightLayer(ctx context.Context, layer uint32, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetHareTotalWeightLayerRequest(c.Server, layer) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetHareWeightNodeIdLayer(ctx context.Context, nodeId externalRef0.NodeID, layer uint32, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetHareWeightNodeIdLayerRequest(c.Server, nodeId, layer) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) PostPoetWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewPostPoetRequestWithBody(c.Server, contentType, body) if err != nil { @@ -284,6 +344,163 @@ func NewGetActivationPositioningAtxPublishEpochRequest(server string, publishEpo return req, nil } +// NewGetHareBeaconEpochRequest generates requests for GetHareBeaconEpoch +func NewGetHareBeaconEpochRequest(server string, epoch externalRef0.EpochID) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "epoch", runtime.ParamLocationPath, epoch) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/hare/beacon/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetHareRoundTemplateLayerIterRoundRequest generates requests for GetHareRoundTemplateLayerIterRound +func NewGetHareRoundTemplateLayerIterRoundRequest(server string, layer externalRef0.LayerID, iter externalRef0.HareIter, round externalRef0.HareRound) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "layer", runtime.ParamLocationPath, layer) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "iter", runtime.ParamLocationPath, iter) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "round", runtime.ParamLocationPath, round) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/hare/round_template/%s/%s/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetHareTotalWeightLayerRequest generates requests for GetHareTotalWeightLayer +func NewGetHareTotalWeightLayerRequest(server string, layer uint32) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "layer", runtime.ParamLocationPath, layer) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/hare/total_weight/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetHareWeightNodeIdLayerRequest generates requests for GetHareWeightNodeIdLayer +func NewGetHareWeightNodeIdLayerRequest(server string, nodeId externalRef0.NodeID, layer uint32) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "node_id", runtime.ParamLocationPath, nodeId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "layer", runtime.ParamLocationPath, layer) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/hare/weight/%s/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + // NewPostPoetRequestWithBody generates requests for PostPoet with any type of body func NewPostPoetRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { var err error @@ -401,6 +618,18 @@ type ClientWithResponsesInterface interface { // GetActivationPositioningAtxPublishEpochWithResponse request GetActivationPositioningAtxPublishEpochWithResponse(ctx context.Context, publishEpoch externalRef0.EpochID, reqEditors ...RequestEditorFn) (*GetActivationPositioningAtxPublishEpochResponse, error) + // GetHareBeaconEpochWithResponse request + GetHareBeaconEpochWithResponse(ctx context.Context, epoch externalRef0.EpochID, reqEditors ...RequestEditorFn) (*GetHareBeaconEpochResponse, error) + + // GetHareRoundTemplateLayerIterRoundWithResponse request + GetHareRoundTemplateLayerIterRoundWithResponse(ctx context.Context, layer externalRef0.LayerID, iter externalRef0.HareIter, round externalRef0.HareRound, reqEditors ...RequestEditorFn) (*GetHareRoundTemplateLayerIterRoundResponse, error) + + // GetHareTotalWeightLayerWithResponse request + GetHareTotalWeightLayerWithResponse(ctx context.Context, layer uint32, reqEditors ...RequestEditorFn) (*GetHareTotalWeightLayerResponse, error) + + // GetHareWeightNodeIdLayerWithResponse request + GetHareWeightNodeIdLayerWithResponse(ctx context.Context, nodeId externalRef0.NodeID, layer uint32, reqEditors ...RequestEditorFn) (*GetHareWeightNodeIdLayerResponse, error) + // PostPoetWithBodyWithResponse request with any body PostPoetWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostPoetResponse, error) @@ -476,6 +705,90 @@ func (r GetActivationPositioningAtxPublishEpochResponse) StatusCode() int { return 0 } +type GetHareBeaconEpochResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r GetHareBeaconEpochResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetHareBeaconEpochResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetHareRoundTemplateLayerIterRoundResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r GetHareRoundTemplateLayerIterRoundResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetHareRoundTemplateLayerIterRoundResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetHareTotalWeightLayerResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r GetHareTotalWeightLayerResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetHareTotalWeightLayerResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetHareWeightNodeIdLayerResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r GetHareWeightNodeIdLayerResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetHareWeightNodeIdLayerResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type PostPoetResponse struct { Body []byte HTTPResponse *http.Response @@ -545,6 +858,42 @@ func (c *ClientWithResponses) GetActivationPositioningAtxPublishEpochWithRespons return ParseGetActivationPositioningAtxPublishEpochResponse(rsp) } +// GetHareBeaconEpochWithResponse request returning *GetHareBeaconEpochResponse +func (c *ClientWithResponses) GetHareBeaconEpochWithResponse(ctx context.Context, epoch externalRef0.EpochID, reqEditors ...RequestEditorFn) (*GetHareBeaconEpochResponse, error) { + rsp, err := c.GetHareBeaconEpoch(ctx, epoch, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetHareBeaconEpochResponse(rsp) +} + +// GetHareRoundTemplateLayerIterRoundWithResponse request returning *GetHareRoundTemplateLayerIterRoundResponse +func (c *ClientWithResponses) GetHareRoundTemplateLayerIterRoundWithResponse(ctx context.Context, layer externalRef0.LayerID, iter externalRef0.HareIter, round externalRef0.HareRound, reqEditors ...RequestEditorFn) (*GetHareRoundTemplateLayerIterRoundResponse, error) { + rsp, err := c.GetHareRoundTemplateLayerIterRound(ctx, layer, iter, round, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetHareRoundTemplateLayerIterRoundResponse(rsp) +} + +// GetHareTotalWeightLayerWithResponse request returning *GetHareTotalWeightLayerResponse +func (c *ClientWithResponses) GetHareTotalWeightLayerWithResponse(ctx context.Context, layer uint32, reqEditors ...RequestEditorFn) (*GetHareTotalWeightLayerResponse, error) { + rsp, err := c.GetHareTotalWeightLayer(ctx, layer, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetHareTotalWeightLayerResponse(rsp) +} + +// GetHareWeightNodeIdLayerWithResponse request returning *GetHareWeightNodeIdLayerResponse +func (c *ClientWithResponses) GetHareWeightNodeIdLayerWithResponse(ctx context.Context, nodeId externalRef0.NodeID, layer uint32, reqEditors ...RequestEditorFn) (*GetHareWeightNodeIdLayerResponse, error) { + rsp, err := c.GetHareWeightNodeIdLayer(ctx, nodeId, layer, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetHareWeightNodeIdLayerResponse(rsp) +} + // PostPoetWithBodyWithResponse request with arbitrary body returning *PostPoetResponse func (c *ClientWithResponses) PostPoetWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostPoetResponse, error) { rsp, err := c.PostPoetWithBody(ctx, contentType, body, reqEditors...) @@ -643,6 +992,70 @@ func ParseGetActivationPositioningAtxPublishEpochResponse(rsp *http.Response) (* return response, nil } +// ParseGetHareBeaconEpochResponse parses an HTTP response from a GetHareBeaconEpochWithResponse call +func ParseGetHareBeaconEpochResponse(rsp *http.Response) (*GetHareBeaconEpochResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetHareBeaconEpochResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetHareRoundTemplateLayerIterRoundResponse parses an HTTP response from a GetHareRoundTemplateLayerIterRoundWithResponse call +func ParseGetHareRoundTemplateLayerIterRoundResponse(rsp *http.Response) (*GetHareRoundTemplateLayerIterRoundResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetHareRoundTemplateLayerIterRoundResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetHareTotalWeightLayerResponse parses an HTTP response from a GetHareTotalWeightLayerWithResponse call +func ParseGetHareTotalWeightLayerResponse(rsp *http.Response) (*GetHareTotalWeightLayerResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetHareTotalWeightLayerResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetHareWeightNodeIdLayerResponse parses an HTTP response from a GetHareWeightNodeIdLayerWithResponse call +func ParseGetHareWeightNodeIdLayerResponse(rsp *http.Response) (*GetHareWeightNodeIdLayerResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetHareWeightNodeIdLayerResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + // ParsePostPoetResponse parses an HTTP response from a PostPoetWithResponse call func ParsePostPoetResponse(rsp *http.Response) (*PostPoetResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/api/node/client/client.go b/api/node/client/client.go index 3ab9abcb63..62f66e2602 100644 --- a/api/node/client/client.go +++ b/api/node/client/client.go @@ -5,7 +5,9 @@ import ( "context" "encoding/hex" "fmt" + "io" "net/http" + "strconv" "time" "github.com/hashicorp/go-retryablehttp" @@ -13,8 +15,10 @@ import ( "github.com/spacemeshos/go-spacemesh/activation" "github.com/spacemeshos/go-spacemesh/api/node/models" + externalRef0 "github.com/spacemeshos/go-spacemesh/api/node/models" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/hare3" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" ) @@ -26,6 +30,7 @@ var ( _ activation.AtxService = (*NodeService)(nil) _ activation.PoetDbStorer = (*NodeService)(nil) _ pubsub.Publisher = (*NodeService)(nil) + _ hare3.NodeService = (*NodeService)(nil) ) type Config struct { @@ -121,3 +126,68 @@ func (s *NodeService) StorePoetProof(ctx context.Context, proof *types.PoetProof } return nil } + +func (s *NodeService) GetHareMessage(ctx context.Context, layer types.LayerID, round hare3.IterRound) ([]byte, error) { + resp, err := s.client.GetHareRoundTemplateLayerIterRound(ctx, + externalRef0.LayerID(layer), + externalRef0.HareIter(round.Iter), + externalRef0.HareRound(round.Round)) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status: %s", resp.Status) + } + bytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read all: %w", err) + } + return bytes, nil +} + +func (s *NodeService) TotalWeight(ctx context.Context, layer types.LayerID) (uint64, error) { + resp, err := s.client.GetHareTotalWeightLayer(ctx, uint32(layer)) + if err != nil { + return 0, err + } + if resp.StatusCode != http.StatusOK { + return 0, fmt.Errorf("unexpected status: %s", resp.Status) + } + bytes, err := io.ReadAll(resp.Body) + if err != nil { + return 0, fmt.Errorf("read all: %w", err) + } + return strconv.ParseUint(string(bytes), 10, 64) +} + +func (s *NodeService) MinerWeight(ctx context.Context, layer types.LayerID, node types.NodeID) (uint64, error) { + resp, err := s.client.GetHareWeightNodeIdLayer(ctx, node.String(), uint32(layer)) + if err != nil { + return 0, err + } + if resp.StatusCode != http.StatusOK { + return 0, fmt.Errorf("unexpected status: %s", resp.Status) + } + bytes, err := io.ReadAll(resp.Body) + if err != nil { + return 0, fmt.Errorf("read all: %w", err) + } + return strconv.ParseUint(string(bytes), 10, 64) +} + +func (s *NodeService) Beacon(ctx context.Context, epoch types.EpochID) (types.Beacon, error) { + v := types.Beacon{} + resp, err := s.client.GetHareBeaconEpoch(ctx, externalRef0.EpochID(epoch)) + if err != nil { + return v, err + } + if resp.StatusCode != http.StatusOK { + return v, fmt.Errorf("unexpected status: %s", resp.Status) + } + bytes, err := io.ReadAll(resp.Body) + if err != nil { + return v, fmt.Errorf("read all: %w", err) + } + copy(v[:], bytes) + return v, nil +} diff --git a/api/node/client/client_e2e_test.go b/api/node/client/client_e2e_test.go index 823efed1aa..fd1ac8f818 100644 --- a/api/node/client/client_e2e_test.go +++ b/api/node/client/client_e2e_test.go @@ -15,6 +15,7 @@ import ( "github.com/spacemeshos/go-spacemesh/api/node/client" "github.com/spacemeshos/go-spacemesh/api/node/server" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/hare3" pubsub "github.com/spacemeshos/go-spacemesh/p2p/pubsub/mocks" ) @@ -23,6 +24,7 @@ const retries = 3 type mocks struct { atxService *activation.MockAtxService poetDb *server.MockpoetDB + hare *server.Mockhare publisher *pubsub.MockPublisher } @@ -33,10 +35,11 @@ func setupE2E(t *testing.T) (*client.NodeService, *mocks) { m := &mocks{ atxService: activation.NewMockAtxService(ctrl), poetDb: server.NewMockpoetDB(ctrl), + hare: server.NewMockhare(ctrl), publisher: pubsub.NewMockPublisher(ctrl), } - activationServiceServer := server.NewServer(m.atxService, m.publisher, m.poetDb, log.Named("server")) + activationServiceServer := server.NewServer(m.atxService, m.publisher, m.poetDb, m.hare, log.Named("server")) listener, err := net.Listen("tcp", "localhost:0") require.NoError(t, err) @@ -149,3 +152,35 @@ func Test_StoringPoetProof(t *testing.T) { mocks.poetDb.EXPECT().ValidateAndStore(gomock.Any(), &proof) svc.StorePoetProof(context.Background(), &proof) } + +func Test_Hare(t *testing.T) { + svc, mock := setupE2E(t) + t.Run("total weight", func(t *testing.T) { + val := uint64(11) + mock.hare.EXPECT().TotalWeight(gomock.Any(), gomock.Any()).Return(val, nil) + v, err := svc.TotalWeight(context.Background(), 112) + require.Equal(t, v, val) + require.NoError(t, err) + }) + t.Run("miner weight", func(t *testing.T) { + val := uint64(101) + mock.hare.EXPECT().MinerWeight(gomock.Any(), gomock.Any(), gomock.Any()).Return(val, nil) + v, err := svc.MinerWeight(context.Background(), 113, types.NodeID{}) + require.Equal(t, v, val) + require.NoError(t, err) + }) + t.Run("beacon", func(t *testing.T) { + beacon := types.Beacon{12, 12, 12, 12} + mock.hare.EXPECT().Beacon(gomock.Any(), gomock.Any()).Return(beacon, nil) + v, err := svc.Beacon(context.Background(), types.EpochID(15)) + require.Equal(t, v, beacon) + require.NoError(t, err) + }) + t.Run("hare message", func(t *testing.T) { + exp := make([]byte, 182) + mock.hare.EXPECT().RoundMessage(gomock.Any(), gomock.Any()).Return(&hare3.Message{}) + v, err := svc.GetHareMessage(context.Background(), types.LayerID(113), hare3.IterRound{}) + require.Equal(t, exp, v) + require.NoError(t, err) + }) +} diff --git a/api/node/models/components.yaml b/api/node/models/components.yaml index 4e445f0e57..35a4cba545 100644 --- a/api/node/models/components.yaml +++ b/api/node/models/components.yaml @@ -42,3 +42,12 @@ components: EpochID: type: integer format: uint32 + LayerID: + type: integer + format: uint64 + HareIter: + type: integer + format: uint8 + HareRound: + type: integer + format: uint8 diff --git a/api/node/models/models.gen.go b/api/node/models/models.gen.go index ccae04b0c8..595e682d37 100644 --- a/api/node/models/models.gen.go +++ b/api/node/models/models.gen.go @@ -20,5 +20,14 @@ type ActivationTx struct { // EpochID defines model for EpochID. type EpochID = uint32 +// HareIter defines model for HareIter. +type HareIter = uint8 + +// HareRound defines model for HareRound. +type HareRound = uint8 + +// LayerID defines model for LayerID. +type LayerID = uint64 + // NodeID defines model for NodeID. type NodeID = string diff --git a/api/node/node_service.yaml b/api/node/node_service.yaml index 608a7ee398..97cbc7c079 100644 --- a/api/node/node_service.yaml +++ b/api/node/node_service.yaml @@ -122,3 +122,105 @@ paths: plain/text: schema: type: string + /hare/round_template/{layer}/{iter}/{round}: + get: + summary: Get a hare message to sign + tags: + - "hare" + parameters: + - in: path + name: layer + required: true + schema: + $ref: "models/components.yaml#/components/schemas/LayerID" + - in: path + name: iter + required: true + schema: + $ref: "models/components.yaml#/components/schemas/HareIter" + - in: path + name: round + required: true + schema: + $ref: "models/components.yaml#/components/schemas/HareRound" + responses: + "200": + description: successfully found the message to return to client + content: + application/octet-stream: + schema: + type: string + format: binary + "204": + description: did not find a message to retrieve + /hare/total_weight/{layer}: + get: + summary: Get the total weight for layer + tags: + - "hare" + parameters: + - in: path + name: layer + required: true + schema: + type: integer + format: uint32 + responses: + "200": + description: successfully found the total weight to return to client + content: + application/octet-stream: + schema: + type: string + format: binary + "204": + description: did not find a message to retrieve + /hare/weight/{node_id}/{layer}: + get: + summary: Get the miner weight in layer + tags: + - "hare" + parameters: + - in: path + name: node_id + required: true + schema: + $ref: "models/components.yaml#/components/schemas/NodeID" + - in: path + name: layer + required: true + schema: + type: integer + format: uint32 + responses: + "200": + description: successfully found the total weight to return to client + content: + application/octet-stream: + schema: + type: string + format: binary + "204": + description: did not find a message to retrieve + /hare/beacon/{epoch}: + get: + summary: Get the beacon value for an epoch + tags: + - "hare" + parameters: + - in: path + name: epoch + required: true + schema: + $ref: "models/components.yaml#/components/schemas/EpochID" + responses: + "200": + description: successfully found the epoch id + content: + application/octet-stream: + schema: + type: string + format: binary + "204": + description: did not find a message to retrieve + diff --git a/api/node/server/mocks.go b/api/node/server/mocks.go index 588ab7de19..a27e4ba135 100644 --- a/api/node/server/mocks.go +++ b/api/node/server/mocks.go @@ -14,6 +14,7 @@ import ( reflect "reflect" types "github.com/spacemeshos/go-spacemesh/common/types" + hare3 "github.com/spacemeshos/go-spacemesh/hare3" gomock "go.uber.org/mock/gomock" ) @@ -78,3 +79,182 @@ func (c *MockpoetDBValidateAndStoreCall) DoAndReturn(f func(context.Context, *ty c.Call = c.Call.DoAndReturn(f) return c } + +// Mockhare is a mock of hare interface. +type Mockhare struct { + ctrl *gomock.Controller + recorder *MockhareMockRecorder + isgomock struct{} +} + +// MockhareMockRecorder is the mock recorder for Mockhare. +type MockhareMockRecorder struct { + mock *Mockhare +} + +// NewMockhare creates a new mock instance. +func NewMockhare(ctrl *gomock.Controller) *Mockhare { + mock := &Mockhare{ctrl: ctrl} + mock.recorder = &MockhareMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *Mockhare) EXPECT() *MockhareMockRecorder { + return m.recorder +} + +// Beacon mocks base method. +func (m *Mockhare) Beacon(ctx context.Context, epoch types.EpochID) (types.Beacon, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Beacon", ctx, epoch) + ret0, _ := ret[0].(types.Beacon) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Beacon indicates an expected call of Beacon. +func (mr *MockhareMockRecorder) Beacon(ctx, epoch any) *MockhareBeaconCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Beacon", reflect.TypeOf((*Mockhare)(nil).Beacon), ctx, epoch) + return &MockhareBeaconCall{Call: call} +} + +// MockhareBeaconCall wrap *gomock.Call +type MockhareBeaconCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockhareBeaconCall) Return(arg0 types.Beacon, arg1 error) *MockhareBeaconCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockhareBeaconCall) Do(f func(context.Context, types.EpochID) (types.Beacon, error)) *MockhareBeaconCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockhareBeaconCall) DoAndReturn(f func(context.Context, types.EpochID) (types.Beacon, error)) *MockhareBeaconCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// MinerWeight mocks base method. +func (m *Mockhare) MinerWeight(ctx context.Context, node types.NodeID, layer types.LayerID) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MinerWeight", ctx, node, layer) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MinerWeight indicates an expected call of MinerWeight. +func (mr *MockhareMockRecorder) MinerWeight(ctx, node, layer any) *MockhareMinerWeightCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MinerWeight", reflect.TypeOf((*Mockhare)(nil).MinerWeight), ctx, node, layer) + return &MockhareMinerWeightCall{Call: call} +} + +// MockhareMinerWeightCall wrap *gomock.Call +type MockhareMinerWeightCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockhareMinerWeightCall) Return(arg0 uint64, arg1 error) *MockhareMinerWeightCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockhareMinerWeightCall) Do(f func(context.Context, types.NodeID, types.LayerID) (uint64, error)) *MockhareMinerWeightCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockhareMinerWeightCall) DoAndReturn(f func(context.Context, types.NodeID, types.LayerID) (uint64, error)) *MockhareMinerWeightCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// RoundMessage mocks base method. +func (m *Mockhare) RoundMessage(layer types.LayerID, round hare3.IterRound) *hare3.Message { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RoundMessage", layer, round) + ret0, _ := ret[0].(*hare3.Message) + return ret0 +} + +// RoundMessage indicates an expected call of RoundMessage. +func (mr *MockhareMockRecorder) RoundMessage(layer, round any) *MockhareRoundMessageCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RoundMessage", reflect.TypeOf((*Mockhare)(nil).RoundMessage), layer, round) + return &MockhareRoundMessageCall{Call: call} +} + +// MockhareRoundMessageCall wrap *gomock.Call +type MockhareRoundMessageCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockhareRoundMessageCall) Return(arg0 *hare3.Message) *MockhareRoundMessageCall { + c.Call = c.Call.Return(arg0) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockhareRoundMessageCall) Do(f func(types.LayerID, hare3.IterRound) *hare3.Message) *MockhareRoundMessageCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockhareRoundMessageCall) DoAndReturn(f func(types.LayerID, hare3.IterRound) *hare3.Message) *MockhareRoundMessageCall { + c.Call = c.Call.DoAndReturn(f) + return c +} + +// TotalWeight mocks base method. +func (m *Mockhare) TotalWeight(ctx context.Context, layer types.LayerID) (uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "TotalWeight", ctx, layer) + ret0, _ := ret[0].(uint64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// TotalWeight indicates an expected call of TotalWeight. +func (mr *MockhareMockRecorder) TotalWeight(ctx, layer any) *MockhareTotalWeightCall { + mr.mock.ctrl.T.Helper() + call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TotalWeight", reflect.TypeOf((*Mockhare)(nil).TotalWeight), ctx, layer) + return &MockhareTotalWeightCall{Call: call} +} + +// MockhareTotalWeightCall wrap *gomock.Call +type MockhareTotalWeightCall struct { + *gomock.Call +} + +// Return rewrite *gomock.Call.Return +func (c *MockhareTotalWeightCall) Return(arg0 uint64, arg1 error) *MockhareTotalWeightCall { + c.Call = c.Call.Return(arg0, arg1) + return c +} + +// Do rewrite *gomock.Call.Do +func (c *MockhareTotalWeightCall) Do(f func(context.Context, types.LayerID) (uint64, error)) *MockhareTotalWeightCall { + c.Call = c.Call.Do(f) + return c +} + +// DoAndReturn rewrite *gomock.Call.DoAndReturn +func (c *MockhareTotalWeightCall) DoAndReturn(f func(context.Context, types.LayerID) (uint64, error)) *MockhareTotalWeightCall { + c.Call = c.Call.DoAndReturn(f) + return c +} diff --git a/api/node/server/server.gen.go b/api/node/server/server.gen.go index 64ed4825d0..8e80c59d1e 100644 --- a/api/node/server/server.gen.go +++ b/api/node/server/server.gen.go @@ -45,6 +45,18 @@ type ServerInterface interface { // Get Positioning ATX ID with given maximum publish epoch // (GET /activation/positioning_atx/{publish_epoch}) GetActivationPositioningAtxPublishEpoch(w http.ResponseWriter, r *http.Request, publishEpoch externalRef0.EpochID) + // Get the beacon value for an epoch + // (GET /hare/beacon/{epoch}) + GetHareBeaconEpoch(w http.ResponseWriter, r *http.Request, epoch externalRef0.EpochID) + // Get a hare message to sign + // (GET /hare/round_template/{layer}/{iter}/{round}) + GetHareRoundTemplateLayerIterRound(w http.ResponseWriter, r *http.Request, layer externalRef0.LayerID, iter externalRef0.HareIter, round externalRef0.HareRound) + // Get the total weight for layer + // (GET /hare/total_weight/{layer}) + GetHareTotalWeightLayer(w http.ResponseWriter, r *http.Request, layer uint32) + // Get the miner weight in layer + // (GET /hare/weight/{node_id}/{layer}) + GetHareWeightNodeIdLayer(w http.ResponseWriter, r *http.Request, nodeId externalRef0.NodeID, layer uint32) // Store PoET proof // (POST /poet) PostPoet(w http.ResponseWriter, r *http.Request) @@ -137,6 +149,133 @@ func (siw *ServerInterfaceWrapper) GetActivationPositioningAtxPublishEpoch(w htt handler.ServeHTTP(w, r) } +// GetHareBeaconEpoch operation middleware +func (siw *ServerInterfaceWrapper) GetHareBeaconEpoch(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "epoch" ------------- + var epoch externalRef0.EpochID + + err = runtime.BindStyledParameterWithOptions("simple", "epoch", r.PathValue("epoch"), &epoch, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "epoch", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetHareBeaconEpoch(w, r, epoch) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetHareRoundTemplateLayerIterRound operation middleware +func (siw *ServerInterfaceWrapper) GetHareRoundTemplateLayerIterRound(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "layer" ------------- + var layer externalRef0.LayerID + + err = runtime.BindStyledParameterWithOptions("simple", "layer", r.PathValue("layer"), &layer, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "layer", Err: err}) + return + } + + // ------------- Path parameter "iter" ------------- + var iter externalRef0.HareIter + + err = runtime.BindStyledParameterWithOptions("simple", "iter", r.PathValue("iter"), &iter, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "iter", Err: err}) + return + } + + // ------------- Path parameter "round" ------------- + var round externalRef0.HareRound + + err = runtime.BindStyledParameterWithOptions("simple", "round", r.PathValue("round"), &round, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "round", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetHareRoundTemplateLayerIterRound(w, r, layer, iter, round) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetHareTotalWeightLayer operation middleware +func (siw *ServerInterfaceWrapper) GetHareTotalWeightLayer(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "layer" ------------- + var layer uint32 + + err = runtime.BindStyledParameterWithOptions("simple", "layer", r.PathValue("layer"), &layer, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "layer", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetHareTotalWeightLayer(w, r, layer) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetHareWeightNodeIdLayer operation middleware +func (siw *ServerInterfaceWrapper) GetHareWeightNodeIdLayer(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "node_id" ------------- + var nodeId externalRef0.NodeID + + err = runtime.BindStyledParameterWithOptions("simple", "node_id", r.PathValue("node_id"), &nodeId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "node_id", Err: err}) + return + } + + // ------------- Path parameter "layer" ------------- + var layer uint32 + + err = runtime.BindStyledParameterWithOptions("simple", "layer", r.PathValue("layer"), &layer, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "layer", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetHareWeightNodeIdLayer(w, r, nodeId, layer) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // PostPoet operation middleware func (siw *ServerInterfaceWrapper) PostPoet(w http.ResponseWriter, r *http.Request) { @@ -299,6 +438,10 @@ func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.H m.HandleFunc("GET "+options.BaseURL+"/activation/atx/{atx_id}", wrapper.GetActivationAtxAtxId) m.HandleFunc("GET "+options.BaseURL+"/activation/last_atx/{node_id}", wrapper.GetActivationLastAtxNodeId) m.HandleFunc("GET "+options.BaseURL+"/activation/positioning_atx/{publish_epoch}", wrapper.GetActivationPositioningAtxPublishEpoch) + m.HandleFunc("GET "+options.BaseURL+"/hare/beacon/{epoch}", wrapper.GetHareBeaconEpoch) + m.HandleFunc("GET "+options.BaseURL+"/hare/round_template/{layer}/{iter}/{round}", wrapper.GetHareRoundTemplateLayerIterRound) + m.HandleFunc("GET "+options.BaseURL+"/hare/total_weight/{layer}", wrapper.GetHareTotalWeightLayer) + m.HandleFunc("GET "+options.BaseURL+"/hare/weight/{node_id}/{layer}", wrapper.GetHareWeightNodeIdLayer) m.HandleFunc("POST "+options.BaseURL+"/poet", wrapper.PostPoet) m.HandleFunc("POST "+options.BaseURL+"/publish/{protocol}", wrapper.PostPublishProtocol) @@ -393,6 +536,149 @@ func (response GetActivationPositioningAtxPublishEpoch200JSONResponse) VisitGetA return json.NewEncoder(w).Encode(response) } +type GetHareBeaconEpochRequestObject struct { + Epoch externalRef0.EpochID `json:"epoch"` +} + +type GetHareBeaconEpochResponseObject interface { + VisitGetHareBeaconEpochResponse(w http.ResponseWriter) error +} + +type GetHareBeaconEpoch200ApplicationoctetStreamResponse struct { + Body io.Reader + ContentLength int64 +} + +func (response GetHareBeaconEpoch200ApplicationoctetStreamResponse) VisitGetHareBeaconEpochResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/octet-stream") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err +} + +type GetHareBeaconEpoch204Response struct { +} + +func (response GetHareBeaconEpoch204Response) VisitGetHareBeaconEpochResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type GetHareRoundTemplateLayerIterRoundRequestObject struct { + Layer externalRef0.LayerID `json:"layer"` + Iter externalRef0.HareIter `json:"iter"` + Round externalRef0.HareRound `json:"round"` +} + +type GetHareRoundTemplateLayerIterRoundResponseObject interface { + VisitGetHareRoundTemplateLayerIterRoundResponse(w http.ResponseWriter) error +} + +type GetHareRoundTemplateLayerIterRound200ApplicationoctetStreamResponse struct { + Body io.Reader + ContentLength int64 +} + +func (response GetHareRoundTemplateLayerIterRound200ApplicationoctetStreamResponse) VisitGetHareRoundTemplateLayerIterRoundResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/octet-stream") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err +} + +type GetHareRoundTemplateLayerIterRound204Response struct { +} + +func (response GetHareRoundTemplateLayerIterRound204Response) VisitGetHareRoundTemplateLayerIterRoundResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type GetHareTotalWeightLayerRequestObject struct { + Layer uint32 `json:"layer"` +} + +type GetHareTotalWeightLayerResponseObject interface { + VisitGetHareTotalWeightLayerResponse(w http.ResponseWriter) error +} + +type GetHareTotalWeightLayer200ApplicationoctetStreamResponse struct { + Body io.Reader + ContentLength int64 +} + +func (response GetHareTotalWeightLayer200ApplicationoctetStreamResponse) VisitGetHareTotalWeightLayerResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/octet-stream") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err +} + +type GetHareTotalWeightLayer204Response struct { +} + +func (response GetHareTotalWeightLayer204Response) VisitGetHareTotalWeightLayerResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type GetHareWeightNodeIdLayerRequestObject struct { + NodeId externalRef0.NodeID `json:"node_id"` + Layer uint32 `json:"layer"` +} + +type GetHareWeightNodeIdLayerResponseObject interface { + VisitGetHareWeightNodeIdLayerResponse(w http.ResponseWriter) error +} + +type GetHareWeightNodeIdLayer200ApplicationoctetStreamResponse struct { + Body io.Reader + ContentLength int64 +} + +func (response GetHareWeightNodeIdLayer200ApplicationoctetStreamResponse) VisitGetHareWeightNodeIdLayerResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/octet-stream") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err +} + +type GetHareWeightNodeIdLayer204Response struct { +} + +func (response GetHareWeightNodeIdLayer204Response) VisitGetHareWeightNodeIdLayerResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + type PostPoetRequestObject struct { Body io.Reader } @@ -456,6 +742,18 @@ type StrictServerInterface interface { // Get Positioning ATX ID with given maximum publish epoch // (GET /activation/positioning_atx/{publish_epoch}) GetActivationPositioningAtxPublishEpoch(ctx context.Context, request GetActivationPositioningAtxPublishEpochRequestObject) (GetActivationPositioningAtxPublishEpochResponseObject, error) + // Get the beacon value for an epoch + // (GET /hare/beacon/{epoch}) + GetHareBeaconEpoch(ctx context.Context, request GetHareBeaconEpochRequestObject) (GetHareBeaconEpochResponseObject, error) + // Get a hare message to sign + // (GET /hare/round_template/{layer}/{iter}/{round}) + GetHareRoundTemplateLayerIterRound(ctx context.Context, request GetHareRoundTemplateLayerIterRoundRequestObject) (GetHareRoundTemplateLayerIterRoundResponseObject, error) + // Get the total weight for layer + // (GET /hare/total_weight/{layer}) + GetHareTotalWeightLayer(ctx context.Context, request GetHareTotalWeightLayerRequestObject) (GetHareTotalWeightLayerResponseObject, error) + // Get the miner weight in layer + // (GET /hare/weight/{node_id}/{layer}) + GetHareWeightNodeIdLayer(ctx context.Context, request GetHareWeightNodeIdLayerRequestObject) (GetHareWeightNodeIdLayerResponseObject, error) // Store PoET proof // (POST /poet) PostPoet(ctx context.Context, request PostPoetRequestObject) (PostPoetResponseObject, error) @@ -571,6 +869,113 @@ func (sh *strictHandler) GetActivationPositioningAtxPublishEpoch(w http.Response } } +// GetHareBeaconEpoch operation middleware +func (sh *strictHandler) GetHareBeaconEpoch(w http.ResponseWriter, r *http.Request, epoch externalRef0.EpochID) { + var request GetHareBeaconEpochRequestObject + + request.Epoch = epoch + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetHareBeaconEpoch(ctx, request.(GetHareBeaconEpochRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetHareBeaconEpoch") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetHareBeaconEpochResponseObject); ok { + if err := validResponse.VisitGetHareBeaconEpochResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// GetHareRoundTemplateLayerIterRound operation middleware +func (sh *strictHandler) GetHareRoundTemplateLayerIterRound(w http.ResponseWriter, r *http.Request, layer externalRef0.LayerID, iter externalRef0.HareIter, round externalRef0.HareRound) { + var request GetHareRoundTemplateLayerIterRoundRequestObject + + request.Layer = layer + request.Iter = iter + request.Round = round + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetHareRoundTemplateLayerIterRound(ctx, request.(GetHareRoundTemplateLayerIterRoundRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetHareRoundTemplateLayerIterRound") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetHareRoundTemplateLayerIterRoundResponseObject); ok { + if err := validResponse.VisitGetHareRoundTemplateLayerIterRoundResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// GetHareTotalWeightLayer operation middleware +func (sh *strictHandler) GetHareTotalWeightLayer(w http.ResponseWriter, r *http.Request, layer uint32) { + var request GetHareTotalWeightLayerRequestObject + + request.Layer = layer + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetHareTotalWeightLayer(ctx, request.(GetHareTotalWeightLayerRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetHareTotalWeightLayer") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetHareTotalWeightLayerResponseObject); ok { + if err := validResponse.VisitGetHareTotalWeightLayerResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// GetHareWeightNodeIdLayer operation middleware +func (sh *strictHandler) GetHareWeightNodeIdLayer(w http.ResponseWriter, r *http.Request, nodeId externalRef0.NodeID, layer uint32) { + var request GetHareWeightNodeIdLayerRequestObject + + request.NodeId = nodeId + request.Layer = layer + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetHareWeightNodeIdLayer(ctx, request.(GetHareWeightNodeIdLayerRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetHareWeightNodeIdLayer") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetHareWeightNodeIdLayerResponseObject); ok { + if err := validResponse.VisitGetHareWeightNodeIdLayerResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // PostPoet operation middleware func (sh *strictHandler) PostPoet(w http.ResponseWriter, r *http.Request) { var request PostPoetRequestObject diff --git a/api/node/server/server.go b/api/node/server/server.go index 2af1ecdacd..a9c9986461 100644 --- a/api/node/server/server.go +++ b/api/node/server/server.go @@ -5,8 +5,10 @@ import ( "context" "encoding/hex" "errors" + "fmt" "io" "net/http" + "strconv" "github.com/google/uuid" "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" @@ -16,6 +18,7 @@ import ( "github.com/spacemeshos/go-spacemesh/api/node/models" "github.com/spacemeshos/go-spacemesh/codec" "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/hare3" "github.com/spacemeshos/go-spacemesh/p2p/pubsub" ) @@ -25,10 +28,18 @@ type poetDB interface { ValidateAndStore(ctx context.Context, proofMessage *types.PoetProofMessage) error } +type hare interface { + RoundMessage(layer types.LayerID, round hare3.IterRound) *hare3.Message + TotalWeight(ctx context.Context, layer types.LayerID) (uint64, error) + MinerWeight(ctx context.Context, node types.NodeID, layer types.LayerID) (uint64, error) + Beacon(ctx context.Context, epoch types.EpochID) (types.Beacon, error) +} + type Server struct { atxService activation.AtxService publisher pubsub.Publisher poetDB poetDB + hare hare logger *zap.Logger } @@ -38,12 +49,14 @@ func NewServer( atxService activation.AtxService, publisher pubsub.Publisher, poetDB poetDB, + hare hare, logger *zap.Logger, ) *Server { return &Server{ atxService: atxService, publisher: publisher, poetDB: poetDB, + hare: hare, logger: logger, } } @@ -196,3 +209,101 @@ func (s *Server) PostPoet(ctx context.Context, request PostPoetRequestObject) (P } return PostPoet200Response{}, nil } + +type hareResponse struct { + message []byte +} + +func (h *hareResponse) VisitGetHareRoundTemplateLayerIterRoundResponse(w http.ResponseWriter) error { + if h.message == nil { + w.WriteHeader(204) // no content + return nil + } + w.Header().Add("content-type", "application/octet-stream") + w.WriteHeader(200) + _, err := w.Write(h.message) + return err +} + +func (s *Server) GetHareRoundTemplateLayerIterRound(ctx context.Context, + request GetHareRoundTemplateLayerIterRoundRequestObject, +) (GetHareRoundTemplateLayerIterRoundResponseObject, error) { + msg := s.hare.RoundMessage(types.LayerID(request.Layer), + hare3.IterRound{ + Round: hare3.Round(request.Round), + Iter: (request.Iter), + }) + if msg == nil { + return &hareResponse{}, nil + } + + return &hareResponse{ + message: codec.MustEncode(msg), + }, nil +} + +type totalWeightResp struct { + w uint64 +} + +func (t *totalWeightResp) VisitGetHareTotalWeightLayerResponse(w http.ResponseWriter) error { + w.Header().Add("content-type", "application/octet-stream") + w.WriteHeader(200) + _, err := w.Write([]byte(strconv.FormatUint(t.w, 10))) + return err +} + +func (s *Server) GetHareTotalWeightLayer(ctx context.Context, + req GetHareTotalWeightLayerRequestObject, +) (GetHareTotalWeightLayerResponseObject, error) { + weight, err := s.hare.TotalWeight(ctx, types.LayerID(req.Layer)) + if err != nil { + return nil, err + } + return &totalWeightResp{weight}, nil +} + +type nodeWeightResp struct { + val uint64 +} + +func (n *nodeWeightResp) VisitGetHareWeightNodeIdLayerResponse(w http.ResponseWriter) error { + w.Header().Add("content-type", "application/octet-stream") + w.WriteHeader(200) + _, err := w.Write([]byte(strconv.FormatUint(n.val, 10))) + return err +} + +func (s *Server) GetHareWeightNodeIdLayer(ctx context.Context, + request GetHareWeightNodeIdLayerRequestObject, +) (GetHareWeightNodeIdLayerResponseObject, error) { + hexBuf, err := hex.DecodeString(request.NodeId) + if err != nil { + return nil, fmt.Errorf("decode node id: %w", err) + } + id := types.BytesToNodeID(hexBuf) + weight, err := s.hare.MinerWeight(ctx, id, types.LayerID(request.Layer)) + if err != nil { + return nil, fmt.Errorf("miner weight: %w", err) + } + return &nodeWeightResp{val: weight}, nil +} + +type beaconResp struct{ b types.Beacon } + +func (b *beaconResp) VisitGetHareBeaconEpochResponse(w http.ResponseWriter) error { + w.Header().Add("content-type", "application/octet-stream") + w.WriteHeader(200) + _, err := w.Write(b.b[:]) + return err +} + +func (s *Server) GetHareBeaconEpoch(ctx context.Context, + request GetHareBeaconEpochRequestObject, +) (GetHareBeaconEpochResponseObject, error) { + beacon, err := s.hare.Beacon(ctx, types.EpochID(request.Epoch)) + if err != nil { + return nil, err + } + return &beaconResp{b: beacon}, nil +} diff --git a/hare3/eligibility/oracle.go b/hare3/eligibility/oracle.go index d72693fa46..6503c56ead 100644 --- a/hare3/eligibility/oracle.go +++ b/hare3/eligibility/oracle.go @@ -92,16 +92,30 @@ type Oracle struct { // until graded oracle is implemented synced bool - beacons system.BeaconGetter - atxsdata *atxsdata.Data - db sql.Executor - vrfVerifier vrfVerifier - cfg Config - log *zap.Logger + beacons system.BeaconGetter + atxsdata *atxsdata.Data + db sql.Executor + vrfVerifier vrfVerifier + cfg Config + minerWeightFn func(ctx context.Context, layer types.LayerID, id types.NodeID) (uint64, error) + totalWeightFn func(ctx context.Context, layer types.LayerID) (uint64, error) + log *zap.Logger } type Opt func(*Oracle) +func WithMinerWeightFunc(f func(ctx context.Context, layer types.LayerID, id types.NodeID) (uint64, error)) Opt { + return func(o *Oracle) { + o.minerWeightFn = f + } +} + +func WithTotalWeightFunc(f func(ctx context.Context, layer types.LayerID) (uint64, error)) Opt { + return func(o *Oracle) { + o.totalWeightFn = f + } +} + func WithConfig(config Config) Opt { return func(o *Oracle) { o.cfg = config @@ -120,7 +134,6 @@ func New( db sql.Executor, atxsdata *atxsdata.Data, vrfVerifier vrfVerifier, - layersPerEpoch uint32, opts ...Opt, ) *Oracle { activesCache, err := lru.New[types.EpochID, *cachedActiveSet](activesCacheSize) @@ -137,10 +150,12 @@ func New( cfg: DefaultConfig(), log: zap.NewNop(), } + oracle.minerWeightFn = oracle.minerWeight + oracle.totalWeightFn = oracle.totalWeight for _, opt := range opts { opt(oracle) } - oracle.log.Info("hare oracle initialized", zap.Uint32("epoch size", layersPerEpoch), zap.Inline(&oracle.cfg)) + oracle.log.Info("hare oracle initialized", zap.Inline(&oracle.cfg)) return oracle } @@ -233,7 +248,7 @@ func (o *Oracle) prepareEligibilityCheck( // calc hash & check threshold // this is cheap in case the node is not eligible - minerWeight, err := o.minerWeight(ctx, layer, id) + minerWeight, err := o.minerWeightFn(ctx, layer, id) if err != nil { return 0, fixed.Fixed{}, fixed.Fixed{}, true, err } @@ -251,7 +266,7 @@ func (o *Oracle) prepareEligibilityCheck( } // get active set size - totalWeight, err := o.totalWeight(ctx, layer) + totalWeight, err := o.totalWeightFn(ctx, layer) if err != nil { logger.Error("failed to get total weight", zap.Error(err)) return 0, fixed.Fixed{}, fixed.Fixed{}, true, err @@ -565,3 +580,11 @@ func (o *Oracle) UpdateActiveSet(epoch types.EpochID, activeSet []types.ATXID) { } o.fallback[epoch] = activeSet } + +func (o *Oracle) TotalWeight(ctx context.Context, layer types.LayerID) (uint64, error) { + return o.totalWeightFn(ctx, layer) +} + +func (o *Oracle) MinerWeight(ctx context.Context, node types.NodeID, layer types.LayerID) (uint64, error) { + return o.minerWeightFn(ctx, layer, node) +} diff --git a/hare3/eligibility/oracle_test.go b/hare3/eligibility/oracle_test.go index fac51a5b6b..916f34432d 100644 --- a/hare3/eligibility/oracle_test.go +++ b/hare3/eligibility/oracle_test.go @@ -67,7 +67,6 @@ func defaultOracle(tb testing.TB) *testOracle { db, atxsdata, mVerifier, - defLayersPerEpoch, WithConfig(Config{ConfidenceParam: confidenceParam}), WithLogger(zaptest.NewLogger(tb)), ), diff --git a/hare3/hare.go b/hare3/hare.go index 50ace9df0a..1473e00986 100644 --- a/hare3/hare.go +++ b/hare3/hare.go @@ -183,12 +183,13 @@ func New( ) *Hare { ctx, cancel := context.WithCancel(context.Background()) hr := &Hare{ - ctx: ctx, - cancel: cancel, - results: make(chan hare4.ConsensusOutput, 32), - coins: make(chan hare4.WeakCoinOutput, 32), - signers: map[string]*signing.EdSigner{}, - sessions: map[types.LayerID]*protocol{}, + ctx: ctx, + cancel: cancel, + results: make(chan hare4.ConsensusOutput, 32), + coins: make(chan hare4.WeakCoinOutput, 32), + signers: map[string]*signing.EdSigner{}, + sessions: map[types.LayerID]*protocol{}, + layerResults: make(map[types.LayerID]map[IterRound]output), config: DefaultConfig(), log: zap.NewNop(), @@ -217,14 +218,15 @@ func New( type Hare struct { // state - ctx context.Context - cancel context.CancelFunc - eg errgroup.Group - results chan hare4.ConsensusOutput - coins chan hare4.WeakCoinOutput - mu sync.Mutex - signers map[string]*signing.EdSigner - sessions map[types.LayerID]*protocol + ctx context.Context + cancel context.CancelFunc + eg errgroup.Group + results chan hare4.ConsensusOutput + coins chan hare4.WeakCoinOutput + mu sync.Mutex + signers map[string]*signing.EdSigner + sessions map[types.LayerID]*protocol + layerResults map[types.LayerID]map[IterRound]output // options config Config @@ -278,6 +280,7 @@ func (h *Hare) Start() { case <-h.nodeClock.AwaitLayer(next): h.log.Debug("notified", zap.Uint32("lid", next.Uint32())) h.onLayer(next) + h.cleanupLayer(next - 1) case <-h.ctx.Done(): return nil } @@ -370,7 +373,6 @@ func (h *Hare) onLayer(layer types.LayerID) { return } h.patrol.SetHareInCharge(layer) - h.mu.Lock() // signer can't join mid session s := &session{ @@ -410,6 +412,17 @@ func (h *Hare) onLayer(layer types.LayerID) { }) } +func (h *Hare) layerResult(layer types.LayerID, iter IterRound, out output) { + h.mu.Lock() + defer h.mu.Unlock() + + _, ok := h.layerResults[layer] + if !ok { + h.layerResults[layer] = make(map[IterRound]output) + } + h.layerResults[layer][iter] = out +} + func (h *Hare) run(session *session) error { // oracle may load non-negligible amount of data from disk // we do it before preround starts, so that load can have some slack time @@ -439,7 +452,10 @@ func (h *Hare) run(session *session) error { session.proto.OnInitial(h.selectProposals(session)) proposalsLatency.Observe(time.Since(start).Seconds()) } - if err := h.onOutput(session, current, session.proto.Next()); err != nil { + + out := session.proto.Next() + h.layerResult(session.lid, current, out) + if err := h.onOutput(session, current, out); err != nil { return err } result := false @@ -469,6 +485,9 @@ func (h *Hare) run(session *session) error { if out.result != nil { result = true } + + h.layerResult(session.lid, current, out) + if err := h.onOutput(session, current, out); err != nil { return err } @@ -637,3 +656,44 @@ type session struct { signers []*signing.EdSigner vrfs []*types.HareEligibility } + +func (h *Hare) cleanupLayer(l types.LayerID) { + h.mu.Lock() + defer h.mu.Unlock() + var rmLayer []types.LayerID + for layer := range h.layerResults { + if layer <= l { + rmLayer = append(rmLayer, layer) + } + } + for _, k := range rmLayer { + delete(h.layerResults, k) + } +} + +func (h *Hare) RoundMessage(layer types.LayerID, round IterRound) *Message { + h.mu.Lock() + defer h.mu.Unlock() + + l, ok := h.layerResults[layer] + if !ok { + return nil + } + r, ok := l[round] + if !ok { + return nil + } + return r.message +} + +func (h *Hare) TotalWeight(ctx context.Context, layer types.LayerID) (uint64, error) { + return h.oracle.oracle.TotalWeight(ctx, layer) +} + +func (h *Hare) MinerWeight(ctx context.Context, miner types.NodeID, layer types.LayerID) (uint64, error) { + return h.oracle.oracle.MinerWeight(ctx, miner, layer) +} + +func (h *Hare) Beacon(ctx context.Context, epoch types.EpochID) (types.Beacon, error) { + return beacons.Get(h.db, epoch) +} diff --git a/hare3/hare_test.go b/hare3/hare_test.go index 6feae4678b..193f7ae0fc 100644 --- a/hare3/hare_test.go +++ b/hare3/hare_test.go @@ -197,7 +197,6 @@ func (n *node) withOracle() *node { n.db, n.atxsdata, signing.NewVRFVerifier(), - layersPerEpoch, ) return n } diff --git a/hare3/legacy_oracle.go b/hare3/legacy_oracle.go index 07be7f77a4..1498636df2 100644 --- a/hare3/legacy_oracle.go +++ b/hare3/legacy_oracle.go @@ -14,6 +14,8 @@ import ( type oracle interface { Validate(context.Context, types.LayerID, uint32, int, types.NodeID, types.VrfSignature, uint16) (bool, error) CalcEligibility(context.Context, types.LayerID, uint32, int, types.NodeID, types.VrfSignature) (uint16, error) + TotalWeight(ctx context.Context, layer types.LayerID) (uint64, error) + MinerWeight(ctx context.Context, node types.NodeID, layer types.LayerID) (uint64, error) } type legacyOracle struct { diff --git a/hare3/remote.go b/hare3/remote.go new file mode 100644 index 0000000000..871b943f06 --- /dev/null +++ b/hare3/remote.go @@ -0,0 +1,269 @@ +package hare3 + +import ( + "context" + "math" + "sync" + "time" + + "github.com/jonboulle/clockwork" + "go.uber.org/zap" + "golang.org/x/exp/maps" + "golang.org/x/sync/errgroup" + + "github.com/spacemeshos/go-spacemesh/codec" + "github.com/spacemeshos/go-spacemesh/common/types" + "github.com/spacemeshos/go-spacemesh/log" + "github.com/spacemeshos/go-spacemesh/signing" +) + +type NodeService interface { + GetHareMessage(ctx context.Context, layer types.LayerID, round IterRound) ([]byte, error) + Beacon(ctx context.Context, epoch types.EpochID) (types.Beacon, error) + Publish(ctx context.Context, proto string, blob []byte) error +} + +type RemoteHare struct { + config Config + wallClock clockwork.Clock + nodeClock nodeClock + mu sync.Mutex + beacons map[types.EpochID]types.Beacon + signers map[string]*signing.EdSigner + oracle *legacyOracle + sessions map[types.LayerID]*protocol + eg errgroup.Group + ctx context.Context + svc NodeService + + log *zap.Logger +} + +func NewRemoteHare(config Config, + nodeClock nodeClock, + nodeService NodeService, + oracle oracle, + log *zap.Logger, +) *RemoteHare { + return &RemoteHare{ + config: config, + nodeClock: nodeClock, + beacons: make(map[types.EpochID]types.Beacon), + signers: make(map[string]*signing.EdSigner), + oracle: &legacyOracle{ + log: zap.NewNop(), + oracle: oracle, + config: DefaultConfig(), + }, + + sessions: make(map[types.LayerID]*protocol), + eg: errgroup.Group{}, + svc: nodeService, + log: log, + wallClock: clockwork.NewRealClock(), + } +} + +func (h *RemoteHare) Register(sig *signing.EdSigner) { + h.mu.Lock() + defer h.mu.Unlock() + h.log.Info("registered signing key", log.ZShortStringer("id", sig.NodeID())) + h.signers[string(sig.NodeID().Bytes())] = sig +} + +func (h *RemoteHare) Start(ctx context.Context) { + current := h.nodeClock.CurrentLayer() + 1 + enabled := max(current, h.config.EnableLayer, types.GetEffectiveGenesis()+1) + disabled := types.LayerID(math.MaxUint32) + h.log.Info("started", + zap.Inline(&h.config), + zap.Uint32("enabled", enabled.Uint32()), + zap.Uint32("disabled", disabled.Uint32()), + ) + h.eg.Go(func() error { + h.log.Info("remote hare processing starting") + for next := enabled; next < disabled; next++ { + h.log.Info("remote hare processing layer", zap.Int("next", int(next))) + select { + case <-h.nodeClock.AwaitLayer(next): + h.log.Debug("notified", zap.Uint32("layer", next.Uint32())) + h.onLayer(ctx, next) + case <-ctx.Done(): + h.log.Info("remote hare exiting") + return nil + } + } + return nil + }) +} + +func (h *RemoteHare) beacon(ctx context.Context, e types.EpochID) types.Beacon { + h.mu.Lock() + defer h.mu.Unlock() + b, ok := h.beacons[e] + if !ok { + bcn, err := h.svc.Beacon(ctx, e) + if err != nil { + h.log.Error("error getting beacon", zap.Error(err)) + return types.EmptyBeacon + } + h.beacons[e] = bcn + return bcn + } + + return b +} + +func (h *RemoteHare) onLayer(ctx context.Context, layer types.LayerID) { + h.log.Debug("remote hare: on layer", zap.Int("layer", int(layer))) + beacon := h.beacon(ctx, layer.GetEpoch()) + if beacon == types.EmptyBeacon { + h.log.Debug("no beacon", + zap.Uint32("epoch", layer.GetEpoch().Uint32()), + zap.Uint32("lid", layer.Uint32()), + ) + return + } + + h.mu.Lock() + s := &session{ + lid: layer, + beacon: beacon, + signers: maps.Values(h.signers), + vrfs: make([]*types.HareEligibility, len(h.signers)), + proto: newProtocol(h.config.CommitteeFor(layer)/2 + 1), + } + h.sessions[layer] = s.proto + h.mu.Unlock() + + sessionStart.Inc() + h.log.Debug("registered layer", zap.Uint32("lid", layer.Uint32())) + h.eg.Go(func() error { + if err := h.run(ctx, s); err != nil { + h.log.Warn("failed", + zap.Uint32("lid", layer.Uint32()), + zap.Error(err), + ) + exitErrors.Inc() + } else { + h.log.Debug("terminated", + zap.Uint32("lid", layer.Uint32()), + ) + } + h.mu.Lock() + delete(h.sessions, layer) + h.mu.Unlock() + sessionTerminated.Inc() + return nil + }) +} + +func (h *RemoteHare) run(ctx context.Context, session *session) error { + var ( + current = IterRound{Round: preround} + start = time.Now() + active bool + ) + for i, signer := range session.signers { + session.vrfs[i] = h.oracle.active(signer, session.beacon, session.lid, current) + active = active || session.vrfs[i] != nil + } + activeLatency.Observe(time.Since(start).Seconds()) + + walltime := h.nodeClock.LayerToTime(session.lid).Add(h.config.PreroundDelay) + if active { + h.log.Debug("active in preround. waiting for preround delay", zap.Uint32("lid", session.lid.Uint32())) + select { + case <-h.wallClock.After(walltime.Sub(h.wallClock.Now())): + case <-h.ctx.Done(): + return h.ctx.Err() + } + } + msgBytes, err := h.svc.GetHareMessage(ctx, session.lid, session.proto.IterRound) + if err != nil && active { + h.log.Error("get hare message on preround", zap.Error(err)) + } else { + msg := &Message{} + if err := codec.Decode(msgBytes, msg); err != nil { + h.log.Error("decode remote hare message", zap.Error(err)) + } else { + h.signPub(ctx, session, msg) + } + } + + onRound(session.proto) + for { + if session.proto.IterRound.Iter >= h.config.IterationsLimit { + return nil + } + + walltime = walltime.Add(h.config.RoundDuration) + current = session.proto.IterRound + start = time.Now() + active := false + + for i := range session.signers { + if current.IsMessageRound() { + session.vrfs[i] = h.oracle.active(session.signers[i], session.beacon, session.lid, current) + active = active || (session.vrfs[i] != nil) + } else { + session.vrfs[i] = nil + } + } + activeLatency.Observe(time.Since(start).Seconds()) + + select { + case <-h.wallClock.After(walltime.Sub(h.wallClock.Now())): + if active { + h.log.Debug("execute round", + zap.Uint32("lid", session.lid.Uint32()), + zap.Uint8("iter", session.proto.Iter), zap.Stringer("round", session.proto.Round), + zap.Bool("active", active), + ) + + msgBytes, err := h.svc.GetHareMessage(ctx, session.lid, session.proto.IterRound) + if err != nil { + h.log.Error("get hare message", zap.Error(err)) + onRound(session.proto) // advance the protocol state before continuing + continue + } + msg := &Message{} + if err := codec.Decode(msgBytes, msg); err != nil { + h.log.Error("decode remote hare message", zap.Error(err)) + } + h.signPub(ctx, session, msg) + } + + onRound(session.proto) // advance the protocol state before continuing + case <-h.ctx.Done(): + return nil + } + } +} + +func (h *RemoteHare) signPub(ctx context.Context, session *session, message *Message) { + for i, vrf := range session.vrfs { + if vrf == nil { + continue + } + msg := *message + msg.Layer = session.lid + msg.Eligibility = *vrf + msg.Sender = session.signers[i].NodeID() + msg.Signature = session.signers[i].Sign(signing.HARE, msg.ToMetadata().ToBytes()) + if err := h.svc.Publish(ctx, h.config.ProtocolName, msg.ToBytes()); err != nil { + h.log.Error("failed to publish", zap.Inline(&msg), zap.Error(err)) + } + } +} + +func onRound(p *protocol) { + if p.Round == preround && p.Iter == 0 { + p.Round = softlock + } else if p.Round == notify { + p.Round = hardlock + p.Iter++ + } else { + p.Round++ + } +} diff --git a/node/node.go b/node/node.go index 2093b95e10..0270d7579d 100644 --- a/node/node.go +++ b/node/node.go @@ -405,6 +405,7 @@ type App struct { clock *timesync.NodeClock hare3 *hare3.Hare hare4 *hare4.Hare + remoteHare *hare3.RemoteHare hareResultsChan chan hare4.ConsensusOutput hOracle *eligibility.Oracle blockGen *blocks.Generator @@ -802,15 +803,20 @@ func (app *App) initServices(ctx context.Context) error { app.host.ID(), app.addLogger(TxHandlerLogger, lg).Zap(), ) - + extraOpts := []eligibility.Opt{ + eligibility.WithConfig(app.Config.HareEligibility), + eligibility.WithLogger(app.addLogger(HareOracleLogger, lg).Zap()), + } + if nodeServiceClient != nil { + extraOpts = append(extraOpts, eligibility.WithTotalWeightFunc(nodeServiceClient.TotalWeight)) + extraOpts = append(extraOpts, eligibility.WithMinerWeightFunc(nodeServiceClient.MinerWeight)) + } app.hOracle = eligibility.New( beaconProtocol, app.db, app.atxsdata, vrfVerifier, - app.Config.LayersPerEpoch, - eligibility.WithConfig(app.Config.HareEligibility), - eligibility.WithLogger(app.addLogger(HareOracleLogger, lg).Zap()), + extraOpts..., ) // TODO: genesisMinerWeight is set to app.Config.SpaceToCommit, because PoET ticks are currently hardcoded to 1 @@ -908,67 +914,80 @@ func (app *App) initServices(ctx context.Context) error { // should be removed after hare4 transition is complete app.hareResultsChan = make(chan hare4.ConsensusOutput, 32) - if app.Config.HARE3.Enable { - app.hare3 = hare3.New( + if nodeServiceClient != nil { + app.remoteHare = hare3.NewRemoteHare( + app.Config.HARE3, app.clock, - app.host, - app.db, - app.atxsdata, - proposalsStore, - app.edVerifier, + nodeServiceClient, app.hOracle, - newSyncer, - patrol, - hare3.WithLogger(logger), - hare3.WithConfig(app.Config.HARE3), - hare3.WithResultsChan(app.hareResultsChan), + logger, ) for _, sig := range app.signers { - app.hare3.Register(sig) + app.remoteHare.Register(sig) } - app.hare3.Start() - app.eg.Go(func() error { - compat.ReportWeakcoin( - ctx, - logger, - app.hare3.Coins(), - tortoiseWeakCoin{db: app.cachedDB, tortoise: trtl}, + app.remoteHare.Start(ctx) + } else { + if app.Config.HARE3.Enable { + app.hare3 = hare3.New( + app.clock, + app.host, + app.db, + app.atxsdata, + proposalsStore, + app.edVerifier, + app.hOracle, + newSyncer, + patrol, + hare3.WithLogger(logger), + hare3.WithConfig(app.Config.HARE3), + hare3.WithResultsChan(app.hareResultsChan), ) - return nil - }) - } - - if app.Config.HARE4.Enable { - app.hare4 = hare4.New( - app.clock, - app.host, - app.db, - app.atxsdata, - proposalsStore, - app.edVerifier, - app.hOracle, - newSyncer, - patrol, - app.host, - hare4.WithLogger(logger), - hare4.WithConfig(app.Config.HARE4), - hare4.WithResultsChan(app.hareResultsChan), - ) - for _, sig := range app.signers { - app.hare4.Register(sig) + for _, sig := range app.signers { + app.hare3.Register(sig) + } + app.hare3.Start() + app.eg.Go(func() error { + compat.ReportWeakcoin( + ctx, + logger, + app.hare3.Coins(), + tortoiseWeakCoin{db: app.cachedDB, tortoise: trtl}, + ) + return nil + }) } - app.hare4.Start() - app.eg.Go(func() error { - compat.ReportWeakcoin( - ctx, - logger, - app.hare4.Coins(), - tortoiseWeakCoin{db: app.cachedDB, tortoise: trtl}, + + if app.Config.HARE4.Enable { + app.hare4 = hare4.New( + app.clock, + app.host, + app.db, + app.atxsdata, + proposalsStore, + app.edVerifier, + app.hOracle, + newSyncer, + patrol, + app.host, + hare4.WithLogger(logger), + hare4.WithConfig(app.Config.HARE4), + hare4.WithResultsChan(app.hareResultsChan), ) - return nil - }) + for _, sig := range app.signers { + app.hare4.Register(sig) + } + app.hare4.Start() + app.eg.Go(func() error { + compat.ReportWeakcoin( + ctx, + logger, + app.hare4.Coins(), + tortoiseWeakCoin{db: app.cachedDB, tortoise: trtl}, + ) + return nil + }) + } } - propHare := &proposalConsumerHare{ hare3: app.hare3, h3DisableLayer: app.Config.HARE3.DisableLayer, @@ -1892,7 +1911,7 @@ func (app *App) startAPIServices(ctx context.Context) error { golden := types.ATXID(app.Config.Genesis.GoldenATX()) logger := app.addLogger(NodeServiceLogger, app.log).Zap() actSvc := activation.NewDBAtxService(app.db, golden, app.atxsdata, app.validator, logger) - server := nodeserver.NewServer(actSvc, app.host, app.poetDb, logger) + server := nodeserver.NewServer(actSvc, app.host, app.poetDb, app.hare3, logger) app.nodeServiceServer = &http.Server{ Handler: server.IntoHandler(http.NewServeMux()), diff --git a/timesync/clock.go b/timesync/clock.go index 36c122b3b7..8d33277fa9 100644 --- a/timesync/clock.go +++ b/timesync/clock.go @@ -184,7 +184,6 @@ func (t *NodeClock) AwaitLayer(layerID types.LayerID) <-chan struct{} { ch = make(chan struct{}) t.layerChannels[layerID] = ch } - if t.minLayer.After(layerID) { t.minLayer = layerID }