diff --git a/packages/transactionspammer/transactionspammer.go b/packages/transactionspammer/transactionspammer.go
index 67ba1fa87f950b6589a2186d47e0c104fc182ad1..00493158b87578f095deb48939f49321475ded94 100644
--- a/packages/transactionspammer/transactionspammer.go
+++ b/packages/transactionspammer/transactionspammer.go
@@ -63,7 +63,7 @@ func Start(tps uint64) {
 				tx.SetTail(true)
 				tx.SetAddress(targetAddress)
 				tx.SetBranchTransactionHash(tipselection.GetRandomTip())
-				tx.SetTrunkTransactionHash(tipselection.GetRandomTip())
+				tx.SetTrunkTransactionHash(tipselection.GetRandomTip(tx.GetBranchTransactionHash()))
 				tx.SetTimestamp(uint(time.Now().Unix()))
 				if err := tx.DoProofOfWork(meta_transaction.MIN_WEIGHT_MAGNITUDE); err != nil {
 					log.Warn("PoW failed", err)
diff --git a/plugins/tipselection/plugin.go b/plugins/tipselection/plugin.go
index eb983d4869cef8c4e5bef38d5958670368dbf985..7587075e303638b84e24fe51494eeff6e257ac8f 100644
--- a/plugins/tipselection/plugin.go
+++ b/plugins/tipselection/plugin.go
@@ -5,17 +5,23 @@ import (
 	"github.com/iotaledger/goshimmer/plugins/tangle"
 	"github.com/iotaledger/hive.go/events"
 	"github.com/iotaledger/hive.go/node"
+	"github.com/iotaledger/iota.go/trinary"
 )
 
 var PLUGIN = node.NewPlugin("Tipselection", node.Enabled, configure, run)
 
-func configure(node *node.Plugin) {
+func configure(*node.Plugin) {
+	tipSet = make(map[trinary.Hash]struct{})
+
 	tangle.Events.TransactionSolid.Attach(events.NewClosure(func(transaction *value_transaction.ValueTransaction) {
-		tips.Delete(transaction.GetBranchTransactionHash())
-		tips.Delete(transaction.GetTrunkTransactionHash())
-		tips.Set(transaction.GetHash(), transaction.GetHash())
+		mutex.Lock()
+		defer mutex.Unlock()
+
+		delete(tipSet, transaction.GetBranchTransactionHash())
+		delete(tipSet, transaction.GetTrunkTransactionHash())
+		tipSet[transaction.GetHash()] = struct{}{}
 	}))
 }
 
-func run(run *node.Plugin) {
+func run(*node.Plugin) {
 }
diff --git a/plugins/tipselection/tipselection.go b/plugins/tipselection/tipselection.go
index 34eb31e60ca7636e49ce4e6813565f828088cd9e..95cb4bc1b523637286be4eabb9692458bc07627f 100644
--- a/plugins/tipselection/tipselection.go
+++ b/plugins/tipselection/tipselection.go
@@ -1,23 +1,54 @@
 package tipselection
 
 import (
-	"github.com/iotaledger/goshimmer/packages/datastructure"
+	"math/rand"
+	"sync"
+
 	"github.com/iotaledger/goshimmer/packages/model/meta_transaction"
 	"github.com/iotaledger/iota.go/trinary"
 )
 
-var tips = datastructure.NewRandomMap()
+var (
+	tipSet map[trinary.Hash]struct{}
+	mutex  sync.RWMutex
+)
+
+func GetRandomTip(excluding ...trinary.Hash) trinary.Trytes {
+	mutex.RLock()
+	defer mutex.RUnlock()
+
+	numTips := len(tipSet)
+	if numTips == 0 {
+		return meta_transaction.BRANCH_NULL_HASH
+	}
 
-func GetRandomTip() (result trinary.Trytes) {
-	if randomTipHash := tips.RandomEntry(); randomTipHash != nil {
-		result = randomTipHash.(trinary.Trytes)
-	} else {
-		result = meta_transaction.BRANCH_NULL_HASH
+	var ignore trinary.Hash
+	if len(excluding) > 0 {
+		ignore = excluding[0]
+	}
+	if _, contains := tipSet[ignore]; contains {
+		if numTips == 1 {
+			return ignore
+		}
+		numTips -= 1
 	}
 
-	return
+	i := rand.Intn(numTips)
+	for k := range tipSet {
+		if k == ignore {
+			continue
+		}
+		if i == 0 {
+			return k
+		}
+		i--
+	}
+	panic("unreachable")
 }
 
 func GetTipsCount() int {
-	return tips.Size()
+	mutex.RLock()
+	defer mutex.RUnlock()
+
+	return len(tipSet)
 }
diff --git a/plugins/tipselection/tipselection_test.go b/plugins/tipselection/tipselection_test.go
index cb8274bfed54cab78ce985ab7ecd001795b52f2e..cb18e47815cde237bcfdedd9dac431d4156af5fb 100644
--- a/plugins/tipselection/tipselection_test.go
+++ b/plugins/tipselection/tipselection_test.go
@@ -1,24 +1,81 @@
 package tipselection
 
 import (
-	"fmt"
+	"log"
 	"testing"
 
+	"github.com/iotaledger/goshimmer/packages/model/meta_transaction"
 	"github.com/iotaledger/goshimmer/packages/model/value_transaction"
 	"github.com/iotaledger/goshimmer/plugins/tangle"
+	"github.com/iotaledger/hive.go/logger"
+	"github.com/spf13/viper"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
-func Test(t *testing.T) {
+func init() {
+	if err := logger.InitGlobalLogger(viper.New()); err != nil {
+		log.Fatal(err)
+	}
+}
+
+func TestEmptyTipSet(t *testing.T) {
+	configure(nil)
+	assert.Equal(t, 0, GetTipsCount())
+	assert.Equal(t, meta_transaction.BRANCH_NULL_HASH, GetRandomTip())
+}
+
+func TestSingleTip(t *testing.T) {
 	configure(nil)
 
-	for i := 0; i < 1000; i++ {
-		tx := value_transaction.New()
-		tx.SetValue(int64(i + 1))
-		tx.SetBranchTransactionHash(GetRandomTip())
-		tx.SetTrunkTransactionHash(GetRandomTip())
+	tx := value_transaction.New()
+	tx.SetValue(int64(1))
+	tx.SetBranchTransactionHash(meta_transaction.BRANCH_NULL_HASH)
+	tx.SetTrunkTransactionHash(meta_transaction.BRANCH_NULL_HASH)
 
-		tangle.Events.TransactionSolid.Trigger(tx)
+	tangle.Events.TransactionSolid.Trigger(tx)
 
-		fmt.Println(GetTipsCount())
-	}
+	assert.Equal(t, 1, GetTipsCount())
+
+	tip1 := GetRandomTip()
+	assert.NotNil(t, tip1)
+	tip2 := GetRandomTip(tip1)
+	assert.NotNil(t, tip2)
+	assert.Equal(t, tip1, tip2)
+}
+
+func TestGetRandomTip(t *testing.T) {
+	configure(nil)
+
+	tx := value_transaction.New()
+	tx.SetValue(int64(1))
+	tx.SetBranchTransactionHash(meta_transaction.BRANCH_NULL_HASH)
+	tx.SetTrunkTransactionHash(meta_transaction.BRANCH_NULL_HASH)
+
+	tangle.Events.TransactionSolid.Trigger(tx)
+
+	tx = value_transaction.New()
+	tx.SetValue(int64(2))
+	tx.SetBranchTransactionHash(meta_transaction.BRANCH_NULL_HASH)
+	tx.SetTrunkTransactionHash(meta_transaction.BRANCH_NULL_HASH)
+
+	tangle.Events.TransactionSolid.Trigger(tx)
+
+	assert.Equal(t, 2, GetTipsCount())
+
+	tip1 := GetRandomTip()
+	require.NotNil(t, tip1)
+	tip2 := GetRandomTip(tip1)
+	require.NotNil(t, tip2)
+	require.NotEqual(t, tip1, tip2)
+
+	tx = value_transaction.New()
+	tx.SetValue(int64(3))
+	tx.SetBranchTransactionHash(tip1)
+	tx.SetTrunkTransactionHash(tip2)
+
+	tangle.Events.TransactionSolid.Trigger(tx)
+
+	assert.Equal(t, 1, GetTipsCount())
+	assert.Equal(t, tx.GetHash(), GetRandomTip())
 }
diff --git a/plugins/webapi/broadcastData/plugin.go b/plugins/webapi/broadcastData/plugin.go
index 2e30888e10d830d62765a3576b0f0efcf992a3c4..180144daece29905658d1e667c5521ed0cb838c2 100644
--- a/plugins/webapi/broadcastData/plugin.go
+++ b/plugins/webapi/broadcastData/plugin.go
@@ -66,7 +66,7 @@ func broadcastData(c echo.Context) error {
 	tx.SetSignatureMessageFragment(trytes)
 	tx.SetValue(0)
 	tx.SetBranchTransactionHash(tipselection.GetRandomTip())
-	tx.SetTrunkTransactionHash(tipselection.GetRandomTip())
+	tx.SetTrunkTransactionHash(tipselection.GetRandomTip(tx.GetBranchTransactionHash()))
 	tx.SetTimestamp(uint(time.Now().Unix()))
 	if err := tx.DoProofOfWork(meta_transaction.MIN_WEIGHT_MAGNITUDE); err != nil {
 		log.Warnf("PoW failed: %s", err)
diff --git a/plugins/webapi/gtta/plugin.go b/plugins/webapi/gtta/plugin.go
index 61217f8e6bde9b1c90cd48e3e51d0d5c6497a6f3..54444bce56bcbb2a7df88dc54816433f5871e762 100644
--- a/plugins/webapi/gtta/plugin.go
+++ b/plugins/webapi/gtta/plugin.go
@@ -17,7 +17,7 @@ var PLUGIN = node.NewPlugin("WebAPI GTTA Endpoint", node.Disabled, func(plugin *
 func Handler(c echo.Context) error {
 
 	branchTransactionHash := tipselection.GetRandomTip()
-	trunkTransactionHash := tipselection.GetRandomTip()
+	trunkTransactionHash := tipselection.GetRandomTip(branchTransactionHash)
 
 	return c.JSON(http.StatusOK, Response{
 		BranchTransaction: branchTransactionHash,