From 453479bf3cc1bb9206dbc3848bdd8b5a413625b4 Mon Sep 17 00:00:00 2001
From: Hans Moog <hm@mkjc.net>
Date: Fri, 5 Jul 2019 21:22:02 +0200
Subject: [PATCH] Feat: finished bundle model

---
 packages/model/approvers/approvers_test.go |   2 +-
 packages/model/bundle/bundle.go            | 146 ++++++++++++++++++---
 packages/model/bundle/bundle_test.go       |  56 ++++++++
 packages/model/bundle/constants.go         |  20 +++
 packages/model/bundle/errors.go            |  10 ++
 packages/model/bundle/hasher.go            |  14 --
 plugins/bundleprocessor/plugin.go          |  33 +++--
 7 files changed, 230 insertions(+), 51 deletions(-)
 create mode 100644 packages/model/bundle/bundle_test.go
 create mode 100644 packages/model/bundle/constants.go
 create mode 100644 packages/model/bundle/errors.go
 delete mode 100644 packages/model/bundle/hasher.go

diff --git a/packages/model/approvers/approvers_test.go b/packages/model/approvers/approvers_test.go
index 61cfef50..5436330c 100644
--- a/packages/model/approvers/approvers_test.go
+++ b/packages/model/approvers/approvers_test.go
@@ -18,7 +18,7 @@ func TestApprovers_SettersGetters(t *testing.T) {
 	assert.Equal(t, approversTest.GetHashes()[0], hashB, "hashes")
 }
 
