From b3fcb9d5cd43ba28a8c5a5e8e94ff4a7d94e6b8d Mon Sep 17 00:00:00 2001 From: Hans Moog <hm@mkjc.net> Date: Mon, 23 Mar 2020 23:20:52 +0100 Subject: [PATCH] Feat: Adds Attachment model for reverse lookups TX => Payload (#305) --- .../binary/marshalutil/marshalutil.bytes.go | 4 + packages/binary/valuetransfer/payload/id.go | 15 +++ .../binary/valuetransfer/tangle/attachment.go | 103 +++++++++++++++++- .../valuetransfer/tangle/missingoutput.go | 4 +- .../valuetransfer/tangle/tangle_test.go | 20 ++++ .../binary/valuetransfer/transaction/id.go | 2 +- 6 files changed, 143 insertions(+), 5 deletions(-) diff --git a/packages/binary/marshalutil/marshalutil.bytes.go b/packages/binary/marshalutil/marshalutil.bytes.go index 0ee36484..9e58affd 100644 --- a/packages/binary/marshalutil/marshalutil.bytes.go +++ b/packages/binary/marshalutil/marshalutil.bytes.go @@ -3,6 +3,10 @@ package marshalutil // WriteBytes appends the given bytes to the internal buffer. // It returns the same MarshalUtil so calls can be chained. func (util *MarshalUtil) WriteBytes(bytes []byte) *MarshalUtil { + if bytes == nil { + return util + } + writeEndOffset := util.expandWriteCapacity(len(bytes)) copy(util.bytes[util.writeOffset:writeEndOffset], bytes) diff --git a/packages/binary/valuetransfer/payload/id.go b/packages/binary/valuetransfer/payload/id.go index d35da661..313af04e 100644 --- a/packages/binary/valuetransfer/payload/id.go +++ b/packages/binary/valuetransfer/payload/id.go @@ -1,6 +1,7 @@ package payload import ( + "crypto/rand" "fmt" "github.com/mr-tron/base58" @@ -71,6 +72,20 @@ func IdFromBytes(bytes []byte, optionalTargetObject ...*Id) (result Id, err erro return } +// Random creates a random id which can for example be used in unit tests. +func RandomId() (id Id) { + // generate a random sequence of bytes + idBytes := make([]byte, IdLength) + if _, err := rand.Read(idBytes); err != nil { + panic(err) + } + + // copy the generated bytes into the result + copy(id[:], idBytes) + + return +} + // String returns a base58 encoded version of the payload id. func (id Id) String() string { return base58.Encode(id[:]) diff --git a/packages/binary/valuetransfer/tangle/attachment.go b/packages/binary/valuetransfer/tangle/attachment.go index 033e3f2b..9dcfadb4 100644 --- a/packages/binary/valuetransfer/tangle/attachment.go +++ b/packages/binary/valuetransfer/tangle/attachment.go @@ -1,15 +1,73 @@ package tangle import ( + "github.com/iotaledger/hive.go/objectstorage" + "github.com/iotaledger/hive.go/stringify" + + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload" "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transaction" ) -// Attachment stores the information which transaction was attached by which transaction. We need this to perform -// reverse lookups for which Payloads contain which Transactions. +// Attachment stores the information which transaction was attached by which payload. We need this to be able to perform +// reverse lookups from transactions to their corresponding payloads, that attach them. type Attachment struct { + objectstorage.StorableObjectFlags + transactionId transaction.Id payloadId payload.Id + + storageKey []byte +} + +// NewAttachment creates an attachment object with the given information. +func NewAttachment(transactionId transaction.Id, payloadId payload.Id) *Attachment { + return &Attachment{ + transactionId: transactionId, + payloadId: payloadId, + + storageKey: marshalutil.New(AttachmentLength). + WriteBytes(transactionId.Bytes()). + WriteBytes(payloadId.Bytes()). + Bytes(), + } +} + +// AttachmentFromBytes unmarshals a MissingOutput from a sequence of bytes - it either creates a new object or fills the +// optionally provided one with the parsed information. +func AttachmentFromBytes(bytes []byte, optionalTargetObject ...*Attachment) (result *Attachment, err error, consumedBytes int) { + // determine the target object that will hold the unmarshaled information + switch len(optionalTargetObject) { + case 0: + result = &Attachment{} + case 1: + result = optionalTargetObject[0] + default: + panic("too many arguments in call to AttachmentFromBytes") + } + + // parse the bytes + marshalUtil := marshalutil.New(bytes) + if result.transactionId, err = transaction.ParseId(marshalUtil); err != nil { + return + } + if result.payloadId, err = payload.ParseId(marshalUtil); err != nil { + return + } + consumedBytes = marshalUtil.ReadOffset() + + return +} + +// AttachmentFromStorage gets called when we restore an Attachment from the storage - it parses the key bytes and +// returns the new object. +func AttachmentFromStorage(keyBytes []byte) objectstorage.StorableObject { + result, err, _ := AttachmentFromBytes(keyBytes) + if err != nil { + panic(err) + } + + return result } // TransactionId returns the transaction id of this Attachment. @@ -21,3 +79,44 @@ func (attachment *Attachment) TransactionId() transaction.Id { func (attachment *Attachment) PayloadId() payload.Id { return attachment.payloadId } + +// Bytes marshals an Attachment into a sequence of bytes. +func (attachment *Attachment) Bytes() []byte { + return attachment.GetStorageKey() +} + +// String returns a human readable version of the Attachment. +func (attachment *Attachment) String() string { + return stringify.Struct("Attachment", + stringify.StructField("transactionId", attachment.TransactionId()), + stringify.StructField("payloadId", attachment.PayloadId()), + ) +} + +// GetStorageKey returns the key that is used to store the object in the database. +func (attachment *Attachment) GetStorageKey() []byte { + return attachment.storageKey +} + +// MarshalBinary marshals the "content part" of an Attachment to a sequence of bytes. Since all of the information for +// this object are stored in its key, this method does nothing and is only required to conform with the interface. +func (attachment *Attachment) MarshalBinary() (data []byte, err error) { + return +} + +// UnmarshalBinary unmarshals the "content part" of an Attachment from a sequence of bytes. Since all of the information +// for this object are stored in its key, this method does nothing and is only required to conform with the interface. +func (attachment *Attachment) UnmarshalBinary(data []byte) (err error) { + return +} + +// Update is disabled - updates are supposed to happen through the setters (if existing). +func (attachment *Attachment) Update(other objectstorage.StorableObject) { + panic("update forbidden") +} + +// Interface contract: make compiler warn if the interface is not implemented correctly. +var _ objectstorage.StorableObject = &Attachment{} + +// AttachmentLength holds the length of a marshaled Attachment in bytes. +const AttachmentLength = transaction.IdLength + payload.IdLength diff --git a/packages/binary/valuetransfer/tangle/missingoutput.go b/packages/binary/valuetransfer/tangle/missingoutput.go index 039bc60c..92b34c38 100644 --- a/packages/binary/valuetransfer/tangle/missingoutput.go +++ b/packages/binary/valuetransfer/tangle/missingoutput.go @@ -25,8 +25,8 @@ func NewMissingOutput(outputId transaction.OutputId) *MissingOutput { } } -// MissingOutput unmarshals a MissingOutput from a sequence of bytes - it either creates a new object or fills the -// optionally provided one with the parsed information. +// MissingOutputFromBytes unmarshals a MissingOutput from a sequence of bytes - it either creates a new object or fills +// the optionally provided one with the parsed information. func MissingOutputFromBytes(bytes []byte, optionalTargetObject ...*MissingOutput) (result *MissingOutput, err error, consumedBytes int) { // determine the target object that will hold the unmarshaled information switch len(optionalTargetObject) { diff --git a/packages/binary/valuetransfer/tangle/tangle_test.go b/packages/binary/valuetransfer/tangle/tangle_test.go index cd83c779..93e35e7d 100644 --- a/packages/binary/valuetransfer/tangle/tangle_test.go +++ b/packages/binary/valuetransfer/tangle/tangle_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/iotaledger/hive.go/events" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" @@ -18,6 +19,25 @@ import ( "github.com/iotaledger/goshimmer/plugins/config" ) +func TestAttachment(t *testing.T) { + transactionId := transaction.RandomId() + payloadId := payload.RandomId() + + attachment := NewAttachment(transactionId, payloadId) + + assert.Equal(t, transactionId, attachment.TransactionId()) + assert.Equal(t, payloadId, attachment.PayloadId()) + + clonedAttachment, err, consumedBytes := AttachmentFromBytes(attachment.Bytes()) + if err != nil { + panic(err) + } + + assert.Equal(t, AttachmentLength, consumedBytes) + assert.Equal(t, transactionId, clonedAttachment.TransactionId()) + assert.Equal(t, payloadId, clonedAttachment.PayloadId()) +} + func TestTangle_AttachPayload(t *testing.T) { dir, err := ioutil.TempDir("", t.Name()) require.NoError(t, err) diff --git a/packages/binary/valuetransfer/transaction/id.go b/packages/binary/valuetransfer/transaction/id.go index 396b5d28..99b954bb 100644 --- a/packages/binary/valuetransfer/transaction/id.go +++ b/packages/binary/valuetransfer/transaction/id.go @@ -58,7 +58,7 @@ func ParseId(marshalUtil *marshalutil.MarshalUtil) (Id, error) { } } -// Random creates a random address, which can for example be used in unit tests. +// Random creates a random id which can for example be used in unit tests. func RandomId() (id Id) { // generate a random sequence of bytes idBytes := make([]byte, IdLength) -- GitLab