diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml
index f1160f1cccc88bbb554416c0d7950ac0678c03fb..cd08c1e7c9f756728e30525aea4b0a67c0d0f6b0 100644
--- a/.github/workflows/integration-tests.yml
+++ b/.github/workflows/integration-tests.yml
@@ -143,3 +143,38 @@ jobs:
         with:
           name: ${{ env.TEST_NAME }}
           path: tools/integration-tests/logs
+
+
+  value:
+    name: value
+    env:
+      TEST_NAME: value
+    runs-on: ubuntu-latest
+    steps:
+
+      - name: Check out code
+        uses: actions/checkout@v2
+
+      - name: Build GoShimmer image
+        run: docker build -t iotaledger/goshimmer .
+
+      - name: Pull additional Docker images
+        run: |
+          docker pull angelocapossele/drand:latest
+          docker pull gaiaadm/pumba:latest
+          docker pull gaiadocker/iproute2:latest
+
+      - name: Run integration tests
+        run: docker-compose -f tools/integration-tests/tester/docker-compose.yml up --abort-on-container-exit --exit-code-from tester --build
+
+      - name: Create logs from tester
+        if: always()
+        run: |
+          docker logs tester &> tools/integration-tests/logs/tester.log
+
+      - name: Save logs as artifacts
+        if: always()
+        uses: actions/upload-artifact@v1
+        with:
+          name: ${{ env.TEST_NAME }}
+          path: tools/integration-tests/logs
diff --git a/Dockerfile b/Dockerfile
index 0df4a2947139217273586781e007f62c9a9cb0a8..ea398c08f8f67d1d4564ecf03bb3d59133627a9c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,7 +2,7 @@
 # Build
 ############################
 # golang:1.14.0-buster
-FROM golang@sha256:fc7e7c9c4b0f6d2d5e8611ee73b9d1d3132750108878517bbf988aa772359ae4 AS build
+FROM golang:1.14.4@sha256:afc8698cebd731cc3f98923ceb697093d378b9de7bd2e9365cb675f863660eea AS build
 
 # Ensure ca-certficates are up to date
 RUN update-ca-certificates
