Skip to content
Snippets Groups Projects
  • Hans Moog's avatar
    b9404478
    Feat: started reworking output model (#311) · b9404478
    Hans Moog authored
    * Feat: started reworking output model
    
    * Refactor: refactored some of the model
    
    * Refactor: started to refactor some additional models
    
    * Refactor: started to refactor message layer
    
    * Refactor: still refactoring :/
    
    * Refactor: refactored some more
    
    * Refactor: some error messages are gone YAY
    
    * Refactor: refactor complete
    Feat: started reworking output model (#311)
    Hans Moog authored
    * Feat: started reworking output model
    
    * Refactor: refactored some of the model
    
    * Refactor: started to refactor some additional models
    
    * Refactor: started to refactor message layer
    
    * Refactor: still refactoring :/
    
    * Refactor: refactored some more
    
    * Refactor: some error messages are gone YAY
    
    * Refactor: refactor complete
message.go 9.75 KiB
package message

import (
	"sync"
	"time"

	"github.com/iotaledger/hive.go/marshalutil"
	"github.com/iotaledger/hive.go/objectstorage"
	"github.com/iotaledger/hive.go/stringify"

	"github.com/iotaledger/goshimmer/packages/binary/messagelayer/payload"
	"github.com/iotaledger/goshimmer/packages/binary/signature/ed25119"

	"github.com/mr-tron/base58"

	"golang.org/x/crypto/blake2b"
)

// region Message //////////////////////////////////////////////////////////////////////////////////////////////////////

type Message struct {
	// base functionality of StorableObject
	objectstorage.StorableObjectFlags

	// core properties (they are part of the transaction when being sent)
	trunkTransactionId  Id
	branchTransactionId Id
	issuerPublicKey     ed25119.PublicKey
	issuingTime         time.Time
	sequenceNumber      uint64
	payload             payload.Payload
	bytes               []byte
	bytesMutex          sync.RWMutex
	signature           ed25119.Signature
	signatureMutex      sync.RWMutex

	// derived properties
	id             *Id
	idMutex        sync.RWMutex
	payloadId      *payload.Id
	payloadIdMutex sync.RWMutex

	// only stored on the machine of the signer
	issuerPrivateKey ed25119.PrivateKey
}

// New creates a new transaction with the details provided by the issuer.
func New(trunkTransactionId Id, branchTransactionId Id, issuerKeyPair ed25119.KeyPair, issuingTime time.Time, sequenceNumber uint64, payload payload.Payload) (result *Message) {
	return &Message{
		trunkTransactionId:  trunkTransactionId,
		branchTransactionId: branchTransactionId,
		issuerPublicKey:     issuerKeyPair.PublicKey,
		issuingTime:         issuingTime,
		sequenceNumber:      sequenceNumber,
		payload:             payload,

		issuerPrivateKey: issuerKeyPair.PrivateKey,
	}
}

func FromBytes(bytes []byte, optionalTargetObject ...*Message) (result *Message, err error, consumedBytes int) {
	marshalUtil := marshalutil.New(bytes)
	result, err = Parse(marshalUtil, optionalTargetObject...)
	consumedBytes = marshalUtil.ReadOffset()

	return
}

func Parse(marshalUtil *marshalutil.MarshalUtil, optionalTargetObject ...*Message) (result *Message, err error) {
	// determine the target object that will hold the unmarshaled information
	switch len(optionalTargetObject) {
	case 0:
		result = &Message{}
	case 1:
		result = optionalTargetObject[0]
	default:
		panic("too many arguments in call to Parse")
	}

	if parsedObject, parseErr := marshalUtil.Parse(func(data []byte) (interface{}, error, int) {
		return StorableObjectFromKey(data)
	}); parseErr != nil {
		err = parseErr

		return
	} else {
		result = parsedObject.(*Message)
	}
	if _, err = marshalUtil.Parse(func(data []byte) (parseResult interface{}, parseErr error, parsedBytes int) {
		parseErr, parsedBytes = result.UnmarshalObjectStorageValue(data)

		return
	}); err != nil {
		return
	}

	return
}

// Get's called when we restore a transaction from storage. The bytes and the content will be unmarshaled by an external
// caller (the objectStorage factory).
func StorableObjectFromKey(key []byte, optionalTargetObject ...*Message) (result objectstorage.StorableObject, err error, consumedBytes int) {
	// determine the target object that will hold the unmarshaled information
	switch len(optionalTargetObject) {
	case 0:
		result = &Message{}
	case 1:
		result = optionalTargetObject[0]
	default:
		panic("too many arguments in call to StorableObjectFromKey")
	}

	marshalUtil := marshalutil.New(key)
	*result.(*Message).id, err = ParseId(marshalUtil)
	if err != nil {
		return
	}
	consumedBytes = marshalUtil.ReadOffset()

	return
}

func (transaction *Message) VerifySignature() (result bool) {
	transactionBytes := transaction.Bytes()

	transaction.signatureMutex.RLock()
	result = transaction.issuerPublicKey.VerifySignature(transactionBytes[:len(transactionBytes)-ed25119.SignatureSize], transaction.signature)
	transaction.signatureMutex.RUnlock()

	return
}

func (transaction *Message) GetId() (result Id) {
	transaction.idMutex.RLock()
	if transaction.id == nil {
		transaction.idMutex.RUnlock()

		transaction.idMutex.Lock()
		if transaction.id == nil {
			result = transaction.calculateTransactionId()

			transaction.id = &result
		} else {
			result = *transaction.id
		}
		transaction.idMutex.Unlock()
	} else {
		result = *transaction.id

		transaction.idMutex.RUnlock()
	}

	return
}

func (transaction *Message) GetTrunkTransactionId() Id {
	return transaction.trunkTransactionId
}

func (transaction *Message) GetBranchTransactionId() Id {
	return transaction.branchTransactionId
}

// IssuingTime returns the time when the transaction was created.
func (transaction *Message) IssuingTime() time.Time {
	return transaction.issuingTime
}

// SequenceNumber returns the sequence number of this transaction.
func (transaction *Message) SequenceNumber() uint64 {
	return transaction.sequenceNumber
}

func (transaction *Message) GetPayload() payload.Payload {
	return transaction.payload
}

func (transaction *Message) GetPayloadId() (result payload.Id) {
	transaction.payloadIdMutex.RLock()
	if transaction.payloadId == nil {
		transaction.payloadIdMutex.RUnlock()

		transaction.payloadIdMutex.Lock()
		if transaction.payloadId == nil {
			result = transaction.calculatePayloadId()

			transaction.payloadId = &result
		} else {
			result = *transaction.payloadId
		}
		transaction.payloadIdMutex.Unlock()
	} else {
		result = *transaction.payloadId

		transaction.payloadIdMutex.RUnlock()
	}

	return
}

func (transaction *Message) calculateTransactionId() Id {
	payloadId := transaction.GetPayloadId()

	hashBase := make([]byte, IdLength+IdLength+payload.IdLength)
	offset := 0

	copy(hashBase[offset:], transaction.trunkTransactionId[:])
	offset += IdLength

	copy(hashBase[offset:], transaction.branchTransactionId[:])
	offset += IdLength

	copy(hashBase[offset:], payloadId[:])
	// offset += payloadIdLength

	return blake2b.Sum512(hashBase)
}

func (transaction *Message) calculatePayloadId() payload.Id {
	bytes := transaction.Bytes()

	return blake2b.Sum512(bytes[2*IdLength:])
}

func (transaction *Message) Bytes() []byte {
	transaction.bytesMutex.RLock()
	if transaction.bytes != nil {
		defer transaction.bytesMutex.RUnlock()

		return transaction.bytes
	}

	transaction.bytesMutex.RUnlock()
	transaction.bytesMutex.RLock()
	defer transaction.bytesMutex.RUnlock()

	if transaction.bytes != nil {
		return transaction.bytes
	}

	// marshal result
	marshalUtil := marshalutil.New()
	marshalUtil.WriteBytes(transaction.trunkTransactionId.Bytes())
	marshalUtil.WriteBytes(transaction.branchTransactionId.Bytes())
	marshalUtil.WriteBytes(transaction.issuerPublicKey.Bytes())
	marshalUtil.WriteTime(transaction.issuingTime)
	marshalUtil.WriteUint64(transaction.sequenceNumber)
	marshalUtil.WriteBytes(transaction.payload.Bytes())
	marshalUtil.WriteBytes(transaction.issuerPrivateKey.Sign(marshalUtil.Bytes()).Bytes())

	return marshalUtil.Bytes()
}

func (transaction *Message) UnmarshalObjectStorageValue(data []byte) (err error, consumedBytes int) {
	// initialize helper
	marshalUtil := marshalutil.New(data)

	// parse information
	if transaction.trunkTransactionId, err = ParseId(marshalUtil); err != nil {
		return
	}
	if transaction.branchTransactionId, err = ParseId(marshalUtil); err != nil {
		return
	}
	if transaction.issuerPublicKey, err = ed25119.ParsePublicKey(marshalUtil); err != nil {
		return
	}
	if transaction.issuingTime, err = marshalUtil.ReadTime(); err != nil {
		return
	}
	if transaction.sequenceNumber, err = marshalUtil.ReadUint64(); err != nil {
		return
	}
	if transaction.payload, err = payload.Parse(marshalUtil); err != nil {
		return
	}
	if transaction.signature, err = ed25119.ParseSignature(marshalUtil); err != nil {
		return
	}
	// return the number of bytes we processed
	consumedBytes = marshalUtil.ReadOffset()

	// store marshaled version
	transaction.bytes = make([]byte, consumedBytes)
	copy(transaction.bytes, data)

	return
}

func (transaction *Message) ObjectStorageKey() []byte {
	return transaction.GetId().Bytes()
}

// Since transactions are immutable and do not get changed after being created, we cache the result of the marshaling.
func (transaction *Message) ObjectStorageValue() []byte {
	return transaction.Bytes()
}

func (transaction *Message) Update(other objectstorage.StorableObject) {
	panic("transactions should never be overwritten and only stored once to optimize IO")
}

func (transaction *Message) String() string {
	transactionId := transaction.GetId()

	return stringify.Struct("Message",
		stringify.StructField("id", base58.Encode(transactionId[:])),
		stringify.StructField("trunkTransactionId", base58.Encode(transaction.trunkTransactionId[:])),
		stringify.StructField("trunkTransactionId", base58.Encode(transaction.branchTransactionId[:])),
		stringify.StructField("payload", transaction.payload),
	)
}

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////

// region CachedMessage ////////////////////////////////////////////////////////////////////////////////////////////////

type CachedMessage struct {
	objectstorage.CachedObject
}

func (cachedMessage *CachedMessage) Retain() *CachedMessage {
	return &CachedMessage{cachedMessage.CachedObject.Retain()}
}

func (cachedMessage *CachedMessage) Consume(consumer func(transaction *Message)) bool {
	return cachedMessage.CachedObject.Consume(func(object objectstorage.StorableObject) {
		consumer(object.(*Message))
	})
}

func (cachedMessage *CachedMessage) Unwrap() *Message {
	if untypedTransaction := cachedMessage.Get(); untypedTransaction == nil {
		return nil
	} else {
		if typeCastedTransaction := untypedTransaction.(*Message); typeCastedTransaction == nil || typeCastedTransaction.IsDeleted() {
			return nil
		} else {
			return typeCastedTransaction
		}
	}
}

// endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////