diff --git a/.gitignore b/.gitignore index 52422ce9cd3e58f2bd5826129ccae669d1eaf759..b376e693d4a49e941472f91450d7dc7937651069 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ testNodes/* # Database directory mainnetdb/ +objectsdb/ # OSX related files .DS_Store diff --git a/go.mod b/go.mod index af0a116b1d214e88bc285a9bc55dc794fd1f7aec..ba050fa6eae140f517bf650b7ab4f4f4198b6d5e 100644 --- a/go.mod +++ b/go.mod @@ -13,13 +13,16 @@ require ( github.com/googollee/go-engine.io v1.4.3-0.20190924125625-798118fc0dd2 github.com/googollee/go-socket.io v1.4.3-0.20191204093753-683f8725b6d0 github.com/gorilla/websocket v1.4.1 - github.com/iotaledger/hive.go v0.0.0-20200207144536-27b18f10f09e + github.com/iotaledger/hive.go v0.0.0-20200207140228-0bb6cf145e6b github.com/iotaledger/iota.go v1.0.0-beta.14 github.com/labstack/echo v3.3.10+incompatible github.com/labstack/gommon v0.3.0 // indirect github.com/magiconair/properties v1.8.1 github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-isatty v0.0.11 // indirect + github.com/mr-tron/base58 v1.1.3 + github.com/oasislabs/ed25519 v0.0.0-20200206134218-2893bee822a3 + github.com/panjf2000/ants/v2 v2.2.2 github.com/pelletier/go-toml v1.6.0 // indirect github.com/pkg/errors v0.9.1 github.com/rogpeppe/go-internal v1.5.2 // indirect diff --git a/go.sum b/go.sum index a943eb062fea49c8fdba4266e04f99acc95be135..00bc8be66ac0563df21eda96138fca5816103f44 100644 --- a/go.sum +++ b/go.sum @@ -123,8 +123,8 @@ github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/iotaledger/hive.go v0.0.0-20200207144536-27b18f10f09e h1:eM0/mEG3ZPflZRJxOn0cc+/8ZZIpirEAyhzF4rzTKjM= -github.com/iotaledger/hive.go v0.0.0-20200207144536-27b18f10f09e/go.mod h1:wj3bFHlcX0NiEOWu5+WOg/MI/5N7PKCFnyaziaylB64= +github.com/iotaledger/hive.go v0.0.0-20200207140228-0bb6cf145e6b h1:SCd1rO4xXhAaQDOdofXl46G7Y/on9KphLtPJjqDgLsY= +github.com/iotaledger/hive.go v0.0.0-20200207140228-0bb6cf145e6b/go.mod h1:wj3bFHlcX0NiEOWu5+WOg/MI/5N7PKCFnyaziaylB64= github.com/iotaledger/iota.go v1.0.0-beta.9/go.mod h1:F6WBmYd98mVjAmmPVYhnxg8NNIWCjjH8VWT9qvv3Rc8= github.com/iotaledger/iota.go v1.0.0-beta.14 h1:Oeb28MfBuJEeXcGrLhTCJFtbsnc8y1u7xidsAmiOD5A= github.com/iotaledger/iota.go v1.0.0-beta.14/go.mod h1:F6WBmYd98mVjAmmPVYhnxg8NNIWCjjH8VWT9qvv3Rc8= @@ -166,10 +166,14 @@ github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00v github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mr-tron/base58 v1.1.3 h1:v+sk57XuaCKGXpWtVBX8YJzO7hMGx4Aajh4TQbdEFdc= +github.com/mr-tron/base58 v1.1.3/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/oasislabs/ed25519 v0.0.0-20200206134218-2893bee822a3 h1:xhsvlWpWPdHXHt8i5eaXf2WbAxHLciGqrfup6zAPjVQ= +github.com/oasislabs/ed25519 v0.0.0-20200206134218-2893bee822a3/go.mod h1:xIpCyrK2ouGA4QBGbiNbkoONrvJ00u9P3QOkXSOAC0c= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= @@ -177,6 +181,7 @@ github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= +github.com/panjf2000/ants/v2 v2.2.2 h1:TWzusBjq/IflXhy+/S6u5wmMLCBdJnB9tPIx9Zmhvok= github.com/panjf2000/ants/v2 v2.2.2/go.mod h1:1GFm8bV8nyCQvU5K4WvBCTG1/YBFOD2VzjffD8fV55A= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= @@ -319,6 +324,7 @@ golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaE golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= diff --git a/packages/binary/identity/constants.go b/packages/binary/identity/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..b8fb985060952f9cdca9bffc0a5b058f12b9d597 --- /dev/null +++ b/packages/binary/identity/constants.go @@ -0,0 +1,7 @@ +package identity + +const ( + PublicKeySize = 32 + PrivateKeySize = 64 + SignatureSize = 64 +) diff --git a/packages/binary/identity/identity.go b/packages/binary/identity/identity.go new file mode 100644 index 0000000000000000000000000000000000000000..fd52c6f3ff73724dd4d2e9c9f17a738af25b09e3 --- /dev/null +++ b/packages/binary/identity/identity.go @@ -0,0 +1,48 @@ +package identity + +import ( + "crypto/rand" + + "github.com/oasislabs/ed25519" +) + +type Identity struct { + Type Type + PublicKey []byte + PrivateKey []byte +} + +func New(publicKey []byte, optionalPrivateKey ...[]byte) *Identity { + this := &Identity{ + PublicKey: make([]byte, len(publicKey)), + } + + copy(this.PublicKey, publicKey) + + if len(optionalPrivateKey) == 0 { + this.Type = Public + } else { + this.Type = Private + this.PrivateKey = optionalPrivateKey[0] + } + + return this +} + +func Generate() *Identity { + if public, private, err := ed25519.GenerateKey(rand.Reader); err != nil { + panic(err) + } else { + return New(public, private) + } +} + +func (identity *Identity) Sign(data []byte) (sig []byte) { + sig = ed25519.Sign(identity.PrivateKey, data) + + return +} + +func (identity *Identity) VerifySignature(data []byte, signature []byte) bool { + return ed25519.Verify(identity.PublicKey, data, signature) +} diff --git a/packages/binary/identity/identity_test.go b/packages/binary/identity/identity_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f5fc0d525240ef9d53eed0e5499b612e4398485a --- /dev/null +++ b/packages/binary/identity/identity_test.go @@ -0,0 +1,41 @@ +package identity + +import ( + "sync" + "testing" + + "github.com/panjf2000/ants/v2" + + "github.com/stretchr/testify/assert" +) + +func BenchmarkIdentity_VerifySignature(b *testing.B) { + identity := Generate() + data := []byte("TESTDATA") + signature := identity.Sign(data) + + var wg sync.WaitGroup + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + wg.Add(1) + + _ = ants.Submit(func() { + identity.VerifySignature(data, signature) + + wg.Done() + }) + } + + wg.Wait() +} + +func Test(t *testing.T) { + identity := Generate() + + signature := identity.Sign([]byte("TESTDATA1")) + + assert.Equal(t, true, identity.VerifySignature([]byte("TESTDATA1"), signature)) + assert.Equal(t, false, identity.VerifySignature([]byte("TESTDATA2"), signature)) +} diff --git a/packages/binary/identity/type.go b/packages/binary/identity/type.go new file mode 100644 index 0000000000000000000000000000000000000000..cc207b0279d2b24bf0b86190431a3753f0b34124 --- /dev/null +++ b/packages/binary/identity/type.go @@ -0,0 +1,8 @@ +package identity + +type Type int + +const ( + Private = Type(0) + Public = Type(1) +) diff --git a/packages/binary/signature/ed25119/ed25119.go b/packages/binary/signature/ed25119/ed25119.go new file mode 100644 index 0000000000000000000000000000000000000000..d4b65997bf4657ceeaa43427a8f0b43a58fc2dfe --- /dev/null +++ b/packages/binary/signature/ed25119/ed25119.go @@ -0,0 +1,18 @@ +package ed25119 + +import ( + "crypto/rand" + + "github.com/oasislabs/ed25519" +) + +func GenerateKeyPair() (keyPair KeyPair) { + if public, private, err := ed25519.GenerateKey(rand.Reader); err != nil { + panic(err) + } else { + copy(keyPair.PrivateKey[:], private) + copy(keyPair.PublicKey[:], public) + + return + } +} diff --git a/packages/binary/signature/ed25119/key_pair.go b/packages/binary/signature/ed25119/key_pair.go new file mode 100644 index 0000000000000000000000000000000000000000..ad0c31bbe6ef5e06df08138dd66282e843322af4 --- /dev/null +++ b/packages/binary/signature/ed25119/key_pair.go @@ -0,0 +1,6 @@ +package ed25119 + +type KeyPair struct { + PrivateKey PrivateKey + PublicKey PublicKey +} diff --git a/packages/binary/signature/ed25119/private_key.go b/packages/binary/signature/ed25119/private_key.go new file mode 100644 index 0000000000000000000000000000000000000000..89dc1a15ecc01f5b088a0aca871390dbbcbd101e --- /dev/null +++ b/packages/binary/signature/ed25119/private_key.go @@ -0,0 +1,15 @@ +package ed25119 + +import ( + "github.com/oasislabs/ed25519" +) + +type PrivateKey [PrivateKeySize]byte + +func (privateKey PrivateKey) Sign(data []byte) (result Signature) { + copy(result[:], ed25519.Sign(privateKey[:], data)) + + return +} + +const PrivateKeySize = 64 diff --git a/packages/binary/signature/ed25119/public_key.go b/packages/binary/signature/ed25119/public_key.go new file mode 100644 index 0000000000000000000000000000000000000000..c9d58e254413349234283406c817f8c68c7d30e5 --- /dev/null +++ b/packages/binary/signature/ed25119/public_key.go @@ -0,0 +1,25 @@ +package ed25119 + +import ( + "errors" + + "github.com/oasislabs/ed25519" +) + +type PublicKey [PublicKeySize]byte + +func (publicKey PublicKey) VerifySignature(data []byte, signature Signature) bool { + return ed25519.Verify(publicKey[:], data, signature[:]) +} + +func (publicKey *PublicKey) UnmarshalBinary(bytes []byte) (err error) { + if len(bytes) < PublicKeySize { + return errors.New("not enough bytes") + } + + copy(publicKey[:], bytes[:]) + + return +} + +const PublicKeySize = 32 diff --git a/packages/binary/signature/ed25119/signature.go b/packages/binary/signature/ed25119/signature.go new file mode 100644 index 0000000000000000000000000000000000000000..bd33e1138e42da2809b0dac22afa1a140d5b8c3b --- /dev/null +++ b/packages/binary/signature/ed25119/signature.go @@ -0,0 +1,19 @@ +package ed25119 + +import ( + "errors" +) + +type Signature [SignatureSize]byte + +func (signature *Signature) UnmarshalBinary(bytes []byte) (err error) { + if len(bytes) < SignatureSize { + return errors.New("not enough bytes") + } + + copy(signature[:], bytes[:]) + + return +} + +const SignatureSize = 64 diff --git a/packages/binary/storageprefix/storageprefix.go b/packages/binary/storageprefix/storageprefix.go new file mode 100644 index 0000000000000000000000000000000000000000..c491a24e5d6adfeae617211e290b148a1f5738b3 --- /dev/null +++ b/packages/binary/storageprefix/storageprefix.go @@ -0,0 +1,17 @@ +package storageprefix + +var ( + TangleTransaction = []byte{0} + TangleTransactionMetadata = []byte{6} + TangleApprovers = []byte{1} + TangleMissingTransaction = []byte{7} + + ValueTangleTransferMetadata = []byte{8} + ValueTangleConsumers = []byte{9} + ValueTangleMissingTransfers = []byte{10} + + LedgerStateTransferOutput = []byte{2} + LedgerStateTransferOutputBooking = []byte{3} + LedgerStateReality = []byte{4} + LedgerStateConflictSet = []byte{5} +) diff --git a/packages/binary/tangle/events.go b/packages/binary/tangle/events.go new file mode 100644 index 0000000000000000000000000000000000000000..d2bf35523764694fd393df88ff9936244ddd5b54 --- /dev/null +++ b/packages/binary/tangle/events.go @@ -0,0 +1,39 @@ +package tangle + +import ( + "github.com/iotaledger/hive.go/events" + + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transactionmetadata" +) + +type Events struct { + TransactionAttached *events.Event + TransactionSolid *events.Event + MissingTransactionReceived *events.Event + TransactionMissing *events.Event + TransactionUnsolidifiable *events.Event + TransactionRemoved *events.Event +} + +func newEvents() *Events { + return &Events{ + TransactionAttached: events.NewEvent(cachedTransactionEvent), + TransactionSolid: events.NewEvent(cachedTransactionEvent), + MissingTransactionReceived: events.NewEvent(cachedTransactionEvent), + TransactionMissing: events.NewEvent(transactionIdEvent), + TransactionUnsolidifiable: events.NewEvent(transactionIdEvent), + TransactionRemoved: events.NewEvent(transactionIdEvent), + } +} + +func transactionIdEvent(handler interface{}, params ...interface{}) { + handler.(func(transaction.Id))(params[0].(transaction.Id)) +} + +func cachedTransactionEvent(handler interface{}, params ...interface{}) { + handler.(func(*transaction.CachedTransaction, *transactionmetadata.CachedTransactionMetadata))( + params[0].(*transaction.CachedTransaction).Retain(), + params[1].(*transactionmetadata.CachedTransactionMetadata).Retain().(*transactionmetadata.CachedTransactionMetadata), + ) +} diff --git a/packages/binary/tangle/model/approver/approver.go b/packages/binary/tangle/model/approver/approver.go new file mode 100644 index 0000000000000000000000000000000000000000..b08bf342d269616674e883d31791650dcdd29006 --- /dev/null +++ b/packages/binary/tangle/model/approver/approver.go @@ -0,0 +1,59 @@ +package approver + +import ( + "github.com/iotaledger/hive.go/objectstorage" + + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction" +) + +type Approver struct { + objectstorage.StorableObjectFlags + + storageKey []byte + referencedTransaction transaction.Id + approvingTransaction transaction.Id +} + +func New(referencedTransaction transaction.Id, approvingTransaction transaction.Id) *Approver { + approver := &Approver{ + storageKey: make([]byte, transaction.IdLength+transaction.IdLength), + referencedTransaction: referencedTransaction, + approvingTransaction: approvingTransaction, + } + + copy(approver.storageKey[:transaction.IdLength], referencedTransaction[:]) + copy(approver.storageKey[transaction.IdLength:], approvingTransaction[:]) + + return approver +} + +func FromStorage(id []byte) (result objectstorage.StorableObject) { + approver := &Approver{ + storageKey: make([]byte, transaction.IdLength+transaction.IdLength), + } + copy(approver.referencedTransaction[:], id[:transaction.IdLength]) + copy(approver.approvingTransaction[:], id[transaction.IdLength:]) + copy(approver.storageKey, id) + + return approver +} + +func (approver *Approver) GetStorageKey() []byte { + return approver.storageKey +} + +func (approver *Approver) GetApprovingTransactionId() transaction.Id { + return approver.approvingTransaction +} + +func (approver *Approver) Update(other objectstorage.StorableObject) { + panic("approvers should never be overwritten and only stored once to optimize IO") +} + +func (approver *Approver) MarshalBinary() (result []byte, err error) { + return +} + +func (approver *Approver) UnmarshalBinary(data []byte) (err error) { + return +} diff --git a/packages/binary/tangle/model/approver/cached_approver.go b/packages/binary/tangle/model/approver/cached_approver.go new file mode 100644 index 0000000000000000000000000000000000000000..c52844320472f2dd0ed38a3c02ba2b1334959d76 --- /dev/null +++ b/packages/binary/tangle/model/approver/cached_approver.go @@ -0,0 +1,33 @@ +package approver + +import ( + "github.com/iotaledger/hive.go/objectstorage" +) + +type CachedApprover struct { + objectstorage.CachedObject +} + +func (cachedApprover *CachedApprover) Unwrap() *Approver { + if untypedObject := cachedApprover.Get(); untypedObject == nil { + return nil + } else { + if typedObject := untypedObject.(*Approver); typedObject == nil || typedObject.IsDeleted() { + return nil + } else { + return typedObject + } + } +} + +type CachedApprovers []*CachedApprover + +func (cachedApprovers CachedApprovers) Consume(consumer func(approver *Approver)) (consumed bool) { + for _, cachedApprover := range cachedApprovers { + consumed = consumed || cachedApprover.Consume(func(object objectstorage.StorableObject) { + consumer(object.(*Approver)) + }) + } + + return +} diff --git a/packages/binary/tangle/model/missingtransaction/cached_missingtransaction.go b/packages/binary/tangle/model/missingtransaction/cached_missingtransaction.go new file mode 100644 index 0000000000000000000000000000000000000000..f50f532127ba98e5f9baaaa5bd1095ba0c9d4216 --- /dev/null +++ b/packages/binary/tangle/model/missingtransaction/cached_missingtransaction.go @@ -0,0 +1,21 @@ +package missingtransaction + +import ( + "github.com/iotaledger/hive.go/objectstorage" +) + +type CachedMissingTransaction struct { + objectstorage.CachedObject +} + +func (cachedObject *CachedMissingTransaction) Unwrap() *MissingTransaction { + if untypedObject := cachedObject.Get(); untypedObject == nil { + return nil + } else { + if typedObject := untypedObject.(*MissingTransaction); typedObject == nil || typedObject.IsDeleted() { + return nil + } else { + return typedObject + } + } +} diff --git a/packages/binary/tangle/model/missingtransaction/missingtransaction.go b/packages/binary/tangle/model/missingtransaction/missingtransaction.go new file mode 100644 index 0000000000000000000000000000000000000000..ea1715c8d0a127324dd5f6a82ac9b2e2719bd0c0 --- /dev/null +++ b/packages/binary/tangle/model/missingtransaction/missingtransaction.go @@ -0,0 +1,53 @@ +package missingtransaction + +import ( + "time" + + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction" + "github.com/iotaledger/hive.go/objectstorage" +) + +type MissingTransaction struct { + objectstorage.StorableObjectFlags + + transactionId transaction.Id + missingSince time.Time +} + +func New(transactionId transaction.Id) *MissingTransaction { + return &MissingTransaction{ + transactionId: transactionId, + missingSince: time.Now(), + } +} + +func FromStorage(key []byte) objectstorage.StorableObject { + result := &MissingTransaction{} + copy(result.transactionId[:], key) + + return result +} + +func (missingTransaction *MissingTransaction) GetTransactionId() transaction.Id { + return missingTransaction.transactionId +} + +func (missingTransaction *MissingTransaction) GetMissingSince() time.Time { + return missingTransaction.missingSince +} + +func (missingTransaction *MissingTransaction) GetStorageKey() []byte { + return missingTransaction.transactionId[:] +} + +func (missingTransaction *MissingTransaction) Update(other objectstorage.StorableObject) { + panic("missing transactions should never be overwritten and only stored once to optimize IO") +} + +func (missingTransaction *MissingTransaction) MarshalBinary() (result []byte, err error) { + return missingTransaction.missingSince.MarshalBinary() +} + +func (missingTransaction *MissingTransaction) UnmarshalBinary(data []byte) (err error) { + return missingTransaction.missingSince.UnmarshalBinary(data) +} diff --git a/packages/binary/tangle/model/transaction/cached_transaction.go b/packages/binary/tangle/model/transaction/cached_transaction.go new file mode 100644 index 0000000000000000000000000000000000000000..5f177ea76c1ce12c474bfe7a1be0ab2b3d0315af --- /dev/null +++ b/packages/binary/tangle/model/transaction/cached_transaction.go @@ -0,0 +1,31 @@ +package transaction + +import ( + "github.com/iotaledger/hive.go/objectstorage" +) + +type CachedTransaction struct { + objectstorage.CachedObject +} + +func (cachedTransaction *CachedTransaction) Retain() *CachedTransaction { + return &CachedTransaction{cachedTransaction.CachedObject.Retain()} +} + +func (cachedTransaction *CachedTransaction) Consume(consumer func(object *Transaction)) bool { + return cachedTransaction.CachedObject.Consume(func(object objectstorage.StorableObject) { + consumer(object.(*Transaction)) + }) +} + +func (cachedTransaction *CachedTransaction) Unwrap() *Transaction { + if untypedTransaction := cachedTransaction.Get(); untypedTransaction == nil { + return nil + } else { + if typeCastedTransaction := untypedTransaction.(*Transaction); typeCastedTransaction == nil || typeCastedTransaction.IsDeleted() { + return nil + } else { + return typeCastedTransaction + } + } +} diff --git a/packages/binary/tangle/model/transaction/id.go b/packages/binary/tangle/model/transaction/id.go new file mode 100644 index 0000000000000000000000000000000000000000..dd5ca1544b311f85740893dd416a04358cb74267 --- /dev/null +++ b/packages/binary/tangle/model/transaction/id.go @@ -0,0 +1,34 @@ +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[:]) + + return +} + +func (id *Id) UnmarshalBinary(data []byte) (err error) { + copy(id[:], data) + + return +} + +func (id Id) String() string { + return base58.Encode(id[:]) +} + +var EmptyId = Id{} + +const IdLength = 64 diff --git a/packages/binary/tangle/model/transaction/init.go b/packages/binary/tangle/model/transaction/init.go new file mode 100644 index 0000000000000000000000000000000000000000..5805588c0b6309d978159aa094c6c4966f73a8c9 --- /dev/null +++ b/packages/binary/tangle/model/transaction/init.go @@ -0,0 +1,10 @@ +package transaction + +import ( + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload/data" +) + +func init() { + payload.SetGenericUnmarshalerFactory(data.GenericPayloadUnmarshalerFactory) +} diff --git a/packages/binary/tangle/model/transaction/payload/data/data.go b/packages/binary/tangle/model/transaction/payload/data/data.go new file mode 100644 index 0000000000000000000000000000000000000000..8b41b99ce062ee65fee8cbd25b5561bc50b7a538 --- /dev/null +++ b/packages/binary/tangle/model/transaction/payload/data/data.go @@ -0,0 +1,52 @@ +package data + +import ( + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload" +) + +type Data struct { + payloadType payload.Type + data []byte +} + +var Type = payload.Type(0) + +func New(data []byte) *Data { + return &Data{ + payloadType: Type, + data: data, + } +} + +func (dataPayload *Data) GetType() payload.Type { + return dataPayload.payloadType +} + +func (dataPayload *Data) GetData() []byte { + return dataPayload.data +} + +func (dataPayload *Data) UnmarshalBinary(data []byte) error { + dataPayload.data = make([]byte, len(data)) + copy(dataPayload.data, data) + + return nil +} + +func (dataPayload *Data) MarshalBinary() (data []byte, err error) { + data = make([]byte, len(dataPayload.data)) + copy(data, dataPayload.data) + + return +} + +func GenericPayloadUnmarshalerFactory(payloadType payload.Type) payload.Unmarshaler { + return func(data []byte) (payload payload.Payload, err error) { + payload = &Data{ + payloadType: payloadType, + } + err = payload.UnmarshalBinary(data) + + return + } +} diff --git a/packages/binary/tangle/model/transaction/payload/data/init.go b/packages/binary/tangle/model/transaction/payload/data/init.go new file mode 100644 index 0000000000000000000000000000000000000000..547bca2c68e3ad3ac12adc1b97ab82ae3878a764 --- /dev/null +++ b/packages/binary/tangle/model/transaction/payload/data/init.go @@ -0,0 +1,9 @@ +package data + +import ( + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload" +) + +func init() { + payload.RegisterType(Type, GenericPayloadUnmarshalerFactory(Type)) +} diff --git a/packages/binary/tangle/model/transaction/payload/id.go b/packages/binary/tangle/model/transaction/payload/id.go new file mode 100644 index 0000000000000000000000000000000000000000..a20ce75f2b069a4299bf70a633deafd1c41c2f93 --- /dev/null +++ b/packages/binary/tangle/model/transaction/payload/id.go @@ -0,0 +1,5 @@ +package payload + +type Id [IdLength]byte + +const IdLength = 64 diff --git a/packages/binary/tangle/model/transaction/payload/payload.go b/packages/binary/tangle/model/transaction/payload/payload.go new file mode 100644 index 0000000000000000000000000000000000000000..23ff09be788c7dc242b3fd640813e43641a72f76 --- /dev/null +++ b/packages/binary/tangle/model/transaction/payload/payload.go @@ -0,0 +1,12 @@ +package payload + +import ( + "encoding" +) + +type Payload interface { + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler + + GetType() Type +} diff --git a/packages/binary/tangle/model/transaction/payload/type.go b/packages/binary/tangle/model/transaction/payload/type.go new file mode 100644 index 0000000000000000000000000000000000000000..a1594aa40bd8d8cfe3bd7d7533304b3c778f182b --- /dev/null +++ b/packages/binary/tangle/model/transaction/payload/type.go @@ -0,0 +1,3 @@ +package payload + +type Type = uint32 diff --git a/packages/binary/tangle/model/transaction/payload/type_register.go b/packages/binary/tangle/model/transaction/payload/type_register.go new file mode 100644 index 0000000000000000000000000000000000000000..aac9a9195eae5282e9514790cff2a957e01bafb1 --- /dev/null +++ b/packages/binary/tangle/model/transaction/payload/type_register.go @@ -0,0 +1,36 @@ +package payload + +import ( + "sync" +) + +type Unmarshaler func(data []byte) (Payload, error) + +var ( + typeRegister = make(map[Type]Unmarshaler) + typeRegisterMutex sync.RWMutex + genericUnmarshalerFactory func(payloadType Type) Unmarshaler +) + +func RegisterType(payloadType Type, unmarshaler Unmarshaler) { + typeRegisterMutex.Lock() + typeRegister[payloadType] = unmarshaler + typeRegisterMutex.Unlock() +} + +func GetUnmarshaler(payloadType Type) Unmarshaler { + typeRegisterMutex.RLock() + if unmarshaler, exists := typeRegister[payloadType]; exists { + typeRegisterMutex.RUnlock() + + return unmarshaler + } else { + typeRegisterMutex.RUnlock() + + return genericUnmarshalerFactory(payloadType) + } +} + +func SetGenericUnmarshalerFactory(unmarshalerFactory func(payloadType Type) Unmarshaler) { + genericUnmarshalerFactory = unmarshalerFactory +} diff --git a/packages/binary/tangle/model/transaction/test/transaction_test.go b/packages/binary/tangle/model/transaction/test/transaction_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1ca51d61b5d7b49fcf31df68a5da4a9b1b6b8708 --- /dev/null +++ b/packages/binary/tangle/model/transaction/test/transaction_test.go @@ -0,0 +1,77 @@ +package test + +import ( + "runtime" + "sync" + "testing" + + "github.com/iotaledger/hive.go/async" + + "github.com/panjf2000/ants/v2" + + "github.com/iotaledger/goshimmer/packages/binary/identity" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload/data" +) + +func BenchmarkVerifyDataTransactions(b *testing.B) { + var pool async.WorkerPool + pool.Tune(runtime.NumCPU() * 2) + + transactions := make([][]byte, b.N) + for i := 0; i < b.N; i++ { + tx := transaction.New(transaction.EmptyId, transaction.EmptyId, identity.Generate(), data.New([]byte("some data"))) + + if marshaledTransaction, err := tx.MarshalBinary(); err != nil { + b.Error(err) + } else { + transactions[i] = marshaledTransaction + } + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + currentIndex := i + pool.Submit(func() { + if tx, err := transaction.FromBytes(transactions[currentIndex]); err != nil { + b.Error(err) + } else { + tx.VerifySignature() + } + }) + } + + pool.Shutdown() +} + +func BenchmarkVerifySignature(b *testing.B) { + pool, _ := ants.NewPool(80, ants.WithNonblocking(false)) + + transactions := make([]*transaction.Transaction, b.N) + for i := 0; i < b.N; i++ { + transactions[i] = transaction.New(transaction.EmptyId, transaction.EmptyId, identity.Generate(), data.New([]byte("test"))) + transactions[i].GetBytes() + } + + var wg sync.WaitGroup + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + wg.Add(1) + + currentIndex := i + if err := pool.Submit(func() { + transactions[currentIndex].VerifySignature() + + wg.Done() + }); err != nil { + b.Error(err) + + return + } + } + + wg.Wait() +} diff --git a/packages/binary/tangle/model/transaction/transaction.go b/packages/binary/tangle/model/transaction/transaction.go new file mode 100644 index 0000000000000000000000000000000000000000..de18ffc47ed6514c4883dc1382da60d23184d758 --- /dev/null +++ b/packages/binary/tangle/model/transaction/transaction.go @@ -0,0 +1,277 @@ +package transaction + +import ( + "encoding/binary" + "sync" + + "github.com/iotaledger/hive.go/stringify" + + "github.com/iotaledger/goshimmer/packages/binary/identity" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload" + + "github.com/iotaledger/hive.go/objectstorage" + + "github.com/mr-tron/base58" + + "golang.org/x/crypto/blake2b" +) + +type Transaction struct { + // base functionality of StorableObject + objectstorage.StorableObjectFlags + + // core properties (they are part of the transaction when being sent) + trunkTransactionId Id + branchTransactionId Id + issuer *identity.Identity + payload payload.Payload + bytes []byte + bytesMutex sync.RWMutex + signature [identity.SignatureSize]byte + signatureMutex sync.RWMutex + + // derived properties + id *Id + idMutex sync.RWMutex + payloadId *payload.Id + payloadIdMutex sync.RWMutex +} + +// Allows us to "issue" a transaction. +func New(trunkTransactionId Id, branchTransactionId Id, issuer *identity.Identity, payload payload.Payload) (result *Transaction) { + return &Transaction{ + trunkTransactionId: trunkTransactionId, + branchTransactionId: branchTransactionId, + issuer: issuer, + payload: payload, + } +} + +// Get's called when we restore a transaction 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 transactionId Id + copy(transactionId[:], id) + + result = &Transaction{ + id: &transactionId, + } + + return +} + +func FromBytes(bytes []byte) (result *Transaction, err error) { + result = &Transaction{} + err = result.UnmarshalBinary(bytes) + + return +} + +func (transaction *Transaction) VerifySignature() (result bool) { + transactionBytes := transaction.GetBytes() + + transaction.signatureMutex.RLock() + result = transaction.issuer.VerifySignature(transactionBytes[:len(transactionBytes)-identity.SignatureSize], transaction.signature[:]) + transaction.signatureMutex.RUnlock() + + return +} + +func (transaction *Transaction) GetId() (result Id) { + transaction.idMutex.RLock() + if transaction.id == nil { + transaction.idMutex.RUnlock() + + transaction.idMutex.Lock() + if transaction.id == nil { + result = transaction.calculateTransactionId() + + transaction.id = &result + } else { + result = *transaction.id + } + transaction.idMutex.Unlock() + } else { + result = *transaction.id + + transaction.idMutex.RUnlock() + } + + return +} + +func (transaction *Transaction) GetTrunkTransactionId() Id { + return transaction.trunkTransactionId +} + +func (transaction *Transaction) GetBranchTransactionId() Id { + return transaction.branchTransactionId +} + +func (transaction *Transaction) GetPayload() payload.Payload { + return transaction.payload +} + +func (transaction *Transaction) GetPayloadId() (result payload.Id) { + transaction.payloadIdMutex.RLock() + if transaction.payloadId == nil { + transaction.payloadIdMutex.RUnlock() + + transaction.payloadIdMutex.Lock() + if transaction.payloadId == nil { + result = transaction.calculatePayloadId() + + transaction.payloadId = &result + } else { + result = *transaction.payloadId + } + transaction.payloadIdMutex.Unlock() + } else { + result = *transaction.payloadId + + transaction.payloadIdMutex.RUnlock() + } + + return +} + +func (transaction *Transaction) GetBytes() []byte { + if result, err := transaction.MarshalBinary(); err != nil { + panic(err) + } else { + return result + } +} + +func (transaction *Transaction) calculateTransactionId() Id { + payloadId := transaction.GetPayloadId() + + hashBase := make([]byte, IdLength+IdLength+payload.IdLength) + offset := 0 + + copy(hashBase[offset:], transaction.trunkTransactionId[:]) + offset += IdLength + + copy(hashBase[offset:], transaction.branchTransactionId[:]) + offset += IdLength + + copy(hashBase[offset:], payloadId[:]) + // offset += payloadIdLength + + return blake2b.Sum512(hashBase) +} + +func (transaction *Transaction) calculatePayloadId() payload.Id { + bytes := transaction.GetBytes() + + return blake2b.Sum512(bytes[2*IdLength:]) +} + +// Since transactions are immutable and do not get changed after being created, we cache the result of the marshaling. +func (transaction *Transaction) MarshalBinary() (result []byte, err error) { + transaction.bytesMutex.RLock() + if transaction.bytes == nil { + transaction.bytesMutex.RUnlock() + + transaction.bytesMutex.Lock() + if transaction.bytes == nil { + var serializedPayload []byte + if transaction.payload != nil { + if serializedPayload, err = transaction.payload.MarshalBinary(); err != nil { + return + } + } + serializedPayloadLength := len(serializedPayload) + + result = make([]byte, IdLength+IdLength+identity.PublicKeySize+4+serializedPayloadLength+identity.SignatureSize) + offset := 0 + + copy(result[offset:], transaction.trunkTransactionId[:]) + offset += IdLength + + 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()) + offset += 4 + + if serializedPayloadLength != 0 { + copy(result[offset:], serializedPayload) + 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 + } else { + result = transaction.bytes + } + transaction.bytesMutex.Unlock() + } else { + result = transaction.bytes + + transaction.bytesMutex.RUnlock() + } + + return +} + +func (transaction *Transaction) UnmarshalBinary(data []byte) (err error) { + offset := 0 + + copy(transaction.trunkTransactionId[:], data[offset:]) + offset += IdLength + + copy(transaction.branchTransactionId[:], data[offset:]) + offset += IdLength + + transaction.issuer = identity.New(data[offset : offset+identity.PublicKeySize]) + offset += identity.PublicKeySize + + payloadType := binary.LittleEndian.Uint32(data[offset:]) + offset += 4 + + if transaction.payload, err = payload.GetUnmarshaler(payloadType)(data[offset : len(data)-identity.SignatureSize]); err != nil { + return + } + offset += len(data) - identity.SignatureSize - offset + + copy(transaction.signature[:], data[offset:]) + // offset += identity.SignatureSize + + transaction.bytes = make([]byte, len(data)) + copy(transaction.bytes, data) + + return +} + +func (transaction *Transaction) GetStorageKey() []byte { + transactionId := transaction.GetId() + + return transactionId[:] +} + +func (transaction *Transaction) Update(other objectstorage.StorableObject) { + panic("transactions should never be overwritten and only stored once to optimize IO") +} + +func (transaction *Transaction) String() string { + transactionId := transaction.GetId() + + return stringify.Struct("Transaction", + stringify.StructField("id", base58.Encode(transactionId[:])), + stringify.StructField("trunkTransactionId", base58.Encode(transaction.trunkTransactionId[:])), + stringify.StructField("trunkTransactionId", base58.Encode(transaction.branchTransactionId[:])), + ) +} diff --git a/packages/binary/tangle/model/transactionmetadata/cached_transactionmetadata.go b/packages/binary/tangle/model/transactionmetadata/cached_transactionmetadata.go new file mode 100644 index 0000000000000000000000000000000000000000..8b327806525d359f39dcbf0dd54e699cdc195f61 --- /dev/null +++ b/packages/binary/tangle/model/transactionmetadata/cached_transactionmetadata.go @@ -0,0 +1,25 @@ +package transactionmetadata + +import ( + "github.com/iotaledger/hive.go/objectstorage" +) + +type CachedTransactionMetadata struct { + objectstorage.CachedObject +} + +func (cachedObject *CachedTransactionMetadata) Retain() objectstorage.CachedObject { + return &CachedTransactionMetadata{cachedObject} +} + +func (cachedObject *CachedTransactionMetadata) Unwrap() *TransactionMetadata { + if untypedObject := cachedObject.Get(); untypedObject == nil { + return nil + } else { + if typedObject := untypedObject.(*TransactionMetadata); typedObject == nil || typedObject.IsDeleted() { + return nil + } else { + return typedObject + } + } +} diff --git a/packages/binary/tangle/model/transactionmetadata/transactionmetadata.go b/packages/binary/tangle/model/transactionmetadata/transactionmetadata.go new file mode 100644 index 0000000000000000000000000000000000000000..f29a6e3425262f50a312892714e3ca32020eb3cb --- /dev/null +++ b/packages/binary/tangle/model/transactionmetadata/transactionmetadata.go @@ -0,0 +1,94 @@ +package transactionmetadata + +import ( + "sync" + "time" + + "github.com/iotaledger/hive.go/objectstorage" + + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction" +) + +type TransactionMetadata struct { + objectstorage.StorableObjectFlags + + transactionId transaction.Id + receivedTime time.Time + solid bool + solidificationTime time.Time + + solidMutex sync.RWMutex + solidificationTimeMutex sync.RWMutex +} + +func New(transactionId transaction.Id) *TransactionMetadata { + return &TransactionMetadata{ + transactionId: transactionId, + receivedTime: time.Now(), + } +} + +func FromStorage(id []byte) objectstorage.StorableObject { + result := &TransactionMetadata{} + copy(result.transactionId[:], id) + + return result +} + +func (transactionMetadata *TransactionMetadata) IsSolid() (result bool) { + transactionMetadata.solidMutex.RLock() + result = transactionMetadata.solid + transactionMetadata.solidMutex.RUnlock() + + return +} + +func (transactionMetadata *TransactionMetadata) SetSolid(solid bool) (modified bool) { + transactionMetadata.solidMutex.RLock() + if transactionMetadata.solid != solid { + transactionMetadata.solidMutex.RUnlock() + + transactionMetadata.solidMutex.Lock() + if transactionMetadata.solid != solid { + transactionMetadata.solid = solid + if solid { + transactionMetadata.solidificationTimeMutex.Lock() + transactionMetadata.solidificationTime = time.Now() + transactionMetadata.solidificationTimeMutex.Unlock() + } + + transactionMetadata.SetModified() + + modified = true + } + transactionMetadata.solidMutex.Unlock() + + } else { + transactionMetadata.solidMutex.RUnlock() + } + + return +} + +func (transactionMetadata *TransactionMetadata) GetSoldificationTime() time.Time { + transactionMetadata.solidificationTimeMutex.RLock() + defer transactionMetadata.solidificationTimeMutex.RUnlock() + + return transactionMetadata.solidificationTime +} + +func (transactionMetadata *TransactionMetadata) GetStorageKey() []byte { + return transactionMetadata.transactionId[:] +} + +func (transactionMetadata *TransactionMetadata) Update(other objectstorage.StorableObject) { + +} + +func (transactionMetadata *TransactionMetadata) MarshalBinary() ([]byte, error) { + return nil, nil +} + +func (transactionMetadata *TransactionMetadata) UnmarshalBinary([]byte) error { + return nil +} diff --git a/packages/binary/tangle/tangle.go b/packages/binary/tangle/tangle.go new file mode 100644 index 0000000000000000000000000000000000000000..9033cfdd65e7360ee3c364f2ed3e47705f6a7c47 --- /dev/null +++ b/packages/binary/tangle/tangle.go @@ -0,0 +1,322 @@ +package tangle + +import ( + "container/list" + "time" + + "github.com/iotaledger/hive.go/types" + + "github.com/iotaledger/goshimmer/packages/binary/storageprefix" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/approver" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/missingtransaction" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transactionmetadata" + + "github.com/iotaledger/hive.go/async" + "github.com/iotaledger/hive.go/objectstorage" +) + +const ( + MAX_MISSING_TIME_BEFORE_CLEANUP = 30 * time.Second + MISSING_CHECK_INTERVAL = 5 * time.Second +) + +type Tangle struct { + storageId []byte + + transactionStorage *objectstorage.ObjectStorage + transactionMetadataStorage *objectstorage.ObjectStorage + approverStorage *objectstorage.ObjectStorage + missingTransactionsStorage *objectstorage.ObjectStorage + + Events Events + + storeTransactionsWorkerPool async.WorkerPool + solidifierWorkerPool async.WorkerPool + cleanupWorkerPool async.WorkerPool +} + +// Constructor for the tangle. +func New(storageId []byte) (result *Tangle) { + result = &Tangle{ + storageId: storageId, + transactionStorage: objectstorage.New(append(storageId, storageprefix.TangleTransaction...), transaction.FromStorage), + transactionMetadataStorage: objectstorage.New(append(storageId, storageprefix.TangleTransactionMetadata...), transactionmetadata.FromStorage), + approverStorage: objectstorage.New(append(storageId, storageprefix.TangleApprovers...), approver.FromStorage, objectstorage.PartitionKey(transaction.IdLength, transaction.IdLength)), + missingTransactionsStorage: objectstorage.New(append(storageId, storageprefix.TangleMissingTransaction...), missingtransaction.FromStorage), + + Events: *newEvents(), + } + + result.solidifierWorkerPool.Tune(1024) + + return +} + +// 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 +} + +// Attaches a new transaction to the tangle. +func (tangle *Tangle) AttachTransaction(transaction *transaction.Transaction) { + tangle.storeTransactionsWorkerPool.Submit(func() { tangle.storeTransactionWorker(transaction) }) +} + +// Retrieves a transaction from the tangle. +func (tangle *Tangle) GetTransaction(transactionId transaction.Id) *transaction.CachedTransaction { + return &transaction.CachedTransaction{CachedObject: tangle.transactionStorage.Load(transactionId[:])} +} + +// Retrieves the metadata of a transaction from the tangle. +func (tangle *Tangle) GetTransactionMetadata(transactionId transaction.Id) *transactionmetadata.CachedTransactionMetadata { + return &transactionmetadata.CachedTransactionMetadata{CachedObject: tangle.transactionMetadataStorage.Load(transactionId[:])} +} + +// Retrieves the approvers of a transaction from the tangle. +func (tangle *Tangle) GetApprovers(transactionId transaction.Id) approver.CachedApprovers { + approvers := make(approver.CachedApprovers, 0) + tangle.approverStorage.ForEach(func(key []byte, cachedObject objectstorage.CachedObject) bool { + approvers = append(approvers, &approver.CachedApprover{CachedObject: cachedObject}) + + return true + }, transactionId[:]) + + return approvers +} + +// Deletes a transaction from the tangle (i.e. for local snapshots) +func (tangle *Tangle) DeleteTransaction(transactionId transaction.Id) { + tangle.GetTransaction(transactionId).Consume(func(currentTransaction *transaction.Transaction) { + trunkTransactionId := currentTransaction.GetTrunkTransactionId() + tangle.deleteApprover(trunkTransactionId, transactionId) + + branchTransactionId := currentTransaction.GetBranchTransactionId() + if branchTransactionId != trunkTransactionId { + tangle.deleteApprover(branchTransactionId, transactionId) + } + + tangle.transactionMetadataStorage.Delete(transactionId[:]) + tangle.transactionStorage.Delete(transactionId[:]) + + tangle.Events.TransactionRemoved.Trigger(transactionId) + }) +} + +// Marks the tangle as stopped, so it will not accept any new transactions (waits for all backgroundTasks to finish. +func (tangle *Tangle) Shutdown() *Tangle { + tangle.storeTransactionsWorkerPool.ShutdownGracefully() + tangle.solidifierWorkerPool.ShutdownGracefully() + tangle.cleanupWorkerPool.ShutdownGracefully() + + tangle.transactionStorage.Shutdown() + tangle.transactionMetadataStorage.Shutdown() + tangle.approverStorage.Shutdown() + tangle.missingTransactionsStorage.Shutdown() + + return tangle +} + +// Resets the database and deletes all objects (good for testing or "node resets"). +func (tangle *Tangle) Prune() error { + for _, storage := range []*objectstorage.ObjectStorage{ + tangle.transactionStorage, + tangle.transactionMetadataStorage, + tangle.approverStorage, + tangle.missingTransactionsStorage, + } { + if err := storage.Prune(); err != nil { + return err + } + } + + return nil +} + +// Worker that stores the transactions and calls the corresponding "Storage events" +func (tangle *Tangle) storeTransactionWorker(tx *transaction.Transaction) { + // store transaction + var cachedTransaction *transaction.CachedTransaction + if _tmp, transactionIsNew := tangle.transactionStorage.StoreIfAbsent(tx); !transactionIsNew { + return + } else { + cachedTransaction = &transaction.CachedTransaction{CachedObject: _tmp} + } + + // store transaction metadata + transactionId := tx.GetId() + cachedTransactionMetadata := &transactionmetadata.CachedTransactionMetadata{CachedObject: tangle.transactionMetadataStorage.Store(transactionmetadata.New(transactionId))} + + // store trunk approver + trunkTransactionID := tx.GetTrunkTransactionId() + tangle.approverStorage.Store(approver.New(trunkTransactionID, transactionId)).Release() + + // store branch approver + if branchTransactionID := tx.GetBranchTransactionId(); branchTransactionID != trunkTransactionID { + tangle.approverStorage.Store(approver.New(branchTransactionID, transactionId)).Release() + } + + // trigger events + if tangle.missingTransactionsStorage.DeleteIfPresent(transactionId[:]) { + tangle.Events.MissingTransactionReceived.Trigger(cachedTransaction, cachedTransactionMetadata) + } + tangle.Events.TransactionAttached.Trigger(cachedTransaction, cachedTransactionMetadata) + + // check solidity + tangle.solidifierWorkerPool.Submit(func() { + tangle.solidifyTransactionWorker(cachedTransaction, cachedTransactionMetadata) + }) +} + +// Worker that solidifies the transactions (recursively from past to present). +func (tangle *Tangle) solidifyTransactionWorker(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *transactionmetadata.CachedTransactionMetadata) { + isTransactionMarkedAsSolid := func(transactionId transaction.Id) bool { + if transactionId == transaction.EmptyId { + return true + } + + transactionMetadataCached := tangle.GetTransactionMetadata(transactionId) + if transactionMetadata := transactionMetadataCached.Unwrap(); transactionMetadata == nil { + transactionMetadataCached.Release() + + // if transaction is missing and was not reported as missing, yet + if cachedMissingTransaction, missingTransactionStored := tangle.missingTransactionsStorage.StoreIfAbsent(missingtransaction.New(transactionId)); missingTransactionStored { + cachedMissingTransaction.Consume(func(object objectstorage.StorableObject) { + tangle.monitorMissingTransactionWorker(object.(*missingtransaction.MissingTransaction).GetTransactionId()) + }) + } + + return false + } else if !transactionMetadata.IsSolid() { + transactionMetadataCached.Release() + + return false + } + transactionMetadataCached.Release() + + return true + } + + isTransactionSolid := func(transaction *transaction.Transaction, transactionMetadata *transactionmetadata.TransactionMetadata) bool { + if transaction == nil || transaction.IsDeleted() { + return false + } + + if transactionMetadata == nil || transactionMetadata.IsDeleted() { + return false + } + + if transactionMetadata.IsSolid() { + return true + } + + return isTransactionMarkedAsSolid(transaction.GetTrunkTransactionId()) && isTransactionMarkedAsSolid(transaction.GetBranchTransactionId()) + } + + popElementsFromStack := func(stack *list.List) (*transaction.CachedTransaction, *transactionmetadata.CachedTransactionMetadata) { + currentSolidificationEntry := stack.Front() + currentCachedTransaction := currentSolidificationEntry.Value.([2]interface{})[0] + currentCachedTransactionMetadata := currentSolidificationEntry.Value.([2]interface{})[1] + stack.Remove(currentSolidificationEntry) + + return currentCachedTransaction.(*transaction.CachedTransaction), currentCachedTransactionMetadata.(*transactionmetadata.CachedTransactionMetadata) + } + + // initialize the stack + solidificationStack := list.New() + solidificationStack.PushBack([2]interface{}{cachedTransaction, cachedTransactionMetadata}) + + // process transactions that are supposed to be checked for solidity recursively + for solidificationStack.Len() > 0 { + currentCachedTransaction, currentCachedTransactionMetadata := popElementsFromStack(solidificationStack) + + currentTransaction := currentCachedTransaction.Unwrap() + currentTransactionMetadata := currentCachedTransactionMetadata.Unwrap() + if currentTransaction == nil || currentTransactionMetadata == nil { + currentCachedTransaction.Release() + currentCachedTransactionMetadata.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) { + tangle.Events.TransactionSolid.Trigger(currentCachedTransaction, currentCachedTransactionMetadata) + + tangle.GetApprovers(currentTransaction.GetId()).Consume(func(approver *approver.Approver) { + approverTransactionId := approver.GetApprovingTransactionId() + + solidificationStack.PushBack([2]interface{}{ + tangle.GetTransaction(approverTransactionId), + tangle.GetTransactionMetadata(approverTransactionId), + }) + }) + } + + // release cached results + currentCachedTransaction.Release() + currentCachedTransactionMetadata.Release() + } +} + +// Worker that Monitors the missing transactions (by scheduling regular checks). +func (tangle *Tangle) monitorMissingTransactionWorker(transactionId transaction.Id) { + var scheduleNextMissingCheck func(transactionId transaction.Id) + scheduleNextMissingCheck = func(transactionId transaction.Id) { + time.AfterFunc(MISSING_CHECK_INTERVAL, func() { + tangle.missingTransactionsStorage.Load(transactionId[:]).Consume(func(object objectstorage.StorableObject) { + missingTransaction := object.(*missingtransaction.MissingTransaction) + + if time.Since(missingTransaction.GetMissingSince()) >= MAX_MISSING_TIME_BEFORE_CLEANUP { + tangle.cleanupWorkerPool.Submit(func() { + tangle.Events.TransactionUnsolidifiable.Trigger(transactionId) + + tangle.deleteSubtangle(missingTransaction.GetTransactionId()) + }) + } else { + // TRIGGER STILL MISSING EVENT? + + scheduleNextMissingCheck(transactionId) + } + }) + }) + } + + tangle.Events.TransactionMissing.Trigger(transactionId) + + scheduleNextMissingCheck(transactionId) +} + +func (tangle *Tangle) deleteApprover(approvedTransaction transaction.Id, approvingTransaction transaction.Id) { + idToDelete := make([]byte, transaction.IdLength+transaction.IdLength) + copy(idToDelete[:transaction.IdLength], approvedTransaction[:]) + copy(idToDelete[transaction.IdLength:], approvingTransaction[:]) + tangle.approverStorage.Delete(idToDelete) +} + +// Deletes a transaction and all of its approvers (recursively). +func (tangle *Tangle) deleteSubtangle(transactionId transaction.Id) { + cleanupStack := list.New() + cleanupStack.PushBack(transactionId) + + processedTransactions := make(map[transaction.Id]types.Empty) + processedTransactions[transactionId] = types.Void + + for cleanupStack.Len() >= 1 { + currentStackEntry := cleanupStack.Front() + currentTransactionId := currentStackEntry.Value.(transaction.Id) + cleanupStack.Remove(currentStackEntry) + + tangle.DeleteTransaction(currentTransactionId) + + tangle.GetApprovers(currentTransactionId).Consume(func(approver *approver.Approver) { + approverId := approver.GetApprovingTransactionId() + + if _, transactionProcessed := processedTransactions[approverId]; !transactionProcessed { + cleanupStack.PushBack(approverId) + + processedTransactions[approverId] = types.Void + } + }) + } +} diff --git a/packages/binary/tangle/tangle_test.go b/packages/binary/tangle/tangle_test.go new file mode 100644 index 0000000000000000000000000000000000000000..33420e87dacbb7611078bcff3875665401de8e12 --- /dev/null +++ b/packages/binary/tangle/tangle_test.go @@ -0,0 +1,83 @@ +package tangle + +import ( + "fmt" + "testing" + "time" + + "github.com/iotaledger/hive.go/events" + + "github.com/iotaledger/goshimmer/packages/binary/identity" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload/data" + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transactionmetadata" +) + +func BenchmarkTangle_AttachTransaction(b *testing.B) { + tangle := New([]byte("TEST_BINARY_TANGLE")) + if err := tangle.Prune(); err != nil { + b.Error(err) + + return + } + + testIdentity := identity.Generate() + + transactionBytes := make([]*transaction.Transaction, b.N) + for i := 0; i < b.N; i++ { + transactionBytes[i] = transaction.New(transaction.EmptyId, transaction.EmptyId, testIdentity, data.New([]byte("some data"))) + transactionBytes[i].GetBytes() + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + tangle.AttachTransaction(transactionBytes[i]) + } + + tangle.Shutdown() +} + +func TestTangle_AttachTransaction(t *testing.T) { + tangle := New([]byte("TEST_BINARY_TANGLE")) + if err := tangle.Prune(); err != nil { + t.Error(err) + + return + } + + tangle.Events.TransactionAttached.Attach(events.NewClosure(func(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *transactionmetadata.CachedTransactionMetadata) { + cachedTransaction.Consume(func(transaction *transaction.Transaction) { + fmt.Println("ATTACHED:", transaction.GetId()) + }) + })) + + tangle.Events.TransactionSolid.Attach(events.NewClosure(func(cachedTransaction *transaction.CachedTransaction, cachedTransactionMetadata *transactionmetadata.CachedTransactionMetadata) { + cachedTransaction.Consume(func(transaction *transaction.Transaction) { + fmt.Println("SOLID:", transaction.GetId()) + }) + })) + + tangle.Events.TransactionUnsolidifiable.Attach(events.NewClosure(func(transactionId transaction.Id) { + fmt.Println("UNSOLIDIFIABLE:", transactionId) + })) + + tangle.Events.TransactionMissing.Attach(events.NewClosure(func(transactionId transaction.Id) { + fmt.Println("MISSING:", transactionId) + })) + + tangle.Events.TransactionRemoved.Attach(events.NewClosure(func(transactionId transaction.Id) { + fmt.Println("REMOVED:", transactionId) + })) + + newTransaction1 := transaction.New(transaction.EmptyId, transaction.EmptyId, identity.Generate(), data.New([]byte("some data"))) + newTransaction2 := transaction.New(newTransaction1.GetId(), newTransaction1.GetId(), identity.Generate(), data.New([]byte("some other data"))) + + tangle.AttachTransaction(newTransaction2) + + time.Sleep(7 * time.Second) + + tangle.AttachTransaction(newTransaction1) + + tangle.Shutdown() +} diff --git a/packages/binary/transactionrequester/constants.go b/packages/binary/transactionrequester/constants.go new file mode 100644 index 0000000000000000000000000000000000000000..b9f59769fe276ba7d9ba7b68267aff806599b3a2 --- /dev/null +++ b/packages/binary/transactionrequester/constants.go @@ -0,0 +1,10 @@ +package transactionrequester + +import ( + "time" +) + +const ( + DEFAULT_REQUEST_WORKER_COUNT = 1024 + DEFAULT_RETRY_INTERVAL = 10 * time.Second +) diff --git a/packages/binary/transactionrequester/events.go b/packages/binary/transactionrequester/events.go new file mode 100644 index 0000000000000000000000000000000000000000..e845d790496fc0c849c9ca0d4c81a7bfa2fefba2 --- /dev/null +++ b/packages/binary/transactionrequester/events.go @@ -0,0 +1,9 @@ +package transactionrequester + +import ( + "github.com/iotaledger/hive.go/events" +) + +type Events struct { + SendRequest *events.Event +} diff --git a/packages/binary/transactionrequester/options.go b/packages/binary/transactionrequester/options.go new file mode 100644 index 0000000000000000000000000000000000000000..05db5db4c3c00448dbc6b6ccc805f109d9194ba0 --- /dev/null +++ b/packages/binary/transactionrequester/options.go @@ -0,0 +1,37 @@ +package transactionrequester + +import ( + "time" +) + +type Options struct { + retryInterval time.Duration + workerCount int +} + +func newOptions(optionalOptions []Option) *Options { + result := &Options{ + retryInterval: 10 * time.Second, + workerCount: DEFAULT_REQUEST_WORKER_COUNT, + } + + for _, optionalOption := range optionalOptions { + optionalOption(result) + } + + return result +} + +type Option func(*Options) + +func RetryInterval(interval time.Duration) Option { + return func(args *Options) { + args.retryInterval = interval + } +} + +func WorkerCount(workerCount int) Option { + return func(args *Options) { + args.workerCount = workerCount + } +} diff --git a/packages/binary/transactionrequester/transactionrequester.go b/packages/binary/transactionrequester/transactionrequester.go new file mode 100644 index 0000000000000000000000000000000000000000..6f036e810abd8f17844ec0bd5f1ffb8c06a3c024 --- /dev/null +++ b/packages/binary/transactionrequester/transactionrequester.go @@ -0,0 +1,74 @@ +package transactionrequester + +import ( + "sync" + "time" + + "github.com/iotaledger/hive.go/async" + "github.com/iotaledger/hive.go/events" + + "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction" +) + +type TransactionRequester struct { + scheduledRequests map[transaction.Id]*time.Timer + requestWorker async.NonBlockingWorkerPool + options *Options + Events Events + + scheduledRequestsMutex sync.RWMutex +} + +func New(optionalOptions ...Option) *TransactionRequester { + requester := &TransactionRequester{ + scheduledRequests: make(map[transaction.Id]*time.Timer), + options: newOptions(optionalOptions), + Events: Events{ + SendRequest: events.NewEvent(func(handler interface{}, params ...interface{}) { + handler.(func(transaction.Id))(params[0].(transaction.Id)) + }), + }, + } + + requester.requestWorker.Tune(requester.options.workerCount) + + return requester +} + +func (requester *TransactionRequester) ScheduleRequest(transactionId transaction.Id) { + var retryRequest func(bool) + retryRequest = func(initialRequest bool) { + requester.requestWorker.Submit(func() { + requester.scheduledRequestsMutex.RLock() + if _, requestExists := requester.scheduledRequests[transactionId]; !initialRequest && !requestExists { + requester.scheduledRequestsMutex.RUnlock() + + return + } + requester.scheduledRequestsMutex.RUnlock() + + requester.Events.SendRequest.Trigger(transactionId) + + requester.scheduledRequestsMutex.Lock() + requester.scheduledRequests[transactionId] = time.AfterFunc(requester.options.retryInterval, func() { retryRequest(false) }) + requester.scheduledRequestsMutex.Unlock() + }) + } + + retryRequest(true) +} + +func (requester *TransactionRequester) StopRequest(transactionId transaction.Id) { + requester.scheduledRequestsMutex.RLock() + if timer, timerExists := requester.scheduledRequests[transactionId]; timerExists { + requester.scheduledRequestsMutex.RUnlock() + + timer.Stop() + + requester.scheduledRequestsMutex.Lock() + delete(requester.scheduledRequests, transactionId) + requester.scheduledRequestsMutex.Unlock() + } else { + requester.scheduledRequestsMutex.RUnlock() + } +}