diff --git a/packages/binary/valuetransfer/address/address.go b/packages/binary/valuetransfer/address/address.go index 26bfa6f28daa4361897509849fc0ef4a58b3fb76..38fd22c166ddc8488766906c11fd7c93f029e869 100644 --- a/packages/binary/valuetransfer/address/address.go +++ b/packages/binary/valuetransfer/address/address.go @@ -34,6 +34,12 @@ func Random() (address Address) { return } +func RandomOfType(versionByte byte) Address { + ret := Random() + ret[0] = versionByte + return ret +} + // FromBase58 creates an address from a base58 encoded string. func FromBase58(base58String string) (address Address, err error) { // decode string diff --git a/packages/binary/valuetransfer/tangle/tangle.go b/packages/binary/valuetransfer/tangle/tangle.go index a1aef9b378268a9383397b412106ddbdd3e02ada..e9e51e71161e1baa7e0fee6b1319c7246d48b3e5 100644 --- a/packages/binary/valuetransfer/tangle/tangle.go +++ b/packages/binary/valuetransfer/tangle/tangle.go @@ -70,7 +70,7 @@ func (tangle *Tangle) AttachPayload(payload *payload.Payload) { tangle.storePayloadWorkerPool.Submit(func() { tangle.storePayloadWorker(payload) }) } -// GetPayload retrieves a payload from the object storage. +// GetDataPayload retrieves a payload from the object storage. func (tangle *Tangle) GetPayload(payloadId payload.Id) *payload.CachedPayload { return &payload.CachedPayload{CachedObject: tangle.payloadStorage.Load(payloadId.Bytes())} } diff --git a/packages/binary/valuetransfer/transaction/transaction.go b/packages/binary/valuetransfer/transaction/transaction.go index 04e3ab016273f6e34586f595f58aa4b8aefdbfc4..9c1560e3533c533857ed7fb7de0e678109684a64 100644 --- a/packages/binary/valuetransfer/transaction/transaction.go +++ b/packages/binary/valuetransfer/transaction/transaction.go @@ -32,6 +32,10 @@ type Transaction struct { signatureBytes []byte signatureBytesMutex sync.RWMutex + dataPayloadType uint32 + dataPayload []byte + dataPayloadMutex sync.RWMutex + bytes []byte bytesMutex sync.RWMutex } @@ -182,6 +186,15 @@ func (transaction *Transaction) EssenceBytes() []byte { // 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() @@ -260,9 +273,43 @@ func (transaction *Transaction) String() string { 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 /////////////////////////////////////////////////////////////////////////// @@ -301,6 +348,30 @@ func (transaction *Transaction) UnmarshalObjectStorageValue(bytes []byte) (err e } 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) diff --git a/packages/binary/valuetransfer/transaction/transaction_test.go b/packages/binary/valuetransfer/transaction/transaction_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ec16af7f8104b1329bafe781ca3c4be6b2718185 --- /dev/null +++ b/packages/binary/valuetransfer/transaction/transaction_test.go @@ -0,0 +1,128 @@ +package transaction + +import ( + "bytes" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address/signaturescheme" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/balance" + "github.com/iotaledger/hive.go/crypto/ed25519" + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +func TestEmptyDataPayload(t *testing.T) { + sigScheme := signaturescheme.ED25519(ed25519.GenerateKeyPair()) + addr := sigScheme.Address() + o1 := NewOutputId(addr, RandomId()) + inputs := NewInputs(o1) + bal := balance.New(balance.COLOR_IOTA, 1) + outputs := NewOutputs(map[address.Address][]*balance.Balance{addr: {bal}}) + tx := New(inputs, outputs) + tx.Sign(sigScheme) + check := tx.SignaturesValid() + + assert.Equal(t, true, check) +} + +func TestShortDataPayload(t *testing.T) { + sigScheme := signaturescheme.ED25519(ed25519.GenerateKeyPair()) + addr := sigScheme.Address() + o1 := NewOutputId(addr, RandomId()) + inputs := NewInputs(o1) + bal := balance.New(balance.COLOR_IOTA, 1) + outputs := NewOutputs(map[address.Address][]*balance.Balance{addr: {bal}}) + tx := New(inputs, outputs) + + dataPayload := []byte("data payload test") + dataPayloadType := uint32(42) + err := tx.SetDataPayload(dataPayload, dataPayloadType) + assert.Equal(t, nil, err) + + dpBack, dptBack := tx.GetDataPayload() + assert.Equal(t, true, bytes.Compare(dpBack, dataPayload) == 0) + assert.Equal(t, true, dptBack == dataPayloadType) + + tx.Sign(sigScheme) + check := tx.SignaturesValid() + assert.Equal(t, true, check) + + // corrupt data payload bytes + // reset essence to force recalculation + tx.essenceBytes = nil + dataPayload[2] = '?' + err = tx.SetDataPayload(dataPayload, dataPayloadType) + assert.Equal(t, nil, err) + + // expect signature is not valid + check = tx.SignaturesValid() + assert.Equal(t, false, check) +} + +func TestTooLongDataPayload(t *testing.T) { + sigScheme := signaturescheme.ED25519(ed25519.GenerateKeyPair()) + addr := sigScheme.Address() + o1 := NewOutputId(addr, RandomId()) + inputs := NewInputs(o1) + bal := balance.New(balance.COLOR_IOTA, 1) + outputs := NewOutputs(map[address.Address][]*balance.Balance{addr: {bal}}) + tx := New(inputs, outputs) + + dataPayload := []byte(strings.Repeat("1", MAX_DATA_PAYLOAD_SIZE+1)) + dataPayloadType := uint32(42) + err := tx.SetDataPayload(dataPayload, dataPayloadType) + assert.Equal(t, true, err != nil) +} + +func TestMarshalingEmptyDataPayload(t *testing.T) { + sigScheme := signaturescheme.RandBLS() + addr := sigScheme.Address() + o1 := NewOutputId(addr, RandomId()) + inputs := NewInputs(o1) + bal := balance.New(balance.COLOR_IOTA, 1) + outputs := NewOutputs(map[address.Address][]*balance.Balance{addr: {bal}}) + tx := New(inputs, outputs) + + tx.Sign(sigScheme) + check := tx.SignaturesValid() + assert.Equal(t, true, check) + + v := tx.ObjectStorageValue() + + tx1 := Transaction{} + err, _ := tx1.UnmarshalObjectStorageValue(v) + if err != nil { + assert.Error(t, err) + } + assert.Equal(t, true, tx1.SignaturesValid()) + assert.Equal(t, true, bytes.Compare(tx1.Id().Bytes(), tx.Id().Bytes()) == 0) +} + +func TestMarshalingDataPayload(t *testing.T) { + sigScheme := signaturescheme.RandBLS() + addr := sigScheme.Address() + o1 := NewOutputId(addr, RandomId()) + inputs := NewInputs(o1) + bal := balance.New(balance.COLOR_IOTA, 1) + outputs := NewOutputs(map[address.Address][]*balance.Balance{addr: {bal}}) + tx := New(inputs, outputs) + + dataPayload := []byte("data payload test") + dataPayloadType := uint32(42) + err := tx.SetDataPayload(dataPayload, dataPayloadType) + assert.Equal(t, nil, err) + + tx.Sign(sigScheme) + check := tx.SignaturesValid() + assert.Equal(t, true, check) + + v := tx.ObjectStorageValue() + + tx1 := Transaction{} + err, _ = tx1.UnmarshalObjectStorageValue(v) + + assert.Equal(t, nil, err) + assert.Equal(t, true, tx1.SignaturesValid()) + + assert.Equal(t, true, bytes.Compare(tx1.Id().Bytes(), tx.Id().Bytes()) == 0) +}