Skip to content
Snippets Groups Projects
Unverified Commit d940ecf7 authored by Hans Moog's avatar Hans Moog Committed by GitHub
Browse files

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
parent 92bc053c
No related branches found
No related tags found
No related merge requests found
Showing
with 262 additions and 26 deletions
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
......
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
......@@ -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
......@@ -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[:]
}
......
......@@ -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 {
......
......@@ -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 {
......
......@@ -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),
},
}),
......
......@@ -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),
},
}),
......
......@@ -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
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{}
package transferoutput
import (
"testing"
)
func TestNew(t *testing.T) {
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment