From 682a9a1bc962fb2436a6dd063baae8905561c74f Mon Sep 17 00:00:00 2001 From: Hans Moog <hm@mkjc.net> Date: Tue, 17 Mar 2020 00:21:50 +0100 Subject: [PATCH] Implemented value tangle (#288) * Refactor: cleaned up unused files * Refactor: changing branches (need to commit) * Refactor: refactored some code * Feat: finished signature scheme for value transactions * Fix: fix broken merge * Refactor: refactored some code * Refactor: refactored transaction marshaling and unmarshaling * Fix: fixed missing err check * Feat: added payload string method * Feat: added Bytes() method to transactionid * Feat: refactored the marshaling * Refactor: refactored serialization code * Feat: started implementing the payload metadata * Docs: added some doc comments * Docs: added some additional docs * Feat: added a CachedObject of the payloadmetadata + added comments * Feat: updated hive.go + added further models * Feat: pc died - rescued files from disk * Feat: further models implemented * Feat: added missing model for value tangle * Feat: started writing test cases for value tangle * Feat: started to adjust marshaling of transaction * Feat: refactored marshaling of payload * Feat: intermediary commit before bigger refactor * Feat: removed identity package * Fix: fixed bugs due to refactor * Fix: fixed further bugs * Fix: fixed further bugs * Fix: hopefully last bugs :p * Feat: changed time marshal to use nanoseconds * Fix: fixed time marshaling * Fix: fixed serialization bug * Docs: added a comment to handling the zero value * Refactor: refactored transaction to separate issuerKey * Feat: added a parse method for Signatures * Feat: updated to latest hive.go --- go.mod | 2 +- go.sum | 10 +- packages/binary/identity/constants.go | 7 - packages/binary/identity/identity.go | 48 ---- packages/binary/identity/identity_test.go | 41 --- packages/binary/identity/type.go | 8 - .../binary/marshalutil/marshalutil.bool.go | 24 ++ packages/binary/marshalutil/marshalutil.go | 9 +- .../binary/marshalutil/marshalutil.time.go | 33 +++ .../binary/marshalutil/marshalutil_test.go | 1 + .../binary/signature/ed25119/private_key.go | 16 ++ .../binary/signature/ed25119/public_key.go | 14 ++ .../binary/signature/ed25119/signature.go | 14 ++ packages/binary/spammer/spammer.go | 11 +- .../binary/storageprefix/storageprefix.go | 15 +- .../binary/tangle/model/transaction/id.go | 11 + .../model/transaction/payload/data/data.go | 17 +- .../model/transaction/payload/payload.go | 41 +++ .../transaction/test/transaction_test.go | 8 +- .../tangle/model/transaction/transaction.go | 76 ++---- packages/binary/tangle/tangle.go | 2 +- packages/binary/tangle/tangle_test.go | 17 +- .../tangle/tipselector/tipselector_test.go | 8 +- .../transactionparser_test.go | 10 +- .../address/address.go | 0 .../coloredbalance/color/color.go | 0 .../coloredbalance/coloredbalance.go | 2 +- .../valuetransfer/payload/cached_object.go | 39 +++ .../binary/valuetransfer/payload/id/id.go | 87 +++++++ .../valuetransfer/payload/id/id_test.go | 26 ++ .../payload/payload.go | 60 +++-- .../payload/transfer}/id/id.go | 6 +- .../payload/transfer/inputs/inputs.go | 8 +- .../payload/transfer/outputs/outputs.go | 4 +- .../payload/transfer/signatures/ed25519.go | 2 +- .../payload/transfer/signatures/interfaces.go | 2 +- .../payload/transfer/signatures/signatures.go | 2 +- .../transfer/signatures/signatures_test.go | 2 +- .../payload/transfer/transfer.go | 10 +- .../binary/valuetransfer/tangle/constants.go | 10 + .../binary/valuetransfer/tangle/events.go | 41 +++ .../tangle/missingpayload/missingpayload.go | 103 ++++++++ .../tangle/payloadapprover/cached_object.go | 49 ++++ .../tangle/payloadapprover/payloadapprover.go | 85 +++++++ .../tangle/payloadmetadata/cached_object.go | 39 +++ .../tangle/payloadmetadata/payloadmetadata.go | 178 +++++++++++++ .../payloadmetadata/payloadmetadata_test.go | 46 ++++ .../binary/valuetransfer/tangle/tangle.go | 236 ++++++++++++++++++ .../valuetransfer/tangle/tangle_test.go | 66 +++++ .../test/payload_test.go | 33 ++- .../transferoutput/id/id.go | 4 +- .../valuetransfers/payload/transfer/id/id.go | 19 -- plugins/gossip/gossip.go | 2 +- plugins/gossip/plugin.go | 2 +- 54 files changed, 1324 insertions(+), 282 deletions(-) delete mode 100644 packages/binary/identity/constants.go delete mode 100644 packages/binary/identity/identity.go delete mode 100644 packages/binary/identity/identity_test.go delete mode 100644 packages/binary/identity/type.go create mode 100644 packages/binary/marshalutil/marshalutil.bool.go create mode 100644 packages/binary/marshalutil/marshalutil.time.go rename packages/binary/{valuetransfers => valuetransfer}/address/address.go (100%) rename packages/binary/{valuetransfers => valuetransfer}/coloredbalance/color/color.go (100%) rename packages/binary/{valuetransfers => valuetransfer}/coloredbalance/coloredbalance.go (93%) create mode 100644 packages/binary/valuetransfer/payload/cached_object.go create mode 100644 packages/binary/valuetransfer/payload/id/id.go create mode 100644 packages/binary/valuetransfer/payload/id/id_test.go rename packages/binary/{valuetransfers => valuetransfer}/payload/payload.go (76%) rename packages/binary/{valuetransfers/payload => valuetransfer/payload/transfer}/id/id.go (83%) rename packages/binary/{valuetransfers => valuetransfer}/payload/transfer/inputs/inputs.go (92%) rename packages/binary/{valuetransfers => valuetransfer}/payload/transfer/outputs/outputs.go (96%) rename packages/binary/{valuetransfers => valuetransfer}/payload/transfer/signatures/ed25519.go (98%) rename packages/binary/{valuetransfers => valuetransfer}/payload/transfer/signatures/interfaces.go (98%) rename packages/binary/{valuetransfers => valuetransfer}/payload/transfer/signatures/signatures.go (97%) rename packages/binary/{valuetransfers => valuetransfer}/payload/transfer/signatures/signatures_test.go (93%) rename packages/binary/{valuetransfers => valuetransfer}/payload/transfer/transfer.go (94%) create mode 100644 packages/binary/valuetransfer/tangle/constants.go create mode 100644 packages/binary/valuetransfer/tangle/events.go create mode 100644 packages/binary/valuetransfer/tangle/missingpayload/missingpayload.go create mode 100644 packages/binary/valuetransfer/tangle/payloadapprover/cached_object.go create mode 100644 packages/binary/valuetransfer/tangle/payloadapprover/payloadapprover.go create mode 100644 packages/binary/valuetransfer/tangle/payloadmetadata/cached_object.go create mode 100644 packages/binary/valuetransfer/tangle/payloadmetadata/payloadmetadata.go create mode 100644 packages/binary/valuetransfer/tangle/payloadmetadata/payloadmetadata_test.go create mode 100644 packages/binary/valuetransfer/tangle/tangle.go create mode 100644 packages/binary/valuetransfer/tangle/tangle_test.go rename packages/binary/{valuetransfers => valuetransfer}/test/payload_test.go (78%) rename packages/binary/{valuetransfers => valuetransfer}/transferoutput/id/id.go (95%) delete mode 100644 packages/binary/valuetransfers/payload/transfer/id/id.go diff --git a/go.mod b/go.mod index aa3d705b..0dbff937 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ 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-20200305153009-d3408b2be05b + github.com/iotaledger/hive.go v0.0.0-20200316213914-76b7a4169e64 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 diff --git a/go.sum b/go.sum index d573f25b..18faf70e 100644 --- a/go.sum +++ b/go.sum @@ -111,12 +111,8 @@ github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE0 github.com/googollee/go-engine.io v1.4.1/go.mod h1:26oFqHsnuWIzNOM0T08x21eQOydBosKOCgK3tyhzPPI= github.com/googollee/go-engine.io v1.4.3-0.20190924125625-798118fc0dd2 h1:6LbNP1ft0muA4LgoPMvwbxFpVhsRAGimY0Rp+4L7Q1M= github.com/googollee/go-engine.io v1.4.3-0.20190924125625-798118fc0dd2/go.mod h1:iaugrHMOoal16IKAWvH6y6RrXXIzfOULxjNwvXBCV4o= -github.com/googollee/go-engine.io v1.4.3-0.20200220091802-9b2ab104b298 h1:mvWmnIGGum8tzT62pTdjxSxCMOyxE7qCToNijulH+zE= -github.com/googollee/go-engine.io v1.4.3-0.20200220091802-9b2ab104b298/go.mod h1:iaugrHMOoal16IKAWvH6y6RrXXIzfOULxjNwvXBCV4o= github.com/googollee/go-socket.io v1.4.3-0.20191204093753-683f8725b6d0 h1:7yrvhLv25w1vtVKrcg8CZAn4Pnkb6pzCAqnZ5y9O4q8= github.com/googollee/go-socket.io v1.4.3-0.20191204093753-683f8725b6d0/go.mod h1:yjlQxKcAZXZjpGwQVW/y1sgyL1ou+DdCpkswURDCRrU= -github.com/googollee/go-socket.io v1.4.3 h1:fdfTDEnr4YyO+ZM4W9qPKJijbxklcyoxzbmHP7LkQmQ= -github.com/googollee/go-socket.io v1.4.3/go.mod h1:MRKd5ouUDz3WpEDRnD44JEAytwK1utsJv59QJyjGCio= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= @@ -134,10 +130,8 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T 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/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/iotaledger/hive.go v0.0.0-20200304073310-d71239623d46 h1:4v/lfoR5CiJKsG8HUuYeKbds+DPvpxxxSrKZYS7Qhrc= -github.com/iotaledger/hive.go v0.0.0-20200304073310-d71239623d46/go.mod h1:0LQvxKmfU4bcQcjYIAq3PRfsA5584U0AioAAas6/QU8= -github.com/iotaledger/hive.go v0.0.0-20200305153009-d3408b2be05b h1:TxHm2TTOG4Xgxdnp5qZsgXKCctnxdDeQgqT4NwPJPQ0= -github.com/iotaledger/hive.go v0.0.0-20200305153009-d3408b2be05b/go.mod h1:0LQvxKmfU4bcQcjYIAq3PRfsA5584U0AioAAas6/QU8= +github.com/iotaledger/hive.go v0.0.0-20200316213914-76b7a4169e64 h1:O+1g39PWKzskXCViRD07ePv6183gDbH81XoShbPTzuc= +github.com/iotaledger/hive.go v0.0.0-20200316213914-76b7a4169e64/go.mod h1:0LQvxKmfU4bcQcjYIAq3PRfsA5584U0AioAAas6/QU8= 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= diff --git a/packages/binary/identity/constants.go b/packages/binary/identity/constants.go deleted file mode 100644 index b8fb9850..00000000 --- a/packages/binary/identity/constants.go +++ /dev/null @@ -1,7 +0,0 @@ -package identity - -const ( - PublicKeySize = 32 - PrivateKeySize = 64 - SignatureSize = 64 -) diff --git a/packages/binary/identity/identity.go b/packages/binary/identity/identity.go deleted file mode 100644 index fd52c6f3..00000000 --- a/packages/binary/identity/identity.go +++ /dev/null @@ -1,48 +0,0 @@ -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 deleted file mode 100644 index f5fc0d52..00000000 --- a/packages/binary/identity/identity_test.go +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index cc207b02..00000000 --- a/packages/binary/identity/type.go +++ /dev/null @@ -1,8 +0,0 @@ -package identity - -type Type int - -const ( - Private = Type(0) - Public = Type(1) -) diff --git a/packages/binary/marshalutil/marshalutil.bool.go b/packages/binary/marshalutil/marshalutil.bool.go new file mode 100644 index 00000000..ab1467f1 --- /dev/null +++ b/packages/binary/marshalutil/marshalutil.bool.go @@ -0,0 +1,24 @@ +package marshalutil + +func (util *MarshalUtil) WriteBool(bool bool) { + writeEndOffset := util.expandWriteCapacity(1) + + if bool { + util.bytes[util.writeOffset] = 1 + } else { + util.bytes[util.writeOffset] = 0 + } + + util.WriteSeek(writeEndOffset) +} + +func (util *MarshalUtil) ReadBool() (bool, error) { + readEndOffset, err := util.checkReadCapacity(1) + if err != nil { + return false, err + } + + defer util.ReadSeek(readEndOffset) + + return util.bytes[util.readOffset] == 1, nil +} diff --git a/packages/binary/marshalutil/marshalutil.go b/packages/binary/marshalutil/marshalutil.go index f6e95e70..87837b83 100644 --- a/packages/binary/marshalutil/marshalutil.go +++ b/packages/binary/marshalutil/marshalutil.go @@ -71,7 +71,14 @@ func (util *MarshalUtil) ReadSeek(offset int) { } } -func (util *MarshalUtil) Bytes() []byte { +func (util *MarshalUtil) Bytes(clone ...bool) []byte { + if len(clone) >= 1 && clone[0] { + clone := make([]byte, util.size) + copy(clone, util.bytes) + + return clone + } + return util.bytes[:util.size] } diff --git a/packages/binary/marshalutil/marshalutil.time.go b/packages/binary/marshalutil/marshalutil.time.go new file mode 100644 index 00000000..36633dd9 --- /dev/null +++ b/packages/binary/marshalutil/marshalutil.time.go @@ -0,0 +1,33 @@ +package marshalutil + +import ( + "time" +) + +// WriteTime marshals the given time into a sequence of bytes, that get appended to the internal buffer. +func (util *MarshalUtil) WriteTime(timeToWrite time.Time) { + nanoSeconds := timeToWrite.UnixNano() + + // the zero value of time translates to -6795364578871345152 + if nanoSeconds == -6795364578871345152 { + util.WriteInt64(0) + } else { + util.WriteInt64(timeToWrite.UnixNano()) + } +} + +// ReadTime unmarshals a time object from the internal read buffer. +func (util *MarshalUtil) ReadTime() (result time.Time, err error) { + nanoSeconds, err := util.ReadInt64() + if err != nil { + return + } + + if nanoSeconds == 0 { + result = time.Time{} + } else { + result = time.Unix(0, nanoSeconds) + } + + return +} diff --git a/packages/binary/marshalutil/marshalutil_test.go b/packages/binary/marshalutil/marshalutil_test.go index 4b38f69c..17605b21 100644 --- a/packages/binary/marshalutil/marshalutil_test.go +++ b/packages/binary/marshalutil/marshalutil_test.go @@ -11,6 +11,7 @@ func Test(t *testing.T) { util.WriteBytes(make([]byte, UINT64_SIZE)) util.WriteInt64(-12) util.WriteUint64(38) + util.WriteUint64(38) fmt.Println(util.ReadBytes(UINT64_SIZE)) fmt.Println(util.ReadInt64()) diff --git a/packages/binary/signature/ed25119/private_key.go b/packages/binary/signature/ed25119/private_key.go index 89dc1a15..9885a643 100644 --- a/packages/binary/signature/ed25119/private_key.go +++ b/packages/binary/signature/ed25119/private_key.go @@ -1,11 +1,27 @@ package ed25119 import ( + "fmt" + "github.com/oasislabs/ed25519" ) type PrivateKey [PrivateKeySize]byte +func PrivateKeyFromBytes(bytes []byte) (result PrivateKey, err error, consumedBytes int) { + if len(bytes) < PrivateKeySize { + err = fmt.Errorf("bytes too short") + + return + } + + copy(result[:], bytes) + + consumedBytes = PrivateKeySize + + return +} + func (privateKey PrivateKey) Sign(data []byte) (result Signature) { copy(result[:], ed25519.Sign(privateKey[:], data)) diff --git a/packages/binary/signature/ed25119/public_key.go b/packages/binary/signature/ed25119/public_key.go index 9afe9766..f050f6ff 100644 --- a/packages/binary/signature/ed25119/public_key.go +++ b/packages/binary/signature/ed25119/public_key.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/oasislabs/ed25519" + + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" ) type PublicKey [PublicKeySize]byte @@ -23,10 +25,22 @@ func PublicKeyFromBytes(bytes []byte) (result PublicKey, err error, consumedByte return } +func ParsePublicKey(marshalUtil *marshalutil.MarshalUtil) (PublicKey, error) { + if id, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return PublicKeyFromBytes(data) }); err != nil { + return PublicKey{}, err + } else { + return id.(PublicKey), nil + } +} + func (publicKey PublicKey) VerifySignature(data []byte, signature Signature) bool { return ed25519.Verify(publicKey[:], data, signature[:]) } +func (publicKey PublicKey) Bytes() []byte { + return publicKey[:] +} + func (publicKey *PublicKey) UnmarshalBinary(bytes []byte) (err error) { if len(bytes) < PublicKeySize { return errors.New("not enough bytes") diff --git a/packages/binary/signature/ed25119/signature.go b/packages/binary/signature/ed25119/signature.go index d8a291ee..01a6750f 100644 --- a/packages/binary/signature/ed25119/signature.go +++ b/packages/binary/signature/ed25119/signature.go @@ -3,6 +3,8 @@ package ed25119 import ( "errors" "fmt" + + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" ) type Signature [SignatureSize]byte @@ -21,6 +23,18 @@ func SignatureFromBytes(bytes []byte) (result Signature, err error, consumedByte return } +func ParseSignature(marshalUtil *marshalutil.MarshalUtil) (Signature, error) { + if id, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return SignatureFromBytes(data) }); err != nil { + return Signature{}, err + } else { + return id.(Signature), nil + } +} + +func (signature Signature) Bytes() []byte { + return signature[:] +} + func (signature *Signature) UnmarshalBinary(bytes []byte) (err error) { if len(bytes) < SignatureSize { return errors.New("not enough bytes") diff --git a/packages/binary/spammer/spammer.go b/packages/binary/spammer/spammer.go index 94539333..f41499e2 100644 --- a/packages/binary/spammer/spammer.go +++ b/packages/binary/spammer/spammer.go @@ -1,13 +1,12 @@ package spammer import ( - "fmt" "sync/atomic" "time" "github.com/iotaledger/hive.go/types" - "github.com/iotaledger/goshimmer/packages/binary/identity" + "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" "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/tipselector" @@ -43,7 +42,7 @@ func (spammer *Spammer) Shutdown() { } func (spammer *Spammer) run(tps int, processId int64) { - fmt.Println(processId) + spammingIdentity := ed25119.GenerateKeyPair() currentSentCounter := 0 start := time.Now() @@ -54,7 +53,7 @@ func (spammer *Spammer) run(tps int, processId int64) { trunkTransactionId, branchTransactionId := spammer.tipSelector.GetTips() spammer.transactionParser.Parse( - transaction.New(trunkTransactionId, branchTransactionId, identity.Generate(), data.New([]byte("SPAM"))).GetBytes(), + transaction.New(trunkTransactionId, branchTransactionId, spammingIdentity, data.New([]byte("SPAM"))).Bytes(), nil, ) @@ -74,7 +73,7 @@ func (spammer *Spammer) run(tps int, processId int64) { } func (spammer *Spammer) sendBurst(transactions int, processId int64) { - spammingIdentity := identity.Generate() + spammingIdentity := ed25119.GenerateKeyPair() previousTransactionId := transaction.EmptyId @@ -86,7 +85,7 @@ func (spammer *Spammer) sendBurst(transactions int, processId int64) { spamTransaction := transaction.New(previousTransactionId, previousTransactionId, spammingIdentity, data.New([]byte("SPAM"))) previousTransactionId = spamTransaction.GetId() - burstBuffer[i] = spamTransaction.GetBytes() + burstBuffer[i] = spamTransaction.Bytes() } for i := 0; i < transactions; i++ { diff --git a/packages/binary/storageprefix/storageprefix.go b/packages/binary/storageprefix/storageprefix.go index fc9a8a21..d288fb2d 100644 --- a/packages/binary/storageprefix/storageprefix.go +++ b/packages/binary/storageprefix/storageprefix.go @@ -8,12 +8,13 @@ var ( TangleApprovers = []byte{3} TangleMissingTransaction = []byte{4} - ValueTangleTransferMetadata = []byte{5} - ValueTangleConsumers = []byte{6} - ValueTangleMissingTransfers = []byte{7} + ValueTransferPayload = []byte{5} + ValueTransferPayloadMetadata = []byte{6} + ValueTransferApprover = []byte{7} + ValueTransferMissingPayload = []byte{8} - LedgerStateTransferOutput = []byte{8} - LedgerStateTransferOutputBooking = []byte{9} - LedgerStateReality = []byte{10} - LedgerStateConflictSet = []byte{11} + LedgerStateTransferOutput = []byte{9} + LedgerStateTransferOutputBooking = []byte{10} + LedgerStateReality = []byte{11} + LedgerStateConflictSet = []byte{12} ) diff --git a/packages/binary/tangle/model/transaction/id.go b/packages/binary/tangle/model/transaction/id.go index 03c17fa2..c7a93190 100644 --- a/packages/binary/tangle/model/transaction/id.go +++ b/packages/binary/tangle/model/transaction/id.go @@ -4,6 +4,8 @@ import ( "fmt" "github.com/mr-tron/base58" + + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" ) type Id [IdLength]byte @@ -41,6 +43,15 @@ func IdFromBytes(bytes []byte) (result Id, err error, consumedBytes int) { return } +// ParseId is a wrapper for simplified unmarshaling in a byte stream using the marshalUtil package. +func ParseId(marshalUtil *marshalutil.MarshalUtil) (Id, error) { + if id, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return IdFromBytes(data) }); err != nil { + return Id{}, err + } else { + return id.(Id), nil + } +} + func (id *Id) MarshalBinary() (result []byte, err error) { return id.Bytes(), nil } diff --git a/packages/binary/tangle/model/transaction/payload/data/data.go b/packages/binary/tangle/model/transaction/payload/data/data.go index 84bc43b7..3c971c20 100644 --- a/packages/binary/tangle/model/transaction/payload/data/data.go +++ b/packages/binary/tangle/model/transaction/payload/data/data.go @@ -36,7 +36,18 @@ func FromBytes(bytes []byte, optionalTargetObject ...*Data) (result *Data, err e marshalUtil := marshalutil.New(bytes) // read data - result.data = marshalUtil.ReadRemainingBytes() + result.payloadType, err = marshalUtil.ReadUint32() + if err != nil { + return + } + payloadBytes, err := marshalUtil.ReadUint32() + if err != nil { + return + } + result.data, err = marshalUtil.ReadBytes(int(payloadBytes)) + if err != nil { + return + } // return the number of bytes we processed consumedBytes = marshalUtil.ReadOffset() @@ -57,7 +68,9 @@ func (dataPayload *Data) Bytes() []byte { // initialize helper marshalUtil := marshalutil.New() - // write the data as raw bytes + // marshal the payload specific information + marshalUtil.WriteUint32(dataPayload.GetType()) + marshalUtil.WriteUint32(uint32(len(dataPayload.data))) marshalUtil.WriteBytes(dataPayload.data[:]) // return result diff --git a/packages/binary/tangle/model/transaction/payload/payload.go b/packages/binary/tangle/model/transaction/payload/payload.go index 2d570a88..06d0d439 100644 --- a/packages/binary/tangle/model/transaction/payload/payload.go +++ b/packages/binary/tangle/model/transaction/payload/payload.go @@ -2,6 +2,8 @@ package payload import ( "encoding" + + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" ) type Payload interface { @@ -12,3 +14,42 @@ type Payload interface { GetType() Type String() string } + +// FromBytes unmarshals a public identity from a sequence of bytes. +func FromBytes(bytes []byte) (result Payload, err error, consumedBytes int) { + // initialize helper + marshalUtil := marshalutil.New(bytes) + + // calculate result + payloadType, err := marshalUtil.ReadUint32() + if err != nil { + return + } + payloadSize, err := marshalUtil.ReadUint32() + if err != nil { + return + } + marshalUtil.ReadSeek(marshalUtil.ReadOffset() - marshalutil.UINT32_SIZE*2) + payloadBytes, err := marshalUtil.ReadBytes(int(payloadSize) + 8) + if err != nil { + return + } + result, err = GetUnmarshaler(payloadType)(payloadBytes) + if err != nil { + return + } + + // return the number of bytes we processed + consumedBytes = marshalUtil.ReadOffset() + + return +} + +// Parse is a wrapper for simplified unmarshaling in a byte stream using the marshalUtil package. +func Parse(marshalUtil *marshalutil.MarshalUtil) (Payload, error) { + if payload, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return FromBytes(data) }); err != nil { + return nil, err + } else { + return payload.(Payload), nil + } +} diff --git a/packages/binary/tangle/model/transaction/test/transaction_test.go b/packages/binary/tangle/model/transaction/test/transaction_test.go index fdac283e..0d88e5d6 100644 --- a/packages/binary/tangle/model/transaction/test/transaction_test.go +++ b/packages/binary/tangle/model/transaction/test/transaction_test.go @@ -9,7 +9,7 @@ import ( "github.com/panjf2000/ants/v2" - "github.com/iotaledger/goshimmer/packages/binary/identity" + "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction" "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload/data" ) @@ -20,7 +20,7 @@ func BenchmarkVerifyDataTransactions(b *testing.B) { 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"))) + tx := transaction.New(transaction.EmptyId, transaction.EmptyId, ed25119.GenerateKeyPair(), data.New([]byte("some data"))) if marshaledTransaction, err := tx.MarshalBinary(); err != nil { b.Error(err) @@ -50,8 +50,8 @@ func BenchmarkVerifySignature(b *testing.B) { 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() + transactions[i] = transaction.New(transaction.EmptyId, transaction.EmptyId, ed25119.GenerateKeyPair(), data.New([]byte("test"))) + transactions[i].Bytes() } var wg sync.WaitGroup diff --git a/packages/binary/tangle/model/transaction/transaction.go b/packages/binary/tangle/model/transaction/transaction.go index 5e662b83..4abed904 100644 --- a/packages/binary/tangle/model/transaction/transaction.go +++ b/packages/binary/tangle/model/transaction/transaction.go @@ -5,8 +5,8 @@ import ( "github.com/iotaledger/hive.go/stringify" - "github.com/iotaledger/goshimmer/packages/binary/identity" "github.com/iotaledger/goshimmer/packages/binary/marshalutil" + "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload" "github.com/iotaledger/hive.go/objectstorage" @@ -23,11 +23,11 @@ type Transaction struct { // core properties (they are part of the transaction when being sent) trunkTransactionId Id branchTransactionId Id - issuer *identity.Identity + issuerPublicKey ed25119.PublicKey payload payload.Payload bytes []byte bytesMutex sync.RWMutex - signature [identity.SignatureSize]byte + signature ed25119.Signature signatureMutex sync.RWMutex // derived properties @@ -35,14 +35,18 @@ type Transaction struct { idMutex sync.RWMutex payloadId *payload.Id payloadIdMutex sync.RWMutex + + // only stored on the machine of the signer + issuerPrivateKey ed25119.PrivateKey } // Allows us to "issue" a transaction. -func New(trunkTransactionId Id, branchTransactionId Id, issuer *identity.Identity, payload payload.Payload) (result *Transaction) { +func New(trunkTransactionId Id, branchTransactionId Id, issuerKeyPair ed25119.KeyPair, payload payload.Payload) (result *Transaction) { return &Transaction{ trunkTransactionId: trunkTransactionId, branchTransactionId: branchTransactionId, - issuer: issuer, + issuerPublicKey: issuerKeyPair.PublicKey, + issuerPrivateKey: issuerKeyPair.PrivateKey, payload: payload, } } @@ -74,61 +78,38 @@ func FromBytes(bytes []byte, optionalTargetObject ...*Transaction) (result *Tran // initialize helper marshalUtil := marshalutil.New(bytes) - // read trunk transaction id - trunkTransactionId, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return IdFromBytes(data) }) - if err != nil { + // parse information + if result.trunkTransactionId, err = ParseId(marshalUtil); err != nil { return } - result.trunkTransactionId = trunkTransactionId.(Id) - - // read branch transaction id - branchTransactionId, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return IdFromBytes(data) }) - if err != nil { + if result.branchTransactionId, err = ParseId(marshalUtil); err != nil { return } - result.branchTransactionId = branchTransactionId.(Id) - - // read issuer - publicKeyBytes, err := marshalUtil.ReadBytes(identity.PublicKeySize) - if err != nil { + if result.issuerPublicKey, err = ed25119.ParsePublicKey(marshalUtil); err != nil { return } - result.issuer = identity.New(publicKeyBytes) - - // read payload type - payloadType, err := marshalUtil.ReadUint32() - if err != nil { + if result.payload, err = payload.Parse(marshalUtil); err != nil { return } - - // read payload - payloadBytes, err := marshalUtil.ReadBytes(-identity.SignatureSize) - if err != nil { - return - } - result.payload, err = payload.GetUnmarshaler(payloadType)(payloadBytes) - if err != nil { + if result.signature, err = ed25119.ParseSignature(marshalUtil); err != nil { return } - // read signature - copy(result.signature[:], marshalUtil.ReadRemainingBytes()) + // return the number of bytes we processed + consumedBytes = marshalUtil.ReadOffset() // store marshaled version - result.bytes = make([]byte, len(bytes)) + result.bytes = make([]byte, consumedBytes) copy(result.bytes, bytes) - // return the number of bytes we processed - consumedBytes = marshalUtil.ReadOffset() - return } func (transaction *Transaction) VerifySignature() (result bool) { - transactionBytes := transaction.GetBytes() + transactionBytes := transaction.Bytes() transaction.signatureMutex.RLock() - result = transaction.issuer.VerifySignature(transactionBytes[:len(transactionBytes)-identity.SignatureSize], transaction.signature[:]) + result = transaction.issuerPublicKey.VerifySignature(transactionBytes[:len(transactionBytes)-ed25119.SignatureSize], transaction.signature) transaction.signatureMutex.RUnlock() return @@ -192,14 +173,6 @@ func (transaction *Transaction) GetPayloadId() (result payload.Id) { 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() @@ -219,7 +192,7 @@ func (transaction *Transaction) calculateTransactionId() Id { } func (transaction *Transaction) calculatePayloadId() payload.Id { - bytes := transaction.GetBytes() + bytes := transaction.Bytes() return blake2b.Sum512(bytes[2*IdLength:]) } @@ -240,14 +213,13 @@ func (transaction *Transaction) Bytes() []byte { return transaction.bytes } + // marshal result marshalUtil := marshalutil.New() - marshalUtil.WriteBytes(transaction.trunkTransactionId.Bytes()) marshalUtil.WriteBytes(transaction.branchTransactionId.Bytes()) - marshalUtil.WriteBytes(transaction.issuer.PublicKey) - marshalUtil.WriteUint32(transaction.payload.GetType()) + marshalUtil.WriteBytes(transaction.issuerPublicKey.Bytes()) marshalUtil.WriteBytes(transaction.payload.Bytes()) - marshalUtil.WriteBytes(transaction.issuer.Sign(marshalUtil.Bytes())) + marshalUtil.WriteBytes(transaction.issuerPrivateKey.Sign(marshalUtil.Bytes()).Bytes()) return marshalUtil.Bytes() } diff --git a/packages/binary/tangle/tangle.go b/packages/binary/tangle/tangle.go index 4b3653ca..0e86da66 100644 --- a/packages/binary/tangle/tangle.go +++ b/packages/binary/tangle/tangle.go @@ -134,7 +134,7 @@ func (tangle *Tangle) Prune() error { return nil } -// Worker that stores the transactions and calls the corresponding "Storage events" +// Worker that stores the transactions and calls the corresponding storage events" func (tangle *Tangle) storeTransactionWorker(tx *transaction.Transaction) { // store transaction var cachedTransaction *transaction.CachedTransaction diff --git a/packages/binary/tangle/tangle_test.go b/packages/binary/tangle/tangle_test.go index f0f8c60e..be15e7e3 100644 --- a/packages/binary/tangle/tangle_test.go +++ b/packages/binary/tangle/tangle_test.go @@ -7,11 +7,10 @@ import ( "testing" "time" - "github.com/dgraph-io/badger/v2" "github.com/iotaledger/hive.go/events" "github.com/stretchr/testify/require" - "github.com/iotaledger/goshimmer/packages/binary/identity" + "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" "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" @@ -19,10 +18,6 @@ import ( "github.com/iotaledger/goshimmer/plugins/config" ) -var testDatabase *badger.DB - -var _ = config.PLUGIN - func BenchmarkTangle_AttachTransaction(b *testing.B) { dir, err := ioutil.TempDir("", b.Name()) require.NoError(b, err) @@ -30,19 +25,19 @@ func BenchmarkTangle_AttachTransaction(b *testing.B) { // use the tempdir for the database config.Node.Set(database.CFG_DIRECTORY, dir) - tangle := New(testDatabase, []byte("TEST_BINARY_TANGLE")) + tangle := New(database.GetBadgerInstance(), []byte("TEST_BINARY_TANGLE")) if err := tangle.Prune(); err != nil { b.Error(err) return } - testIdentity := identity.Generate() + testIdentity := ed25119.GenerateKeyPair() 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() + transactionBytes[i].Bytes() } b.ResetTimer() @@ -96,8 +91,8 @@ func TestTangle_AttachTransaction(t *testing.T) { 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"))) + newTransaction1 := transaction.New(transaction.EmptyId, transaction.EmptyId, ed25119.GenerateKeyPair(), data.New([]byte("some data"))) + newTransaction2 := transaction.New(newTransaction1.GetId(), newTransaction1.GetId(), ed25119.GenerateKeyPair(), data.New([]byte("some other data"))) tangle.AttachTransaction(newTransaction2) diff --git a/packages/binary/tangle/tipselector/tipselector_test.go b/packages/binary/tangle/tipselector/tipselector_test.go index 0bd3d385..05329cc3 100644 --- a/packages/binary/tangle/tipselector/tipselector_test.go +++ b/packages/binary/tangle/tipselector/tipselector_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/iotaledger/goshimmer/packages/binary/identity" + "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction" "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload/data" ) @@ -20,7 +20,7 @@ func Test(t *testing.T) { assert.Equal(t, transaction.EmptyId, branch1) // create a transaction and attach it - transaction1 := transaction.New(trunk1, branch1, identity.Generate(), data.New([]byte("testtransaction"))) + transaction1 := transaction.New(trunk1, branch1, ed25119.GenerateKeyPair(), data.New([]byte("testtransaction"))) tipSelector.AddTip(transaction1) // check if the tip shows up in the tip count @@ -32,7 +32,7 @@ func Test(t *testing.T) { assert.Equal(t, transaction1.GetId(), branch2) // create a 2nd transaction and attach it - transaction2 := transaction.New(transaction.EmptyId, transaction.EmptyId, identity.Generate(), data.New([]byte("testtransaction"))) + transaction2 := transaction.New(transaction.EmptyId, transaction.EmptyId, ed25119.GenerateKeyPair(), data.New([]byte("testtransaction"))) tipSelector.AddTip(transaction2) // check if the tip shows up in the tip count @@ -40,7 +40,7 @@ func Test(t *testing.T) { // attach a transaction to our two tips trunk3, branch3 := tipSelector.GetTips() - transaction3 := transaction.New(trunk3, branch3, identity.Generate(), data.New([]byte("testtransaction"))) + transaction3 := transaction.New(trunk3, branch3, ed25119.GenerateKeyPair(), data.New([]byte("testtransaction"))) tipSelector.AddTip(transaction3) // check if the tip shows replaces the current tips diff --git a/packages/binary/tangle/transactionparser/transactionparser_test.go b/packages/binary/tangle/transactionparser/transactionparser_test.go index 57412529..8f31c0e9 100644 --- a/packages/binary/tangle/transactionparser/transactionparser_test.go +++ b/packages/binary/tangle/transactionparser/transactionparser_test.go @@ -7,13 +7,13 @@ import ( "github.com/iotaledger/hive.go/events" - "github.com/iotaledger/goshimmer/packages/binary/identity" + "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction" "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload/data" ) func BenchmarkTransactionParser_ParseBytesSame(b *testing.B) { - txBytes := transaction.New(transaction.EmptyId, transaction.EmptyId, identity.Generate(), data.New([]byte("Test"))).GetBytes() + txBytes := transaction.New(transaction.EmptyId, transaction.EmptyId, ed25119.GenerateKeyPair(), data.New([]byte("Test"))).Bytes() txParser := New() b.ResetTimer() @@ -28,7 +28,7 @@ func BenchmarkTransactionParser_ParseBytesSame(b *testing.B) { func BenchmarkTransactionParser_ParseBytesDifferent(b *testing.B) { transactionBytes := make([][]byte, b.N) for i := 0; i < b.N; i++ { - transactionBytes[i] = transaction.New(transaction.EmptyId, transaction.EmptyId, identity.Generate(), data.New([]byte("Test"+strconv.Itoa(i)))).GetBytes() + transactionBytes[i] = transaction.New(transaction.EmptyId, transaction.EmptyId, ed25119.GenerateKeyPair(), data.New([]byte("Test"+strconv.Itoa(i)))).Bytes() } txParser := New() @@ -43,10 +43,10 @@ func BenchmarkTransactionParser_ParseBytesDifferent(b *testing.B) { } func TestTransactionParser_ParseTransaction(t *testing.T) { - tx := transaction.New(transaction.EmptyId, transaction.EmptyId, identity.Generate(), data.New([]byte("Test"))) + tx := transaction.New(transaction.EmptyId, transaction.EmptyId, ed25119.GenerateKeyPair(), data.New([]byte("Test"))) txParser := New() - txParser.Parse(tx.GetBytes(), nil) + txParser.Parse(tx.Bytes(), nil) txParser.Events.TransactionParsed.Attach(events.NewClosure(func(tx *transaction.Transaction) { fmt.Println("PARSED!!!") diff --git a/packages/binary/valuetransfers/address/address.go b/packages/binary/valuetransfer/address/address.go similarity index 100% rename from packages/binary/valuetransfers/address/address.go rename to packages/binary/valuetransfer/address/address.go diff --git a/packages/binary/valuetransfers/coloredbalance/color/color.go b/packages/binary/valuetransfer/coloredbalance/color/color.go similarity index 100% rename from packages/binary/valuetransfers/coloredbalance/color/color.go rename to packages/binary/valuetransfer/coloredbalance/color/color.go diff --git a/packages/binary/valuetransfers/coloredbalance/coloredbalance.go b/packages/binary/valuetransfer/coloredbalance/coloredbalance.go similarity index 93% rename from packages/binary/valuetransfers/coloredbalance/coloredbalance.go rename to packages/binary/valuetransfer/coloredbalance/coloredbalance.go index 6d8f576b..b0f4d43a 100644 --- a/packages/binary/valuetransfers/coloredbalance/coloredbalance.go +++ b/packages/binary/valuetransfer/coloredbalance/coloredbalance.go @@ -4,7 +4,7 @@ import ( "strconv" "github.com/iotaledger/goshimmer/packages/binary/marshalutil" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/coloredbalance/color" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/coloredbalance/color" ) type ColoredBalance struct { diff --git a/packages/binary/valuetransfer/payload/cached_object.go b/packages/binary/valuetransfer/payload/cached_object.go new file mode 100644 index 00000000..5c1639b1 --- /dev/null +++ b/packages/binary/valuetransfer/payload/cached_object.go @@ -0,0 +1,39 @@ +package payload + +import ( + "github.com/iotaledger/hive.go/objectstorage" +) + +// CachedObject is a wrapper for the object storage, that takes care of type casting the managed objects. +// Since go does not have generics (yet), the object storage works based on the generic "interface{}" type, which means +// that we have to regularly type cast the returned objects, to match the expected type. To reduce the burden of +// manually managing these type, we create a wrapper that does this for us. This way, we can consistently handle the +// specialized types of CachedObjects, without having to manually type cast over and over again. +type CachedObject struct { + objectstorage.CachedObject +} + +// Retain wraps the underlying method to return a new "wrapped object". +func (cachedPayload *CachedObject) Retain() *CachedObject { + return &CachedObject{cachedPayload.CachedObject.Retain()} +} + +// Consume wraps the underlying method to return the correctly typed objects in the callback. +func (cachedPayload *CachedObject) Consume(consumer func(payload *Payload)) bool { + return cachedPayload.CachedObject.Consume(func(object objectstorage.StorableObject) { + consumer(object.(*Payload)) + }) +} + +// Unwrap provides a way to "Get" a type casted version of the underlying object. +func (cachedPayload *CachedObject) Unwrap() *Payload { + if untypedTransaction := cachedPayload.Get(); untypedTransaction == nil { + return nil + } else { + if typeCastedTransaction := untypedTransaction.(*Payload); typeCastedTransaction == nil || typeCastedTransaction.IsDeleted() { + return nil + } else { + return typeCastedTransaction + } + } +} diff --git a/packages/binary/valuetransfer/payload/id/id.go b/packages/binary/valuetransfer/payload/id/id.go new file mode 100644 index 00000000..8a8f24f0 --- /dev/null +++ b/packages/binary/valuetransfer/payload/id/id.go @@ -0,0 +1,87 @@ +package id + +import ( + "fmt" + + "github.com/mr-tron/base58" + + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" +) + +// Id represents the hash of a payload that is used to identify the given payload. +type Id [Length]byte + +// New creates a payload id from a base58 encoded string. +func New(base58EncodedString string) (result Id, err error) { + bytes, err := base58.Decode(base58EncodedString) + if err != nil { + return + } + + if len(bytes) != Length { + err = fmt.Errorf("length of base58 formatted payload id is wrong") + + return + } + + copy(result[:], bytes) + + return +} + +// Parse is a wrapper for simplified unmarshaling in a byte stream using the marshalUtil package. +func Parse(marshalUtil *marshalutil.MarshalUtil) (Id, error) { + if id, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return FromBytes(data) }); err != nil { + return Id{}, err + } else { + return id.(Id), nil + } +} + +// FromBytes unmarshals a payload id from a sequence of bytes. +// It either creates a new payload id or fills the optionally provided object with the parsed information. +func FromBytes(bytes []byte, optionalTargetObject ...*Id) (result Id, err error, consumedBytes int) { + // determine the target object that will hold the unmarshaled information + var targetObject *Id + switch len(optionalTargetObject) { + case 0: + targetObject = &result + case 1: + targetObject = optionalTargetObject[0] + default: + panic("too many arguments in call to FromBytes") + } + + // initialize helper + marshalUtil := marshalutil.New(bytes) + + // read id from bytes + idBytes, err := marshalUtil.ReadBytes(Length) + if err != nil { + return + } + copy(targetObject[:], idBytes) + + // copy result if we have provided a target object + result = *targetObject + + // return the number of bytes we processed + consumedBytes = marshalUtil.ReadOffset() + + return +} + +// String returns a base58 encoded version of the payload id. +func (id Id) String() string { + return base58.Encode(id[:]) +} + +func (id Id) Bytes() []byte { + return id[:] +} + +// Empty represents the id encoding the genesis. +var Genesis Id + +// Length defined the amount of bytes in a payload id (32 bytes hash value). +const Length = 32 diff --git a/packages/binary/valuetransfer/payload/id/id_test.go b/packages/binary/valuetransfer/payload/id/id_test.go new file mode 100644 index 00000000..86fd25ab --- /dev/null +++ b/packages/binary/valuetransfer/payload/id/id_test.go @@ -0,0 +1,26 @@ +package id + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test(t *testing.T) { + // create variable for id + sourceId, err := New("4uQeVj5tqViQh7yWWGStvkEG1Zmhx6uasJtWCJziofM") + if err != nil { + panic(err) + } + + // read serialized id into both variables + var restoredIdPointer Id + restoredIdValue, err, _ := FromBytes(sourceId.Bytes(), &restoredIdPointer) + if err != nil { + panic(err) + } + + // check if both variables give the same result + assert.Equal(t, sourceId, restoredIdValue) + assert.Equal(t, sourceId, restoredIdPointer) +} diff --git a/packages/binary/valuetransfers/payload/payload.go b/packages/binary/valuetransfer/payload/payload.go similarity index 76% rename from packages/binary/valuetransfers/payload/payload.go rename to packages/binary/valuetransfer/payload/payload.go index 39430b21..ee248f7d 100644 --- a/packages/binary/valuetransfers/payload/payload.go +++ b/packages/binary/valuetransfer/payload/payload.go @@ -9,9 +9,9 @@ import ( "github.com/iotaledger/goshimmer/packages/binary/marshalutil" "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction/payload" - payloadid "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/id" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer" - transferid "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer/id" + payloadid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/id" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer" + transferid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/id" ) type Payload struct { @@ -36,6 +36,17 @@ func New(trunkPayloadId, branchPayloadId payloadid.Id, valueTransfer *transfer.T } } +func FromStorage(key []byte) objectstorage.StorableObject { + id, err, _ := payloadid.FromBytes(key) + if err != nil { + panic(err) + } + + return &Payload{ + id: &id, + } +} + // FromBytes parses the marshaled version of a Payload into an object. // It either returns a new Payload or fills an optionally provided Payload with the parsed information. func FromBytes(bytes []byte, optionalTargetObject ...*Payload) (result *Payload, err error, consumedBytes int) { @@ -52,19 +63,29 @@ func FromBytes(bytes []byte, optionalTargetObject ...*Payload) (result *Payload, // initialize helper marshalUtil := marshalutil.New(bytes) + // read information that are required to identify the payload from the outside + _, err = marshalUtil.ReadUint32() + if err != nil { + return + } + _, err = marshalUtil.ReadUint32() + if err != nil { + return + } + // parse trunk payload id - parsedTrunkPayloadId, err := marshalUtil.ReadBytes(payloadid.Length) + parsedTrunkPayloadId, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return payloadid.FromBytes(data) }) if err != nil { return } - result.trunkPayloadId = payloadid.New(parsedTrunkPayloadId) + result.trunkPayloadId = parsedTrunkPayloadId.(payloadid.Id) // parse branch payload id - parsedBranchPayloadId, err := marshalUtil.ReadBytes(payloadid.Length) + parsedBranchPayloadId, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return payloadid.FromBytes(data) }) if err != nil { return } - result.branchPayloadId = payloadid.New(parsedBranchPayloadId) + result.branchPayloadId = parsedBranchPayloadId.(payloadid.Id) // parse transfer parsedTransfer, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return transfer.FromBytes(data) }) @@ -104,22 +125,22 @@ func (payload *Payload) GetId() payloadid.Id { } // otherwise calculate the id - transferId := payload.GetTransfer().GetId() marshalUtil := marshalutil.New(payloadid.Length + payloadid.Length + transferid.Length) - marshalUtil.WriteBytes(payload.trunkPayloadId[:]) - marshalUtil.WriteBytes(payload.branchPayloadId[:]) - marshalUtil.WriteBytes(transferId[:]) + marshalUtil.WriteBytes(payload.trunkPayloadId.Bytes()) + marshalUtil.WriteBytes(payload.branchPayloadId.Bytes()) + marshalUtil.WriteBytes(payload.GetTransfer().GetId().Bytes()) + var id payloadid.Id = blake2b.Sum256(marshalUtil.Bytes()) payload.id = &id return id } -func (payload *Payload) GetTrunkPayloadId() payloadid.Id { +func (payload *Payload) GetTrunkId() payloadid.Id { return payload.trunkPayloadId } -func (payload *Payload) GetBranchPayloadId() payloadid.Id { +func (payload *Payload) GetBranchId() payloadid.Id { return payload.branchPayloadId } @@ -155,9 +176,12 @@ func (payload *Payload) Bytes() (bytes []byte) { } // marshal fields - marshalUtil := marshalutil.New(payloadid.Length + payloadid.Length + transferid.Length) - marshalUtil.WriteBytes(payload.trunkPayloadId[:]) - marshalUtil.WriteBytes(payload.branchPayloadId[:]) + payloadLength := payloadid.Length + payloadid.Length + len(transferBytes) + marshalUtil := marshalutil.New(marshalutil.UINT32_SIZE + marshalutil.UINT32_SIZE + payloadLength) + marshalUtil.WriteUint32(Type) + marshalUtil.WriteUint32(uint32(payloadLength)) + marshalUtil.WriteBytes(payload.trunkPayloadId.Bytes()) + marshalUtil.WriteBytes(payload.branchPayloadId.Bytes()) marshalUtil.WriteBytes(transferBytes) bytes = marshalUtil.Bytes() @@ -170,8 +194,8 @@ func (payload *Payload) Bytes() (bytes []byte) { func (payload *Payload) String() string { return stringify.Struct("Payload", stringify.StructField("id", payload.GetId()), - stringify.StructField("trunk", payload.GetTrunkPayloadId()), - stringify.StructField("branch", payload.GetBranchPayloadId()), + stringify.StructField("trunk", payload.GetTrunkId()), + stringify.StructField("branch", payload.GetBranchId()), stringify.StructField("transfer", payload.GetTransfer()), ) } diff --git a/packages/binary/valuetransfers/payload/id/id.go b/packages/binary/valuetransfer/payload/transfer/id/id.go similarity index 83% rename from packages/binary/valuetransfers/payload/id/id.go rename to packages/binary/valuetransfer/payload/transfer/id/id.go index 8f9847f5..8036c466 100644 --- a/packages/binary/valuetransfers/payload/id/id.go +++ b/packages/binary/valuetransfer/payload/transfer/id/id.go @@ -12,10 +12,12 @@ func New(idBytes []byte) (result Id) { return } +func (id Id) Bytes() []byte { + return id[:] +} + func (id Id) String() string { return base58.Encode(id[:]) } -var Empty Id - const Length = 32 diff --git a/packages/binary/valuetransfers/payload/transfer/inputs/inputs.go b/packages/binary/valuetransfer/payload/transfer/inputs/inputs.go similarity index 92% rename from packages/binary/valuetransfers/payload/transfer/inputs/inputs.go rename to packages/binary/valuetransfer/payload/transfer/inputs/inputs.go index bfb7e688..be2c3fa4 100644 --- a/packages/binary/valuetransfers/payload/transfer/inputs/inputs.go +++ b/packages/binary/valuetransfer/payload/transfer/inputs/inputs.go @@ -3,10 +3,10 @@ package inputs import ( "github.com/iotaledger/goshimmer/packages/binary/datastructure/orderedmap" "github.com/iotaledger/goshimmer/packages/binary/marshalutil" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/address" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer/id" - transferid "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer/id" - transferoutputid "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/transferoutput/id" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/id" + transferid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/id" + transferoutputid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transferoutput/id" ) type Inputs struct { diff --git a/packages/binary/valuetransfers/payload/transfer/outputs/outputs.go b/packages/binary/valuetransfer/payload/transfer/outputs/outputs.go similarity index 96% rename from packages/binary/valuetransfers/payload/transfer/outputs/outputs.go rename to packages/binary/valuetransfer/payload/transfer/outputs/outputs.go index 7a6d94fe..f39268bc 100644 --- a/packages/binary/valuetransfers/payload/transfer/outputs/outputs.go +++ b/packages/binary/valuetransfer/payload/transfer/outputs/outputs.go @@ -3,8 +3,8 @@ package outputs import ( "github.com/iotaledger/goshimmer/packages/binary/datastructure/orderedmap" "github.com/iotaledger/goshimmer/packages/binary/marshalutil" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/address" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/coloredbalance" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/coloredbalance" ) type Outputs struct { diff --git a/packages/binary/valuetransfers/payload/transfer/signatures/ed25519.go b/packages/binary/valuetransfer/payload/transfer/signatures/ed25519.go similarity index 98% rename from packages/binary/valuetransfers/payload/transfer/signatures/ed25519.go rename to packages/binary/valuetransfer/payload/transfer/signatures/ed25519.go index 17cdc17d..e7423f55 100644 --- a/packages/binary/valuetransfers/payload/transfer/signatures/ed25519.go +++ b/packages/binary/valuetransfer/payload/transfer/signatures/ed25519.go @@ -5,7 +5,7 @@ import ( "github.com/iotaledger/goshimmer/packages/binary/marshalutil" "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/address" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" ) // region PUBLIC API /////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/binary/valuetransfers/payload/transfer/signatures/interfaces.go b/packages/binary/valuetransfer/payload/transfer/signatures/interfaces.go similarity index 98% rename from packages/binary/valuetransfers/payload/transfer/signatures/interfaces.go rename to packages/binary/valuetransfer/payload/transfer/signatures/interfaces.go index 9b28078f..81fe7247 100644 --- a/packages/binary/valuetransfers/payload/transfer/signatures/interfaces.go +++ b/packages/binary/valuetransfer/payload/transfer/signatures/interfaces.go @@ -1,6 +1,6 @@ package signatures -import "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/address" +import "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" // SignatureScheme defines an interface for different signature generation methods (i.e. ED25519, WOTS, and so on ...). type SignatureScheme interface { diff --git a/packages/binary/valuetransfers/payload/transfer/signatures/signatures.go b/packages/binary/valuetransfer/payload/transfer/signatures/signatures.go similarity index 97% rename from packages/binary/valuetransfers/payload/transfer/signatures/signatures.go rename to packages/binary/valuetransfer/payload/transfer/signatures/signatures.go index f5cfee2c..d07d1a43 100644 --- a/packages/binary/valuetransfers/payload/transfer/signatures/signatures.go +++ b/packages/binary/valuetransfer/payload/transfer/signatures/signatures.go @@ -3,7 +3,7 @@ package signatures import ( "github.com/iotaledger/goshimmer/packages/binary/datastructure/orderedmap" "github.com/iotaledger/goshimmer/packages/binary/marshalutil" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/address" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" ) // Signatures represents a container for the address signatures of a value transfer. diff --git a/packages/binary/valuetransfers/payload/transfer/signatures/signatures_test.go b/packages/binary/valuetransfer/payload/transfer/signatures/signatures_test.go similarity index 93% rename from packages/binary/valuetransfers/payload/transfer/signatures/signatures_test.go rename to packages/binary/valuetransfer/payload/transfer/signatures/signatures_test.go index a4aca509..214092e8 100644 --- a/packages/binary/valuetransfers/payload/transfer/signatures/signatures_test.go +++ b/packages/binary/valuetransfer/payload/transfer/signatures/signatures_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/address" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" ) func TestSignatures(t *testing.T) { diff --git a/packages/binary/valuetransfers/payload/transfer/transfer.go b/packages/binary/valuetransfer/payload/transfer/transfer.go similarity index 94% rename from packages/binary/valuetransfers/payload/transfer/transfer.go rename to packages/binary/valuetransfer/payload/transfer/transfer.go index fd041ce5..0703fcba 100644 --- a/packages/binary/valuetransfers/payload/transfer/transfer.go +++ b/packages/binary/valuetransfer/payload/transfer/transfer.go @@ -10,11 +10,11 @@ import ( "golang.org/x/crypto/blake2b" "github.com/iotaledger/goshimmer/packages/binary/marshalutil" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/address" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer/id" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer/inputs" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer/outputs" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer/signatures" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/id" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/inputs" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/outputs" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/signatures" ) // region IMPLEMENT Transfer /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/binary/valuetransfer/tangle/constants.go b/packages/binary/valuetransfer/tangle/constants.go new file mode 100644 index 00000000..937b6728 --- /dev/null +++ b/packages/binary/valuetransfer/tangle/constants.go @@ -0,0 +1,10 @@ +package tangle + +import ( + "time" +) + +const ( + MAX_MISSING_TIME_BEFORE_CLEANUP = 30 * time.Second + MISSING_CHECK_INTERVAL = 5 * time.Second +) diff --git a/packages/binary/valuetransfer/tangle/events.go b/packages/binary/valuetransfer/tangle/events.go new file mode 100644 index 00000000..653ae828 --- /dev/null +++ b/packages/binary/valuetransfer/tangle/events.go @@ -0,0 +1,41 @@ +package tangle + +import ( + "github.com/iotaledger/hive.go/events" + + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload" + payloadid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/id" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/tangle/payloadmetadata" +) + +type Events struct { + // Get's called whenever a transaction + PayloadAttached *events.Event + PayloadSolid *events.Event + MissingPayloadReceived *events.Event + PayloadMissing *events.Event + PayloadUnsolidifiable *events.Event + TransactionRemoved *events.Event +} + +func newEvents() *Events { + return &Events{ + PayloadAttached: events.NewEvent(cachedPayloadEvent), + PayloadSolid: events.NewEvent(cachedPayloadEvent), + MissingPayloadReceived: events.NewEvent(cachedPayloadEvent), + PayloadMissing: events.NewEvent(payloadIdEvent), + PayloadUnsolidifiable: events.NewEvent(payloadIdEvent), + TransactionRemoved: events.NewEvent(payloadIdEvent), + } +} + +func payloadIdEvent(handler interface{}, params ...interface{}) { + handler.(func(payloadid.Id))(params[0].(payloadid.Id)) +} + +func cachedPayloadEvent(handler interface{}, params ...interface{}) { + handler.(func(*payload.CachedObject, *payloadmetadata.CachedObject))( + params[0].(*payload.CachedObject).Retain(), + params[1].(*payloadmetadata.CachedObject).Retain(), + ) +} diff --git a/packages/binary/valuetransfer/tangle/missingpayload/missingpayload.go b/packages/binary/valuetransfer/tangle/missingpayload/missingpayload.go new file mode 100644 index 00000000..38afa809 --- /dev/null +++ b/packages/binary/valuetransfer/tangle/missingpayload/missingpayload.go @@ -0,0 +1,103 @@ +package missingpayload + +import ( + "time" + + "github.com/iotaledger/hive.go/objectstorage" + + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" + payloadid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/id" +) + +// MissingPayload represents a payload that was referenced through branch or trunk but that is missing in our object +// storage. +type MissingPayload struct { + objectstorage.StorableObjectFlags + + payloadId payloadid.Id + missingSince time.Time +} + +// New creates an entry for a missing value transfer payload. +func New(payloadId payloadid.Id) *MissingPayload { + return &MissingPayload{ + payloadId: payloadId, + missingSince: time.Now(), + } +} + +// FromBytes unmarshals an entry for a missing value transfer payload from a sequence of bytes. +// It either creates a new entry or fills the optionally provided one with the parsed information. +func FromBytes(bytes []byte, optionalTargetObject ...*MissingPayload) (result *MissingPayload, err error, consumedBytes int) { + // determine the target object that will hold the unmarshaled information + switch len(optionalTargetObject) { + case 0: + result = &MissingPayload{} + case 1: + result = optionalTargetObject[0] + default: + panic("too many arguments in call to FromBytes") + } + + // parse the bytes + marshalUtil := marshalutil.New(bytes) + if result.payloadId, err = payloadid.Parse(marshalUtil); err != nil { + return + } + if result.missingSince, err = marshalUtil.ReadTime(); err != nil { + return + } + consumedBytes = marshalUtil.ReadOffset() + + return +} + +// FromStorage gets called when we restore an entry for a missing value transfer payload from the storage. The bytes and +// the content will be unmarshaled by an external caller using the binary.MarshalBinary interface. +func FromStorage([]byte) objectstorage.StorableObject { + return &MissingPayload{} +} + +// GetId returns the payload id, that is missing. +func (missingPayload *MissingPayload) GetId() payloadid.Id { + return missingPayload.payloadId +} + +// GetMissingSince returns the time.Time since the transaction was first reported as being missing. +func (missingPayload *MissingPayload) GetMissingSince() time.Time { + return missingPayload.missingSince +} + +// Bytes marshals the missing payload into a sequence of bytes. +func (missingPayload *MissingPayload) Bytes() []byte { + marshalUtil := marshalutil.New() + + marshalUtil.WriteBytes(missingPayload.payloadId.Bytes()) + marshalUtil.WriteTime(missingPayload.missingSince) + + return marshalUtil.Bytes() +} + +// GetStorageKey returns the key that is used to store the object in the database. +// It is required to match StorableObject interface. +func (missingPayload *MissingPayload) GetStorageKey() []byte { + return missingPayload.payloadId.Bytes() +} + +// Update is disabled and panics if it ever gets called - updates are supposed to happen through the setters. +// It is required to match StorableObject interface. +func (missingPayload *MissingPayload) Update(other objectstorage.StorableObject) { + panic("implement me") +} + +// MarshalBinary is required to match the encoding.BinaryMarshaler interface. +func (missingPayload *MissingPayload) MarshalBinary() (data []byte, err error) { + return missingPayload.Bytes(), nil +} + +// UnmarshalBinary is required to match the encoding.BinaryUnmarshaler interface. +func (missingPayload *MissingPayload) UnmarshalBinary(data []byte) (err error) { + _, err, _ = FromBytes(data, missingPayload) + + return +} diff --git a/packages/binary/valuetransfer/tangle/payloadapprover/cached_object.go b/packages/binary/valuetransfer/tangle/payloadapprover/cached_object.go new file mode 100644 index 00000000..b90c7042 --- /dev/null +++ b/packages/binary/valuetransfer/tangle/payloadapprover/cached_object.go @@ -0,0 +1,49 @@ +package payloadapprover + +import ( + "github.com/iotaledger/hive.go/objectstorage" +) + +// CachedObject is a wrapper for the object storage, that takes care of type casting the managed objects. +// Since go does not have generics (yet), the object storage works based on the generic "interface{}" type, which means +// that we have to regularly type cast the returned objects, to match the expected type. To reduce the burden of +// manually managing these type, we create a wrapper that does this for us. This way, we can consistently handle the +// specialized types of CachedObjects, without having to manually type cast over and over again. +type CachedObject struct { + objectstorage.CachedObject +} + +// Retain wraps the underlying method to return a new "wrapped object". +func (cachedApprover *CachedObject) Retain() *CachedObject { + return &CachedObject{cachedApprover.CachedObject.Retain()} +} + +// Consume wraps the underlying method to return the correctly typed objects in the callback. +func (cachedApprover *CachedObject) Consume(consumer func(payload *PayloadApprover)) bool { + return cachedApprover.CachedObject.Consume(func(object objectstorage.StorableObject) { + consumer(object.(*PayloadApprover)) + }) +} + +// Unwrap provides a way to "Get" a type casted version of the underlying object. +func (cachedApprover *CachedObject) Unwrap() *PayloadApprover { + if untypedTransaction := cachedApprover.Get(); untypedTransaction == nil { + return nil + } else { + if typeCastedTransaction := untypedTransaction.(*PayloadApprover); typeCastedTransaction == nil || typeCastedTransaction.IsDeleted() { + return nil + } else { + return typeCastedTransaction + } + } +} + +type CachedObjects []*CachedObject + +func (cachedApprovers CachedObjects) Consume(consumer func(approver *PayloadApprover)) (consumed bool) { + for _, cachedApprover := range cachedApprovers { + consumed = cachedApprover.Consume(consumer) || consumed + } + + return +} diff --git a/packages/binary/valuetransfer/tangle/payloadapprover/payloadapprover.go b/packages/binary/valuetransfer/tangle/payloadapprover/payloadapprover.go new file mode 100644 index 00000000..7c92ae68 --- /dev/null +++ b/packages/binary/valuetransfer/tangle/payloadapprover/payloadapprover.go @@ -0,0 +1,85 @@ +package payloadapprover + +import ( + "github.com/iotaledger/hive.go/objectstorage" + + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" + payloadid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/id" +) + +// PayloadApprover is a database entity, that allows us to keep track of the "tangle structure" by encoding which +// payload approves which other payload. It allows us to traverse the tangle in the opposite direction of the referenced +// trunk and branch payloads. +type PayloadApprover struct { + objectstorage.StorableObjectFlags + + storageKey []byte + referencedPayloadId payloadid.Id + approvingPayloadId payloadid.Id +} + +// New creates an approver object that encodes a single relation between an approved and an approving payload. +func New(referencedPayload payloadid.Id, approvingPayload payloadid.Id) *PayloadApprover { + marshalUtil := marshalutil.New(payloadid.Length + payloadid.Length) + marshalUtil.WriteBytes(referencedPayload.Bytes()) + marshalUtil.WriteBytes(approvingPayload.Bytes()) + + return &PayloadApprover{ + referencedPayloadId: referencedPayload, + approvingPayloadId: approvingPayload, + storageKey: marshalUtil.Bytes(), + } +} + +// FromStorage get's called when we restore transaction metadata from the storage. +// In contrast to other database models, it unmarshals the information from the key and does not use the UnmarshalBinary +// method. +func FromStorage(idBytes []byte) objectstorage.StorableObject { + marshalUtil := marshalutil.New(idBytes) + + referencedPayloadId, err := payloadid.Parse(marshalUtil) + if err != nil { + panic(err) + } + approvingPayloadId, err := payloadid.Parse(marshalUtil) + if err != nil { + panic(err) + } + + result := &PayloadApprover{ + referencedPayloadId: referencedPayloadId, + approvingPayloadId: approvingPayloadId, + storageKey: marshalUtil.Bytes(true), + } + + return result +} + +// GetApprovingPayloadId returns the id of the approving payload. +func (approver *PayloadApprover) GetApprovingPayloadId() payloadid.Id { + return approver.approvingPayloadId +} + +// GetStorageKey returns the key that is used to store the object in the database. +// It is required to match StorableObject interface. +func (approver *PayloadApprover) GetStorageKey() []byte { + return approver.storageKey +} + +// MarshalBinary is implemented to conform with the StorableObject interface, but it does not really do anything, +// since all of the information about an approver are stored in the "key". +func (approver *PayloadApprover) MarshalBinary() (data []byte, err error) { + return +} + +// UnmarshalBinary is implemented to conform with the StorableObject interface, but it does not really do anything, +// since all of the information about an approver are stored in the "key". +func (approver *PayloadApprover) UnmarshalBinary(data []byte) error { + return nil +} + +// Update is disabled and panics if it ever gets called - updates are supposed to happen through the setters. +// It is required to match StorableObject interface. +func (approver *PayloadApprover) Update(other objectstorage.StorableObject) { + panic("implement me") +} diff --git a/packages/binary/valuetransfer/tangle/payloadmetadata/cached_object.go b/packages/binary/valuetransfer/tangle/payloadmetadata/cached_object.go new file mode 100644 index 00000000..bde06d7c --- /dev/null +++ b/packages/binary/valuetransfer/tangle/payloadmetadata/cached_object.go @@ -0,0 +1,39 @@ +package payloadmetadata + +import ( + "github.com/iotaledger/hive.go/objectstorage" +) + +// CachedObject is a wrapper for the object storage, that takes care of type casting the managed objects. +// Since go does not have generics (yet), the object storage works based on the generic "interface{}" type, which means +// that we have to regularly type cast the returned objects, to match the expected type. To reduce the burden of +// manually managing these type, we create a wrapper that does this for us. This way, we can consistently handle the +// specialized types of CachedObjects, without having to manually type cast over and over again. +type CachedObject struct { + objectstorage.CachedObject +} + +// Retain wraps the underlying method to return a new "wrapped object". +func (cachedPayload *CachedObject) Retain() *CachedObject { + return &CachedObject{cachedPayload.CachedObject.Retain()} +} + +// Consume wraps the underlying method to return the correctly typed objects in the callback. +func (cachedPayload *CachedObject) Consume(consumer func(payload *PayloadMetadata)) bool { + return cachedPayload.CachedObject.Consume(func(object objectstorage.StorableObject) { + consumer(object.(*PayloadMetadata)) + }) +} + +// Unwrap provides a way to "Get" a type casted version of the underlying object. +func (cachedPayload *CachedObject) Unwrap() *PayloadMetadata { + if untypedTransaction := cachedPayload.Get(); untypedTransaction == nil { + return nil + } else { + if typeCastedTransaction := untypedTransaction.(*PayloadMetadata); typeCastedTransaction == nil || typeCastedTransaction.IsDeleted() { + return nil + } else { + return typeCastedTransaction + } + } +} diff --git a/packages/binary/valuetransfer/tangle/payloadmetadata/payloadmetadata.go b/packages/binary/valuetransfer/tangle/payloadmetadata/payloadmetadata.go new file mode 100644 index 00000000..b6eb53fb --- /dev/null +++ b/packages/binary/valuetransfer/tangle/payloadmetadata/payloadmetadata.go @@ -0,0 +1,178 @@ +package payloadmetadata + +import ( + "sync" + "time" + + "github.com/iotaledger/hive.go/objectstorage" + "github.com/iotaledger/hive.go/stringify" + + "github.com/iotaledger/goshimmer/packages/binary/marshalutil" + payloadid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/id" +) + +// PayloadMetadata is a container for the metadata of a value transfer payload. +// It is used to store the information in the database. +type PayloadMetadata struct { + objectstorage.StorableObjectFlags + + payloadId payloadid.Id + solid bool + solidificationTime time.Time + + solidMutex sync.RWMutex + solidificationTimeMutex sync.RWMutex +} + +// New creates an empty container for the metadata of a value transfer payload. +func New(payloadId payloadid.Id) *PayloadMetadata { + return &PayloadMetadata{ + payloadId: payloadId, + } +} + +// FromBytes unmarshals a container with the metadata of a value transfer payload from a sequence of bytes. +// It either creates a new container or fills the optionally provided container with the parsed information. +func FromBytes(bytes []byte, optionalTargetObject ...*PayloadMetadata) (result *PayloadMetadata, err error, consumedBytes int) { + // determine the target object that will hold the unmarshaled information + switch len(optionalTargetObject) { + case 0: + result = &PayloadMetadata{} + case 1: + result = optionalTargetObject[0] + default: + panic("too many arguments in call to FromBytes") + } + + // parse the bytes + marshalUtil := marshalutil.New(bytes) + if result.payloadId, err = payloadid.Parse(marshalUtil); err != nil { + return + } + if result.solidificationTime, err = marshalUtil.ReadTime(); err != nil { + return + } + if result.solid, err = marshalUtil.ReadBool(); err != nil { + return + } + consumedBytes = marshalUtil.ReadOffset() + + return +} + +// FromStorage gets called when we restore transaction metadata from the storage. The bytes and the content will be +// unmarshaled by an external caller using the binary.MarshalBinary interface. +func FromStorage(id []byte) objectstorage.StorableObject { + result := &PayloadMetadata{} + + var err error + if result.payloadId, err = payloadid.Parse(marshalutil.New(id)); err != nil { + panic(err) + } + + return result +} + +// Parse is a wrapper for simplified unmarshaling in a byte stream using the marshalUtil package. +func Parse(marshalUtil *marshalutil.MarshalUtil) (*PayloadMetadata, error) { + if payloadMetadata, err := marshalUtil.Parse(func(data []byte) (interface{}, error, int) { return FromBytes(data) }); err != nil { + return nil, err + } else { + return payloadMetadata.(*PayloadMetadata), nil + } +} + +// GetPayloadId return the id of the payload that this metadata is associated to. +func (payloadMetadata *PayloadMetadata) GetPayloadId() payloadid.Id { + return payloadMetadata.payloadId +} + +// IsSolid returns true of the payload has been marked as solid. +func (payloadMetadata *PayloadMetadata) IsSolid() (result bool) { + payloadMetadata.solidMutex.RLock() + result = payloadMetadata.solid + payloadMetadata.solidMutex.RUnlock() + + return +} + +// SetSolid marks a payload as either solid or not solid. +// It returns true if the solid flag was changes and automatically updates the solidificationTime as well. +func (payloadMetadata *PayloadMetadata) SetSolid(solid bool) (modified bool) { + payloadMetadata.solidMutex.RLock() + if payloadMetadata.solid != solid { + payloadMetadata.solidMutex.RUnlock() + + payloadMetadata.solidMutex.Lock() + if payloadMetadata.solid != solid { + payloadMetadata.solid = solid + if solid { + payloadMetadata.solidificationTimeMutex.Lock() + payloadMetadata.solidificationTime = time.Now() + payloadMetadata.solidificationTimeMutex.Unlock() + } + + payloadMetadata.SetModified() + + modified = true + } + payloadMetadata.solidMutex.Unlock() + + } else { + payloadMetadata.solidMutex.RUnlock() + } + + return +} + +// GetSoldificationTime returns the time when the payload was marked to be solid. +func (payloadMetadata *PayloadMetadata) GetSoldificationTime() time.Time { + payloadMetadata.solidificationTimeMutex.RLock() + defer payloadMetadata.solidificationTimeMutex.RUnlock() + + return payloadMetadata.solidificationTime +} + +// Bytes marshals the metadata into a sequence of bytes. +func (payloadMetadata *PayloadMetadata) Bytes() []byte { + marshalUtil := marshalutil.New() + + marshalUtil.WriteBytes(payloadMetadata.payloadId.Bytes()) + marshalUtil.WriteTime(payloadMetadata.solidificationTime) + marshalUtil.WriteBool(payloadMetadata.solid) + + return marshalUtil.Bytes() +} + +// String creates a human readable version of the metadata (for debug purposes). +func (payloadMetadata *PayloadMetadata) String() string { + return stringify.Struct("PayloadMetadata", + stringify.StructField("payloadId", payloadMetadata.GetPayloadId()), + stringify.StructField("solid", payloadMetadata.IsSolid()), + stringify.StructField("solidificationTime", payloadMetadata.GetSoldificationTime()), + ) +} + +// GetStorageKey returns the key that is used to store the object in the database. +// It is required to match StorableObject interface. +func (payloadMetadata *PayloadMetadata) GetStorageKey() []byte { + return payloadMetadata.payloadId.Bytes() +} + +// Update is disabled and panics if it ever gets called - updates are supposed to happen through the setters. +// It is required to match StorableObject interface. +func (payloadMetadata *PayloadMetadata) Update(other objectstorage.StorableObject) { + panic("update forbidden") +} + +// MarshalBinary is required to match the encoding.BinaryMarshaler interface. +func (payloadMetadata *PayloadMetadata) MarshalBinary() ([]byte, error) { + return payloadMetadata.Bytes(), nil +} + +// UnmarshalBinary is required to match the encoding.BinaryUnmarshaler interface. +func (payloadMetadata *PayloadMetadata) UnmarshalBinary(data []byte) (err error) { + _, err, _ = FromBytes(data, payloadMetadata) + + return +} diff --git a/packages/binary/valuetransfer/tangle/payloadmetadata/payloadmetadata_test.go b/packages/binary/valuetransfer/tangle/payloadmetadata/payloadmetadata_test.go new file mode 100644 index 00000000..fd85c6f5 --- /dev/null +++ b/packages/binary/valuetransfer/tangle/payloadmetadata/payloadmetadata_test.go @@ -0,0 +1,46 @@ +package payloadmetadata + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/id" +) + +func TestMarshalUnmarshal(t *testing.T) { + originalMetadata := New(id.Genesis) + + clonedMetadata, err, _ := FromBytes(originalMetadata.Bytes()) + if err != nil { + panic(err) + } + + assert.Equal(t, originalMetadata.GetPayloadId(), clonedMetadata.GetPayloadId()) + assert.Equal(t, originalMetadata.IsSolid(), clonedMetadata.IsSolid()) + assert.Equal(t, originalMetadata.GetSoldificationTime().Round(time.Second), clonedMetadata.GetSoldificationTime().Round(time.Second)) + + originalMetadata.SetSolid(true) + + clonedMetadata, err, _ = FromBytes(originalMetadata.Bytes()) + if err != nil { + panic(err) + } + + assert.Equal(t, originalMetadata.GetPayloadId(), clonedMetadata.GetPayloadId()) + assert.Equal(t, originalMetadata.IsSolid(), clonedMetadata.IsSolid()) + assert.Equal(t, originalMetadata.GetSoldificationTime().Round(time.Second), clonedMetadata.GetSoldificationTime().Round(time.Second)) +} + +func TestPayloadMetadata_SetSolid(t *testing.T) { + originalMetadata := New(id.Genesis) + + assert.Equal(t, false, originalMetadata.IsSolid()) + assert.Equal(t, time.Time{}, originalMetadata.GetSoldificationTime()) + + originalMetadata.SetSolid(true) + + assert.Equal(t, true, originalMetadata.IsSolid()) + assert.Equal(t, time.Now().Round(time.Second), originalMetadata.GetSoldificationTime().Round(time.Second)) +} diff --git a/packages/binary/valuetransfer/tangle/tangle.go b/packages/binary/valuetransfer/tangle/tangle.go new file mode 100644 index 00000000..39c97d71 --- /dev/null +++ b/packages/binary/valuetransfer/tangle/tangle.go @@ -0,0 +1,236 @@ +package tangle + +import ( + "container/list" + "time" + + "github.com/dgraph-io/badger/v2" + "github.com/iotaledger/hive.go/async" + "github.com/iotaledger/hive.go/objectstorage" + + "github.com/iotaledger/goshimmer/packages/binary/storageprefix" + valuepayload "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload" + payloadid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/id" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/tangle/missingpayload" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/tangle/payloadapprover" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/tangle/payloadmetadata" +) + +// Tangle represents the value tangle that consists out of value payloads. +// It is an independent ontology, that lives inside the tangle. +type Tangle struct { + storageId []byte + + payloadStorage *objectstorage.ObjectStorage + payloadMetadataStorage *objectstorage.ObjectStorage + approverStorage *objectstorage.ObjectStorage + missingPayloadStorage *objectstorage.ObjectStorage + + Events Events + + storePayloadWorkerPool async.WorkerPool + solidifierWorkerPool async.WorkerPool + cleanupWorkerPool async.WorkerPool +} + +func New(badgerInstance *badger.DB, storageId []byte) (result *Tangle) { + result = &Tangle{ + storageId: storageId, + + payloadStorage: objectstorage.New(badgerInstance, append(storageId, storageprefix.ValueTransferPayload...), valuepayload.FromStorage, objectstorage.CacheTime(time.Second)), + payloadMetadataStorage: objectstorage.New(badgerInstance, append(storageId, storageprefix.ValueTransferPayloadMetadata...), payloadmetadata.FromStorage, objectstorage.CacheTime(time.Second)), + approverStorage: objectstorage.New(badgerInstance, append(storageId, storageprefix.ValueTransferApprover...), payloadapprover.FromStorage, objectstorage.CacheTime(time.Second), objectstorage.PartitionKey(payloadid.Length, payloadid.Length), objectstorage.KeysOnly(true)), + missingPayloadStorage: objectstorage.New(badgerInstance, append(storageId, storageprefix.ValueTransferMissingPayload...), missingpayload.FromStorage, objectstorage.CacheTime(time.Second)), + + Events: *newEvents(), + } + + return +} + +// AttachPayload adds a new payload to the value tangle. +func (tangle *Tangle) AttachPayload(payload *valuepayload.Payload) { + tangle.storePayloadWorkerPool.Submit(func() { tangle.storePayloadWorker(payload) }) +} + +// GetPayload retrieves a payload from the object storage. +func (tangle *Tangle) GetPayload(payloadId payloadid.Id) *valuepayload.CachedObject { + return &valuepayload.CachedObject{CachedObject: tangle.payloadStorage.Load(payloadId.Bytes())} +} + +// GetPayloadMetadata retrieves the metadata of a value payload from the object storage. +func (tangle *Tangle) GetPayloadMetadata(payloadId payloadid.Id) *payloadmetadata.CachedObject { + return &payloadmetadata.CachedObject{CachedObject: tangle.payloadMetadataStorage.Load(payloadId.Bytes())} +} + +// GetApprovers retrieves the approvers of a payload from the object storage. +func (tangle *Tangle) GetApprovers(transactionId payloadid.Id) payloadapprover.CachedObjects { + approvers := make(payloadapprover.CachedObjects, 0) + tangle.approverStorage.ForEach(func(key []byte, cachedObject objectstorage.CachedObject) bool { + approvers = append(approvers, &payloadapprover.CachedObject{CachedObject: cachedObject}) + + return true + }, transactionId[:]) + + return approvers +} + +// Shutdown stops the worker pools and shuts down the object storage instances. +func (tangle *Tangle) Shutdown() *Tangle { + tangle.storePayloadWorkerPool.ShutdownGracefully() + tangle.solidifierWorkerPool.ShutdownGracefully() + tangle.cleanupWorkerPool.ShutdownGracefully() + + tangle.payloadStorage.Shutdown() + tangle.payloadMetadataStorage.Shutdown() + tangle.approverStorage.Shutdown() + tangle.missingPayloadStorage.Shutdown() + + return tangle +} + +// Prune resets the database and deletes all objects (for testing or "node resets"). +func (tangle *Tangle) Prune() error { + for _, storage := range []*objectstorage.ObjectStorage{ + tangle.payloadStorage, + tangle.payloadMetadataStorage, + tangle.approverStorage, + tangle.missingPayloadStorage, + } { + if err := storage.Prune(); err != nil { + return err + } + } + + return nil +} + +// storePayloadWorker is the worker function that stores the payload and calls the corresponding storage events. +func (tangle *Tangle) storePayloadWorker(payload *valuepayload.Payload) { + // store payload + var cachedPayload *valuepayload.CachedObject + if _tmp, transactionIsNew := tangle.payloadStorage.StoreIfAbsent(payload); !transactionIsNew { + return + } else { + cachedPayload = &valuepayload.CachedObject{CachedObject: _tmp} + } + + // store payload metadata + payloadId := payload.GetId() + cachedMetadata := &payloadmetadata.CachedObject{CachedObject: tangle.payloadMetadataStorage.Store(payloadmetadata.New(payloadId))} + + // store trunk approver + trunkId := payload.GetTrunkId() + tangle.approverStorage.Store(payloadapprover.New(trunkId, payloadId)).Release() + + // store branch approver + if branchId := payload.GetBranchId(); branchId != trunkId { + tangle.approverStorage.Store(payloadapprover.New(branchId, trunkId)).Release() + } + + // trigger events + if tangle.missingPayloadStorage.DeleteIfPresent(payloadId.Bytes()) { + tangle.Events.MissingPayloadReceived.Trigger(cachedPayload, cachedMetadata) + } + tangle.Events.PayloadAttached.Trigger(cachedPayload, cachedMetadata) + + // check solidity + tangle.solidifierWorkerPool.Submit(func() { + tangle.solidifyTransactionWorker(cachedPayload, cachedMetadata) + }) +} + +// solidifyTransactionWorker is the worker function that solidifies the payloads (recursively from past to present). +func (tangle *Tangle) solidifyTransactionWorker(cachedPayload *valuepayload.CachedObject, cachedMetadata *payloadmetadata.CachedObject) { + popElementsFromStack := func(stack *list.List) (*valuepayload.CachedObject, *payloadmetadata.CachedObject) { + currentSolidificationEntry := stack.Front() + currentCachedPayload := currentSolidificationEntry.Value.([2]interface{})[0] + currentCachedMetadata := currentSolidificationEntry.Value.([2]interface{})[1] + stack.Remove(currentSolidificationEntry) + + return currentCachedPayload.(*valuepayload.CachedObject), currentCachedMetadata.(*payloadmetadata.CachedObject) + } + + // initialize the stack + solidificationStack := list.New() + solidificationStack.PushBack([2]interface{}{cachedPayload, cachedMetadata}) + + // process payloads that are supposed to be checked for solidity recursively + for solidificationStack.Len() > 0 { + currentCachedPayload, currentCachedMetadata := popElementsFromStack(solidificationStack) + + currentPayload := currentCachedPayload.Unwrap() + currentMetadata := currentCachedMetadata.Unwrap() + if currentPayload == nil || currentMetadata == nil { + currentCachedPayload.Release() + currentCachedMetadata.Release() + + continue + } + + // if current transaction is solid and was not marked as solid before: mark as solid and propagate + if tangle.isPayloadSolid(currentPayload, currentMetadata) && currentMetadata.SetSolid(true) { + tangle.Events.PayloadSolid.Trigger(currentCachedPayload, currentCachedMetadata) + + tangle.GetApprovers(currentPayload.GetId()).Consume(func(approver *payloadapprover.PayloadApprover) { + approverTransactionId := approver.GetApprovingPayloadId() + + solidificationStack.PushBack([2]interface{}{ + tangle.GetPayload(approverTransactionId), + tangle.GetPayloadMetadata(approverTransactionId), + }) + }) + } + + // release cached results + currentCachedPayload.Release() + currentCachedMetadata.Release() + } +} + +// isPayloadSolid returns true if the given payload is solid. A payload is considered to be solid solid, if it is either +// already marked as solid or if its referenced payloads are marked as solid. +func (tangle *Tangle) isPayloadSolid(payload *valuepayload.Payload, metadata *payloadmetadata.PayloadMetadata) bool { + if payload == nil || payload.IsDeleted() { + return false + } + + if metadata == nil || metadata.IsDeleted() { + return false + } + + if metadata.IsSolid() { + return true + } + + return tangle.isPayloadMarkedAsSolid(payload.GetTrunkId()) && tangle.isPayloadMarkedAsSolid(payload.GetBranchId()) +} + +// isPayloadMarkedAsSolid returns true if the payload was marked as solid already (by setting the corresponding flags +// in its metadata. +func (tangle *Tangle) isPayloadMarkedAsSolid(payloadId payloadid.Id) bool { + if payloadId == payloadid.Genesis { + return true + } + + transactionMetadataCached := tangle.GetPayloadMetadata(payloadId) + if transactionMetadata := transactionMetadataCached.Unwrap(); transactionMetadata == nil { + transactionMetadataCached.Release() + + // if transaction is missing and was not reported as missing, yet + if cachedMissingPayload, missingPayloadStored := tangle.missingPayloadStorage.StoreIfAbsent(missingpayload.New(payloadId)); missingPayloadStored { + cachedMissingPayload.Consume(func(object objectstorage.StorableObject) { + tangle.Events.PayloadMissing.Trigger(object.(*missingpayload.MissingPayload).GetId()) + }) + } + + return false + } else if !transactionMetadata.IsSolid() { + transactionMetadataCached.Release() + + return false + } + transactionMetadataCached.Release() + + return true +} diff --git a/packages/binary/valuetransfer/tangle/tangle_test.go b/packages/binary/valuetransfer/tangle/tangle_test.go new file mode 100644 index 00000000..b271eaa3 --- /dev/null +++ b/packages/binary/valuetransfer/tangle/tangle_test.go @@ -0,0 +1,66 @@ +package tangle + +import ( + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/iotaledger/hive.go/events" + "github.com/stretchr/testify/require" + + "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/coloredbalance" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/coloredbalance/color" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/id" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer" + transferid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/id" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/inputs" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/outputs" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/tangle/payloadmetadata" + transferoutputid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transferoutput/id" + "github.com/iotaledger/goshimmer/packages/database" + "github.com/iotaledger/goshimmer/plugins/config" +) + +func TestTangle_AttachPayload(t *testing.T) { + dir, err := ioutil.TempDir("", t.Name()) + require.NoError(t, err) + defer os.Remove(dir) + + config.Node.Set(database.CFG_DIRECTORY, dir) + + tangle := New(database.GetBadgerInstance(), []byte("TEST_BINARY_TANGLE")) + if err := tangle.Prune(); err != nil { + t.Error(err) + + return + } + + tangle.Events.PayloadSolid.Attach(events.NewClosure(func(payload *payload.CachedObject, metadata *payloadmetadata.CachedObject) { + fmt.Println(payload.Unwrap()) + + payload.Release() + metadata.Release() + })) + + addressKeyPair1 := ed25119.GenerateKeyPair() + addressKeyPair2 := ed25119.GenerateKeyPair() + + tangle.AttachPayload(payload.New(id.Genesis, id.Genesis, transfer.New( + inputs.New( + transferoutputid.New(address.FromED25519PubKey(addressKeyPair1.PublicKey), transferid.New([]byte("transfer1"))), + transferoutputid.New(address.FromED25519PubKey(addressKeyPair2.PublicKey), transferid.New([]byte("transfer2"))), + ), + + outputs.New(map[address.Address][]*coloredbalance.ColoredBalance{ + address.New([]byte("output_address")): { + coloredbalance.New(color.IOTA, 1337), + }, + }), + ))) + + tangle.Shutdown() +} diff --git a/packages/binary/valuetransfers/test/payload_test.go b/packages/binary/valuetransfer/test/payload_test.go similarity index 78% rename from packages/binary/valuetransfers/test/payload_test.go rename to packages/binary/valuetransfer/test/payload_test.go index 6a91a628..11b8e145 100644 --- a/packages/binary/valuetransfers/test/payload_test.go +++ b/packages/binary/valuetransfer/test/payload_test.go @@ -6,20 +6,19 @@ import ( "github.com/stretchr/testify/assert" - "github.com/iotaledger/goshimmer/packages/binary/identity" "github.com/iotaledger/goshimmer/packages/binary/signature/ed25119" "github.com/iotaledger/goshimmer/packages/binary/tangle/model/transaction" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/address" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/coloredbalance" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/coloredbalance/color" - valuepayload "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload" - payloadid "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/id" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer" - transferid "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer/id" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer/inputs" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer/outputs" - "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer/signatures" - transferoutputid "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/transferoutput/id" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/coloredbalance" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/coloredbalance/color" + valuepayload "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload" + payloadid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/id" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer" + transferid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/id" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/inputs" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/outputs" + "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/signatures" + transferoutputid "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/transferoutput/id" ) func ExamplePayload() { @@ -42,10 +41,10 @@ func ExamplePayload() { // 2. create value payload (the ontology creates this and wraps the user provided transfer accordingly) valuePayload := valuepayload.New( // trunk in "value transfer ontology" (filled by ontology tipSelector) - payloadid.Empty, + payloadid.Genesis, // branch in "value transfer ontology" (filled by ontology tipSelector) - payloadid.Empty, + payloadid.Genesis, // value transfer valueTransfer, @@ -60,7 +59,7 @@ func ExamplePayload() { transaction.EmptyId, // issuer of the transaction (signs automatically) - identity.Generate(), + ed25119.GenerateKeyPair(), // payload valuePayload, @@ -74,8 +73,8 @@ func TestPayload(t *testing.T) { addressKeyPair2 := ed25119.GenerateKeyPair() originalPayload := valuepayload.New( - payloadid.Empty, - payloadid.Empty, + payloadid.Genesis, + payloadid.Genesis, transfer.New( inputs.New( transferoutputid.New(address.FromED25519PubKey(addressKeyPair1.PublicKey), transferid.New([]byte("transfer1"))), diff --git a/packages/binary/valuetransfers/transferoutput/id/id.go b/packages/binary/valuetransfer/transferoutput/id/id.go similarity index 95% rename from packages/binary/valuetransfers/transferoutput/id/id.go rename to packages/binary/valuetransfer/transferoutput/id/id.go index 0d2c7e95..b43b9491 100644 --- a/packages/binary/valuetransfers/transferoutput/id/id.go +++ b/packages/binary/valuetransfer/transferoutput/id/id.go @@ -3,8 +3,8 @@ package id import ( "github.com/mr-tron/base58" - address2 "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/address" - id2 "github.com/iotaledger/goshimmer/packages/binary/valuetransfers/payload/transfer/id" + address2 "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/address" + id2 "github.com/iotaledger/goshimmer/packages/binary/valuetransfer/payload/transfer/id" ) type Id [Length]byte diff --git a/packages/binary/valuetransfers/payload/transfer/id/id.go b/packages/binary/valuetransfers/payload/transfer/id/id.go deleted file mode 100644 index 3f25a879..00000000 --- a/packages/binary/valuetransfers/payload/transfer/id/id.go +++ /dev/null @@ -1,19 +0,0 @@ -package id - -import ( - "github.com/mr-tron/base58" -) - -type Id [Length]byte - -func New(idBytes []byte) (result Id) { - copy(result[:], idBytes) - - return -} - -func (id Id) String() string { - return base58.Encode(id[:]) -} - -const Length = 32 diff --git a/plugins/gossip/gossip.go b/plugins/gossip/gossip.go index 4995280f..7eaf015f 100644 --- a/plugins/gossip/gossip.go +++ b/plugins/gossip/gossip.go @@ -80,7 +80,7 @@ func getTransaction(transactionId transaction.Id) (bytes []byte, err error) { log.Debugw("get tx from db", "id", transactionId.String()) if !tangle.Instance.GetTransaction(transactionId).Consume(func(transaction *transaction.Transaction) { - bytes = transaction.GetBytes() + bytes = transaction.Bytes() }) { err = fmt.Errorf("transaction not found: hash=%s", transactionId) } diff --git a/plugins/gossip/plugin.go b/plugins/gossip/plugin.go index dcf615e8..3d917c98 100644 --- a/plugins/gossip/plugin.go +++ b/plugins/gossip/plugin.go @@ -81,7 +81,7 @@ func configureEvents() { transactionMetadata.Release() cachedTransaction.Consume(func(transaction *transaction.Transaction) { - mgr.SendTransaction(transaction.GetBytes()) + mgr.SendTransaction(transaction.Bytes()) }) })) -- GitLab