Skip to content
Snippets Groups Projects
Select Git revision
  • b04ec534995bba84ad8532e154ee1efc85f38251
  • without_tipselection default
  • develop protected
  • fix/grafana-local-dashboard
  • wasp
  • fix/dashboard-explorer-freeze
  • master
  • feat/timerqueue
  • test/sync_debug_and_650
  • feat/sync_revamp_inv
  • wip/sync
  • tool/db-recovery
  • portcheck/fix
  • fix/synchronization
  • feat/new-dashboard-analysis
  • feat/refactored-analysis-dashboard
  • feat/new-analysis-dashboard
  • test/demo-prometheus-fpc
  • prometheus_metrics
  • wip/analysis-server
  • merge/fpc-test-value-transfer
  • v0.2.2
  • v0.2.1
  • v0.2.0
  • v0.1.3
  • v0.1.2
  • v0.1.1
  • v0.1.0
28 results

create-volume.sh

Blame
  • transaction.go 12.48 KiB
    package transaction
    
    import (
    	"fmt"
    	"sync"
    
    	"github.com/iotaledger/hive.go/marshalutil"
    	"github.com/iotaledger/hive.go/objectstorage"
    	"github.com/iotaledger/hive.go/stringify"
    	"github.com/mr-tron/base58"
    	"golang.org/x/crypto/blake2b"
    
    	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address"
    	"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address/signaturescheme"
    )
    
    // region IMPLEMENT Transaction ///////////////////////////////////////////////////////////////////////////////////////////
    
    type Transaction struct {
    	objectstorage.StorableObjectFlags
    
    	inputs     *Inputs
    	outputs    *Outputs
    	signatures *Signatures
    
    	id      *Id
    	idMutex sync.RWMutex
    
    	essenceBytes      []byte
    	essenceBytesMutex sync.RWMutex
    
    	signatureBytes      []byte
    	signatureBytesMutex sync.RWMutex
    
    	dataPayloadType  uint32
    	dataPayload      []byte
    	dataPayloadMutex sync.RWMutex
    
    	bytes      []byte
    	bytesMutex sync.RWMutex
    }
    
    func New(inputs *Inputs, outputs *Outputs) *Transaction {
    	return &Transaction{
    		inputs:     inputs,
    		outputs:    outputs,
    		signatures: NewSignatures(),
    	}
    }
    
    func FromBytes(bytes []byte, optionalTargetObject ...*Transaction) (result *Transaction, err error, consumedBytes int) {
    	marshalUtil := marshalutil.New(bytes)
    	result, err = Parse(marshalUtil, optionalTargetObject...)
    	consumedBytes = marshalUtil.ReadOffset()
    
    	return
    }
    
    func FromStorageKey(key []byte, optionalTargetObject ...*Transaction) (result objectstorage.StorableObject, err error, consumedBytes int) {
    	// determine the target object that will hold the unmarshaled information
    	switch len(optionalTargetObject) {
    	case 0:
    		result = &Transaction{}
    	case 1:
    		result = optionalTargetObject[0]
    	default:
    		panic("too many arguments in call to FromStorageKey")
    	}
    
    	marshalUtil := marshalutil.New(key)
    	id, err := ParseId(marshalUtil)
    	if err != nil {
    		return
    	}
    	result.(*Transaction).id = &id
    
    	return
    }
    
    func Parse(marshalUtil *marshalutil.MarshalUtil, optionalTargetObject ...*Transaction) (result *Transaction, err error) {
    	// determine the target object that will hold the unmarshaled information
    	switch len(optionalTargetObject) {
    	case 0:
    		result = &Transaction{}
    	case 1:
    		result = optionalTargetObject[0]
    	default:
    		panic("too many arguments in call to Parse")
    	}
    
    	if _, err = marshalUtil.Parse(func(data []byte) (parseResult interface{}, parseErr error, parsedBytes int) {
    		parseErr, parsedBytes = result.UnmarshalObjectStorageValue(data)
    
    		return
    	}); err != nil {
    		return
    	}
    
    	return
    }
    
    func (transaction *Transaction) Id() Id {
    	// acquire lock for reading id
    	transaction.idMutex.RLock()
    
    	// return if id has been calculated already
    	if transaction.id != nil {
    		defer transaction.idMutex.RUnlock()
    
    		return *transaction.id
    	}
    
    	// switch to write lock
    	transaction.idMutex.RUnlock()
    	transaction.idMutex.Lock()
    	defer transaction.idMutex.Unlock()
    
    	// return if id has been calculated in the mean time
    	if transaction.id != nil {
    		return *transaction.id
    	}
    
    	// otherwise calculate the id
    	idBytes := blake2b.Sum256(transaction.Bytes())
    	id, err, _ := IdFromBytes(idBytes[:])
    	if err != nil {
    		panic(err)
    	}
    
    	// cache result for later calls
    	transaction.id = &id
    
    	return id
    }
    
    func (transaction *Transaction) Inputs() *Inputs {
    	return transaction.inputs
    }
    
    func (transaction *Transaction) Outputs() *Outputs {
    	return transaction.outputs
    }
    
    func (transaction *Transaction) SignaturesValid() bool {
    	signaturesValid := true
    	transaction.inputs.ForEachAddress(func(address address.Address) bool {
    		if signature, exists := transaction.signatures.Get(address); !exists || !signature.IsValid(transaction.EssenceBytes()) {
    			signaturesValid = false
    
    			return false
    		}
    
    		return true
    	})
    
    	return signaturesValid
    }
    
    func (transaction *Transaction) EssenceBytes() []byte {
    	// acquire read lock on essenceBytes
    	transaction.essenceBytesMutex.RLock()
    
    	// return essenceBytes if the object has been marshaled already
    	if transaction.essenceBytes != nil {
    		defer transaction.essenceBytesMutex.RUnlock()
    
    		return transaction.essenceBytes
    	}
    
    	// switch to write lock
    	transaction.essenceBytesMutex.RUnlock()
    	transaction.essenceBytesMutex.Lock()
    	defer transaction.essenceBytesMutex.Unlock()
    
    	// return essenceBytes if the object has been marshaled in the mean time
    	if essenceBytes := transaction.essenceBytes; essenceBytes != nil {
    		return essenceBytes
    	}
    
    	// create marshal helper
    	marshalUtil := marshalutil.New()
    
    	// marshal inputs
    	marshalUtil.WriteBytes(transaction.inputs.Bytes())
    
    	// marshal outputs
    	marshalUtil.WriteBytes(transaction.outputs.Bytes())
    
    	// marshal dataPayload type
    	marshalUtil.WriteUint32(transaction.dataPayloadType)
    
    	// marshal dataPayload size
    	marshalUtil.WriteUint32(transaction.DataPayloadSize())
    
    	// marshal dataPayload data
    	marshalUtil.WriteBytes(transaction.dataPayload)
    
    	// store marshaled result
    	transaction.essenceBytes = marshalUtil.Bytes()
    
    	return transaction.essenceBytes
    }
    
    func (transaction *Transaction) SignatureBytes() []byte {
    	transaction.signatureBytesMutex.RLock()
    	if transaction.signatureBytes != nil {
    		defer transaction.signatureBytesMutex.RUnlock()
    
    		return transaction.signatureBytes
    	}
    
    	transaction.signatureBytesMutex.RUnlock()
    	transaction.signatureBytesMutex.Lock()
    	defer transaction.signatureBytesMutex.Unlock()
    
    	if transaction.signatureBytes != nil {
    		return transaction.signatureBytes
    	}
    
    	// generate signatures
    	transaction.signatureBytes = transaction.signatures.Bytes()
    
    	return transaction.signatureBytes
    }
    
    func (transaction *Transaction) Bytes() []byte {
    	// acquire read lock on bytes
    	transaction.bytesMutex.RLock()
    
    	// return bytes if the object has been marshaled already
    	if transaction.bytes != nil {
    		defer transaction.bytesMutex.RUnlock()
    
    		return transaction.bytes
    	}
    
    	// switch to write lock
    	transaction.bytesMutex.RUnlock()
    	transaction.bytesMutex.Lock()
    	defer transaction.bytesMutex.Unlock()
    
    	// return bytes if the object has been marshaled in the mean time
    	if bytes := transaction.bytes; bytes != nil {
    		return bytes
    	}
    
    	// create marshal helper
    	marshalUtil := marshalutil.New()
    
    	// marshal essence bytes
    	marshalUtil.WriteBytes(transaction.EssenceBytes())
    
    	// marshal signature bytes
    	marshalUtil.WriteBytes(transaction.SignatureBytes())
    
    	// store marshaled result
    	transaction.bytes = marshalUtil.Bytes()
    
    	return transaction.bytes
    }
    
    func (transaction *Transaction) Sign(signature signaturescheme.SignatureScheme) *Transaction {
    	transaction.signatures.Add(signature.Address(), signature.Sign(transaction.EssenceBytes()))
    
    	return transaction
    }
    
    func (transaction *Transaction) String() string {
    	id := transaction.Id()
    
    	return stringify.Struct("Transaction"+fmt.Sprintf("(%p)", transaction),
    		stringify.StructField("id", base58.Encode(id[:])),
    		stringify.StructField("inputs", transaction.inputs),
    		stringify.StructField("outputs", transaction.outputs),
    		stringify.StructField("signatures", transaction.signatures),
    		stringify.StructField("dataPayloadSize", transaction.DataPayloadSize()),
    	)
    }
    
    // max dataPayload size limit
    const MAX_DATA_PAYLOAD_SIZE = 64 * 1024
    
    // sets yhe dataPayload and its type
    func (transaction *Transaction) SetDataPayload(data []byte, payloadType uint32) error {
    	transaction.dataPayloadMutex.Lock()
    	defer transaction.dataPayloadMutex.Unlock()
    
    	if len(data) > MAX_DATA_PAYLOAD_SIZE {
    		return fmt.Errorf("maximum dataPayload size of %d bytes exceeded", MAX_DATA_PAYLOAD_SIZE)
    	}
    	transaction.dataPayload = data
    	transaction.dataPayloadType = payloadType
    	return nil
    }
    
    // gets the dataPayload and its type
    func (transaction *Transaction) GetDataPayload() ([]byte, uint32) {
    	transaction.dataPayloadMutex.RLock()
    	defer transaction.dataPayloadMutex.RUnlock()
    
    	return transaction.dataPayload, transaction.dataPayloadType
    }
    
    // return size of the dataPayload as uint32
    // nil payload as size 0
    func (transaction *Transaction) DataPayloadSize() uint32 {
    	transaction.dataPayloadMutex.RLock()
    	defer transaction.dataPayloadMutex.RUnlock()
    
    	return uint32(len(transaction.dataPayload))
    }
    
    // endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    // region IMPLEMENT StorableObject interface ///////////////////////////////////////////////////////////////////////////
    
    // define contract (ensure that the struct fulfills the given interface)
    var _ objectstorage.StorableObject = &Transaction{}
    
    func (transaction *Transaction) ObjectStorageKey() []byte {
    	return transaction.Id().Bytes()
    }
    
    func (transaction *Transaction) Update(other objectstorage.StorableObject) {
    	panic("update forbidden")
    }
    
    // ObjectStorageValue returns a bytes representation of the Transaction by implementing the encoding.BinaryMarshaler interface.
    func (transaction *Transaction) ObjectStorageValue() []byte {
    	return transaction.Bytes()
    }
    
    func (transaction *Transaction) UnmarshalObjectStorageValue(bytes []byte) (err error, consumedBytes int) {
    	// initialize helper
    	marshalUtil := marshalutil.New(bytes)
    
    	// unmarshal inputs
    	parsedInputs, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return InputsFromBytes(data) })
    	if err != nil {
    		return
    	}
    	transaction.inputs = parsedInputs.(*Inputs)
    
    	// unmarshal outputs
    	parsedOutputs, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return OutputsFromBytes(data) })
    	if err != nil {
    		return
    	}
    	transaction.outputs = parsedOutputs.(*Outputs)
    
    	// unmarshal data payload type
    	transaction.dataPayloadType, err = marshalUtil.ReadUint32()
    	if err != nil {
    		return
    	}
    
    	// unmarshal data payload size
    	var dataPayloadSize uint32
    	dataPayloadSize, err = marshalUtil.ReadUint32()
    	if err != nil {
    		return
    	}
    	if dataPayloadSize > MAX_DATA_PAYLOAD_SIZE {
    		err = fmt.Errorf("data payload size of %d bytes exceeds maximum limit of %d bytes",
    			dataPayloadSize, MAX_DATA_PAYLOAD_SIZE)
    		return
    	}
    
    	// unmarshal data payload
    	transaction.dataPayload, err = marshalUtil.ReadBytes(int(dataPayloadSize))
    	if err != nil {
    		return
    	}
    
    	// store essence bytes
    	essenceBytesCount := marshalUtil.ReadOffset()
    	transaction.essenceBytes = make([]byte, essenceBytesCount)
    	copy(transaction.essenceBytes, bytes[:essenceBytesCount])
    
    	// unmarshal outputs
    	parsedSignatures, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return SignaturesFromBytes(data) })
    	if err != nil {
    		return
    	}
    	transaction.signatures = parsedSignatures.(*Signatures)
    
    	// store signature bytes
    	signatureBytesCount := marshalUtil.ReadOffset() - essenceBytesCount
    	transaction.signatureBytes = make([]byte, signatureBytesCount)
    	copy(transaction.signatureBytes, bytes[essenceBytesCount:essenceBytesCount+signatureBytesCount])
    
    	// return the number of bytes we processed
    	consumedBytes = essenceBytesCount + signatureBytesCount
    
    	// store bytes, so we don't have to marshal manually
    	transaction.bytes = bytes[:consumedBytes]
    
    	return
    }
    
    // endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    // CachedTransaction is a wrapper for the object storage, that takes care of type casting the Transaction objects.
    // Since go does not have generics (yet), the object storage works based on the generic "interface{}" type, which means
    // that we have to regularly type cast the returned objects, to match the expected type. To reduce the burden of
    // manually managing these type, we create a wrapper that does this for us. This way, we can consistently handle the
    // specialized types of Transaction, without having to manually type cast over and over again.
    type CachedTransaction struct {
    	objectstorage.CachedObject
    }
    
    // Retain overrides the underlying method to return a new CachedTransaction instead of a generic CachedObject.
    func (cachedTransaction *CachedTransaction) Retain() *CachedTransaction {
    	return &CachedTransaction{cachedTransaction.CachedObject.Retain()}
    }
    
    // Consume  overrides the underlying method to use a CachedTransaction object instead of a generic CachedObject in the
    // consumer).
    func (cachedTransaction *CachedTransaction) Consume(consumer func(metadata *Transaction)) bool {
    	return cachedTransaction.CachedObject.Consume(func(object objectstorage.StorableObject) {
    		consumer(object.(*Transaction))
    	})
    }
    
    // Unwrap provides a way to retrieve a type casted version of the underlying object.
    func (cachedTransaction *CachedTransaction) Unwrap() *Transaction {
    	if untypedTransaction := cachedTransaction.Get(); untypedTransaction == nil {
    		return nil
    	} else {
    		if typeCastedTransaction := untypedTransaction.(*Transaction); typeCastedTransaction == nil || typeCastedTransaction.IsDeleted() {
    			return nil
    		} else {
    			return typeCastedTransaction
    		}
    	}
    }