diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6aad17b2..e5207528 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: true env: - GO_VERSION: "1.25.8" + GO_VERSION: "1.25.9" CGO_ENABLED: "1" jobs: diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 9fba5794..2098efd2 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -13,7 +13,7 @@ concurrency: cancel-in-progress: false env: - GO_VERSION: "1.25.8" + GO_VERSION: "1.25.9" jobs: release: diff --git a/Dockerfile b/Dockerfile index 217b287d..3a8d2ce9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25.8-alpine@sha256:8e02eb337d9e0ea459e041f1ee5eece41cbb61f1d83e7d883a3e2fb4862063fa AS build +FROM golang:1.25.9-alpine@sha256:7a00384194cf2cb68924bbb918d675f1517357433c8541bac0ab2f929b9d5447 AS build ARG VERSION=dev diff --git a/README.md b/README.md index 175ecdf3..a0337378 100644 --- a/README.md +++ b/README.md @@ -200,11 +200,11 @@ Due to Windows operating system's [limitation](https://learn.microsoft.com/en-us #### Build from source -To build from source you'll need to have Go version 1.25 installed on your system +To build from source you'll need to have Go version 1.25.9 installed on your system ##### Build -A prerequisite for this is to have `go` version 1.25 installed on the system, and an optional requirement is to have the `make` tool installed as well (alternatively you could run the corresponding command defined in the `Makefile`). +A prerequisite for this is to have `go` version 1.25.9 installed on the system, and an optional requirement is to have the `make` tool installed as well (alternatively you could run the corresponding command defined in the `Makefile`). ```sh make install @@ -433,11 +433,11 @@ You can, of course, change the configuration above to one that suits you better, #### Build from source -To build from source you'll need to have Go version 1.25 installed on your system +To build from source you'll need to have Go version 1.25.9 installed on your system ##### Build -A prerequisite for this is to have `go` version 1.25 installed on the system, and an optional requirement is to have the `make` tool installed as well (alternatively you could run the corresponding command defined in the `Makefile`). +A prerequisite for this is to have `go` version 1.25.9 installed on the system, and an optional requirement is to have the `make` tool installed as well (alternatively you could run the corresponding command defined in the `Makefile`). ```sh make install diff --git a/go.mod b/go.mod index d85f148a..fc0f3faa 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ssvlabs/ssv-dkg -go 1.25.8 +go 1.25.9 require ( github.com/aquasecurity/table v1.11.0 diff --git a/pkgs/operator/instance.go b/pkgs/operator/instance.go index c1a88fd3..365d04df 100644 --- a/pkgs/operator/instance.go +++ b/pkgs/operator/instance.go @@ -1,6 +1,7 @@ package operator import ( + "context" "crypto/rsa" "fmt" @@ -72,5 +73,15 @@ func (iw *instWrapper) ProcessMessages(msg *wire.MultipleSignedTransports) ([]by return nil, fmt.Errorf("process message: failed to process dkg message: %w", err) } } - return <-iw.respChan, nil + ctx, cancel := context.WithTimeout(context.Background(), MaxInstancePhaseTimeout) + defer cancel() + select { + case resp, ok := <-iw.respChan: + if !ok { + return nil, fmt.Errorf("process message: response channel closed") + } + return resp, nil + case <-ctx.Done(): + return nil, fmt.Errorf("process message: timed out waiting for response: %w", ctx.Err()) + } } diff --git a/pkgs/operator/instances_manager.go b/pkgs/operator/instances_manager.go index b9acb274..c1dc2727 100644 --- a/pkgs/operator/instances_manager.go +++ b/pkgs/operator/instances_manager.go @@ -2,6 +2,7 @@ package operator import ( "bytes" + "context" "crypto/rsa" "encoding/hex" "fmt" @@ -76,8 +77,17 @@ func (s *Switch) CreateInstance(reqID [24]byte, operators []*spec.Operator, mess if err := owner.Broadcast(resp); err != nil { return nil, nil, err } - res := <-bchan - return &instWrapper{owner, initiatorPublicKey, bchan}, res, nil + ctx, cancel := context.WithTimeout(context.Background(), MaxInstancePhaseTimeout) + defer cancel() + select { + case res, ok := <-bchan: + if !ok { + return nil, nil, fmt.Errorf("create instance: response channel closed") + } + return &instWrapper{owner, initiatorPublicKey, bchan}, res, nil + case <-ctx.Done(): + return nil, nil, fmt.Errorf("create instance: timed out waiting for initial response: %w", ctx.Err()) + } } // InitInstance creates a LocalOwner instance and DKG public key message (Exchange) diff --git a/pkgs/operator/state.go b/pkgs/operator/state.go index 7f40474e..fb0b7cb2 100644 --- a/pkgs/operator/state.go +++ b/pkgs/operator/state.go @@ -6,6 +6,7 @@ import ( "crypto/rsa" "encoding/hex" "encoding/json" + "errors" "fmt" "sync" "time" @@ -27,6 +28,7 @@ import ( const MaxInstances = 1024 const MaxInstanceTime = 1 * time.Minute +const MaxInstancePhaseTimeout = MaxInstanceTime // InstanceID each new DKG ceremony has a unique random ID that we can identify messages and be able to process them in parallel type InstanceID [24]byte @@ -131,7 +133,17 @@ func (s *Switch) ProcessMessage(dkgMsg []byte) ([]byte, error) { if !ok { return nil, utils.ErrMissingInstance } - return inst.ProcessMessages(st) + resp, err := inst.ProcessMessages(st) + if errors.Is(err, context.DeadlineExceeded) { + s.Mtx.Lock() + current, ok := s.Instances[id] + if ok && current == inst { + delete(s.Instances, id) + delete(s.InstanceInitTime, id) + } + s.Mtx.Unlock() + } + return resp, err } func (s *Switch) MarshallAndSign(msg wire.SSZMarshaller, msgType wire.TransportType, operatorID uint64, id [24]byte) ([]byte, error) { diff --git a/pkgs/operator/state_timeout_test.go b/pkgs/operator/state_timeout_test.go new file mode 100644 index 00000000..ca1728bb --- /dev/null +++ b/pkgs/operator/state_timeout_test.go @@ -0,0 +1,42 @@ +package operator + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ssvlabs/ssv-dkg/pkgs/wire" +) + +type timeoutInstance struct{} + +func (timeoutInstance) ProcessMessages(*wire.MultipleSignedTransports) ([]byte, error) { + return nil, fmt.Errorf("timed out: %w", context.DeadlineExceeded) +} + +func (timeoutInstance) VerifyInitiatorMessage([]byte, []byte) error { return nil } + +func TestProcessMessage_DeletesInstanceOnTimeout(t *testing.T) { + var instanceID InstanceID + copy(instanceID[:], "test-instance-id-0123456") + + s := &Switch{ + Instances: map[InstanceID]Instance{instanceID: timeoutInstance{}}, + InstanceInitTime: map[InstanceID]time.Time{instanceID: time.Now()}, + } + + st := &wire.MultipleSignedTransports{Identifier: [24]byte(instanceID)} + raw, err := st.MarshalSSZ() + require.NoError(t, err) + + _, err = s.ProcessMessage(raw) + require.Error(t, err) + + s.Mtx.RLock() + _, ok := s.Instances[instanceID] + s.Mtx.RUnlock() + require.False(t, ok, "expected instance to be removed on timeout") +}