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 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)) writeEndOffset := util.expandWriteCapacity(len(bytes))
copy(util.bytes[util.writeOffset:writeEndOffset], bytes) copy(util.bytes[util.writeOffset:writeEndOffset], bytes)
util.WriteSeek(writeEndOffset) 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) { func (util *MarshalUtil) ReadBytes(length int) ([]byte, error) {
if length < 0 { if length < 0 {
length = len(util.bytes) - util.readOffset + length length = len(util.bytes) - util.readOffset + length
......
package address package address
import ( import (
"crypto/rand"
"fmt"
"github.com/mr-tron/base58" "github.com/mr-tron/base58"
"golang.org/x/crypto/blake2b" "golang.org/x/crypto/blake2b"
"github.com/iotaledger/goshimmer/packages/binary/marshalutil"
"github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119"
) )
...@@ -13,7 +17,36 @@ type AddressDigest = []byte ...@@ -13,7 +17,36 @@ type AddressDigest = []byte
type Address [Length]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) copy(address[:], bytes)
return return
...@@ -28,6 +61,29 @@ func FromED25519PubKey(key ed25119.PublicKey) (address Address) { ...@@ -28,6 +61,29 @@ func FromED25519PubKey(key ed25119.PublicKey) (address Address) {
return 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 { func (address *Address) GetVersion() AddressVersion {
return address[0] return address[0]
} }
...@@ -36,12 +92,14 @@ func (address *Address) GetDigest() AddressDigest { ...@@ -36,12 +92,14 @@ func (address *Address) GetDigest() AddressDigest {
return address[1:] return address[1:]
} }
func (address Address) ToBytes() []byte { // Bytes returns a marshaled version of this address.
func (address Address) Bytes() []byte {
return address[:] return address[:]
} }
// String returns a human readable (base58 encoded) version of the address.
func (address Address) String() string { func (address Address) String() string {
return "Address(" + base58.Encode(address.ToBytes()) + ")" return base58.Encode(address.Bytes())
} }
const Length = 33 const Length = 33
...@@ -44,6 +44,15 @@ func FromBytes(bytes []byte) (result *ColoredBalance, err error, consumedBytes i ...@@ -44,6 +44,15 @@ func FromBytes(bytes []byte) (result *ColoredBalance, err error, consumedBytes i
return 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 { func (balance *ColoredBalance) Bytes() []byte {
marshalUtil := marshalutil.New(color.Length + marshalutil.UINT32_SIZE) marshalUtil := marshalutil.New(color.Length + marshalutil.UINT32_SIZE)
...@@ -56,3 +65,6 @@ func (balance *ColoredBalance) Bytes() []byte { ...@@ -56,3 +65,6 @@ func (balance *ColoredBalance) Bytes() []byte {
func (balance *ColoredBalance) String() string { func (balance *ColoredBalance) String() string {
return strconv.FormatInt(balance.balance, 10) + " " + balance.color.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 ...@@ -2,6 +2,8 @@ package id
import ( import (
"github.com/mr-tron/base58" "github.com/mr-tron/base58"
"github.com/iotaledger/goshimmer/packages/binary/marshalutil"
) )
type Id [Length]byte type Id [Length]byte
...@@ -12,6 +14,29 @@ func New(idBytes []byte) (result Id) { ...@@ -12,6 +14,29 @@ func New(idBytes []byte) (result Id) {
return 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 { func (id Id) Bytes() []byte {
return id[:] return id[:]
} }
......
...@@ -32,13 +32,12 @@ func FromBytes(bytes []byte) (inputs *Inputs, err error, consumedBytes int) { ...@@ -32,13 +32,12 @@ func FromBytes(bytes []byte) (inputs *Inputs, err error, consumedBytes int) {
} }
for i := uint32(0); i < inputCount; i++ { for i := uint32(0); i < inputCount; i++ {
addressBytes, readErr := marshalUtil.ReadBytes(address.Length) readAddress, addressErr := address.Parse(marshalUtil)
if readErr != nil { if addressErr != nil {
err = readErr err = addressErr
return return
} }
readAddress := address.New(addressBytes)
transferIdBytes, readErr := marshalUtil.ReadBytes(transferid.Length) transferIdBytes, readErr := marshalUtil.ReadBytes(transferid.Length)
if readErr != nil { if readErr != nil {
......
...@@ -45,13 +45,12 @@ func FromBytes(bytes []byte, optionalTargetObject ...*Outputs) (result *Outputs, ...@@ -45,13 +45,12 @@ func FromBytes(bytes []byte, optionalTargetObject ...*Outputs) (result *Outputs,
// iterate the corresponding times and collect addresses + their details // iterate the corresponding times and collect addresses + their details
for i := uint32(0); i < addressCount; i++ { for i := uint32(0); i < addressCount; i++ {
// read address // read address
addressBytes, addressErr := marshalUtil.ReadBytes(address.Length) address, addressErr := address.Parse(marshalUtil)
if addressErr != nil { if addressErr != nil {
err = addressErr err = addressErr
return return
} }
address := address.New(addressBytes)
// read number of balances in the outputs // read number of balances in the outputs
balanceCount, balanceCountErr := marshalUtil.ReadUint32() balanceCount, balanceCountErr := marshalUtil.ReadUint32()
...@@ -109,7 +108,7 @@ func (outputs *Outputs) Bytes() []byte { ...@@ -109,7 +108,7 @@ func (outputs *Outputs) Bytes() []byte {
marshalUtil.WriteUint32(uint32(outputs.Size())) marshalUtil.WriteUint32(uint32(outputs.Size()))
outputs.ForEach(func(address address.Address, balances []*coloredbalance.ColoredBalance) { outputs.ForEach(func(address address.Address, balances []*coloredbalance.ColoredBalance) {
marshalUtil.WriteBytes(address.ToBytes()) marshalUtil.WriteBytes(address.Bytes())
marshalUtil.WriteUint32(uint32(len(balances))) marshalUtil.WriteUint32(uint32(len(balances)))
for _, balance := range balances { for _, balance := range balances {
......
...@@ -56,7 +56,7 @@ func TestTangle_AttachPayload(t *testing.T) { ...@@ -56,7 +56,7 @@ func TestTangle_AttachPayload(t *testing.T) {
), ),
outputs.New(map[address.Address][]*coloredbalance.ColoredBalance{ outputs.New(map[address.Address][]*coloredbalance.ColoredBalance{
address.New([]byte("output_address")): { address.Random(): {
coloredbalance.New(color.IOTA, 1337), coloredbalance.New(color.IOTA, 1337),
}, },
}), }),
......
...@@ -27,13 +27,13 @@ func ExamplePayload() { ...@@ -27,13 +27,13 @@ func ExamplePayload() {
valueTransfer := transfer.New( valueTransfer := transfer.New(
// inputs // inputs
inputs.New( inputs.New(
transferoutputid.New(address.New([]byte("input_address1")), transferid.New([]byte("transfer1"))), transferoutputid.New(address.Random(), transferid.New([]byte("transfer1"))),
transferoutputid.New(address.New([]byte("input_address2")), transferid.New([]byte("transfer2"))), transferoutputid.New(address.Random(), transferid.New([]byte("transfer2"))),
), ),
// outputs // outputs
outputs.New(map[address.Address][]*coloredbalance.ColoredBalance{ outputs.New(map[address.Address][]*coloredbalance.ColoredBalance{
address.New([]byte("output_address")): { address.Random(): {
coloredbalance.New(color.IOTA, 1337), coloredbalance.New(color.IOTA, 1337),
}, },
}), }),
...@@ -89,7 +89,7 @@ func TestPayload(t *testing.T) { ...@@ -89,7 +89,7 @@ func TestPayload(t *testing.T) {
), ),
outputs.New(map[address.Address][]*coloredbalance.ColoredBalance{ outputs.New(map[address.Address][]*coloredbalance.ColoredBalance{
address.New([]byte("output_address")): { address.Random(): {
coloredbalance.New(color.IOTA, 1337), coloredbalance.New(color.IOTA, 1337),
}, },
}), }),
......
...@@ -3,15 +3,15 @@ package id ...@@ -3,15 +3,15 @@ package id
import ( import (
"github.com/mr-tron/base58" "github.com/mr-tron/base58"
address2 "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" "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/payload/transfer/id"
) )
type Id [Length]byte type Id [Length]byte
func New(outputAddress address2.Address, transferId id2.Id) (transferOutputId Id) { func New(outputAddress address.Address, transferId id.Id) (transferOutputId Id) {
copy(transferOutputId[:address2.Length], outputAddress.ToBytes()) copy(transferOutputId[:address.Length], outputAddress.Bytes())
copy(transferOutputId[address2.Length:], transferId[:]) copy(transferOutputId[address.Length:], transferId[:])
return return
} }
...@@ -22,12 +22,14 @@ func FromBytes(bytes []byte) (transferOutputId Id) { ...@@ -22,12 +22,14 @@ func FromBytes(bytes []byte) (transferOutputId Id) {
return return
} }
func (transferOutputId Id) GetAddress() address2.Address { func (transferOutputId Id) GetAddress() (address address.Address) {
return address2.New(transferOutputId[:address2.Length]) copy(address[:], transferOutputId[:])
return
} }
func (transferOutputId Id) GetTransferId() id2.Id { func (transferOutputId Id) GetTransferId() id.Id {
return id2.New(transferOutputId[address2.Length:]) return id.New(transferOutputId[address.Length:])
} }
func (transferOutputId Id) ToBytes() []byte { func (transferOutputId Id) ToBytes() []byte {
...@@ -38,4 +40,4 @@ func (transferOutputId Id) String() string { ...@@ -38,4 +40,4 @@ func (transferOutputId Id) String() string {
return "Id(" + base58.Encode(transferOutputId[:]) + ")" 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