diff --git a/go.mod b/go.mod index 0fb5e807eb..adccdf25bc 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/gin-gonic/gin v1.7.0 github.com/go-resty/resty/v2 v2.6.0 github.com/gorilla/websocket v1.5.0 - github.com/iotaledger/hive.go v0.0.0-20220607150119-1be29e962175 + github.com/iotaledger/hive.go v0.0.0-20220620133504-13cc326a3d17 github.com/labstack/echo v3.3.10+incompatible github.com/labstack/gommon v0.3.0 github.com/libp2p/go-libp2p v0.15.0 diff --git a/go.sum b/go.sum index f3a49db149..6925999cb8 100644 --- a/go.sum +++ b/go.sum @@ -473,8 +473,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/iotaledger/hive.go v0.0.0-20220607150119-1be29e962175 h1:IgXxiPx51WJglOL5EtIurlMbujnrLP4vLYQqyfmR0zg= -github.com/iotaledger/hive.go v0.0.0-20220607150119-1be29e962175/go.mod h1:8f9U7qHFby0W3cxv/nKnz9LHn9BbwWU0tMsWDnfqzRI= +github.com/iotaledger/hive.go v0.0.0-20220620133504-13cc326a3d17 h1:VH6ZeNKdnuQ/TrRgZRscyL2jXQeI/pN4RfhNMUXHFL0= +github.com/iotaledger/hive.go v0.0.0-20220620133504-13cc326a3d17/go.mod h1:8f9U7qHFby0W3cxv/nKnz9LHn9BbwWU0tMsWDnfqzRI= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= diff --git a/packages/manaverse/bucket.go b/packages/manaverse/bucket.go new file mode 100644 index 0000000000..489277eec6 --- /dev/null +++ b/packages/manaverse/bucket.go @@ -0,0 +1,31 @@ +package manaverse + +import ( + "github.com/iotaledger/hive.go/generics/priorityqueue" + + "github.com/iotaledger/goshimmer/packages/tangle" +) + +type Bucket struct { + mana int64 + + *priorityqueue.PriorityQueue[*tangle.Message] +} + +func newManaBucket(mana int64) *Bucket { + return &Bucket{ + mana: mana, + PriorityQueue: priorityqueue.New[*tangle.Message](), + } +} + +func (b *Bucket) Compare(other *Bucket) int { + switch true { + case b.mana < other.mana: + return -1 + case b.mana > other.mana: + return 1 + default: + return 0 + } +} diff --git a/packages/manaverse/events.go b/packages/manaverse/events.go new file mode 100644 index 0000000000..0925e5ffee --- /dev/null +++ b/packages/manaverse/events.go @@ -0,0 +1,65 @@ +package manaverse + +import ( + "time" + + "github.com/iotaledger/hive.go/generics/event" + + "github.com/iotaledger/goshimmer/packages/tangle" +) + +// region SchedulerEvents ////////////////////////////////////////////////////////////////////////////////////////////// + +type SchedulerEvents struct { + BlockQueued *event.Event[*SchedulerBlockEvent] + BlockScheduled *event.Event[*SchedulerBlockEvent] + BlockDropped *event.Event[*SchedulerBlockEvent] + BucketProcessingStarted *event.Event[*SchedulerBucketEvent] + BucketProcessingFinished *event.Event[*SchedulerBucketEvent] +} + +func newSchedulerEvents() (newInstance *SchedulerEvents) { + return &SchedulerEvents{ + BlockQueued: event.New[*SchedulerBlockEvent](), + BlockScheduled: event.New[*SchedulerBlockEvent](), + BlockDropped: event.New[*SchedulerBlockEvent](), + BucketProcessingStarted: event.New[*SchedulerBucketEvent](), + BucketProcessingFinished: event.New[*SchedulerBucketEvent](), + } +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region SchedulerBlockEvent ////////////////////////////////////////////////////////////////////////////////////////// + +type SchedulerBlockEvent struct { + Block *tangle.Message + Bucket int64 + Time time.Time +} + +func newSchedulerBlockEvent(block *tangle.Message, bucket int64, time time.Time) (newInstance *SchedulerBlockEvent) { + return &SchedulerBlockEvent{ + Block: block, + Bucket: bucket, + Time: time, + } +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region SchedulerBucketEvent ///////////////////////////////////////////////////////////////////////////////////////// + +type SchedulerBucketEvent struct { + Bucket int64 + Time time.Time +} + +func newSchedulerBucketEvent(bucket int64, time time.Time) (newInstance *SchedulerBucketEvent) { + return &SchedulerBucketEvent{ + Bucket: bucket, + Time: time, + } +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/manaverse/interfaces.go b/packages/manaverse/interfaces.go new file mode 100644 index 0000000000..d9ffb19824 --- /dev/null +++ b/packages/manaverse/interfaces.go @@ -0,0 +1,10 @@ +package manaverse + +import ( + "github.com/iotaledger/hive.go/identity" +) + +type ManaLedger interface { + IncreaseMana(id identity.ID, mana int64) (newBalance int64) + DecreaseMana(id identity.ID, mana int64) (newBalance int64) +} diff --git a/packages/manaverse/manaverse_test.go b/packages/manaverse/manaverse_test.go new file mode 100644 index 0000000000..b092e233a5 --- /dev/null +++ b/packages/manaverse/manaverse_test.go @@ -0,0 +1,46 @@ +package manaverse + +import ( + "fmt" + "testing" + "time" + + "github.com/iotaledger/hive.go/crypto/ed25519" + "github.com/iotaledger/hive.go/generics/event" + "github.com/iotaledger/hive.go/identity" + + "github.com/iotaledger/goshimmer/packages/tangle" +) + +func Test(t *testing.T) { + identity1KeyPair := ed25519.GenerateKeyPair() + identity1 := identity.New(identity1KeyPair.PublicKey) + + manaLedger := NewMockedManaLedger() + manaLedger.IncreaseMana(identity1.ID(), 100) + + testTangle := tangle.NewTestTangle() + testFramework := tangle.NewMessageTestFramework(testTangle) + testFramework.CreateMessage("A", tangle.WithStrongParents("Genesis"), tangle.WithIssuer(identity1.PublicKey())) + testFramework.CreateMessage("B", tangle.WithStrongParents("A"), tangle.WithIssuer(identity1.PublicKey())) + testFramework.CreateMessage("C", tangle.WithStrongParents("A"), tangle.WithIssuer(identity1.PublicKey())) + + scheduler := NewScheduler(testTangle.ConfirmationOracle, manaLedger) + scheduler.Events.BlockScheduled.Hook(event.NewClosure(func(event *SchedulerBlockEvent) { + fmt.Println(event.Time, "BlockScheduled", event.Block.ID(), event.Bucket) + })) + + scheduler.Events.BucketProcessingStarted.Hook(event.NewClosure(func(event *SchedulerBucketEvent) { + fmt.Println(event.Time, "BucketProcessingStarted", event.Bucket) + })) + + scheduler.Events.BucketProcessingFinished.Hook(event.NewClosure(func(event *SchedulerBucketEvent) { + fmt.Println(event.Time, "BucketProcessingFinished", event.Bucket) + })) + + scheduler.Push(testFramework.Message("A")) + scheduler.Push(testFramework.Message("B")) + scheduler.Push(testFramework.Message("C")) + + time.Sleep(2 * time.Second) +} diff --git a/packages/manaverse/scheduler.go b/packages/manaverse/scheduler.go new file mode 100644 index 0000000000..b43f10bbfb --- /dev/null +++ b/packages/manaverse/scheduler.go @@ -0,0 +1,193 @@ +package manaverse + +import ( + "sync" + "time" + + "github.com/cockroachdb/errors" + "github.com/iotaledger/hive.go/generics/event" + "github.com/iotaledger/hive.go/generics/lo" + "github.com/iotaledger/hive.go/generics/priorityqueue" + "github.com/iotaledger/hive.go/identity" + "github.com/iotaledger/hive.go/timeutil" + + "github.com/iotaledger/goshimmer/packages/tangle" +) + +type Scheduler struct { + Events *SchedulerEvents + + confirmationOracle tangle.ConfirmationOracle + manaLedger ManaLedger + priorityQueue *priorityqueue.PriorityQueue[*Bucket] + bucketsByMana map[int64]*Bucket + currentBucket int64 + ticker *timeutil.PrecisionTicker + mutex sync.Mutex + + // unqueuedBlocks + unqueuedBlocks map[tangle.MessageID]int + unscheduledBlocks map[tangle.MessageID][]*tangle.Message +} + +func NewScheduler(confirmationOracle tangle.ConfirmationOracle, manaLedger ManaLedger) (newScheduler *Scheduler) { + newScheduler = &Scheduler{ + Events: newSchedulerEvents(), + confirmationOracle: confirmationOracle, + manaLedger: manaLedger, + priorityQueue: priorityqueue.New[*Bucket](), + bucketsByMana: make(map[int64]*Bucket, 0), + currentBucket: -1, + unqueuedBlocks: make(map[tangle.MessageID]int), + unscheduledBlocks: make(map[tangle.MessageID][]*tangle.Message), + } + newScheduler.ticker = timeutil.NewPrecisionTicker(newScheduler.ScheduleBlock, 500*time.Millisecond) + + return newScheduler +} + +func (s *Scheduler) Setup() { + s.confirmationOracle.Events().MessageConfirmed.Attach(event.NewClosure(s.onMessageConfirmed)) +} + +func (s *Scheduler) onMessageConfirmed(event *tangle.MessageConfirmedEvent) { + s.mutex.Lock() + defer s.mutex.Unlock() + + s.markBlockQueued(event.Message) + s.markBlockScheduled(event.Message, time.Now()) +} + +func (s *Scheduler) Push(block *tangle.Message) { + s.mutex.Lock() + defer s.mutex.Unlock() + + if !s.hasUnscheduledParents(block) { + s.queueBlock(block, time.Now()) + } +} + +func (s *Scheduler) ScheduleBlock() { + s.mutex.Lock() + defer s.mutex.Unlock() + + s.scheduleNextBlock(time.Now()) +} + +func (s *Scheduler) queueBlock(block *tangle.Message, now time.Time) { + bucket := s.bucket(block.BurnedMana()) + bucket.Push(block) + + s.markBlockQueued(block) + + s.Events.BlockQueued.Trigger(newSchedulerBlockEvent(block, bucket.mana, now)) +} + +func (s *Scheduler) markBlockQueued(block *tangle.Message) { + delete(s.unqueuedBlocks, block.ID()) +} + +func (s *Scheduler) markBlockScheduled(block *tangle.Message, now time.Time) { + s.decreaseUnscheduledParentCountersOfChildren(block.ID(), now) + + delete(s.unscheduledBlocks, block.ID()) +} + +func (s *Scheduler) dropBucket() { + bucket, exists := s.priorityQueue.Pop() + if !exists { + panic(errors.New("bucket should never be empty")) + } + + delete(s.bucketsByMana, bucket.mana) + s.currentBucket = -1 +} + +func (s *Scheduler) scheduleNextBlock(now time.Time) { + blockToSchedule, bucket := s.nextBlock(now) + for ; blockToSchedule != nil && !s.issuerHasEnoughMana(blockToSchedule); blockToSchedule, bucket = s.nextBlock(now) { + s.Events.BlockDropped.Trigger(newSchedulerBlockEvent(blockToSchedule, bucket, now)) + + } + + if blockToSchedule == nil { + return + } + + s.markBlockScheduled(blockToSchedule, now) + + s.Events.BlockScheduled.Trigger(newSchedulerBlockEvent(blockToSchedule, bucket, now)) +} + +func (s *Scheduler) nextBlock(now time.Time) (block *tangle.Message, bucket int64) { + firstBucket, success := s.priorityQueue.Peek() + if !success { + return nil, 0 + } + + if s.currentBucket != firstBucket.mana { + s.currentBucket = firstBucket.mana + s.Events.BucketProcessingStarted.Trigger(newSchedulerBucketEvent(firstBucket.mana, now)) + } + + if block, success = firstBucket.Pop(); !success { + panic(errors.Errorf("bucket %v should never be empty", firstBucket)) + } + + if firstBucket.IsEmpty() { + s.Events.BucketProcessingFinished.Trigger(newSchedulerBucketEvent(firstBucket.mana, now)) + + s.dropBucket() + } + + return block, firstBucket.mana +} + +func (s *Scheduler) issuerHasEnoughMana(block *tangle.Message) (canSchedule bool) { + return s.manaLedger.DecreaseMana(identity.NewID(block.IssuerPublicKey()), block.BurnedMana()) >= 0 +} + +func (s *Scheduler) hasUnscheduledParents(block *tangle.Message) (hasUnscheduledParents bool) { + s.unscheduledBlocks[block.ID()] = make([]*tangle.Message, 0) + + if unscheduledParents := s.unscheduledParents(block); unscheduledParents > 0 { + s.unqueuedBlocks[block.ID()] = unscheduledParents + + return true + } + + return false +} + +func (s *Scheduler) unscheduledParents(block *tangle.Message) (unscheduledParents int) { + for it := lo.Unique(block.Parents()).Iterator(); it.HasNext(); { + parentID := it.Next() + + if children, isUnscheduled := s.unscheduledBlocks[parentID]; isUnscheduled { + s.unscheduledBlocks[parentID] = append(children, block) + + unscheduledParents++ + } + } + + return unscheduledParents +} + +func (s *Scheduler) bucket(mana int64) (bucket *Bucket) { + bucket, exists := s.bucketsByMana[mana] + if !exists { + bucket = newManaBucket(mana) + s.bucketsByMana[mana] = bucket + s.priorityQueue.Push(bucket) + } + + return bucket +} + +func (s *Scheduler) decreaseUnscheduledParentCountersOfChildren(blockID tangle.MessageID, now time.Time) { + for _, child := range s.unscheduledBlocks[blockID] { + if s.unqueuedBlocks[child.ID()]--; s.unqueuedBlocks[child.ID()] == 0 { + s.queueBlock(child, now) + } + } +} diff --git a/packages/manaverse/testframework.go b/packages/manaverse/testframework.go new file mode 100644 index 0000000000..7cc87f950f --- /dev/null +++ b/packages/manaverse/testframework.go @@ -0,0 +1,38 @@ +package manaverse + +import ( + "sync" + + "github.com/iotaledger/hive.go/identity" +) + +type MockedManaLedger struct { + manaBalances map[identity.ID]int64 + manaBalancesMutex sync.Mutex +} + +func NewMockedManaLedger() (newMockedManaLedger *MockedManaLedger) { + return &MockedManaLedger{ + manaBalances: make(map[identity.ID]int64), + } +} + +func (m *MockedManaLedger) IncreaseMana(id identity.ID, mana int64) (newBalance int64) { + m.manaBalancesMutex.Lock() + defer m.manaBalancesMutex.Unlock() + + m.manaBalances[id] += mana + + return m.manaBalances[id] +} + +func (m *MockedManaLedger) DecreaseMana(id identity.ID, mana int64) (newBalance int64) { + m.manaBalancesMutex.Lock() + defer m.manaBalancesMutex.Unlock() + + m.manaBalances[id] -= mana + + return m.manaBalances[id] +} + +var _ ManaLedger = new(MockedManaLedger) diff --git a/packages/pow/pow.go b/packages/pow/pow.go index 08dbccdac0..fecafa7a0f 100644 --- a/packages/pow/pow.go +++ b/packages/pow/pow.go @@ -98,9 +98,7 @@ func (w *Worker) Mine(ctx context.Context, msg []byte, target int) (uint64, erro // LeadingZeros returns the number of leading zeros in the digest of the given data. func (w *Worker) LeadingZeros(data []byte) (int, error) { - digest := blake2b.Sum512(data) - asAnInt := new(big.Int).SetBytes(digest[:]) - return 8*blake2b.Size - asAnInt.BitLen(), nil + return LeadingZeros(data) } // LeadingZerosWithNonce returns the number of leading zeros in the digest @@ -142,3 +140,10 @@ func (w *Worker) worker(msg []byte, startNonce uint64, target int, done *uint32, func putUint64(b []byte, v uint64) { binary.LittleEndian.PutUint64(b, v) } + +// LeadingZeros returns the number of leading zeros in the digest of the given data. +func LeadingZeros(data []byte) (int, error) { + digest := blake2b.Sum512(data) + asAnInt := new(big.Int).SetBytes(digest[:]) + return 8*blake2b.Size - asAnInt.BitLen(), nil +} diff --git a/packages/tangle/message.go b/packages/tangle/message.go index c09e77fc27..952374861b 100644 --- a/packages/tangle/message.go +++ b/packages/tangle/message.go @@ -23,6 +23,7 @@ import ( "github.com/iotaledger/goshimmer/packages/ledger/utxo" "github.com/iotaledger/goshimmer/packages/ledger/vm/devnetvm" "github.com/iotaledger/goshimmer/packages/markers" + "github.com/iotaledger/goshimmer/packages/pow" "github.com/iotaledger/goshimmer/packages/tangle/payload" ) @@ -292,6 +293,7 @@ type Message struct { model.Storable[MessageID, Message, *Message, MessageModel] `serix:"0"` payload payload.Payload } + type MessageModel struct { // core properties (get sent over the wire) Version uint8 `serix:"0"` @@ -299,9 +301,10 @@ type MessageModel struct { IssuerPublicKey ed25519.PublicKey `serix:"2"` IssuingTime time.Time `serix:"3"` SequenceNumber uint64 `serix:"4"` - PayloadBytes []byte `serix:"5,lengthPrefixType=uint32"` - Nonce uint64 `serix:"6"` - Signature ed25519.Signature `serix:"7"` + BurnedMana int64 `serix:"5"` + PayloadBytes []byte `serix:"6,lengthPrefixType=uint32"` + Nonce uint64 `serix:"7"` + Signature ed25519.Signature `serix:"8"` } // NewMessage creates a new message with the details provided by the issuer. @@ -424,6 +427,11 @@ func (m *Message) SequenceNumber() uint64 { return m.M.SequenceNumber } +// BurnedMana returns the mana burned by this message. +func (m *Message) BurnedMana() int64 { + return m.M.BurnedMana +} + // Payload returns the Payload of the message. func (m *Message) Payload() payload.Payload { m.Lock() @@ -491,12 +499,49 @@ func (m *Message) String() string { builder.AddField(stringify.StructField("Issuer", m.IssuerPublicKey())) builder.AddField(stringify.StructField("IssuingTime", m.IssuingTime())) builder.AddField(stringify.StructField("SequenceNumber", m.SequenceNumber())) + builder.AddField(stringify.StructField("BurnedMana", m.BurnedMana())) builder.AddField(stringify.StructField("Payload", m.Payload())) builder.AddField(stringify.StructField("Nonce", m.Nonce())) builder.AddField(stringify.StructField("Signature", m.Signature())) return builder.String() } +// PoW returns the PoW of the Block. +func (m *Message) PoW() (leadingZeros int) { + serializedMessage, err := m.Bytes() + if err != nil { + return 0 + } + + leadingZeros, err = pow.LeadingZeros(serializedMessage) + if err != nil { + return 0 + } + + return leadingZeros +} + +// Compare is a comparator for Blocks. +func (m *Message) Compare(other *Message) int { + if m.BurnedMana() < other.BurnedMana() { + return -1 + } + + if m.BurnedMana() > other.BurnedMana() { + return 1 + } + + if m.PoW() < other.PoW() { + return -1 + } + + if m.PoW() > other.PoW() { + return 1 + } + + return 0 +} + // sorts given parents and returns a new slice with sorted parents func sortParents(parents MessageIDs) (sorted []MessageID) { sorted = parents.Slice() diff --git a/packages/tangle/payload/payload.go b/packages/tangle/payload/payload.go index 20bdd4c6e5..639818b68b 100644 --- a/packages/tangle/payload/payload.go +++ b/packages/tangle/payload/payload.go @@ -11,7 +11,7 @@ import ( // (version(1) + parentsBlocksCount(1) + 4 * (parentsType(1) + parentsCount(1) + 8 * reference(32)) + // issuerPK(32) + issuanceTime(8) + seqNum(8) + payloadLength(4) + nonce(8) + signature(64) // = MaxMessageSize - 1158 bytes = 64378 -const MaxSize = 64378 +const MaxSize = 64370 // Payload represents the generic interface for an object that can be embedded in Messages of the Tangle. type Payload interface { diff --git a/packages/tangle/testutils.go b/packages/tangle/testutils.go index 11ad4bd4a5..0a130cfebc 100644 --- a/packages/tangle/testutils.go +++ b/packages/tangle/testutils.go @@ -475,6 +475,7 @@ type MessageTestFrameworkMessageOptions struct { shallowDislikeParents map[string]types.Empty issuer ed25519.PublicKey issuingTime time.Time + burnedMana int64 reattachmentMessageAlias string sequenceNumber uint64 overrideSequenceNumber bool @@ -568,6 +569,13 @@ func WithIssuer(issuer ed25519.PublicKey) MessageOption { } } +// WithBurnedMana returns a MessageOption that is used to define the amount of burned mana. +func WithBurnedMana(burnedMana int64) MessageOption { + return func(options *MessageTestFrameworkMessageOptions) { + options.burnedMana = burnedMana + } +} + // WithIssuingTime returns a MessageOption that is used to set issuing time of the Message. func WithIssuingTime(issuingTime time.Time) MessageOption { return func(options *MessageTestFrameworkMessageOptions) { diff --git a/tools/integration-tests/tester/go.mod b/tools/integration-tests/tester/go.mod index e3398286c8..ab6f707660 100644 --- a/tools/integration-tests/tester/go.mod +++ b/tools/integration-tests/tester/go.mod @@ -7,7 +7,7 @@ require ( github.com/docker/docker v1.13.1 github.com/docker/go-connections v0.4.0 github.com/iotaledger/goshimmer v0.1.3 - github.com/iotaledger/hive.go v0.0.0-20220607150119-1be29e962175 + github.com/iotaledger/hive.go v0.0.0-20220620133504-13cc326a3d17 github.com/mr-tron/base58 v1.2.0 github.com/stretchr/testify v1.7.1 golang.org/x/crypto v0.0.0-20220214200702-86341886e292 diff --git a/tools/integration-tests/tester/go.sum b/tools/integration-tests/tester/go.sum index ff87a649e5..59c5afe511 100644 --- a/tools/integration-tests/tester/go.sum +++ b/tools/integration-tests/tester/go.sum @@ -461,8 +461,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= -github.com/iotaledger/hive.go v0.0.0-20220607150119-1be29e962175 h1:IgXxiPx51WJglOL5EtIurlMbujnrLP4vLYQqyfmR0zg= -github.com/iotaledger/hive.go v0.0.0-20220607150119-1be29e962175/go.mod h1:8f9U7qHFby0W3cxv/nKnz9LHn9BbwWU0tMsWDnfqzRI= +github.com/iotaledger/hive.go v0.0.0-20220620133504-13cc326a3d17 h1:VH6ZeNKdnuQ/TrRgZRscyL2jXQeI/pN4RfhNMUXHFL0= +github.com/iotaledger/hive.go v0.0.0-20220620133504-13cc326a3d17/go.mod h1:8f9U7qHFby0W3cxv/nKnz9LHn9BbwWU0tMsWDnfqzRI= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=