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

Feat: Started implementing branch based leder state (#324)

* Feat: value transfers are working

* Feat: tangle now detects invalid transactions

* Refactor: refactored some code

* Feat: finished unit test for value transaction

* Feat: added ledger state (reads information from value tangle)

* Refactor: refactored some code

* Feat: finished refactoring isTransactionSolid method

* Feat: added RealityId model for parallel reality based ledger

* Refactor: moved Output to tangle and introduced branches

* Refactor: refactored some code

* Refactor: renamed "NEW" color

* Feat: started adding branches
parent 14948393
No related branches found
No related tags found
No related merge requests found
...@@ -34,3 +34,5 @@ func (color Color) String() string { ...@@ -34,3 +34,5 @@ func (color Color) String() string {
} }
var COLOR_IOTA Color = [32]byte{} var COLOR_IOTA Color = [32]byte{}
var COLOR_NEW = [32]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}
package ledgerstate
type LedgerState struct {
}
package tangle
import (
"github.com/iotaledger/hive.go/objectstorage"
)
type Branch struct {
objectstorage.StorableObjectFlags
id BranchId
parentBranches []BranchId
}
func (branch *Branch) Update(other objectstorage.StorableObject) {
panic("implement me")
}
func (branch *Branch) ObjectStorageKey() []byte {
panic("implement me")
}
func (branch *Branch) ObjectStorageValue() []byte {
panic("implement me")
}
func (branch *Branch) UnmarshalObjectStorageValue(valueBytes []byte) (err error, consumedBytes int) {
panic("implement me")
}
func NewBranch(id BranchId, parentBranches []BranchId) *Branch {
return nil
}
func (branch *Branch) Id() BranchId {
return branch.id
}
func (branch *Branch) ParentBranches() []BranchId {
return branch.parentBranches
}
func (branch *Branch) IsAggregated() bool {
return len(branch.parentBranches) > 1
}
type CachedBranch struct {
objectstorage.CachedObject
}
func (cachedBranches *CachedBranch) Unwrap() *Branch {
if untypedObject := cachedBranches.Get(); untypedObject == nil {
return nil
} else {
if typedObject := untypedObject.(*Branch); typedObject == nil || typedObject.IsDeleted() {
return nil
} else {
return typedObject
}
}
}
func (cachedBranches *CachedBranch) Consume(consumer func(branch *Branch), forceRelease ...bool) (consumed bool) {
return cachedBranches.CachedObject.Consume(func(object objectstorage.StorableObject) {
consumer(object.(*Branch))
}, forceRelease...)
}
type CachedBranches map[BranchId]*CachedBranch
func (cachedBranches CachedBranches) Consume(consumer func(branch *Branch)) (consumed bool) {
for _, cachedBranch := range cachedBranches {
consumed = cachedBranch.Consume(func(output *Branch) {
consumer(output)
}) || consumed
}
return
}
func (cachedBranches CachedBranches) Release(force ...bool) {
for _, cachedBranch := range cachedBranches {
cachedBranch.Release(force...)
}
}
package tangle
import (
"fmt"
"github.com/iotaledger/hive.go/marshalutil"
"github.com/mr-tron/base58"
"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transaction"
)
type BranchId [BranchIdLength]byte
var (
UndefinedBranchId = BranchId{}
MasterBranchId = BranchId{1}
)
// NewBranchId creates a new BranchId from a transaction Id.
func NewBranchId(transactionId transaction.Id) (branchId BranchId) {
copy(branchId[:], transactionId.Bytes())
return
}
func BranchIdFromBytes(bytes []byte) (result BranchId, err error, consumedBytes int) {
// parse the bytes
marshalUtil := marshalutil.New(bytes)
branchIdBytes, idErr := marshalUtil.ReadBytes(BranchIdLength)
if idErr != nil {
err = idErr
return
}
copy(result[:], branchIdBytes)
consumedBytes = marshalUtil.ReadOffset()
return
}
func BranchIdFromBase58(base58String string) (branchId BranchId, err error) {
// decode string
bytes, err := base58.Decode(base58String)
if err != nil {
return
}
// sanitize input
if len(bytes) != BranchIdLength {
err = fmt.Errorf("base58 encoded string does not match the length of a BranchId")
return
}
// copy bytes to result
copy(branchId[:], bytes)
return
}
func ParseBranchId(marshalUtil *marshalutil.MarshalUtil) (result BranchId, err error) {
var branchIdBytes []byte
if branchIdBytes, err = marshalUtil.ReadBytes(BranchIdLength); err != nil {
return
}
copy(result[:], branchIdBytes)
return
}
// Bytes marshals the BranchId into a sequence of bytes.
func (branchId BranchId) Bytes() []byte {
return branchId[:]
}
// String creates a base58 encoded version of the BranchId.
func (branchId BranchId) String() string {
return base58.Encode(branchId[:])
}
// BranchIdLength encodes the length of a branch identifier - since branches get created by transactions, it has the
// same length as a transaction Id.
const BranchIdLength = transaction.IdLength
...@@ -4,7 +4,6 @@ import ( ...@@ -4,7 +4,6 @@ import (
"github.com/iotaledger/hive.go/objectstorage" "github.com/iotaledger/hive.go/objectstorage"
"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload" "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload"
"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transaction"
) )
const ( const (
...@@ -43,7 +42,7 @@ func osAttachmentFactory(key []byte) (objectstorage.StorableObject, error, int) ...@@ -43,7 +42,7 @@ func osAttachmentFactory(key []byte) (objectstorage.StorableObject, error, int)
} }
func osOutputFactory(key []byte) (objectstorage.StorableObject, error, int) { func osOutputFactory(key []byte) (objectstorage.StorableObject, error, int) {
return transaction.OutputFromStorageKey(key) return OutputFromStorageKey(key)
} }
func osMissingOutputFactory(key []byte) (objectstorage.StorableObject, error, int) { func osMissingOutputFactory(key []byte) (objectstorage.StorableObject, error, int) {
......
package transaction package tangle
import ( import (
"sync" "sync"
...@@ -6,33 +6,40 @@ import ( ...@@ -6,33 +6,40 @@ import (
"github.com/iotaledger/hive.go/marshalutil" "github.com/iotaledger/hive.go/marshalutil"
"github.com/iotaledger/hive.go/objectstorage" "github.com/iotaledger/hive.go/objectstorage"
"github.com/iotaledger/hive.go/stringify"
"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address"
"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/balance" "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/balance"
"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transaction"
) )
var OutputKeyPartitions = objectstorage.PartitionKey([]int{address.Length, IdLength}...) var OutputKeyPartitions = objectstorage.PartitionKey([]int{address.Length, transaction.IdLength}...)
// Output represents the output of a Transaction and contains the balances and the identifiers for this output. // Output represents the output of a Transaction and contains the balances and the identifiers for this output.
type Output struct { type Output struct {
address address.Address address address.Address
transactionId Id transactionId transaction.Id
branchId BranchId
solid bool solid bool
solidificationTime time.Time solidificationTime time.Time
firstConsumer transaction.Id
consumerCount int
balances []*balance.Balance balances []*balance.Balance
solidMutex sync.RWMutex solidMutex sync.RWMutex
solidificationTimeMutex sync.RWMutex solidificationTimeMutex sync.RWMutex
consumerMutex sync.RWMutex
objectstorage.StorableObjectFlags objectstorage.StorableObjectFlags
storageKey []byte storageKey []byte
} }
// NewOutput creates an Output that contains the balances and identifiers of a Transaction. // NewOutput creates an Output that contains the balances and identifiers of a Transaction.
func NewOutput(address address.Address, transactionId Id, balances []*balance.Balance) *Output { func NewOutput(address address.Address, transactionId transaction.Id, branchId BranchId, balances []*balance.Balance) *Output {
return &Output{ return &Output{
address: address, address: address,
transactionId: transactionId, transactionId: transactionId,
branchId: branchId,
solid: false, solid: false,
solidificationTime: time.Time{}, solidificationTime: time.Time{},
balances: balances, balances: balances,
...@@ -93,26 +100,35 @@ func OutputFromStorageKey(keyBytes []byte, optionalTargetObject ...*Output) (res ...@@ -93,26 +100,35 @@ func OutputFromStorageKey(keyBytes []byte, optionalTargetObject ...*Output) (res
if err != nil { if err != nil {
return return
} }
result.transactionId, err = ParseId(marshalUtil) result.transactionId, err = transaction.ParseId(marshalUtil)
if err != nil { if err != nil {
return return
} }
result.storageKey = marshalutil.New(keyBytes[:OutputIdLength]).Bytes(true) result.storageKey = marshalutil.New(keyBytes[:transaction.OutputIdLength]).Bytes(true)
consumedBytes = marshalUtil.ReadOffset() consumedBytes = marshalUtil.ReadOffset()
return return
} }
func (output *Output) Id() transaction.OutputId {
return transaction.NewOutputId(output.Address(), output.TransactionId())
}
// Address returns the address that this output belongs to. // Address returns the address that this output belongs to.
func (output *Output) Address() address.Address { func (output *Output) Address() address.Address {
return output.address return output.address
} }
// TransactionId returns the id of the Transaction, that created this output. // TransactionId returns the id of the Transaction, that created this output.
func (output *Output) TransactionId() Id { func (output *Output) TransactionId() transaction.Id {
return output.transactionId return output.transactionId
} }
// BranchId returns the id of the ledger state branch, that this output was booked in.
func (output *Output) BranchId() BranchId {
return output.branchId
}
// Solid returns true if the output has been marked as solid. // Solid returns true if the output has been marked as solid.
func (output *Output) Solid() bool { func (output *Output) Solid() bool {
output.solidMutex.RLock() output.solidMutex.RLock()
...@@ -155,6 +171,20 @@ func (output *Output) SolidificationTime() time.Time { ...@@ -155,6 +171,20 @@ func (output *Output) SolidificationTime() time.Time {
return output.solidificationTime return output.solidificationTime
} }
func (output *Output) RegisterConsumer(consumer transaction.Id) (consumerCount int, firstConsumerId transaction.Id) {
output.consumerMutex.Lock()
defer output.consumerMutex.Unlock()
if consumerCount = output.consumerCount; consumerCount == 0 {
output.firstConsumer = consumer
}
output.consumerCount++
firstConsumerId = output.firstConsumer
return
}
// Balances returns the colored balances (color + balance) that this output contains. // Balances returns the colored balances (color + balance) that this output contains.
func (output *Output) Balances() []*balance.Balance { func (output *Output) Balances() []*balance.Balance {
return output.balances return output.balances
...@@ -171,7 +201,7 @@ func (output *Output) Bytes() []byte { ...@@ -171,7 +201,7 @@ func (output *Output) Bytes() []byte {
// ObjectStorageKey returns the key that is used to store the object in the database. // ObjectStorageKey returns the key that is used to store the object in the database.
// It is required to match StorableObject interface. // It is required to match StorableObject interface.
func (output *Output) ObjectStorageKey() []byte { func (output *Output) ObjectStorageKey() []byte {
return marshalutil.New(OutputIdLength). return marshalutil.New(transaction.OutputIdLength).
WriteBytes(output.address.Bytes()). WriteBytes(output.address.Bytes()).
WriteBytes(output.transactionId.Bytes()). WriteBytes(output.transactionId.Bytes()).
Bytes() Bytes()
...@@ -184,7 +214,8 @@ func (output *Output) ObjectStorageValue() []byte { ...@@ -184,7 +214,8 @@ func (output *Output) ObjectStorageValue() []byte {
balanceCount := len(output.balances) balanceCount := len(output.balances)
// initialize helper // initialize helper
marshalUtil := marshalutil.New(marshalutil.BOOL_SIZE + marshalutil.TIME_SIZE + marshalutil.UINT32_SIZE + balanceCount*balance.Length) marshalUtil := marshalutil.New(BranchIdLength + marshalutil.BOOL_SIZE + marshalutil.TIME_SIZE + marshalutil.UINT32_SIZE + balanceCount*balance.Length)
marshalUtil.WriteBytes(output.branchId.Bytes())
marshalUtil.WriteBool(output.solid) marshalUtil.WriteBool(output.solid)
marshalUtil.WriteTime(output.solidificationTime) marshalUtil.WriteTime(output.solidificationTime)
marshalUtil.WriteUint32(uint32(balanceCount)) marshalUtil.WriteUint32(uint32(balanceCount))
...@@ -199,6 +230,9 @@ func (output *Output) ObjectStorageValue() []byte { ...@@ -199,6 +230,9 @@ func (output *Output) ObjectStorageValue() []byte {
// being stored in its key rather than the content of the database to reduce storage requirements. // being stored in its key rather than the content of the database to reduce storage requirements.
func (output *Output) UnmarshalObjectStorageValue(data []byte) (err error, consumedBytes int) { func (output *Output) UnmarshalObjectStorageValue(data []byte) (err error, consumedBytes int) {
marshalUtil := marshalutil.New(data) marshalUtil := marshalutil.New(data)
if output.branchId, err = ParseBranchId(marshalUtil); err != nil {
return
}
if output.solid, err = marshalUtil.ReadBool(); err != nil { if output.solid, err = marshalUtil.ReadBool(); err != nil {
return return
} }
...@@ -227,6 +261,17 @@ func (output *Output) Update(other objectstorage.StorableObject) { ...@@ -227,6 +261,17 @@ func (output *Output) Update(other objectstorage.StorableObject) {
panic("this object should never be updated") panic("this object should never be updated")
} }
func (output *Output) String() string {
return stringify.Struct("Output",
stringify.StructField("address", output.Address()),
stringify.StructField("transactionId", output.TransactionId()),
stringify.StructField("branchId", output.BranchId()),
stringify.StructField("solid", output.Solid()),
stringify.StructField("solidificationTime", output.SolidificationTime()),
stringify.StructField("balances", output.Balances()),
)
}
// define contract (ensure that the struct fulfills the given interface) // define contract (ensure that the struct fulfills the given interface)
var _ objectstorage.StorableObject = &Output{} var _ objectstorage.StorableObject = &Output{}
...@@ -254,7 +299,7 @@ func (cachedOutput *CachedOutput) Consume(consumer func(output *Output)) (consum ...@@ -254,7 +299,7 @@ func (cachedOutput *CachedOutput) Consume(consumer func(output *Output)) (consum
}) })
} }
type CachedOutputs []*CachedOutput type CachedOutputs map[transaction.OutputId]*CachedOutput
func (cachedOutputs CachedOutputs) Consume(consumer func(output *Output)) (consumed bool) { func (cachedOutputs CachedOutputs) Consume(consumer func(output *Output)) (consumed bool) {
for _, cachedOutput := range cachedOutputs { for _, cachedOutput := range cachedOutputs {
...@@ -266,4 +311,10 @@ func (cachedOutputs CachedOutputs) Consume(consumer func(output *Output)) (consu ...@@ -266,4 +311,10 @@ func (cachedOutputs CachedOutputs) Consume(consumer func(output *Output)) (consu
return return
} }
func (cachedOutputs CachedOutputs) Release(force ...bool) {
for _, cachedOutput := range cachedOutputs {
cachedOutput.Release(force...)
}
}
// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// // endregion ///////////////////////////////////////////////////////////////////////////////////////////////////////////
package transaction package tangle
import ( import (
"testing" "testing"
...@@ -8,13 +8,14 @@ import ( ...@@ -8,13 +8,14 @@ import (
"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address"
"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/balance" "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/balance"
"github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transaction"
) )
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
randomAddress := address.Random() randomAddress := address.Random()
randomTransactionId := RandomId() randomTransactionId := transaction.RandomId()
output := NewOutput(randomAddress, randomTransactionId, []*balance.Balance{ output := NewOutput(randomAddress, randomTransactionId, MasterBranchId, []*balance.Balance{
balance.New(balance.COLOR_IOTA, 1337), balance.New(balance.COLOR_IOTA, 1337),
}) })
......
This diff is collapsed.
package tangle package tangle
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"testing" "testing"
"time"
"github.com/iotaledger/hive.go/crypto/ed25519" "github.com/iotaledger/hive.go/crypto/ed25519"
"github.com/iotaledger/hive.go/events"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -52,31 +51,47 @@ func TestTangle_AttachPayload(t *testing.T) { ...@@ -52,31 +51,47 @@ func TestTangle_AttachPayload(t *testing.T) {
return return
} }
tangle.Events.PayloadSolid.Attach(events.NewClosure(func(payload *payload.CachedPayload, metadata *CachedPayloadMetadata) {
fmt.Println(payload.Unwrap())
payload.Release()
metadata.Release()
}))
addressKeyPair1 := ed25519.GenerateKeyPair() addressKeyPair1 := ed25519.GenerateKeyPair()
addressKeyPair2 := ed25519.GenerateKeyPair() addressKeyPair2 := ed25519.GenerateKeyPair()
transferId1, _ := transaction.IdFromBase58("8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh") transferId1, _ := transaction.IdFromBase58("8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh")
transferId2, _ := transaction.IdFromBase58("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM") transferId2, _ := transaction.IdFromBase58("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM")
tangle.AttachPayload(payload.New(payload.GenesisId, payload.GenesisId, transaction.New( input1 := NewOutput(address.FromED25519PubKey(addressKeyPair1.PublicKey), transferId1, MasterBranchId, []*balance.Balance{
balance.New(balance.COLOR_IOTA, 337),
})
input1.SetSolid(true)
input2 := NewOutput(address.FromED25519PubKey(addressKeyPair2.PublicKey), transferId2, MasterBranchId, []*balance.Balance{
balance.New(balance.COLOR_IOTA, 1000),
})
input2.SetSolid(true)
tangle.outputStorage.Store(input1)
tangle.outputStorage.Store(input2)
outputAddress := address.Random()
tx := transaction.New(
transaction.NewInputs( transaction.NewInputs(
transaction.NewOutputId(address.FromED25519PubKey(addressKeyPair1.PublicKey), transferId1), input1.Id(),
transaction.NewOutputId(address.FromED25519PubKey(addressKeyPair2.PublicKey), transferId2), input2.Id(),
), ),
transaction.NewOutputs(map[address.Address][]*balance.Balance{ transaction.NewOutputs(map[address.Address][]*balance.Balance{
address.Random(): { outputAddress: {
balance.New(balance.COLOR_IOTA, 1337), balance.New(balance.COLOR_NEW, 1337),
}, },
}), }),
))) )
tangle.AttachPayload(payload.New(payload.GenesisId, payload.GenesisId, tx))
time.Sleep(1 * time.Second)
outputFound := tangle.GetTransactionOutput(transaction.NewOutputId(outputAddress, tx.Id())).Consume(func(output *Output) {
assert.Equal(t, true, output.Solid())
})
assert.Equal(t, true, outputFound)
tangle.Shutdown() tangle.Shutdown()
} }
...@@ -90,11 +90,9 @@ func (outputs *Outputs) Add(address address.Address, balances []*balance.Balance ...@@ -90,11 +90,9 @@ func (outputs *Outputs) Add(address address.Address, balances []*balance.Balance
return outputs return outputs
} }
func (outputs *Outputs) ForEach(consumer func(address address.Address, balances []*balance.Balance)) { func (outputs *Outputs) ForEach(consumer func(address address.Address, balances []*balance.Balance) bool) bool {
outputs.OrderedMap.ForEach(func(key, value interface{}) bool { return outputs.OrderedMap.ForEach(func(key, value interface{}) bool {
consumer(key.(address.Address), value.([]*balance.Balance)) return consumer(key.(address.Address), value.([]*balance.Balance))
return true
}) })
} }
...@@ -108,13 +106,15 @@ func (outputs *Outputs) Bytes() []byte { ...@@ -108,13 +106,15 @@ func (outputs *Outputs) Bytes() []byte {
} }
marshalUtil.WriteUint32(uint32(outputs.Size())) marshalUtil.WriteUint32(uint32(outputs.Size()))
outputs.ForEach(func(address address.Address, balances []*balance.Balance) { outputs.ForEach(func(address address.Address, balances []*balance.Balance) bool {
marshalUtil.WriteBytes(address.Bytes()) marshalUtil.WriteBytes(address.Bytes())
marshalUtil.WriteUint32(uint32(len(balances))) marshalUtil.WriteUint32(uint32(len(balances)))
for _, balance := range balances { for _, balance := range balances {
marshalUtil.WriteBytes(balance.Bytes()) marshalUtil.WriteBytes(balance.Bytes())
} }
return true
}) })
return marshalUtil.Bytes() return marshalUtil.Bytes()
...@@ -127,7 +127,7 @@ func (outputs *Outputs) String() string { ...@@ -127,7 +127,7 @@ func (outputs *Outputs) String() string {
result := "[\n" result := "[\n"
empty := true empty := true
outputs.ForEach(func(address address.Address, balances []*balance.Balance) { outputs.ForEach(func(address address.Address, balances []*balance.Balance) bool {
empty = false empty = false
result += " " + address.String() + ": [\n" result += " " + address.String() + ": [\n"
...@@ -144,6 +144,8 @@ func (outputs *Outputs) String() string { ...@@ -144,6 +144,8 @@ func (outputs *Outputs) String() string {
} }
result += " ]\n" result += " ]\n"
return true
}) })
if empty { if empty {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment