Skip to content

Commit

Permalink
Merge pull request #858 from ipfs-force-community/feat/0x5459/display…
Browse files Browse the repository at this point in the history
…-partition-indexes

Feat/0x5459/display partition indexes
  • Loading branch information
0x5459 authored Aug 1, 2023
2 parents 3cab73a + 4a4b45f commit c307ffb
Show file tree
Hide file tree
Showing 18 changed files with 126 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -751,7 +751,7 @@ var utilSealerProvingSimulateWdPoStCmd = &cli.Command{
return fmt.Errorf("convert to winning post proof: %w", err)
}

err = api.Damocles.SimulateWdPoSt(ctx, ddlIdx, maddr, ppt, proofSectors, rand)
err = api.Damocles.SimulateWdPoSt(ctx, ddlIdx, pidx, maddr, ppt, proofSectors, rand)
if err != nil {
return err
}
Expand Down
18 changes: 13 additions & 5 deletions damocles-manager/cmd/damocles-manager/internal/util_worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/urfave/cli/v2"

"github.com/ipfs-force-community/damocles/damocles-manager/core"
"github.com/ipfs-force-community/damocles/damocles-manager/pkg/strings"
"github.com/ipfs-force-community/damocles/damocles-manager/pkg/workercli"
)

Expand Down Expand Up @@ -334,7 +335,7 @@ var utilWdPostListCmd = &cli.Command{
}
defer stopper()