diff --git a/dapps/valuetransfers/dapp.go b/dapps/valuetransfers/dapp.go
index 7964e47159906f81943a9410a9af87b603daffc8..e929205d15da32a27021d5a2656ef750e6cf0617 100644
--- a/dapps/valuetransfers/dapp.go
+++ b/dapps/valuetransfers/dapp.go
@@ -1,6 +1,9 @@
 package valuetransfers
 
 import (
+	"github.com/iotaledger/goshimmer/plugins/config"
+	flag "github.com/spf13/pflag"
+	"os"
 	"sync"
 	"time"
 
@@ -25,10 +28,21 @@ const (
 	// PluginName contains the human readable name of the plugin.
 	PluginName = "ValueTransfers"
 
-	// AverageNetworkDelay contains the average time it takes for a network to propagate through gossip.
-	AverageNetworkDelay = 5 * time.Second
+	// DefaultAverageNetworkDelay contains the default average time it takes for a network to propagate through gossip.
+	DefaultAverageNetworkDelay = 5 * time.Second
+
+	// CfgValueLayerSnapshotFile is the path to the snapshot file.
+	CfgValueLayerSnapshotFile = "valueLayer.snapshot.file"
+
+	// CfgValueLayerFCOBAverageNetworkDelay is the avg. network delay to use for FCoB rules
+	CfgValueLayerFCOBAverageNetworkDelay = "valueLayer.fcob.averageNetworkDelay"
 )
 
+func init() {
+	flag.String(CfgValueLayerSnapshotFile, "", "the path to the snapshot file")
+	flag.Int(CfgValueLayerFCOBAverageNetworkDelay, 5, "the avg. network delay to use for FCoB rules")
+}
+
 var (
 	// App is the "plugin" instance of the value-transfers application.
 	App = node.NewPlugin(PluginName, node.Enabled, configure, run)
@@ -58,6 +72,22 @@ func configure(_ *node.Plugin) {
 
 	// configure Tangle
 	Tangle = tangle.New(database.Store())
+
+	// read snapshot file
+	snapshotFilePath := config.Node.GetString(CfgValueLayerSnapshotFile)
+	if len(snapshotFilePath) != 0 {
+		snapshot := tangle.Snapshot{}
+		f, err := os.Open(snapshotFilePath)
+		if err != nil {
+			log.Panic("can not open snapshot file:", err)
+		}
+		if _, err := snapshot.ReadFrom(f); err != nil {
+			log.Panic("could not read snapshot file:", err)
+		}
+		Tangle.LoadSnapshot(snapshot)
+		log.Info("read snapshot from %s", snapshotFilePath)
+	}
+
 	Tangle.Events.Error.Attach(events.NewClosure(func(err error) {
 		log.Error(err)
 	}))
@@ -76,7 +106,9 @@ func configure(_ *node.Plugin) {
 	}))
 
 	// configure FCOB consensus rules
-	FCOB = consensus.NewFCOB(Tangle, AverageNetworkDelay)
+	cfgAvgNetworkDelay := config.Node.GetInt(CfgValueLayerFCOBAverageNetworkDelay)
+	log.Infof("avg. network delay configured to %d seconds", cfgAvgNetworkDelay)
+	FCOB = consensus.NewFCOB(Tangle, time.Duration(cfgAvgNetworkDelay)*time.Second)
 	FCOB.Events.Vote.Attach(events.NewClosure(func(id string, initOpn vote.Opinion) {
 		if err := voter.Vote(id, initOpn); err != nil {
 			log.Error(err)
diff --git a/dapps/valuetransfers/packages/balance/balance.go b/dapps/valuetransfers/packages/balance/balance.go
index 142b95f595219b08f45b2171dc2932d4076caf57..6559145b2b172898e42831a9b31e9bda99520804 100644
--- a/dapps/valuetransfers/packages/balance/balance.go
+++ b/dapps/valuetransfers/packages/balance/balance.go
@@ -8,15 +8,17 @@ import (
 
 // Balance represents a balance in the IOTA ledger. It consists out of a numeric value and a color.
 type Balance struct {
-	value int64
-	color Color
+	// The numeric value of the balance.
+	Value int64 `json:"value"`
+	// The color of the balance.
+	Color Color `json:"color"`
 }
 
 // New creates a new Balance with the given details.
 func New(color Color, balance int64) (result *Balance) {
 	result = &Balance{
-		color: color,
-		value: balance,
+		Color: color,
+		Value: balance,
 	}
 
 	return
@@ -28,7 +30,7 @@ func FromBytes(bytes []byte) (result *Balance, consumedBytes int, err error) {
 
 	marshalUtil := marshalutil.New(bytes)
 
-	result.value, err = marshalUtil.ReadInt64()
+	result.Value, err = marshalUtil.ReadInt64()
 	if err != nil {
 		return
 	}
@@ -40,7 +42,7 @@ func FromBytes(bytes []byte) (result *Balance, consumedBytes int, err error) {
 		return nil, marshalUtil.ReadOffset(), colorErr
 	}
 
-	result.color = coinColor.(Color)
+	result.Color = coinColor.(Color)
 	consumedBytes = marshalUtil.ReadOffset()
 
 	return
@@ -56,29 +58,19 @@ func Parse(marshalUtil *marshalutil.MarshalUtil) (*Balance, error) {
 	return address.(*Balance), nil
 }
 
-// Value returns the numeric value of the balance.
-func (balance *Balance) Value() int64 {
-	return balance.value
-}
-
-// Color returns the Color of the balance.
-func (balance *Balance) Color() Color {
-	return balance.color
-}
-
 // Bytes marshals the Balance into a sequence of bytes.
 func (balance *Balance) Bytes() []byte {
 	marshalUtil := marshalutil.New(Length)
 
-	marshalUtil.WriteInt64(balance.value)
-	marshalUtil.WriteBytes(balance.color.Bytes())
+	marshalUtil.WriteInt64(balance.Value)
+	marshalUtil.WriteBytes(balance.Color.Bytes())
 
 	return marshalUtil.Bytes()
 }
 
 // String creates a human readable string of the Balance.
 func (balance *Balance) String() string {
-	return strconv.FormatInt(balance.value, 10) + " " + balance.color.String()
+	return strconv.FormatInt(balance.Value, 10) + " " + balance.Color.String()
 }
 
 // Length encodes the length of a marshaled Balance (the length of the color + 8 bytes for the balance).
diff --git a/dapps/valuetransfers/packages/balance/balance_test.go b/dapps/valuetransfers/packages/balance/balance_test.go
index 5dd98567cf634ec3ad3bd8ede4a42ed6d0ab4932..35401c66a5eb5bed4ace5281f4ff1de1e289feb7 100644
--- a/dapps/valuetransfers/packages/balance/balance_test.go
+++ b/dapps/valuetransfers/packages/balance/balance_test.go
@@ -8,8 +8,8 @@ import (
 
 func TestMarshalUnmarshal(t *testing.T) {
 	balance := New(ColorIOTA, 1337)
-	assert.Equal(t, int64(1337), balance.Value())
-	assert.Equal(t, ColorIOTA, balance.Color())
+	assert.Equal(t, int64(1337), balance.Value)
+	assert.Equal(t, ColorIOTA, balance.Color)
 
 	marshaledBalance := balance.Bytes()
 	assert.Equal(t, Length, len(marshaledBalance))
@@ -19,6 +19,6 @@ func TestMarshalUnmarshal(t *testing.T) {
 		panic(err)
 	}
 	assert.Equal(t, Length, consumedBytes)
-	assert.Equal(t, balance.value, restoredBalance.Value())
-	assert.Equal(t, balance.color, restoredBalance.Color())
+	assert.Equal(t, balance.Value, restoredBalance.Value)
+	assert.Equal(t, balance.Color, restoredBalance.Color)
 }
diff --git a/dapps/valuetransfers/packages/consensus/fcob.go b/dapps/valuetransfers/packages/consensus/fcob.go
index d5b86548b57dc8c2eeb850a6ab5d7aa6cd564e44..1d2a98aa55f4a4299b015386b24a7bdf1f33e7fb 100644
--- a/dapps/valuetransfers/packages/consensus/fcob.go
+++ b/dapps/valuetransfers/packages/consensus/fcob.go
@@ -3,6 +3,7 @@ package consensus
 import (
 	"time"
 
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager"
 	"github.com/iotaledger/hive.go/events"
 
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tangle"
@@ -138,9 +139,10 @@ func (fcob *FCOB) setFinalized(cachedTransactionMetadata *tangle.CachedTransacti
 
 // onFork triggers a voting process whenever a Transaction gets forked into a new Branch. The initial opinion is derived
 // from the preferred flag that was set using the FCOB rule.
-func (fcob *FCOB) onFork(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *tangle.CachedTransactionMetadata) {
+func (fcob *FCOB) onFork(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *tangle.CachedTransactionMetadata, cachedTargetBranch *branchmanager.CachedBranch, conflictingInputs []transaction.OutputID) {
 	defer cachedTransaction.Release()
 	defer cachedTransactionMetadata.Release()
+	defer cachedTargetBranch.Release()
 
 	transactionMetadata := cachedTransactionMetadata.Unwrap()
 	if transactionMetadata == nil {
diff --git a/dapps/valuetransfers/packages/tangle/ledgerstate.go b/dapps/valuetransfers/packages/tangle/ledgerstate.go
index 5c0d8c31a7b7bd0c93dcb01aaa29c35719806c22..9b676acfedfebbf7a417bfc8d63c0bf24bdc8991 100644
--- a/dapps/valuetransfers/packages/tangle/ledgerstate.go
+++ b/dapps/valuetransfers/packages/tangle/ledgerstate.go
@@ -25,7 +25,7 @@ func (ledgerState *LedgerState) Balances(address address.Address) (coloredBalanc
 	ledgerState.tangle.OutputsOnAddress(address).Consume(func(output *Output) {
 		if output.ConsumerCount() == 0 {
 			for _, coloredBalance := range output.Balances() {
-				coloredBalances[coloredBalance.Color()] += coloredBalance.Value()
+				coloredBalances[coloredBalance.Color] += coloredBalance.Value
 			}
 		}
 	})
diff --git a/dapps/valuetransfers/packages/tangle/snapshot.go b/dapps/valuetransfers/packages/tangle/snapshot.go
new file mode 100644
index 0000000000000000000000000000000000000000..b13df4d81409167068c882c8c893767c030cfcaa
--- /dev/null
+++ b/dapps/valuetransfers/packages/tangle/snapshot.go
@@ -0,0 +1,125 @@
+package tangle
+
+import (
+	"encoding/binary"
+	"fmt"
+	"io"
+
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
+)
+
+// Snapshot defines a snapshot of the ledger state.
+type Snapshot map[transaction.ID]map[address.Address][]*balance.Balance
+
+// WriteTo writes the snapshot data to the given writer in the following format:
+// 	transaction_count(int64)
+//	-> transaction_count * transaction_id(32byte)
+//		->address_count(int64)
+//			->address_count * address(33byte)
+//				->balance_count(int64)
+//					->balance_count * value(int64)+color(32byte)
+func (s Snapshot) WriteTo(writer io.Writer) (int64, error) {
+	var bytesWritten int64
+	transactionCount := len(s)
+	if err := binary.Write(writer, binary.LittleEndian, int64(transactionCount)); err != nil {
+		return 0, fmt.Errorf("unable to write transactions count: %w", err)
+	}
+	bytesWritten += 8
+	for txID, addresses := range s {
+		if err := binary.Write(writer, binary.LittleEndian, txID); err != nil {
+			return bytesWritten, fmt.Errorf("unable to write transaction ID: %w", err)
+		}
+		bytesWritten += transaction.IDLength
+		if err := binary.Write(writer, binary.LittleEndian, int64(len(addresses))); err != nil {
+			return bytesWritten, fmt.Errorf("unable to write address count: %w", err)
+		}
+		bytesWritten += 8
+		for addr, balances := range addresses {
+			if err := binary.Write(writer, binary.LittleEndian, addr); err != nil {
+				return bytesWritten, fmt.Errorf("unable to write address: %w", err)
+			}
+			bytesWritten += address.Length
+			if err := binary.Write(writer, binary.LittleEndian, int64(len(balances))); err != nil {
+				return bytesWritten, fmt.Errorf("unable to write balance count: %w", err)
+			}
+			bytesWritten += 8
+			for _, bal := range balances {
+				if err := binary.Write(writer, binary.LittleEndian, bal.Value); err != nil {
+					return bytesWritten, fmt.Errorf("unable to write balance value: %w", err)
+				}
+				bytesWritten += 8
+				if err := binary.Write(writer, binary.LittleEndian, bal.Color); err != nil {
+					return bytesWritten, fmt.Errorf("unable to write balance color: %w", err)
+				}
+				bytesWritten += balance.ColorLength
+			}
+		}
+	}
+
+	return bytesWritten, nil
+}
+
+// ReadFrom reads the snapshot bytes from the given reader.
+// This function overrides existing content of the snapshot.
+func (s Snapshot) ReadFrom(reader io.Reader) (int64, error) {
+	var bytesRead int64
+	var transactionCount int64
+	if err := binary.Read(reader, binary.LittleEndian, &transactionCount); err != nil {
+		return 0, fmt.Errorf("unable to read transaction count: %w", err)
+	}
+	bytesRead += 8
+
+	var i int64
+	for ; i < transactionCount; i++ {
+		txIDBytes := make([]byte, transaction.IDLength)
+		if err := binary.Read(reader, binary.LittleEndian, txIDBytes); err != nil {
+			return bytesRead, fmt.Errorf("unable to read transaction ID: %w", err)
+		}
+		bytesRead += transaction.IDLength
+		var addrCount int64
+		if err := binary.Read(reader, binary.LittleEndian, &addrCount); err != nil {
+			return bytesRead, fmt.Errorf("unable to read address count: %w", err)
+		}
+		bytesRead += 8
+		txAddrMap := make(map[address.Address][]*balance.Balance, addrCount)
+		var j int64
+		for ; j < addrCount; j++ {
+			addrBytes := make([]byte, address.Length)
+			if err := binary.Read(reader, binary.LittleEndian, addrBytes); err != nil {
+				return bytesRead, fmt.Errorf("unable to read address: %w", err)
+			}
+			bytesRead += address.Length
+			var balanceCount int64
+			if err := binary.Read(reader, binary.LittleEndian, &balanceCount); err != nil {
+				return bytesRead, fmt.Errorf("unable to read balance count: %w", err)
+			}
+			bytesRead += 8
+
+			balances := make([]*balance.Balance, balanceCount)
+			var k int64
+			for ; k < balanceCount; k++ {
+				var value int64
+				if err := binary.Read(reader, binary.LittleEndian, &value); err != nil {
+					return bytesRead, fmt.Errorf("unable to read balance value: %w", err)
+				}
+				bytesRead += 8
+				color := balance.Color{}
+				if err := binary.Read(reader, binary.LittleEndian, &color); err != nil {
+					return bytesRead, fmt.Errorf("unable to read balance color: %w", err)
+				}
+				bytesRead += balance.ColorLength
+				balances[k] = &balance.Balance{Value: value, Color: color}
+			}
+			addr := address.Address{}
+			copy(addr[:], addrBytes)
+			txAddrMap[addr] = balances
+		}
+		txID := transaction.ID{}
+		copy(txID[:], txIDBytes)
+		s[txID] = txAddrMap
+	}
+
+	return bytesRead, nil
+}
diff --git a/dapps/valuetransfers/packages/tangle/snapshot_test.go b/dapps/valuetransfers/packages/tangle/snapshot_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7ef8b153864d90fdfebd5034ec22ab676f87f6bb
--- /dev/null
+++ b/dapps/valuetransfers/packages/tangle/snapshot_test.go
@@ -0,0 +1,88 @@
+package tangle
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/branchmanager"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet"
+	"github.com/iotaledger/hive.go/kvstore/mapdb"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestLoadSnapshot(t *testing.T) {
+	tangle := New(mapdb.NewMapDB())
+
+	snapshot := map[transaction.ID]map[address.Address][]*balance.Balance{
+		transaction.GenesisID: {
+			address.Random(): []*balance.Balance{
+				balance.New(balance.ColorIOTA, 337),
+			},
+
+			address.Random(): []*balance.Balance{
+				balance.New(balance.ColorIOTA, 1000),
+				balance.New(balance.ColorIOTA, 1000),
+			},
+		},
+	}
+	tangle.LoadSnapshot(snapshot)
+
+	// check whether outputs can be retrieved from tangle
+	for addr, balances := range snapshot[transaction.GenesisID] {
+		cachedOutput := tangle.TransactionOutput(transaction.NewOutputID(addr, transaction.GenesisID))
+		cachedOutput.Consume(func(output *Output) {
+			assert.Equal(t, addr, output.Address())
+			assert.ElementsMatch(t, balances, output.Balances())
+			assert.True(t, output.Solid())
+			assert.Equal(t, branchmanager.MasterBranchID, output.BranchID())
+		})
+	}
+}
+
+func TestSnapshotMarshalUnmarshal(t *testing.T) {
+	const genesisBalance = 1000000000
+	seed := wallet.NewSeed()
+	genesisAddr := seed.Address(GENESIS)
+
+	snapshot := Snapshot{
+		transaction.GenesisID: {
+			genesisAddr: {
+				balance.New(balance.ColorIOTA, genesisBalance),
+			},
+		},
+	}
+
+	// includes txs count
+	const int64ByteSize = 8
+	expectedLength := int64ByteSize
+	for _, addresses := range snapshot {
+		// tx id
+		expectedLength += transaction.IDLength
+		// addr count
+		expectedLength += int64ByteSize
+		for _, balances := range addresses {
+			// addr
+			expectedLength += address.Length
+			// balance count
+			expectedLength += int64ByteSize
+			// balances
+			expectedLength += len(balances) * (int64ByteSize + balance.ColorLength)
+		}
+	}
+
+	var buf bytes.Buffer
+	written, err := snapshot.WriteTo(&buf)
+	assert.NoError(t, err, "writing the snapshot to the buffer should succeed")
+	assert.EqualValues(t, expectedLength, written, "written byte count should match the expected count")
+
+	snapshotFromBytes := Snapshot{}
+	read, err := snapshotFromBytes.ReadFrom(&buf)
+	assert.NoError(t, err, "expected no error from reading valid snapshot bytes")
+	assert.EqualValues(t, expectedLength, read, "read byte count should match the expected count")
+
+	// check that the source and unmarshaled snapshot are equivalent
+	assert.Equal(t, snapshot, snapshotFromBytes)
+}
diff --git a/dapps/valuetransfers/packages/tangle/tangle.go b/dapps/valuetransfers/packages/tangle/tangle.go
index fd1d4e377d036985f9c2c396c15eae176ce3120a..cdd0c16c6206af4b113d4874b5634ae4a966be7e 100644
--- a/dapps/valuetransfers/packages/tangle/tangle.go
+++ b/dapps/valuetransfers/packages/tangle/tangle.go
@@ -1309,8 +1309,8 @@ func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTrans
 		// create correctly colored balances (replacing color of newly minted coins with color of transaction id)
 		coloredBalances := make([]*balance.Balance, len(balances))
 		for i, currentBalance := range balances {
-			if currentBalance.Color() == balance.ColorNew {
-				coloredBalances[i] = balance.New(mintedColor, currentBalance.Value())
+			if currentBalance.Color == balance.ColorNew {
+				coloredBalances[i] = balance.New(mintedColor, currentBalance.Value)
 			} else {
 				coloredBalances[i] = currentBalance
 			}
@@ -1527,9 +1527,9 @@ func (tangle *Tangle) retrieveConsumedInputDetails(tx *transaction.Transaction)
 		// calculate the input balances
 		for _, inputBalance := range input.Balances() {
 			var newBalance int64
-			if currentBalance, balanceExists := consumedBalances[inputBalance.Color()]; balanceExists {
+			if currentBalance, balanceExists := consumedBalances[inputBalance.Color]; balanceExists {
 				// check overflows in the numbers
-				if inputBalance.Value() > math.MaxInt64-currentBalance {
+				if inputBalance.Value > math.MaxInt64-currentBalance {
 					// TODO: make it an explicit error var
 					err = fmt.Errorf("buffer overflow in balances of inputs")
 
@@ -1538,11 +1538,11 @@ func (tangle *Tangle) retrieveConsumedInputDetails(tx *transaction.Transaction)
 					return
 				}
 
-				newBalance = currentBalance + inputBalance.Value()
+				newBalance = currentBalance + inputBalance.Value
 			} else {
-				newBalance = inputBalance.Value()
+				newBalance = inputBalance.Value
 			}
-			consumedBalances[inputBalance.Color()] = newBalance
+			consumedBalances[inputBalance.Color] = newBalance
 		}
 	}
 	inputsSolid = true
@@ -1563,51 +1563,51 @@ func (tangle *Tangle) checkTransactionOutputs(inputBalances map[balance.Color]in
 	aborted := !outputs.ForEach(func(address address.Address, balances []*balance.Balance) bool {
 		for _, outputBalance := range balances {
 			// abort if the output creates a negative or empty output
-			if outputBalance.Value() <= 0 {
+			if outputBalance.Value <= 0 {
 				return false
 			}
 
 			// sidestep logic if we have a newly colored output (we check the supply later)
-			if outputBalance.Color() == balance.ColorNew {
+			if outputBalance.Color == balance.ColorNew {
 				// catch overflows
-				if newlyColoredCoins > math.MaxInt64-outputBalance.Value() {
+				if newlyColoredCoins > math.MaxInt64-outputBalance.Value {
 					return false
 				}
 
-				newlyColoredCoins += outputBalance.Value()
+				newlyColoredCoins += outputBalance.Value
 
 				continue
 			}
 
 			// sidestep logic if we have ColorIOTA
-			if outputBalance.Color() == balance.ColorIOTA {
+			if outputBalance.Color == balance.ColorIOTA {
 				// catch overflows
-				if uncoloredCoins > math.MaxInt64-outputBalance.Value() {
+				if uncoloredCoins > math.MaxInt64-outputBalance.Value {
 					return false
 				}
 
-				uncoloredCoins += outputBalance.Value()
+				uncoloredCoins += outputBalance.Value
 
 				continue
 			}
 
 			// check if the used color does not exist in our supply
-			availableBalance, spentColorExists := inputBalances[outputBalance.Color()]
+			availableBalance, spentColorExists := inputBalances[outputBalance.Color]
 			if !spentColorExists {
 				return false
 			}
 
 			// abort if we spend more coins of the given color than we have
-			if availableBalance < outputBalance.Value() {
+			if availableBalance < outputBalance.Value {
 				return false
 			}
 
 			// subtract the spent coins from the supply of this color
-			inputBalances[outputBalance.Color()] -= outputBalance.Value()
+			inputBalances[outputBalance.Color] -= outputBalance.Value
 
 			// cleanup empty map entries (we have exhausted our funds)
-			if inputBalances[outputBalance.Color()] == 0 {
-				delete(inputBalances, outputBalance.Color())
+			if inputBalances[outputBalance.Color] == 0 {
+				delete(inputBalances, outputBalance.Color)
 			}
 		}
 
@@ -1907,4 +1907,4 @@ func (stackEntry *valuePayloadPropagationStackEntry) Unwrap() (payload *payload.
 	transactionMetadata = stackEntry.CachedTransactionMetadata.Unwrap()
 
 	return
-}
+}
\ No newline at end of file
diff --git a/dapps/valuetransfers/packages/tangle/tangle_test.go b/dapps/valuetransfers/packages/tangle/tangle_test.go
index dab6589cc4d360d0fa88d0651f74a08000749a05..598a25c473cb68698f061a2cc90bb2e2018c2f56 100644
--- a/dapps/valuetransfers/packages/tangle/tangle_test.go
+++ b/dapps/valuetransfers/packages/tangle/tangle_test.go
@@ -865,35 +865,6 @@ func TestGetCachedOutputsFromTransactionInputs(t *testing.T) {
 	}
 }
 
-func TestLoadSnapshot(t *testing.T) {
-	tangle := New(mapdb.NewMapDB())
-
-	snapshot := map[transaction.ID]map[address.Address][]*balance.Balance{
-		transaction.GenesisID: {
-			address.Random(): []*balance.Balance{
-				balance.New(balance.ColorIOTA, 337),
-			},
-
-			address.Random(): []*balance.Balance{
-				balance.New(balance.ColorIOTA, 1000),
-				balance.New(balance.ColorIOTA, 1000),
-			},
-		},
-	}
-	tangle.LoadSnapshot(snapshot)
-
-	// check whether outputs can be retrieved from tangle
-	for addr, balances := range snapshot[transaction.GenesisID] {
-		cachedOutput := tangle.TransactionOutput(transaction.NewOutputID(addr, transaction.GenesisID))
-		cachedOutput.Consume(func(output *Output) {
-			assert.Equal(t, addr, output.Address())
-			assert.ElementsMatch(t, balances, output.Balances())
-			assert.True(t, output.Solid())
-			assert.Equal(t, branchmanager.MasterBranchID, output.BranchID())
-		})
-	}
-}
-
 func TestRetrieveConsumedInputDetails(t *testing.T) {
 	// test simple happy case
 	{
@@ -1525,7 +1496,7 @@ func sumOutputsByColor(outputs map[address.Address][]*balance.Balance) map[balan
 
 	for _, balances := range outputs {
 		for _, bal := range balances {
-			totals[bal.Color()] += bal.Value()
+			totals[bal.Color] += bal.Value
 		}
 	}
 
@@ -1547,4 +1518,4 @@ func createDummyTransaction() *transaction.Transaction {
 			},
 		}),
 	)
-}
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 5c30e344e36a413a767041c4717b3deeb2c7b0cb..07ea533ab1327f151744785a703ab5bac400eb90 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
 	github.com/gin-gonic/gin v1.6.3
 	github.com/gobuffalo/packr/v2 v2.7.1
 	github.com/golang/protobuf v1.3.5
-	github.com/google/go-cmp v0.4.0
+	github.com/google/go-cmp v0.4.1
 	github.com/gorilla/websocket v1.4.1
 	github.com/iotaledger/hive.go v0.0.0-20200610104211-d603429af242
 	github.com/iotaledger/iota.go v1.0.0-beta.14
@@ -24,7 +24,7 @@ require (
 	github.com/prometheus/client_golang v1.5.1
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.6.2
-	github.com/stretchr/testify v1.5.1
+	github.com/stretchr/testify v1.6.1
 	github.com/valyala/fasttemplate v1.1.0 // indirect
 	go.dedis.ch/kyber/v3 v3.0.12
 	go.mongodb.org/mongo-driver v1.0.0
diff --git a/go.sum b/go.sum
index df56ca18c2bbb77b90e0e96ed7de352fc3d212ea..1b9f1a3f7b9ef9aa472c784ac14d6645fb5bdab3 100644
--- a/go.sum
+++ b/go.sum
@@ -132,6 +132,8 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
 github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
@@ -316,6 +318,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
@@ -519,6 +523,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
diff --git a/pluginmgr/core/plugins.go b/pluginmgr/core/plugins.go
index 1eba3f117348d559b68e2f677c8789f23f58a082..eb4ff3b619c6ea1cd48a61d4325d54ddf5dd7241 100644
--- a/pluginmgr/core/plugins.go
+++ b/pluginmgr/core/plugins.go
@@ -18,6 +18,7 @@ import (
 	"github.com/iotaledger/goshimmer/plugins/portcheck"
 	"github.com/iotaledger/goshimmer/plugins/profiling"
 	"github.com/iotaledger/goshimmer/plugins/sync"
+	"github.com/iotaledger/goshimmer/plugins/testsnapshots"
 
 	"github.com/iotaledger/hive.go/node"
 )
@@ -40,4 +41,5 @@ var PLUGINS = node.Plugins(
 	metrics.Plugin,
 	drng.Plugin,
 	valuetransfers.App,
+	testsnapshots.Plugin,
 )
diff --git a/plugins/autopeering/parameters.go b/plugins/autopeering/parameters.go
index 01022ef352e2ae927b2841f399dd20c8a366607f..0cd2088fb04e0ace1c5dd58df51241a8a252c512 100644
--- a/plugins/autopeering/parameters.go
+++ b/plugins/autopeering/parameters.go
@@ -7,8 +7,12 @@ import (
 const (
 	// CfgEntryNodes defines the config flag of the entry nodes.
 	CfgEntryNodes = "autopeering.entryNodes"
+
+	// CfgOutboundUpdateIntervalMs time after which out neighbors are updated.
+	CfgOutboundUpdateIntervalMs = "autopeering.outboundUpdateIntervalMs"
 )
 
 func init() {
 	flag.StringSlice(CfgEntryNodes, []string{"V8LYtWWcPYYDTTXLeIEFjJEuWlsjDiI0+Pq/Cx9ai6g=@116.202.49.178:14626"}, "list of trusted entry nodes for auto peering")
+	flag.Int(CfgOutboundUpdateIntervalMs, 10, "time after which out neighbors are updated")
 }
diff --git a/plugins/config/plugin.go b/plugins/config/plugin.go
index ab43e6054cd4bfaff9aa9ceb373f80e75644b234..da882233e400356dfeffae261fb0e500f37453a4 100644
--- a/plugins/config/plugin.go
+++ b/plugins/config/plugin.go
@@ -3,6 +3,7 @@ package config
 import (
 	"fmt"
 	"os"
+	"strings"
 
 	"github.com/iotaledger/hive.go/events"
 	"github.com/iotaledger/hive.go/node"
@@ -56,6 +57,12 @@ func init() {
 // It automatically reads in a single config file starting with "config" (can be changed via the --config CLI flag)
 // and ending with: .json, .toml, .yaml or .yml (in this sequence).
 func fetch(printConfig bool, ignoreSettingsAtPrint ...[]string) error {
+	// replace dots with underscores in env
+	dotReplacer := strings.NewReplacer(".", "_")
+	Node.SetEnvKeyReplacer(dotReplacer)
+	// read in ENV variables
+	Node.AutomaticEnv()
+
 	flag.Parse()
 	err := parameter.LoadConfigFile(Node, *configDirPath, *configName, true, *skipConfigAvailable)
 	if err != nil {
diff --git a/plugins/dashboard/payload_handler.go b/plugins/dashboard/payload_handler.go
index db8ab642269e0f7db2f9161de2a62dbf4d2bb23b..6ffa3593a9927bdeb97c61f3de2d74d05c3d76b4 100644
--- a/plugins/dashboard/payload_handler.go
+++ b/plugins/dashboard/payload_handler.go
@@ -135,8 +135,8 @@ func processValuePayload(p payload.Payload) (vp ValuePayload) {
 		var b []Balance
 		for _, balance := range balances {
 			b = append(b, Balance{
-				Value: balance.Value(),
-				Color: balance.Color().String(),
+				Value: balance.Value,
+				Color: balance.Color.String(),
 			})
 		}
 		t := OutputContent{
diff --git a/plugins/testsnapshots/plugin.go b/plugins/testsnapshots/plugin.go
new file mode 100644
index 0000000000000000000000000000000000000000..2e47c4c02938840a68a6d401ab6b745a1f97adb7
--- /dev/null
+++ b/plugins/testsnapshots/plugin.go
@@ -0,0 +1,41 @@
+package testsnapshots
+
+import (
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tangle"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
+	"github.com/iotaledger/hive.go/logger"
+	"github.com/iotaledger/hive.go/node"
+)
+
+const (
+	// PluginName is the plugin name of the bootstrap plugin.
+	PluginName = "TestSnapshots"
+)
+
+var (
+	// Plugin is the plugin instance of the bootstrap plugin.
+	Plugin = node.NewPlugin(PluginName, node.Disabled, configure, run)
+	log    *logger.Logger
+
+	// addresses for snapshots
+	address0, _ = address.FromBase58("JaMauTaTSVBNc13edCCvBK9fZxZ1KKW5fXegT1B7N9jY")
+)
+
+func configure(_ *node.Plugin) {
+	log = logger.NewLogger(PluginName)
+
+	valuetransfers.Tangle.LoadSnapshot(tangle.Snapshot{
+		transaction.GenesisID: {
+			address0: []*balance.Balance{
+				balance.New(balance.ColorIOTA, 10000000),
+			},
+		},
+	})
+
+	log.Infof("load snapshots to tangle")
+}
+
+func run(_ *node.Plugin) {}
diff --git a/plugins/webapi/value/gettransactionbyid/handler.go b/plugins/webapi/value/gettransactionbyid/handler.go
index df4791aba9deab3ff23bd74334a2f173d5d3f7da..b4bee704b621f56a4e5afdb2b34cc93dc40cc4dd 100644
--- a/plugins/webapi/value/gettransactionbyid/handler.go
+++ b/plugins/webapi/value/gettransactionbyid/handler.go
@@ -7,40 +7,40 @@ import (
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
 	"github.com/iotaledger/goshimmer/plugins/webapi/value/utils"
 	"github.com/labstack/echo"
-	"github.com/labstack/gommon/log"
 )
 
 // Handler gets the transaction by id.
 func Handler(c echo.Context) error {
 	txnID, err := transaction.IDFromBase58(c.QueryParam("txnID"))
 	if err != nil {
-		log.Info(err)
 		return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
 	}
 
 	// get txn by txn id
-	txnObj := valuetransfers.Tangle.Transaction(txnID)
-	defer txnObj.Release()
-	if !txnObj.Exists() {
+	cachedTxnMetaObj := valuetransfers.Tangle.TransactionMetadata(txnID)
+	defer cachedTxnMetaObj.Release()
+	if !cachedTxnMetaObj.Exists() {
 		return c.JSON(http.StatusNotFound, Response{Error: "Transaction not found"})
 	}
-	txn := utils.ParseTransaction(txnObj.Unwrap())
-
-	// get txn metadata
-	txnMetadataObj := valuetransfers.Tangle.TransactionMetadata(txnID)
-	defer txnMetadataObj.Release()
-	if !txnMetadataObj.Exists() {
-		return c.JSON(http.StatusNotFound, Response{Error: "Transaction Metadata not found"})
+	cachedTxnObj := valuetransfers.Tangle.Transaction(txnID)
+	defer cachedTxnObj.Release()
+	if !cachedTxnObj.Exists() {
+		return c.JSON(http.StatusNotFound, Response{Error: "Transaction not found"})
 	}
-	txnMetadata := txnMetadataObj.Unwrap()
+	txn := utils.ParseTransaction(cachedTxnObj.Unwrap())
 
+	txnMeta := cachedTxnMetaObj.Unwrap()
+	txnMeta.Preferred()
 	return c.JSON(http.StatusOK, Response{
 		Transaction: txn,
 		InclusionState: utils.InclusionState{
-			Solid:     txnMetadata.Solid(),
-			Confirmed: txnMetadata.Confirmed(),
-			Rejected:  txnMetadata.Rejected(),
-			Liked:     txnMetadata.Liked(),
+			Confirmed:   txnMeta.Confirmed(),
+			Conflicting: txnMeta.Conflicting(),
+			Liked:       txnMeta.Liked(),
+			Solid:       txnMeta.Solid(),
+			Rejected:    txnMeta.Rejected(),
+			Finalized:   txnMeta.Finalized(),
+			Preferred:   txnMeta.Preferred(),
 		},
 	})
 }
diff --git a/plugins/webapi/value/sendtransaction/handler.go b/plugins/webapi/value/sendtransaction/handler.go
index b1dfa63d99ed7f3b85b4ac1432f4e9e4ab1ecc61..8a65571533c113d60d7a74a9e7108acaa4e34d65 100644
--- a/plugins/webapi/value/sendtransaction/handler.go
+++ b/plugins/webapi/value/sendtransaction/handler.go
@@ -7,21 +7,18 @@ import (
 	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
 	"github.com/iotaledger/goshimmer/plugins/issuer"
 	"github.com/labstack/echo"
-	"github.com/labstack/gommon/log"
 )
 
 // Handler sends a transaction.
 func Handler(c echo.Context) error {
 	var request Request
 	if err := c.Bind(&request); err != nil {
-		log.Info(err.Error())
 		return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
 	}
 
 	// prepare transaction
 	tx, _, err := transaction.FromBytes(request.TransactionBytes)
 	if err != nil {
-		log.Info(err.Error())
 		return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
 	}
 
@@ -29,7 +26,6 @@ func Handler(c echo.Context) error {
 	payload := valuetransfers.ValueObjectFactory().IssueTransaction(tx)
 	_, err = issuer.IssuePayload(payload)
 	if err != nil {
-		log.Info(err.Error())
 		return c.JSON(http.StatusBadRequest, Response{Error: err.Error()})
 	}
 
diff --git a/plugins/webapi/value/unspentoutputs/handler.go b/plugins/webapi/value/unspentoutputs/handler.go
index 738c278ff426654e75f9571c8df2a5e18fe0860c..05be4eb55ba1d901e0f3544cdec998fcef66c9a8 100644
--- a/plugins/webapi/value/unspentoutputs/handler.go
+++ b/plugins/webapi/value/unspentoutputs/handler.go
@@ -28,29 +28,38 @@ func Handler(c echo.Context) error {
 
 		outputids := make([]OutputID, 0)
 		// get outputids by address
-		for id, outputObj := range valuetransfers.Tangle.OutputsOnAddress(address) {
-			defer outputObj.Release()
-			output := outputObj.Unwrap()
+		for id, cachedOutput := range valuetransfers.Tangle.OutputsOnAddress(address) {
+			// TODO: don't do this in a for
+			defer cachedOutput.Release()
+			output := cachedOutput.Unwrap()
+			cachedTxMeta := valuetransfers.Tangle.TransactionMetadata(output.TransactionID())
+			// TODO: don't do this in a for
+			defer cachedTxMeta.Release()
 
 			if output.ConsumerCount() == 0 {
 				// iterate balances
 				var b []utils.Balance
 				for _, balance := range output.Balances() {
 					b = append(b, utils.Balance{
-						Value: balance.Value(),
-						Color: balance.Color().String(),
+						Value: balance.Value,
+						Color: balance.Color.String(),
 					})
 				}
 
+				inclusionState := utils.InclusionState{}
+				if cachedTxMeta.Exists() {
+					txMeta := cachedTxMeta.Unwrap()
+					inclusionState.Confirmed = txMeta.Confirmed()
+					inclusionState.Liked = txMeta.Liked()
+					inclusionState.Rejected = txMeta.Rejected()
+					inclusionState.Finalized = txMeta.Finalized()
+					inclusionState.Conflicting = txMeta.Conflicting()
+					inclusionState.Confirmed = txMeta.Confirmed()
+				}
 				outputids = append(outputids, OutputID{
-					ID:       id.String(),
-					Balances: b,
-					InclusionState: utils.InclusionState{
-						Solid:     output.Solid(),
-						Confirmed: output.Confirmed(),
-						Rejected:  output.Rejected(),
-						Liked:     output.Liked(),
-					},
+					ID:             id.String(),
+					Balances:       b,
+					InclusionState: inclusionState,
 				})
 			}
 		}
diff --git a/plugins/webapi/value/utils/transaction_handler.go b/plugins/webapi/value/utils/transaction_handler.go
index 359446a258072f14091c027ea639d10bb84ef675..9664c52df05f7f9ec2a7c975b9f6fa0ab6cd8fe8 100644
--- a/plugins/webapi/value/utils/transaction_handler.go
+++ b/plugins/webapi/value/utils/transaction_handler.go
@@ -21,8 +21,8 @@ func ParseTransaction(t *transaction.Transaction) (txn Transaction) {
 		var b []Balance
 		for _, balance := range balances {
 			b = append(b, Balance{
-				Value: balance.Value(),
-				Color: balance.Color().String(),
+				Value: balance.Value,
+				Color: balance.Color.String(),
 			})
 		}
 		t := Output{
@@ -64,8 +64,11 @@ type Balance struct {
 
 // InclusionState represents the different states of an OutputID
 type InclusionState struct {
-	Solid     bool `json:"solid,omitempty"`
-	Confirmed bool `json:"confirmed,omitempty"`
-	Rejected  bool `json:"rejected,omitempty"`
-	Liked     bool `json:"liked,omitempty"`
+	Solid       bool `json:"solid,omitempty"`
+	Confirmed   bool `json:"confirmed,omitempty"`
+	Rejected    bool `json:"rejected,omitempty"`
+	Liked       bool `json:"liked,omitempty"`
+	Conflicting bool `json:"conflicting,omitempty"`
+	Finalized   bool `json:"finalized,omitempty"`
+	Preferred   bool `json:"preferred,omitempty"`
 }
diff --git a/tools/docker-network/docker-compose.yml b/tools/docker-network/docker-compose.yml
index e51aa9af1d6faac82e6d7fc939dd985212c22f57..25b9fd0387f2adc489a9a096bc7f19a56905f04a 100644
--- a/tools/docker-network/docker-compose.yml
+++ b/tools/docker-network/docker-compose.yml
@@ -40,10 +40,12 @@ services:
     command: >
       --config-dir=/tmp
       --database.directory=/tmp/mainnetdb
+      --valueLayer.snapshot.file=/tmp/assets/7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih.bin
       --node.enablePlugins=bootstrap
     volumes:
       - ./config.docker.json:/tmp/config.json:ro
       - goshimmer-cache:/go
+      - ../integration-tests/assets:/tmp/assets
     ports:
       - "127.0.0.1:8080:8080/tcp" # web API
       - "127.0.0.1:8081:8081/tcp" # dashboard
@@ -57,10 +59,12 @@ services:
       --config-dir=/tmp
       --database.directory=/tmp/mainnetdb
       --node.enablePlugins=bootstrap
+      --valueLayer.snapshot.file=/tmp/assets/7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih.bin
       --node.disablePlugins=dashboard,portcheck
     volumes:
       - ./config.docker.json:/tmp/config.json:ro
       - goshimmer-cache:/go
+      - ../integration-tests/assets:/tmp/assets
     expose:
       - "8080/tcp" # web API (within Docker network)
     depends_on:
diff --git a/tools/double-spend/double-spend.go b/tools/double-spend/double-spend.go
new file mode 100644
index 0000000000000000000000000000000000000000..ce29625f6bd1e0808ba19b2d0b56c4302785aafe
--- /dev/null
+++ b/tools/double-spend/double-spend.go
@@ -0,0 +1,60 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+	"time"
+
+	"github.com/iotaledger/goshimmer/client"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address/signaturescheme"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet"
+	"github.com/mr-tron/base58"
+)
+
+func main() {
+
+	client := client.NewGoShimmerAPI("http://ressims.iota.cafe:8080", http.Client{Timeout: 30 * time.Second})
+
+	// genesis wallet
+	genesisSeedBytes, err := base58.Decode("7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih")
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	const genesisBalance = 1000000000
+	genesisWallet := wallet.New(genesisSeedBytes)
+	genesisAddr := genesisWallet.Seed().Address(0)
+	genesisOutputID := transaction.NewOutputID(genesisAddr, transaction.GenesisID)
+
+	// issue transactions which spend the same genesis output in all partitions
+	conflictingTxs := make([]*transaction.Transaction, 2)
+	conflictingTxIDs := make([]string, 2)
+	receiverWallets := make([]*wallet.Wallet, 2)
+	for i := range conflictingTxs {
+
+		// create a new receiver wallet for the given conflict
+		receiverWallet := wallet.New()
+		destAddr := receiverWallet.Seed().Address(0)
+		receiverWallets[i] = receiverWallet
+		tx := transaction.New(
+			transaction.NewInputs(genesisOutputID),
+			transaction.NewOutputs(map[address.Address][]*balance.Balance{
+				destAddr: {
+					{Value: genesisBalance, Color: balance.ColorIOTA},
+				},
+			}))
+		tx = tx.Sign(signaturescheme.ED25519(*genesisWallet.Seed().KeyPair(0)))
+		conflictingTxs[i] = tx
+
+		// issue the transaction
+		txID, err := client.SendTransaction(tx.Bytes())
+		if err != nil {
+			fmt.Println(err)
+		}
+		conflictingTxIDs[i] = txID
+		fmt.Printf("issued conflict transaction %s\n", txID)
+	}
+}
diff --git a/tools/integration-tests/assets/7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih.bin b/tools/integration-tests/assets/7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih.bin
new file mode 100644
index 0000000000000000000000000000000000000000..2e55197593a21cb650727fb4a4f9c58e92acbc4d
Binary files /dev/null and b/tools/integration-tests/assets/7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih.bin differ
diff --git a/tools/integration-tests/assets/entrypoint.sh b/tools/integration-tests/assets/entrypoint.sh
new file mode 100755
index 0000000000000000000000000000000000000000..a5ec0407b81c57c162ead53b0494c224f3c76ae7
--- /dev/null
+++ b/tools/integration-tests/assets/entrypoint.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+echo "copying assets into shared volume..."
+rm -rf /assets/*
+cp -rp /tmp/assets/* /assets
+chmod 777 /assets/*
+echo "assets:"
+ls /assets
+echo "running tests..."
+go test ./tests/"${TEST_NAME}" -v -timeout 30m
diff --git a/tools/integration-tests/runTests.sh b/tools/integration-tests/runTests.sh
index 71a65da52f4cf9fb0441d8caeef39e2d578b8c0a..42926b752932817fcc014824a45a490ca1f36d36 100755
--- a/tools/integration-tests/runTests.sh
+++ b/tools/integration-tests/runTests.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-TEST_NAMES='autopeering common drng message'
+TEST_NAMES='autopeering common drng message consensus'
 
 echo "Build GoShimmer image"
 docker build -t iotaledger/goshimmer ../../.
@@ -10,8 +10,6 @@ docker pull angelocapossele/drand:latest
 docker pull gaiaadm/pumba:latest
 docker pull gaiadocker/iproute2:latest
 
-echo "Run integration tests"
-
 for name in $TEST_NAMES
 do
   TEST_NAME=$name docker-compose -f tester/docker-compose.yml up --abort-on-container-exit --exit-code-from tester --build
diff --git a/tools/integration-tests/tester/docker-compose.yml b/tools/integration-tests/tester/docker-compose.yml
index 3b291d69fabb2447f74b68d6e3421b85600efd25..592f47e7af9eade0bf1a1fcd61c161723e4c3870 100644
--- a/tools/integration-tests/tester/docker-compose.yml
+++ b/tools/integration-tests/tester/docker-compose.yml
@@ -5,13 +5,19 @@ services:
     container_name: tester
     image: golang:1.14
     working_dir: /tmp/goshimmer/tools/integration-tests/tester
-    entrypoint: go test ./tests/${TEST_NAME} -v -mod=readonly -timeout 30m
+    command: /tmp/assets/entrypoint.sh
+    environment:
+      - TEST_NAME=${TEST_NAME}
     volumes:
       - /var/run/docker.sock:/var/run/docker.sock:ro
-      - ../../..:/tmp/goshimmer:ro
+      - ../../..:/tmp/goshimmer:rw
       - ../logs:/tmp/logs
+      - ../assets:/tmp/assets
       - goshimmer-testing-cache:/go
+      - goshimmer-testing-assets:/assets
 
 volumes:
   goshimmer-testing-cache:
     name: goshimmer-testing-cache
+  goshimmer-testing-assets:
+    name: goshimmer-testing-assets
\ No newline at end of file
diff --git a/tools/integration-tests/tester/framework/docker.go b/tools/integration-tests/tester/framework/docker.go
index a5c2cb2493fc6732b013f8d608caf8d8003e2926..7b901ce4ff033a1bc438663a9bdb8f9f99275db7 100644
--- a/tools/integration-tests/tester/framework/docker.go
+++ b/tools/integration-tests/tester/framework/docker.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"strings"
 	"time"
 
 	"github.com/docker/docker/api/types"
@@ -81,13 +82,21 @@ func (d *DockerContainer) CreateGoShimmerPeer(config GoShimmerConfig) error {
 		Cmd: strslice.StrSlice{
 			"--skip-config=true",
 			"--logger.level=debug",
+			fmt.Sprintf("--valueLayer.fcob.averageNetworkDelay=%d", ParaFCoBAverageNetworkDelay),
+			fmt.Sprintf("--autopeering.outboundUpdateIntervalMs=%d", ParaOutboundUpdateIntervalMs),
 			fmt.Sprintf("--node.disablePlugins=%s", config.DisabledPlugins),
 			fmt.Sprintf("--node.enablePlugins=%s", func() string {
+				var plugins []string
 				if config.Bootstrap {
-					return "Bootstrap"
+					plugins = append(plugins, "Bootstrap")
 				}
-				return ""
+				if config.Faucet {
+					plugins = append(plugins, "faucet")
+					plugins = append(plugins, "testSnapshots")
+				}
+				return strings.Join(plugins[:], ",")
 			}()),
+			fmt.Sprintf("--valueLayer.snapshot.file=%s", config.SnapshotFilePath),
 			fmt.Sprintf("--bootstrap.initialIssuance.timePeriodSec=%d", config.BootstrapInitialIssuanceTimePeriodSec),
 			"--webapi.bindAddress=0.0.0.0:8080",
 			fmt.Sprintf("--autopeering.seed=base58:%s", config.Seed),
@@ -99,7 +108,9 @@ func (d *DockerContainer) CreateGoShimmerPeer(config GoShimmerConfig) error {
 		},
 	}
 
-	return d.CreateContainer(config.Name, containerConfig)
+	return d.CreateContainer(config.Name, containerConfig, &container.HostConfig{
+		Binds: []string{"goshimmer-testing-assets:/assets:rw"},
+	})
 }
 
 // CreateDrandMember creates a new container with the drand configuration.
@@ -148,7 +159,7 @@ func (d *DockerContainer) CreatePumba(name string, containerName string, targetI
 	cmd = append(cmd, slice...)
 
 	containerConfig := &container.Config{
-		Image: "gaiaadm/pumba:latest",
+		Image: "gaiaadm/pumba:0.7.2",
 		Cmd:   cmd,
 	}
 
diff --git a/tools/integration-tests/tester/framework/drngnetwork.go b/tools/integration-tests/tester/framework/drngnetwork.go
index 9df42cdb73c4b856cc828e13095c72fc1b20a318..2f3031ceab001dc7d8e415bb41dce956673cf7b5 100644
--- a/tools/integration-tests/tester/framework/drngnetwork.go
+++ b/tools/integration-tests/tester/framework/drngnetwork.go
@@ -55,7 +55,7 @@ func (n *DRNGNetwork) CreatePeer(c GoShimmerConfig, publicKey ed25519.PublicKey)
 		return nil, err
 	}
 
-	peer, err := newPeer(name, identity.New(publicKey), container, n.network)
+	peer, err := newPeer(name, identity.New(publicKey), container, nil, n.network)
 	if err != nil {
 		return nil, err
 	}
diff --git a/tools/integration-tests/tester/framework/framework.go b/tools/integration-tests/tester/framework/framework.go
index 449c2dc6d07993d3bc722f342c21731e0c6252f0..fa1a80a90f48784f4931dff730fb1bbfbd15476f 100644
--- a/tools/integration-tests/tester/framework/framework.go
+++ b/tools/integration-tests/tester/framework/framework.go
@@ -80,8 +80,14 @@ func (f *Framework) CreateNetwork(name string, peers int, minimumNeighbors int,
 	// create peers/GoShimmer nodes
 	for i := 0; i < peers; i++ {
 		config := GoShimmerConfig{
-			Bootstrap:                             i == 0,
+			Bootstrap: func(i int) bool {
+				if ParaBootstrapOnEveryNode {
+					return true
+				}
+				return i == 0
+			}(i),
 			BootstrapInitialIssuanceTimePeriodSec: bootstrapInitialIssuanceTimePeriodSec,
+			Faucet:                                i == 0,
 		}
 		if _, err = network.CreatePeer(config); err != nil {
 			return nil, err
@@ -127,9 +133,12 @@ func (f *Framework) CreateNetworkWithPartitions(name string, peers, partitions,
 
 	// create peers/GoShimmer nodes
 	for i := 0; i < peers; i++ {
-		config := GoShimmerConfig{
-			Bootstrap: i == 0,
-		}
+		config := GoShimmerConfig{Bootstrap: func(i int) bool {
+			if ParaBootstrapOnEveryNode {
+				return true
+			}
+			return i == 0
+		}(i)}
 		if _, err = network.CreatePeer(config); err != nil {
 			return nil, err
 		}
@@ -235,7 +244,12 @@ func (f *Framework) CreateDRNGNetwork(name string, members, peers, minimumNeighb
 
 	// create peers/GoShimmer nodes
 	for i := 0; i < peers; i++ {
-		config.Bootstrap = i == 0
+		config.Bootstrap = func(i int) bool {
+			if ParaBootstrapOnEveryNode {
+				return true
+			}
+			return i == 0
+		}(i)
 		config.Seed = privKeys[i].Seed().String()
 		if _, err = drng.CreatePeer(config, pubKeys[i]); err != nil {
 			return nil, err
diff --git a/tools/integration-tests/tester/framework/network.go b/tools/integration-tests/tester/framework/network.go
index c115fcb5a364e57a59183f05ae6b2cedc170c817..b2001e554c81fc8bcfd922a6acf5b23466b32bdc 100644
--- a/tools/integration-tests/tester/framework/network.go
+++ b/tools/integration-tests/tester/framework/network.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/client"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet"
 	"github.com/iotaledger/hive.go/crypto/ed25519"
 	"github.com/iotaledger/hive.go/identity"
 )
@@ -99,6 +100,15 @@ func (n *Network) CreatePeer(c GoShimmerConfig) (*Peer, error) {
 	config.EntryNodeHost = n.namePrefix(containerNameEntryNode)
 	config.EntryNodePublicKey = n.entryNodePublicKey()
 	config.DisabledPlugins = disabledPluginsPeer
+	config.SnapshotFilePath = snapshotFilePath
+
+	// create wallet
+	var nodeWallet *wallet.Wallet
+	if c.Faucet == true {
+		nodeWallet = wallet.New(faucetSeed)
+	} else {
+		nodeWallet = wallet.New()
+	}
 
 	// create Docker container
 	container := NewDockerContainer(n.dockerClient)
@@ -115,7 +125,7 @@ func (n *Network) CreatePeer(c GoShimmerConfig) (*Peer, error) {
 		return nil, err
 	}
 
-	peer, err := newPeer(name, identity.New(publicKey), container, n)
+	peer, err := newPeer(name, identity.New(publicKey), container, nodeWallet, n)
 	if err != nil {
 		return nil, err
 	}
diff --git a/tools/integration-tests/tester/framework/parameters.go b/tools/integration-tests/tester/framework/parameters.go
index 6e2ae8c4719bc7ffe388474f9d3094e6f6cba0bd..9b88366083f9aed1e0d03198b4029efff86ec613 100644
--- a/tools/integration-tests/tester/framework/parameters.go
+++ b/tools/integration-tests/tester/framework/parameters.go
@@ -1,5 +1,9 @@
 package framework
 
+import (
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet"
+)
+
 const (
 	autopeeringMaxTries = 50
 
@@ -13,16 +17,31 @@ const (
 
 	logsDir = "/tmp/logs/"
 
-	disabledPluginsEntryNode = "portcheck,dashboard,analysis-client,profiling,gossip,drng,issuer,sync,metrics,valuetransfers,messagelayer,webapi,webapibroadcastdataendpoint,webapifindtransactionhashesendpoint,webapigetneighborsendpoint,webapigettransactionobjectsbyhashendpoint,webapigettransactiontrytesbyhashendpoint"
+	disabledPluginsEntryNode = "portcheck,dashboard,analysis-client,profiling,gossip,drng,issuer,sync,metrics,valuetransfers,testsnapshots,messagelayer,webapi,webapibroadcastdataendpoint,webapifindtransactionhashesendpoint,webapigetneighborsendpoint,webapigettransactionobjectsbyhashendpoint,webapigettransactiontrytesbyhashendpoint"
 	disabledPluginsPeer      = "portcheck,dashboard,analysis-client,profiling"
-
-	dockerLogsPrefixLen = 8
+	snapshotFilePath         = "/assets/7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih.bin"
+	dockerLogsPrefixLen      = 8
 
 	dkgMaxTries = 50
 
 	exitStatusSuccessful = 0
 )
 
+// Parameters to override before calling any peer creation function.
+var (
+	// ParaFCoBAverageNetworkDelay defines the configured avg. network delay (in seconds) for the FCOB rules.
+	ParaFCoBAverageNetworkDelay = 5
+	// ParaOutboundUpdateIntervalMs the autopeering outbound update interval in milliseconds.
+	ParaOutboundUpdateIntervalMs = 100
+	// ParaBootstrapOnEveryNode whether to enable the bootstrap plugin on every node.
+	ParaBootstrapOnEveryNode = false
+)
+
+var (
+	faucetSeed = []byte{251, 163, 190, 98, 92, 82, 164, 79, 74, 48, 203, 162, 247, 119, 140, 76, 33, 100, 148, 204, 244, 248, 232, 18,
+		132, 217, 85, 31, 246, 83, 193, 193}
+)
+
 // GoShimmerConfig defines the config of a GoShimmer node.
 type GoShimmerConfig struct {
 	Seed               string
@@ -30,6 +49,7 @@ type GoShimmerConfig struct {
 	EntryNodeHost      string
 	EntryNodePublicKey string
 	DisabledPlugins    string
+	SnapshotFilePath   string
 
 	Bootstrap                             bool
 	BootstrapInitialIssuanceTimePeriodSec int
@@ -38,6 +58,10 @@ type GoShimmerConfig struct {
 	DRNGDistKey   string
 	DRNGInstance  int
 	DRNGThreshold int
+
+	Faucet bool
+
+	Wallet *wallet.Wallet
 }
 
 // NetworkConfig defines the config of a GoShimmer Docker network.
diff --git a/tools/integration-tests/tester/framework/peer.go b/tools/integration-tests/tester/framework/peer.go
index 18c059e8d91d6d07b2131fbf620d0b35f45d39ea..8da31c20ef4634f2dc408034b5046a9b7b589712 100644
--- a/tools/integration-tests/tester/framework/peer.go
+++ b/tools/integration-tests/tester/framework/peer.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/iotaledger/goshimmer/client"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet"
 	"github.com/iotaledger/goshimmer/plugins/webapi/autopeering"
 	"github.com/iotaledger/hive.go/identity"
 )
@@ -24,13 +25,16 @@ type Peer struct {
 	// the DockerContainer that this peer is running in
 	*DockerContainer
 
+	// Wallet
+	*wallet.Wallet
+
 	chosen   []autopeering.Neighbor
 	accepted []autopeering.Neighbor
 }
 
 // newPeer creates a new instance of Peer with the given information.
 // dockerContainer needs to be started in order to determine the container's (and therefore peer's) IP correctly.
-func newPeer(name string, identity *identity.Identity, dockerContainer *DockerContainer, network *Network) (*Peer, error) {
+func newPeer(name string, identity *identity.Identity, dockerContainer *DockerContainer, wallet *wallet.Wallet, network *Network) (*Peer, error) {
 	// after container is started we can get its IP
 	ip, err := dockerContainer.IP(network.name)
 	if err != nil {
@@ -43,6 +47,7 @@ func newPeer(name string, identity *identity.Identity, dockerContainer *DockerCo
 		Identity:        identity,
 		GoShimmerAPI:    client.NewGoShimmerAPI(getWebAPIBaseURL(name), http.Client{Timeout: 30 * time.Second}),
 		DockerContainer: dockerContainer,
+		Wallet:          wallet,
 	}, nil
 }
 
diff --git a/tools/integration-tests/tester/go.mod b/tools/integration-tests/tester/go.mod
index 81fe8f1db5258747f5dd843238207468d7dc4c99..fb23805656004ff35923f67605f823ac705c6c07 100644
--- a/tools/integration-tests/tester/go.mod
+++ b/tools/integration-tests/tester/go.mod
@@ -11,8 +11,8 @@ require (
 	github.com/drand/drand v0.8.1
 	github.com/iotaledger/goshimmer v0.1.3
 	github.com/iotaledger/hive.go v0.0.0-20200610104211-d603429af242
-	github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
-	github.com/stretchr/testify v1.5.1
+	github.com/mr-tron/base58 v1.1.3
+	github.com/stretchr/testify v1.6.1
 )
 
 replace github.com/iotaledger/goshimmer => ../../..
diff --git a/tools/integration-tests/tester/go.sum b/tools/integration-tests/tester/go.sum
index dcd5ef8f8d50ea0a9d8c12f8ff8040b4e3171a6c..8d1d03b6fd96ca2ba969cd7d6285a4dbb9ad53d1 100644
--- a/tools/integration-tests/tester/go.sum
+++ b/tools/integration-tests/tester/go.sum
@@ -204,6 +204,8 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
 github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/panjf2000/ants/v2 v2.2.2 h1:TWzusBjq/IflXhy+/S6u5wmMLCBdJnB9tPIx9Zmhvok=
 github.com/panjf2000/ants/v2 v2.2.2/go.mod h1:1GFm8bV8nyCQvU5K4WvBCTG1/YBFOD2VzjffD8fV55A=
 github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
@@ -287,6 +289,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
@@ -478,6 +482,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
diff --git a/tools/integration-tests/tester/tests/consensus/consensus_conflicts_test.go b/tools/integration-tests/tester/tests/consensus/consensus_conflicts_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e0ebf25abb19b226d1fee692ad7dd4d30e8c2dff
--- /dev/null
+++ b/tools/integration-tests/tester/tests/consensus/consensus_conflicts_test.go
@@ -0,0 +1,199 @@
+package consensus
+
+import (
+	"github.com/iotaledger/goshimmer/tools/integration-tests/tester/framework"
+	"log"
+	"testing"
+	"time"
+
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address/signaturescheme"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet"
+	"github.com/iotaledger/goshimmer/plugins/webapi/value/utils"
+	"github.com/iotaledger/goshimmer/tools/integration-tests/tester/tests"
+	"github.com/mr-tron/base58/base58"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// TestConsensusFiftyFiftyOpinionSplit spawns two network partitions with their own peers,
+// then issues valid value objects spending the genesis in both, deletes the partitions (and lets them merge)
+// and then checks that the conflicts are resolved via FPC.
+func TestConsensusFiftyFiftyOpinionSplit(t *testing.T) {
+
+	// override avg. network delay to accustom integration test slowness
+	framework.ParaFCoBAverageNetworkDelay = 90
+	framework.ParaBootstrapOnEveryNode = true
+
+	// create two partitions with their own peers
+	n, err := f.CreateNetworkWithPartitions("abc", 6, 2, 2)
+	require.NoError(t, err)
+	defer tests.ShutdownNetwork(t, n)
+
+	// split the network
+	for i, partition := range n.Partitions() {
+		log.Printf("partition %d peers:", i)
+		for _, p := range partition.Peers() {
+			log.Println(p.ID().String())
+		}
+	}
+
+	// genesis wallet
+	genesisSeedBytes, err := base58.Decode("7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih")
+	require.NoError(t, err, "couldn't decode genesis seed from base58 seed")
+
+	const genesisBalance = 1000000000
+	genesisWallet := wallet.New(genesisSeedBytes)
+	genesisAddr := genesisWallet.Seed().Address(0)
+	genesisOutputID := transaction.NewOutputID(genesisAddr, transaction.GenesisID)
+
+	// issue transactions which spend the same genesis output in all partitions
+	conflictingTxs := make([]*transaction.Transaction, len(n.Partitions()))
+	conflictingTxIDs := make([]string, len(n.Partitions()))
+	receiverWallets := make([]*wallet.Wallet, len(n.Partitions()))
+	for i, partition := range n.Partitions() {
+
+		// create a new receiver wallet for the given partition
+		partitionReceiverWallet := wallet.New()
+		destAddr := partitionReceiverWallet.Seed().Address(0)
+		receiverWallets[i] = partitionReceiverWallet
+		tx := transaction.New(
+			transaction.NewInputs(genesisOutputID),
+			transaction.NewOutputs(map[address.Address][]*balance.Balance{
+				destAddr: {
+					{Value: genesisBalance, Color: balance.ColorIOTA},
+				},
+			}))
+		tx = tx.Sign(signaturescheme.ED25519(*genesisWallet.Seed().KeyPair(0)))
+		conflictingTxs[i] = tx
+
+		// issue the transaction on the first peer of the partition
+		issuerPeer := partition.Peers()[0]
+		txID, err := issuerPeer.SendTransaction(tx.Bytes())
+		conflictingTxIDs[i] = txID
+		log.Printf("issued conflict transaction %s on partition %d on peer %s", txID, i, issuerPeer.ID().String())
+		assert.NoError(t, err)
+
+		// check that the transaction is actually available on all the peers of the partition
+		missing, err := tests.AwaitTransactionAvailability(partition.Peers(), []string{txID}, 15*time.Second)
+		if err != nil {
+			assert.NoError(t, err, "transactions should have been available in partition")
+			for p, missingOnPeer := range missing {
+				log.Printf("missing on peer %s:", p)
+				for missingTx := range missingOnPeer {
+					log.Println("tx id: ", missingTx)
+				}
+			}
+			return
+		}
+
+		require.NoError(t, err)
+	}
+
+	// sleep the avg. network delay so both partitions prefer their own first seen transaction
+	log.Printf("waiting %d seconds avg. network delay to make the transactions "+
+		"preferred in their corresponding partition", framework.ParaFCoBAverageNetworkDelay)
+	time.Sleep(time.Duration(framework.ParaFCoBAverageNetworkDelay) * time.Second)
+
+	// check that each partition is preferring its corresponding transaction
+	log.Println("checking that each partition likes its corresponding transaction before the conflict:")
+	for i, partition := range n.Partitions() {
+		tests.CheckTransactions(t, partition.Peers(), map[string]*tests.ExpectedTransaction{
+			conflictingTxIDs[i]: nil,
+		}, true, tests.ExpectedInclusionState{
+			Confirmed:   tests.False(),
+			Finalized:   tests.False(),
+			Conflicting: tests.False(),
+			Solid:       tests.True(),
+			Rejected:    tests.False(),
+			Liked:       tests.True(),
+			Preferred:   tests.True(),
+		})
+	}
+
+	// merge back the partitions
+	log.Println("merging partitions...")
+	assert.NoError(t, n.DeletePartitions(), "merging the network partitions should work")
+	log.Println("waiting for resolved partitions to autopeer to each other")
+	err = n.WaitForAutopeering(4)
+	require.NoError(t, err)
+
+	// ensure message flow so that both partitions will get the conflicting tx
+	for _, p := range n.Peers() {
+		tests.SendDataMessage(t, p, []byte("DATA"), 10)
+	}
+
+	log.Println("waiting for transactions to be available on all peers...")
+	missing, err := tests.AwaitTransactionAvailability(n.Peers(), conflictingTxIDs, 30*time.Second)
+	if err != nil {
+		assert.NoError(t, err, "transactions should have been available")
+		for p, missingOnPeer := range missing {
+			log.Printf("missing on peer %s:", p)
+			for missingTx := range missingOnPeer {
+				log.Println("tx id: ", missingTx)
+			}
+		}
+		return
+	}
+
+	expectations := map[string]*tests.ExpectedTransaction{}
+	for _, conflictingTx := range conflictingTxs {
+		utilsTx := utils.ParseTransaction(conflictingTx)
+		expectations[conflictingTx.ID().String()] = &tests.ExpectedTransaction{
+			Inputs:    &utilsTx.Inputs,
+			Outputs:   &utilsTx.Outputs,
+			Signature: &utilsTx.Signature,
+		}
+	}
+
+	// check that the transactions are marked as conflicting
+	tests.CheckTransactions(t, n.Peers(), expectations, true, tests.ExpectedInclusionState{
+		Finalized:   tests.False(),
+		Conflicting: tests.True(),
+		Solid:       tests.True(),
+	})
+
+	// wait until the voting has finalized
+	awaitFinalization := map[string]tests.ExpectedInclusionState{}
+	for _, conflictingTx := range conflictingTxs {
+		awaitFinalization[conflictingTx.ID().String()] = tests.ExpectedInclusionState{
+			Finalized: tests.True(),
+		}
+	}
+	err = tests.AwaitTransactionInclusionState(n.Peers(), awaitFinalization, 2*time.Minute)
+	assert.NoError(t, err)
+
+	// now all transactions must be finalized and at most one must be confirmed
+	var confirmedOverConflictSet int
+	for _, conflictingTx := range conflictingTxIDs {
+		var rejected, confirmed int
+		for _, p := range n.Peers() {
+			tx, err := p.GetTransactionByID(conflictingTx)
+			assert.NoError(t, err)
+			if tx.InclusionState.Confirmed {
+				confirmed++
+				continue
+			}
+			if tx.InclusionState.Rejected {
+				rejected++
+			}
+		}
+
+		if rejected != 0 {
+			assert.Len(t, n.Peers(), rejected, "the rejected count for %s should be equal to the amount of peers", conflictingTx)
+		}
+		if confirmed != 0 {
+			assert.Len(t, n.Peers(), confirmed, "the confirmed count for %s should be equal to the amount of peers", conflictingTx)
+			confirmedOverConflictSet++
+		}
+
+		assert.False(t, rejected == 0 && confirmed == 0, "a transaction must either be rejected or confirmed")
+	}
+
+	// there must only be one confirmed transaction out of the conflict set
+	if confirmedOverConflictSet != 0 {
+		assert.Equal(t, 1, confirmedOverConflictSet, "only one transaction can be confirmed out of the conflict set. %d of %d are confirmed", confirmedOverConflictSet, len(conflictingTxIDs))
+	}
+}
diff --git a/tools/integration-tests/tester/tests/consensus/consensus_noconflicts_test.go b/tools/integration-tests/tester/tests/consensus/consensus_noconflicts_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..e4840807684108caec0ec15a5e0318c53d1afdf7
--- /dev/null
+++ b/tools/integration-tests/tester/tests/consensus/consensus_noconflicts_test.go
@@ -0,0 +1,118 @@
+package consensus
+
+import (
+	"math/rand"
+	"testing"
+	"time"
+
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address/signaturescheme"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/wallet"
+	"github.com/iotaledger/goshimmer/plugins/webapi/value/utils"
+	"github.com/iotaledger/goshimmer/tools/integration-tests/tester/tests"
+	"github.com/mr-tron/base58/base58"
+	"github.com/stretchr/testify/require"
+)
+
+// TestConsensusNoConflicts issues valid non-conflicting value objects and then checks
+// whether the ledger of every peer reflects the same correct state.
+func TestConsensusNoConflicts(t *testing.T) {
+	n, err := f.CreateNetwork("consensus_TestConsensusNoConflicts", 4, 2)
+	require.NoError(t, err)
+	defer tests.ShutdownNetwork(t, n)
+
+	time.Sleep(5 * time.Second)
+
+	// genesis wallet
+	genesisSeedBytes, err := base58.Decode("7R1itJx5hVuo9w9hjg5cwKFmek4HMSoBDgJZN8hKGxih")
+	require.NoError(t, err, "couldn't decode genesis seed from base58 seed")
+
+	const genesisBalance = 1000000000
+	genesisWallet := wallet.New(genesisSeedBytes)
+	genesisAddr := genesisWallet.Seed().Address(0)
+	genesisOutputID := transaction.NewOutputID(genesisAddr, transaction.GenesisID)
+
+	firstReceiver := wallet.New()
+	const depositCount = 10
+	const deposit = genesisBalance / depositCount
+	firstReceiverAddresses := make([]string, depositCount)
+	firstReceiverDepositAddrs := make([]address.Address, depositCount)
+	firstReceiverDepositOutputs := map[address.Address][]*balance.Balance{}
+	firstReceiverExpectedBalances := map[string]map[balance.Color]int64{}
+	for i := 0; i < depositCount; i++ {
+		addr := firstReceiver.Seed().Address(uint64(i))
+		firstReceiverDepositAddrs[i] = addr
+		firstReceiverAddresses[i] = addr.String()
+		firstReceiverDepositOutputs[addr] = []*balance.Balance{{Value: deposit, Color: balance.ColorIOTA}}
+		firstReceiverExpectedBalances[addr.String()] = map[balance.Color]int64{balance.ColorIOTA: deposit}
+	}
+
+	// issue transaction spending from the genesis output
+	tx := transaction.New(transaction.NewInputs(genesisOutputID), transaction.NewOutputs(firstReceiverDepositOutputs))
+	tx = tx.Sign(signaturescheme.ED25519(*genesisWallet.Seed().KeyPair(0)))
+	utilsTx := utils.ParseTransaction(tx)
+
+	txID, err := n.Peers()[0].SendTransaction(tx.Bytes())
+	require.NoError(t, err)
+
+	// wait for the transaction to be propagated through the network
+	// and it becoming preferred, finalized and confirmed
+	time.Sleep(valuetransfers.DefaultAverageNetworkDelay*2 + valuetransfers.DefaultAverageNetworkDelay/2)
+
+	// since we just issued a transaction spending the genesis output, there
+	// shouldn't be any UTXOs on the genesis address anymore
+	tests.CheckAddressOutputsFullyConsumed(t, n.Peers(), []string{genesisAddr.String()})
+
+	// since we waited 2.5 avg. network delays and there were no conflicting transactions,
+	// the transaction we just issued must be preferred, liked, finalized and confirmed
+	tests.CheckTransactions(t, n.Peers(), map[string]*tests.ExpectedTransaction{
+		txID: {Inputs: &utilsTx.Inputs, Outputs: &utilsTx.Outputs, Signature: &utilsTx.Signature},
+	}, true, tests.ExpectedInclusionState{
+		Confirmed: tests.True(), Finalized: tests.True(),
+		Conflicting: tests.False(), Solid: tests.True(),
+		Rejected: tests.False(), Liked: tests.True(),
+	})
+
+	// check balances on peers
+	tests.CheckBalances(t, n.Peers(), firstReceiverExpectedBalances)
+
+	// issue transactions spending all the outputs which were just created from a random peer
+	secondReceiverWallet := wallet.New()
+	secondReceiverAddresses := make([]string, depositCount)
+	secondReceiverExpectedBalances := map[string]map[balance.Color]int64{}
+	secondReceiverExpectedTransactions := map[string]*tests.ExpectedTransaction{}
+	for i := 0; i < depositCount; i++ {
+		addr := secondReceiverWallet.Seed().Address(uint64(i))
+		tx := transaction.New(
+			transaction.NewInputs(transaction.NewOutputID(firstReceiver.Seed().Address(uint64(i)), tx.ID())),
+			transaction.NewOutputs(map[address.Address][]*balance.Balance{
+				addr: {{Value: deposit, Color: balance.ColorIOTA}},
+			}),
+		)
+		secondReceiverAddresses[i] = addr.String()
+		tx = tx.Sign(signaturescheme.ED25519(*secondReceiverWallet.Seed().KeyPair(uint64(i))))
+		txID, err := n.Peers()[rand.Intn(len(n.Peers()))].SendTransaction(tx.Bytes())
+		require.NoError(t, err)
+
+		utilsTx := utils.ParseTransaction(tx)
+		secondReceiverExpectedBalances[addr.String()] = map[balance.Color]int64{balance.ColorIOTA: deposit}
+		secondReceiverExpectedTransactions[txID] = &tests.ExpectedTransaction{
+			Inputs: &utilsTx.Inputs, Outputs: &utilsTx.Outputs, Signature: &utilsTx.Signature,
+		}
+	}
+
+	// wait again some network delays for the transactions to materialize
+	time.Sleep(valuetransfers.DefaultAverageNetworkDelay*2 + valuetransfers.DefaultAverageNetworkDelay/2)
+	tests.CheckAddressOutputsFullyConsumed(t, n.Peers(), firstReceiverAddresses)
+	tests.CheckTransactions(t, n.Peers(), secondReceiverExpectedTransactions, true,
+		tests.ExpectedInclusionState{
+			Confirmed: tests.True(), Finalized: tests.True(),
+			Conflicting: tests.False(), Solid: tests.True(),
+			Rejected: tests.False(), Liked: tests.True(),
+		},
+	)
+	tests.CheckBalances(t, n.Peers(), secondReceiverExpectedBalances)
+}
diff --git a/tools/integration-tests/tester/tests/consensus/main_test.go b/tools/integration-tests/tester/tests/consensus/main_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..422928f9627868376b0610691f0e3ac2f6108636
--- /dev/null
+++ b/tools/integration-tests/tester/tests/consensus/main_test.go
@@ -0,0 +1,23 @@
+package consensus
+
+import (
+	"os"
+	"testing"
+
+	"github.com/iotaledger/goshimmer/tools/integration-tests/tester/framework"
+)
+
+var f *framework.Framework
+
+// TestMain gets called by the test utility and is executed before any other test in this package.
+// It is therefore used to initialize the integration testing framework.
+func TestMain(m *testing.M) {
+	var err error
+	f, err = framework.Instance()
+	if err != nil {
+		panic(err)
+	}
+
+	// call the tests
+	os.Exit(m.Run())
+}
diff --git a/tools/integration-tests/tester/tests/testutil.go b/tools/integration-tests/tester/tests/testutil.go
index 9e11ee44d83c13f5f9108ce43998198d0e800bb8..31332cffee2dc8975dce16680afb3e3cbb3a7f4e 100644
--- a/tools/integration-tests/tester/tests/testutil.go
+++ b/tools/integration-tests/tester/tests/testutil.go
@@ -1,16 +1,31 @@
 package tests
 
 import (
+	"errors"
 	"fmt"
 	"math/rand"
+	"sync"
+	"sync/atomic"
 	"testing"
+	"time"
 
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address/signaturescheme"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/balance"
+	"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction"
 	"github.com/iotaledger/goshimmer/packages/binary/messagelayer/payload"
+	"github.com/iotaledger/goshimmer/plugins/webapi/value/utils"
 	"github.com/iotaledger/goshimmer/tools/integration-tests/tester/framework"
+	"github.com/iotaledger/hive.go/types"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
+var (
+	ErrTransactionNotAvailableInTime = errors.New("transaction was not available in time")
+	ErrTransactionStateNotSameInTime = errors.New("transaction state did not materialize in time")
+)
+
 // DataMessageSent defines a struct to identify from which issuer a data message was sent.
 type DataMessageSent struct {
 	number          int
@@ -95,6 +110,460 @@ func CheckForMessageIds(t *testing.T, peers []*framework.Peer, ids map[string]Da
 	}
 }
 
+// SendValueMessagesOnFaucet sends funds to peers from the faucet and returns the transaction ID.
+func SendValueMessagesOnFaucet(t *testing.T, peers []*framework.Peer) (txIds []string, addrBalance map[string]map[balance.Color]int64) {
+	// initiate addrBalance map
+	addrBalance = make(map[string]map[balance.Color]int64)
+	for _, p := range peers {
+		addr := p.Seed().Address(0).String()
+		addrBalance[addr] = make(map[balance.Color]int64)
+		addrBalance[addr][balance.ColorIOTA] = 0
+	}
+
+	faucetPeer := peers[0]
+	faucetAddrStr := faucetPeer.Seed().Address(0).String()
+
+	// get faucet balances
+	unspentOutputs, err := faucetPeer.GetUnspentOutputs([]string{faucetAddrStr})
+	require.NoErrorf(t, err, "Could not get unspent outputs on %s", faucetPeer.String())
+	addrBalance[faucetAddrStr][balance.ColorIOTA] = unspentOutputs.UnspentOutputs[0].OutputIDs[0].Balances[0].Value
+
+	// send funds to other peers
+	for i := 1; i < len(peers); i++ {
+		fail, txId := SendIotaValueMessages(t, faucetPeer, peers[i], addrBalance)
+		require.False(t, fail)
+		txIds = append(txIds, txId)
+
+		// let the transaction propagate
+		time.Sleep(1 * time.Second)
+	}
+	return
+}
+
+// SendValueMessagesOnRandomPeer sends IOTA tokens on random peer and saves the sent message token to a map.
+func SendValueMessagesOnRandomPeer(t *testing.T, peers []*framework.Peer, addrBalance map[string]map[balance.Color]int64, numMessages int) (txIds []string) {
+	for i := 0; i < numMessages; i++ {
+		from := rand.Intn(len(peers))
+		to := rand.Intn(len(peers))
+		fail, txId := SendIotaValueMessages(t, peers[from], peers[to], addrBalance)
+		if fail {
+			i--
+			continue
+		}
+
+		// attach tx id
+		txIds = append(txIds, txId)
+
+		// let the transaction propagate
+		time.Sleep(1 * time.Second)
+	}
+
+	return
+}
+
+// SendIotaValueMessages sends IOTA token from and to a given peer and returns the transaction ID.
+// The same addresses are used in each round
+func SendIotaValueMessages(t *testing.T, from *framework.Peer, to *framework.Peer, addrBalance map[string]map[balance.Color]int64) (fail bool, txId string) {
+	var sentValue int64 = 100
+	sigScheme := signaturescheme.ED25519(*from.Seed().KeyPair(0))
+	inputAddr := from.Seed().Address(0)
+	outputAddr := to.Seed().Address(0)
+
+	// prepare inputs
+	resp, err := from.GetUnspentOutputs([]string{inputAddr.String()})
+	require.NoErrorf(t, err, "Could not get unspent outputs on %s", from.String())
+
+	// abort if no unspent outputs
+	if len(resp.UnspentOutputs[0].OutputIDs) == 0 {
+		return true, ""
+	}
+	availableValue := resp.UnspentOutputs[0].OutputIDs[0].Balances[0].Value
+
+	//abort if the balance is not enough
+	if availableValue < sentValue {
+		return true, ""
+	}
+
+	out, err := transaction.OutputIDFromBase58(resp.UnspentOutputs[0].OutputIDs[0].ID)
+	require.NoErrorf(t, err, "Invalid unspent outputs ID on %s", from.String())
+	inputs := transaction.NewInputs([]transaction.OutputID{out}...)
+
+	// prepare outputs
+	outmap := map[address.Address][]*balance.Balance{}
+	if inputAddr == outputAddr {
+		sentValue = availableValue
+	}
+
+	// set balances
+	outmap[outputAddr] = []*balance.Balance{balance.New(balance.ColorIOTA, sentValue)}
+	outputs := transaction.NewOutputs(outmap)
+
+	// handle remain address
+	if availableValue > sentValue {
+		outputs.Add(inputAddr, []*balance.Balance{balance.New(balance.ColorIOTA, availableValue-sentValue)})
+	}
+
+	// sign transaction
+	txn := transaction.New(inputs, outputs).Sign(sigScheme)
+
+	// send transaction
+	txId, err = from.SendTransaction(txn.Bytes())
+	require.NoErrorf(t, err, "Could not send transaction on %s", from.String())
+
+	addrBalance[inputAddr.String()][balance.ColorIOTA] -= sentValue
+	addrBalance[outputAddr.String()][balance.ColorIOTA] += sentValue
+
+	return false, txId
+}
+
+// SendColoredValueMessagesOnRandomPeer sends colored token on a random peer and saves the sent token to a map.
+func SendColoredValueMessagesOnRandomPeer(t *testing.T, peers []*framework.Peer, addrBalance map[string]map[balance.Color]int64, numMessages int) (txIds []string) {
+	for i := 0; i < numMessages; i++ {
+		from := rand.Intn(len(peers))
+		to := rand.Intn(len(peers))
+		fail, txId := SendColoredValueMessage(t, peers[from], peers[to], addrBalance)
+		if fail {
+			i--
+			continue
+		}
+
+		// attach tx id
+		txIds = append(txIds, txId)
+
+		// let the transaction propagate
+		time.Sleep(1 * time.Second)
+	}
+
+	return
+}
+
+// SendColoredValueMessage sends a colored tokens from and to a given peer and returns the transaction ID.
+// The same addresses are used in each round
+func SendColoredValueMessage(t *testing.T, from *framework.Peer, to *framework.Peer, addrBalance map[string]map[balance.Color]int64) (fail bool, txId string) {
+	sigScheme := signaturescheme.ED25519(*from.Seed().KeyPair(0))
+	inputAddr := from.Seed().Address(0)
+	outputAddr := to.Seed().Address(0)
+
+	// prepare inputs
+	resp, err := from.GetUnspentOutputs([]string{inputAddr.String()})
+	require.NoErrorf(t, err, "Could not get unspent outputs on %s", from.String())
+
+	// abort if no unspent outputs
+	if len(resp.UnspentOutputs[0].OutputIDs) == 0 {
+		return true, ""
+	}
+
+	out, err := transaction.OutputIDFromBase58(resp.UnspentOutputs[0].OutputIDs[0].ID)
+	require.NoErrorf(t, err, "Invalid unspent outputs ID on %s", from.String())
+	inputs := transaction.NewInputs([]transaction.OutputID{out}...)
+
+	// prepare outputs
+	outmap := map[address.Address][]*balance.Balance{}
+	bs := []*balance.Balance{}
+	var outputs *transaction.Outputs
+	var availableIOTA int64
+	availableBalances := resp.UnspentOutputs[0].OutputIDs[0].Balances
+	newColor := false
+
+	// set balances
+	if len(availableBalances) > 1 {
+		// the balances contain more than one color, send it all
+		for _, b := range availableBalances {
+			value := b.Value
+			color := getColorFromString(b.Color)
+			bs = append(bs, balance.New(color, value))
+
+			// update balance list
+			addrBalance[inputAddr.String()][color] -= value
+			if _, ok := addrBalance[outputAddr.String()][color]; ok {
+				addrBalance[outputAddr.String()][color] += value
+			} else {
+				addrBalance[outputAddr.String()][color] = value
+			}
+		}
+	} else {
+		// create new colored token if inputs only contain IOTA
+		// half of availableIota tokens remain IOTA, else get recolored
+		newColor = true
+		availableIOTA = availableBalances[0].Value
+
+		bs = append(bs, balance.New(balance.ColorIOTA, availableIOTA/2))
+		bs = append(bs, balance.New(balance.ColorNew, availableIOTA/2))
+
+		// update balance list
+		addrBalance[inputAddr.String()][balance.ColorIOTA] -= availableIOTA
+		addrBalance[outputAddr.String()][balance.ColorIOTA] += availableIOTA / 2
+	}
+	outmap[outputAddr] = bs
+
+	outputs = transaction.NewOutputs(outmap)
+
+	// sign transaction
+	txn := transaction.New(inputs, outputs).Sign(sigScheme)
+
+	// send transaction
+	txId, err = from.SendTransaction(txn.Bytes())
+	require.NoErrorf(t, err, "Could not send transaction on %s", from.String())
+
+	// FIXME: the new color should be txn ID
+	if newColor {
+		if _, ok := addrBalance[outputAddr.String()][balance.ColorNew]; ok {
+			addrBalance[outputAddr.String()][balance.ColorNew] += availableIOTA / 2
+		} else {
+			addrBalance[outputAddr.String()][balance.ColorNew] = availableIOTA / 2
+		}
+		//addrBalance[outputAddr.String()][getColorFromString(txId)] = availableIOTA / 2
+	}
+	return false, txId
+}
+
+func getColorFromString(colorStr string) (color balance.Color) {
+	if colorStr == "IOTA" {
+		color = balance.ColorIOTA
+	} else {
+		t, _ := transaction.IDFromBase58(colorStr)
+		color, _, _ = balance.ColorFromBytes(t.Bytes())
+	}
+	return
+}
+
+// CheckBalances performs checks to make sure that all peers have the same ledger state.
+func CheckBalances(t *testing.T, peers []*framework.Peer, addrBalance map[string]map[balance.Color]int64) {
+	for _, peer := range peers {
+		for addr, b := range addrBalance {
+			sum := make(map[balance.Color]int64)
+			resp, err := peer.GetUnspentOutputs([]string{addr})
+			require.NoError(t, err)
+			assert.Equal(t, addr, resp.UnspentOutputs[0].Address)
+
+			// calculate the balances of each color coin
+			for _, unspents := range resp.UnspentOutputs[0].OutputIDs {
+				for _, b := range unspents.Balances {
+					color := getColorFromString(b.Color)
+					if _, ok := sum[color]; ok {
+						sum[color] += b.Value
+					} else {
+						sum[color] = b.Value
+					}
+				}
+			}
+
+			// check balances
+			for color, value := range sum {
+				assert.Equal(t, b[color], value)
+			}
+		}
+	}
+}
+
+// CheckAddressOutputsFullyConsumed performs checks to make sure that on all given peers,
+// the given addresses have no UTXOs.
+func CheckAddressOutputsFullyConsumed(t *testing.T, peers []*framework.Peer, addrs []string) {
+	for _, peer := range peers {
+		resp, err := peer.GetUnspentOutputs(addrs)
+		assert.NoError(t, err)
+		assert.Len(t, resp.Error, 0)
+		for i, utxos := range resp.UnspentOutputs {
+			assert.Len(t, utxos.OutputIDs, 0, "address %s should not have any UTXOs", addrs[i])
+		}
+	}
+}
+
+// ExpectedInclusionState is an expected inclusion state.
+// All fields are optional.
+type ExpectedInclusionState struct {
+	// The optional confirmed state to check against.
+	Confirmed *bool
+	// The optional finalized state to check against.
+	Finalized *bool
+	// The optional conflict state to check against.
+	Conflicting *bool
+	// The optional solid state to check against.
+	Solid *bool
+	// The optional rejected state to check against.
+	Rejected *bool
+	// The optional liked state to check against.
+	Liked *bool
+	// The optional preferred state to check against.
+	Preferred *bool
+}
+
+// True returns a pointer to a true bool.
+func True() *bool {
+	x := true
+	return &x
+}
+
+// False returns a pointer to a false bool.
+func False() *bool {
+	x := false
+	return &x
+}
+
+// ExpectedTransaction defines the expected data of a transaction.
+// All fields are optional.
+type ExpectedTransaction struct {
+	// The optional input IDs to check against.
+	Inputs *[]string
+	// The optional outputs to check against.
+	Outputs *[]utils.Output
+	// The optional signature to check against.
+	Signature *[]byte
+}
+
+// CheckTransactions performs checks to make sure that all peers have received all transactions.
+// Optionally takes an expected inclusion state for all supplied transaction IDs and expected transaction
+// data per transaction ID.
+func CheckTransactions(t *testing.T, peers []*framework.Peer, transactionIDs map[string]*ExpectedTransaction, checkSynchronized bool, expectedInclusionState ExpectedInclusionState) {
+	for _, peer := range peers {
+		if checkSynchronized {
+			// check that the peer sees itself as synchronized
+			info, err := peer.Info()
+			require.NoError(t, err)
+			require.True(t, info.Synced)
+		}
+
+		for txId, expectedTransaction := range transactionIDs {
+			resp, err := peer.GetTransactionByID(txId)
+			require.NoError(t, err)
+
+			// check inclusion state
+			if expectedInclusionState.Confirmed != nil {
+				assert.Equal(t, *expectedInclusionState.Confirmed, resp.InclusionState.Confirmed, "confirmed state doesn't match - %s", txId)
+			}
+			if expectedInclusionState.Conflicting != nil {
+				assert.Equal(t, *expectedInclusionState.Conflicting, resp.InclusionState.Conflicting, "conflict state doesn't match - %s", txId)
+			}
+			if expectedInclusionState.Solid != nil {
+				assert.Equal(t, *expectedInclusionState.Solid, resp.InclusionState.Solid, "solid state doesn't match - %s", txId)
+			}
+			if expectedInclusionState.Rejected != nil {
+				assert.Equal(t, *expectedInclusionState.Rejected, resp.InclusionState.Rejected, "rejected state doesn't match - %s", txId)
+			}
+			if expectedInclusionState.Liked != nil {
+				assert.Equal(t, *expectedInclusionState.Liked, resp.InclusionState.Liked, "liked state doesn't match - %s", txId)
+			}
+			if expectedInclusionState.Preferred != nil {
+				assert.Equal(t, *expectedInclusionState.Preferred, resp.InclusionState.Preferred, "preferred state doesn't match - %s", txId)
+			}
+
+			if expectedTransaction != nil {
+				if expectedTransaction.Inputs != nil {
+					assert.Equal(t, *expectedTransaction.Inputs, resp.Transaction.Inputs, "inputs do not match - %s", txId)
+				}
+				if expectedTransaction.Outputs != nil {
+					assert.Equal(t, *expectedTransaction.Outputs, resp.Transaction.Outputs, "outputs do not match - %s", txId)
+				}
+				if expectedTransaction.Signature != nil {
+					assert.Equal(t, *expectedTransaction.Signature, resp.Transaction.Signature, "signatures do not match - %s", txId)
+				}
+			}
+		}
+	}
+}
+
+// AwaitTransactionAvailability awaits until the given transaction IDs become available on all given peers or
+// the max duration is reached. Returns a map of missing transactions per peer. An error is returned if at least
+// one peer does not have all specified transactions available.
+func AwaitTransactionAvailability(peers []*framework.Peer, transactionIDs []string, maxAwait time.Duration) (missing map[string]map[string]types.Empty, err error) {
+	s := time.Now()
+	var missingMu sync.Mutex
+	missing = map[string]map[string]types.Empty{}
+	for ; time.Since(s) < maxAwait; time.Sleep(500 * time.Millisecond) {
+		var wg sync.WaitGroup
+		wg.Add(len(peers))
+		counter := int32(len(peers) * len(transactionIDs))
+		for _, p := range peers {
+			go func(p *framework.Peer) {
+				defer wg.Done()
+				for _, txID := range transactionIDs {
+					_, err := p.GetTransactionByID(txID)
+					if err == nil {
+						missingMu.Lock()
+						m, has := missing[p.ID().String()]
+						if has {
+							delete(m, txID)
+							if len(m) == 0 {
+								delete(missing, p.ID().String())
+							}
+						}
+						missingMu.Unlock()
+						atomic.AddInt32(&counter, -1)
+						continue
+					}
+					missingMu.Lock()
+					m, has := missing[p.ID().String()]
+					if !has {
+						m = map[string]types.Empty{}
+					}
+					m[txID] = types.Empty{}
+					missing[p.ID().String()] = m
+					missingMu.Unlock()
+				}
+			}(p)
+		}
+		wg.Wait()
+		if counter == 0 {
+			// everything available
+			return missing, nil
+		}
+	}
+	return missing, ErrTransactionNotAvailableInTime
+}
+
+// AwaitTransactionInclusionState awaits on all given peers until the specified transactions
+// have the expected state or max duration is reached. This function does not gracefully
+// handle the transactions not existing on the given peers, therefore it must be ensured
+// the the transactions exist beforehand.
+func AwaitTransactionInclusionState(peers []*framework.Peer, transactionIDs map[string]ExpectedInclusionState, maxAwait time.Duration) error {
+	s := time.Now()
+	for ; time.Since(s) < maxAwait; time.Sleep(1 * time.Second) {
+		var wg sync.WaitGroup
+		wg.Add(len(peers))
+		counter := int32(len(peers) * len(transactionIDs))
+		for _, p := range peers {
+			go func(p *framework.Peer) {
+				defer wg.Done()
+				for txID := range transactionIDs {
+					tx, err := p.GetTransactionByID(txID)
+					if err != nil {
+						continue
+					}
+					expInclState := transactionIDs[txID]
+					if expInclState.Confirmed != nil && *expInclState.Confirmed != tx.InclusionState.Confirmed {
+						continue
+					}
+					if expInclState.Conflicting != nil && *expInclState.Conflicting != tx.InclusionState.Conflicting {
+						continue
+					}
+					if expInclState.Finalized != nil && *expInclState.Finalized != tx.InclusionState.Finalized {
+						continue
+					}
+					if expInclState.Liked != nil && *expInclState.Liked != tx.InclusionState.Liked {
+						continue
+					}
+					if expInclState.Preferred != nil && *expInclState.Preferred != tx.InclusionState.Preferred {
+						continue
+					}
+					if expInclState.Rejected != nil && *expInclState.Rejected != tx.InclusionState.Rejected {
+						continue
+					}
+					if expInclState.Solid != nil && *expInclState.Solid != tx.InclusionState.Solid {
+						continue
+					}
+					atomic.AddInt32(&counter, -1)
+				}
+			}(p)
+		}
+		wg.Wait()
+		if counter == 0 {
+			// everything available
+			return nil
+		}
+	}
+	return ErrTransactionStateNotSameInTime
+}
+
 // ShutdownNetwork shuts down the network and reports errors.
 func ShutdownNetwork(t *testing.T, n Shutdowner) {
 	err := n.Shutdown()
diff --git a/tools/integration-tests/tester/tests/value/main_test.go b/tools/integration-tests/tester/tests/value/main_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..bcc09c2e53590df061cbff5a2159071d567440f1
--- /dev/null
+++ b/tools/integration-tests/tester/tests/value/main_test.go
@@ -0,0 +1,23 @@
+package autopeering
+
+import (
+	"os"
+	"testing"
+
+	"github.com/iotaledger/goshimmer/tools/integration-tests/tester/framework"
+)
+
+var f *framework.Framework
+
+// TestMain gets called by the test utility and is executed before any other test in this package.
+// It is therefore used to initialize the integration testing framework.
+func TestMain(m *testing.M) {
+	var err error
+	f, err = framework.Instance()
+	if err != nil {
+		panic(err)
+	}
+
+	// call the tests
+	os.Exit(m.Run())
+}
diff --git a/tools/integration-tests/tester/tests/value/value_test.go b/tools/integration-tests/tester/tests/value/value_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f0cb8109be17c0bea4ffaa1959042cd902070f43
--- /dev/null
+++ b/tools/integration-tests/tester/tests/value/value_test.go
@@ -0,0 +1,145 @@
+package autopeering
+
+import (
+	"testing"
+	"time"
+
+	"github.com/iotaledger/goshimmer/tools/integration-tests/tester/tests"
+	"github.com/stretchr/testify/require"
+)
+
+// TestValueIotaPersistence issues messages on random peers, restarts them and checks for persistence after restart.
+func TestValueIotaPersistence(t *testing.T) {
+	n, err := f.CreateNetwork("valueIota_TestPersistence", 4, 2)
+	require.NoError(t, err)
+	defer tests.ShutdownNetwork(t, n)
+
+	// wait for peers to change their state to synchronized
+	time.Sleep(5 * time.Second)
+
+	// master node sends funds to all peers in the network
+	txIdsSlice, addrBalance := tests.SendValueMessagesOnFaucet(t, n.Peers())
+	txIds := make(map[string]*tests.ExpectedTransaction)
+	for _, txID := range txIdsSlice {
+		txIds[txID] = nil
+	}
+
+	// wait for messages to be gossiped
+	time.Sleep(10 * time.Second)
+
+	// check whether the first issued transaction is available on all nodes, and confirmed
+	tests.CheckTransactions(t, n.Peers(), txIds, true, tests.ExpectedInclusionState{
+		Confirmed: tests.True(),
+	})
+
+	// check ledger state
+	tests.CheckBalances(t, n.Peers(), addrBalance)
+
+	// send value message randomly
+	randomTxIds := tests.SendValueMessagesOnRandomPeer(t, n.Peers(), addrBalance, 10)
+	for _, randomTxId := range randomTxIds {
+		txIds[randomTxId] = nil
+	}
+
+	// wait for messages to be gossiped
+	time.Sleep(10 * time.Second)
+
+	// check whether all issued transactions are persistently available on all nodes, and confirmed
+	tests.CheckTransactions(t, n.Peers(), txIds, true, tests.ExpectedInclusionState{
+		Confirmed: tests.True(),
+	})
+
+	// check ledger state
+	tests.CheckBalances(t, n.Peers(), addrBalance)
+
+	// 3. stop all nodes
+	for _, peer := range n.Peers() {
+		err = peer.Stop()
+		require.NoError(t, err)
+	}
+
+	// 4. start all nodes
+	for _, peer := range n.Peers() {
+		err = peer.Start()
+		require.NoError(t, err)
+	}
+
+	// wait for peers to start
+	time.Sleep(10 * time.Second)
+
+	// check whether all issued transactions are persistently available on all nodes, and confirmed
+	tests.CheckTransactions(t, n.Peers(), txIds, true, tests.ExpectedInclusionState{
+		Confirmed: tests.True(),
+	})
+
+	// 5. check ledger state
+	tests.CheckBalances(t, n.Peers(), addrBalance)
+}
+
+// TestValueColoredPersistence issues colored tokens on random peers, restarts them and checks for persistence after restart.
+func TestValueColoredPersistence(t *testing.T) {
+	n, err := f.CreateNetwork("valueColor_TestPersistence", 4, 2)
+	require.NoError(t, err)
+	defer tests.ShutdownNetwork(t, n)
+
+	// wait for peers to change their state to synchronized
+	time.Sleep(5 * time.Second)
+
+	// master node sends funds to all peers in the network
+	txIdsSlice, addrBalance := tests.SendValueMessagesOnFaucet(t, n.Peers())
+	txIds := make(map[string]*tests.ExpectedTransaction)
+	for _, txID := range txIdsSlice {
+		txIds[txID] = nil
+	}
+
+	// wait for messages to be gossiped
+	time.Sleep(10 * time.Second)
+
+	// check whether the transactions are available on all nodes, and confirmed
+	tests.CheckTransactions(t, n.Peers(), txIds, true, tests.ExpectedInclusionState{
+		Confirmed: tests.True(),
+	})
+
+	// check ledger state
+	tests.CheckBalances(t, n.Peers(), addrBalance)
+
+	// send funds around
+	randomTxIds := tests.SendColoredValueMessagesOnRandomPeer(t, n.Peers(), addrBalance, 10)
+	for _, randomTxId := range randomTxIds {
+		txIds[randomTxId] = nil
+	}
+
+	// wait for value messages to be gossiped
+	time.Sleep(10 * time.Second)
+
+	// check whether all issued transactions are persistently available on all nodes, and confirmed
+	tests.CheckTransactions(t, n.Peers(), txIds, true, tests.ExpectedInclusionState{
+		Confirmed: tests.True(),
+	})
+
+	// check ledger state
+	tests.CheckBalances(t, n.Peers(), addrBalance)
+
+	// stop all nodes
+	for _, peer := range n.Peers() {
+		err = peer.Stop()
+		require.NoError(t, err)
+	}
+
+	// start all nodes
+	for _, peer := range n.Peers() {
+		err = peer.Start()
+		require.NoError(t, err)
+	}
+
+	// wait for peers to start
+	time.Sleep(10 * time.Second)
+
+	// check whether all issued transactions are persistently available on all nodes, and confirmed
+	tests.CheckTransactions(t, n.Peers(), txIds, true, tests.ExpectedInclusionState{
+		Confirmed: tests.True(),
+	})
+
+	// 5. check ledger state
+	tests.CheckBalances(t, n.Peers(), addrBalance)
+}