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
No related branches found
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