var jobs []*core.WdPoStJob
var jobs []core.WdPoStJobBrief
jobs, err = a.Damocles.WdPoStAllJobs(actx)
if err != nil {
return fmt.Errorf("get wdpost jobs: %w", err)
Expand All @@ -344,9 +345,9 @@ var utilWdPostListCmd = &cli.Command{

w := tabwriter.NewWriter(os.Stdout, 2, 4, 2, ' ', 0)
if detail {
_, err = w.Write([]byte("JobID\tPrefix\tMiner\tDDL\tWorker\tState\tTry\tCreateAt\tStartedAt\tHeartbeatAt\tFinishedAt\tUpdatedAt\tError\n"))
_, err = w.Write([]byte("JobID\tPrefix\tMiner\tDDL\tPartitions\tWorker\tState\tTry\tCreateAt\tStartedAt\tHeartbeatAt\tFinishedAt\tUpdatedAt\tError\n"))
} else {
_, err = w.Write([]byte("JobID\tMinerID\tDDL\tWorker\tState\tTry\tCreateAt\tElapsed\tError\n"))
_, err = w.Write([]byte("JobID\tMinerID\tDDL\tPartitions\tWorker\tState\tTry\tCreateAt\tElapsed\tHeartbeat\tError\n"))
}
if err != nil {
return err
Expand All @@ -362,11 +363,12 @@ var utilWdPostListCmd = &cli.Command{
continue
}
if detail {
fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\t%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\n",
fmt.Fprintf(w, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\n",
job.ID,
job.State,
job.Input.MinerID,
job.DeadlineIdx,
strings.Join(job.Partitions, ","),
job.WorkerName,
job.DisplayState(),
job.TryNum,
Expand All @@ -379,24 +381,30 @@ var utilWdPostListCmd = &cli.Command{
)
} else {
var elapsed string
var heartbeat string

if job.StartedAt == 0 {
elapsed = "-"
heartbeat = "-"
} else if job.FinishedAt == 0 {
elapsed = time.Since(time.Unix(int64(job.StartedAt), 0)).Truncate(time.Second).String()
heartbeat = time.Since(time.Unix(int64(job.HeartbeatAt), 0)).Truncate(time.Millisecond).String()
} else {
elapsed = fmt.Sprintf("%s(done)", time.Duration(job.FinishedAt-job.StartedAt)*time.Second)
heartbeat = "-"
}

fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s\t%d\t%s\t%s\t%s\n",
fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n",
job.ID,
job.Input.MinerID,
job.DeadlineIdx,
strings.Join(job.Partitions, ","),
job.WorkerName,
job.DisplayState(),
job.TryNum,
formatDateTime(job.CreatedAt),
elapsed,
heartbeat,
job.ErrorReason,
)
}
Expand Down
2 changes: 1 addition & 1 deletion damocles-manager/core/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ type SealerCliAPI interface {

CheckProvable(ctx context.Context, mid abi.ActorID, postProofType abi.RegisteredPoStProof, sectors []builtin.ExtendedSectorInfo, strict, stateCheck bool) (map[abi.SectorNumber]string, error)

SimulateWdPoSt(context.Context, uint64, address.Address, abi.RegisteredPoStProof, []builtin.ExtendedSectorInfo, abi.PoStRandomness) error
SimulateWdPoSt(ctx context.Context, ddlIndex, partitionIndex uint64, maddr address.Address, postProofType abi.RegisteredPoStProof, sis []builtin.ExtendedSectorInfo, rand abi.PoStRandomness) error

SnapUpPreFetch(ctx context.Context, mid abi.ActorID, dlindex *uint64) (*SnapUpFetchResult, error)

Expand Down
8 changes: 4 additions & 4 deletions damocles-manager/core/client_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ type SealerCliAPIClient struct {
ImportSector func(ctx context.Context, ws SectorWorkerState, state *SectorState, override bool) (bool, error)
RestoreSector func(ctx context.Context, sid abi.SectorID, forced bool) (Meta, error)
CheckProvable func(ctx context.Context, mid abi.ActorID, postProofType abi.RegisteredPoStProof, sectors []builtin.ExtendedSectorInfo, strict, stateCheck bool) (map[abi.SectorNumber]string, error)
SimulateWdPoSt func(context.Context, uint64, address.Address, abi.RegisteredPoStProof, []builtin.ExtendedSectorInfo, abi.PoStRandomness) error
SimulateWdPoSt func(ctx context.Context, ddlIndex, partitionIndex uint64, maddr address.Address, postProofType abi.RegisteredPoStProof, sis []builtin.ExtendedSectorInfo, rand abi.PoStRandomness) error
SnapUpPreFetch func(ctx context.Context, mid abi.ActorID, dlindex *uint64) (*SnapUpFetchResult, error)
SnapUpCandidates func(ctx context.Context, mid abi.ActorID) ([]*bitfield.BitField, error)
SnapUpCancelCommitment func(ctx context.Context, sid abi.SectorID) error
Expand Down Expand Up @@ -166,7 +166,7 @@ var UnavailableSealerCliAPIClient = SealerCliAPIClient{
CheckProvable: func(ctx context.Context, mid abi.ActorID, postProofType abi.RegisteredPoStProof, sectors []builtin.ExtendedSectorInfo, strict, stateCheck bool) (map[abi.SectorNumber]string, error) {
panic("SealerCliAPI client unavailable")
},
SimulateWdPoSt: func(context.Context, uint64, address.Address, abi.RegisteredPoStProof, []builtin.ExtendedSectorInfo, abi.PoStRandomness) error {
SimulateWdPoSt: func(ctx context.Context, ddlIndex, partitionIndex uint64, maddr address.Address, postProofType abi.RegisteredPoStProof, sis []builtin.ExtendedSectorInfo, rand abi.PoStRandomness) error {
panic("SealerCliAPI client unavailable")
},
SnapUpPreFetch: func(ctx context.Context, mid abi.ActorID, dlindex *uint64) (*SnapUpFetchResult, error) {
Expand Down Expand Up @@ -269,7 +269,7 @@ type WorkerWdPoStAPIClient struct {
WdPoStFinishJob func(ctx context.Context, jobID string, output *stage.WindowPoStOutput, errorReason string) (Meta, error)
WdPoStResetJob func(ctx context.Context, jobID string) (Meta, error)
WdPoStRemoveJob func(ctx context.Context, jobID string) (Meta, error)
WdPoStAllJobs func(ctx context.Context) ([]*WdPoStJob, error)
WdPoStAllJobs func(ctx context.Context) ([]WdPoStJobBrief, error)
}

var UnavailableWorkerWdPoStAPIClient = WorkerWdPoStAPIClient{
Expand All @@ -289,7 +289,7 @@ var UnavailableWorkerWdPoStAPIClient = WorkerWdPoStAPIClient{
WdPoStRemoveJob: func(ctx context.Context, jobID string) (Meta, error) {
panic("WorkerWdPoStAPI client unavailable")
},
WdPoStAllJobs: func(ctx context.Context) ([]*WdPoStJob, error) {
WdPoStAllJobs: func(ctx context.Context) ([]WdPoStJobBrief, error) {
panic("WorkerWdPoStAPI client unavailable")
},
}
12 changes: 10 additions & 2 deletions damocles-manager/core/prover.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/proof"
"github.com/filecoin-project/specs-storage/storage"

"github.com/filecoin-project/venus/venus-shared/actors/builtin"
"github.com/filecoin-project/venus/venus-shared/actors/builtin/miner"
"github.com/ipfs-force-community/damocles/damocles-manager/pkg/extproc/stage"
Expand Down Expand Up @@ -55,9 +54,18 @@ type Verifier interface {
VerifyWinningPoSt(ctx context.Context, info WinningPoStVerifyInfo) (bool, error)
}

type GenerateWindowPoStParams struct {
DeadlineIdx uint64
MinerID abi.ActorID
ProofType abi.RegisteredPoStProof
Partitions []uint64
Sectors []builtin.ExtendedSectorInfo
Randomness abi.PoStRandomness
}

type Prover interface {
AggregateSealProofs(ctx context.Context, aggregateInfo AggregateSealVerifyProofAndInfos, proofs [][]byte) ([]byte, error)
GenerateWindowPoSt(ctx context.Context, deadlineIdx uint64, minerID abi.ActorID, proofType abi.RegisteredPoStProof, sectors []builtin.ExtendedSectorInfo, randomness abi.PoStRandomness) (proof []builtin.PoStProof, skipped []abi.SectorID, err error)
GenerateWindowPoSt(ctx context.Context, params GenerateWindowPoStParams) (proof []builtin.PoStProof, skipped []abi.SectorID, err error)
GenerateWinningPoSt(ctx context.Context, minerID abi.ActorID, ppt abi.RegisteredPoStProof, sectors []builtin.ExtendedSectorInfo, randomness abi.PoStRandomness) ([]builtin.PoStProof, error)

GeneratePoStFallbackSectorChallenges(ctx context.Context, proofType abi.RegisteredPoStProof, minerID abi.ActorID, randomness abi.PoStRandomness, sectorIds []abi.SectorNumber) (*FallbackChallenges, error)
Expand Down
3 changes: 2 additions & 1 deletion damocles-manager/core/types_wdpost.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type WdPoStJob struct {
ID string `json:"Id"`
State string
DeadlineIdx uint64
Partitions []uint64
Input WdPoStInput
Output *stage.WindowPoStOutput
TryNum uint32
Expand Down Expand Up @@ -106,7 +107,7 @@ type AllocateWdPoStJobSpec struct {
type WorkerWdPoStJobManager interface {
All(ctx context.Context, filter func(*WdPoStJob) bool) ([]*WdPoStJob, error)
ListByJobIDs(ctx context.Context, jobIDs ...string) ([]*WdPoStJob, error)
Create(ctx context.Context, deadlineIdx uint64, input WdPoStInput) (*WdPoStJob, error)
Create(ctx context.Context, deadlineIdx uint64, partitions []uint64, input WdPoStInput) (*WdPoStJob, error)
AllocateJobs(ctx context.Context, spec AllocateWdPoStJobSpec, num uint32, workerName string) (allocatedJobs []*WdPoStAllocatedJob, err error)
Heartbeat(ctx context.Context, jobIDs []string, workerName string) error
Finish(ctx context.Context, jobID string, output *stage.WindowPoStOutput, errorReason string) error
Expand Down
5 changes: 3 additions & 2 deletions damocles-manager/modules/impl/prover/ext/prover.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,12 @@ func (p *Prover) AggregateSealProofs(ctx context.Context, aggregateInfo core.Agg
return p.localProver.AggregateSealProofs(ctx, aggregateInfo, proofs)
}

func (p *Prover) GenerateWindowPoSt(ctx context.Context, deadlineIdx uint64, minerID abi.ActorID, proofType abi.RegisteredPoStProof, sectors []builtin.ExtendedSectorInfo, randomness abi.PoStRandomness) ([]builtin.PoStProof, []abi.SectorID, error) {
func (p *Prover) GenerateWindowPoSt(ctx context.Context, params core.GenerateWindowPoStParams) ([]builtin.PoStProof, []abi.SectorID, error) {

if p.windowProc == nil {
return p.localProver.GenerateWindowPoSt(ctx, deadlineIdx, minerID, proofType, sectors, randomness)
return p.localProver.GenerateWindowPoSt(ctx, params)
}
minerID, proofType, sectors, randomness := params.MinerID, params.ProofType, params.Sectors, params.Randomness

if len(sectors) == 0 {
return nil, nil, nil
Expand Down
2 changes: 1 addition & 1 deletion damocles-manager/modules/impl/prover/prover_fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (fakeProver) AggregateSealProofs(ctx context.Context, aggregateInfo core.Ag
return make([]byte, 32), nil
}

func (fakeProver) GenerateWindowPoSt(ctx context.Context, deadlineIdx uint64, minerID abi.ActorID, proofType abi.RegisteredPoStProof, sectors []builtin.ExtendedSectorInfo, randomness abi.PoStRandomness) (proof []builtin.PoStProof, skipped []abi.SectorID, err error) {
func (fakeProver) GenerateWindowPoSt(ctx context.Context, params core.GenerateWindowPoStParams) (proof []builtin.PoStProof, skipped []abi.SectorID, err error) {
return nil, nil, nil
}

Expand Down
5 changes: 3 additions & 2 deletions damocles-manager/modules/impl/prover/prover_prod.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ func (prodProver) AggregateSealProofs(ctx context.Context, aggregateInfo core.Ag
return ffi.AggregateSealProofs(aggregateInfo, proofs)
}

func (p prodProver) GenerateWindowPoSt(ctx context.Context, deadlineIdx uint64, minerID abi.ActorID, ppt abi.RegisteredPoStProof, sectors []builtin.ExtendedSectorInfo, randomness abi.PoStRandomness) (proof []builtin.PoStProof, skipped []abi.SectorID, err error) {
func (p prodProver) GenerateWindowPoSt(ctx context.Context, params core.GenerateWindowPoStParams) (proof []builtin.PoStProof, skipped []abi.SectorID, err error) {
minerID, proofType, sectors, randomness := params.MinerID, params.ProofType, params.Sectors, params.Randomness
randomness[31] &= 0x3f

privSectors, err := p.sectorTracker.PubToPrivate(ctx, minerID, ppt, sectors)
privSectors, err := p.sectorTracker.PubToPrivate(ctx, minerID, proofType, sectors)
if err != nil {
return nil, nil, fmt.Errorf("turn public sector infos into private: %w", err)
}
Expand Down
3 changes: 2 additions & 1 deletion damocles-manager/modules/impl/prover/worker/job_mgr_kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (tm *kvJobManager) ListByJobIDs(ctx context.Context, jobIDs ...string) ([]*
return jobs, err
}

func (tm *kvJobManager) Create(ctx context.Context, deadlineIdx uint64, input core.WdPoStInput) (*core.WdPoStJob, error) {
func (tm *kvJobManager) Create(ctx context.Context, deadlineIdx uint64, partitions []uint64, input core.WdPoStInput) (*core.WdPoStJob, error) {
var (
jobID string
job core.WdPoStJob
Expand Down Expand Up @@ -120,6 +120,7 @@ func (tm *kvJobManager) Create(ctx context.Context, deadlineIdx uint64, input co
ID: jobID,
State: string(core.WdPoStJobReadyToRun),
DeadlineIdx: deadlineIdx,
Partitions: partitions,
Input: input,
Output: nil,
TryNum: 0,
Expand Down
10 changes: 6 additions & 4 deletions damocles-manager/modules/impl/prover/worker/prover.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,9 @@ func (p *Prover) AggregateSealProofs(ctx context.Context, aggregateInfo core.Agg
return p.localProver.AggregateSealProofs(ctx, aggregateInfo, proofs)
}

func (p *Prover) GenerateWindowPoSt(ctx context.Context, deadlineIdx uint64, minerID abi.ActorID, proofType abi.RegisteredPoStProof, sectors []builtin.ExtendedSectorInfo, randomness abi.PoStRandomness) (proof []builtin.PoStProof, skipped []abi.SectorID, err error) {
func (p *Prover) GenerateWindowPoSt(ctx context.Context, params core.GenerateWindowPoStParams) (proof []builtin.PoStProof, skipped []abi.SectorID, err error) {
deadlineIdx, partitions, minerID, proofType, sectors, randomness := params.DeadlineIdx, params.Partitions, params.MinerID, params.ProofType, params.Sectors, params.Randomness

randomness[31] &= 0x3f

sis := make([]core.WdPoStSectorInfo, len(sectors))
Expand Down Expand Up @@ -229,7 +231,7 @@ func (p *Prover) GenerateWindowPoSt(ctx context.Context, deadlineIdx uint64, min

var output *stage.WindowPoStOutput
for {
output, err = p.doWindowPoSt(ctx, deadlineIdx, input)
output, err = p.doWindowPoSt(ctx, deadlineIdx, partitions, input)
if !errors.Is(err, ErrJobRemovedManually) {
break
}
Expand Down Expand Up @@ -261,8 +263,8 @@ func (p *Prover) GenerateWindowPoSt(ctx context.Context, deadlineIdx uint64, min
return proofs, nil, nil
}

func (p *Prover) doWindowPoSt(ctx context.Context, deadlineIdx uint64, input core.WdPoStInput) (output *stage.WindowPoStOutput, err error) {
job, err := p.jobMgr.Create(ctx, deadlineIdx, input)
func (p *Prover) doWindowPoSt(ctx context.Context, deadlineIdx uint64, partitions []uint64, input core.WdPoStInput) (output *stage.WindowPoStOutput, err error) {
job, err := p.jobMgr.Create(ctx, deadlineIdx, partitions, input)
if err != nil {
return nil, fmt.Errorf("create wdPoSt job: %w", err)
}
Expand Down
9 changes: 4 additions & 5 deletions damocles-manager/modules/impl/prover/worker/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/ipfs-force-community/damocles/damocles-manager/core"
"github.com/ipfs-force-community/damocles/damocles-manager/pkg/extproc/stage"
"github.com/ipfs-force-community/damocles/damocles-manager/pkg/kvstore"
"github.com/ipfs-force-community/damocles/damocles-manager/pkg/slices"
)

func NewWdPoStAPIImpl(jobMgr core.WorkerWdPoStJobManager) core.WorkerWdPoStAPI {
Expand Down Expand Up @@ -53,11 +54,9 @@ func (api WdPoStAPIImpl) WdPoStAllJobs(ctx context.Context) ([]core.WdPoStJobBri
if err != nil {
return nil, err
}
ret := make([]core.WdPoStJobBrief, len(jobs))
for i, job := range jobs {
ret[i] = core.WdPoStJobBrief{
return slices.Map(jobs, func(job *core.WdPoStJob) core.WdPoStJobBrief {
return core.WdPoStJobBrief{
WdPoStJob: job,
}
}
return ret, nil
}), nil
}
13 changes: 12 additions & 1 deletion damocles-manager/modules/poster/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"github.com/ipfs-force-community/damocles/damocles-manager/modules/util"
"github.com/ipfs-force-community/damocles/damocles-manager/pkg/chain"
"github.com/ipfs-force-community/damocles/damocles-manager/pkg/logging"
"github.com/ipfs-force-community/damocles/damocles-manager/pkg/slices"
)

type runnerConstructor func(ctx context.Context, deps postDeps, mid abi.ActorID, maddr address.Address, proofType abi.RegisteredPoStProof, dinfo *dline.Info) PoStRunner
Expand Down Expand Up @@ -324,7 +325,17 @@ func (pr *postRunner) generatePoStForPartitionBatch(glog *logging.ZapLogger, ran
return false, fmt.Errorf("convert to v1_1 post proof: %w", err)
}

postOut, ps, err := pr.deps.prover.GenerateWindowPoSt(pr.ctx, pr.dinfo.Index, pr.mid, pp, xsinfos, append(abi.PoStRandomness{}, rand.Rand...))
proverParams := core.GenerateWindowPoStParams{
DeadlineIdx: pr.dinfo.Index,
MinerID: pr.mid,
ProofType: pp,
Partitions: slices.Map(partitions, func(p miner.PoStPartition) uint64 {
return p.Index
}),
Sectors: xsinfos,
Randomness: append(abi.PoStRandomness{}, rand.Rand...),
}
postOut, ps, err := pr.deps.prover.GenerateWindowPoSt(pr.ctx, proverParams)

alog.Infow("computing window post", "elapsed", time.Since(tsStart))

Expand Down
12 changes: 10 additions & 2 deletions damocles-manager/modules/sealer/sealer_cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (s *Sealer) CheckProvable(ctx context.Context, mid abi.ActorID, postProofTy
return s.sectorProving.Provable(ctx, mid, postProofType, sectors, strict, stateCheck)
}

func (s *Sealer) SimulateWdPoSt(ctx context.Context, ddlIndex uint64, maddr address.Address, postProofType abi.RegisteredPoStProof, sis []builtin.ExtendedSectorInfo, rand abi.PoStRandomness) error {
func (s *Sealer) SimulateWdPoSt(ctx context.Context, ddlIndex, partitionIndex uint64, maddr address.Address, postProofType abi.RegisteredPoStProof, sis []builtin.ExtendedSectorInfo, rand abi.PoStRandomness) error {
mid, err := address.IDFromAddress(maddr)
if err != nil {
return err
Expand All @@ -74,7 +74,15 @@ func (s *Sealer) SimulateWdPoSt(ctx context.Context, ddlIndex uint64, maddr addr
tsStart := clock.NewSystemClock().Now()

slog.Info("mock generate window post start")
proof, skipped, err := s.prover.GenerateWindowPoSt(tCtx, ddlIndex, abi.ActorID(mid), postProofType, sis, append(abi.PoStRandomness{}, rand...))
params := core.GenerateWindowPoStParams{
DeadlineIdx: ddlIndex,
MinerID: abi.ActorID(mid),
ProofType: postProofType,
Partitions: []uint64{partitionIndex},
Sectors: sis,
Randomness: append(abi.PoStRandomness{}, rand...),
}
proof, skipped, err := s.prover.GenerateWindowPoSt(tCtx, params)
if err != nil {
slog.Warnf("generate window post failed: %v", err.Error())
return
Expand Down
12 changes: 12 additions & 0 deletions damocles-manager/pkg/slices/map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package slices

func Map[T, U any](data []T, f func(T) U) []U {

res := make([]U, len(data))

for i, e := range data {
res[i] = f(e)
}

return res
}
23 changes: 23 additions & 0 deletions damocles-manager/pkg/strings/join.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package strings

import (
"fmt"
"strings"
)

func Join[T any](data []T, sep string) string {
if len(data) < 1 {
return ""
}

var sb strings.Builder

sb.WriteString(fmt.Sprint(data[0]))

for _, item := range data[1:] {
sb.WriteString(sep)
sb.WriteString(fmt.Sprint(item))
}

return sb.String()
}
13 changes: 13 additions & 0 deletions damocles-manager/pkg/strings/join_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package strings_test

import (
"testing"

"github.com/ipfs-force-community/damocles/damocles-manager/pkg/strings"
"github.com/stretchr/testify/require"
)

func TestJoin(t *testing.T) {
require.Equal(t, "", strings.Join([]uint64{}, ","))
require.Equal(t, "1,2,3", strings.Join([]uint64{1, 2, 3}, ","))
}
Loading

0 comments on commit c307ffb

Please sign in to comment.