-func TestApprovers_MarshalUnmarshalGetters(t *testing.T) {
+func TestApprovers_MarshalUnmarshal(t *testing.T) {
 	hashA := ternary.Trytes("A9999999999999999999999999999999999999999999999999999999999999999999999999999999F")
 	hashB := ternary.Trytes("B9999999999999999999999999999999999999999999999999999999999999999999999999999999F")
 	approversTest := New(hashA)
diff --git a/packages/model/bundle/bundle.go b/packages/model/bundle/bundle.go
index 36c50cb9..88ff6cb1 100644
--- a/packages/model/bundle/bundle.go
+++ b/packages/model/bundle/bundle.go
@@ -1,22 +1,29 @@
 package bundle
 
 import (
+	"encoding/binary"
+	"strconv"
 	"sync"
+	"unsafe"
 
+	"github.com/iotaledger/goshimmer/packages/bitutils"
 	"github.com/iotaledger/goshimmer/packages/errors"
+	"github.com/iotaledger/goshimmer/packages/typeutils"
 
-	"github.com/iotaledger/goshimmer/packages/model/value_transaction"
 	"github.com/iotaledger/goshimmer/packages/ternary"
 )
 
 type Bundle struct {
-	hash               ternary.Trytes
-	transactionHashes  []ternary.Trytes
-	isValueBundle      bool
-	isValueBundleMutex sync.RWMutex
-	bundleEssenceHash  ternary.Trytes
-	modified           bool
-	modifiedMutex      sync.RWMutex
+	hash                   ternary.Trytes
+	hashMutex              sync.RWMutex
+	transactionHashes      []ternary.Trytes
+	transactionHashesMutex sync.RWMutex
+	isValueBundle          bool
+	isValueBundleMutex     sync.RWMutex
+	bundleEssenceHash      ternary.Trytes
+	bundleEssenceHashMutex sync.RWMutex
+	modified               bool
+	modifiedMutex          sync.RWMutex
 }
 
 func New(headTransactionHash ternary.Trytes) (result *Bundle) {
@@ -27,12 +34,32 @@ func New(headTransactionHash ternary.Trytes) (result *Bundle) {
 	return
 }
 
-func (bundle *Bundle) GetTransactionHashes() []ternary.Trytes {
-	return bundle.transactionHashes
+func (bundle *Bundle) GetHash() (result ternary.Trytes) {
+	bundle.hashMutex.RLock()
+	result = bundle.hash
+	bundle.hashMutex.RUnlock()
+
+	return
+}
+
+func (bundle *Bundle) SetHash(hash ternary.Trytes) {
+	bundle.hashMutex.Lock()
+	bundle.hash = hash
+	bundle.hashMutex.Unlock()
+}
+
+func (bundle *Bundle) GetTransactionHashes() (result []ternary.Trytes) {
+	bundle.bundleEssenceHashMutex.RLock()
+	result = bundle.transactionHashes
+	bundle.bundleEssenceHashMutex.RUnlock()
+
+	return
 }
 
-func (bundle *Bundle) GetHash() ternary.Trytes {
-	return bundle.hash
+func (bundle *Bundle) SetTransactionHashes(transactionHashes []ternary.Trytes) {
+	bundle.transactionHashesMutex.Lock()
+	bundle.transactionHashes = transactionHashes
+	bundle.transactionHashesMutex.Unlock()
 }
 
 func (bundle *Bundle) IsValueBundle() (result bool) {
@@ -49,6 +76,20 @@ func (bundle *Bundle) SetValueBundle(valueBundle bool) {
 	bundle.isValueBundleMutex.Unlock()
 }
 
+func (bundle *Bundle) GetBundleEssenceHash() (result ternary.Trytes) {
+	bundle.bundleEssenceHashMutex.RLock()
+	result = bundle.bundleEssenceHash
+	bundle.bundleEssenceHashMutex.RUnlock()
+
+	return
+}
+
+func (bundle *Bundle) SetBundleEssenceHash(bundleEssenceHash ternary.Trytes) {
+	bundle.bundleEssenceHashMutex.Lock()
+	bundle.bundleEssenceHash = bundleEssenceHash
+	bundle.bundleEssenceHashMutex.Unlock()
+}
+
 func (bundle *Bundle) GetModified() (result bool) {
 	bundle.modifiedMutex.RLock()
 	result = bundle.modified
@@ -63,14 +104,81 @@ func (bundle *Bundle) SetModified(modified bool) {
 	bundle.modifiedMutex.Unlock()
 }
 
-func (bundle *Bundle) Marshal() []byte {
-	return nil
-}
+func (bundle *Bundle) Marshal() (result []byte) {
+	bundle.hashMutex.RLock()
+	bundle.bundleEssenceHashMutex.RLock()
+	bundle.isValueBundleMutex.RLock()
+	bundle.transactionHashesMutex.RLock()
+
+	result = make([]byte, MARSHALED_MIN_SIZE+len(bundle.transactionHashes)*MARSHALED_TRANSACTION_HASH_SIZE)
+
+	binary.BigEndian.PutUint64(result[MARSHALED_TRANSACTIONS_COUNT_START:MARSHALED_TRANSACTIONS_COUNT_END], uint64(len(bundle.transactionHashes)))
+
+	copy(result[MARSHALED_HASH_START:MARSHALED_HASH_END], bundle.hash.CastToBytes())
+	copy(result[MARSHALED_BUNDLE_ESSENCE_HASH_START:MARSHALED_BUNDLE_ESSENCE_HASH_END], bundle.bundleEssenceHash.CastToBytes())
+
+	var flags bitutils.BitMask
+	if bundle.isValueBundle {
+		flags = flags.SetFlag(0)
+	}
+	result[MARSHALED_FLAGS_START] = *(*byte)(unsafe.Pointer(&flags))
+
+	i := 0
+	for _, hash := range bundle.transactionHashes {
+		var HASH_START = MARSHALED_APPROVERS_HASHES_START + i*(MARSHALED_TRANSACTION_HASH_SIZE)
+		var HASH_END = HASH_START + MARSHALED_TRANSACTION_HASH_SIZE
+
+		copy(result[HASH_START:HASH_END], hash.CastToBytes())
 
-func (bundle *Bundle) Unmarshal(data []byte) errors.IdentifiableError {
-	return nil
+		i++
+	}
+
+	bundle.transactionHashesMutex.RUnlock()
+	bundle.isValueBundleMutex.RUnlock()
+	bundle.bundleEssenceHashMutex.RUnlock()
+	bundle.hashMutex.RUnlock()
+
+	return
 }
 
-func CalculateBundleHash(transactions []*value_transaction.ValueTransaction) ternary.Trytes {
-	return (<-Hasher.Hash(transactions[0].GetBundleEssence())).ToTrytes()
+func (bundle *Bundle) Unmarshal(data []byte) (err errors.IdentifiableError) {
+	dataLen := len(data)
+
+	if dataLen < MARSHALED_MIN_SIZE {
+		return ErrMarshallFailed.Derive(errors.New("unmarshall failed"), "marshaled bundle is too short")
+	}
+
+	hashesCount := binary.BigEndian.Uint64(data[MARSHALED_TRANSACTIONS_COUNT_START:MARSHALED_TRANSACTIONS_COUNT_END])
+
+	if dataLen < MARSHALED_MIN_SIZE+int(hashesCount)*MARSHALED_TRANSACTION_HASH_SIZE {
+		return ErrMarshallFailed.Derive(errors.New("unmarshall failed"), "marshaled bundle is too short for "+strconv.FormatUint(hashesCount, 10)+" transactions")
+	}
+
+	bundle.hashMutex.Lock()
+	bundle.bundleEssenceHashMutex.Lock()
+	bundle.isValueBundleMutex.Lock()
+	bundle.transactionHashesMutex.Lock()
+
+	bundle.hash = ternary.Trytes(typeutils.BytesToString(data[MARSHALED_HASH_START:MARSHALED_HASH_END]))
+	bundle.bundleEssenceHash = ternary.Trytes(typeutils.BytesToString(data[MARSHALED_BUNDLE_ESSENCE_HASH_START:MARSHALED_BUNDLE_ESSENCE_HASH_END]))
+
+	flags := bitutils.BitMask(data[MARSHALED_FLAGS_START])
+	if flags.HasFlag(0) {
+		bundle.isValueBundle = true
+	}
+
+	bundle.transactionHashes = make([]ternary.Trytes, hashesCount)
+	for i := uint64(0); i < hashesCount; i++ {
+		var HASH_START = MARSHALED_APPROVERS_HASHES_START + i*(MARSHALED_TRANSACTION_HASH_SIZE)
+		var HASH_END = HASH_START + MARSHALED_TRANSACTION_HASH_SIZE
+
+		bundle.transactionHashes[i] = ternary.Trytes(typeutils.BytesToString(data[HASH_START:HASH_END]))
+	}
+
+	bundle.transactionHashesMutex.Unlock()
+	bundle.isValueBundleMutex.Unlock()
+	bundle.bundleEssenceHashMutex.Unlock()
+	bundle.hashMutex.Unlock()
+
+	return
 }
diff --git a/packages/model/bundle/bundle_test.go b/packages/model/bundle/bundle_test.go
new file mode 100644
index 00000000..ee1b0e32
--- /dev/null
+++ b/packages/model/bundle/bundle_test.go
@@ -0,0 +1,56 @@
+package bundle
+
+import (
+	"testing"
+
+	"github.com/iotaledger/goshimmer/packages/ternary"
+	"github.com/magiconair/properties/assert"
+)
+
+func TestBundle_SettersGetters(t *testing.T) {
+	bundleHash := ternary.Trytes("A9999999999999999999999999999999999999999999999999999999999999999999999999999999F")
+	bundleEssenceHash := ternary.Trytes("B9999999999999999999999999999999999999999999999999999999999999999999999999999999F")
+	transactions := []ternary.Trytes{
+		bundleHash,
+		ternary.Trytes("C9999999999999999999999999999999999999999999999999999999999999999999999999999999F"),
+	}
+
+	testBundle := New(bundleHash)
+	testBundle.SetTransactionHashes(transactions)
+	testBundle.SetBundleEssenceHash(bundleEssenceHash)
+	testBundle.SetValueBundle(true)
+
+	assert.Equal(t, testBundle.GetHash(), bundleHash, "hash of source")
+	assert.Equal(t, testBundle.GetBundleEssenceHash(), bundleEssenceHash, "bundle essence hash of source")
+	assert.Equal(t, testBundle.IsValueBundle(), true, "value bundle of source")
+	assert.Equal(t, len(testBundle.GetTransactionHashes()), len(transactions), "# of transactions of source")
+	assert.Equal(t, testBundle.GetTransactionHashes()[0], transactions[0], "transaction[0] of source")
+	assert.Equal(t, testBundle.GetTransactionHashes()[1], transactions[1], "transaction[1] of source")
+}
+
+func TestBundle_SettersGettersMarshalUnmarshal(t *testing.T) {
+	bundleHash := ternary.Trytes("A9999999999999999999999999999999999999999999999999999999999999999999999999999999F")
+	bundleEssenceHash := ternary.Trytes("B9999999999999999999999999999999999999999999999999999999999999999999999999999999F")
+	transactions := []ternary.Trytes{
+		bundleHash,
+		ternary.Trytes("C9999999999999999999999999999999999999999999999999999999999999999999999999999999F"),
+	}
+
+	testBundle := New(bundleHash)
+	testBundle.SetTransactionHashes(transactions)
+	testBundle.SetBundleEssenceHash(bundleEssenceHash)
+	testBundle.SetValueBundle(true)
+
+	var bundleUnmarshaled Bundle
+	err := bundleUnmarshaled.Unmarshal(testBundle.Marshal())
+	if err != nil {
+		t.Error(err)
+	}
+
+	assert.Equal(t, bundleUnmarshaled.GetHash(), testBundle.GetHash(), "hash of target")
+	assert.Equal(t, bundleUnmarshaled.GetBundleEssenceHash(), testBundle.GetBundleEssenceHash(), "bundle essence hash of target")
+	assert.Equal(t, bundleUnmarshaled.IsValueBundle(), true, "value bundle of target")
+	assert.Equal(t, len(bundleUnmarshaled.GetTransactionHashes()), len(transactions), "# of transactions of target")
+	assert.Equal(t, bundleUnmarshaled.GetTransactionHashes()[0], transactions[0], "transaction[0] of target")
+	assert.Equal(t, bundleUnmarshaled.GetTransactionHashes()[1], transactions[1], "transaction[1] of target")
+}
diff --git a/packages/model/bundle/constants.go b/packages/model/bundle/constants.go
new file mode 100644
index 00000000..1575f582
--- /dev/null
+++ b/packages/model/bundle/constants.go
@@ -0,0 +1,20 @@
+package bundle
+
+const (
+	MARSHALED_TRANSACTIONS_COUNT_START  = 0
+	MARSHALED_HASH_START                = MARSHALED_TRANSACTIONS_COUNT_END
+	MARSHALED_BUNDLE_ESSENCE_HASH_START = MARSHALED_HASH_END
+	MARSHALED_FLAGS_START               = MARSHALED_BUNDLE_ESSENCE_HASH_END
+	MARSHALED_APPROVERS_HASHES_START    = MARSHALED_FLAGS_END
+
+	MARSHALED_TRANSACTIONS_COUNT_END  = MARSHALED_TRANSACTIONS_COUNT_START + MARSHALED_TRANSACTIONS_COUNT_SIZE
+	MARSHALED_HASH_END                = MARSHALED_HASH_START + MARSHALED_TRANSACTION_HASH_SIZE
+	MARSHALED_BUNDLE_ESSENCE_HASH_END = MARSHALED_BUNDLE_ESSENCE_HASH_START + MARSHALED_BUNDLE_ESSENCE_HASH_SIZE
+	MARSHALED_FLAGS_END               = MARSHALED_FLAGS_START + MARSHALED_FLAGS_SIZE
+
+	MARSHALED_TRANSACTIONS_COUNT_SIZE  = 8
+	MARSHALED_TRANSACTION_HASH_SIZE    = 81
+	MARSHALED_BUNDLE_ESSENCE_HASH_SIZE = 81
+	MARSHALED_FLAGS_SIZE               = 1
+	MARSHALED_MIN_SIZE                 = MARSHALED_TRANSACTIONS_COUNT_SIZE + MARSHALED_TRANSACTION_HASH_SIZE + MARSHALED_BUNDLE_ESSENCE_HASH_SIZE + MARSHALED_FLAGS_SIZE
+)
diff --git a/packages/model/bundle/errors.go b/packages/model/bundle/errors.go
new file mode 100644
index 00000000..c4805e6b
--- /dev/null
+++ b/packages/model/bundle/errors.go
@@ -0,0 +1,10 @@
+package bundle
+
+import (
+	"github.com/iotaledger/goshimmer/packages/errors"
+)
+
+var (
+	ErrUnmarshalFailed = errors.Wrap(errors.New("unmarshall failed"), "input data is corrupted")
+	ErrMarshallFailed  = errors.Wrap(errors.New("marshal failed"), "the source object contains invalid values")
+)
diff --git a/packages/model/bundle/hasher.go b/packages/model/bundle/hasher.go
deleted file mode 100644
index e1eccd1e..00000000
--- a/packages/model/bundle/hasher.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package bundle
-
-import (
-	"github.com/iotaledger/goshimmer/packages/curl"
-)
-
-const (
-	CURLP81_HASH_LENGTH = 243
-	CURLP81_ROUNDS      = 81
-)
-
-var (
-	Hasher = curl.NewBatchHasher(CURLP81_HASH_LENGTH, CURLP81_ROUNDS)
-)
diff --git a/plugins/bundleprocessor/plugin.go b/plugins/bundleprocessor/plugin.go
index 4139f233..30477231 100644
--- a/plugins/bundleprocessor/plugin.go
+++ b/plugins/bundleprocessor/plugin.go
@@ -31,8 +31,8 @@ func ProcessSolidBundleHead(headTransaction *value_transaction.ValueTransaction)
 			return nil, ErrProcessBundleFailed.Derive(errors.New("invalid parameter"), "transaction needs to be head of bundle")
 		}
 
-		// initialize result variables
-		processedBundle := bundle.New(headTransactionHash)
+		// initialize event variables
+		newBundle := bundle.New(headTransactionHash)
 		bundleTransactions := make([]*value_transaction.ValueTransaction, 0)
 
 		// iterate through trunk transactions until we reach the tail
@@ -40,9 +40,9 @@ func ProcessSolidBundleHead(headTransaction *value_transaction.ValueTransaction)
 		for {
 			// abort if we reached a previous head
 			if currentTransaction.IsHead() && currentTransaction != headTransaction {
-				processedBundle.SetTransactionHashes(mapTransactionsToTransactionHashes(bundleTransactions))
+				newBundle.SetTransactionHashes(mapTransactionsToTransactionHashes(bundleTransactions))
 
-				Events.InvalidBundleReceived.Trigger(processedBundle, bundleTransactions)
+				Events.InvalidBundleReceived.Trigger(newBundle, bundleTransactions)
 
 				return nil, ErrProcessBundleFailed.Derive(errors.New("invalid bundle found"), "missing bundle tail")
 			}
@@ -58,21 +58,21 @@ func ProcessSolidBundleHead(headTransaction *value_transaction.ValueTransaction)
 			currentTransactionMetadata.SetBundleHeadHash(headTransactionHash)
 
 			// update value bundle flag
-			if !processedBundle.IsValueBundle() && currentTransaction.GetValue() != 0 {
-				processedBundle.SetValueBundle(true)
+			if !newBundle.IsValueBundle() && currentTransaction.GetValue() != 0 {
+				newBundle.SetValueBundle(true)
 			}
 
 			// if we are done -> trigger events
 			if currentTransaction.IsTail() {
-				processedBundle.SetTransactionHashes(mapTransactionsToTransactionHashes(bundleTransactions))
+				newBundle.SetTransactionHashes(mapTransactionsToTransactionHashes(bundleTransactions))
 
-				if processedBundle.IsValueBundle() {
-					Events.ValueBundleReceived.Trigger(processedBundle, bundleTransactions)
+				if newBundle.IsValueBundle() {
+					Events.ValueBundleReceived.Trigger(newBundle, bundleTransactions)
 				} else {
-					Events.DataBundleReceived.Trigger(processedBundle, bundleTransactions)
+					Events.DataBundleReceived.Trigger(newBundle, bundleTransactions)
 				}
 
-				return processedBundle, nil
+				return newBundle, nil
 			}
 
 			// try to iterate to next turn
@@ -85,12 +85,11 @@ func ProcessSolidBundleHead(headTransaction *value_transaction.ValueTransaction)
 	})
 }
 
-func mapTransactionsToTransactionHashes(transactions []*value_transaction.ValueTransaction) []ternary.Trytes {
-	result := make([]ternary.Trytes, len(transactions))
-
-	for i, v := range transactions {
-		result[i] = v.GetHash()
+func mapTransactionsToTransactionHashes(transactions []*value_transaction.ValueTransaction) (result []ternary.Trytes) {
+	result = make([]ternary.Trytes, len(transactions))
+	for k, v := range transactions {
+		result[k] = v.GetHash()
 	}
 
-	return result
+	return
 }
-- 
GitLab