From d940ecf762291699c957e05e23b7c0c47b4ac91d Mon Sep 17 00:00:00 2001 From: Hans Moog <hm@mkjc.net> Date: Wed, 18 Mar 2020 16:36:22 +0100 Subject: [PATCH] Add TransferOutput model (#299) * Feat: added additional transaction fields (sequenceNumber + time) * Feat: added getters + adjusted new constructor use * Feat: added TransferOutput model * Docs: added some doc comments * Docs: updated docs comments * Refactor: refactored some code * Docs: added comments for the bytes methods of marshalUtil * Docs: adjusted some docs * Feat: refactored some code --- .../binary/marshalutil/marshalutil.bytes.go | 7 +- .../binary/valuetransfer/address/address.go | 64 ++++++++- .../coloredbalance/coloredbalance.go | 12 ++ .../valuetransfer/payload/transfer/id/id.go | 25 ++++ .../payload/transfer/inputs/inputs.go | 7 +- .../payload/transfer/outputs/outputs.go | 5 +- .../valuetransfer/tangle/tangle_test.go | 2 +- .../binary/valuetransfer/test/payload_test.go | 8 +- .../valuetransfer/transferoutput/id/id.go | 22 +-- .../transferoutput/transferoutput.go | 127 ++++++++++++++++++ .../transferoutput/transferoutput_test.go | 9 ++ 11 files changed, 262 insertions(+), 26 deletions(-) create mode 100644 packages/binary/valuetransfer/transferoutput/transferoutput.go create mode 100644 packages/binary/valuetransfer/transferoutput/transferoutput_test.go diff --git a/packages/binary/marshalutil/marshalutil.bytes.go b/packages/binary/marshalutil/marshalutil.bytes.go index 22d2580a..0ee36484 100644 --- a/packages/binary/marshalutil/marshalutil.bytes.go +++ b/packages/binary/marshalutil/marshalutil.bytes.go @@ -1,13 +1,18 @@ package marshalutil -func (util *MarshalUtil) WriteBytes(bytes []byte) { +// 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 { writeEndOffset := util.expandWriteCapacity(len(bytes)) copy(util.bytes[util.writeOffset:writeEndOffset], bytes) util.WriteSeek(writeEndOffset) + + return util } +// ReadBytes unmarshals the given amount of bytes from the internal read buffer. func (util *MarshalUtil) ReadBytes(length int) ([]byte, error) { if length < 0 { length = len(util.bytes) - util.readOffset + length diff --git a/packages/binary/valuetransfer/address/address.go b/packages/binary/valuetransfer/address/address.go index 17cfddba..13e89793 100644 --- a/packages/binary/valuetransfer/address/address.go +++ b/packages/binary/valuetransfer/address/address.go @@ -1,9 +1,13 @@ package address import ( + "crypto/rand" + "fmt" + "github.com/mr-tron/base58" "golang.org/x/crypto/blake2b" + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" ) @@ -13,7 +17,36 @@ type AddressDigest = []byte type Address [Length]byte -func New(bytes []byte) (address Address) { +// Random create a random address, which can for example be used in unit tests. +func Random() (address Address) { + // generate a random sequence of bytes + addressBytes := make([]byte, Length) + if _, err := rand.Read(addressBytes); err != nil { + panic(err) + } + + // copy the generated bytes into the result + copy(address[:], addressBytes) + + return +} + +// FromBase58 creates an address from base58 encoded string. +func FromBase58(base58String string) (address Address, err error) { + // decode string + bytes, err := base58.Decode(base58String) + if err != nil { + return + } + + // sanitize input + if len(bytes) != Length { + err = fmt.Errorf("base58 encoded string does not match the length of an address") + + return + } + + // copy bytes to result copy(address[:], bytes) return @@ -28,6 +61,29 @@ func FromED25519PubKey(key ed25119.PublicKey) (address Address) { return } +// FromBytes unmarshals an address from a sequence of bytes. +func FromBytes(bytes []byte) (result Address, err error, consumedBytes int) { + // parse the bytes + marshalUtil := marshalutil.New(bytes) + addressBytes, err := marshalUtil.ReadBytes(Length) + if err != nil { + return + } + copy(result[:], addressBytes) + consumedBytes = marshalUtil.ReadOffset() + + return +} + +// Parse is a wrapper for simplified unmarshaling in a byte stream using the marshalUtil package. +func Parse(marshalUtil *marshalutil.MarshalUtil) (Address, error) { + if address, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return FromBytes(data) }); err != nil { + return Address{}, err + } else { + return address.(Address), nil + } +} + func (address *Address) GetVersion() AddressVersion { return address[0] } @@ -36,12 +92,14 @@ func (address *Address) GetDigest() AddressDigest { return address[1:] } -func (address Address) ToBytes() []byte { +// Bytes returns a marshaled version of this address. +func (address Address) Bytes() []byte { return address[:] } +// String returns a human readable (base58 encoded) version of the address. func (address Address) String() string { - return "Address(" + base58.Encode(address.ToBytes()) + ")" + return base58.Encode(address.Bytes()) } const Length = 33 diff --git a/packages/binary/valuetransfer/coloredbalance/coloredbalance.go b/packages/binary/valuetransfer/coloredbalance/coloredbalance.go index b0f4d43a..e17e8d68 100644 --- a/packages/binary/valuetransfer/coloredbalance/coloredbalance.go +++ b/packages/binary/valuetransfer/coloredbalance/coloredbalance.go @@ -44,6 +44,15 @@ func FromBytes(bytes []byte) (result *ColoredBalance, err error, consumedBytes i return } +// Parse is a wrapper for simplified unmarshaling in a byte stream using the marshalUtil package. +func Parse(marshalUtil *marshalutil.MarshalUtil) (*ColoredBalance, error) { + if address, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return FromBytes(data) }); err != nil { + return nil, err + } else { + return address.(*ColoredBalance), nil + } +} + func (balance *ColoredBalance) Bytes() []byte { marshalUtil := marshalutil.New(color.Length + marshalutil.UINT32_SIZE) @@ -56,3 +65,6 @@ func (balance *ColoredBalance) Bytes() []byte { func (balance *ColoredBalance) String() string { return strconv.FormatInt(balance.balance, 10) + " " + balance.color.String() } + +// Length encodes the length of a marshaled ColoredBalance (the length of the color + 8 bytes for the balance). +const Length = color.Length + 8 diff --git a/packages/binary/valuetransfer/payload/transfer/id/id.go b/packages/binary/valuetransfer/payload/transfer/id/id.go index 8036c466..22baa614 100644 --- a/packages/binary/valuetransfer/payload/transfer/id/id.go +++ b/packages/binary/valuetransfer/payload/transfer/id/id.go @@ -2,6 +2,8 @@ package id import ( "github.com/mr-tron/base58" + + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" ) type Id [Length]byte @@ -12,6 +14,29 @@ func New(idBytes []byte) (result Id) { return } +// FromBytes unmarshals a transfer id from a sequence of bytes. +func FromBytes(bytes []byte) (result Id, err error, consumedBytes int) { + // parse the bytes + marshalUtil := marshalutil.New(bytes) + idBytes, err := marshalUtil.ReadBytes(Length) + if err != nil { + return + } + copy(result[:], idBytes) + consumedBytes = marshalUtil.ReadOffset() + + return +} + +// Parse is a wrapper for simplified unmarshaling in a byte stream using the marshalUtil package. +func Parse(marshalUtil *marshalutil.MarshalUtil) (Id, error) { + if id, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return FromBytes(data) }); err != nil { + return Id{}, err + } else { + return id.(Id), nil + } +} + func (id Id) Bytes() []byte { return id[:] } diff --git a/packages/binary/valuetransfer/payload/transfer/inputs/inputs.go b/packages/binary/valuetransfer/payload/transfer/inputs/inputs.go index be2c3fa4..1d107dc7 100644 --- a/packages/binary/valuetransfer/payload/transfer/inputs/inputs.go +++ b/packages/binary/valuetransfer/payload/transfer/inputs/inputs.go @@ -32,13 +32,12 @@ func FromBytes(bytes []byte) (inputs *Inputs, err error, consumedBytes int) { } for i := uint32(0); i < inputCount; i++ { - addressBytes, readErr := marshalUtil.ReadBytes(address.Length) - if readErr != nil { - err = readErr + readAddress, addressErr := address.Parse(marshalUtil) + if addressErr != nil { + err = addressErr return } - readAddress := address.New(addressBytes) transferIdBytes, readErr := marshalUtil.ReadBytes(transferid.Length) if readErr != nil { diff --git a/packages/binary/valuetransfer/payload/transfer/outputs/outputs.go b/packages/binary/valuetransfer/payload/transfer/outputs/outputs.go index f39268bc..d32956f4 100644 --- a/packages/binary/valuetransfer/payload/transfer/outputs/outputs.go +++ b/packages/binary/valuetransfer/payload/transfer/outputs/outputs.go @@ -45,13 +45,12 @@ func FromBytes(bytes []byte, optionalTargetObject ...*Outputs) (result *Outputs, // iterate the corresponding times and collect addresses + their details for i := uint32(0); i < addressCount; i++ { // read address - addressBytes, addressErr := marshalUtil.ReadBytes(address.Length) + address, addressErr := address.Parse(marshalUtil) if addressErr != nil { err = addressErr return } - address := address.New(addressBytes) // read number of balances in the outputs balanceCount, balanceCountErr := marshalUtil.ReadUint32() @@ -109,7 +108,7 @@ func (outputs *Outputs) Bytes() []byte { marshalUtil.WriteUint32(uint32(outputs.Size())) outputs.ForEach(func(address address.Address, balances []*coloredbalance.ColoredBalance) { - marshalUtil.WriteBytes(address.ToBytes()) + marshalUtil.WriteBytes(address.Bytes()) marshalUtil.WriteUint32(uint32(len(balances))) for _, balance := range balances { diff --git a/packages/binary/valuetransfer/tangle/tangle_test.go b/packages/binary/valuetransfer/tangle/tangle_test.go index b271eaa3..ccbbea0a 100644 --- a/packages/binary/valuetransfer/tangle/tangle_test.go +++ b/packages/binary/valuetransfer/tangle/tangle_test.go @@ -56,7 +56,7 @@ func TestTangle_AttachPayload(t *testing.T) { ), outputs.New(map[address.Address][]*coloredbalance.ColoredBalance{ - address.New([]byte("output_address")): { + address.Random(): { coloredbalance.New(color.IOTA, 1337), }, }), diff --git a/packages/binary/valuetransfer/test/payload_test.go b/packages/binary/valuetransfer/test/payload_test.go index f5e9f9d1..dcee4c24 100644 --- a/packages/binary/valuetransfer/test/payload_test.go +++ b/packages/binary/valuetransfer/test/payload_test.go @@ -27,13 +27,13 @@ func ExamplePayload() { valueTransfer := transfer.New( // inputs inputs.New( - transferoutputid.New(address.New([]byte("input_address1")), transferid.New([]byte("transfer1"))), - transferoutputid.New(address.New([]byte("input_address2")), transferid.New([]byte("transfer2"))), + transferoutputid.New(address.Random(), transferid.New([]byte("transfer1"))), + transferoutputid.New(address.Random(), transferid.New([]byte("transfer2"))), ), // outputs outputs.New(map[address.Address][]*coloredbalance.ColoredBalance{ - address.New([]byte("output_address")): { + address.Random(): { coloredbalance.New(color.IOTA, 1337), }, }), @@ -89,7 +89,7 @@ func TestPayload(t *testing.T) { ), outputs.New(map[address.Address][]*coloredbalance.ColoredBalance{ - address.New([]byte("output_address")): { + address.Random(): { coloredbalance.New(color.IOTA, 1337), }, }), diff --git a/packages/binary/valuetransfer/transferoutput/id/id.go b/packages/binary/valuetransfer/transferoutput/id/id.go index b43b9491..b8d9252e 100644 --- a/packages/binary/valuetransfer/transferoutput/id/id.go +++ b/packages/binary/valuetransfer/transferoutput/id/id.go @@ -3,15 +3,15 @@ package id import ( "github.com/mr-tron/base58" - address2 "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" - id2 "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/id" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/id" ) type Id [Length]byte -func New(outputAddress address2.Address, transferId id2.Id) (transferOutputId Id) { - copy(transferOutputId[:address2.Length], outputAddress.ToBytes()) - copy(transferOutputId[address2.Length:], transferId[:]) +func New(outputAddress address.Address, transferId id.Id) (transferOutputId Id) { + copy(transferOutputId[:address.Length], outputAddress.Bytes()) + copy(transferOutputId[address.Length:], transferId[:]) return } @@ -22,12 +22,14 @@ func FromBytes(bytes []byte) (transferOutputId Id) { return } -func (transferOutputId Id) GetAddress() address2.Address { - return address2.New(transferOutputId[:address2.Length]) +func (transferOutputId Id) GetAddress() (address address.Address) { + copy(address[:], transferOutputId[:]) + + return } -func (transferOutputId Id) GetTransferId() id2.Id { - return id2.New(transferOutputId[address2.Length:]) +func (transferOutputId Id) GetTransferId() id.Id { + return id.New(transferOutputId[address.Length:]) } func (transferOutputId Id) ToBytes() []byte { @@ -38,4 +40,4 @@ func (transferOutputId Id) String() string { return "Id(" + base58.Encode(transferOutputId[:]) + ")" } -const Length = address2.Length + id2.Length +const Length = address.Length + id.Length diff --git a/packages/binary/valuetransfer/transferoutput/transferoutput.go b/packages/binary/valuetransfer/transferoutput/transferoutput.go new file mode 100644 index 00000000..b2808491 --- /dev/null +++ b/packages/binary/valuetransfer/transferoutput/transferoutput.go @@ -0,0 +1,127 @@ +package transferoutput + +import ( + "fmt" + + "github.com/iotaledger/hive.go/objectstorage" + + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/coloredbalance" + transferId "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/id" +) + +// TransferOutput represents the output of a transfer and it contains the balances and the identifiers for this output. +type TransferOutput struct { + address address.Address + transferId transferId.Id + balances []*coloredbalance.ColoredBalance + + objectstorage.StorableObjectFlags + storageKey []byte +} + +// New creates a transfer output that contains the balances and identifiers of a successful transfer. +func New(address address.Address, transferId transferId.Id, balances []*coloredbalance.ColoredBalance) *TransferOutput { + return &TransferOutput{ + address: address, + transferId: transferId, + balances: balances, + + storageKey: marshalutil.New().WriteBytes(address.Bytes()).WriteBytes(transferId.Bytes()).Bytes(), + } +} + +// FromStorage get's called when we restore a TransferOutput from the storage. +// In contrast to other database models, it unmarshals some information from the key so we simply store the key before +// it gets handed over to UnmarshalBinary (by the ObjectStorage). +func FromStorage(keyBytes []byte) objectstorage.StorableObject { + return &TransferOutput{ + storageKey: marshalutil.New(keyBytes).Bytes(true), + } +} + +// Address returns the address that this output belongs to. +func (transferOutput *TransferOutput) Address() address.Address { + return transferOutput.address +} + +// TransferId returns the transfer id, that created this output. +func (transferOutput *TransferOutput) TransferId() transferId.Id { + return transferOutput.transferId +} + +// Balances returns the colored balances (color + balance) that this output contains. +func (transferOutput *TransferOutput) Balances() []*coloredbalance.ColoredBalance { + return transferOutput.balances +} + +// MarshalBinary marshals the balances into a sequence of bytes - the address and transferId are stored inside the key +// and are ignored here. +func (transferOutput *TransferOutput) MarshalBinary() (data []byte, err error) { + // determine amount of balances in the output + balanceCount := len(transferOutput.balances) + + // initialize helper + marshalUtil := marshalutil.New(4 + balanceCount*coloredbalance.Length) + + // marshal the amount of balances + marshalUtil.WriteUint32(uint32(balanceCount)) + + // marshal balances + for _, balance := range transferOutput.balances { + marshalUtil.WriteBytes(balance.Bytes()) + } + + return +} + +// UnmarshalBinary restores a TransferOutput from a serialized version in the ObjectStorage with parts of the object +// being stored in its key rather than the content of the database to reduce storage requirements. +func (transferOutput *TransferOutput) UnmarshalBinary(data []byte) (err error) { + // check if the storageKey has been set + if transferOutput.storageKey == nil { + return fmt.Errorf("missing storageKey when trying to unmarshal TransferOutput (it contains part of the information)") + } + + // parse information from storageKey + storageKeyUnmarshaler := marshalutil.New(transferOutput.storageKey) + transferOutput.address, err = address.Parse(storageKeyUnmarshaler) + if err != nil { + return + } + transferOutput.transferId, err = transferId.Parse(storageKeyUnmarshaler) + if err != nil { + return + } + + // parse information from content bytes + contentUnmarshaler := marshalutil.New(data) + balanceCount, err := contentUnmarshaler.ReadUint32() + if err != nil { + return + } + transferOutput.balances = make([]*coloredbalance.ColoredBalance, balanceCount) + for i := uint32(0); i < balanceCount; i++ { + transferOutput.balances[i], err = coloredbalance.Parse(contentUnmarshaler) + if err != nil { + return + } + } + + return +} + +// Update is disabled and panics if it ever gets called - it is required to match StorableObject interface. +func (transferOutput *TransferOutput) Update(other objectstorage.StorableObject) { + panic("this object should never be updated") +} + +// GetStorageKey returns the key that is used to store the object in the database. +// It is required to match StorableObject interface. +func (transferOutput *TransferOutput) GetStorageKey() []byte { + return transferOutput.storageKey +} + +// define contract (ensure that the struct fulfills the given interface) +var _ objectstorage.StorableObject = &TransferOutput{} diff --git a/packages/binary/valuetransfer/transferoutput/transferoutput_test.go b/packages/binary/valuetransfer/transferoutput/transferoutput_test.go new file mode 100644 index 00000000..c7614b3b --- /dev/null +++ b/packages/binary/valuetransfer/transferoutput/transferoutput_test.go @@ -0,0 +1,9 @@ +package transferoutput + +import ( + "testing" +) + +func TestNew(t *testing.T) { + +} -- GitLab