From 18ca0daec0f6989a9f7a0020700d017e605a770c Mon Sep 17 00:00:00 2001
From: jonastheis <mail@jonastheis.de>
Date: Thu, 29 Oct 2020 21:55:59 +0100
Subject: [PATCH] Start refactoring codebase to use new message layout - WIP

---
 .../packages/payload/payload_test.go          |  7 +--
 packages/tangle/message.go                    | 44 ++++++++++++++++---
 packages/tangle/message_test.go               |  8 ++--
 packages/tangle/messagefactory.go             | 30 +++++++------
 packages/tangle/messagefactory_test.go        | 14 +++---
 packages/tangle/payload_test.go               |  4 +-
 packages/tangle/tangle.go                     | 33 +++++++-------
 packages/tangle/tangle_test.go                |  7 +--
 packages/tangle/test_utils.go                 |  8 ++--
 packages/tangle/tipselector.go                | 32 ++++++--------
 plugins/dashboard/explorer_routes.go          | 10 ++---
 plugins/dashboard/visualizer.go               |  8 ++--
 plugins/webapi/message/findById.go            |  7 +--
 plugins/webapi/tools/message/pastcone.go      | 32 ++++++++------
 14 files changed, 137 insertions(+), 107 deletions(-)

diff --git a/dapps/valuetransfers/packages/payload/payload_test.go b/dapps/valuetransfers/packages/payload/payload_test.go
index 3b0f8761..4314ae42 100644
--- a/dapps/valuetransfers/packages/payload/payload_test.go
+++ b/dapps/valuetransfers/packages/payload/payload_test.go
@@ -46,11 +46,8 @@ func ExamplePayload() {
 
 	// 3. build actual transaction (the base layer creates this and wraps the ontology provided payload)
 	tx := tangle.NewMessage(
-		// parent1 in "network tangle" ontology (filled by tipSelector)
-		tangle.EmptyMessageID,
-
-		// parent2 in "network tangle" ontology (filled by tipSelector)
-		tangle.EmptyMessageID,
+		[]tangle.MessageID{tangle.EmptyMessageID},
+		[]tangle.MessageID{},
 
 		// the time when the transaction was created
 		time.Now(),
diff --git a/packages/tangle/message.go b/packages/tangle/message.go
index 0cb88673..beb6924e 100644
--- a/packages/tangle/message.go
+++ b/packages/tangle/message.go
@@ -14,12 +14,15 @@ import (
 	"github.com/iotaledger/hive.go/marshalutil"
 	"github.com/iotaledger/hive.go/objectstorage"
 	"github.com/iotaledger/hive.go/stringify"
+	"github.com/iotaledger/hive.go/types"
 	"github.com/mr-tron/base58"
 	"golang.org/x/crypto/blake2b"
 	"golang.org/x/xerrors"
 )
 
 const (
+	MessageVersion uint8 = 1
+
 	// MaxMessageSize defines the maximum size of a message.
 	MaxMessageSize = 64 * 1024
 
@@ -50,6 +53,7 @@ var EmptyMessageID = MessageID{}
 
 // NewMessageID creates a new message id.
 func NewMessageID(base58EncodedString string) (result MessageID, err error) {
+	// TODO: rename to avoid collision with imported package
 	bytes, err := base58.Decode(base58EncodedString)
 	if err != nil {
 		err = fmt.Errorf("failed to decode base58 encoded string '%s': %w", base58EncodedString, err)
@@ -140,13 +144,21 @@ type Message struct {
 }
 
 // NewMessage creates a new message with the details provided by the issuer.
-func NewMessage(parent1ID MessageID, parent2ID MessageID, issuingTime time.Time, issuerPublicKey ed25519.PublicKey, sequenceNumber uint64, payload payload.Payload, nonce uint64, signature ed25519.Signature) (result *Message) {
-	// TODO: do syntactical validation
+func NewMessage(strongParents []MessageID, weakParents []MessageID, issuingTime time.Time, issuerPublicKey ed25519.PublicKey, sequenceNumber uint64, payload payload.Payload, nonce uint64, signature ed25519.Signature) (result *Message) {
+	// syntactical validation
+	parentsCount := len(strongParents) + len(weakParents)
+	if parentsCount < MinParentsCount || parentsCount > MaxParentsCount {
+		panic(fmt.Sprintf("amount of parents (%d) not in valid range (%d-%d)", parentsCount, MinParentsCount, MaxParentsCount))
+	}
+
+	if len(strongParents) < MinStrongParentsCount {
+		panic(fmt.Sprintf("amount of strong parents (%d) failed to reach MinStrongParentsCount (%d)", len(strongParents), MinStrongParentsCount))
+	}
 
 	return &Message{
-		// TODO: copy slices for strong/weak parents?
-		//parent1ID:       parent1ID,
-		//parent2ID:       parent2ID,
+		version:         MessageVersion,
+		strongParents:   sortParents(strongParents),
+		weakParents:     sortParents(weakParents),
 		issuerPublicKey: issuerPublicKey,
 		issuingTime:     issuingTime,
 		sequenceNumber:  sequenceNumber,
@@ -156,6 +168,28 @@ func NewMessage(parent1ID MessageID, parent2ID MessageID, issuingTime time.Time,
 	}
 }
 
+// filters and sorts given parents and returns a new slice with sorted parents
+func sortParents(parents []MessageID) (sorted []MessageID) {
+	seen := make(map[MessageID]types.Empty)
+	sorted = make([]MessageID, 0, len(parents))
+
+	// filter duplicates
+	for _, parent := range parents {
+		if _, seenAlready := seen[parent]; seenAlready {
+			continue
+		}
+		seen[parent] = types.Void
+		sorted = append(sorted, parent)
+	}
+
+	// sort parents
+	sort.Slice(parents, func(i, j int) bool {
+		return bytes.Compare(parents[i].Bytes(), parents[j].Bytes()) < 0
+	})
+
+	return
+}
+
 // MessageFromBytes parses the given bytes into a message.
 func MessageFromBytes(bytes []byte) (result *Message, consumedBytes int, err error) {
 	marshalUtil := marshalutil.New(bytes)
diff --git a/packages/tangle/message_test.go b/packages/tangle/message_test.go
index 7946f3ba..d0c55f13 100644
--- a/packages/tangle/message_test.go
+++ b/packages/tangle/message_test.go
@@ -16,13 +16,13 @@ func TestMessage_VerifySignature(t *testing.T) {
 	keyPair := ed25519.GenerateKeyPair()
 	pl := payload.NewGenericDataPayload([]byte("test"))
 
-	unsigned := NewMessage(EmptyMessageID, EmptyMessageID, time.Time{}, keyPair.PublicKey, 0, pl, 0, ed25519.Signature{})
+	unsigned := NewMessage([]MessageID{EmptyMessageID}, []MessageID{}, time.Time{}, keyPair.PublicKey, 0, pl, 0, ed25519.Signature{})
 	assert.False(t, unsigned.VerifySignature())
 
 	unsignedBytes := unsigned.Bytes()
 	signature := keyPair.PrivateKey.Sign(unsignedBytes[:len(unsignedBytes)-ed25519.SignatureSize])
 
-	signed := NewMessage(EmptyMessageID, EmptyMessageID, time.Time{}, keyPair.PublicKey, 0, pl, 0, signature)
+	signed := NewMessage([]MessageID{EmptyMessageID}, []MessageID{}, time.Time{}, keyPair.PublicKey, 0, pl, 0, signature)
 	assert.True(t, signed.VerifySignature())
 }
 
@@ -39,8 +39,8 @@ func TestMessage_MarshalUnmarshal(t *testing.T) {
 	restoredMessage, _, err := MessageFromBytes(testMessage.Bytes())
 	if assert.NoError(t, err, err) {
 		assert.Equal(t, testMessage.ID(), restoredMessage.ID())
-		assert.Equal(t, testMessage.Parent1ID(), restoredMessage.Parent1ID())
-		assert.Equal(t, testMessage.Parent2ID(), restoredMessage.Parent2ID())
+		assert.ElementsMatch(t, testMessage.StrongParents(), restoredMessage.StrongParents())
+		assert.ElementsMatch(t, testMessage.WeakParents(), restoredMessage.WeakParents())
 		assert.Equal(t, testMessage.IssuerPublicKey(), restoredMessage.IssuerPublicKey())
 		assert.Equal(t, testMessage.IssuingTime().Round(time.Second), restoredMessage.IssuingTime().Round(time.Second))
 		assert.Equal(t, testMessage.SequenceNumber(), restoredMessage.SequenceNumber())
diff --git a/packages/tangle/messagefactory.go b/packages/tangle/messagefactory.go
index b2ba17af..9faa2715 100644
--- a/packages/tangle/messagefactory.go
+++ b/packages/tangle/messagefactory.go
@@ -20,7 +20,7 @@ var (
 
 // A TipSelector selects two tips, parent2 and parent1, for a new message to attach to.
 type TipSelector interface {
-	Tips() (parent1 MessageID, parent2 MessageID)
+	Tips(count int) (parents []MessageID)
 }
 
 // A Worker performs the PoW for the provided message in serialized byte form.
@@ -83,12 +83,16 @@ func (f *MessageFactory) IssuePayload(p payload.Payload) (*Message, error) {
 		return nil, err
 	}
 
-	parent1ID, parent2ID := f.selector.Tips()
+	// TODO: change hardcoded amount of parents
+	strongParents := f.selector.Tips(2)
+	// TODO: approval switch: select weak parents
+	weakParents := make([]MessageID, 0)
+
 	issuingTime := time.Now()
 	issuerPublicKey := f.localIdentity.PublicKey()
 
 	// do the PoW
-	nonce, err := f.doPOW(parent1ID, parent2ID, issuingTime, issuerPublicKey, sequenceNumber, p)
+	nonce, err := f.doPOW(strongParents, weakParents, issuingTime, issuerPublicKey, sequenceNumber, p)
 	if err != nil {
 		err = fmt.Errorf("pow failed: %w", err)
 		f.Events.Error.Trigger(err)
@@ -96,11 +100,11 @@ func (f *MessageFactory) IssuePayload(p payload.Payload) (*Message, error) {
 	}
 
 	// create the signature
-	signature := f.sign(parent1ID, parent2ID, issuingTime, issuerPublicKey, sequenceNumber, p, nonce)
+	signature := f.sign(strongParents, weakParents, issuingTime, issuerPublicKey, sequenceNumber, p, nonce)
 
 	msg := NewMessage(
-		parent1ID,
-		parent2ID,
+		strongParents,
+		weakParents,
 		issuingTime,
 		issuerPublicKey,
 		sequenceNumber,
@@ -119,18 +123,18 @@ func (f *MessageFactory) Shutdown() {
 	}
 }
 
-func (f *MessageFactory) doPOW(parent1ID MessageID, parent2ID MessageID, issuingTime time.Time, key ed25519.PublicKey, seq uint64, payload payload.Payload) (uint64, error) {
+func (f *MessageFactory) doPOW(strongParents []MessageID, weakParents []MessageID, issuingTime time.Time, key ed25519.PublicKey, seq uint64, payload payload.Payload) (uint64, error) {
 	// create a dummy message to simplify marshaling
-	dummy := NewMessage(parent1ID, parent2ID, issuingTime, key, seq, payload, 0, ed25519.EmptySignature).Bytes()
+	dummy := NewMessage(strongParents, weakParents, issuingTime, key, seq, payload, 0, ed25519.EmptySignature).Bytes()
 
 	f.workerMutex.RLock()
 	defer f.workerMutex.RUnlock()
 	return f.worker.DoPOW(dummy)
 }
 
-func (f *MessageFactory) sign(parent1ID MessageID, parent2ID MessageID, issuingTime time.Time, key ed25519.PublicKey, seq uint64, payload payload.Payload, nonce uint64) ed25519.Signature {
+func (f *MessageFactory) sign(strongParents []MessageID, weakParents []MessageID, issuingTime time.Time, key ed25519.PublicKey, seq uint64, payload payload.Payload, nonce uint64) ed25519.Signature {
 	// create a dummy message to simplify marshaling
-	dummy := NewMessage(parent1ID, parent2ID, issuingTime, key, seq, payload, nonce, ed25519.EmptySignature)
+	dummy := NewMessage(strongParents, weakParents, issuingTime, key, seq, payload, nonce, ed25519.EmptySignature)
 	dummyBytes := dummy.Bytes()
 
 	contentLength := len(dummyBytes) - len(dummy.Signature())
@@ -138,11 +142,11 @@ func (f *MessageFactory) sign(parent1ID MessageID, parent2ID MessageID, issuingT
 }
 
 // The TipSelectorFunc type is an adapter to allow the use of ordinary functions as tip selectors.
-type TipSelectorFunc func() (MessageID, MessageID)
+type TipSelectorFunc func(count int) (parents []MessageID)
 
 // Tips calls f().
-func (f TipSelectorFunc) Tips() (MessageID, MessageID) {
-	return f()
+func (f TipSelectorFunc) Tips(count int) (parents []MessageID) {
+	return f(count)
 }
 
 // The WorkerFunc type is an adapter to allow the use of ordinary functions as a PoW performer.
diff --git a/packages/tangle/messagefactory_test.go b/packages/tangle/messagefactory_test.go
index 304e0836..4bdcb730 100644
--- a/packages/tangle/messagefactory_test.go
+++ b/packages/tangle/messagefactory_test.go
@@ -30,7 +30,7 @@ func TestMessageFactory_BuildMessage(t *testing.T) {
 		mapdb.NewMapDB(),
 		[]byte(sequenceKey),
 		identity.GenerateLocalIdentity(),
-		TipSelectorFunc(func() (MessageID, MessageID) { return EmptyMessageID, EmptyMessageID }),
+		TipSelectorFunc(func(count int) []MessageID { return []MessageID{EmptyMessageID} }),
 	)
 	defer msgFactory.Shutdown()
 
@@ -48,8 +48,8 @@ func TestMessageFactory_BuildMessage(t *testing.T) {
 		msg, err := msgFactory.IssuePayload(p)
 		require.NoError(t, err)
 
-		assert.NotNil(t, msg.Parent1ID())
-		assert.NotNil(t, msg.Parent2ID())
+		// TODO: approval switch: make test case with weak parents
+		assert.NotEmpty(t, msg.StrongParents())
 
 		// time in range of 0.1 seconds
 		assert.InDelta(t, time.Now().UnixNano(), msg.IssuingTime().UnixNano(), 100000000)
@@ -74,8 +74,8 @@ func TestMessageFactory_BuildMessage(t *testing.T) {
 				msg, err := msgFactory.IssuePayload(p)
 				require.NoError(t, err)
 
-				assert.NotNil(t, msg.Parent1ID())
-				assert.NotNil(t, msg.Parent2ID())
+				// TODO: approval switch: make test case with weak parents
+				assert.NotEmpty(t, msg.StrongParents())
 
 				// time in range of 0.1 seconds
 				assert.InDelta(t, time.Now().UnixNano(), msg.IssuingTime().UnixNano(), 100000000)
@@ -116,7 +116,7 @@ func TestMessageFactory_POW(t *testing.T) {
 		mapdb.NewMapDB(),
 		[]byte(sequenceKey),
 		identity.GenerateLocalIdentity(),
-		TipSelectorFunc(func() (MessageID, MessageID) { return EmptyMessageID, EmptyMessageID }),
+		TipSelectorFunc(func(count int) []MessageID { return []MessageID{EmptyMessageID} }),
 	)
 	defer msgFactory.Shutdown()
 
@@ -143,7 +143,7 @@ func TestWorkerFunc_PayloadSize(t *testing.T) {
 		mapdb.NewMapDB(),
 		[]byte(sequenceKey),
 		identity.GenerateLocalIdentity(),
-		TipSelectorFunc(func() (MessageID, MessageID) { return EmptyMessageID, EmptyMessageID }),
+		TipSelectorFunc(func(count int) []MessageID { return []MessageID{EmptyMessageID} }),
 	)
 	defer msgFactory.Shutdown()
 
diff --git a/packages/tangle/payload_test.go b/packages/tangle/payload_test.go
index 28e78ce2..d3c42837 100644
--- a/packages/tangle/payload_test.go
+++ b/packages/tangle/payload_test.go
@@ -18,7 +18,7 @@ func BenchmarkVerifyDataMessages(b *testing.B) {
 	var pool async.WorkerPool
 	pool.Tune(runtime.GOMAXPROCS(0))
 
-	factory := NewMessageFactory(mapdb.NewMapDB(), []byte(DBSequenceNumber), identity.GenerateLocalIdentity(), TipSelectorFunc(func() (MessageID, MessageID) { return EmptyMessageID, EmptyMessageID }))
+	factory := NewMessageFactory(mapdb.NewMapDB(), []byte(DBSequenceNumber), identity.GenerateLocalIdentity(), TipSelectorFunc(func(count int) []MessageID { return []MessageID{EmptyMessageID} }))
 
 	messages := make([][]byte, b.N)
 	for i := 0; i < b.N; i++ {
@@ -46,7 +46,7 @@ func BenchmarkVerifyDataMessages(b *testing.B) {
 func BenchmarkVerifySignature(b *testing.B) {
 	pool, _ := ants.NewPool(80, ants.WithNonblocking(false))
 
-	factory := NewMessageFactory(mapdb.NewMapDB(), []byte(DBSequenceNumber), identity.GenerateLocalIdentity(), TipSelectorFunc(func() (MessageID, MessageID) { return EmptyMessageID, EmptyMessageID }))
+	factory := NewMessageFactory(mapdb.NewMapDB(), []byte(DBSequenceNumber), identity.GenerateLocalIdentity(), TipSelectorFunc(func(count int) []MessageID { return []MessageID{EmptyMessageID} }))
 
 	messages := make([]*Message, b.N)
 	for i := 0; i < b.N; i++ {
diff --git a/packages/tangle/tangle.go b/packages/tangle/tangle.go
index 6fd24baf..862a06a9 100644
--- a/packages/tangle/tangle.go
+++ b/packages/tangle/tangle.go
@@ -90,13 +90,11 @@ func (t *Tangle) Approvers(messageID MessageID) CachedApprovers {
 // message as an approver.
 func (t *Tangle) DeleteMessage(messageID MessageID) {
 	t.Message(messageID).Consume(func(currentMsg *Message) {
-		parent1MsgID := currentMsg.Parent1ID()
-		t.deleteApprover(parent1MsgID, messageID)
 
-		parent2MsgID := currentMsg.Parent2ID()
-		if parent2MsgID != parent1MsgID {
-			t.deleteApprover(parent2MsgID, messageID)
-		}
+		// TODO: reconsider behavior with approval switch
+		currentMsg.ForEachParent(func(parent Parent) {
+			t.deleteApprover(parent.ID, messageID)
+		})
 
 		t.messageMetadataStorage.Delete(messageID[:])
 		t.messageStorage.Delete(messageID[:])
@@ -196,14 +194,11 @@ func (t *Tangle) storeMessageWorker(msg *Message) {
 	messageID := msg.ID()
 	cachedMsgMetadata := &CachedMessageMetadata{CachedObject: t.messageMetadataStorage.Store(NewMessageMetadata(messageID))}
 
-	// store parent1 approver
-	parent1MsgID := msg.Parent1ID()
-	t.approverStorage.Store(NewApprover(parent1MsgID, messageID)).Release()
-
-	// store parent2 approver
-	if parent2MsgID := msg.Parent2ID(); parent2MsgID != parent1MsgID {
-		t.approverStorage.Store(NewApprover(parent2MsgID, messageID)).Release()
-	}
+	// TODO: approval switch: we probably need to introduce approver types
+	// store approvers
+	msg.ForEachStrongParent(func(parent MessageID) {
+		t.approverStorage.Store(NewApprover(parent, messageID)).Release()
+	})
 
 	// trigger events
 	if t.missingMessageStorage.DeleteIfPresent(messageID[:]) {
@@ -269,9 +264,13 @@ func (t *Tangle) isMessageSolid(msg *Message, msgMetadata *MessageMetadata) bool
 	}
 
 	// as missing messages are requested in isMessageMarkedAsSolid, we want to prevent short-circuit evaluation
-	parent1Solid := t.isMessageMarkedAsSolid(msg.Parent1ID())
-	parent2Solid := t.isMessageMarkedAsSolid(msg.Parent2ID())
-	return parent1Solid && parent2Solid
+	solid := true
+
+	msg.ForEachParent(func(parent Parent) {
+		solid = solid && t.isMessageMarkedAsSolid(parent.ID)
+	})
+
+	return solid
 }
 
 // builds up a stack from the given message and tries to solidify into the present.
diff --git a/packages/tangle/tangle_test.go b/packages/tangle/tangle_test.go
index c8d5d377..5d569bb9 100644
--- a/packages/tangle/tangle_test.go
+++ b/packages/tangle/tangle_test.go
@@ -126,11 +126,8 @@ func TestTangle_MissingMessages(t *testing.T) {
 
 		// remove a tip if the width of the tangle is reached
 		if tips.Size() >= widthOfTheTangle {
-			if rand.Intn(1000) < 500 {
-				tips.Delete(msg.Parent2ID())
-			} else {
-				tips.Delete(msg.Parent1ID())
-			}
+			index := rand.Intn(len(msg.StrongParents()))
+			tips.Delete(msg.StrongParents()[index])
 		}
 
 		// add current message as a tip
diff --git a/packages/tangle/test_utils.go b/packages/tangle/test_utils.go
index e0808bbe..3b81f54e 100644
--- a/packages/tangle/test_utils.go
+++ b/packages/tangle/test_utils.go
@@ -8,13 +8,13 @@ import (
 )
 
 func newTestNonceMessage(nonce uint64) *Message {
-	return NewMessage(EmptyMessageID, EmptyMessageID, time.Time{}, ed25519.PublicKey{}, 0, payload.NewGenericDataPayload([]byte("test")), nonce, ed25519.Signature{})
+	return NewMessage([]MessageID{EmptyMessageID}, []MessageID{}, time.Time{}, ed25519.PublicKey{}, 0, payload.NewGenericDataPayload([]byte("test")), nonce, ed25519.Signature{})
 }
 
 func newTestDataMessage(payloadString string) *Message {
-	return NewMessage(EmptyMessageID, EmptyMessageID, time.Now(), ed25519.PublicKey{}, 0, payload.NewGenericDataPayload([]byte(payloadString)), 0, ed25519.Signature{})
+	return NewMessage([]MessageID{EmptyMessageID}, []MessageID{}, time.Now(), ed25519.PublicKey{}, 0, payload.NewGenericDataPayload([]byte(payloadString)), 0, ed25519.Signature{})
 }
 
-func newTestParentsDataMessage(payloadString string, parent1, parent2 MessageID) *Message {
-	return NewMessage(parent1, parent2, time.Now(), ed25519.PublicKey{}, 0, payload.NewGenericDataPayload([]byte(payloadString)), 0, ed25519.Signature{})
+func newTestParentsDataMessage(payloadString string, strongParents, weakParents []MessageID) *Message {
+	return NewMessage(strongParents, weakParents, time.Now(), ed25519.PublicKey{}, 0, payload.NewGenericDataPayload([]byte(payloadString)), 0, ed25519.Signature{})
 }
diff --git a/packages/tangle/tipselector.go b/packages/tangle/tipselector.go
index ded0de28..8cd54929 100644
--- a/packages/tangle/tipselector.go
+++ b/packages/tangle/tipselector.go
@@ -38,34 +38,28 @@ func (t *MessageTipSelector) AddTip(msg *Message) {
 		t.Events.TipAdded.Trigger(messageID)
 	}
 
-	parent1MessageID := msg.Parent1ID()
-	if _, deleted := t.tips.Delete(parent1MessageID); deleted {
-		t.Events.TipRemoved.Trigger(parent1MessageID)
-	}
-
-	parent2MessageID := msg.Parent2ID()
-	if _, deleted := t.tips.Delete(parent2MessageID); deleted {
-		t.Events.TipRemoved.Trigger(parent2MessageID)
-	}
+	msg.ForEachStrongParent(func(parent MessageID) {
+		if _, deleted := t.tips.Delete(parent); deleted {
+			t.Events.TipRemoved.Trigger(parent)
+		}
+	})
 }
 
 // Tips returns two tips.
-func (t *MessageTipSelector) Tips() (parent1MessageID, parent2MessageID MessageID) {
+func (t *MessageTipSelector) Tips(count int) (parents []MessageID) {
+	parents = make([]MessageID, 0, count)
+
 	tip := t.tips.RandomEntry()
 	if tip == nil {
-		parent1MessageID = EmptyMessageID
-		parent2MessageID = EmptyMessageID
-
+		parents = append(parents, EmptyMessageID)
 		return
 	}
 
-	parent2MessageID = tip.(MessageID)
-
-	if t.tips.Size() == 1 {
-		parent1MessageID = parent2MessageID
-		return
-	}
+	tipMessageID := tip.(MessageID)
+	parents = append(parents, tipMessageID)
 
+	// TODO: adjust tip selection to select as many tips as count
+	// it is a bit tricky to not cause a deadlock if we don't allow duplicates
 	parent1MessageID = t.tips.RandomEntry().(MessageID)
 	for parent1MessageID == parent2MessageID && t.tips.Size() > 1 {
 		parent1MessageID = t.tips.RandomEntry().(MessageID)
diff --git a/plugins/dashboard/explorer_routes.go b/plugins/dashboard/explorer_routes.go
index d6a1b898..900edffd 100644
--- a/plugins/dashboard/explorer_routes.go
+++ b/plugins/dashboard/explorer_routes.go
@@ -52,11 +52,11 @@ func createExplorerMessage(msg *tangle.Message) (*ExplorerMessage, error) {
 		IssuerPublicKey:         msg.IssuerPublicKey().String(),
 		Signature:               msg.Signature().String(),
 		SequenceNumber:          msg.SequenceNumber(),
-		Parent1MessageID:        msg.Parent1ID().String(),
-		Parent2MessageID:        msg.Parent2ID().String(),
-		Solid:                   cachedMessageMetadata.Unwrap().IsSolid(),
-		PayloadType:             uint32(msg.Payload().Type()),
-		Payload:                 ProcessPayload(msg.Payload()),
+		//Parent1MessageID:        msg.Parent1ID().String(),
+		//Parent2MessageID:        msg.Parent2ID().String(),
+		Solid:       cachedMessageMetadata.Unwrap().IsSolid(),
+		PayloadType: uint32(msg.Payload().Type()),
+		Payload:     ProcessPayload(msg.Payload()),
 	}
 
 	return t, nil
diff --git a/plugins/dashboard/visualizer.go b/plugins/dashboard/visualizer.go
index d33e669c..2b6d41c7 100644
--- a/plugins/dashboard/visualizer.go
+++ b/plugins/dashboard/visualizer.go
@@ -52,10 +52,10 @@ func sendVertex(cachedMessage *tangle.CachedMessage, cachedMessageMetadata *tang
 		return
 	}
 	broadcastWsMessage(&wsmsg{MsgTypeVertex, &vertex{
-		ID:        msg.ID().String(),
-		Parent1ID: msg.Parent1ID().String(),
-		Parent2ID: msg.Parent2ID().String(),
-		IsSolid:   cachedMessageMetadata.Unwrap().IsSolid(),
+		ID: msg.ID().String(),
+		//Parent1ID: msg.Parent1ID().String(),
+		//Parent2ID: msg.Parent2ID().String(),
+		IsSolid: cachedMessageMetadata.Unwrap().IsSolid(),
 	}}, true)
 }
 
diff --git a/plugins/webapi/message/findById.go b/plugins/webapi/message/findById.go
index 707d8fe4..fa1089c4 100644
--- a/plugins/webapi/message/findById.go
+++ b/plugins/webapi/message/findById.go
@@ -46,9 +46,10 @@ func findByIDHandler(c echo.Context) error {
 				Solid:              msgMetadata.IsSolid(),
 				SolidificationTime: msgMetadata.SolidificationTime().Unix(),
 			},
-			ID:              msg.ID().String(),
-			Parent1ID:       msg.Parent1ID().String(),
-			Parent2ID:       msg.Parent2ID().String(),
+			ID: msg.ID().String(),
+			//TODO: adjust
+			//Parent1ID:       msg.Parent1ID().String(),
+			//Parent2ID:       msg.Parent2ID().String(),
 			IssuerPublicKey: msg.IssuerPublicKey().String(),
 			IssuingTime:     msg.IssuingTime().Unix(),
 			SequenceNumber:  msg.SequenceNumber(),
diff --git a/plugins/webapi/tools/message/pastcone.go b/plugins/webapi/tools/message/pastcone.go
index 1d92c9bb..360f4866 100644
--- a/plugins/webapi/tools/message/pastcone.go
+++ b/plugins/webapi/tools/message/pastcone.go
@@ -48,26 +48,30 @@ func PastconeHandler(c echo.Context) error {
 
 		// get parent1 and parent2
 		msg := msgObject.Unwrap()
-		parent2ID := msg.Parent2ID()
-		parent1ID := msg.Parent1ID()
 
-		// release objects
-		msgObject.Release()
-		msgMetadataObject.Release()
+		onlyGenesis := true
+		msg.ForEachParent(func(parent tangle.Parent) {
+			onlyGenesis = onlyGenesis && (parent.ID == tangle.EmptyMessageID)
+		})
 
-		if parent2ID == tangle.EmptyMessageID && msg.Parent1ID() == tangle.EmptyMessageID {
+		if onlyGenesis {
+			// release objects
+			msgObject.Release()
+			msgMetadataObject.Release()
 			// msg only attaches to genesis
 			continue
 		} else {
-			if !submitted[parent2ID] && parent2ID != tangle.EmptyMessageID {
-				stack.PushBack(parent2ID)
-				submitted[parent2ID] = true
-			}
-			if !submitted[parent1ID] && parent1ID != tangle.EmptyMessageID {
-				stack.PushBack(parent1ID)
-				submitted[parent1ID] = true
-			}
+			msg.ForEachParent(func(parent tangle.Parent) {
+				if !submitted[parent.ID] && parent.ID != tangle.EmptyMessageID {
+					stack.PushBack(parent.ID)
+					submitted[parent.ID] = true
+				}
+			})
 		}
+
+		// release objects
+		msgObject.Release()
+		msgMetadataObject.Release()
 	}
 	return c.JSON(http.StatusOK, PastconeResponse{Exist: true, PastConeSize: checkedMessageCount})
 }
-- 
GitLab