Skip to content
Snippets Groups Projects
Commit ea4302a8 authored by Hans Moog's avatar Hans Moog
Browse files

Feat: preliminary version of value tangle as an ontology

parent b123a005
Branches
No related tags found
No related merge requests found
Showing
with 827 additions and 96 deletions
package tangle
import (
"github.com/iotaledger/goshimmer/packages/binary/address"
"github.com/iotaledger/goshimmer/packages/binary/transaction"
"github.com/iotaledger/goshimmer/packages/ledgerstate/coloredcoins"
)
type Snapshot struct {
SolidEntryPoints map[transaction.Id]map[address.Address]*coloredcoins.ColoredBalance
}
......@@ -4,9 +4,12 @@ import (
"container/list"
"time"
"github.com/iotaledger/goshimmer/packages/binary/transaction/payload/valuetransfer"
"github.com/iotaledger/goshimmer/packages/binary/tangle/approvers"
"github.com/iotaledger/goshimmer/packages/binary/tangle/missingtransaction"
"github.com/iotaledger/goshimmer/packages/binary/transaction"
"github.com/iotaledger/goshimmer/packages/binary/transaction/payload/data"
"github.com/iotaledger/goshimmer/packages/binary/transactionmetadata"
"github.com/iotaledger/goshimmer/packages/storageprefix"
"github.com/iotaledger/hive.go/async"
......@@ -50,6 +53,31 @@ func New(storageId []byte) (result *Tangle) {
return
}
func (tangle *Tangle) LoadSnapshot(snapshot *Snapshot) {
fakeTransactionId := func(tx *transaction.Transaction, id transaction.Id) *transaction.Transaction {
fakedTransaction := transaction.FromStorage(id[:])
if err := fakedTransaction.UnmarshalBinary(tx.GetBytes()); err != nil {
panic(err)
}
return fakedTransaction.(*transaction.Transaction)
}
for transactionId, addresses := range snapshot.SolidEntryPoints {
if addresses == nil {
tangle.AttachTransaction(fakeTransactionId(transaction.New(transaction.EmptyId, transaction.EmptyId, nil, data.New(nil)), transactionId))
} else {
valueTransfer := valuetransfer.New()
for address, coloredBalance := range addresses {
valueTransfer.AddOutput(address, coloredBalance)
}
tangle.AttachTransaction(fakeTransactionId(transaction.New(transaction.EmptyId, transaction.EmptyId, nil, valueTransfer), transactionId))
}
}
}
// Returns the storage id of this tangle (can be used to create ontologies that follow the storage of the main tangle).
func (tangle *Tangle) GetStorageId() []byte {
return tangle.storageId
......@@ -157,7 +185,7 @@ func (tangle *Tangle) storeTransactionWorker(tx *transaction.Transaction) {
transactionId := tx.GetId()
cachedTransactionMetadata := &transactionmetadata.CachedTransactionMetadata{CachedObject: tangle.transactionMetadataStorage.Store(transactionmetadata.New(tx.GetId()))}
cachedTransactionMetadata := &transactionmetadata.CachedTransactionMetadata{CachedObject: tangle.transactionMetadataStorage.Store(transactionmetadata.New(transactionId))}
addTransactionToApprovers(transactionId, tx.GetTrunkTransactionId())
addTransactionToApprovers(transactionId, tx.GetBranchTransactionId())
......
package transaction
import (
"github.com/mr-tron/base58"
)
type Id [IdLength]byte
func NewId(id []byte) (result Id) {
copy(result[:], id)
return
}
func (id *Id) MarshalBinary() (result []byte, err error) {
result = make([]byte, IdLength)
copy(result, id[:])
......@@ -15,6 +25,10 @@ func (id *Id) UnmarshalBinary(data []byte) (err error) {
return
}
func (id *Id) String() string {
return base58.Encode(id[:])
}
var EmptyId = Id{}
const IdLength = 64
......@@ -79,6 +79,14 @@ func (valueTransfer *ValueTransfer) AddOutput(address address.Address, balance *
return valueTransfer
}
func (valueTransfer *ValueTransfer) GetOutputs() (result map[address.Address][]*coloredcoins.ColoredBalance) {
valueTransfer.outputsMutex.RLock()
result = valueTransfer.outputs
valueTransfer.outputsMutex.RUnlock()
return
}
func (valueTransfer *ValueTransfer) Sign(keyPair ed25119.KeyPair) *ValueTransfer {
payloadBytes := valueTransfer.marshalPayloadBytes()
......
......@@ -191,7 +191,9 @@ func (transaction *Transaction) MarshalBinary() (result []byte, err error) {
copy(result[offset:], transaction.branchTransactionId[:])
offset += IdLength
if transaction.issuer != nil {
copy(result[offset:], transaction.issuer.PublicKey)
}
offset += identity.PublicKeySize
binary.LittleEndian.PutUint32(result[offset:], transaction.payload.GetType())
......@@ -202,10 +204,12 @@ func (transaction *Transaction) MarshalBinary() (result []byte, err error) {
offset += serializedPayloadLength
}
if transaction.issuer != nil {
transaction.signatureMutex.Lock()
copy(transaction.signature[:], transaction.issuer.Sign(result[:offset]))
transaction.signatureMutex.Unlock()
copy(result[offset:], transaction.signature[:])
}
// offset += identity.SignatureSize
transaction.bytes = result
......
package valuetangle
import (
"github.com/iotaledger/goshimmer/packages/binary/valuetangle/model"
"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
"github.com/iotaledger/hive.go/events"
)
......@@ -11,3 +13,29 @@ type Events struct {
MissingTransferReceived *events.Event
TransferRemoved *events.Event
}
func newEvents() *Events {
return &Events{
TransferAttached: events.NewEvent(cachedTransferEvent),
TransferSolid: events.NewEvent(cachedTransferEvent),
TransferMissing: events.NewEvent(transferIdEvent),
MissingTransferReceived: events.NewEvent(transferIdEvent),
TransferRemoved: events.NewEvent(transferIdEvent),
}
}
func transferIdEvent(handler interface{}, params ...interface{}) {
missingTransactionId := params[0].(transfer.Id)
handler.(func(transfer.Id))(missingTransactionId)
}
func cachedTransferEvent(handler interface{}, params ...interface{}) {
cachedTransfer := params[0].(*model.CachedValueTransfer)
cachedTransferMetadata := params[1].(*model.CachedTransferMetadata)
cachedTransfer.RegisterConsumer()
cachedTransferMetadata.RegisterConsumer()
handler.(func(*model.CachedValueTransfer, *model.CachedTransferMetadata))(cachedTransfer, cachedTransferMetadata)
}
package model
import (
"github.com/iotaledger/hive.go/objectstorage"
)
type CachedConsumers struct {
*objectstorage.CachedObject
}
func (cachedObject *CachedConsumers) Unwrap() *Consumers {
if untypedObject := cachedObject.Get(); untypedObject == nil {
return nil
} else {
if typedObject := untypedObject.(*Consumers); typedObject == nil || typedObject.IsDeleted() {
return nil
} else {
return typedObject
}
}
}
package model
import (
"github.com/iotaledger/hive.go/objectstorage"
)
type CachedTransferMetadata struct {
*objectstorage.CachedObject
}
func (cachedObject *CachedTransferMetadata) Unwrap() *TransferMetadata {
if untypedObject := cachedObject.Get(); untypedObject == nil {
return nil
} else {
if typedObject := untypedObject.(*TransferMetadata); typedObject == nil || typedObject.IsDeleted() {
return nil
} else {
return typedObject
}
}
}
......@@ -3,23 +3,20 @@ package model
import (
"github.com/iotaledger/goshimmer/packages/binary/transaction"
"github.com/iotaledger/goshimmer/packages/binary/transaction/payload/valuetransfer"
"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
)
type CachedValueTransfer struct {
*transaction.CachedTransaction
}
func (cachedValueTransfer *CachedValueTransfer) Unwrap() (*valuetransfer.ValueTransfer, transfer.Id) {
func (cachedValueTransfer *CachedValueTransfer) Unwrap() *ValueTransfer {
if untypedTransaction := cachedValueTransfer.Get(); untypedTransaction == nil {
return nil, transfer.EmptyId
return nil
} else {
if typeCastedTransaction := untypedTransaction.(*transaction.Transaction); typeCastedTransaction == nil || typeCastedTransaction.IsDeleted() || typeCastedTransaction.GetPayload().GetType() != valuetransfer.Type {
return nil, transfer.EmptyId
return nil
} else {
transactionId := typeCastedTransaction.GetId()
return typeCastedTransaction.GetPayload().(*valuetransfer.ValueTransfer), transfer.NewId(transactionId[:])
return NewValueTransfer(typeCastedTransaction)
}
}
}
......@@ -26,15 +26,11 @@ func NewConsumers(transferId transfer.Id) *Consumers {
// Get's called when we restore the approvers from storage. The bytes and the content will be unmarshaled by an external
// caller (the objectStorage factory).
func FromStorage(id []byte) (result objectstorage.StorableObject) {
var transferId transfer.Id
copy(transferId[:], id)
func ConsumersFromStorage(id []byte) objectstorage.StorableObject {
result := &Consumers{}
copy(result.transferId[:], id)
result = &Consumers{
transferId: transferId,
}
return
return result
}
func (consumers *Consumers) GetTransferId() transfer.Id {
......
......@@ -15,7 +15,7 @@ type MissingTransfer struct {
missingSince time.Time
}
func New(transferId transfer.Id) *MissingTransfer {
func NewMissingTransfer(transferId transfer.Id) *MissingTransfer {
return &MissingTransfer{
transferId: transferId,
missingSince: time.Now(),
......
package model
import (
"sync"
"time"
"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
"github.com/iotaledger/hive.go/objectstorage"
)
type TransferMetadata struct {
objectstorage.StorableObjectFlags
transferId transfer.Id
receivedTime time.Time
solid bool
solidificationTime time.Time
solidMutex sync.RWMutex
}
func NewTransferMetadata(transferId transfer.Id) *TransferMetadata {
return &TransferMetadata{
transferId: transferId,
receivedTime: time.Now(),
}
}
func TransferMetadataFromStorage(id []byte) objectstorage.StorableObject {
result := &TransferMetadata{}
copy(result.transferId[:], id)
return result
}
func (transferMetadata *TransferMetadata) IsSolid() (result bool) {
transferMetadata.solidMutex.RLock()
result = transferMetadata.solid
transferMetadata.solidMutex.RUnlock()
return
}
func (transferMetadata *TransferMetadata) SetSolid(solid bool) (modified bool) {
transferMetadata.solidMutex.RLock()
if transferMetadata.solid != solid {
transferMetadata.solidMutex.RUnlock()
transferMetadata.solidMutex.Lock()
if transferMetadata.solid != solid {
transferMetadata.solid = solid
transferMetadata.SetModified()
modified = true
}
transferMetadata.solidMutex.Unlock()
} else {
transferMetadata.solidMutex.RUnlock()
}
return
}
func (transferMetadata *TransferMetadata) GetStorageKey() []byte {
return transferMetadata.transferId[:]
}
func (transferMetadata *TransferMetadata) Update(other objectstorage.StorableObject) {
panic("TransferMetadata should never be overwritten")
}
func (transferMetadata *TransferMetadata) MarshalBinary() ([]byte, error) {
return nil, nil
}
func (transferMetadata *TransferMetadata) UnmarshalBinary([]byte) error {
return nil
}
package transferoutput
import (
"encoding/binary"
"sync"
"github.com/iotaledger/goshimmer/packages/binary/address"
"github.com/iotaledger/goshimmer/packages/binary/transaction"
"github.com/iotaledger/goshimmer/packages/binary/types"
"github.com/iotaledger/goshimmer/packages/ledgerstate/coloredcoins"
"github.com/iotaledger/goshimmer/packages/ledgerstate/reality"
"github.com/iotaledger/goshimmer/packages/stringify"
"github.com/iotaledger/hive.go/objectstorage"
)
type TransferOutput struct {
objectstorage.StorableObjectFlags
transactionId transaction.Id
address address.Address
realityId reality.Id
balances []*coloredcoins.ColoredBalance
consumers map[transaction.Id]types.Empty
realityIdMutex sync.RWMutex
consumersMutex sync.RWMutex
}
func NewTransferOutput(transactionId transaction.Id, address address.Address, balances ...*coloredcoins.ColoredBalance) *TransferOutput {
return &TransferOutput{
transactionId: transactionId,
address: address,
balances: balances,
realityId: reality.EmptyId,
consumers: make(map[transaction.Id]types.Empty),
}
}
func FromStorage(key []byte) objectstorage.StorableObject {
result := &TransferOutput{}
offset := 0
if err := result.transactionId.UnmarshalBinary(key[offset:]); err != nil {
panic(err)
}
offset += transaction.IdLength
if err := result.address.UnmarshalBinary(key[offset:]); err != nil {
panic(err)
}
return result
}
func (transferOutput *TransferOutput) GetTransactionId() (transactionId transaction.Id) {
transactionId = transferOutput.transactionId
return
}
func (transferOutput *TransferOutput) GetAddress() (address address.Address) {
return transferOutput.address
}
func (transferOutput *TransferOutput) GetRealityId() (realityId reality.Id) {
transferOutput.realityIdMutex.RLock()
realityId = transferOutput.realityId
transferOutput.realityIdMutex.RUnlock()
return
}
func (transferOutput *TransferOutput) SetRealityId(realityId reality.Id) (modified bool) {
transferOutput.realityIdMutex.RLock()
if transferOutput.realityId != realityId {
transferOutput.realityIdMutex.RUnlock()
transferOutput.realityIdMutex.Lock()
if transferOutput.realityId != realityId {
transferOutput.realityId = realityId
transferOutput.SetModified()
modified = true
}
transferOutput.realityIdMutex.Unlock()
} else {
transferOutput.realityIdMutex.RUnlock()
}
return
}
func (transferOutput *TransferOutput) GetBalances() []*coloredcoins.ColoredBalance {
return transferOutput.balances
}
func (transferOutput *TransferOutput) GetConsumers() (consumers map[transaction.Id]types.Empty) {
consumers = make(map[transaction.Id]types.Empty)
transferOutput.consumersMutex.RLock()
for transferHash := range transferOutput.consumers {
consumers[transferHash] = types.Void
}
transferOutput.consumersMutex.RUnlock()
return
}
func (transferOutput *TransferOutput) IsSpent() (result bool) {
transferOutput.consumersMutex.RLock()
result = len(transferOutput.consumers) >= 1
transferOutput.consumersMutex.RUnlock()
return
}
func (transferOutput *TransferOutput) AddConsumer(consumer transaction.Id) (previousConsumers map[transaction.Id]types.Empty) {
transferOutput.consumersMutex.RLock()
if _, exist := transferOutput.consumers[consumer]; !exist {
transferOutput.consumersMutex.RUnlock()
transferOutput.consumersMutex.Lock()
if _, exist := transferOutput.consumers[consumer]; !exist {
previousConsumers = make(map[transaction.Id]types.Empty)
for transactionId := range transferOutput.consumers {
previousConsumers[transactionId] = types.Void
}
transferOutput.consumers[consumer] = types.Void
transferOutput.SetModified()
}
transferOutput.consumersMutex.Unlock()
} else {
transferOutput.consumersMutex.RUnlock()
}
return
}
func (transferOutput *TransferOutput) String() string {
return stringify.Struct("TransferOutput",
stringify.StructField("transactionId", transferOutput.GetTransactionId().String()),
stringify.StructField("address", transferOutput.GetAddress().String()),
stringify.StructField("realityId", transferOutput.GetRealityId().String()),
stringify.StructField("balances", transferOutput.GetBalances()),
stringify.StructField("spent", len(transferOutput.GetConsumers()) >= 1),
)
}
func (transferOutput *TransferOutput) GetStorageKey() []byte {
return append(transferOutput.transactionId[:], transferOutput.address[:]...)
}
func (transferOutput *TransferOutput) Update(other objectstorage.StorableObject) {
panic("TransferOutput should never be overwritten / updated")
}
func (transferOutput *TransferOutput) MarshalBinary() ([]byte, error) {
transferOutput.realityIdMutex.RLock()
transferOutput.consumersMutex.RLock()
balanceCount := len(transferOutput.balances)
consumerCount := len(transferOutput.consumers)
result := make([]byte, reality.IdLength+4+balanceCount*coloredcoins.BalanceLength+4+consumerCount*transaction.IdLength)
offset := 0
copy(result[0:], transferOutput.realityId[:])
offset += reality.IdLength
binary.LittleEndian.PutUint32(result[offset:], uint32(balanceCount))
offset += 4
for i := 0; i < balanceCount; i++ {
if marshaledColoredBalance, err := transferOutput.balances[i].MarshalBinary(); err != nil {
return nil, err
} else {
copy(result[offset:], marshaledColoredBalance)
offset += coloredcoins.BalanceLength
}
}
binary.LittleEndian.PutUint32(result[offset:], uint32(consumerCount))
offset += 4
for transactionId := range transferOutput.consumers {
copy(result[offset:], transactionId[:])
offset += transaction.IdLength
}
transferOutput.consumersMutex.RUnlock()
transferOutput.realityIdMutex.RUnlock()
return result, nil
}
func (transferOutput *TransferOutput) UnmarshalBinary(serializedObject []byte) error {
offset := 0
if err := transferOutput.realityId.UnmarshalBinary(serializedObject[offset:]); err != nil {
return err
}
offset += reality.IdLength
if balances, err := transferOutput.unmarshalBalances(serializedObject, &offset); err != nil {
return err
} else {
transferOutput.balances = balances
}
if consumers, err := transferOutput.unmarshalConsumers(serializedObject, &offset); err != nil {
return err
} else {
transferOutput.consumers = consumers
}
return nil
}
func (transferOutput *TransferOutput) unmarshalBalances(serializedBalances []byte, offset *int) ([]*coloredcoins.ColoredBalance, error) {
balanceCount := int(binary.LittleEndian.Uint32(serializedBalances))
*offset += 4
balances := make([]*coloredcoins.ColoredBalance, balanceCount)
for i := 0; i < balanceCount; i++ {
coloredBalance := coloredcoins.ColoredBalance{}
if err := coloredBalance.UnmarshalBinary(serializedBalances[4+i*coloredcoins.BalanceLength:]); err != nil {
return nil, err
}
*offset += coloredcoins.BalanceLength
balances[i] = &coloredBalance
}
return balances, nil
}
func (transferOutput *TransferOutput) unmarshalConsumers(serializedConsumers []byte, offset *int) (map[transaction.Id]types.Empty, error) {
consumerCount := int(binary.LittleEndian.Uint32(serializedConsumers[*offset:]))
*offset += 4
consumers := make(map[transaction.Id]types.Empty, consumerCount)
for i := 0; i < consumerCount; i++ {
var transactionId transaction.Id
if err := transactionId.UnmarshalBinary(serializedConsumers[*offset:]); err != nil {
return nil, err
}
*offset += transaction.IdLength
}
return consumers, nil
}
type CachedTransferOutput struct {
*objectstorage.CachedObject
}
func (cachedObject *CachedTransferOutput) Unwrap() *TransferOutput {
if untypedObject := cachedObject.Get(); untypedObject == nil {
return nil
} else {
if typedObject := untypedObject.(*TransferOutput); typedObject == nil || typedObject.IsDeleted() {
return nil
} else {
return typedObject
}
}
}
package model
import (
"sync"
"github.com/iotaledger/goshimmer/packages/binary/address"
"github.com/iotaledger/goshimmer/packages/binary/transaction"
"github.com/iotaledger/goshimmer/packages/binary/transaction/payload/valuetransfer"
"github.com/iotaledger/goshimmer/packages/binary/types"
"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
)
type ValueTransfer struct {
*transaction.Transaction
*valuetransfer.ValueTransfer
inputs map[transfer.Id]map[address.Address]types.Empty
inputsMutex sync.RWMutex
}
func NewValueTransfer(transaction *transaction.Transaction) *ValueTransfer {
return &ValueTransfer{
Transaction: transaction,
ValueTransfer: transaction.GetPayload().(*valuetransfer.ValueTransfer),
}
}
func (valueTransfer *ValueTransfer) GetId() transfer.Id {
transactionId := valueTransfer.Transaction.GetId()
return transfer.NewId(transactionId[:])
}
func (valueTransfer *ValueTransfer) GetInputs() (result map[transfer.Id]map[address.Address]types.Empty) {
valueTransfer.inputsMutex.RLock()
if valueTransfer.inputs == nil {
valueTransfer.inputsMutex.RUnlock()
valueTransfer.inputsMutex.Lock()
if valueTransfer.inputs == nil {
result = make(map[transfer.Id]map[address.Address]types.Empty)
for _, transferOutputReference := range valueTransfer.ValueTransfer.GetInputs() {
addressMap, addressMapExists := result[transferOutputReference.GetTransferHash()]
if !addressMapExists {
addressMap = make(map[address.Address]types.Empty)
result[transferOutputReference.GetTransferHash()] = addressMap
}
addressMap[transferOutputReference.GetAddress()] = types.Void
}
valueTransfer.inputs = result
} else {
result = valueTransfer.inputs
}
valueTransfer.inputsMutex.Unlock()
} else {
result = valueTransfer.inputs
}
return
}
......@@ -2,15 +2,19 @@ package valuetangle
import (
"container/list"
"fmt"
"time"
"github.com/iotaledger/goshimmer/packages/binary/address"
"github.com/iotaledger/goshimmer/packages/binary/types"
"github.com/iotaledger/goshimmer/packages/storageprefix"
"github.com/iotaledger/goshimmer/packages/binary/tangle"
"github.com/iotaledger/goshimmer/packages/binary/tangle/approvers"
"github.com/iotaledger/goshimmer/packages/binary/tangle/missingtransaction"
"github.com/iotaledger/goshimmer/packages/binary/transaction"
"github.com/iotaledger/goshimmer/packages/binary/transaction/payload/valuetransfer"
"github.com/iotaledger/goshimmer/packages/binary/transactionmetadata"
"github.com/iotaledger/goshimmer/packages/binary/types"
"github.com/iotaledger/goshimmer/packages/binary/valuetangle/model"
"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
"github.com/iotaledger/hive.go/async"
......@@ -18,7 +22,12 @@ import (
"github.com/iotaledger/hive.go/objectstorage"
)
// The value tangle defines an "ontology" on top of the tangle that "sees"" the value transfers as a hidden tangle in
const (
MaxMissingTimeBeforeCleanup = 30 * time.Second
MissingCheckInterval = 5 * time.Second
)
// The "value tangle" defines an "ontology" on top of the tangle that "sees"" the value transfers as a hidden tangle in
// the tangle.
type ValueTangle struct {
tangle *tangle.Tangle
......@@ -29,7 +38,7 @@ type ValueTangle struct {
storeTransactionsWorkerPool async.WorkerPool
solidifierWorkerPool async.WorkerPool
// cleanupWorkerPool async.WorkerPool
cleanupWorkerPool async.WorkerPool
Events Events
}
......@@ -37,8 +46,16 @@ type ValueTangle struct {
func New(tangle *tangle.Tangle) (valueTangle *ValueTangle) {
valueTangle = &ValueTangle{
tangle: tangle,
transferMetadataStorage: objectstorage.New(append(tangle.GetStorageId(), storageprefix.ValueTangleTransferMetadata...), model.TransferMetadataFromStorage),
consumersStorage: objectstorage.New(append(tangle.GetStorageId(), storageprefix.ValueTangleConsumers...), model.ConsumersFromStorage),
missingTransferStorage: objectstorage.New(append(tangle.GetStorageId(), storageprefix.TangleMissingTransaction...), model.MissingTransferFromStorage),
Events: *newEvents(),
}
valueTangle.solidifierWorkerPool.Tune(1024)
tangle.Events.TransactionSolid.Attach(events.NewClosure(valueTangle.attachTransaction))
tangle.Events.TransactionRemoved.Attach(events.NewClosure(valueTangle.deleteTransfer))
......@@ -51,36 +68,60 @@ func (valueTangle *ValueTangle) attachTransaction(cachedTransaction *transaction
})
}
// Retrieves a transaction from the tangle.
func (valueTangle *ValueTangle) GetTransfer(transactionId transaction.Id) *model.CachedValueTransfer {
cachedTransaction := valueTangle.tangle.GetTransaction(transactionId)
// Retrieves a transfer from the tangle.
func (valueTangle *ValueTangle) GetTransfer(transferId transfer.Id) *model.CachedValueTransfer {
cachedTransaction := valueTangle.tangle.GetTransaction(transaction.NewId(transferId[:]))
// return an empty result if the transaction is no value transaction
// return an empty result if the transfer is no value transfer
if tx := cachedTransaction.Unwrap(); tx != nil && tx.GetPayload().GetType() != valuetransfer.Type {
cachedTransaction.Release()
return &model.CachedValueTransfer{CachedTransaction: &transaction.CachedTransaction{CachedObject: objectstorage.NewEmptyCachedObject(transactionId[:])}}
return &model.CachedValueTransfer{CachedTransaction: &transaction.CachedTransaction{CachedObject: objectstorage.NewEmptyCachedObject(transferId[:])}}
}
return &model.CachedValueTransfer{CachedTransaction: cachedTransaction}
}
// Retrieves the metadata of a transaction from the tangle.
func (valueTangle *ValueTangle) GetTransferMetadata(transactionId transaction.Id) *transactionmetadata.CachedTransactionMetadata {
return &transactionmetadata.CachedTransactionMetadata{CachedObject: valueTangle.transferMetadataStorage.Load(transactionId[:])}
// Retrieves the metadata of a transfer from the tangle.
func (valueTangle *ValueTangle) GetTransferMetadata(transferId transfer.Id) *model.CachedTransferMetadata {
return &model.CachedTransferMetadata{CachedObject: valueTangle.transferMetadataStorage.Load(transferId[:])}
}
// Retrieves the approvers of a transfer from the tangle.
func (valueTangle *ValueTangle) GetConsumers(transferId transfer.Id) *model.CachedConsumers {
return &model.CachedConsumers{CachedObject: valueTangle.consumersStorage.Load(transferId[:])}
}
func (valueTangle *ValueTangle) deleteTransfer(transactionId transaction.Id) {
func (valueTangle *ValueTangle) deleteTransfer(transferIf transfer.Id) {
}
// Marks the tangle as stopped, so it will not accept any new transactions (waits for all backgroundTasks to finish.
// Marks the tangle as stopped, so it will not accept any new transfers (waits for all backgroundTasks to finish.
func (valueTangle *ValueTangle) Shutdown() *ValueTangle {
valueTangle.tangle.Shutdown()
valueTangle.storeTransactionsWorkerPool.ShutdownGracefully()
valueTangle.solidifierWorkerPool.ShutdownGracefully()
valueTangle.cleanupWorkerPool.ShutdownGracefully()
return valueTangle
}
// Resets the database and deletes all objects (good for testing or "node resets").
func (valueTangle *ValueTangle) Prune() error {
for _, storage := range []*objectstorage.ObjectStorage{
valueTangle.transferMetadataStorage,
valueTangle.consumersStorage,
valueTangle.missingTransferStorage,
} {
if err := storage.Prune(); err != nil {
return err
}
}
return nil
}
func (valueTangle *ValueTangle) storeTransactionWorker(cachedTx *transaction.CachedTransaction, cachedTxMetadata *transactionmetadata.CachedTransactionMetadata) {
addTransferToConsumers := func(transferId transfer.Id, consumedTransferHash transfer.Id) {
cachedConsumers := valueTangle.consumersStorage.ComputeIfAbsent(consumedTransferHash[:], func([]byte) objectstorage.StorableObject {
......@@ -107,113 +148,129 @@ func (valueTangle *ValueTangle) storeTransactionWorker(cachedTx *transaction.Cac
cachedValueTransfer := &model.CachedValueTransfer{CachedTransaction: cachedTx}
valueTransfer, transferId := cachedValueTransfer.Unwrap()
valueTransfer := cachedValueTransfer.Unwrap()
if valueTransfer == nil {
cachedValueTransfer.Release()
return
}
for transferId := range valueTangle.getInputsMap(valueTransfer) {
addTransferToConsumers(transferId, transferId)
transferId := valueTransfer.GetId()
cachedTransferMetadata := &model.CachedTransferMetadata{CachedObject: valueTangle.transferMetadataStorage.Store(model.NewTransferMetadata(transferId))}
for referencedTransferId := range valueTransfer.GetInputs() {
addTransferToConsumers(transferId, referencedTransferId)
}
if valueTangle.missingTransferStorage.DeleteIfPresent(transferId[:]) {
valueTangle.Events.MissingTransferReceived.Trigger(transferId)
}
valueTangle.Events.TransferAttached.Trigger(cachedValueTransfer)
valueTangle.Events.TransferAttached.Trigger(cachedValueTransfer, cachedTransferMetadata)
valueTangle.solidifierWorkerPool.Submit(func() {
valueTangle.solidifyTransferWorker(cachedValueTransfer, transferId)
valueTangle.solidifyTransferWorker(cachedValueTransfer, cachedTransferMetadata)
})
}
// Worker that solidifies the transactions (recursively from past to present).
func (valueTangle *ValueTangle) solidifyTransferWorker(cachedValueTransfer *model.CachedValueTransfer, transferId transfer.Id) {
isTransferMarkedAsSolid := func(transactionId transaction.Id) bool {
if transactionId == transaction.EmptyId {
return true
}
transferMetadataCached := valueTangle.GetTransferMetadata(transactionId)
if transactionMetadata := transferMetadataCached.Unwrap(); transactionMetadata == nil {
// Worker that solidifies the transfers (recursively from past to present).
func (valueTangle *ValueTangle) solidifyTransferWorker(cachedValueTransfer *model.CachedValueTransfer, cachedTransferMetadata *model.CachedTransferMetadata) {
areInputsSolid := func(transferId transfer.Id, addresses map[address.Address]types.Empty) bool {
transferMetadataCached := valueTangle.GetTransferMetadata(transferId)
if transferMetadata := transferMetadataCached.Unwrap(); transferMetadata == nil {
transferMetadataCached.Release()
// if transaction is missing and was not reported as missing, yet
if cachedMissingTransfer, missingTransactionStored := valueTangle.missingTransferStorage.StoreIfAbsent(transactionId[:], missingtransaction.New(transactionId)); missingTransactionStored {
// if transfer is missing and was not reported as missing, yet
if cachedMissingTransfer, missingTransactionStored := valueTangle.missingTransferStorage.StoreIfAbsent(transferId[:], model.NewMissingTransfer(transferId)); missingTransactionStored {
cachedMissingTransfer.Consume(func(object objectstorage.StorableObject) {
valueTangle.monitorMissingTransactionWorker(object.(*missingtransaction.MissingTransaction).GetTransactionId())
valueTangle.monitorMissingTransactionWorker(object.(*model.MissingTransfer).GetTransferId())
})
}
return false
} else if !transactionMetadata.IsSolid() {
} else if !transferMetadata.IsSolid() {
transferMetadataCached.Release()
return false
}
transferMetadataCached.Release()
cachedTransfer := valueTangle.GetTransfer(transferId)
if valueTransfer := cachedTransfer.Unwrap(); valueTransfer == nil {
cachedTransfer.Release()
return false
} else {
outputs := valueTransfer.GetOutputs()
for address := range outputs {
if _, addressExists := outputs[address]; !addressExists {
cachedTransfer.Release()
fmt.Println("INVALID TX DETECTED")
return false
}
}
}
cachedTransfer.Release()
return true
}
isTransactionSolid := func(transaction *transaction.Transaction, transactionMetadata *transactionmetadata.TransactionMetadata) bool {
if transaction == nil || transaction.IsDeleted() {
isTransferSolid := func(transfer *model.ValueTransfer, transferMetadata *model.TransferMetadata) bool {
if transfer == nil || transfer.IsDeleted() {
return false
}
if transactionMetadata == nil || transactionMetadata.IsDeleted() {
if transferMetadata == nil || transferMetadata.IsDeleted() {
return false
}
if transactionMetadata.IsSolid() {
if transferMetadata.IsSolid() {
return true
}
// 1. check tangle solidity
isTrunkSolid := isTransferMarkedAsSolid(transaction.GetTrunkTransactionId())
isBranchSolid := isTransferMarkedAsSolid(transaction.GetBranchTransactionId())
if isTrunkSolid && isBranchSolid {
// 2. check payload solidity
return true
inputsSolid := true
for inputTransferId, addresses := range transfer.GetInputs() {
inputsSolid = inputsSolid && areInputsSolid(inputTransferId, addresses)
}
return false
return inputsSolid
}
popElementsFromStack := func(stack *list.List) (*transaction.CachedTransaction, *transactionmetadata.CachedTransactionMetadata) {
popElementsFromStack := func(stack *list.List) (*model.CachedValueTransfer, *model.CachedTransferMetadata) {
currentSolidificationEntry := stack.Front()
currentCachedTransaction := currentSolidificationEntry.Value.([2]interface{})[0]
currentCachedTransactionMetadata := currentSolidificationEntry.Value.([2]interface{})[1]
currentCachedTransfer := currentSolidificationEntry.Value.([2]interface{})[0]
currentCachedTransferMetadata := currentSolidificationEntry.Value.([2]interface{})[1]
stack.Remove(currentSolidificationEntry)
return currentCachedTransaction.(*transaction.CachedTransaction), currentCachedTransactionMetadata.(*transactionmetadata.CachedTransactionMetadata)
return currentCachedTransfer.(*model.CachedValueTransfer), currentCachedTransferMetadata.(*model.CachedTransferMetadata)
}
// initialize the stack
solidificationStack := list.New()
solidificationStack.PushBack([2]interface{}{cachedValueTransfer, transferId})
solidificationStack.PushBack([2]interface{}{cachedValueTransfer, cachedTransferMetadata})
// process transactions that are supposed to be checked for solidity recursively
// process transfers that are supposed to be checked for solidity recursively
for solidificationStack.Len() > 0 {
currentCachedTransaction, currentCachedTransactionMetadata := popElementsFromStack(solidificationStack)
currentCachedTransfer, currentCachedTransferMetadata := popElementsFromStack(solidificationStack)
currentTransaction := currentCachedTransaction.Unwrap()
currentTransactionMetadata := currentCachedTransactionMetadata.Unwrap()
if currentTransaction == nil || currentTransactionMetadata == nil {
currentCachedTransaction.Release()
currentCachedTransactionMetadata.Release()
currentTransfer := currentCachedTransfer.Unwrap()
currentTransferMetadata := currentCachedTransferMetadata.Unwrap()
if currentTransfer == nil || currentTransferMetadata == nil {
currentCachedTransfer.Release()
currentCachedTransferMetadata.Release()
continue
}
// if current transaction is solid and was not marked as solid before: mark as solid and propagate
if isTransactionSolid(currentTransaction, currentTransactionMetadata) && currentTransactionMetadata.SetSolid(true) {
valueTangle.Events.TransferSolid.Trigger(currentCachedTransaction, currentCachedTransactionMetadata)
// if current transfer is solid and was not marked as solid before: mark as solid and propagate
if isTransferSolid(currentTransfer, currentTransferMetadata) && currentTransferMetadata.SetSolid(true) {
valueTangle.Events.TransferSolid.Trigger(currentCachedTransfer, currentCachedTransferMetadata)
valueTangle.GetConsumers(currentTransaction.GetId()).Consume(func(object objectstorage.StorableObject) {
for approverTransactionId := range object.(*approvers.Approvers).Get() {
valueTangle.GetConsumers(currentTransfer.GetId()).Consume(func(object objectstorage.StorableObject) {
for approverTransactionId := range object.(*model.Consumers).Get() {
solidificationStack.PushBack([2]interface{}{
valueTangle.GetTransfer(approverTransactionId),
valueTangle.GetTransferMetadata(approverTransactionId),
......@@ -223,24 +280,50 @@ func (valueTangle *ValueTangle) solidifyTransferWorker(cachedValueTransfer *mode
}
// release cached results
currentCachedTransaction.Release()
currentCachedTransactionMetadata.Release()
currentCachedTransfer.Release()
currentCachedTransferMetadata.Release()
}
}
func (valueTangle *ValueTangle) getInputsMap(valueTransfer *valuetransfer.ValueTransfer) (result map[transfer.Id]map[address.Address]types.Empty) {
result = make(map[transfer.Id]map[address.Address]types.Empty)
// Worker that Monitors the missing transfers (by scheduling regular checks).
func (valueTangle *ValueTangle) monitorMissingTransactionWorker(transferId transfer.Id) {
var scheduleNextMissingCheck func(transferId transfer.Id)
scheduleNextMissingCheck = func(transferId transfer.Id) {
time.AfterFunc(MissingCheckInterval, func() {
valueTangle.missingTransferStorage.Load(transferId[:]).Consume(func(object objectstorage.StorableObject) {
missingTransfer := object.(*model.MissingTransfer)
for _, transferOutputReference := range valueTransfer.GetInputs() {
addressMap, addressMapExists := result[transferOutputReference.GetTransferHash()]
if !addressMapExists {
addressMap = make(map[address.Address]types.Empty)
if time.Since(missingTransfer.GetMissingSince()) >= MaxMissingTimeBeforeCleanup {
valueTangle.cleanupWorkerPool.Submit(func() { valueTangle.cleanupWorker(missingTransfer.GetTransferId()) })
} else {
valueTangle.Events.TransferMissing.Trigger(transferId)
result[transferOutputReference.GetTransferHash()] = addressMap
scheduleNextMissingCheck(transferId)
}
})
})
}
valueTangle.Events.TransferMissing.Trigger(transferId)
addressMap[transferOutputReference.GetAddress()] = types.Void
scheduleNextMissingCheck(transferId)
}
return
// Worker that recursively cleans up the approvers of a unsolidifiable missing transfer.
func (valueTangle *ValueTangle) cleanupWorker(transferId transfer.Id) {
cleanupStack := list.New()
cleanupStack.PushBack(transferId)
for cleanupStack.Len() >= 1 {
currentStackEntry := cleanupStack.Front()
currentTransferId := currentStackEntry.Value.(transfer.Id)
cleanupStack.Remove(currentStackEntry)
valueTangle.GetConsumers(currentTransferId).Consume(func(object objectstorage.StorableObject) {
for approverTransactionId := range object.(*model.Consumers).Get() {
valueTangle.deleteTransfer(currentTransferId)
cleanupStack.PushBack(approverTransactionId)
}
})
}
}
package valuetangle
import (
"fmt"
"testing"
"time"
"github.com/mr-tron/base58"
"github.com/iotaledger/goshimmer/packages/binary/address"
"github.com/iotaledger/goshimmer/packages/binary/transactionmetadata"
"github.com/iotaledger/goshimmer/packages/ledgerstate/coloredcoins"
"github.com/iotaledger/goshimmer/packages/ledgerstate/transfer"
"github.com/iotaledger/goshimmer/packages/binary/signature/ed25119"
"github.com/iotaledger/goshimmer/packages/binary/transaction/payload/valuetransfer"
"github.com/iotaledger/goshimmer/packages/binary/identity"
"github.com/iotaledger/goshimmer/packages/binary/transaction"
"github.com/iotaledger/goshimmer/packages/binary/tangle"
"github.com/iotaledger/goshimmer/packages/binary/valuetangle/model"
"github.com/iotaledger/hive.go/events"
)
var localSnapshot *tangle.Snapshot
var keyPairOfAddress1 = ed25119.GenerateKeyPair()
var keyPairOfAddress2 = ed25119.GenerateKeyPair()
func getLocalSnapshot() *tangle.Snapshot {
if localSnapshot == nil {
localSnapshot = &tangle.Snapshot{
SolidEntryPoints: map[transaction.Id]map[address.Address]*coloredcoins.ColoredBalance{
transaction.NewId([]byte("tx0")): nil,
transaction.NewId([]byte("tx1")): {
address.FromPublicKey(keyPairOfAddress1.PublicKey): coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 1337),
address.FromPublicKey(keyPairOfAddress2.PublicKey): coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 1337),
},
transaction.NewId([]byte("tx2")): {
address.New([]byte("address3")): coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 1337),
address.New([]byte("address4")): coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 1337),
},
},
}
}
return localSnapshot
}
func TestValueTangle(t *testing.T) {
transactionTangle := tangle.New([]byte("testtangle"))
transactionTangle.Prune()
valueTangle := New(transactionTangle)
valueTangle.Prune()
transactionTangle.Events.TransactionAttached.Attach(events.NewClosure(func(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *transactionmetadata.CachedTransactionMetadata) {
transactionId := cachedTransaction.Unwrap().GetId()
if cachedTransaction.Unwrap().GetPayload().GetType() != valuetransfer.Type {
fmt.Println("[TRANSACTION TANGLE] Data transaction attached: ", base58.Encode(transactionId[:]))
} else {
fmt.Println("[TRANSACTION TANGLE] Value Transaction attached:", base58.Encode(transactionId[:]))
}
}))
transactionTangle.Events.TransactionSolid.Attach(events.NewClosure(func(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *transactionmetadata.CachedTransactionMetadata) {
transactionId := cachedTransaction.Unwrap().GetId()
if cachedTransaction.Unwrap().GetPayload().GetType() != valuetransfer.Type {
fmt.Println("[TRANSACTION TANGLE] Data transaction solid: ", base58.Encode(transactionId[:]))
} else {
fmt.Println("[TRANSACTION TANGLE] Value Transaction solid: ", base58.Encode(transactionId[:]))
}
}))
valueTangle.Events.TransferAttached.Attach(events.NewClosure(func(cachedValueTransfer *model.CachedValueTransfer, cachedTransferMetadata *model.CachedTransferMetadata) {
transactionId := cachedValueTransfer.Unwrap().GetId()
fmt.Println("[VALUE TANGLE] Value Transaction attached:", base58.Encode(transactionId[:]))
}))
valueTangle.Events.TransferSolid.Attach(events.NewClosure(func(cachedValueTransfer *model.CachedValueTransfer, cachedTransferMetadata *model.CachedTransferMetadata) {
transactionId := cachedValueTransfer.Unwrap().GetId()
fmt.Println("[VALUE TANGLE] Value Transaction solid: ", base58.Encode(transactionId[:]))
}))
transactionTangle.LoadSnapshot(getLocalSnapshot())
myIdentity := identity.Generate()
transactionTangle.AttachTransaction(transaction.New(transaction.EmptyId, transaction.EmptyId, myIdentity, valuetransfer.New().
AddInput(transfer.NewId([]byte("tx1")), address.FromPublicKey(keyPairOfAddress1.PublicKey)).
AddOutput(address.FromPublicKey(keyPairOfAddress2.PublicKey), coloredcoins.NewColoredBalance(coloredcoins.NewColor("IOTA"), 12)).
Sign(keyPairOfAddress1)))
time.Sleep(1 * time.Second)
valueTangle.Shutdown()
}
......@@ -44,4 +44,4 @@ func (transferId Id) String() string {
var EmptyId = Id{}
const IdLength = 32
const IdLength = 64
......@@ -6,6 +6,10 @@ var (
TangleApprovers = []byte{1}
TangleMissingTransaction = []byte{7}
ValueTangleTransferMetadata = []byte{8}
ValueTangleConsumers = []byte{9}
ValueTangleMissingTransfers = []byte{10}
LedgerStateTransferOutput = []byte{2}
LedgerStateTransferOutputBooking = []byte{3}
LedgerStateReality = []byte{4}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment